using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using TapSDK.UI;
using TapSDK.Core;
using TapSDK.Login;
using TapSDK.Login.Internal;
using TapSDK.Login.Standalone;
using TapSDK.Compliance.Internal;
using TapSDK.Compliance.Model;
using TapSDK.Core.Internal;
using UnityEngine;
using System.Linq;
using TapSDK.Compliance.Standalone.Internal;
using TapSDK.Core.Standalone.Internal.Http;
using TapSDK.Core.Internal.Log;
namespace TapSDK.Compliance
{
public sealed class ComplianceWorker : BaseComplianceWorker
{
#region Abstract Override
private TaptapComplianceIDInputController idInputPanel;
private static bool showPopupInVerification;
internal const string SCOPE_COMPLIANCE = "compliance";
internal const string SCOPE_COMPLIANCE_BASIC = "compliance_basic";
///
/// 激活手动认证
///
protected override async Task VerifyManuallyAsync()
{
do
{
try
{
// 这里处理了拿 Verification 信息时,5xx服务器错误或者网络错误的情况,策略为不停的弹出重试弹窗
var result = await OpenVerificationPanelCn();
// await Verification.Save(userId, Region.China, result);
if (Verification.IsVerifyFailed) {
// TODO@luran:本地化
UIManager.Instance.OpenToast("认证未通过,请重新提交实名信息", UIManager.GeneralToastLevel.Error);
continue;
}
break;
}
catch (TaskCanceledException)
{
TapLog.Log(string.Format("[TapTap: ChinaCompliance] Close manual verification panel."));
throw;
}
catch (Exception e)
{
TapLog.Error(string.Format("[TapTap: ChinaCompliance] manual verification exception! {0}", e.ToString()));
if (e is HttpRequestException || e is WebException)
{
UIManager.Instance.OpenToast(TapTapComplianceManager.LocalizationItems.Current.NetError, UIManager.GeneralToastLevel.Error);
continue;
}
if (e is ComplianceException aae)
{
if (aae.Description != null && aae.Description.Length > 0)
{
UIManager.Instance.OpenToast(aae.message, UIManager.GeneralToastLevel.Error);
continue;
}
if (aae.Code >= (int)HttpStatusCode.InternalServerError)
{
// TODO@luran:本地化
UIManager.Instance.OpenToast("请求出错", UIManager.GeneralToastLevel.Error);
continue;
}
}
if (e.Message.Contains("Interval server error."))
{
// TODO@luran:本地化
UIManager.Instance.OpenToast("请求出错", UIManager.GeneralToastLevel.Error);
continue;
}
UIManager.Instance.OpenToast(TapTapComplianceManager.LocalizationItems.Current.NoVerification, UIManager.GeneralToastLevel.Error);
await Task.Yield();
}
}
while (true);
}
protected override PlayableResult CheckOfflineMinorPlayable()
{
// 未成年人
if (IsGameTime())
{
// 可玩:节假日并且是游戏时间
HealthReminderDesc playableTip = Config.GetMinorPlayableHealthReminderTip();
// 计算时间
DateTimeOffset gameEndTime = Config.StrictEndTime;
DateTimeOffset now = DateTimeOffset.FromUnixTimeSeconds(GetCurrentTime()).ToOffset(DateTimeOffset.Now.Offset);
TimeSpan remain = gameEndTime.TimeOfDay - now.TimeOfDay;
return new PlayableResult
{
Title = playableTip?.tipTitle,
Content = playableTip?.tipDescription,
RemainTime = Convert.ToInt32(remain.TotalSeconds)
};
}
// 不可玩:未成年人不在可玩时间
HealthReminderDesc unplayableTip = Config.GetMinorUnplayableHealthReminderTip();
return new PlayableResult
{
Title = unplayableTip?.tipTitle,
Content = unplayableTip?.tipDescription,
RemainTime = 0
};
}
protected override async Task InternalStartup(string userId) {
try {
UIManager.Instance.CloseLoading();
return await GetVerificationResult(userId);
}
catch (Exception e) {
TapLog.Error("[TapTap: ChinaCompliance] " + e.ToString());
return StartUpResult.INVALID_CLIENT_OR_NETWORK_ERROR;
}
}
#endregion
private static bool HaveComplianceScope(AccessToken token) {
if (token == null ) return false;
bool useAgeRange = TapTapComplianceManager.config.useAgeRange;
if(useAgeRange){
return token.scopeSet != null && token.scopeSet.Contains(SCOPE_COMPLIANCE);
}else{
return token.scopeSet != null &&
(token.scopeSet.Contains(SCOPE_COMPLIANCE) || token.scopeSet.Contains(SCOPE_COMPLIANCE_BASIC));
}
}
// private static async Task GetComplianceCode(string clientId, AccessToken token) {
// string url = TapTapSdk.CurrentRegion.ApiHost() + "/account/compliance/v1?client_id=" + clientId;
// var uri = new Uri(url);
// var sign = GetMacToken(token, uri);
// var headers = new Dictionary {
// { "Authorization", sign }
// };
// ComplianceCodeResponse response = await LoginService.HttpClient.Get(url, headers: headers);
// return response.Data;
// }
// private static string GetMacToken(AccessToken token, Uri uri, int timestamp = 0) {
// var ts = timestamp;
// if (ts == 0) {
// var dt = DateTime.UtcNow - new DateTime(1970, 1, 1);
// ts = (int)dt.TotalSeconds;
// }
// var sign = "MAC " + LoginService.GetAuthorizationHeader(token.kid,
// token.macKey,
// token.macAlgorithm,
// "GET",
// uri.PathAndQuery,
// uri.Host,
// "443", ts);
// return sign;
// }
///
/// 使用 TapToken 获取实名信息
///
///
///
/// 0-正常;1-走手动;-1-打断流程
private static async Task FetchByTapToken(string userId, AccessToken accessToken) {
UIManager.Instance.OpenLoading();
var tcs = new TaskCompletionSource();
// get verification
try {
VerificationResult result = await Verification.FetchVerificationByTapToken(userId,accessToken);
UIManager.Instance.CloseLoading();
if(result.Status != null && !result.IsVerifyFailed){
tcs.TrySetResult(0);
}else{
tcs.TrySetResult(-1);
}
}
catch (Exception e) {
TapLog.Error(e.ToString());
UIManager.Instance.CloseLoading();
var aae = e as TapException;
if (aae != null && aae.code >= (int)HttpStatusCode.InternalServerError && aae.code < 600) {
tcs.TrySetResult(1);
TapLog.Warning($"[Compliance] 通过 code 拿去实名信息网络错误,将打断认证流程");
}
else {
TapLog.Warning($"[Compliance] 通过 code 拿去实名信息失败,将启动手动认证");
tcs.TrySetResult(1);
}
}
UIManager.Instance.CloseLoading();
return tcs.Task.Result;
}
///
/// 是否在允许游戏时间段
///
private bool IsGameTime()
{
DateTimeOffset now = DateTimeOffset.FromUnixTimeSeconds(GetCurrentTime()).ToOffset(DateTimeOffset.Now.Offset);
string currentDate = now.ToString( "yyyy-MM-dd" );
List holidays = TapTapComplianceManager.CurrentUserAntiResult.localConfig.timeRangeConfig.holidays;
TapLog.Log("current date = " + currentDate + " holidays = " + holidays.ToArray());
if(holidays.Contains(currentDate)){
DateTimeOffset strictStart = Config.StrictStartTime;
DateTimeOffset strictEnd = Config.StrictEndTime;
bool playable;
TapLog.Log(" now = " + now.TimeOfDay + " ,start = " + strictStart.TimeOfDay + ", end = " + strictEnd.TimeOfDay);
playable = now.TimeOfDay >= strictStart.TimeOfDay && now.TimeOfDay < strictEnd.TimeOfDay;
TapLog.Log("check result = " + playable);
return playable;
}
return false;
}
private long GetCurrentTime(){
long serverTime = TapHttpTime.GetCurrentServerTime();
TapLog.Log("current serverTime = " + serverTime + " localTime = " + GetCurrentLocalTime());
if ( serverTime > 0){
return serverTime;
}
return GetCurrentLocalTime();
}
///
/// 打开中国实名制窗口
///
///
private Task OpenVerificationPanelCn()
{
var tcs = new TaskCompletionSource();
var path = ComplianceConst.GetPrefabPath(ComplianceConst.ID_NUMBER_INPUT_PANEL_NAME,
false);
idInputPanel =
UIManager.Instance.OpenUI(path);
idInputPanel.activeManualVerification = true;;
if (idInputPanel != null)
{
idInputPanel.OnVerified = (verification) => tcs.TrySetResult(verification);
idInputPanel.OnException = (e) =>
{
if (e is HttpRequestException || e is WebException)
{
tcs.TrySetException(e);
}
else
{
if (e is ComplianceException aae)
{
tcs.TrySetException(e);
}
else
{
if (e.Message.Contains("Interval server error."))
tcs.TrySetException(e);
else {
UIManager.Instance.OpenToast("身份证号码错误", UIManager.GeneralToastLevel.Error);
}
}
}
};
idInputPanel.OnClosed = () => tcs.TrySetCanceled();
}
return tcs.Task;
}
private async Task GetVerificationResult(string userId) {
var tcs = new TaskCompletionSource();
int mannualVerify = 0;
TapTapAccount tapAccount = await TapTapLogin.Instance.GetCurrentTapAccount();
AccessToken accessToken = tapAccount?.accessToken;
bool isTapUser = accessToken != null;
TapLog.Log(" Verification current = " + Verification.Current );
//0 不使用手动验证;1 主动使用手动认证; 2 保底触发(被动)使用手动验证
try {
accessToken = await GetAccessToken(accessToken);
if (accessToken != null) {
if (HaveComplianceScope(accessToken)) {
// 0-正常;1-异常;-1-实名失败
var fetchResult = await FetchByTapToken(userId, accessToken);
if (fetchResult != 0 ) {
/// 异常问题
if (fetchResult == 1)
{
UIManager.Instance.OpenToast("授权异常", UIManager.GeneralToastLevel.Error);
}
mannualVerify = 2;
}
}
else {
//TODO@luran:本地化
UIManager.Instance.OpenToast("授权错误", UIManager.GeneralToastLevel.Error);
mannualVerify = 2;
}
}
else {
TapLog.Log("[TapTap: ChinaCompliance] 不能登录Access Token!直接降级为手动认证");
mannualVerify = 2;
}
}
catch (AggregateException aggregateException) {
var cancelException = aggregateException.InnerException as TaskCanceledException;
if (cancelException != null) {
TapLog.Log("[TapTap: ChinaCompliance] 获得登录Access Token 中主动退出! 触发 AggregateException.TaskCanceledException");
tcs.TrySetResult(StartUpResult.REAL_NAME_STOP);
return tcs.Task.Result;
}
}
catch (TaskCanceledException) {
TapLog.Log("[TapTap: ChinaCompliance] 获得登录Access Token 中主动退出! 触发 TaskCanceledException");
tcs.TrySetResult(StartUpResult.REAL_NAME_STOP);
return tcs.Task.Result;
}
catch (ComplianceException aae) {
TapLog.Log(
$"[TapTap: ChinaCompliance] 获取快速实名制信息出错! code: {aae.Code} error: {aae.Error} errorMsg: {aae.Description}");
//TapToken过期->重新授权实名流程
if (aae.message.ToLower().Contains("refuse quick verify")) {
mannualVerify = 1;
}
else {
mannualVerify = 2;
}
}
catch (Exception e) {
TapLog.Log(string.Format("[TapTap: ChinaCompliance] 获得登录Access Token 碰到错误,降级为手动验证! 错误信息: {0}", e.ToString()));
mannualVerify = 2;
}
// 手动认证
if (mannualVerify != 0) {
try {
await VerifyManuallyAsync();
}
catch (TaskCanceledException) {
TapLog.Log("[TapTap: ChinaCompliance] 手动实名制过程中主动退出! 触发 TaskCanceledException");
tcs.TrySetResult(StartUpResult.REAL_NAME_STOP);
return tcs.Task.Result;
}
}
return Verification.IsVerified ? 0 : await ShowVerifingTip();
}
///
/// 获取 Token,分为几种情况:1)不是TapUser的话,需要去展示包含手动的授权界面;2)是TapUser,这时需要去展示仅包含 Tap 的授权界面;
///
///
///
private static async Task GetAccessToken(AccessToken accessToken) {
showPopupInVerification = false;
bool haveCompliance = false;
var isTapUser = IsTapUser(accessToken);
if (isTapUser) {
haveCompliance = HaveComplianceScope(accessToken);
}
TapLog.Log($"[TapTap: ChinaCompliance] 启动快速实名制. 是否为Tap用户: {isTapUser} 是否包含Complaince: {haveCompliance}");
return await InternalGetAccessToken(isTapUser, haveCompliance, accessToken);
}
private static async Task InternalGetAccessToken(bool isTapUser, bool haveCompliance, AccessToken accessToken) {
var tcs = new TaskCompletionSource();
Func> getToken = null;
// 先弹出快速认证提示,再弹出授权页面
showPopupInVerification = true;
bool useAgeRange = TapTapComplianceManager.config.useAgeRange;
string permission = useAgeRange ? SCOPE_COMPLIANCE : SCOPE_COMPLIANCE_BASIC;
bool justShowConfirmBtn = isTapUser && !haveCompliance|| !Config.Current.useManual;
var goQuickVerify = await ShowQuickVerifyTipWindow(justShowConfirmBtn);
TapLog.Log($"[TapTap: ChinaCompliance] 因为不是Tap用户启动快速认证提示弹窗!弹窗结果: {goQuickVerify}");
// 同意授权
if (goQuickVerify) {
getToken = () =>
{
//https://xindong.slack.com/archives/C0271SPG77A/p1701161783480859?thread_ts=1700206625.065689&cid=C0271SPG77A
// TapLogin.Authorize(new string[] { permission});
IAuthorizationProvider provider = PlatformTypeUtils.CreatePlatformImplementationObject(typeof(IAuthorizationProvider),
"TapSDK.Login") as IAuthorizationProvider;
return provider?.Authorize(new string[] { permission});
};
}
//不同意授权
else {
TapLog.Log("[TapTap: ChinaCompliance] 用户拒绝快速认证!");
}
// 不同意快速认证: 需要降级为手动
if (getToken == null) {
var aae = new ComplianceException(-1, "refuse quick verify");
throw aae;
}
else {
try {
var tToken = await getToken();
tcs.TrySetResult(tToken);
}
catch (Exception e) {
TapLog.Error(e.ToString());
throw;
}
}
return tcs.Task.Result;
}
///
/// 显示快速认证提示弹窗
///
/// 是否只包括确认快速认证按钮,如果否的话,还包含不启动快速认证按钮
/// 返回 bool 里表示玩家是否选择快速认证
private static Task ShowQuickVerifyTipWindow(bool justShowConfirmBtn) {
var tcs = new TaskCompletionSource();
var openParams = new TapTapComplianceQuickVerifyTipController.OpenParams()
{
justShowConfirmBtn = justShowConfirmBtn,
onClicked = (isQuickVerify) => tcs.TrySetResult(isQuickVerify),
onClose = ()=> tcs.TrySetCanceled(),
};
var path = ComplianceConst.GetPrefabPath(ComplianceConst.QUICK_VERIFY_TIP_PANEL_NAME,
false);
UIManager.Instance
.OpenUI(path, openParams);
return tcs.Task;
}
private static bool IsTapUser(AccessToken accessToken) {
return accessToken != null && !string.IsNullOrEmpty(accessToken.kid);
}
}
}