hi-client/lib/app/common/app_run_data.dart

795 lines
30 KiB
Dart
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'dart:math' show min;
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/common/app_config.dart';
import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
import 'package:kaer_with_panels/app/services/kr_socket_service.dart';
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
import 'package:kaer_with_panels/app/services/kr_device_info_service.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import '../services/api_service/kr_api.user.dart';
import '../services/singbox_imp/kr_sing_box_imp.dart';
import '../services/kr_site_config_service.dart';
import '../services/kr_subscribe_service.dart';
import '../utils/kr_event_bus.dart';
import '../../singbox/model/singbox_status.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart';
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/utils/kr_device_util.dart';
import 'package:openinstall_flutter_plugin/openinstall_flutter_plugin.dart';
import 'dart:io' show Platform;
class KRAppRunData {
static final KRAppRunData _instance = KRAppRunData._internal();
static const String _keyUserInfo = 'USER_INFO';
static const bool inviteDebugMode = false;
/// 登录token
String? kr_token;
/// 用户账号(使用响应式变量以便 UI 能监听变化)
final Rx<String?> kr_account = Rx<String?>(null);
/// 分享链接(使用响应式变量以便 UI 能监听变化)
final Rx<String?> shareUrl = Rx<String?>(null);
/// 分享链接(使用响应式变量以便 UI 能监听变化)
final Rx<String?> kr_authType = Rx<String?>(null);
/// 用户ID使用响应式变量以便 UI 能监听变化)
final Rx<int?> kr_userId = Rx<int?>(null);
/// 用户邀请码(从用户信息接口获取)
final RxString kr_referCode = ''.obs;
/// 邀请人ID谁邀请了当前用户
final RxInt kr_refererId = 0.obs;
/// 用户余额
final RxInt kr_balance = 0.obs;
/// 佣金
final RxInt kr_commission = 0.obs;
/// 设备ID
String? deviceId;
/// 最后一次设备登录错误信息
String? kr_lastDeviceLoginError;
/// 区号
String? kr_areaCode;
// 需要被监听的属性,用 obs 包装
final kr_isLogin = false.obs;
// 存储临时待绑定的邀请码(处理唤醒数据比登录完成早的情况)
String? _kr_pendingInviteCode;
KRAppRunData._internal();
factory KRAppRunData() => _instance;
static KRAppRunData getInstance() {
return _instance;
}
DateTime _stepStartTime = DateTime.now();
void _logStepTiming(String stepName) {
final now = DateTime.now();
final stepMs = now.difference(_stepStartTime).inMilliseconds;
KRLogUtil.kr_i('[SPLASH_TIMING] ⏱️ $stepName 耗时: ${stepMs}ms',
tag: 'AppRunData');
_stepStartTime = now;
}
/// 判断是否是设备登录(游客模式)
bool isDeviceLogin() {
// 设备登录的账号格式为 "device_设备ID"
return kr_authType.value != null && kr_authType.value == 'device';
}
/// 🔧 修复2.1验证Token格式是否有效
/// 检查Token是否符合JWT格式header.payload.signature
/// 这能有效防止被污染的或过期的Token数据
bool _kr_isValidToken(String token) {
try {
// JWT格式检查: header.payload.signature (三段,每段用.分隔)
final parts = token.split('.');
if (parts.length != 3) {
KRLogUtil.kr_w('❌ Token格式无效分段数不对 (${parts.length} != 3)',
tag: 'AppRunData');
return false;
}
// 检查每一段是否为非空
for (var i = 0; i < parts.length; i++) {
if (parts[i].isEmpty) {
KRLogUtil.kr_w('❌ Token格式无效${i + 1}段为空', tag: 'AppRunData');
return false;
}
}
// 尝试解码payload部分验证是否是有效的base64和JSON
String payload = parts[1];
// 手动添加必要的padding
switch (payload.length % 4) {
case 0:
break;
case 2:
payload += '==';
break;
case 3:
payload += '=';
break;
default:
KRLogUtil.kr_w('❌ Token payload长度无效', tag: 'AppRunData');
return false;
}
// 尝试解码和解析
try {
final decodedBytes = base64.decode(payload);
final decodedString = utf8.decode(decodedBytes);
jsonDecode(decodedString); // 验证是否是有效JSON
KRLogUtil.kr_i('✅ Token格式验证通过', tag: 'AppRunData');
return true;
} catch (e) {
KRLogUtil.kr_w('❌ Token payload无法解析: $e', tag: 'AppRunData');
return false;
}
} catch (e) {
KRLogUtil.kr_e('Token验证异常: $e', tag: 'AppRunData');
return false;
}
}
/// 从JWT token中解析userId
int? _kr_parseUserIdFromToken(String token) {
try {
// JWT格式: header.payload.signature
final parts = token.split('.');
if (parts.length != 3) {
KRLogUtil.kr_e('JWT token格式错误', tag: 'AppRunData');
return null;
}
// 解码payload部分base64
String payload = parts[1];
// 手动添加必要的paddingbase64要求长度是4的倍数
switch (payload.length % 4) {
case 0:
break; // 不需要padding
case 2:
payload += '==';
break;
case 3:
payload += '=';
break;
default:
KRLogUtil.kr_e('JWT payload长度无效', tag: 'AppRunData');
return null;
}
final decodedBytes = base64.decode(payload);
final decodedString = utf8.decode(decodedBytes);
// 解析JSON
final Map<String, dynamic> payloadMap = jsonDecode(decodedString);
// 获取UserId
if (payloadMap.containsKey('UserId')) {
final userId = payloadMap['UserId'];
KRLogUtil.kr_i('从JWT解析出userId: $userId', tag: 'AppRunData');
return userId is int ? userId : int.tryParse(userId.toString());
}
return null;
} catch (e) {
KRLogUtil.kr_e('解析JWT token失败: $e', tag: 'AppRunData');
return null;
}
}
/// 保存用户信息
Future<void> kr_saveUserInfo(
String token,
String account,
) async {
KRLogUtil.kr_i('开始保存用户信息', tag: 'AppRunData');
try {
// 从JWT token中解析userId
kr_userId.value = _kr_parseUserIdFromToken(token);
KRLogUtil.kr_i('从JWT解析userId: ${kr_userId.value}', tag: 'AppRunData');
final accountText = account.startsWith('device_') ? '9000${kr_userId}' : account;
// 更新内存中的数据
kr_token = token;
kr_account.value = accountText;
final Map<String, dynamic> userInfo = {
'token': token,
'account': accountText,
};
KRLogUtil.kr_i('准备保存用户信息到存储', tag: 'AppRunData');
await KRSecureStorage().kr_saveData(
key: _keyUserInfo,
value: jsonEncode(userInfo),
);
// 验证保存是否成功
final savedData = await KRSecureStorage().kr_readData(key: _keyUserInfo);
KRLogUtil.kr_i('用户信息-kr_readData$savedData', tag: 'AppRunData');
if (savedData == null || savedData.isEmpty) {
KRLogUtil.kr_e('数据保存后无法读取,保存失败', tag: 'AppRunData');
kr_isLogin.value = false;
return;
}
KRLogUtil.kr_i('用户信息保存成功设置登录状态为true', tag: 'AppRunData');
// 只有在保存成功后才设置登录状态
kr_isLogin.value = true;
KRLogUtil.kr_i('用户信息-kr_isLogin$kr_isLogin', tag: 'AppRunData');
KRLogUtil.kr_i('🔍 [AppRunData] 检查登录模式: account=$account',
tag: 'AppRunData');
// 不等待userinfo接口返回
_fetchUserInfo();
} catch (e) {
KRLogUtil.kr_e('保存用户信息失败: $e', tag: 'AppRunData');
// 如果出错,重置登录状态
kr_isLogin.value = false;
rethrow; // 重新抛出异常,让调用者知道保存失败
}
}
/// 退出登录(其实是设备重新登录)
Future<void> kr_loginOut() async {
HIDialog.show(
message: '当前登录已过期,请重新登录',
preventBackDismiss: true,
confirmText: '确定',
autoClose: false,
onConfirm: () async {
// 先将登录状态设置为 false防止重连
kr_isLogin.value = false;
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
tag: 'AppRunData');
KRLogUtil.kr_i('开始重新进行设备登录', tag: 'AppRunData');
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
tag: 'AppRunData');
// === 停止 VPN 服务 ===
try {
// 检查 SingBox 服务状态并停止
if (KRSingBoxImp.instance.kr_status.value is SingboxStarted) {
await KRSingBoxImp.instance.kr_stop();
KRLogUtil.kr_i('VPN 服务已停止', tag: 'Logout');
}
} catch (e) {
KRLogUtil.kr_e('停止 VPN 服务失败: $e', tag: 'Logout');
}
// 断开 Socket 连接
await _kr_disconnectSocket();
// 清理用户信息
kr_token = null;
kr_account.value = null;
kr_userId.value = null;
kr_areaCode = null;
// 删除存储的用户信息
await KRSecureStorage().kr_deleteData(key: _keyUserInfo);
// 🔧 修复4: 清理订阅服务数据 - 防止未登录用户访问订阅
try {
final subscribeService =
Get.find<dynamic>(tag: 'KRSubscribeService');
if (subscribeService != null &&
subscribeService is KRSubscribeService) {
KRLogUtil.kr_i('🧹 清理订阅服务数据...', tag: 'AppRunData');
await subscribeService.kr_logout();
KRLogUtil.kr_i('✅ 订阅服务数据已清理', tag: 'AppRunData');
}
} catch (e) {
// 忽略异常:如果订阅服务未初始化或不可用,直接继续
KRLogUtil.kr_d('⚠️ 无法获取订阅服务,跳过清理: $e', tag: 'AppRunData');
}
// 5⃣ 执行设备登录
final success = await kr_checkAndPerformDeviceLogin();
if (!success) {
// 设备登录失败 → 提示用户重试
HIDialog.show(
message: '设备登录失败\n\n原因:${kr_lastDeviceLoginError ?? "未知错误"}\n\n请检查网络或重试',
confirmText: '重试',
preventBackDismiss: true,
onConfirm: () async {
await kr_loginOut(); // 递归重试
},
);
return; // 阻止跳首页
}
// 等待一小段时间,确保登录状态已经更新
await Future.delayed(const Duration(milliseconds: 300));
// 刷新订阅信息
KRLogUtil.kr_i('🔄 开始刷新订阅信息...', tag: 'DeviceManagement');
try {
await KRSubscribeService().kr_refreshAll();
KRLogUtil.kr_i('✅ 订阅信息刷新成功', tag: 'DeviceManagement');
} catch (e) {
KRLogUtil.kr_e('订阅信息刷新失败: $e', tag: 'DeviceManagement');
}
Get.offAllNamed(Routes.KR_HOME);
});
}
Future<void> kr_loginOut_loading() async {
KRCommonUtil.kr_showLoading();
// 先将登录状态设置为 false防止重连
kr_isLogin.value = false;
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'AppRunData');
KRLogUtil.kr_i('开始重新进行设备登录', tag: 'AppRunData');
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'AppRunData');
// === 停止 VPN 服务 ===
try {
// 检查 SingBox 服务状态并停止
if (KRSingBoxImp.instance.kr_status.value is SingboxStarted) {
await KRSingBoxImp.instance.kr_stop();
KRLogUtil.kr_i('VPN 服务已停止', tag: 'Logout');
}
} catch (e) {
KRCommonUtil.kr_hideLoading();
KRLogUtil.kr_e('停止 VPN 服务失败: $e', tag: 'Logout');
}
// 断开 Socket 连接
await _kr_disconnectSocket();
// 清理用户信息
kr_token = null;
kr_account.value = null;
kr_userId.value = null;
kr_areaCode = null;
// 删除存储的用户信息
await KRSecureStorage().kr_deleteData(key: _keyUserInfo);
// 🔧 修复4: 清理订阅服务数据 - 防止未登录用户访问订阅
try {
final subscribeService = Get.find<dynamic>(tag: 'KRSubscribeService');
if (subscribeService != null && subscribeService is KRSubscribeService) {
KRLogUtil.kr_i('🧹 清理订阅服务数据...', tag: 'AppRunData');
await subscribeService.kr_logout();
KRLogUtil.kr_i('✅ 订阅服务数据已清理', tag: 'AppRunData');
}
} catch (e) {
KRCommonUtil.kr_hideLoading();
// 忽略异常:如果订阅服务未初始化或不可用,直接继续
KRLogUtil.kr_d('⚠️ 无法获取订阅服务,跳过清理: $e', tag: 'AppRunData');
}
// 5⃣ 执行设备登录
final success = await kr_checkAndPerformDeviceLogin();
if (!success) {
KRCommonUtil.kr_hideLoading();
// 设备登录失败 → 提示用户重试
HIDialog.show(
message: '设备登录失败\n\n原因:${kr_lastDeviceLoginError ?? "未知错误"}\n\n请检查网络或重试',
confirmText: '重试',
preventBackDismiss: true,
onConfirm: () async {
await kr_loginOut(); // 递归重试
},
);
return; // 阻止跳首页
}
// 等待一小段时间,确保登录状态已经更新
await Future.delayed(const Duration(milliseconds: 300));
// 刷新订阅信息
KRLogUtil.kr_i('🔄 开始刷新订阅信息...', tag: 'DeviceManagement');
try {
await KRSubscribeService().kr_refreshAll();
KRLogUtil.kr_i('✅ 订阅信息刷新成功', tag: 'DeviceManagement');
} catch (e) {
KRCommonUtil.kr_hideLoading();
KRLogUtil.kr_e('订阅信息刷新失败: $e', tag: 'DeviceManagement');
}
KRCommonUtil.kr_hideLoading();
Get.offAllNamed(Routes.KR_HOME);
}
/// 检查并执行设备登录
Future<bool> kr_checkAndPerformDeviceLogin() async {
try {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('🔍 开始执行设备登录...');
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
_logStepTiming('设备登录开始');
// 初始化设备信息服务
await KRDeviceInfoService().initialize();
_logStepTiming('初始化设备信息完成');
KRLogUtil.kr_i('🔐 开始执行设备登录', tag: 'AppRunData');
// 执行设备登录
_logStepTiming('开始设备登录请求');
final authApi = KRAuthApi();
final result = await authApi.kr_deviceLogin();
_logStepTiming('设备登录请求完成');
return await result.fold(
(error) {
kr_lastDeviceLoginError = error.msg;
print('❌ 设备登录失败: ${error.msg}');
KRLogUtil.kr_e('❌ 设备登录失败: ${error.msg}', tag: 'SplashController');
_logStepTiming('设备登录完成');
return false;
},
(token) async {
print('✅ 设备登录成功Token: $token');
KRLogUtil.kr_i('✅ 设备登录成功', tag: 'SplashController');
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
_logStepTiming('开始保存用户信息');
await kr_saveUserInfo(
token,
'device_$deviceId', // 临时账号
);
_logStepTiming('保存用户信息完成');
kr_isLogin.value = true;
print('✅ 已标记为登录状态');
// 静默邀请绑定
if (inviteDebugMode) {
// Debug 模式下等待调试弹窗完成,避免页面跳转
await _kr_handleSilentInvitation();
} else {
// 正式环境异步执行,不阻塞主流程
_kr_handleSilentInvitation();
}
_logStepTiming('设备登录完成');
return true;
},
);
} catch (e, stackTrace) {
print('❌ 设备登录检查异常: $e');
print('📚 堆栈跟踪: $stackTrace');
kr_lastDeviceLoginError = e.toString();
KRLogUtil.kr_e('❌ 设备登录检查异常: $e', tag: 'SplashController');
return false;
}
}
/// 初始化用户信息
Future<void> kr_initializeUserInfo() async {
KRLogUtil.kr_i('开始初始化用户信息', tag: 'AppRunData');
try {
deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
final String? userInfoString =
await KRSecureStorage().kr_readData(key: _keyUserInfo);
if (userInfoString != null && userInfoString.isNotEmpty) {
KRLogUtil.kr_i('找到存储的用户信息,开始解析', tag: 'AppRunData');
try {
final Map<String, dynamic> userInfo = jsonDecode(userInfoString);
kr_token = userInfo['token'];
kr_account.value = userInfo['account'];
// 从token中解析userId
if (kr_token != null && kr_token!.isNotEmpty) {
kr_userId.value = _kr_parseUserIdFromToken(kr_token!);
}
KRLogUtil.kr_i(
'解析用户信息成功: token=${kr_token != null}, account=${kr_account.value}',
tag: 'AppRunData');
// 🔧 修复2验证token有效性和账号信息完整性
// 防止恢复被污染的或过期的数据
if (kr_token != null &&
kr_token!.isNotEmpty &&
_kr_isValidToken(kr_token!)) {
// token格式验证通过JWT格式检查
if (kr_account.value != null && kr_account.value!.isNotEmpty) {
// 账号信息完整
KRLogUtil.kr_i('✅ Token和账号验证通过设置登录状态为true', tag: 'AppRunData');
KRLogUtil.kr_i('📊 恢复账号: ${kr_account.value}', tag: 'AppRunData');
kr_isLogin.value = true;
// 🔧 新增:恢复登录状态后也尝试检测一次静默邀请(重要:针对已安装后启动的情况)
_kr_handleSilentInvitation();
} else {
// 账号信息为空,清理旧数据
KRLogUtil.kr_w('⚠️ 账号信息为空,清理该条用户数据', tag: 'AppRunData');
await kr_loginOut();
}
} else {
// Token无效或格式错误清理旧数据
KRLogUtil.kr_w('⚠️ Token验证失败或格式错误清理该条用户数据', tag: 'AppRunData');
if (kr_token != null && kr_token!.isNotEmpty) {
KRLogUtil.kr_w(
' ❌ 可能的原因Token已过期或被污染格式: ${kr_token!.substring(0, min(30, kr_token!.length))}...',
tag: 'AppRunData');
}
await kr_loginOut();
}
} catch (e) {
KRLogUtil.kr_e('解析用户信息失败: $e', tag: 'AppRunData');
await kr_loginOut();
}
} else {
KRLogUtil.kr_i('未找到存储的用户信息,设置为未登录状态', tag: 'AppRunData');
kr_isLogin.value = false;
}
} catch (e) {
KRLogUtil.kr_e('初始化用户信息过程出错: $e', tag: 'AppRunData');
kr_isLogin.value = false;
}
KRLogUtil.kr_i('用户信息初始化完成,登录状态: ${kr_isLogin.value}', tag: 'AppRunData');
}
/// 建立 Socket 连接
/// /// ⚠️ 已遗弃wsBaseUrl 不再使用
Future<void> _kr_connectSocket(String userId) async {
// 已遗弃,不再建立 Socket 连接
KRLogUtil.kr_i('Socket 连接已遗弃', tag: 'AppRunData');
// 如果需要实时消息,使用其他实现方式
}
/// 处理接收到的消息
void _kr_handleMessage(Map<String, dynamic> message) {
try {
final String method = message['method'] as String;
switch (method) {
case 'kicked_device':
KRLogUtil.kr_i('超出登录设备限制', tag: 'AppRunData');
kr_loginOut();
break;
case 'kicked_admin':
KRLogUtil.kr_i('强制退出', tag: 'AppRunData');
kr_loginOut();
break;
case 'subscribe_update':
KRLogUtil.kr_i('订阅信息已更新', tag: 'AppRunData');
// 发送订阅更新事件
KREventBus().kr_sendMessage(KRMessageType.kr_subscribe_update);
break;
default:
KRLogUtil.kr_w('收到未知类型的消息: $message', tag: 'AppRunData');
}
} catch (e) {
KRLogUtil.kr_e('处理消息失败: $e', tag: 'AppRunData');
}
}
/// 处理连接状态变化
void _kr_handleConnectionState(bool isConnected) {
KRLogUtil.kr_i('WebSocket 连接状态: ${isConnected ? "已连接" : "已断开"}',
tag: 'AppRunData');
}
/// 断开 Socket 连接
Future<void> _kr_disconnectSocket() async {
await KrSocketService.instance.disconnect();
}
/// 获取用户详细信息(登录后调用)
Future<void> _fetchUserInfo() async {
try {
KRLogUtil.kr_i('📞 [AppRunData] 开始调用用户信息接口 /v1/public/user/info ...',
tag: 'AppRunData');
KRLogUtil.kr_i('🔐 [AppRunData] 当前 Token: ${kr_token ?? "null"}',
tag: 'AppRunData');
final result = await KRUserApi.kr_getUserInfo();
result.fold(
(error) {
KRLogUtil.kr_e(
'❌ [AppRunData] 获取用户信息失败: ${error.msg} (code: ${error.code})',
tag: 'AppRunData');
},
(userInfo) {
final authType = userInfo.authMethods.isNotEmpty
? userInfo.authMethods[0].authType
: null;
final authIdentifier = userInfo.authMethods.isNotEmpty
? userInfo.authMethods[0].authIdentifier
: null;
KRLogUtil.kr_i('✅ [AppRunData] 获取用户信息成功', tag: 'AppRunData');
KRLogUtil.kr_i('📋 [AppRunData] refer_code: "${userInfo.referCode}"',
tag: 'AppRunData');
KRLogUtil.kr_i('💰 [AppRunData] balance: ${userInfo.balance}',
tag: 'AppRunData');
KRLogUtil.kr_i('💵 [AppRunData] commission: ${userInfo.commission}',
tag: 'AppRunData');
KRLogUtil.kr_i('📧 [AppRunData] 登录类型: ${authType}',
tag: 'AppRunData');
KRLogUtil.kr_i('📧 [AppRunData] email: ${authIdentifier}',
tag: 'AppRunData');
KRLogUtil.kr_i('🆔 [AppRunData] id: ${userInfo.id}',
tag: 'AppRunData');
KRLogUtil.kr_i('🆔 [AppRunData] id: ${userInfo.shareUrl}',
tag: 'AppRunData');
// 保存到全局状态
kr_referCode.value = userInfo.referCode;
kr_refererId.value = userInfo.refererId;
kr_account.value =
authType == 'device' ? '${userInfo.id}' : authIdentifier;
kr_balance.value = userInfo.balance;
kr_commission.value = userInfo.commission;
shareUrl.value = userInfo.shareUrl;
kr_authType.value = authType;
KRLogUtil.kr_i('💾 [AppRunData] 用户信息已保存到全局状态:', tag: 'AppRunData');
KRLogUtil.kr_i(' - kr_referCode: "${kr_referCode.value}"',
tag: 'AppRunData');
KRLogUtil.kr_i(' - kr_refererId: ${kr_refererId.value}',
tag: 'AppRunData');
KRLogUtil.kr_i(' - kr_balance: ${kr_balance.value}',
tag: 'AppRunData');
KRLogUtil.kr_i(' - kr_commission: ${kr_commission.value}',
tag: 'AppRunData');
KRLogUtil.kr_i(' - kr_commission: ${shareUrl.value}',
tag: 'AppRunData');
},
);
} catch (e, stackTrace) {
KRLogUtil.kr_e('💥 [AppRunData] 获取用户信息异常: $e', tag: 'AppRunData');
KRLogUtil.kr_e('📚 [AppRunData] 错误堆栈: $stackTrace', tag: 'AppRunData');
}
}
/// 处理静默邀请
Future<void> _kr_handleSilentInvitation() async {
KRLogUtil.kr_i('🚀 开始处理静默邀请...', tag: 'AppRunData');
String? inviteCode;
// 1. 先检查是否有之前通过唤醒/安装暂存的邀请码
if (_kr_pendingInviteCode != null && _kr_pendingInviteCode!.isNotEmpty) {
inviteCode = _kr_pendingInviteCode;
KRLogUtil.kr_i('📎 使用暂存的待绑定邀请码: $inviteCode', tag: 'AppRunData');
}
// 2. 如果没有暂存码,尝试从平台环境获取
if (inviteCode == null || inviteCode!.isEmpty) {
try {
if (Platform.isMacOS || Platform.isWindows) {
inviteCode = await KRDeviceUtil().kr_getDesktopInviteCode();
} else if (Platform.isAndroid || Platform.isIOS) {
final Completer<String?> completer = Completer<String?>();
OpeninstallFlutterPlugin().install((data) async {
final code = kr_parseInviteCodeFromData(data);
KRLogUtil.kr_i('收到 OpenInstall 安装数据: $data, 解析出邀请码: $code', tag: 'AppRunData');
if (!completer.isCompleted) completer.complete(code);
});
inviteCode = await completer.future
.timeout(const Duration(seconds: 8), onTimeout: () => null);
}
} catch (e) {
KRLogUtil.kr_e('获取静默邀请码异常: $e', tag: 'AppRunData');
}
}
if (inviteCode != null && inviteCode!.isNotEmpty) {
KRLogUtil.kr_i('🔍 最终识别到邀请码: $inviteCode', tag: 'AppRunData');
if (inviteDebugMode) {
// Debug 模式下弹出对话框确认
final bool isDesktop = Platform.isMacOS || Platform.isWindows;
await HIDialog.show(
title: isDesktop ? '调试:唤醒识别到邀请码' : '调试:邀请码绑定确认',
message: isDesktop
? '桌面端识别到邀请码:$inviteCode\n是否进行绑定?'
: '识别到邀请码:$inviteCode\n是否进行绑定?',
confirmText: isDesktop ? '绑定' : '确认绑定',
cancelText: isDesktop ? '跳过' : '取消',
onConfirm: () async {
await _kr_performInviteBinding(inviteCode!);
_kr_pendingInviteCode = null; // 绑定后清除
},
onCancel: () {
_kr_pendingInviteCode = null; // 取消也清除,避免重复弹窗
},
);
} else {
// 正式环境静默绑定
await _kr_performInviteBinding(inviteCode!);
_kr_pendingInviteCode = null; // 绑定后清除
}
} else {
KRLogUtil.kr_i('⚠️ 未识别到有效的邀请码,跳外静默绑定', tag: 'AppRunData');
}
}
/// 执行邀请码绑定请求
Future<void> _kr_performInviteBinding(String inviteCode) async {
KRLogUtil.kr_i('🚀 准备执行邀请码绑定: $inviteCode', tag: 'AppRunData');
final result =
await KRUserApi().hi_inviteCode(inviteCode, isSilentInvite: true);
result.fold(
(error) => KRLogUtil.kr_w('❌ 邀请绑定失败: ${error.msg}', tag: 'AppRunData'),
(_) => KRLogUtil.kr_i('✅ 邀请绑定成功', tag: 'AppRunData'),
);
}
/// 公开方法:直接处理 OpenInstall 返回的原始数据(用于唤醒等场景)
Future<void> kr_handleOpenInstallData(Map<dynamic, dynamic> data) async {
final code = kr_parseInviteCodeFromData(data);
if (code != null && code.isNotEmpty) {
KRLogUtil.kr_i('🔗 收到 OpenInstall 原始参数并触发解析: $code', tag: 'AppRunData');
// 暂存该邀请码
_kr_pendingInviteCode = code;
// 如果当前已经是登录状态则由于是唤醒Hot Start触发直接按业务逻辑处理
if (kr_isLogin.value) {
KRLogUtil.kr_i('✅ 用户已登录,立即处理唤醒绑定', tag: 'AppRunData');
if (inviteDebugMode) {
await HIDialog.show(
title: '调试:唤醒识别到邀请码',
message: '唤醒数据解析到邀请码:$code\n是否进行绑定?',
confirmText: '绑定',
cancelText: '跳过',
onConfirm: () async {
await _kr_performInviteBinding(code);
_kr_pendingInviteCode = null;
},
onCancel: () => _kr_pendingInviteCode = null,
);
} else {
_kr_performInviteBinding(code).then((_) => _kr_pendingInviteCode = null);
}
} else {
KRLogUtil.kr_i('⏳ 用户未登录,已暂存邀请码,等待登录完成后自动绑定', tag: 'AppRunData');
}
}
}
/// 从 OpenInstall 数据中解析邀请码
String? kr_parseInviteCodeFromData(Map<dynamic, dynamic> data) {
try {
if (data.containsKey('bindData')) {
final bindDataStr = data['bindData'] as String?;
if (bindDataStr != null && bindDataStr.isNotEmpty) {
final Map<String, dynamic> bindData = jsonDecode(bindDataStr);
return bindData['inviteCode']?.toString();
}
}
} catch (e) {
KRLogUtil.kr_e('解析 OpenInstall 数据中邀请码失败: $e', tag: 'AppRunData');
}
return null;
}
}