diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7d94389..5e2ce8f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -37,6 +37,14 @@ + + + + + + + exitProcess(0) + } + .setCancelable(false) + .create() + dialog.show() + } + +} \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 329d614..3c2b0e8 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -10,13 +10,6 @@ import NetworkExtension ) -> Bool { GeneratedPluginRegistrant.register(with: self) -// let url = URL(string: "http://www.baidu.com")! -// let task = URLSession.shared.dataTask(with: url) {(data, response, error) in -// guard let data = data else { return } -// print(String(data: data, encoding: .utf8)!) -// } -// task.resume() - let controller: FlutterViewController = window.rootViewController as! FlutterViewController ; let batteryChannel = FlutterMethodChannel.init(name: "com.proxy/proxyVpn", binaryMessenger: controller as! FlutterBinaryMessenger); batteryChannel.setMethodCallHandler({ @@ -54,14 +47,11 @@ import NetworkExtension } } - var backgroundUpdateTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0) - func endBackgroundUpdateTask() { - AudioManager.shared.openBackgroundAudioAutoplay = false - UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask) - self.backgroundUpdateTask = UIBackgroundTaskIdentifier.invalid - } - override func applicationWillResignActive(_ application: UIApplication) { + if (!VpnManager.shared.isRunning()) { + return + } + AudioManager.shared.openBackgroundAudioAutoplay = true self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: { self.endBackgroundUpdateTask() @@ -69,7 +59,17 @@ import NetworkExtension } override func applicationDidBecomeActive(_ application: UIApplication) { self.endBackgroundUpdateTask() - + } + + var backgroundUpdateTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0) + func endBackgroundUpdateTask() { + if (!VpnManager.shared.isRunning()) { + return + } + + AudioManager.shared.openBackgroundAudioAutoplay = false + UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask) + self.backgroundUpdateTask = UIBackgroundTaskIdentifier.invalid } } diff --git a/ios/Runner/VpnManager.swift b/ios/Runner/VpnManager.swift index 381c8a0..7840898 100755 --- a/ios/Runner/VpnManager.swift +++ b/ios/Runner/VpnManager.swift @@ -169,5 +169,9 @@ extension VpnManager{ } } + + func isRunning() -> Bool { + return vpnStatus == VPNStatus.on + } } diff --git a/lib/main.dart b/lib/main.dart index 6c9e9d9..223eb03 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:chinese_font_library/chinese_font_library.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/ui/component/split_view.dart'; import 'package:network_proxy/ui/content/body.dart'; @@ -20,11 +21,6 @@ import 'network/handler.dart'; import 'network/http/http.dart'; void main(List args) async { - if (Platforms.isMobile()) { - runApp(const FluentApp(MobileHomePage())); - return; - } - //多窗口 if (args.firstOrNull == 'multi_window') { final windowId = int.parse(args[1]); @@ -34,6 +30,13 @@ void main(List args) async { } WidgetsFlutterBinding.ensureInitialized(); + + var configuration = Configuration.instance; + if (Platforms.isMobile()) { + runApp(FluentApp(MobileHomePage(configuration: (await configuration)))); + return; + } + await windowManager.ensureInitialized(); //设置窗口大小 WindowOptions windowOptions = WindowOptions( @@ -46,7 +49,7 @@ void main(List args) async { await windowManager.focus(); }); - runApp(const FluentApp(DesktopHomePage())); + runApp(FluentApp(DesktopHomePage(configuration: (await configuration)))); } ///多窗口 @@ -104,7 +107,9 @@ class FluentApp extends StatelessWidget { } class DesktopHomePage extends StatefulWidget { - const DesktopHomePage({super.key}); + final Configuration configuration; + + const DesktopHomePage({super.key, required this.configuration}); @override State createState() => _DesktopHomePagePageState(); @@ -129,13 +134,10 @@ class _DesktopHomePagePageState extends State implements EventL @override void initState() { super.initState(); - proxyServer = ProxyServer(listener: this); + proxyServer = ProxyServer(widget.configuration, listener: this); panel = NetworkTabController(tabStyle: const TextStyle(fontSize: 18), proxyServer: proxyServer); - proxyServer.initializedListener(() { - if (!proxyServer.guide) { - return; - } + if (widget.configuration.guide) { //首次引导 showDialog( context: context, @@ -145,8 +147,8 @@ class _DesktopHomePagePageState extends State implements EventL actions: [ TextButton( onPressed: () { - proxyServer.guide = false; - proxyServer.flushConfig(); + widget.configuration.guide = false; + widget.configuration.flushConfig(); Navigator.pop(context); }, child: const Text('关闭')) @@ -155,7 +157,9 @@ class _DesktopHomePagePageState extends State implements EventL content: const Text('默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n' '点击的HTTPS抓包(加锁图标),选择安装根证书,按照提示操作即可。')); }); - }); + + return; + } } @override diff --git a/lib/network/bin/configuration.dart b/lib/network/bin/configuration.dart new file mode 100644 index 0000000..9f21a41 --- /dev/null +++ b/lib/network/bin/configuration.dart @@ -0,0 +1,142 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:network_proxy/network/util/host_filter.dart'; +import 'package:network_proxy/network/util/logger.dart'; +import 'package:network_proxy/network/util/request_rewrite.dart'; +import 'package:network_proxy/utils/platform.dart'; +import 'package:path_provider/path_provider.dart'; + +class Configuration { + int port = 9099; + + //是否启用https抓包 + bool enableSsl = false; + + //是否启用桌面抓包 + bool enableDesktop = true; + + //是否引导 + bool guide = false; + + //是否显示更新内容公告 + bool upgradeNotice = true; + + //请求重写 + RequestRewrites requestRewrites = RequestRewrites(); + + Configuration._(); + + /// 单例 + static Configuration? _instance; + + static Future get instance async { + if (_instance == null) { + Configuration configuration = Configuration._(); + await configuration.initConfig(); + _instance = configuration; + } + return _instance!; + } + + /// 初始化配置 + Future initConfig() async { + // 读取配置文件 + await _loadConfig(); + } + + Future homeDir() async { + String? userHome; + if (Platforms.isDesktop()) { + userHome = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']; + } else { + userHome = (await getApplicationSupportDirectory()).path; + } + + var separator = Platform.pathSeparator; + return File("${userHome!}$separator.proxypin"); + } + + /// 配置文件 + Future configFile() async { + var separator = Platform.pathSeparator; + var home = await homeDir(); + return File("${home.path}${separator}config.cnf"); + } + + /// 刷新配置文件 + flushConfig() async { + var file = await configFile(); + var exists = await file.exists(); + if (!exists) { + file = await file.create(recursive: true); + } + HostFilter.whitelist.toJson(); + HostFilter.blacklist.toJson(); + var json = jsonEncode(toJson()); + logger.i('刷新配置文件 $runtimeType ${toJson()}'); + file.writeAsString(json); + } + + /// 加载配置文件 + Future _loadConfig() async { + var file = await configFile(); + var exits = await file.exists(); + if (!exits) { + guide = true; + return; + } + + Map config = jsonDecode(await file.readAsString()); + logger.i('加载配置文件 [$file]'); + port = config['port'] ?? port; + enableSsl = config['enableSsl'] == true; + enableDesktop = config['enableDesktop'] ?? true; + guide = config['guide'] ?? false; + upgradeNotice = config['upgradeNotice'] ?? true; + HostFilter.whitelist.load(config['whitelist']); + HostFilter.blacklist.load(config['blacklist']); + + await _loadRequestRewriteConfig(); + } + + /// 加载请求重写配置文件 + Future _loadRequestRewriteConfig() async { + var home = await homeDir(); + var file = File('${home.path}${Platform.pathSeparator}request_rewrite.json'); + var exits = await file.exists(); + if (!exits) { + return; + } + + Map config = jsonDecode(await file.readAsString()); + + logger.i('加载请求重写配置文件 [$file]'); + requestRewrites.load(config); + } + + /// 保存请求重写配置文件 + flushRequestRewriteConfig() async { + var home = await homeDir(); + var file = File('${home.path}${Platform.pathSeparator}request_rewrite.json'); + bool exists = await file.exists(); + if (!exists) { + await file.create(recursive: true); + } + var json = jsonEncode(requestRewrites.toJson()); + logger.i('刷新请求重写配置文件 ${file.path}'); + file.writeAsString(json); + } + + Map toJson() { + return { + 'guide': guide, + 'upgradeNotice': upgradeNotice, + 'port': port, + 'enableSsl': enableSsl, + 'enableDesktop': enableDesktop, + 'whitelist': HostFilter.whitelist.toJson(), + 'blacklist': HostFilter.blacklist.toJson(), + }; + } +} diff --git a/lib/network/bin/server.dart b/lib/network/bin/server.dart index b6585da..3e4ffef 100644 --- a/lib/network/bin/server.dart +++ b/lib/network/bin/server.dart @@ -1,82 +1,39 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; -import 'package:network_proxy/network/util/host_filter.dart'; -import 'package:network_proxy/utils/platform.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import '../channel.dart'; import '../handler.dart'; import '../http/codec.dart'; import '../util/logger.dart'; -import '../util/request_rewrite.dart'; import '../util/system_proxy.dart'; Future main() async { - ProxyServer().start(); + var configuration = await Configuration.instance; + ProxyServer(configuration).start(); } /// 代理服务器 class ProxyServer { - //是否初始化 - bool init = false; - int port = 9099; - - //是否启用https抓包 - bool _enableSsl = false; - - //是否启用桌面抓包 - bool enableDesktop = true; - - //是否引导 - bool guide = false; - //是否启动 bool get isRunning => server?.isRunning ?? false; - Server? server; //请求事件监听 EventListener? listener; - //请求重写 - RequestRewrites requestRewrites = RequestRewrites(); + final Configuration configuration; - final List _initializedListeners = []; - - ProxyServer({this.listener}); - - //初始化 - Future initializedListener(Function action) async { - _initializedListeners.add(action); - } - - Future homeDir() async { - String? userHome; - if (Platforms.isDesktop()) { - userHome = - Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']; - } else { - userHome = (await getApplicationSupportDirectory()).path; - } - - var separator = Platform.pathSeparator; - return File("${userHome!}$separator.proxypin"); - } - - /// 配置文件 - Future configFile() async { - var separator = Platform.pathSeparator; - var home = await homeDir(); - return File("${home.path}${separator}config.cnf"); - } + ProxyServer(this.configuration, {this.listener}); ///是否启用https抓包 - bool get enableSsl => _enableSsl; + bool get enableSsl => configuration.enableSsl; + + int get port => configuration.port; set enableSsl(bool enableSsl) { - _enableSsl = enableSsl; + configuration.enableSsl = enableSsl; server?.enableSsl = enableSsl; if (server == null || server?.isRunning == false) { return; @@ -90,27 +47,16 @@ class ProxyServer { /// 启动代理服务 Future start() async { Server server = Server(); - if (!init) { - // 读取配置文件 - init = true; - await _loadConfig(); - for (var element in _initializedListeners) { - element.call(); - } - } - server.enableSsl = _enableSsl; + server.enableSsl = configuration.enableSsl; server.initChannel((channel) { - channel.pipeline.handle( - HttpRequestCodec(), - HttpResponseCodec(), - HttpChannelHandler( - listener: listener, requestRewrites: requestRewrites)); + channel.pipeline.handle(HttpRequestCodec(), HttpResponseCodec(), + HttpChannelHandler(listener: listener, requestRewrites: configuration.requestRewrites)); }); return server.bind(port).then((serverSocket) { logger.i("listen on $port"); this.server = server; - if (enableDesktop) { + if (configuration.enableDesktop) { SystemProxy.setSystemProxy(port, enableSsl); } return server; @@ -120,7 +66,7 @@ class ProxyServer { /// 停止代理服务 Future stop() async { logger.i("stop on $port"); - if (enableDesktop) { + if (configuration.enableDesktop) { if (Platform.isMacOS) { await SystemProxy.setProxyEnableMacOS(false, enableSsl); } else if (Platform.isWindows) { @@ -135,80 +81,4 @@ class ProxyServer { restart() { stop().then((value) => start()); } - - /// 刷新配置文件 - flushConfig() async { - var file = await configFile(); - var exists = await file.exists(); - if (!exists) { - file = await file.create(recursive: true); - } - HostFilter.whitelist.toJson(); - HostFilter.blacklist.toJson(); - var json = jsonEncode(toJson()); - logger.i('刷新配置文件 $runtimeType ${toJson()}'); - file.writeAsString(json); - } - - /// 加载配置文件 - Future _loadConfig() async { - var file = await configFile(); - var exits = await file.exists(); - if (!exits) { - guide = true; - return; - } - - Map config = jsonDecode(await file.readAsString()); - logger.i('加载配置文件 [$file]'); - port = config['port'] ?? port; - enableSsl = config['enableSsl'] == true; - enableDesktop = config['enableDesktop'] ?? true; - guide = config['guide'] ?? false; - HostFilter.whitelist.load(config['whitelist']); - HostFilter.blacklist.load(config['blacklist']); - - await _loadRequestRewriteConfig(); - } - - /// 加载请求重写配置文件 - Future _loadRequestRewriteConfig() async { - var home = await homeDir(); - var file = - File('${home.path}${Platform.pathSeparator}request_rewrite.json'); - var exits = await file.exists(); - if (!exits) { - return; - } - - Map config = jsonDecode(await file.readAsString()); - - logger.i('加载请求重写配置文件 [$file]'); - requestRewrites.load(config); - } - - /// 保存请求重写配置文件 - flushRequestRewriteConfig() async { - var home = await homeDir(); - var file = - File('${home.path}${Platform.pathSeparator}request_rewrite.json'); - bool exists = await file.exists(); - if (!exists) { - await file.create(recursive: true); - } - var json = jsonEncode(requestRewrites.toJson()); - logger.i('刷新请求重写配置文件 ${file.path}'); - file.writeAsString(json); - } - - Map toJson() { - return { - 'guide': guide, - 'port': port, - 'enableSsl': enableSsl, - 'enableDesktop': enableDesktop, - 'whitelist': HostFilter.whitelist.toJson(), - 'blacklist': HostFilter.blacklist.toJson(), - }; - } } diff --git a/lib/ui/content/body.dart b/lib/ui/content/body.dart index d9aca40..9c45a2a 100644 --- a/lib/ui/content/body.dart +++ b/lib/ui/content/body.dart @@ -125,7 +125,7 @@ class HttpBodyState extends State { var size = MediaQuery.of(context).size; var ratio = 1.0; if (Platform.isWindows) { - WindowManager.instance.getDevicePixelRatio(); + ratio = WindowManager.instance.getDevicePixelRatio(); } final window = await DesktopMultiWindow.createWindow(jsonEncode( {'name': 'HttpBodyWidget', 'httpMessage': widget.httpMessage, 'inNewWindow': true}, diff --git a/lib/ui/desktop/left/domain.dart b/lib/ui/desktop/left/domain.dart index 7efd5e4..b44d342 100644 --- a/lib/ui/desktop/left/domain.dart +++ b/lib/ui/desktop/left/domain.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/channel.dart'; import 'package:network_proxy/network/http/http.dart'; @@ -154,7 +155,15 @@ class HeaderBody extends StatefulWidget { ///根据文本过滤 Iterable filter(String text) { - return _body.where((element) => element.request.requestUrl.toLowerCase().contains(text)); + return _body.where((element) { + if (element.request.method.name.toLowerCase() == text) { + return true; + } + if (element.request.requestUrl.toLowerCase().contains(text)) { + return true; + } + return element.response.get()?.contentType.name.toLowerCase().contains(text) == true; + }); } ///复制 @@ -175,12 +184,13 @@ class HeaderBody extends StatefulWidget { class _HeaderBodyState extends State { final GlobalKey transitionState = GlobalKey(); - + late Configuration configuration; late bool selected; @override void initState() { super.initState(); + configuration = widget.proxyServer.configuration; selected = widget.selected; } @@ -241,21 +251,21 @@ class _HeaderBodyState extends State { child: const Text("添加黑名单", style: TextStyle(fontSize: 14)), onTap: () { HostFilter.blacklist.add(widget.header.host); - widget.proxyServer.flushConfig(); + configuration.flushConfig(); }), PopupMenuItem( height: 38, child: const Text("添加白名单", style: TextStyle(fontSize: 14)), onTap: () { HostFilter.whitelist.add(widget.header.host); - widget.proxyServer.flushConfig(); + configuration.flushConfig(); }), PopupMenuItem( height: 38, child: const Text("删除白名单", style: TextStyle(fontSize: 14)), onTap: () { HostFilter.whitelist.remove(widget.header.host); - widget.proxyServer.flushConfig(); + configuration.flushConfig(); }), PopupMenuItem(height: 38, child: const Text("删除", style: TextStyle(fontSize: 14)), onTap: () => _delete()), ], diff --git a/lib/ui/desktop/left/path.dart b/lib/ui/desktop/left/path.dart index 4b55ae1..c744628 100644 --- a/lib/ui/desktop/left/path.dart +++ b/lib/ui/desktop/left/path.dart @@ -131,7 +131,7 @@ class _PathRowState extends State { var size = MediaQuery.of(context).size; var ratio = 1.0; if (Platform.isWindows) { - WindowManager.instance.getDevicePixelRatio(); + ratio = WindowManager.instance.getDevicePixelRatio(); } final window = await DesktopMultiWindow.createWindow(jsonEncode( diff --git a/lib/ui/desktop/toolbar/setting/filter.dart b/lib/ui/desktop/toolbar/setting/filter.dart index cdb2dd8..c067043 100644 --- a/lib/ui/desktop/toolbar/setting/filter.dart +++ b/lib/ui/desktop/toolbar/setting/filter.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/util/host_filter.dart'; - class FilterDialog extends StatefulWidget { - final ProxyServer proxyServer; + final Configuration configuration; - const FilterDialog({super.key, required this.proxyServer}); + const FilterDialog({super.key, required this.configuration}); @override State createState() => _FilterDialogState(); @@ -49,7 +48,7 @@ class _FilterDialogState extends State { title: "白名单", subtitle: "只代理白名单中的域名, 白名单启用黑名单将会失效", hostList: HostFilter.whitelist, - proxyServer: widget.proxyServer, + configuration: widget.configuration, hostEnableNotifier: hostEnableNotifier)), const SizedBox(width: 10), Expanded( @@ -58,7 +57,7 @@ class _FilterDialogState extends State { title: "黑名单", subtitle: "黑名单中的域名不会代理", hostList: HostFilter.blacklist, - proxyServer: widget.proxyServer, + configuration: widget.configuration, hostEnableNotifier: hostEnableNotifier)), ], ), @@ -70,7 +69,7 @@ class DomainFilter extends StatefulWidget { final String title; final String subtitle; final HostList hostList; - final ProxyServer proxyServer; + final Configuration configuration; final ValueNotifier hostEnableNotifier; const DomainFilter( @@ -79,7 +78,7 @@ class DomainFilter extends StatefulWidget { required this.subtitle, required this.hostList, required this.hostEnableNotifier, - required this.proxyServer}); + required this.configuration}); @override State createState() { @@ -145,7 +144,7 @@ class _DomainFilterState extends State { @override void dispose() { if (changed) { - widget.proxyServer.flushConfig(); + widget.configuration.flushConfig(); } super.dispose(); } diff --git a/lib/ui/desktop/toolbar/setting/request_rewrite.dart b/lib/ui/desktop/toolbar/setting/request_rewrite.dart index 3f128b5..a491a10 100644 --- a/lib/ui/desktop/toolbar/setting/request_rewrite.dart +++ b/lib/ui/desktop/toolbar/setting/request_rewrite.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/util/request_rewrite.dart'; class RequestRewrite extends StatefulWidget { - final ProxyServer proxyServer; + final Configuration configuration; - const RequestRewrite({super.key, required this.proxyServer}); + const RequestRewrite({super.key, required this.configuration}); @override State createState() => _RequestRewriteState(); @@ -19,15 +19,15 @@ class _RequestRewriteState extends State { @override void initState() { super.initState(); - requestRuleList = RequestRuleList(widget.proxyServer.requestRewrites); - enableNotifier = ValueNotifier(widget.proxyServer.requestRewrites.enabled == true); + requestRuleList = RequestRuleList(widget.configuration.requestRewrites); + enableNotifier = ValueNotifier(widget.configuration.requestRewrites.enabled == true); } @override void dispose() { - if (changed || enableNotifier.value != widget.proxyServer.requestRewrites.enabled) { - widget.proxyServer.requestRewrites.enabled = enableNotifier.value; - widget.proxyServer.flushRequestRewriteConfig(); + if (changed || enableNotifier.value != widget.configuration.requestRewrites.enabled) { + widget.configuration.requestRewrites.enabled = enableNotifier.value; + widget.configuration.flushRequestRewriteConfig(); } enableNotifier.dispose(); @@ -80,7 +80,7 @@ class _RequestRewriteState extends State { changed = true; setState(() { - widget.proxyServer.requestRewrites.removeIndex(removeSelected); + widget.configuration.requestRewrites.removeIndex(removeSelected); requestRuleList.changeState(); }); }) @@ -97,7 +97,7 @@ class _RequestRewriteState extends State { barrierDismissible: false, builder: (BuildContext context) { return RuleAddDialog( - requestRewrites: widget.proxyServer.requestRewrites, + requestRewrites: widget.configuration.requestRewrites, currentIndex: currentIndex, onChange: () { changed = true; @@ -250,7 +250,7 @@ class _RequestRuleListState extends State { border: TableBorder.symmetric(outside: BorderSide(width: 1, color: Theme.of(context).highlightColor)), columns: const [ DataColumn(label: Text('启用')), - DataColumn(label: Text('URL')), + DataColumn(label: Text('Path')), DataColumn(label: Text('请求体')), DataColumn(label: Text('响应体')), ], diff --git a/lib/ui/desktop/toolbar/setting/setting.dart b/lib/ui/desktop/toolbar/setting/setting.dart index 06248d3..a809b80 100644 --- a/lib/ui/desktop/toolbar/setting/setting.dart +++ b/lib/ui/desktop/toolbar/setting/setting.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/util/system_proxy.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/request_rewrite.dart'; @@ -20,10 +21,12 @@ class Setting extends StatefulWidget { class _SettingState extends State { late ValueNotifier enableDesktopListenable; + late Configuration configuration; @override void initState() { - enableDesktopListenable = ValueNotifier(widget.proxyServer.enableDesktop); + configuration = widget.proxyServer.configuration; + enableDesktopListenable = ValueNotifier(configuration.enableDesktop); super.initState(); } @@ -54,12 +57,12 @@ class _SettingState extends State { title: const Text("抓取电脑请求"), visualDensity: const VisualDensity(horizontal: -4), dense: true, - value: widget.proxyServer.enableDesktop, + value: configuration.enableDesktop, onChanged: (val) { SystemProxy.setSystemProxyEnable(widget.proxyServer.port, val, widget.proxyServer.enableSsl); - widget.proxyServer.enableDesktop = val; + configuration.enableDesktop = val; enableDesktopListenable.value = !enableDesktopListenable.value; - widget.proxyServer.flushConfig(); + configuration.flushConfig(); }))), const PopupMenuItem(padding: EdgeInsets.all(0), child: ThemeSetting(dense: true)), menuItem("域名过滤", onTap: () => hostFilter()), @@ -106,7 +109,7 @@ class _SettingState extends State { label: const Text("关闭"), onPressed: () => Navigator.of(context).pop()))) ]), - content: RequestRewrite(proxyServer: widget.proxyServer), + content: RequestRewrite(configuration: configuration), ); }); } @@ -117,7 +120,7 @@ class _SettingState extends State { barrierDismissible: false, context: context, builder: (context) { - return FilterDialog(proxyServer: widget.proxyServer); + return FilterDialog(configuration: configuration); }, ); } @@ -146,11 +149,11 @@ class _PortState extends State { portFocus.addListener(() async { //失去焦点 if (!portFocus.hasFocus && textController.text != widget.proxyServer.port.toString()) { - widget.proxyServer.port = int.parse(textController.text); + widget.proxyServer.configuration.port = int.parse(textController.text); if (widget.proxyServer.isRunning) { widget.proxyServer.restart(); } - widget.proxyServer.flushConfig(); + widget.proxyServer.configuration.flushConfig(); } }); } diff --git a/lib/ui/desktop/toolbar/ssl/ssl.dart b/lib/ui/desktop/toolbar/ssl/ssl.dart index 4b78d83..45a3c8f 100644 --- a/lib/ui/desktop/toolbar/ssl/ssl.dart +++ b/lib/ui/desktop/toolbar/ssl/ssl.dart @@ -19,22 +19,10 @@ class SslWidget extends StatefulWidget { } class _SslState extends State { - bool _enableSsl = true; - - @override - void initState() { - super.initState(); - - widget.proxyServer.initializedListener(() { - _enableSsl = widget.proxyServer.enableSsl; - setState(() {}); - }); - } - @override Widget build(BuildContext context) { return PopupMenuButton( - icon: Icon(Icons.https, color: _enableSsl ? null : Colors.red), + icon: Icon(Icons.https, color: widget.proxyServer.enableSsl ? null : Colors.red), surfaceTintColor: Colors.white70, tooltip: "HTTPS代理", offset: const Offset(10, 30), @@ -42,11 +30,7 @@ class _SslState extends State { return [ PopupMenuItem( padding: const EdgeInsets.all(0), - child: _Switch( - proxyServer: widget.proxyServer, - onEnableChange: (val) => setState(() { - _enableSsl = val; - }))), + child: _Switch(proxyServer: widget.proxyServer, onEnableChange: (val) => setState(() {}))), PopupMenuItem( padding: const EdgeInsets.all(0), child: ListTile( @@ -282,7 +266,7 @@ class _SwitchState extends State<_Switch> { void dispose() { super.dispose(); if (changed) { - widget.proxyServer.flushConfig(); + widget.proxyServer.configuration.flushConfig(); } } } diff --git a/lib/ui/launch/launch.dart b/lib/ui/launch/launch.dart index a5c47c9..aa8623f 100644 --- a/lib/ui/launch/launch.dart +++ b/lib/ui/launch/launch.dart @@ -1,16 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/server.dart'; -import 'package:network_proxy/network/channel.dart'; import 'package:window_manager/window_manager.dart'; class SocketLaunch extends StatefulWidget { final ProxyServer proxyServer; final int size; + final bool startup; final Function? onStart; final Function? onStop; - const SocketLaunch({super.key, required this.proxyServer, this.size = 25, this.onStart, this.onStop}); + const SocketLaunch( + {super.key, required this.proxyServer, this.size = 25, this.onStart, this.onStop, this.startup = true}); @override State createState() { @@ -27,14 +28,9 @@ class _SocketLaunchState extends State with WindowListener, Widget windowManager.addListener(this); WidgetsBinding.instance.addObserver(this); //启动代理服务器 - widget.proxyServer.start().then((value) { - setState(() { - started = true; - }); - widget.onStart?.call(); - }).catchError((e) { - FlutterToastr.show("启动失败,请检查端口号${widget.proxyServer.port}是否被占用", context, duration: 3); - }); + if (widget.startup) { + start(); + } } @override @@ -70,19 +66,27 @@ class _SocketLaunchState extends State with WindowListener, Widget icon: Icon(started ? Icons.stop : Icons.play_arrow_sharp, color: started ? Colors.red : Colors.green, size: widget.size.toDouble()), onPressed: () async { - Future result = started ? widget.proxyServer.stop() : widget.proxyServer.start(); if (started) { - widget.onStop?.call(); + widget.proxyServer.stop().then((value) { + widget.onStop?.call(); + setState(() { + started = !started; + }); + }); } else { - widget.onStart?.call(); + start(); } - result - .then((value) => setState(() { - started = !started; - })) - .catchError((e) { - FlutterToastr.show("启动失败,请检查端口号${widget.proxyServer.port}是否被占用", context); - }); }); } + + start() { + widget.proxyServer.start().then((value) { + setState(() { + started = true; + }); + widget.onStart?.call(); + }).catchError((e) { + FlutterToastr.show("启动失败,请检查端口号${widget.proxyServer.port}是否被占用", context, duration: 3); + }); + } } diff --git a/lib/ui/mobile/connect_remote.dart b/lib/ui/mobile/connect_remote.dart index f353b3c..074c619 100644 --- a/lib/ui/mobile/connect_remote.dart +++ b/lib/ui/mobile/connect_remote.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/http_client.dart'; import 'package:network_proxy/network/util/host_filter.dart'; @@ -75,7 +76,7 @@ class ConnectRemoteState extends State { showDialog( context: context, builder: (context) { - return ConfigSyncWidget(proxyServer: widget.proxyServer, config: config); + return ConfigSyncWidget(configuration: widget.proxyServer.configuration, config: config); }); } }).onError((error, stackTrace) { @@ -86,10 +87,10 @@ class ConnectRemoteState extends State { } class ConfigSyncWidget extends StatefulWidget { - final ProxyServer proxyServer; + final Configuration configuration; final Map config; - const ConfigSyncWidget({super.key, required this.proxyServer, required this.config}); + const ConfigSyncWidget({super.key, required this.configuration, required this.config}); @override State createState() { @@ -152,10 +153,10 @@ class ConfigSyncState extends State { HostFilter.blacklist.load(widget.config['blacklist']); } if (syncRewrite) { - widget.proxyServer.requestRewrites.load(widget.config['requestRewrites']); - widget.proxyServer.flushRequestRewriteConfig(); + widget.configuration.requestRewrites.load(widget.config['requestRewrites']); + widget.configuration.flushRequestRewriteConfig(); } - widget.proxyServer.flushConfig(); + widget.configuration.flushConfig(); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('同步成功'))); }), diff --git a/lib/ui/mobile/menu.dart b/lib/ui/mobile/menu.dart index 013181a..4d91674 100644 --- a/lib/ui/mobile/menu.dart +++ b/lib/ui/mobile/menu.dart @@ -43,17 +43,17 @@ class DrawerWidget extends StatelessWidget { ListTile( title: const Text("域名白名单"), trailing: const Icon(Icons.arrow_right), - onTap: () => - navigator(context, MobileFilterWidget(proxyServer: proxyServer, hostList: HostFilter.whitelist))), + onTap: () => navigator( + context, MobileFilterWidget(configuration: proxyServer.configuration, hostList: HostFilter.whitelist))), ListTile( title: const Text("域名黑名单"), trailing: const Icon(Icons.arrow_right), - onTap: () => - navigator(context, MobileFilterWidget(proxyServer: proxyServer, hostList: HostFilter.blacklist))), + onTap: () => navigator( + context, MobileFilterWidget(configuration: proxyServer.configuration, hostList: HostFilter.blacklist))), ListTile( title: const Text("请求重写"), trailing: const Icon(Icons.arrow_right), - onTap: () => navigator(context, MobileRequestRewrite(proxyServer: proxyServer))), + onTap: () => navigator(context, MobileRequestRewrite(configuration: proxyServer.configuration))), ListTile( title: const Text("Github"), trailing: const Icon(Icons.arrow_right), @@ -169,7 +169,7 @@ class MoreEnum extends StatelessWidget { hostname: response.headers.get("hostname")); if (context.mounted && Navigator.canPop(context)) { - FlutterToastr.show("连接成功", context); + FlutterToastr.show("连接成功${proxyServer.isRunning ? '' : ',手机需要开启抓包才可以抓取请求哦'}", context); Navigator.pop(context); } } diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index 48d2404..b6ec3db 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:network_proxy/native/vpn.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/channel.dart'; import 'package:network_proxy/network/handler.dart'; @@ -13,7 +14,9 @@ import 'package:network_proxy/ui/mobile/menu.dart'; import 'package:network_proxy/ui/mobile/request/list.dart'; class MobileHomePage extends StatefulWidget { - const MobileHomePage({super.key}); + final Configuration configuration; + + const MobileHomePage({super.key, required this.configuration}); @override State createState() { @@ -39,7 +42,7 @@ class MobileHomeState extends State implements EventListener { @override void initState() { - proxyServer = ProxyServer(listener: this); + proxyServer = ProxyServer(widget.configuration, listener: this); desktop.addListener(() { if (desktop.value.connect) { proxyServer.server?.remoteHost = "http://${desktop.value.host}:${desktop.value.port}"; @@ -48,7 +51,26 @@ class MobileHomeState extends State implements EventListener { proxyServer.server?.remoteHost = null; } }); + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.configuration.guide) { + //首次引导 + String content = '默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n' + '点击的设置 -> HTTPS抓包,根据提示安装证书操作即可。'; + showAlertDialog('提示', content, () { + widget.configuration.guide = false; + widget.configuration.upgradeNotice = false; + widget.configuration.flushConfig(); + }); + return; + } + + if (widget.configuration.upgradeNotice) { + showUpgradeNotice(); + } + }); } @override @@ -74,6 +96,7 @@ class MobileHomeState extends State implements EventListener { onPressed: () {}, child: SocketLaunch( proxyServer: proxyServer, + startup: false, size: 38, onStart: () => Vpn.startVpn("127.0.0.1", proxyServer.port), onStop: () => Vpn.stopVpn())), @@ -100,6 +123,32 @@ class MobileHomeState extends State implements EventListener { ); } + showUpgradeNotice() { + String content = '1. 手机版启动默认不再自动开启抓包,请手动点击启动按钮。\n' + '2. 增加外部代理,可配置其他VPN软件地址,开启抓包不会影响访问外网。\n' + '3. 搜索功能增强,可直接搜索响应类型和请求方法。'; + showAlertDialog('更新内容', content, () { + widget.configuration.upgradeNotice = false; + widget.configuration.flushConfig(); + }); + } + + showAlertDialog(String title, String content, Function onClose) { + showDialog( + context: context, + barrierDismissible: false, + builder: (_) { + return AlertDialog(actions: [ + TextButton( + onPressed: () { + onClose.call(); + Navigator.pop(context); + }, + child: const Text('关闭')) + ], title: Text(title, style: const TextStyle(fontSize: 18)), content: Text(content)); + }); + } + /// 搜索框 Widget search() { return Padding( diff --git a/lib/ui/mobile/request/list.dart b/lib/ui/mobile/request/list.dart index 65a52ae..1cf131b 100644 --- a/lib/ui/mobile/request/list.dart +++ b/lib/ui/mobile/request/list.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:date_format/date_format.dart'; import 'package:flutter/material.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/channel.dart'; import 'package:network_proxy/network/http/http.dart'; @@ -160,17 +161,26 @@ class RequestSequenceState extends State with AutomaticKeepAliv } bool filter(HttpRequest request) { - if (searchText?.isNotEmpty == true) { - return request.requestUrl.toLowerCase().contains(searchText!); + if (searchText == null || searchText!.isEmpty) { + return true; } - return true; + + if (request.method.name.toLowerCase() == searchText) { + return true; + } + + if (request.requestUrl.toLowerCase().contains(searchText!)) { + return true; + } + + return request.response?.contentType.name.toLowerCase().contains(searchText!) == true; } changeState() { //防止频繁刷新 if (!changing) { changing = true; - Future.delayed(const Duration(milliseconds: 300), () { + Future.delayed(const Duration(milliseconds: 100), () { setState(() { changing = false; }); @@ -217,6 +227,7 @@ class DomainList extends StatefulWidget { class DomainListState extends State with AutomaticKeepAliveClientMixin { GlobalKey requestSequenceKey = GlobalKey(); + late Configuration configuration; //域名和对应请求列表的映射 Map> containerMap = {}; @@ -235,6 +246,8 @@ class DomainListState extends State with AutomaticKeepAliveClientMix @override initState() { super.initState(); + configuration = widget.proxyServer.configuration; + for (var request in widget.list) { var hostAndPort = request.hostAndPort!; container.add(hostAndPort); @@ -271,7 +284,7 @@ class DomainListState extends State with AutomaticKeepAliveClientMix //防止频繁刷新 if (!changing) { changing = true; - Future.delayed(const Duration(milliseconds: 200), () { + Future.delayed(const Duration(milliseconds: 50), () { setState(() { changing = false; }); @@ -332,7 +345,7 @@ class DomainListState extends State with AutomaticKeepAliveClientMix var time = formatDate(containerMap[list.elementAt(index)]!.last.requestTime, [m, '/', d, ' ', HH, ':', nn, ':', ss]); return ListTile( - visualDensity: const VisualDensity( vertical: -4), + visualDensity: const VisualDensity(vertical: -4), title: Text(list.elementAt(index).domain, maxLines: 1, overflow: TextOverflow.ellipsis), trailing: const Icon(Icons.chevron_right), subtitle: Text("最后请求时间: $time, 次数: ${containerMap[list.elementAt(index)]!.length}", @@ -366,7 +379,7 @@ class DomainListState extends State with AutomaticKeepAliveClientMix child: const SizedBox(width: double.infinity, child: Text("添加黑名单", textAlign: TextAlign.center)), onPressed: () { HostFilter.blacklist.add(hostAndPort.host); - widget.proxyServer.flushConfig(); + configuration.flushConfig(); FlutterToastr.show("已添加至黑名单", context); Navigator.of(context).pop(); }), @@ -375,7 +388,7 @@ class DomainListState extends State with AutomaticKeepAliveClientMix child: const SizedBox(width: double.infinity, child: Text("添加白名单", textAlign: TextAlign.center)), onPressed: () { HostFilter.whitelist.add(hostAndPort.host); - widget.proxyServer.flushConfig(); + configuration.flushConfig(); FlutterToastr.show("已添加至白名单", context); Navigator.of(context).pop(); }), @@ -384,7 +397,7 @@ class DomainListState extends State with AutomaticKeepAliveClientMix child: const SizedBox(width: double.infinity, child: Text("删除白名单", textAlign: TextAlign.center)), onPressed: () { HostFilter.whitelist.remove(hostAndPort.host); - widget.proxyServer.flushConfig(); + configuration.flushConfig(); FlutterToastr.show("已删除白名单", context); Navigator.of(context).pop(); }), diff --git a/lib/ui/mobile/setting/filter.dart b/lib/ui/mobile/setting/filter.dart index 6ef6a0d..52a57c3 100644 --- a/lib/ui/mobile/setting/filter.dart +++ b/lib/ui/mobile/setting/filter.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import '../../../../network/util/host_filter.dart'; class MobileFilterWidget extends StatefulWidget { - final ProxyServer proxyServer; + final Configuration configuration; final HostList hostList; - const MobileFilterWidget({super.key, required this.proxyServer, required this.hostList}); + const MobileFilterWidget({super.key, required this.configuration, required this.hostList}); @override State createState() => _MobileFilterState(); @@ -34,7 +34,7 @@ class _MobileFilterState extends State { title: title, subtitle: subtitle, hostList: widget.hostList, - proxyServer: widget.proxyServer, + configuration: widget.configuration, hostEnableNotifier: hostEnableNotifier), )); } @@ -44,7 +44,7 @@ class DomainFilter extends StatefulWidget { final String title; final String subtitle; final HostList hostList; - final ProxyServer proxyServer; + final Configuration configuration; final ValueNotifier hostEnableNotifier; const DomainFilter( @@ -53,7 +53,7 @@ class DomainFilter extends StatefulWidget { required this.subtitle, required this.hostList, required this.hostEnableNotifier, - required this.proxyServer}); + required this.configuration}); @override State createState() { @@ -114,7 +114,7 @@ class _DomainFilterState extends State { @override void dispose() { if (changed) { - widget.proxyServer.flushConfig(); + widget.configuration.flushConfig(); } super.dispose(); } diff --git a/lib/ui/mobile/setting/request_rewrite.dart b/lib/ui/mobile/setting/request_rewrite.dart index 0983d84..f1b3f56 100644 --- a/lib/ui/mobile/setting/request_rewrite.dart +++ b/lib/ui/mobile/setting/request_rewrite.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/util/request_rewrite.dart'; class MobileRequestRewrite extends StatefulWidget { - final ProxyServer proxyServer; + final Configuration configuration; - const MobileRequestRewrite({super.key, required this.proxyServer}); + const MobileRequestRewrite({super.key, required this.configuration}); @override State createState() => _MobileRequestRewriteState(); @@ -19,15 +19,15 @@ class _MobileRequestRewriteState extends State { @override void initState() { super.initState(); - requestRuleList = RequestRuleList(widget.proxyServer.requestRewrites); - enableNotifier = ValueNotifier(widget.proxyServer.requestRewrites.enabled); + requestRuleList = RequestRuleList(widget.configuration.requestRewrites); + enableNotifier = ValueNotifier(widget.configuration.requestRewrites.enabled); } @override void dispose() { - if (changed || enableNotifier.value != widget.proxyServer.requestRewrites.enabled) { - widget.proxyServer.requestRewrites.enabled = enableNotifier.value; - widget.proxyServer.flushRequestRewriteConfig(); + if (changed || enableNotifier.value != widget.configuration.requestRewrites.enabled) { + widget.configuration.requestRewrites.enabled = enableNotifier.value; + widget.configuration.flushRequestRewriteConfig(); } enableNotifier.dispose(); @@ -81,7 +81,7 @@ class _MobileRequestRewriteState extends State { changed = true; setState(() { - widget.proxyServer.requestRewrites.removeIndex(removeSelected); + widget.configuration.requestRewrites.removeIndex(removeSelected); requestRuleList.changeState(); }); }) @@ -97,7 +97,7 @@ class _MobileRequestRewriteState extends State { context: context, builder: (BuildContext context) { return RuleAddDialog( - requestRewrites: widget.proxyServer.requestRewrites, + requestRewrites: widget.configuration.requestRewrites, currentIndex: currentIndex, onChange: () { changed = true; @@ -248,7 +248,7 @@ class _RequestRuleListState extends State { border: TableBorder.symmetric(outside: BorderSide(width: 1, color: Theme.of(context).highlightColor)), columns: const [ DataColumn(label: Text('启用')), - DataColumn(label: Text('URL')), + DataColumn(label: Text('Path')), DataColumn(label: Text('请求体')), DataColumn(label: Text('响应体')), ], diff --git a/lib/ui/mobile/setting/ssl.dart b/lib/ui/mobile/setting/ssl.dart index 956097d..ed18325 100644 --- a/lib/ui/mobile/setting/ssl.dart +++ b/lib/ui/mobile/setting/ssl.dart @@ -21,7 +21,7 @@ class _MobileSslState extends State { @override void dispose() { if (changed) { - widget.proxyServer.flushConfig(); + widget.proxyServer.configuration.flushConfig(); } super.dispose(); } diff --git a/test/widget_test.dart b/test/widget_test.dart index 9da8e48..e112485 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -9,11 +9,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:network_proxy/main.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const FluentApp(DesktopHomePage())); + Configuration configuration = await Configuration.instance; + await tester.pumpWidget(FluentApp(DesktopHomePage(configuration: configuration))); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);