windows_core_api.dart 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'package:flutter/services.dart';
  5. import 'package:path/path.dart' as path;
  6. import 'package:path_provider/path_provider.dart';
  7. import '../../utils/log/logger.dart';
  8. import '../constants/enums.dart';
  9. import '../constants/errors.dart';
  10. import 'base_core_api.dart';
  11. import 'windows/vpn_windows_service.dart';
  12. /// Windows 实现
  13. class WindowsCoreApi implements BaseCoreApi {
  14. static const _tag = 'WindowsCoreApi';
  15. WindowsCoreApi._() {
  16. // 初始化事件通道
  17. _initEventChannel();
  18. // 初始化vpn服务
  19. _initVpnService();
  20. }
  21. // 创建vpn服务
  22. static final _vpn = VpnWindowsService();
  23. // 检查定时器
  24. Timer? _checkTimer;
  25. bool _hasConnectedOnce = false;
  26. bool _isVpnInited = false;
  27. int _remainTime = 0x7FFFFFFFFFFFFFFF; // 会员剩余时间
  28. int _sessionCountUp = 0; // session计时
  29. bool _isCountdown = false; // 汇报计时类型 倒计时还是正计时
  30. /// 内部构造方法,供 BaseCoreApi 工厂使用
  31. factory WindowsCoreApi.create() => WindowsCoreApi._();
  32. // Windows Method Channel
  33. static const MethodChannel _channel = MethodChannel('app.xixi.nomo/core_api');
  34. // Windows 事件流控制器
  35. static final StreamController<String> _eventController =
  36. StreamController<String>.broadcast();
  37. // Windows 事件流
  38. static Stream<String> get eventStream => _eventController.stream;
  39. // 初始化事件监听
  40. void _initEventChannel() {
  41. // 监听来自 Windows 原生端的方法调用
  42. _channel.setMethodCallHandler(_handleMethodCall);
  43. }
  44. // 处理来自 Windows 原生端的方法调用
  45. Future<dynamic> _handleMethodCall(MethodCall call) async {
  46. switch (call.method) {
  47. case 'onEventChange':
  48. // 原生端发送事件,转发到事件流
  49. final String event = call.arguments as String;
  50. _eventController.add(event);
  51. return null;
  52. default:
  53. throw PlatformException(
  54. code: 'Unimplemented',
  55. message: 'Method ${call.method} not implemented',
  56. );
  57. }
  58. }
  59. void _initVpnService() {
  60. if (_isVpnInited) {
  61. return;
  62. }
  63. _isVpnInited = true;
  64. // 初始化vpn服务 10秒超时
  65. _vpn.initialize(10, false);
  66. // 监听VPN服务状态
  67. _vpn.onStatusChanged.listen((event) async {
  68. final (status, data) = event;
  69. // 处理VPN连接状态
  70. switch (status) {
  71. case ConnectionState.connecting:
  72. _handleStateConnecting();
  73. break;
  74. case ConnectionState.connected:
  75. _handleStateConnected();
  76. break;
  77. case ConnectionState.error:
  78. final code = data != null ? data as int : -1;
  79. _handleStateError(code);
  80. break;
  81. case ConnectionState.disconnected:
  82. _handleStateDisconnected();
  83. break;
  84. }
  85. });
  86. }
  87. void _handleStateConnecting() {
  88. _hasConnectedOnce = false;
  89. _sessionCountUp = 0;
  90. // 正在连接
  91. _eventController.add(
  92. '{"type":"vpn_status","status":1,"code":0,"message":""}',
  93. );
  94. }
  95. void _handleStateConnected() {
  96. // 只记录第一次连接成功的时间戳
  97. if (!_hasConnectedOnce) {
  98. _sessionCountUp = 0;
  99. }
  100. _hasConnectedOnce = true;
  101. // 创建检测定时器
  102. _checkTimer ??= Timer.periodic(const Duration(seconds: 1), (_) {
  103. // 累加1秒
  104. _sessionCountUp += 1000;
  105. // 累减1秒
  106. _remainTime -= 1000;
  107. // 检查用户会员剩余时间
  108. _checkMembershipRemaining();
  109. // 更新连接时长
  110. _updateSessionDuration();
  111. });
  112. // 通知 已经连接
  113. _eventController.add(
  114. '{"type":"vpn_status","status":2,"code":0,"message":""}',
  115. );
  116. // TODO: 汇报日志
  117. // _eventController.add(
  118. // '{"type":"boost_result","locationCode":"US","nodeId":"xxx","success":true}',
  119. // );
  120. }
  121. void _handleStateError(int code) {
  122. _eventController.add(
  123. '{"type":"vpn_status","status":3,"code":$code,"message":""}',
  124. );
  125. _vpn.stop();
  126. }
  127. void _handleStateDisconnected() {
  128. _checkTimer?.cancel();
  129. _checkTimer = null;
  130. final isNoRemainTime = _remainTime <= 0;
  131. if (isNoRemainTime) {
  132. _eventController.add(
  133. '{"type":"vpn_status","status":3,"code":${Errors.ERROR_REMAIN_TIME},"message":""}',
  134. );
  135. } else {
  136. _eventController.add(
  137. '{"type":"vpn_status","status":0,"code":0,"message":""}',
  138. );
  139. }
  140. // TODO: 汇报日志
  141. // _eventController.add(
  142. // '{"type":"boost_result","locationCode":"US","nodeId":"xxx","success":true}',
  143. // );
  144. }
  145. void _checkMembershipRemaining() {
  146. // 没有会员时间
  147. if (_remainTime < 1000) {
  148. log(_tag, 'no remain time, need to disconnect.');
  149. // 断开vpn
  150. _vpn.stop();
  151. }
  152. }
  153. void _updateSessionDuration() {
  154. if (_isCountdown) {
  155. _eventController.add(
  156. '{"type":"timer_update","currentTime":$_remainTime,"mode":1}',
  157. );
  158. } else {
  159. _eventController.add(
  160. '{"type":"timer_update","currentTime":$_sessionCountUp,"mode":0}',
  161. );
  162. }
  163. }
  164. @override
  165. Future<String?> getApps() async {
  166. // Windows 不需要获取应用列表
  167. return null;
  168. }
  169. @override
  170. Future<String?> getSystemLocale() async {
  171. return Platform.localeName;
  172. }
  173. @override
  174. Future<bool?> connect(
  175. String sessionId,
  176. int socksPort,
  177. String tunnelConfig,
  178. String configJson,
  179. int remainTime,
  180. bool isCountdown,
  181. List<String> allowVpnApps,
  182. List<String> disallowVpnApps,
  183. String accessToken,
  184. String aesKey,
  185. String aesIv,
  186. int locationId,
  187. String locationCode,
  188. List<String> baseUrls,
  189. String params,
  190. int peekTimeInterval,
  191. ) async {
  192. // 记录会员剩余时间
  193. _remainTime = remainTime;
  194. _isCountdown = isCountdown;
  195. String geoPath = await _getGeoDirectory();
  196. final selfExecutable = Platform.resolvedExecutable;
  197. List<String> allowExes = [];
  198. List<String> disallowExes = [selfExecutable];
  199. // 连接参数
  200. Map<String, dynamic> params = {
  201. 'sessionId': sessionId,
  202. 'connectOptions': jsonEncode({
  203. 'geoPath': geoPath,
  204. 'nodesConfig': configJson,
  205. }),
  206. 'allowExes': allowExes,
  207. 'disallowExes': disallowExes,
  208. };
  209. // 连接vpn
  210. _vpn.start(params);
  211. return true;
  212. }
  213. @override
  214. Future<bool?> disconnect() async {
  215. // 实现 Windows 断开连接逻辑
  216. await _vpn.stop();
  217. return true;
  218. }
  219. @override
  220. Future<String?> getRemoteIp() async {
  221. // 实现 Windows 获取远程 IP
  222. return await _vpn.getRemoteAddress();
  223. }
  224. @override
  225. Future<String?> getAdvertisingId() async {
  226. // Windows 不支持广告 ID
  227. return null;
  228. }
  229. @override
  230. Future<bool?> moveTaskToBack() async {
  231. // Windows 不需要此功能
  232. return true;
  233. }
  234. @override
  235. Future<bool?> isConnected() async {
  236. return _vpn.isOnline && _vpn.status == ConnectionState.connected;
  237. }
  238. @override
  239. Future<String?> getSimInfo() async {
  240. // Windows 不支持 SIM 卡信息
  241. return null;
  242. }
  243. @override
  244. Future<String?> getChannel() async {
  245. // TODO: 实现 Windows 渠道获取
  246. return 'windows';
  247. }
  248. /// 发送事件(供 Windows 实现内部使用)
  249. ///
  250. /// Windows 原生端可以通过 MethodChannel 发送事件:
  251. /// ```cpp
  252. /// // C++ 示例
  253. /// flutter::MethodChannel<flutter::EncodableValue> channel(
  254. /// messenger, "app.xixi.nomo/core_api",
  255. /// &flutter::StandardMethodCodec::GetInstance());
  256. ///
  257. /// // 发送 VPN 状态变化
  258. /// channel.InvokeMethod("onEventChange",
  259. /// flutter::EncodableValue("{\"type\":\"vpn_status\",\"status\":2}"));
  260. /// ```
  261. ///
  262. /// 事件 JSON 格式:
  263. /// - vpn_status: {"type":"vpn_status","status":0|1|2|3,"code":0,"message":""}
  264. /// - status: 0=idle, 1=connecting, 2=connected, 3=error
  265. /// - timer_update: {"type":"timer_update","currentTime":123,"mode":"countdown"}
  266. /// - boost_result: {"type":"boost_result","locationCode":"US","nodeId":"xxx","success":true}
  267. static void sendEvent(String event) {
  268. _eventController.add(event);
  269. }
  270. /// 释放资源
  271. static void dispose() {
  272. _vpn.dispose();
  273. _eventController.close();
  274. }
  275. /// 获取 geo 文件目录
  276. Future<String> _getGeoDirectory() async {
  277. try {
  278. final appDir = await getApplicationSupportDirectory();
  279. final geoDir = Directory(path.join(appDir.path, 'geo'));
  280. return geoDir.path;
  281. } catch (_) {
  282. return '';
  283. }
  284. }
  285. }