import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:nomo/app/controllers/windows/window_service.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:uuid/uuid.dart'; import '../../config/translations/strings_enum.dart'; import '../../utils/log/logger.dart'; import '../constants/configs.dart'; import '../constants/enums.dart'; import '../constants/errors.dart'; import 'base_core_api.dart'; import 'windows/menu_base/src/menu.dart'; import 'windows/menu_base/src/menu_item.dart'; import 'windows/stat_log_entry.dart'; import 'windows/vpn_service.dart'; /// Windows 实现 class WindowsCoreApi implements BaseCoreApi { static const _tag = 'WindowsCoreApi'; WindowsCoreApi._() { // 初始化事件通道 _initEventChannel(); // 初始化vpn服务 _initVpnService(); // 初始化窗口服务 _initWindowService(); } // 创建vpn服务 static final _vpn = VpnService(); // 创建窗口服务 static final _windowService = WindowService(); // 检查定时器 Timer? _checkTimer; bool _hasConnectedOnce = false; bool _isVpnInited = false; int _remainTime = 0x7FFFFFFFFFFFFFFF; // 会员剩余时间 int _sessionCountUp = 0; // session计时 bool _isCountdown = false; // 汇报计时类型 倒计时还是正计时 int _locationId = 0; String _locationCode = ''; String _boostSessionId = ''; /// 内部构造方法,供 BaseCoreApi 工厂使用 factory WindowsCoreApi.create() => WindowsCoreApi._(); // Windows Method Channel static const MethodChannel _channel = MethodChannel('app.xixi.nomo/core_api'); // Windows 事件流控制器 static final StreamController _eventController = StreamController.broadcast(); // Windows 事件流 static Stream get eventStream => _eventController.stream; // 初始化事件监听 void _initEventChannel() { // 监听来自 Windows 原生端的方法调用 _channel.setMethodCallHandler(_handleMethodCall); } // 处理来自 Windows 原生端的方法调用 Future _handleMethodCall(MethodCall call) async { switch (call.method) { case 'onEventChange': // 原生端发送事件,转发到事件流 final String event = call.arguments as String; _eventController.add(event); return null; default: throw PlatformException( code: 'Unimplemented', message: 'Method ${call.method} not implemented', ); } } void _initWindowService() { _windowService.initialize(); _updatTrayIcon(); _setTrayMenu(); } void _initVpnService() { if (_isVpnInited) { return; } _isVpnInited = true; // 初始化vpn服务 10秒超时 _vpn.initialize(10, false); // 监听VPN服务状态 _vpn.onStatusChanged.listen((event) async { final (status, data) = event; // 处理VPN连接状态 switch (status) { case VpnStatus.connecting: _handleStateConnecting(); break; case VpnStatus.connected: _handleStateConnected(); break; case VpnStatus.error: final code = data != null ? data as int : -1; _handleStateError(code); break; case VpnStatus.idle: _handleStateDisconnected(); break; } // 更新托盘图标 _updatTrayIcon(); }); } void _handleStateConnecting() { _hasConnectedOnce = false; _sessionCountUp = 0; // 正在连接 _eventController.add( '{"type":"vpn_status","status":1,"code":0,"message":""}', ); } void _handleStateConnected() { // 只记录第一次连接成功的时间戳 if (!_hasConnectedOnce) { _sessionCountUp = 0; } _hasConnectedOnce = true; // 创建检测定时器 _checkTimer ??= Timer.periodic(const Duration(seconds: 1), (_) { // 累加1秒 _sessionCountUp += 1000; // 累减1秒 _remainTime -= 1000; // 检查用户会员剩余时间 _checkMembershipRemaining(); // 更新连接时长 _updateSessionDuration(); }); // 通知 已经连接 _eventController.add( '{"type":"vpn_status","status":2,"code":0,"message":""}', ); // 获取统计 _vpn.queryStat().then((stat) { log(_tag, 'stat: $stat'); try { final ts = DateTime.now().millisecondsSinceEpoch; final connectionHistory = List.from( (stat?['connectionHistory'] ?? []).map( (x) => ConnectionHistory.fromJson(x), ), ); final param = jsonEncode([ StatLogEntry( id: Uuid().v4(), time: ts, level: 'info', module: 'NM_BoostResult', category: 'nomo', fields: Fields( code: 0, boostSessionId: _boostSessionId, success: true, locationId: _locationId, locationCode: _locationCode, generatedTime: ts, connectionHistory: connectionHistory, ), ), ]); final nodeId = connectionHistory.last.nodeId ?? ''; final msg = jsonEncode({ "type": "boost_result", "locationCode": _locationCode, "nodeId": nodeId, "success": true, "param": param, }); log(_tag, msg); _eventController.add(msg); } catch (e) { log(_tag, 'parse stat json error: $e'); } }); } void _handleStateError(int code) { _eventController.add( '{"type":"vpn_status","status":3,"code":$code,"message":""}', ); // 获取统计 _vpn.queryStat().then((stat) { log(_tag, 'stat: $stat'); try { final ts = DateTime.now().millisecondsSinceEpoch; final connectionHistory = List.from( (stat?['connectionHistory'] ?? []).map( (x) => ConnectionHistory.fromJson(x), ), ); final param = jsonEncode([ StatLogEntry( id: Uuid().v4(), time: ts, level: 'info', module: 'NM_BoostResult', category: 'nomo', fields: Fields( code: code, boostSessionId: _boostSessionId, success: false, locationId: _locationId, locationCode: _locationCode, generatedTime: ts, connectionHistory: connectionHistory, ), ), ]); final nodeId = connectionHistory.last.nodeId ?? ''; final msg = '{"type":"boost_result","locationCode":"$_locationCode","nodeId":"$nodeId","success":false, "param": $param}'; _eventController.add(msg); } catch (e) { log(_tag, 'parse stat json error: $e'); } }); _vpn.stop(); } void _handleStateDisconnected() { _checkTimer?.cancel(); _checkTimer = null; final isNoRemainTime = _remainTime <= 0; if (isNoRemainTime) { _eventController.add( '{"type":"vpn_status","status":3,"code":${Errors.ERROR_REMAIN_TIME},"message":""}', ); } else { _eventController.add( '{"type":"vpn_status","status":0,"code":0,"message":""}', ); } } void _checkMembershipRemaining() { // 没有会员时间 if (_remainTime < 1000) { log(_tag, 'no remain time, need to disconnect.'); // 断开vpn _vpn.stop(); } } void _updateSessionDuration() { if (_isCountdown) { _eventController.add( '{"type":"timer_update","currentTime":$_remainTime,"mode":1}', ); } else { _eventController.add( '{"type":"timer_update","currentTime":$_sessionCountUp,"mode":0}', ); } } void _updatTrayIcon() { final isDark = Get.theme.brightness == Brightness.dark; // 更新提示栏 _windowService.setSystemTrayIcon( _vpn.status == VpnStatus.connected, isDark, Configs.appName, ); } void _setTrayMenu() { final trayMenu = Menu( items: [ MenuItem(label: Strings.showWindow.tr, key: 'active'), MenuItem.separator(), MenuItem(label: Strings.quitApp.tr, key: 'quit'), ], ); _windowService.setSystemTrayMenu(trayMenu, (menuItem) { if (menuItem.key == 'quit') { _windowService.quitApplication(); } else if (menuItem.key == 'active') { _windowService.activeWindow(); } }); } @override Future getApps() async { // Windows 不需要获取应用列表 return null; } @override Future getSystemLocale() async { return Platform.localeName; } @override Future connect( String sessionId, int socksPort, String tunnelConfig, String configJson, int remainTime, bool isCountdown, List allowVpnApps, List disallowVpnApps, String accessToken, String aesKey, String aesIv, int locationId, String locationCode, List baseUrls, String params, int peekTimeInterval, ) async { // 记录会员剩余时间 _remainTime = remainTime; _isCountdown = isCountdown; _locationId = locationId; _locationCode = locationCode; _boostSessionId = sessionId; String geoPath = await _getGeoDirectory(); final selfExecutable = Platform.resolvedExecutable; List allowExes = []; List disallowExes = [selfExecutable]; // 连接参数 Map params = { 'sessionId': sessionId, 'connectOptions': jsonEncode({ 'geoPath': geoPath, 'nodesConfig': configJson, }), 'allowExes': allowExes, 'disallowExes': disallowExes, }; // 连接vpn _vpn.start(params); return true; } @override Future disconnect() async { // 实现 Windows 断开连接逻辑 await _vpn.stop(); return true; } @override Future getRemoteIp() async { // 实现 Windows 获取远程 IP return await _vpn.getRemoteAddress(); } @override Future getAdvertisingId() async { // Windows 不支持广告 ID return null; } @override Future moveTaskToBack() async { // Windows 不需要此功能 return true; } @override Future isConnected() async { return _vpn.isOnline && _vpn.status == ConnectionState.connected; } @override Future getSimInfo() async { // Windows 不支持 SIM 卡信息 return null; } @override Future getChannel() async { return 'windows'; } @override Future openPackage(String packageName) async { // Windows 不支持打开应用 } /// 发送事件(供 Windows 实现内部使用) /// /// Windows 原生端可以通过 MethodChannel 发送事件: /// ```cpp /// // C++ 示例 /// flutter::MethodChannel channel( /// messenger, "app.xixi.nomo/core_api", /// &flutter::StandardMethodCodec::GetInstance()); /// /// // 发送 VPN 状态变化 /// channel.InvokeMethod("onEventChange", /// flutter::EncodableValue("{\"type\":\"vpn_status\",\"status\":2}")); /// ``` /// /// 事件 JSON 格式: /// - vpn_status: {"type":"vpn_status","status":0|1|2|3,"code":0,"message":""} /// - status: 0=idle, 1=connecting, 2=connected, 3=error /// - timer_update: {"type":"timer_update","currentTime":123,"mode":"countdown"} /// - boost_result: {"type":"boost_result","locationCode":"US","nodeId":"xxx","success":true} static void sendEvent(String event) { _eventController.add(event); } /// 释放资源 static void dispose() { _vpn.dispose(); _windowService.dispose(); _eventController.close(); } /// 获取 geo 文件目录 Future _getGeoDirectory() async { try { final appDir = await getApplicationSupportDirectory(); final geoDir = Directory(path.join(appDir.path, 'geo')); return geoDir.path; } catch (_) { return ''; } } }