windows_core_api.dart 9.6 KB

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