| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- import 'dart:async';
- import 'dart:convert';
- import 'dart:io';
- import 'package:flutter/services.dart';
- import 'package:path/path.dart' as path;
- 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';
- /// Windows 实现
- class WindowsCoreApi implements BaseCoreApi {
- static const _tag = 'WindowsCoreApi';
- WindowsCoreApi._() {
- // 初始化事件通道
- _initEventChannel();
- // 初始化vpn服务
- _initVpnService();
- }
- // 创建vpn服务
- static final _vpn = VpnWindowsService();
- // 检查定时器
- Timer? _checkTimer;
- bool _hasConnectedOnce = false;
- bool _isVpnInited = false;
- int _remainTime = 0x7FFFFFFFFFFFFFFF; // 会员剩余时间
- int _sessionCountUp = 0; // session计时
- bool _isCountdown = false; // 汇报计时类型 倒计时还是正计时
- /// 内部构造方法,供 BaseCoreApi 工厂使用
- factory WindowsCoreApi.create() => WindowsCoreApi._();
- // Windows Method Channel
- static const MethodChannel _channel = MethodChannel('app.xixi.nomo/core_api');
- // Windows 事件流控制器
- static final StreamController<String> _eventController =
- StreamController<String>.broadcast();
- // Windows 事件流
- static Stream<String> get eventStream => _eventController.stream;
- // 初始化事件监听
- void _initEventChannel() {
- // 监听来自 Windows 原生端的方法调用
- _channel.setMethodCallHandler(_handleMethodCall);
- }
- // 处理来自 Windows 原生端的方法调用
- Future<dynamic> _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 _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 ConnectionState.connecting:
- _handleStateConnecting();
- break;
- case ConnectionState.connected:
- _handleStateConnected();
- break;
- case ConnectionState.error:
- final code = data != null ? data as int : -1;
- _handleStateError(code);
- break;
- case ConnectionState.disconnected:
- _handleStateDisconnected();
- break;
- }
- });
- }
- 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":""}',
- );
- // 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
- Future<String?> getApps() async {
- // Windows 不需要获取应用列表
- return null;
- }
- @override
- Future<String?> getSystemLocale() async {
- return Platform.localeName;
- }
- @override
- Future<bool?> connect(
- String sessionId,
- int socksPort,
- String tunnelConfig,
- String configJson,
- int remainTime,
- bool isCountdown,
- List<String> allowVpnApps,
- List<String> disallowVpnApps,
- String accessToken,
- String aesKey,
- String aesIv,
- int locationId,
- String locationCode,
- List<String> baseUrls,
- String params,
- int peekTimeInterval,
- ) async {
- // 记录会员剩余时间
- _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
- Future<bool?> disconnect() async {
- // 实现 Windows 断开连接逻辑
- await _vpn.stop();
- return true;
- }
- @override
- Future<String?> getRemoteIp() async {
- // 实现 Windows 获取远程 IP
- return await _vpn.getRemoteAddress();
- }
- @override
- Future<String?> getAdvertisingId() async {
- // Windows 不支持广告 ID
- return null;
- }
- @override
- Future<bool?> moveTaskToBack() async {
- // Windows 不需要此功能
- return true;
- }
- @override
- Future<bool?> isConnected() async {
- return _vpn.isOnline && _vpn.status == ConnectionState.connected;
- }
- @override
- Future<String?> getSimInfo() async {
- // Windows 不支持 SIM 卡信息
- return null;
- }
- @override
- Future<String?> getChannel() async {
- // TODO: 实现 Windows 渠道获取
- return 'windows';
- }
- @override
- Future<void> openPackage(String packageName) async {
- // TODO: 实现 Windows 打开应用
- }
- /// 发送事件(供 Windows 实现内部使用)
- ///
- /// Windows 原生端可以通过 MethodChannel 发送事件:
- /// ```cpp
- /// // C++ 示例
- /// flutter::MethodChannel<flutter::EncodableValue> 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();
- _eventController.close();
- }
- /// 获取 geo 文件目录
- Future<String> _getGeoDirectory() async {
- try {
- final appDir = await getApplicationSupportDirectory();
- final geoDir = Directory(path.join(appDir.path, 'geo'));
- return geoDir.path;
- } catch (_) {
- return '';
- }
- }
- }
|