From 6d71982ee61fe07e9cdc851448f55d3ebde1e85b Mon Sep 17 00:00:00 2001 From: wanghongenpin <178070584@qq.com> Date: Thu, 19 Oct 2023 19:21:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E8=97=8F=E5=A2=9E=E5=8A=A0=E9=87=8D?= =?UTF-8?q?=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/network/bin/configuration.dart | 6 +- lib/network/handler.dart | 6 ++ lib/network/util/script_manager.dart | 3 +- lib/storage/favorites.dart | 54 ++++++++++-------- lib/ui/desktop/desktop.dart | 17 +++--- lib/ui/desktop/left/favorite.dart | 68 ++++++++++++++++++----- lib/ui/mobile/mobile.dart | 18 +++--- lib/ui/mobile/request/favorite.dart | 83 ++++++++++++++++++++-------- lib/ui/mobile/request/list.dart | 3 +- lib/ui/mobile/request/request.dart | 3 +- 10 files changed, 175 insertions(+), 86 deletions(-) diff --git a/lib/network/bin/configuration.dart b/lib/network/bin/configuration.dart index 8f72a90..6fd6ab0 100644 --- a/lib/network/bin/configuration.dart +++ b/lib/network/bin/configuration.dart @@ -39,7 +39,7 @@ class Configuration { String proxyPassDomains = SystemProxy.proxyPassDomains; //是否显示更新内容公告 - bool upgradeNoticeV3 = true; + bool upgradeNoticeV4 = true; //请求重写 RequestRewrites requestRewrites = RequestRewrites.instance; @@ -124,7 +124,7 @@ class Configuration { enableSsl = config['enableSsl'] == true; enableSystemProxy = config['enableSystemProxy'] ?? (config['enableDesktop'] ?? true); proxyPassDomains = config['proxyPassDomains'] ?? SystemProxy.proxyPassDomains; - upgradeNoticeV3 = config['upgradeNoticeV3'] ?? true; + upgradeNoticeV4 = config['upgradeNoticeV4'] ?? true; if (config['externalProxy'] != null) { externalProxy = ProxyInfo.fromJson(config['externalProxy']); } @@ -165,7 +165,7 @@ class Configuration { Map toJson() { return { - 'upgradeNoticeV3': upgradeNoticeV3, + 'upgradeNoticeV4': upgradeNoticeV4, 'port': port, 'enableSsl': enableSsl, 'enableSystemProxy': enableSystemProxy, diff --git a/lib/network/handler.dart b/lib/network/handler.dart index 8362ee0..68e32ce 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -89,6 +89,12 @@ class HttpChannelHandler extends ChannelHandler { "requestRewrites": requestRewrites?.toJson(), 'whitelist': HostFilter.whitelist.toJson(), 'blacklist': HostFilter.blacklist.toJson(), + 'scripts': await ScriptManager.instance.then((script) { + var list = script.list.map((e) async { + return {'name': e.name, 'enabled': e.enabled, 'url': e.url, 'script': await script.getScript(e)}; + }); + return Future.wait(list); + }), }; response.body = utf8.encode(json.encode(body)); channel.writeAndClose(response); diff --git a/lib/network/util/script_manager.dart b/lib/network/util/script_manager.dart index b678cdc..971b774 100644 --- a/lib/network/util/script_manager.dart +++ b/lib/network/util/script_manager.dart @@ -198,7 +198,7 @@ async function onResponse(context, request, response) { return response; } - /// + /// js结果转换 static Future jsResultResolve(JsEvalResult jsResult) async { if (jsResult.isPromise) { jsResult = await flutterJs.handlePromise(jsResult); @@ -266,6 +266,7 @@ async function onResponse(context, request, response) { response.body = map['body']?.toString().codeUnits; return response; } + } class ScriptItem { diff --git a/lib/storage/favorites.dart b/lib/storage/favorites.dart index 53d3e20..871bdc1 100644 --- a/lib/storage/favorites.dart +++ b/lib/storage/favorites.dart @@ -3,31 +3,31 @@ import 'dart:convert'; import 'dart:io'; import 'package:network_proxy/network/http/http.dart'; +import 'package:network_proxy/network/util/logger.dart'; import 'package:path_provider/path_provider.dart'; class FavoriteStorage { - static Queue? _requests; + static Queue? list; /// 获取收藏列表 - static Future> get favorites async { - if (_requests == null) { + static Future> get favorites async { + if (list == null) { + list = ListQueue(); var file = await _path; - print(file); - _requests = ListQueue(); if (await file.exists()) { var value = await file.readAsString(); try { - var list = jsonDecode(value) as List; - for (var element in list) { - _requests!.add(_Item.fromJson(element).request); + var config = jsonDecode(value) as List; + for (var element in config) { + list?.add(Favorite.fromJson(element)); } - } catch (e) { - print(e); + } catch (e, t) { + logger.e('收藏列表解析失败', error: e, stackTrace: t); } } } - return _requests!; + return list!; } static Future get _path async { @@ -42,45 +42,51 @@ class FavoriteStorage { /// 添加收藏 static Future addFavorite(HttpRequest request) async { var favorites = await FavoriteStorage.favorites; - if (favorites.contains(request)) { + if (favorites.any((element) => element.request == request)) { return; } - favorites.addFirst(request); - _path.then((file) async { - file.writeAsString(jsonEncode(toJson(favorites))); - }); + favorites.addFirst(Favorite(request)); + flushConfig(); } static Future removeFavorite(HttpRequest request) async { var list = await favorites; list.remove(request); - _path.then((file) => file.writeAsString(jsonEncode(toJson(list)))); + flushConfig(); } - static List toJson(Queue list) { - return list.map((e) => _Item(e).toJson()).toList(); + //刷新配置 + static void flushConfig() async { + var list = await favorites; + _path.then((file) => file.writeAsString(toJson(list))); + } + + static String toJson(Queue list) { + return jsonEncode(list.map((e) => e.toJson()).toList()); } } -class _Item { +class Favorite { + String? name; final HttpRequest request; HttpResponse? response; - _Item(this.request, [this.response]) { + Favorite(this.request, {this.name, this.response}) { response ??= request.response; request.response = response; response?.request = request; } - factory _Item.fromJson(Map json) { - return _Item(HttpRequest.fromJson(json['request']), - json['response'] == null ? null : HttpResponse.fromJson(json['response'])); + factory Favorite.fromJson(Map json) { + return Favorite(HttpRequest.fromJson(json['request']), + name: json['name'], response: json['response'] == null ? null : HttpResponse.fromJson(json['response'])); } toJson() { return { + 'name': name, 'request': request.toJson(), 'response': response?.toJson(), }; diff --git a/lib/ui/desktop/desktop.dart b/lib/ui/desktop/desktop.dart index cbc516f..db9e604 100644 --- a/lib/ui/desktop/desktop.dart +++ b/lib/ui/desktop/desktop.dart @@ -54,7 +54,7 @@ class _DesktopHomePagePageState extends State implements EventL proxyServer.addListener(this); panel = NetworkTabController(tabStyle: const TextStyle(fontSize: 16), proxyServer: proxyServer); - if (widget.configuration.upgradeNoticeV3) { + if (widget.configuration.upgradeNoticeV4) { WidgetsBinding.instance.addPostFrameCallback((_) { showUpgradeNotice(); }); @@ -126,22 +126,23 @@ class _DesktopHomePagePageState extends State implements EventL actions: [ TextButton( onPressed: () { - widget.configuration.upgradeNoticeV3 = false; + widget.configuration.upgradeNoticeV4 = false; widget.configuration.flushConfig(); Navigator.pop(context); }, child: const Text('关闭')) ], - title: const Text('更新内容V1.0.3', style: TextStyle(fontSize: 18)), + title: const Text('更新内容V1.0.4', style: TextStyle(fontSize: 18)), content: const Text( '提示:默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n' '点击的HTTPS抓包(加锁图标),选择安装根证书,按照提示操作即可。\n\n' '新增更新:\n' - '1. 增加历史记录功能,默认不会保存,需要去历史页点击保存;\n' - '2. 请求重写增加名称&URL参数重写;\n' - '3. 请求重写增加重定向;\n' - '4. 建立连接异常显示请求体;\n' - '5. 请求编辑重发响应体查看增加多种格式,详情Body体增加快速解码入口;', + '1. 增加JS脚本,可修改请求和影响,脚本支持fetch API请求接口;\n' + '2. 扫码配置同步增加脚本配置;\n' + '3. 桌面端增加系统代理忽略域名设置,忽略的域名不会进行代理;\n' + '4. 收藏增加重命名;\n' + '5. 手机端认启动监听端口,无需开启抓包可直接下载证书;\n' + '6. 修复安卓白名单已经删除应用展示异常问题;', style: TextStyle(fontSize: 14))); }); } diff --git a/lib/ui/desktop/left/favorite.dart b/lib/ui/desktop/left/favorite.dart index 3012d7a..0883765 100644 --- a/lib/ui/desktop/left/favorite.dart +++ b/lib/ui/desktop/left/favorite.dart @@ -33,7 +33,7 @@ class _FavoritesState extends State { Widget build(BuildContext context) { return FutureBuilder( future: FavoriteStorage.favorites, - builder: (BuildContext context, AsyncSnapshot> snapshot) { + builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasData) { var favorites = snapshot.data ?? Queue(); if (favorites.isEmpty) { @@ -66,11 +66,12 @@ class _FavoritesState extends State { class _FavoriteItem extends StatefulWidget { final int index; - final HttpRequest request; + final Favorite favorite; final NetworkTabController panel; final Function(HttpRequest request)? onRemove; - const _FavoriteItem(this.request, {Key? key, required this.panel, required this.onRemove, required this.index}) : super(key: key); + const _FavoriteItem(this.favorite, {Key? key, required this.panel, required this.onRemove, required this.index}) + : super(key: key); @override State<_FavoriteItem> createState() => _FavoriteItemState(); @@ -81,11 +82,17 @@ class _FavoriteItemState extends State<_FavoriteItem> { static _FavoriteItemState? selectedState; bool selected = false; + late HttpRequest request; + + @override + void initState() { + super.initState(); + request = widget.favorite.request; + } @override Widget build(BuildContext context) { - var request = widget.request; - var response = request.response; + var response = widget.favorite.response; var title = '${request.method.name} ${request.requestUrl}'; var time = formatDate(request.requestTime, [mm, '-', d, ' ', HH, ':', nn, ':', ss]); return GestureDetector( @@ -93,12 +100,12 @@ class _FavoriteItemState extends State<_FavoriteItem> { child: ListTile( minLeadingWidth: 25, leading: getIcon(response), - title: Text(title, overflow: TextOverflow.ellipsis, maxLines: 2), + title: Text(widget.favorite.name ?? title, overflow: TextOverflow.ellipsis, maxLines: 2), subtitle: Text.rich( style: const TextStyle(fontSize: 12), maxLines: 1, TextSpan(children: [ - TextSpan(text: '#${widget.index} ', style: const TextStyle( color: Colors.teal)), + TextSpan(text: '#${widget.index} ', style: const TextStyle(color: Colors.teal)), TextSpan( text: '$time - [${response?.status.code ?? ''}] ${response?.contentType.name.toUpperCase() ?? ''} ${response?.costTime() ?? ''} '), @@ -115,20 +122,22 @@ class _FavoriteItemState extends State<_FavoriteItem> { details.globalPosition, items: [ popupItem("复制请求链接", onTap: () { - var requestUrl = widget.request.requestUrl; + var requestUrl = request.requestUrl; Clipboard.setData(ClipboardData(text: requestUrl)).then((value) => FlutterToastr.show('已复制到剪切板', context)); }), popupItem("复制请求和响应", onTap: () { - Clipboard.setData(ClipboardData(text: copyRequest(widget.request, widget.request.response))) + Clipboard.setData(ClipboardData(text: copyRequest(request, request.response))) .then((value) => FlutterToastr.show('已复制到剪切板', context)); }), popupItem("复制 cURL 请求", onTap: () { - Clipboard.setData(ClipboardData(text: curlRequest(widget.request))) + Clipboard.setData(ClipboardData(text: curlRequest(request))) .then((value) => FlutterToastr.show('已复制到剪切板', context)); }), + const PopupMenuDivider(height: 0.3), + popupItem("重命名", onTap: () => rename(widget.favorite)), popupItem("重放请求", onTap: () { - var request = widget.request.copy(uri: widget.request.requestUrl); - HttpClients.proxyRequest(request); + var httpRequest = request.copy(uri: request.requestUrl); + HttpClients.proxyRequest(httpRequest); FlutterToastr.show('已重新发送请求', context); }), @@ -139,7 +148,7 @@ class _FavoriteItemState extends State<_FavoriteItem> { }), const PopupMenuDivider(height: 0.3), popupItem("删除收藏", onTap: () { - widget.onRemove?.call(widget.request); + widget.onRemove?.call(request); }) ], ); @@ -149,6 +158,35 @@ class _FavoriteItemState extends State<_FavoriteItem> { return CustomPopupMenuItem(height: 35, onTap: onTap, child: Text(text, style: const TextStyle(fontSize: 13))); } + //重命名 + rename(Favorite item) { + String? name = item.name; + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: TextFormField( + initialValue: name, + decoration: const InputDecoration(label: Text("名称")), + onChanged: (val) => name = val, + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(context), child: const Text("取消")), + TextButton( + child: const Text('保存'), + onPressed: () { + Navigator.maybePop(context); + setState(() { + item.name = name?.isEmpty == true ? null : name; + FavoriteStorage.flushConfig(); + }); + }, + ), + ], + ); + }); + } + ///请求编辑 requestEdit() async { var size = MediaQuery.of(context).size; @@ -158,7 +196,7 @@ class _FavoriteItemState extends State<_FavoriteItem> { } final window = await DesktopMultiWindow.createWindow(jsonEncode( - {'name': 'RequestEditor', 'request': widget.request}, + {'name': 'RequestEditor', 'request': request}, )); window.setTitle('请求编辑'); window @@ -183,6 +221,6 @@ class _FavoriteItemState extends State<_FavoriteItem> { }); } selectedState = this; - widget.panel.change(widget.request, widget.request.response); + widget.panel.change(request, request.response); } } diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index bd357ec..f36c9a5 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -58,7 +58,7 @@ class MobileHomeState extends State implements EventListener { }); super.initState(); - if (widget.configuration.upgradeNoticeV3) { + if (widget.configuration.upgradeNoticeV4) { WidgetsBinding.instance.addPostFrameCallback((_) { showUpgradeNotice(); }); @@ -113,14 +113,14 @@ class MobileHomeState extends State implements EventListener { showUpgradeNotice() { String content = '提示:默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n\n' '新增更新:\n' - '1. 增加历史记录功能,默认不会保存,需要去历史页点击保存;\n' - '2. 请求重写增加名称&URL参数重写;\n' - '3. 请求重写增加重定向;\n' - '4. 建立连接异常显示请求体;\n' - '5. 请求编辑重发响应体查看增加多种格式,详情Body体增加快速解码入口\n' - '6. 请求列表增加编号;'; - showAlertDialog('更新内容V1.0.3', content, () { - widget.configuration.upgradeNoticeV3 = false; + '1. 增加JS脚本,可修改请求和影响,脚本支持fetch API请求接口;\n' + '2. 扫码配置同步增加脚本配置;\n' + '3. 桌面端增加系统代理忽略域名设置,忽略的域名不会进行代理;\n' + '4. 收藏增加重命名;\n' + '5. 手机端认启动监听端口,无需开启抓包可直接下载证书;\n' + '6. 修复安卓白名单已经删除应用展示异常问题;'; + showAlertDialog('更新内容V1.0.4', content, () { + widget.configuration.upgradeNoticeV4 = false; widget.configuration.flushConfig(); }); } diff --git a/lib/ui/mobile/request/favorite.dart b/lib/ui/mobile/request/favorite.dart index 43134cc..b8e89e3 100644 --- a/lib/ui/mobile/request/favorite.dart +++ b/lib/ui/mobile/request/favorite.dart @@ -32,7 +32,7 @@ class _FavoritesState extends State { appBar: AppBar(title: const Text("收藏请求", style: TextStyle(fontSize: 16)), centerTitle: true), body: FutureBuilder( future: FavoriteStorage.favorites, - builder: (BuildContext context, AsyncSnapshot> snapshot) { + builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasData) { var favorites = snapshot.data ?? Queue(); if (favorites.isEmpty) { @@ -42,9 +42,9 @@ class _FavoritesState extends State { return ListView.separated( itemCount: favorites.length, itemBuilder: (_, index) { - var request = favorites.elementAt(index); + var favorite = favorites.elementAt(index); return _FavoriteItem( - request, + favorite, index: index, onRemove: (HttpRequest request) { FavoriteStorage.removeFavorite(request); @@ -65,11 +65,11 @@ class _FavoritesState extends State { class _FavoriteItem extends StatefulWidget { final int index; + final Favorite favorite; final ProxyServer proxyServer; - final HttpRequest request; final Function(HttpRequest request)? onRemove; - const _FavoriteItem(this.request, {Key? key, required this.onRemove, required this.proxyServer, required this.index}) + const _FavoriteItem(this.favorite, {Key? key, required this.onRemove, required this.proxyServer, required this.index}) : super(key: key); @override @@ -77,9 +77,16 @@ class _FavoriteItem extends StatefulWidget { } class _FavoriteItemState extends State<_FavoriteItem> { + late HttpRequest request; + + @override + void initState() { + super.initState(); + request = widget.favorite.request; + } + @override Widget build(BuildContext context) { - var request = widget.request; var response = request.response; var title = '${request.method.name} ${request.requestUrl}'; var time = formatDate(request.requestTime, [mm, '-', d, ' ', HH, ':', nn, ':', ss]); @@ -89,7 +96,7 @@ class _FavoriteItemState extends State<_FavoriteItem> { onLongPress: menu, minLeadingWidth: 25, leading: getIcon(response), - title: Text(title, overflow: TextOverflow.ellipsis, maxLines: 2), + title: Text(widget.favorite.name ?? title, overflow: TextOverflow.ellipsis, maxLines: 2), subtitle: Text.rich( maxLines: 1, TextSpan(children: [ @@ -104,22 +111,27 @@ class _FavoriteItemState extends State<_FavoriteItem> { menu() { showModalBottomSheet( context: context, - enableDrag: true, + isScrollControlled: true, builder: (ctx) { return Wrap(alignment: WrapAlignment.center, children: [ - menuItem("复制请求链接", () => widget.request.requestUrl), + menuItem("复制请求链接", () => request.requestUrl), const Divider(thickness: 0.5), - menuItem("复制请求和响应", () => copyRequest(widget.request, widget.request.response)), + menuItem("复制 cURL 请求", () => curlRequest(request)), const Divider(thickness: 0.5), - menuItem("复制 cURL 请求", () => curlRequest(widget.request)), + TextButton( + child: const SizedBox(width: double.infinity, child: Text("重命名", textAlign: TextAlign.center)), + onPressed: () { + rename(widget.favorite); + Navigator.of(context).pop(); + }), const Divider(thickness: 0.5), TextButton( child: const SizedBox(width: double.infinity, child: Text("请求重放", textAlign: TextAlign.center)), onPressed: () { - var request = widget.request.copy(uri: widget.request.requestUrl); + var httpRequest = request.copy(uri: request.requestUrl); HttpClients.proxyRequest( proxyInfo: widget.proxyServer.isRunning ? ProxyInfo.of("127.0.0.1", widget.proxyServer.port) : null, - request); + httpRequest); FlutterToastr.show('已重新发送请求', context); Navigator.of(context).pop(); @@ -130,24 +142,20 @@ class _FavoriteItemState extends State<_FavoriteItem> { onPressed: () { Navigator.of(context).pop(); Navigator.of(context).push(MaterialPageRoute( - builder: (context) => - MobileRequestEditor(request: widget.request, proxyServer: widget.proxyServer))); + builder: (context) => MobileRequestEditor(request: request, proxyServer: widget.proxyServer))); }), const Divider(thickness: 0.5), TextButton( child: const SizedBox(width: double.infinity, child: Text("删除收藏", textAlign: TextAlign.center)), onPressed: () { - widget.onRemove?.call(widget.request); + widget.onRemove?.call(request); FlutterToastr.show('删除成功', context); Navigator.of(context).pop(); }), - Container( - color: Theme.of(context).hoverColor, - height: 8, - ), + Container(color: Theme.of(context).hoverColor, height: 8), TextButton( child: Container( - height: 60, + height: 40, width: double.infinity, padding: const EdgeInsets.only(top: 10), child: const Text("取消", textAlign: TextAlign.center)), @@ -171,13 +179,42 @@ class _FavoriteItemState extends State<_FavoriteItem> { }); } + //重命名 + rename(Favorite item) { + String? name = item.name; + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: TextFormField( + initialValue: name, + decoration: const InputDecoration(label: Text("名称")), + onChanged: (val) => name = val, + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(context), child: const Text("取消")), + TextButton( + child: const Text('保存'), + onPressed: () { + Navigator.maybePop(context); + setState(() { + item.name = name?.isEmpty == true ? null : name; + FavoriteStorage.flushConfig(); + }); + }, + ), + ], + ); + }); + } + //点击事件 void onClick() { Navigator.push(context, MaterialPageRoute(builder: (context) { return NetworkTabController( proxyServer: widget.proxyServer, - httpRequest: widget.request, - httpResponse: widget.request.response, + httpRequest: request, + httpResponse: request.response, title: const Text("抓包详情", style: TextStyle(fontSize: 16))); })); } diff --git a/lib/ui/mobile/request/list.dart b/lib/ui/mobile/request/list.dart index ee36657..874267b 100644 --- a/lib/ui/mobile/request/list.dart +++ b/lib/ui/mobile/request/list.dart @@ -161,7 +161,6 @@ class RequestSequenceState extends State with AutomaticKeepAliv //搜索视图 if (searchModel?.filter(response.request!, response) == true && state == null) { - if (!view.contains(response.request)) { view.addFirst(response.request!); changeState(); @@ -215,7 +214,7 @@ class RequestSequenceState extends State with AutomaticKeepAliv GlobalKey key = GlobalKey(); indexes[view.elementAt(index)] = key; return RequestRow( - index: index, + index: view.length - index, key: key, request: view.elementAt(index), proxyServer: widget.proxyServer, diff --git a/lib/ui/mobile/request/request.dart b/lib/ui/mobile/request/request.dart index 6894989..83acf19 100644 --- a/lib/ui/mobile/request/request.dart +++ b/lib/ui/mobile/request/request.dart @@ -81,6 +81,7 @@ class RequestRowState extends State { menu() { showModalBottomSheet( context: context, + isScrollControlled: true, enableDrag: true, builder: (ctx) { return Wrap(alignment: WrapAlignment.center, children: [ @@ -130,7 +131,7 @@ class RequestRowState extends State { ), TextButton( child: Container( - height: 60, + height: 40, width: double.infinity, padding: const EdgeInsets.only(top: 10), child: const Text("取消", textAlign: TextAlign.center)),