| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719 |
- import 'dart:async';
- import 'dart:convert';
- import 'dart:io';
- import 'package:device_info_plus/device_info_plus.dart';
- import 'package:dio/dio.dart';
- import 'package:get/get.dart';
- import 'package:nomo/app/constants/api_domains.dart';
- import 'package:nomo/app/constants/keys.dart';
- import 'package:nomo/app/dialog/all_dialog.dart';
- import 'package:package_info_plus/package_info_plus.dart';
- import 'package:uuid/uuid.dart';
- import '../../config/translations/strings_enum.dart';
- import 'base_core_api.dart';
- import '../../utils/boost_report_manager.dart';
- import '../../utils/haptic_feedback_manager.dart';
- import '../../utils/log/logger.dart';
- import '../../utils/network_helper.dart';
- import '../components/ix_snackbar.dart';
- import '../constants/enums.dart';
- import '../constants/errors.dart';
- import '../data/models/api_exception.dart';
- import '../data/models/failure.dart';
- import '../data/models/vpn_message.dart';
- import '../data/sp/ix_sp.dart';
- import '../constants/sp_keys.dart';
- import '../dialog/error_dialog.dart';
- import 'api_controller.dart';
- class CoreController extends GetxService {
- final TAG = 'CoreController';
- final _apiController = Get.find<ApiController>();
- final _state = ConnectionState.disconnected.obs;
- ConnectionState get state => _state.value;
- set state(ConnectionState value) => _state.value = value;
- // 公开状态流供外部监听
- Rx<ConnectionState> get stateStream => _state;
- final _timer = "00:00:00".obs;
- String get timer => _timer.value;
- set timer(String value) => _timer.value = value;
- // VPN 事件流订阅
- StreamSubscription<String>? _eventSubscription;
- CancelToken? _cancelToken;
- //全局uuid
- final _globalUuid = Uuid().v4();
- String locationSelectionType = 'auto';
- // 标记 VPN 逻辑是否已开启(调用了 BaseCoreApi().connect())
- bool _isVpnStarted = false;
- // 标记是否有待处理的断开请求
- bool _pendingDisconnect = false;
- // 用于等待断开完成的 Completer
- Completer<void>? _disconnectCompleter;
- @override
- void onInit() {
- super.onInit();
- _initCheckConnect();
- _startListeningToEvents();
- }
- @override
- void onClose() {
- super.onClose();
- // 取消事件流订阅
- _eventSubscription?.cancel();
- _eventSubscription = null;
- }
- void _initCheckConnect() {
- BaseCoreApi().isConnected().then((value) {
- if (value == true) {
- state = ConnectionState.connected;
- _isVpnStarted = true;
- } else {
- state = ConnectionState.disconnected;
- _isVpnStarted = false;
- }
- });
- }
- void handleConnection() async {
- // 如果正在断开中,忽略操作
- if (state == ConnectionState.disconnecting) {
- log(TAG, '正在断开中,忽略操作');
- return;
- }
- if (state == ConnectionState.disconnected) {
- // 开始连接 - 轻微震动
- state = ConnectionState.connectingVirtual;
- _isVpnStarted = false;
- _pendingDisconnect = false;
- HapticFeedbackManager.connectionStart();
- getDispatchInfo();
- } else if (state == ConnectionState.connectingVirtual) {
- // 虚拟连接中点击断开
- if (_isVpnStarted) {
- // VPN 逻辑已开启,标记待断开,等待 _onVpnConnecting 返回后执行断开
- log(TAG, 'VPN 已启动,标记待断开');
- _pendingDisconnect = true;
- } else {
- // VPN 逻辑还没开启,取消请求并初始化状态
- log(TAG, 'VPN 未启动,取消请求并初始化状态');
- _cancelToken?.cancel('用户取消连接');
- _cancelToken = null;
- _uninitState();
- }
- } else if (state == ConnectionState.connecting) {
- // 真实连接中点击断开 - 执行断开逻辑
- log(TAG, '真实连接中,执行断开逻辑');
- await _performDisconnect();
- } else if (state == ConnectionState.connected) {
- // 已连接状态,执行断开逻辑
- await _performDisconnect();
- }
- }
- /// 执行断开逻辑,等待断开完成
- Future<void> _performDisconnect() async {
- // 如果已经在断开中,等待断开完成(带超时)
- if (_disconnectCompleter != null) {
- log(TAG, '已经在断开中,等待完成');
- try {
- await _disconnectCompleter!.future.timeout(
- const Duration(seconds: 10),
- onTimeout: () {
- log(TAG, '等待断开超时,强制清理');
- _forceCleanupDisconnect();
- },
- );
- } catch (e) {
- log(TAG, '等待断开异常: $e');
- _forceCleanupDisconnect();
- }
- return;
- }
- log(TAG, '开始执行断开逻辑, _isVpnStarted=$_isVpnStarted');
- // 如果 VPN 根本没启动,直接清理状态即可
- if (!_isVpnStarted) {
- log(TAG, 'VPN 未启动,直接清理状态');
- _uninitState();
- return;
- }
- state = ConnectionState.disconnecting;
- _disconnectCompleter = Completer<void>();
- try {
- BaseCoreApi().disconnect();
- } catch (e) {
- log(TAG, 'disconnect 调用异常: $e');
- _forceCleanupDisconnect();
- return;
- }
- // 等待断开完成(带超时)
- try {
- await _disconnectCompleter!.future.timeout(
- const Duration(seconds: 10),
- onTimeout: () {
- log(TAG, '断开超时,强制清理');
- _forceCleanupDisconnect();
- },
- );
- } catch (e) {
- log(TAG, '断开等待异常: $e');
- _forceCleanupDisconnect();
- }
- }
- /// 强制清理断开状态
- void _forceCleanupDisconnect() {
- if (_disconnectCompleter != null && !_disconnectCompleter!.isCompleted) {
- _disconnectCompleter!.complete();
- }
- _disconnectCompleter = null;
- _isVpnStarted = false;
- _pendingDisconnect = false;
- _uninitState();
- }
- void selectLocationConnect() async {
- if (state == ConnectionState.disconnecting) {
- log(TAG, '正在断开中,等待断开完成');
- if (_disconnectCompleter != null) {
- await _disconnectCompleter!.future;
- }
- }
- if (state != ConnectionState.disconnected) {
- await _performDisconnect();
- // 延迟300ms
- await Future.delayed(const Duration(milliseconds: 300));
- log(TAG, 'selectLocationConnect disconnected = $state');
- state = ConnectionState.connectingVirtual;
- _isVpnStarted = false;
- _pendingDisconnect = false;
- getDispatchInfo();
- } else {
- handleConnection();
- }
- }
- Future<void> getDispatchInfo() async {
- // 如果正在请求中,取消当前请求
- if (_cancelToken != null) {
- log(TAG, '取消当前请求,重新发起新请求');
- _cancelToken?.cancel('取消旧请求,发起新请求');
- }
- // 创建新的 CancelToken
- final currentToken = CancelToken();
- _cancelToken = currentToken;
- // 创建一条加速日志
- await createBoostLog();
- try {
- final locationId = IXSP.getSelectedLocation()?['id'];
- final locationCode = IXSP.getSelectedLocation()?['code'];
- final launch = await _apiController.getDispatchInfo(
- locationId,
- locationCode,
- cancelToken: currentToken,
- );
- // 只有当前 token 没有被替换时才清空
- if (_cancelToken == currentToken) {
- _cancelToken = null;
- }
- final remainTime = launch.userConfig?.remainTime ?? 0;
- if (remainTime <= 0) {
- _onVpnError(Errors.ERROR_REMAIN_TIME, Strings.remainTimeEnded.tr);
- return;
- }
- if (state == ConnectionState.connectingVirtual) {
- // 标记 VPN 逻辑已开启
- _isVpnStarted = true;
- final sessionId = Uuid().v4();
- final socksPort = launch.nodesConfig!.socketPort!;
- final tunnelConfig = launch.nodesConfig!.tunnelConfig!;
- final configJson = jsonEncode(launch.nodesConfig!);
- // 根据分流隧道设置获取 allowVpnApps 和 disallowVpnApps
- final splitTunnelingApps = _getSplitTunnelingApps();
- final allowVpnApps = splitTunnelingApps['allowVpnApps']!;
- final disallowVpnApps = splitTunnelingApps['disallowVpnApps']!;
- final accessToken = launch.userConfig?.accessToken ?? '';
- final peekTimeInterval = launch.appConfig?.peekTimeInterval ?? 600;
- final aesKey = Keys.aesKey;
- final aesIv = Keys.aesIv;
- final baseUrls = ApiDomains.instance.getAllLogUrls();
- final params = jsonEncode(_apiController.fp);
- BaseCoreApi().connect(
- sessionId,
- socksPort,
- tunnelConfig,
- configJson,
- remainTime,
- false,
- allowVpnApps,
- disallowVpnApps,
- accessToken,
- aesKey,
- aesIv,
- locationId,
- locationCode,
- baseUrls,
- params,
- peekTimeInterval,
- );
- }
- } on DioException catch (e, s) {
- // 只有当前 token 没有被替换时才清空
- if (_cancelToken == currentToken) {
- _cancelToken = null;
- }
- // 如果是取消错误,不处理
- if (e.type == DioExceptionType.cancel) {
- log(TAG, '请求已取消');
- return;
- }
- if (state == ConnectionState.connectingVirtual) {
- state = ConnectionState.disconnected;
- }
- handleErrorDialog(e, s);
- log(TAG, 'getDispatchInfo error: $e');
- } catch (e, s) {
- // 只有当前 token 没有被替换时才清空
- if (_cancelToken == currentToken) {
- _cancelToken = null;
- }
- if (state == ConnectionState.connectingVirtual) {
- state = ConnectionState.disconnected;
- }
- handleErrorDialog(e, s);
- log(TAG, 'getDispatchInfo error: $e');
- }
- }
- /// 根据分流隧道设置获取 allowVpnApps 和 disallowVpnApps
- /// 直接从 IXSP 读取,参考 SplittunnelingController 的逻辑
- Map<String, List<String>> _getSplitTunnelingApps() {
- List<String> allowVpnApps = [];
- List<String> disallowVpnApps = [];
- try {
- // 读取选中的模式
- final modeString = IXSP.getString(SPKeys.splittunnelingSelectedMode);
- if (modeString == null) {
- log(TAG, '分流隧道未设置模式');
- return {
- 'allowVpnApps': allowVpnApps,
- 'disallowVpnApps': disallowVpnApps,
- };
- }
- // 判断模式类型
- final isExcludeMode = modeString.contains('exclude');
- final isIncludeMode = modeString.contains('include');
- if (!isExcludeMode && !isIncludeMode) {
- log(TAG, '分流隧道模式为 none');
- return {
- 'allowVpnApps': allowVpnApps,
- 'disallowVpnApps': disallowVpnApps,
- };
- }
- // 根据模式获取对应的应用列表
- final key = isExcludeMode
- ? SPKeys.splittunnelingExcludeSelectedApps
- : SPKeys.splittunnelingIncludeSelectedApps;
- final selectedAppsJson = IXSP.getString(key);
- if (selectedAppsJson != null) {
- final selectedPackageNames =
- (jsonDecode(selectedAppsJson) as List<dynamic>).cast<String>();
- if (isIncludeMode) {
- // include 模式:只有选中的应用走 VPN
- allowVpnApps = selectedPackageNames;
- log(TAG, '分流隧道 include 模式,允许的应用: $allowVpnApps');
- } else {
- // exclude 模式:选中的应用不走 VPN
- disallowVpnApps = selectedPackageNames;
- log(TAG, '分流隧道 exclude 模式,排除的应用: $disallowVpnApps');
- }
- }
- } catch (e) {
- log(TAG, '获取分流隧道设置失败: $e');
- }
- return {'allowVpnApps': allowVpnApps, 'disallowVpnApps': disallowVpnApps};
- }
- /// 开始监听来自 Android 的事件
- void _startListeningToEvents() {
- _eventSubscription = onEventChange().listen(
- _handleEventChange,
- onError: (error) {
- log(TAG, '事件流错误: $error');
- },
- );
- }
- // 处理从原生端接收到的消息
- void _handleEventChange(String message) {
- try {
- final Map<String, dynamic> json = jsonDecode(message);
- final String type = json['type'] ?? '';
- switch (type) {
- case 'vpn_status':
- _handleVpnStatus(VpnStatusMessage.fromJson(json));
- break;
- case 'timer_update':
- _handleTimerUpdate(TimerUpdateMessage.fromJson(json));
- break;
- case 'boost_result':
- _handleBoostResult(BoostResultMessage.fromJson(json));
- break;
- default:
- log(TAG, '未知消息类型: $type');
- }
- } catch (e) {
- log(TAG, '解析消息失败: $e');
- }
- }
- void _handleVpnStatus(VpnStatusMessage message) {
- final vpnError = VpnStatus.fromValue(message.status);
- log(
- TAG,
- 'VPN状态变化: ${vpnError.label}, status=${message.status}, code=${message.code}, message=${message.message}',
- );
- // 根据状态码处理不同的VPN状态
- switch (vpnError) {
- case VpnStatus.idle:
- // disconnected
- _onVpnDisconnected();
- break;
- case VpnStatus.connecting:
- // connecting
- _onVpnConnecting();
- break;
- case VpnStatus.connected:
- // connected
- _onVpnConnected();
- break;
- case VpnStatus.error:
- // error
- _onVpnError(message.code, message.message);
- break;
- }
- }
- void _handleTimerUpdate(TimerUpdateMessage message) {
- //log(TAG, '计时更新: time=${message.currentTime}, mode=${message.mode}');
- timer = _formatTime(message.currentTime);
- }
- void _handleBoostResult(BoostResultMessage message) async {
- log(
- TAG,
- '加速结果: locationCode=${message.locationCode}, nodeId=${message.nodeId}, success=${message.success}',
- );
- if (message.success) {
- try {
- await _apiController.connected({
- 'locationCode': message.locationCode,
- 'instanceId': message.nodeId,
- });
- } catch (e) {
- log('handleRouterConnected error: $e');
- }
- }
- try {
- final json = jsonDecode(message.param);
- await _apiController.uploadLogs(json);
- } catch (e) {
- log('handleBoostResult error: $e');
- }
- }
- // VPN状态处理方法
- void _onVpnDisconnected() {
- log(TAG, 'VPN已断开连接');
- // 完成断开 Completer
- if (_disconnectCompleter != null && !_disconnectCompleter!.isCompleted) {
- _disconnectCompleter!.complete();
- }
- _disconnectCompleter = null;
- // 重置标志位
- _isVpnStarted = false;
- _pendingDisconnect = false;
- // 更新UI状态
- _uninitState();
- // 上传 Boost 日志(不阻塞)
- _apiController.uploadBoostLog();
- // FeedbackBottomSheet.show();
- }
- void _onVpnConnecting() {
- log(TAG, 'VPN正在连接');
- // 检查是否有待处理的断开请求
- if (_pendingDisconnect) {
- log(TAG, '检测到待处理的断开请求,执行断开');
- _pendingDisconnect = false;
- _performDisconnect();
- return;
- }
- // 显示连接中状态
- state = ConnectionState.connecting;
- }
- void _onVpnConnected() {
- log(TAG, 'VPN已连接');
- // 显示已连接状态
- state = ConnectionState.connected;
- HapticFeedbackManager.connectionSuccess();
- }
- void _onVpnError(int code, String message) {
- log(TAG, 'VPN连接错误: code=$code, message=$message');
- // 完成断开 Completer(如果有)
- if (_disconnectCompleter != null && !_disconnectCompleter!.isCompleted) {
- _disconnectCompleter!.complete();
- }
- _disconnectCompleter = null;
- // 重置标志位
- _isVpnStarted = false;
- _pendingDisconnect = false;
- // 显示错误信息
- _uninitState();
- showErrorDialog(code, message);
- // 上传 Boost 日志(不阻塞)
- _apiController.uploadBoostLog();
- }
- void showErrorDialog(int code, String message) {
- var errorMessage = message;
- switch (code) {
- case Errors.ERROR_NODE_TIMEOUT:
- errorMessage = Strings.vpnConnectionTimeoutError.tr;
- break;
- case Errors.ERROR_NO_NODE:
- errorMessage = Strings.vpnNoNodeError.tr;
- break;
- case Errors.ERROR_INIT:
- errorMessage = Strings.vpnInitError.tr;
- break;
- case Errors.ERROR_KILL:
- errorMessage = Strings.vpnKillError.tr;
- break;
- case Errors.ERROR_REVOKE:
- errorMessage = Strings.vpnRevokeError.tr;
- break;
- case Errors.ERROR_SERVICE_EMPTY:
- errorMessage = Strings.vpnServiceEmptyError.tr;
- break;
- case Errors.ERROR_ROUTER:
- errorMessage = Strings.vpnRouterError.tr;
- break;
- case Errors.ERROR_PERMISSION_DENIED:
- errorMessage = Strings.vpnPermissionDeniedError.tr;
- break;
- case Errors.ERROR_REMAIN_TIME:
- errorMessage = Strings.remainTimeEnded.tr;
- break;
- // 桌面版本错误
- case Errors.ERROR_RPC_CALL_FAILED:
- errorMessage = Strings.vpnRPCCallFailed.tr;
- break;
- case Errors.ERROR_RPC_RETURN_FALSE:
- errorMessage = Strings.vpnRPCReturnFalse.tr;
- break;
- }
- if (code == Errors.ERROR_REMAIN_TIME) {
- AllDialog.showMembershipExpired();
- return;
- }
- if (errorMessage.isNotEmpty) {
- ErrorDialog.show(message: errorMessage);
- }
- }
- void _uninitState() {
- if (state != ConnectionState.disconnected) {
- state = ConnectionState.disconnected;
- timer = "00:00:00";
- HapticFeedbackManager.connectionDisconnected();
- }
- }
- // 格式化时间显示
- String _formatTime(int timeMs) {
- final totalSeconds = (timeMs / 1000).abs().round();
- final hours = totalSeconds ~/ 3600;
- final minutes = (totalSeconds % 3600) ~/ 60;
- final seconds = totalSeconds % 60;
- return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
- }
- void handleSnackBarError(dynamic error, StackTrace stackTrace) {
- if (error is ApiException) {
- IXSnackBar.showIXErrorSnackBar(
- title: Strings.error.tr,
- message: error.message,
- );
- } else if (error is Failure) {
- IXSnackBar.showIXErrorSnackBar(
- title: Strings.error.tr,
- message: error.message ?? Strings.unknownError.tr,
- );
- } else if (error is DioException) {
- switch (error.type) {
- case DioExceptionType.connectionError:
- case DioExceptionType.connectionTimeout:
- case DioExceptionType.receiveTimeout:
- case DioExceptionType.sendTimeout:
- IXSnackBar.showIXErrorSnackBar(
- title: Strings.error.tr,
- message: Strings.unableToConnectNetwork.tr,
- );
- break;
- default:
- IXSnackBar.showIXErrorSnackBar(
- title: Strings.error.tr,
- message: Strings.unableToConnectServer.tr,
- );
- }
- } else {
- IXSnackBar.showIXErrorSnackBar(
- title: Strings.error.tr,
- message: error.toString(),
- );
- }
- }
- void handleErrorDialog(dynamic error, StackTrace stackTrace) {
- if (error is ApiException) {
- ErrorDialog.show(title: Strings.error.tr, message: error.message);
- } else if (error is Failure) {
- ErrorDialog.show(
- title: Strings.error.tr,
- message: error.message ?? Strings.unknownError.tr,
- );
- } else if (error is DioException) {
- switch (error.type) {
- case DioExceptionType.connectionError:
- case DioExceptionType.connectionTimeout:
- case DioExceptionType.receiveTimeout:
- case DioExceptionType.sendTimeout:
- ErrorDialog.show(
- title: Strings.error.tr,
- message: Strings.unableToConnectNetwork.tr,
- );
- break;
- default:
- ErrorDialog.show(
- title: Strings.error.tr,
- message: Strings.unableToConnectServer.tr,
- );
- }
- } else {
- ErrorDialog.show(title: Strings.error.tr, message: error.toString());
- }
- }
- // 创建一条加速日志
- Future<void> createBoostLog() async {
- await initLog();
- await setSessionInfoLog();
- await setTargetInfoLog();
- }
- // 初始化日志
- Future<void> initLog() async {
- await BoostReportManager().init();
- }
- // 读取历史日志
- Future<void> readHistoryLog() async {
- await BoostReportManager().readHistoryLog();
- }
- // 初始化会话日志
- Future<void> setSessionInfoLog() async {
- final deviceInfoPlugin = DeviceInfoPlugin();
- final appVersion = await PackageInfo.fromPlatform().then(
- (value) => value.version,
- );
- final networkType = await NetworkHelper.instance.getNetworkType();
- Map<String, String> deviceInfo = {};
- if (Platform.isIOS) {
- final iosOsInfo = await deviceInfoPlugin.iosInfo;
- deviceInfo = {
- 'deviceModel': iosOsInfo.model,
- 'osVersion': iosOsInfo.systemVersion,
- 'appVersion': appVersion,
- 'networkType': networkType,
- 'deviceBrand': iosOsInfo.utsname.machine,
- };
- } else if (Platform.isAndroid) {
- final androidOsInfo = await deviceInfoPlugin.androidInfo;
- deviceInfo = {
- 'deviceModel': androidOsInfo.model,
- 'osVersion': androidOsInfo.version.release,
- 'appVersion': appVersion,
- 'networkType': networkType,
- 'deviceBrand': androidOsInfo.brand,
- };
- }
- final boostSessionId = Uuid().v4();
- await BoostReportManager().initSessionInfo(
- appSessionId: _globalUuid,
- boostSessionId: boostSessionId,
- deviceInfo: deviceInfo,
- );
- }
- // 初始化目标信息
- Future<void> setTargetInfoLog() async {
- await BoostReportManager().addTargetInfo(
- locationSelectionType: locationSelectionType,
- location: IXSP.getSelectedLocation(),
- );
- }
- }
|