Explorar o código

feat: update windows support

Tony hai 1 mes
pai
achega
0e29d801a7

+ 3 - 0
.gitignore

@@ -43,3 +43,6 @@ app.*.map.json
 /android/app/debug
 /android/app/profile
 /android/app/release
+
+cache.db
+*.lock

+ 4 - 0
lib/app/constants/errors.dart

@@ -31,4 +31,8 @@ class Errors {
   static const int ERROR_ROUTER = 1104; // 调度失败
   static const int ERROR_PERMISSION_DENIED = 1105; // 拒绝权限
   static const int ERROR_REMAIN_TIME = 1106; // 没有可用时间
+
+  // Windows 错误代码
+  static const int ERROR_RPC_CALL_FAILED = 1107; // RPC 调用失败
+  static const int ERROR_RPC_RETURN_FALSE = 1108; // RPC 调用返回False
 }

+ 11 - 13
lib/app/controllers/windows/vpn_windows_service.dart

@@ -2,6 +2,7 @@ import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 import 'package:dio/dio.dart';
+import 'package:nomo/app/constants/errors.dart';
 import 'package:shelf/shelf_io.dart' as shelf_io;
 import 'package:shelf_web_socket/shelf_web_socket.dart';
 
@@ -32,7 +33,7 @@ class VpnWindowsService {
   final _rpcDio = Dio();
 
   ConnectionState _status = ConnectionState.disconnected;
-  final _ecStatus = StreamController<ConnectionState>.broadcast();
+  final _ecStatus = StreamController<(ConnectionState, dynamic)>.broadcast();
 
   bool _isOnline = false;
   final _ecOnline = StreamController<bool>.broadcast();
@@ -49,10 +50,10 @@ class VpnWindowsService {
 
   Map<String, dynamic>? _connectionParams;
 
-  void _setStatus(ConnectionState value) {
+  void _setStatus(ConnectionState value, dynamic data) {
     if (_status != value) {
       _status = value;
-      _ecStatus.add(_status);
+      _ecStatus.add((_status, data));
     }
   }
 
@@ -153,8 +154,6 @@ class VpnWindowsService {
         runInShell: false,
         mode: ProcessStartMode.detached,
       );
-      // _process?.stdout.drain();
-      // _process?.stderr.drain();
       log(_tag, '_resetProcess: process started successfully.');
     } catch (e, stack) {
       log(_tag, '_resetProcess error: $e\nStack: $stack');
@@ -171,7 +170,6 @@ class VpnWindowsService {
   void dispose() async {
     log(_tag, 'VpnWindowsService dispose start.');
     await _shutdown();
-    //_ecTraffic.close();
     _ecStatus.close();
     _ecOnline.close();
   }
@@ -180,13 +178,13 @@ class VpnWindowsService {
     _connectionParams = null;
 
     // 通知正在连接
-    _setStatus(ConnectionState.connecting);
+    _setStatus(ConnectionState.connecting, params);
 
     try {
       final result = await _rpcPost('connect', params);
       if (!result.success) {
         log(_tag, 'start call error: ${result.message}');
-        _setStatus(ConnectionState.error);
+        _setStatus(ConnectionState.error, Errors.ERROR_RPC_RETURN_FALSE);
       } else {
         _connectionParams = params;
         log(_tag, 'start call success.');
@@ -194,7 +192,7 @@ class VpnWindowsService {
     } catch (e, stack) {
       log(_tag, 'start call exception: $e\nStack: $stack');
       // 通知连接失败
-      _setStatus(ConnectionState.error);
+      _setStatus(ConnectionState.error, Errors.ERROR_RPC_CALL_FAILED);
     }
   }
 
@@ -292,8 +290,8 @@ class VpnWindowsService {
                     return;
                   }
                   final int state = data['state'] as int;
-                  //final dynamic param = data['param']; // 错误code
-                  _setStatus(ConnectionState.values[state]);
+                  final dynamic param = data['param'];
+                  _setStatus(ConnectionState.values[state], param);
 
                   // 恢复连接
                   if (_needRecoverConnection) {
@@ -377,7 +375,7 @@ class VpnWindowsService {
 
     _isStarted = false;
     _needRecoverConnection = false;
-    _setStatus(ConnectionState.disconnected);
+    _setStatus(ConnectionState.disconnected, null);
     log(_tag, 'shutdown finished. _isStarted: $_isStarted');
   }
 
@@ -426,5 +424,5 @@ class VpnWindowsService {
 
   Stream<bool> get onOnlineChanged => _ecOnline.stream;
 
-  Stream<ConnectionState> get onStatusChanged => _ecStatus.stream;
+  Stream<(ConnectionState, dynamic)> get onStatusChanged => _ecStatus.stream;
 }

+ 128 - 59
lib/app/controllers/windows_core_api.dart

@@ -8,6 +8,7 @@ import 'package:path_provider/path_provider.dart';
 
 import '../../utils/log/logger.dart';
 import '../constants/enums.dart';
+import '../constants/errors.dart';
 import 'base_core_api.dart';
 import 'windows/vpn_windows_service.dart';
 
@@ -16,6 +17,7 @@ class WindowsCoreApi implements BaseCoreApi {
   static const _tag = 'WindowsCoreApi';
 
   WindowsCoreApi._() {
+    // 初始化事件通道
     _initEventChannel();
     // 初始化vpn服务
     _initVpnService();
@@ -24,10 +26,16 @@ class WindowsCoreApi implements BaseCoreApi {
   // 创建vpn服务
   static final _vpn = VpnWindowsService();
 
-  StreamSubscription? _ssStatus;
+  // 检查定时器
+  Timer? _checkTimer;
 
+  bool _hasConnectedOnce = false;
   bool _isVpnInited = false;
 
+  int _remainTime = 0x7FFFFFFFFFFFFFFF; // 会员剩余时间
+  int _sessionCountUp = 0; // session计时
+  bool _isCountdown = false; // 汇报计时类型 倒计时还是正计时
+
   /// 内部构造方法,供 BaseCoreApi 工厂使用
   factory WindowsCoreApi.create() => WindowsCoreApi._();
 
@@ -73,40 +81,110 @@ class WindowsCoreApi implements BaseCoreApi {
     _vpn.initialize(10, false);
 
     // 监听VPN服务状态
-    _ssStatus = _vpn.onStatusChanged.listen((event) async {
-      switch (event) {
+    _vpn.onStatusChanged.listen((event) async {
+      final (status, data) = event;
+      // 处理VPN连接状态
+      switch (status) {
         case ConnectionState.connecting:
-          // 正在连接
-          _eventController.add(
-            '{"type":"vpn_status","status":1,"code":0,"message":""}',
-          );
+          _handleStateConnecting();
           break;
-
-        // 已经连接
         case ConnectionState.connected:
-          _eventController.add(
-            '{"type":"vpn_status","status":2,"code":0,"message":""}',
-          );
+          _handleStateConnected();
           break;
-
-        // 连接错误
         case ConnectionState.error:
-          _eventController.add(
-            '{"type":"vpn_status","status":3,"code":0,"message":""}',
-          );
-          _vpn.stop();
+          final code = data != null ? data as int : -1;
+          _handleStateError(code);
           break;
-
-        // 已经断开
         case ConnectionState.disconnected:
-          _eventController.add(
-            '{"type":"vpn_status","status":0,"code":0,"message":""}',
-          );
+          _handleStateDisconnected();
           break;
       }
+    });
+  }
+
+  void _handleStateConnecting() {
+    _hasConnectedOnce = false;
+    _sessionCountUp = 0;
+    // 正在连接
+    _eventController.add(
+      '{"type":"vpn_status","status":1,"code":0,"message":""}',
+    );
+  }
 
-      // TODO: 通知状态变更
+  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":""}',
+    );
+
+    // TODO: 汇报日志
+    // _eventController.add(
+    //   '{"type":"boost_result","locationCode":"US","nodeId":"xxx","success":true}',
+    // );
+  }
+
+  void _handleStateError(int code) {
+    _eventController.add(
+      '{"type":"vpn_status","status":3,"code":$code,"message":""}',
+    );
+    _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":""}',
+      );
+    }
+
+    // TODO: 汇报日志
+    // _eventController.add(
+    //   '{"type":"boost_result","locationCode":"US","nodeId":"xxx","success":true}',
+    // );
+  }
+
+  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}',
+      );
+    }
   }
 
   @override
@@ -139,41 +217,31 @@ class WindowsCoreApi implements BaseCoreApi {
     String params,
     int peekTimeInterval,
   ) async {
-    try {
-      String geoPath = await _getGeoDirectory();
-      log(_tag, 'geoPath: $geoPath');
-
-      final selfExecutable = Platform.resolvedExecutable;
-      //List<String> apps = isSplitTunnelEnabled ? splitTunnelApps : [];
-
-      List<String> allowExes = [];
-      List<String> disallowExes = [selfExecutable];
-
-      // 连接vpn
-      Map<String, dynamic> params = {
-        'sessionId': sessionId,
-        'connectOptions': jsonEncode({
-          'geoPath': geoPath,
-          'nodesConfig': configJson,
-        }),
-        'allowExes': allowExes,
-        'disallowExes': disallowExes,
-      };
-
-      log(_tag, jsonEncode(params));
-
-      // 连接vpn
-      _vpn.start(params);
-      return true;
-    } catch (e) {
-      // 通知连接出错
-      _eventController.add(
-        '{"type":"vpn_status","status":3,"code":0,"message":""}',
-      );
-      //logger.e('get nodes error: $e');
-
-      return false;
-    }
+    // 记录会员剩余时间
+    _remainTime = remainTime;
+    _isCountdown = isCountdown;
+
+    String geoPath = await _getGeoDirectory();
+
+    final selfExecutable = Platform.resolvedExecutable;
+
+    List<String> allowExes = [];
+    List<String> disallowExes = [selfExecutable];
+
+    // 连接参数
+    Map<String, dynamic> params = {
+      'sessionId': sessionId,
+      'connectOptions': jsonEncode({
+        'geoPath': geoPath,
+        'nodesConfig': configJson,
+      }),
+      'allowExes': allowExes,
+      'disallowExes': disallowExes,
+    };
+
+    // 连接vpn
+    _vpn.start(params);
+    return true;
   }
 
   @override
@@ -243,6 +311,7 @@ class WindowsCoreApi implements BaseCoreApi {
 
   /// 释放资源
   static void dispose() {
+    _vpn.dispose();
     _eventController.close();
   }