|
|
@@ -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();
|
|
|
}
|
|
|
|