using System; using System.IO; using System.Net; using System.Net.Http; using System.Threading.Tasks; using TapSDK.UI; using TapSDK.Compliance.Internal; using TapSDK.Compliance.Model; using TapSDK.Core; using TapSDK.Core.Internal.Utils; using TapSDK.Login; using UnityEngine; using Network = TapSDK.Compliance.Internal.Network; using Random = System.Random; using System.Text; using TapSDK.Core.Internal.Log; namespace TapSDK.Compliance { public abstract class BaseComplianceWorker { protected TapTapComplianceOption config => TapTapComplianceManager.ComplianceConfig; #region Abstract static readonly string USER_ANTI_FILENAME = "user_anti_config"; // 本地基准时间戳 private long BaseLocalTime = 0; //获取本地时间戳时,应用运行的时间 private long BaseLocalStartUpTime = 0; /// /// 激活手动认证 /// protected abstract Task VerifyManuallyAsync(); /// /// 检查未成年人可玩性 /// /// protected abstract PlayableResult CheckOfflineMinorPlayable(); /// /// 直接通过 Tap 账号获取实名制信息 /// /// protected abstract Task InternalStartup(string userId); #endregion #region Virutal /// /// 检查可玩性后,已知成年人时的处理 /// /// /// protected virtual void OnCheckedPlayableWithAdult(PlayableResult playable) { TryStartPoll(); } /// /// 检查可玩性后,已知未成年人时的处理 /// /// /// protected virtual async Task OnCheckedPlayableWithMinorAsync(PlayableResult playable) { if (playable.RemainTime > 0) { TapTapComplianceManager.ShowAntiAddictionTip(); } var tcs = new TaskCompletionSource(); Action onOk; if (playable.RemainTime > 0) { onOk = () => { tcs.TrySetResult(StartUpResult.LOGIN_SUCCESS); TryStartPoll(); }; } else { tcs.TrySetResult(StartUpResult.PERIOD_RESTRICT); onOk = () => { Application.Quit(); }; } Action onSwitchAccount = null; if (config.showSwitchAccount && playable.RemainTime <= 0) { onSwitchAccount = () => { Logout(false); TapTapCompliance.OnInvokeExternalCallback(StartUpResult.SWITCH_ACCOUNT, null); }; } TapComplianceUI.OpenHealthReminderPanel(playable, onOk, onSwitchAccount); return await tcs.Task; } /// /// 检查可玩性,如果出现异常就会用本地的计算方式 /// /// protected virtual async Task CheckPlayableAsyncWithFallback() { try { var playableResult = await CheckPlayableAsync(); return playableResult; } catch (ComplianceException e) { if (e.IsTokenExpired() || e.Code < 500 || !TapTapComplianceManager.CurrentUserAntiResult.policy.active.Equals(ComplianceConst.POLICY_ACTIVE_TIME_RANGE)) { throw; } return CheckOfflinePlayable(); } catch (Exception e) { TapLog.Error(e); // 单机判断是否可玩 return CheckOfflinePlayable(); } } /// /// 获取用户配置,失败时,会尝试获取本地配置,如果都无效,抛出异常 /// /// protected virtual async Task CheckUserConfigAsyncWithFallback() { string filename = Tool.EncryptString(Verification.Current.UserId); Persistence persistence = new Persistence(Path.Combine( USER_ANTI_FILENAME, filename)); UserComplianceConfigResult result = await persistence.Load(); try { UserComplianceConfigResult serverResult = await Network.CheckUserConfig(); if(serverResult != null){ await persistence.Save(serverResult); TapTapComplianceManager.CurrentSession = GenerateSession(); } return serverResult; } catch (ComplianceException e) { if (e.IsTokenExpired() || e.Code < 500) { throw; }else{ if(result != null){ ResetBaseLocalTime(); TapTapComplianceManager.CurrentSession = GenerateSession(); return result; }else{ throw; } } } catch (Exception e) { TapLog.Error(e.Message ?? ""); if(result == null){ throw; }else{ ResetBaseLocalTime(); TapTapComplianceManager.CurrentSession = GenerateSession(); return result; } } } private void ResetBaseLocalTime(){ if(BaseLocalTime > 0){ return; } BaseLocalTime = DateTimeOffset.Now.ToUnixTimeSeconds(); BaseLocalStartUpTime = (long)Time.realtimeSinceStartup; TapLog.Log(" current local time " + DateTimeOffset.Now); } /// /// 获取基于本地时间的当前时间戳,用于离线心跳计算 /// /// protected long GetCurrentLocalTime(){ long currentStartUpTime = (long) Time.realtimeSinceStartup; return BaseLocalTime + currentStartUpTime - BaseLocalStartUpTime; } private string GenerateSession() { const string baseStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var random = new Random(); var builder = new StringBuilder(); int l = baseStr.Length; for (var i = 0; i < 32; i++) { var number = random.Next(l); builder.Append(baseStr[number]); } return builder.ToString(); } /// /// 检查可玩性 /// /// 当内部发生错误的时候,是否抛出异常.默认不抛出异常,就会按照本地规则计算playable /// protected virtual async Task CheckPlayableAsync() { try { var playableResult = await Network.CheckPlayable(); return playableResult; } catch (Exception e) { TapLog.Error(e.Message ?? ""); throw; } } /// /// 本地判断可玩性 /// /// protected virtual PlayableResult CheckOfflinePlayable() { TapLog.Log("CheckOfflinePlayable"); // 成年人 if (Verification.IsAdult) { // 可玩 return new PlayableResult { RemainTime = 9999 }; } return CheckOfflineMinorPlayable(); } /// /// 是否需要开启轮询检查可玩性 /// /// protected virtual bool IsNeedStartPoll() { return !Verification.IsAdult || Config.NeedUploadUserAction; } /// /// 轮询时,检查可玩性判断 /// /// public virtual async Task CheckPlayableOnPollingAsync() { try { var playable = await CheckPlayableAsyncWithFallback(); if (playable.RemainTime <= 0) { OnUnplayablePostPoll(playable); } return playable; } catch (Exception e) { if (e is ComplianceException aae && aae.IsTokenExpired()) { Logout(); TapTapCompliance.OnInvokeExternalCallback(StartUpResult.EXITED, null); return new PlayableResult { RemainTime = 0 }; } else { CompliancePoll.Logout(); TapTapCompliance.OnInvokeExternalCallback(StartUpResult.INVALID_CLIENT_OR_NETWORK_ERROR, null); return new PlayableResult { RemainTime = 0 }; } } } /// /// 轮询时,发现不可玩处理 /// /// protected virtual void OnUnplayablePostPoll(PlayableResult playable) { Action onExitGame = Application.Quit; Action onSwitch = null; if (config.showSwitchAccount && playable.RemainTime <= 0) { onSwitch = () => { Logout(false); TapTapCompliance.OnInvokeExternalCallback(StartUpResult.SWITCH_ACCOUNT, null); }; } TapTapCompliance.OnInvokeExternalCallback(StartUpResult.PERIOD_RESTRICT, null); TapComplianceUI.OpenHealthReminderPanel(playable, onExitGame, onSwitch); } /// /// 通过检查,发现不可支付时的处理 /// /// protected virtual void OnCheckedUnpayable(PayableResult payable) { //当服务端返回提示文案无效时,不显示 if(payable.Title != null && payable.Title.Length > 0 && payable.Content != null && payable.Content.Length > 0){ TapComplianceUI.OpenHealthPaymentPanel(payable); } } /// /// 登出处理 /// public virtual void Logout(bool needClearCache = true) { if(needClearCache){ string filename = Tool.EncryptString(Verification.Current.UserId); Persistence persistence = new Persistence(Path.Combine( USER_ANTI_FILENAME, filename)); persistence.Delete(); } Verification.Logout(needClearCache); CompliancePoll.Logout(); TapTapComplianceManager.UserId = null; } #endregion #region Internal public async Task StartUp(string userId) { await FetchVerificationAsync(userId); if (Verification.Current == null || Verification.IsVerifyFailed) { TapLog.Log("try get token internal by UI"); var result = await InternalStartup(userId); // 目前只会返回 9002 和 0-认证成功 // 9002 是 StartUpResult.REAL_NAME_STOP if (result != 0) { return result; } } else if (Verification.IsVerifing) { return await ShowVerifingTip(); } else { UIManager.Instance.CloseLoading(); } return await OnVerificationFetched(); } /// /// 显示实名中提示 /// internal async Task ShowVerifingTip() { var task = new TaskCompletionSource(); var tip = Config.GetInputIdentifyBlockingTip(); Action onOk = () => { TapLog.Log("[TapTap: ChinaCompliance] 认证中,实名取消!"); task.TrySetResult(StartUpResult.REAL_NAME_STOP); }; TapComplianceUI.OpenHealthPaymentPanel(tip.Title, tip.Content, tip.PositiveButtonText, onOk); return await task.Task; } /// /// 获得实名信息 /// public async Task FetchVerificationAsync(string userId) { // 拉取服务端实名信息 try { await Verification.Fetch(userId); UIManager.Instance.CloseLoading(); } catch (Exception e) { TapLog.Error(e.Message ?? ""); UIManager.Instance.CloseLoading(); //所有错误跳过,执行实名 } } /// /// 获得实名信息后的处理 /// /// public async Task OnVerificationFetched() { return await ValidateUserConfigAsync(); } private async Task ValidateUserConfigAsync(){ do { try{ UserComplianceConfigResult result = await CheckUserConfigAsyncWithFallback(); TapTapComplianceManager.CurrentUserAntiResult = result; await Verification.setAgeState(result.userState.ageLimit, result.userState.isAdult); // 适龄限制判断 if (!result.ageCheckResult.allow) { return StartUpResult.AGE_LIMIT; } // 检查时长 else{ return await ValidatePlayableAsync(); } } catch (Exception e){ if (e is ComplianceException aae && aae.IsTokenExpired()) { //用户登出 Logout(); return StartUpResult.EXITED; } else { return StartUpResult.INVALID_CLIENT_OR_NETWORK_ERROR; } } }while (true); } /// /// 检查可玩性 /// /// private async Task ValidatePlayableAsync() { int tryCount = 0; do { try{ tryCount++; PlayableResult playable = await CheckPlayableAsyncWithFallback(); TapTapComplianceManager.CurrentPlayableResult = playable; // 2.1 成年人-后处理 if (Verification.Current.CheckIsAdult) { OnCheckedPlayableWithAdult(playable); return StartUpResult.LOGIN_SUCCESS; } // 2.2 未成年人-后处理 else{ return await OnCheckedPlayableWithMinorAsync(playable); } } catch (Exception e){ TapLog.Log("ValidatePlayableAsync ERROR = " + e.Message + " stack = " + e.StackTrace); if (e is ComplianceException aae && aae.IsTokenExpired()) { //用户登出 Logout(); return StartUpResult.EXITED; }else{ return StartUpResult.INVALID_CLIENT_OR_NETWORK_ERROR; } } }while (true); } /// /// 尝试开启轮询检查 /// protected void TryStartPoll() { TapLog.Log("TryStartPoll "); if (IsNeedStartPoll()) { TapLog.Log("TryStartPoll interval = " + TapTapComplianceManager.CurrentUserAntiResult.policy.heartbeatInterval); CompliancePoll.StartUp(TapTapComplianceManager.CurrentUserAntiResult.policy.heartbeatInterval); } } /// /// 检查是否可以支付 /// /// /// public async virtual Task CheckPayableAsync(long amount) { PayableResult payable = await Network.CheckPayable(amount); if (!payable.Status) { OnCheckedUnpayable(payable); } return payable; } /// /// 提交充值结果 /// /// /// public virtual Task SubmitPayResult(long amount) { return Network.SubmitPayment(amount); } /// /// 获取配置 /// public async Task FetchConfigAsync(string userId) { return await Config.Fetch(userId); } protected void ShowVerifiedToast() { Texture avatar = TapSDK.UI.UIManager.TapTapToastIcon; string str = "您已在 TapTap 实名!"; TapSDK.UI.UIManager.Instance.OpenToast(false, str, 3, icon: avatar); } #endregion } }