收藏增加重命名

This commit is contained in:
wanghongenpin
2023-10-19 19:21:01 +08:00
parent d4be1bc0e0
commit 6d71982ee6
10 changed files with 175 additions and 86 deletions

View File

@@ -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<String, dynamic> toJson() {
return {
'upgradeNoticeV3': upgradeNoticeV3,
'upgradeNoticeV4': upgradeNoticeV4,
'port': port,
'enableSsl': enableSsl,
'enableSystemProxy': enableSystemProxy,

View File

@@ -89,6 +89,12 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
"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);

View File

@@ -198,7 +198,7 @@ async function onResponse(context, request, response) {
return response;
}
///
/// js结果转换
static Future<dynamic> 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 {

View File

@@ -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<HttpRequest>? _requests;
static Queue<Favorite>? list;
/// 获取收藏列表
static Future<Queue<HttpRequest>> get favorites async {
if (_requests == null) {
static Future<Queue<Favorite>> 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<dynamic>;
for (var element in list) {
_requests!.add(_Item.fromJson(element).request);
var config = jsonDecode(value) as List<dynamic>;
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<File> get _path async {
@@ -42,45 +42,51 @@ class FavoriteStorage {
/// 添加收藏
static Future<void> 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<void> 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<Favorite> 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<String, dynamic> json) {
return _Item(HttpRequest.fromJson(json['request']),
json['response'] == null ? null : HttpResponse.fromJson(json['response']));
factory Favorite.fromJson(Map<String, dynamic> 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(),
};

View File

@@ -54,7 +54,7 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> 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<DesktopHomePage> 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)));
});
}

View File

@@ -33,7 +33,7 @@ class _FavoritesState extends State<Favorites> {
Widget build(BuildContext context) {
return FutureBuilder(
future: FavoriteStorage.favorites,
builder: (BuildContext context, AsyncSnapshot<Queue<HttpRequest>> snapshot) {
builder: (BuildContext context, AsyncSnapshot<Queue<Favorite>> snapshot) {
if (snapshot.hasData) {
var favorites = snapshot.data ?? Queue();
if (favorites.isEmpty) {
@@ -66,11 +66,12 @@ class _FavoritesState extends State<Favorites> {
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: <PopupMenuEntry>[
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: <Widget>[
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);
}
}

View File

@@ -58,7 +58,7 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener {
});
super.initState();
if (widget.configuration.upgradeNoticeV3) {
if (widget.configuration.upgradeNoticeV4) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showUpgradeNotice();
});
@@ -113,14 +113,14 @@ class MobileHomeState extends State<MobileHomePage> 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();
});
}

View File

@@ -32,7 +32,7 @@ class _FavoritesState extends State<MobileFavorites> {
appBar: AppBar(title: const Text("收藏请求", style: TextStyle(fontSize: 16)), centerTitle: true),
body: FutureBuilder(
future: FavoriteStorage.favorites,
builder: (BuildContext context, AsyncSnapshot<Queue<HttpRequest>> snapshot) {
builder: (BuildContext context, AsyncSnapshot<Queue<Favorite>> snapshot) {
if (snapshot.hasData) {
var favorites = snapshot.data ?? Queue();
if (favorites.isEmpty) {
@@ -42,9 +42,9 @@ class _FavoritesState extends State<MobileFavorites> {
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<MobileFavorites> {
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: <Widget>[
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)));
}));
}

View File

@@ -161,7 +161,6 @@ class RequestSequenceState extends State<RequestSequence> 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<RequestSequence> with AutomaticKeepAliv
GlobalKey<RequestRowState> key = GlobalKey();
indexes[view.elementAt(index)] = key;
return RequestRow(
index: index,
index: view.length - index,
key: key,
request: view.elementAt(index),
proxyServer: widget.proxyServer,

View File

@@ -81,6 +81,7 @@ class RequestRowState extends State<RequestRow> {
menu() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
builder: (ctx) {
return Wrap(alignment: WrapAlignment.center, children: [
@@ -130,7 +131,7 @@ class RequestRowState extends State<RequestRow> {
),
TextButton(
child: Container(
height: 60,
height: 40,
width: double.infinity,
padding: const EdgeInsets.only(top: 10),
child: const Text("取消", textAlign: TextAlign.center)),