mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-03-15 04:23:17 +08:00
桌面端增加JS脚本
This commit is contained in:
@@ -1,23 +1,19 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
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/ui/component/chinese_font.dart';
|
||||
import 'package:network_proxy/ui/component/encoder.dart';
|
||||
import 'package:network_proxy/ui/content/body.dart';
|
||||
import 'package:network_proxy/ui/component/multi_window.dart';
|
||||
import 'package:network_proxy/ui/desktop/desktop.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/request_editor.dart';
|
||||
import 'package:network_proxy/ui/mobile/mobile.dart';
|
||||
import 'package:network_proxy/ui/ui_configuration.dart';
|
||||
import 'package:network_proxy/utils/platform.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'network/http/http.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
var instance = UIConfiguration.instance;
|
||||
//多窗口
|
||||
if (args.firstOrNull == 'multi_window') {
|
||||
@@ -28,6 +24,7 @@ void main(List<String> args) async {
|
||||
}
|
||||
|
||||
var configuration = Configuration.instance;
|
||||
//移动端
|
||||
if (Platforms.isMobile()) {
|
||||
var uiConfiguration = await instance;
|
||||
runApp(FluentApp(MobileHomePage(configuration: (await configuration)), uiConfiguration: uiConfiguration));
|
||||
@@ -51,32 +48,6 @@ void main(List<String> args) async {
|
||||
runApp(FluentApp(DesktopHomePage(configuration: await configuration), uiConfiguration: uiConfiguration));
|
||||
}
|
||||
|
||||
///多窗口
|
||||
Widget multiWindow(int windowId, Map<dynamic, dynamic> argument) {
|
||||
if (argument['name'] == 'RequestEditor') {
|
||||
return RequestEditor(
|
||||
windowController: WindowController.fromWindowId(windowId),
|
||||
request: argument['request'] == null ? null : HttpRequest.fromJson(argument['request']));
|
||||
}
|
||||
|
||||
if (argument['name'] == 'HttpBodyWidget') {
|
||||
return HttpBodyWidget(
|
||||
windowController: WindowController.fromWindowId(windowId),
|
||||
httpMessage: HttpMessage.fromJson(argument['httpMessage']),
|
||||
inNewWindow: true,
|
||||
hideRequestRewrite: true);
|
||||
}
|
||||
|
||||
if (argument['name'] == 'EncoderWidget') {
|
||||
return EncoderWidget(
|
||||
type: EncoderType.nameOf(argument['type']),
|
||||
text: argument['text'],
|
||||
windowController: WindowController.fromWindowId(windowId));
|
||||
}
|
||||
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
class ThemeModel {
|
||||
ThemeMode mode;
|
||||
bool useMaterial3;
|
||||
@@ -89,7 +60,7 @@ class ThemeModel {
|
||||
);
|
||||
}
|
||||
|
||||
/// 主题
|
||||
///主题
|
||||
late ValueNotifier<ThemeModel> themeNotifier;
|
||||
|
||||
class FluentApp extends StatelessWidget {
|
||||
|
||||
@@ -85,7 +85,7 @@ class Channel {
|
||||
|
||||
Future<void> write(Object obj) async {
|
||||
if (isClosed) {
|
||||
logger.w("[$id] channel is closed $obj");
|
||||
logger.w("[$id] channel is closed");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,8 +98,14 @@ class Channel {
|
||||
isWriting = true;
|
||||
try {
|
||||
var data = pipeline._encoder.encode(obj);
|
||||
_socket.add(data);
|
||||
if (!isClosed) {
|
||||
_socket.add(data);
|
||||
}
|
||||
await _socket.flush();
|
||||
} catch (e, t) {
|
||||
print(getAttribute(id)._attributes);
|
||||
print(e);
|
||||
print(t);
|
||||
} finally {
|
||||
isWriting = false;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import 'package:network_proxy/network/util/attribute_keys.dart';
|
||||
import 'package:network_proxy/network/util/file_read.dart';
|
||||
import 'package:network_proxy/network/util/host_filter.dart';
|
||||
import 'package:network_proxy/network/util/request_rewrite.dart';
|
||||
import 'package:network_proxy/network/util/script_manager.dart';
|
||||
import 'package:network_proxy/utils/ip.dart';
|
||||
|
||||
import 'channel.dart';
|
||||
@@ -126,15 +127,20 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
|
||||
//实现抓包代理转发
|
||||
if (httpRequest.method != HttpMethod.connect) {
|
||||
log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}");
|
||||
|
||||
//替换请求体
|
||||
_rewriteBody(httpRequest);
|
||||
|
||||
if (!HostFilter.filter(httpRequest.hostAndPort?.host)) {
|
||||
listener?.onRequest(channel, httpRequest);
|
||||
// log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}");
|
||||
if (HostFilter.filter(httpRequest.hostAndPort?.host)) {
|
||||
await remoteChannel.write(httpRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
//脚本替换
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
httpRequest = await scriptManager.runScript(httpRequest);
|
||||
//替换请求体
|
||||
rewriteBody(httpRequest);
|
||||
|
||||
listener?.onRequest(channel, httpRequest);
|
||||
|
||||
//重定向
|
||||
var redirectRewrite =
|
||||
requestRewrites?.findRequestRewrite(httpRequest.hostAndPort?.host, httpRequest.path(), RuleType.redirect);
|
||||
@@ -152,7 +158,7 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
}
|
||||
|
||||
//替换请求体
|
||||
_rewriteBody(HttpRequest httpRequest) {
|
||||
rewriteBody(HttpRequest httpRequest) {
|
||||
var rewrite = requestRewrites?.findRequestRewrite(httpRequest.hostAndPort?.host, httpRequest.path(), RuleType.body);
|
||||
|
||||
if (rewrite?.requestBody?.isNotEmpty == true) {
|
||||
@@ -222,7 +228,7 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
/// 异常处理
|
||||
_exceptionHandler(Channel channel, HttpRequest? request, error) {
|
||||
HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.host);
|
||||
hostAndPort ??= HostAndPort.host(scheme:HostAndPort.httpScheme, channel.remoteAddress.host, channel.remotePort);
|
||||
hostAndPort ??= HostAndPort.host(scheme: HostAndPort.httpScheme, channel.remoteAddress.host, channel.remotePort);
|
||||
String message = error.toString();
|
||||
HttpStatus status = HttpStatus(-1, message);
|
||||
if (error is HandshakeException) {
|
||||
@@ -231,7 +237,10 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
status = HttpStatus(-3, error.message);
|
||||
} else if (error is SocketException) {
|
||||
status = HttpStatus(-4, error.message);
|
||||
} else if (error is SignalException) {
|
||||
status.reason('执行脚本异常');
|
||||
}
|
||||
|
||||
request ??= HttpRequest(HttpMethod.connect, hostAndPort.domain)
|
||||
..body = message.codeUnits
|
||||
..headers.contentLength = message.codeUnits.length
|
||||
@@ -241,6 +250,7 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
..headers.contentType = 'text/plain'
|
||||
..headers.contentLength = message.codeUnits.length
|
||||
..body = message.codeUnits;
|
||||
|
||||
listener?.onRequest(channel, request);
|
||||
listener?.onResponse(channel, request.response!);
|
||||
}
|
||||
@@ -260,17 +270,29 @@ class HttpResponseProxyHandler extends ChannelHandler<HttpResponse> {
|
||||
void channelRead(Channel channel, HttpResponse msg) async {
|
||||
msg.request = clientChannel.getAttribute(AttributeKeys.request);
|
||||
msg.request?.response = msg;
|
||||
// log.i("[${clientChannel.id}] Response ${msg}");
|
||||
//域名是否过滤
|
||||
if (HostFilter.filter(msg.request?.hostAndPort?.host) || msg.request?.method == HttpMethod.connect) {
|
||||
await clientChannel.write(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// log.i("[${clientChannel.id}] Response $msg");
|
||||
//脚本替换
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
try {
|
||||
msg = await scriptManager.runResponseScript(msg);
|
||||
} catch (e, t) {
|
||||
msg.status = HttpStatus(-1, '执行脚本异常');
|
||||
msg.body = "$e\n${msg.bodyAsString}".codeUnits;
|
||||
log.e('[${clientChannel.id}] 执行脚本异常 ', error: e, stackTrace: t);
|
||||
}
|
||||
|
||||
var replaceBody = requestRewrites?.findResponseReplaceWith(msg.request?.hostAndPort?.host, msg.request?.path());
|
||||
if (replaceBody?.isNotEmpty == true) {
|
||||
msg.body = utf8.encode(replaceBody!);
|
||||
}
|
||||
|
||||
if (!HostFilter.filter(msg.request?.hostAndPort?.host) && msg.request?.method != HttpMethod.connect) {
|
||||
listener?.onResponse(clientChannel, msg);
|
||||
}
|
||||
|
||||
listener?.onResponse(clientChannel, msg);
|
||||
//发送给客户端
|
||||
await clientChannel.write(msg);
|
||||
}
|
||||
@@ -287,7 +309,7 @@ class RelayHandler extends ChannelHandler<Object> {
|
||||
RelayHandler(this.remoteChannel);
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, Object msg) {
|
||||
void channelRead(Channel channel, Object msg) async {
|
||||
//发送给客户端
|
||||
remoteChannel.write(msg);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ class HttpRequest extends HttpMessage {
|
||||
HostAndPort? hostAndPort;
|
||||
DateTime requestTime = DateTime.now(); //请求时间
|
||||
HttpResponse? response;
|
||||
Map<String, dynamic> attributes = {};
|
||||
|
||||
HttpRequest(this.method, this.uri, {String protocolVersion = "HTTP/1.1"}) : super(protocolVersion);
|
||||
|
||||
@@ -176,7 +177,7 @@ enum ContentType {
|
||||
|
||||
///HTTP响应。
|
||||
class HttpResponse extends HttpMessage {
|
||||
final HttpStatus status;
|
||||
HttpStatus status;
|
||||
DateTime responseTime = DateTime.now();
|
||||
HttpRequest? request;
|
||||
|
||||
@@ -283,7 +284,7 @@ class HttpStatus {
|
||||
return HttpStatus(statusCode, reasonPhrase);
|
||||
}
|
||||
|
||||
static HttpStatus? valueOf(int code) {
|
||||
static HttpStatus valueOf(int code) {
|
||||
switch (code) {
|
||||
case 200:
|
||||
return ok;
|
||||
@@ -304,7 +305,7 @@ class HttpStatus {
|
||||
case 504:
|
||||
return gatewayTimeout;
|
||||
}
|
||||
return null;
|
||||
return HttpStatus(code, "");
|
||||
}
|
||||
|
||||
final int code;
|
||||
|
||||
@@ -128,6 +128,12 @@ class HttpHeaders {
|
||||
}
|
||||
}
|
||||
|
||||
//清空
|
||||
void clean() {
|
||||
_headers.clear();
|
||||
_originalHeaderNames.clear();
|
||||
}
|
||||
|
||||
String headerLines() {
|
||||
StringBuffer sb = StringBuffer();
|
||||
forEach((name, values) {
|
||||
@@ -148,6 +154,15 @@ class HttpHeaders {
|
||||
return json;
|
||||
}
|
||||
|
||||
///转换json
|
||||
Map<String, String> toMap() {
|
||||
Map<String, String> json = {};
|
||||
forEach((name, values) {
|
||||
json[name] = values.join(";");
|
||||
});
|
||||
return json;
|
||||
}
|
||||
|
||||
///从json解析
|
||||
factory HttpHeaders.fromJson(Map<String, dynamic> json) {
|
||||
HttpHeaders headers = HttpHeaders();
|
||||
|
||||
@@ -87,7 +87,7 @@ class HttpClients {
|
||||
/// 发送get请求
|
||||
static Future<HttpResponse> get(String url, {Duration duration = const Duration(seconds: 3)}) async {
|
||||
HttpRequest msg = HttpRequest(HttpMethod.get, url);
|
||||
return request(HostAndPort.of(url), msg);
|
||||
return request(HostAndPort.of(url), msg, duration: duration);
|
||||
}
|
||||
|
||||
/// 发送请求
|
||||
@@ -106,7 +106,7 @@ class HttpClients {
|
||||
|
||||
/// 发送代理请求
|
||||
static Future<HttpResponse> proxyRequest(HttpRequest request,
|
||||
{ProxyInfo? proxyInfo, Duration timeout = const Duration(seconds: 3)}) async {
|
||||
{ProxyInfo? proxyInfo, Duration timeout = const Duration(seconds: 10)}) async {
|
||||
if (request.headers.host == null || request.headers.host?.trim().isEmpty == true) {
|
||||
try {
|
||||
request.headers.host = '${Uri.parse(request.uri).host}:${Uri.parse(request.uri).port}';
|
||||
@@ -114,9 +114,7 @@ class HttpClients {
|
||||
}
|
||||
|
||||
var httpResponseHandler = HttpResponseHandler();
|
||||
|
||||
HostAndPort hostPort = HostAndPort.of(request.uri);
|
||||
|
||||
Channel channel = await proxyConnect(proxyInfo: proxyInfo, hostPort, httpResponseHandler);
|
||||
|
||||
if (hostPort.isSsl()) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'package:network_proxy/network/handler.dart';
|
||||
import 'package:network_proxy/network/util/attribute_keys.dart';
|
||||
import 'package:network_proxy/network/util/crts.dart';
|
||||
import 'package:network_proxy/network/util/host_filter.dart';
|
||||
import 'package:network_proxy/utils/platform.dart';
|
||||
|
||||
import 'host_port.dart';
|
||||
|
||||
@@ -61,7 +62,8 @@ class Network {
|
||||
HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.host);
|
||||
|
||||
//黑名单 或 没开启https 直接转发
|
||||
if (HostFilter.filter(hostAndPort?.host) || (hostAndPort?.isSsl() == true && configuration?.enableSsl == false)) {
|
||||
if ((Platforms.isMobile() && HostFilter.filter(hostAndPort?.host)) ||
|
||||
(hostAndPort?.isSsl() == true && configuration?.enableSsl == false)) {
|
||||
relay(channel, channel.getAttribute(channel.id));
|
||||
channel.pipeline.channelRead(channel, data);
|
||||
return;
|
||||
|
||||
@@ -42,6 +42,7 @@ class RequestRewrites {
|
||||
return null;
|
||||
}
|
||||
|
||||
///
|
||||
String? findResponseReplaceWith(String? domain, String? path) {
|
||||
if (!enabled || path == null) {
|
||||
return null;
|
||||
@@ -57,7 +58,7 @@ class RequestRewrites {
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
///添加规则
|
||||
void addRule(RequestRewriteRule rule) {
|
||||
rules.removeWhere((it) => it.path == rule.path && it.domain == rule.domain);
|
||||
rules.add(rule);
|
||||
|
||||
274
lib/network/util/script_manager.dart
Normal file
274
lib/network/util/script_manager.dart
Normal file
@@ -0,0 +1,274 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter_js/flutter_js.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
/// @author wanghongen
|
||||
/// 2023/10/06
|
||||
/// js脚本
|
||||
class ScriptManager {
|
||||
static String separator = Platform.pathSeparator;
|
||||
static ScriptManager? _instance;
|
||||
bool enabled = true;
|
||||
List<ScriptItem> list = [];
|
||||
|
||||
final Map<ScriptItem, String> _scriptMap = {};
|
||||
|
||||
static JavascriptRuntime flutterJs = getJavascriptRuntime();
|
||||
|
||||
ScriptManager._();
|
||||
|
||||
///单例
|
||||
static Future<ScriptManager> get instance async {
|
||||
if (_instance == null) {
|
||||
_instance = ScriptManager._();
|
||||
await _instance?.reloadScript();
|
||||
print('init script manager');
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
///重新加载脚本
|
||||
Future<void> reloadScript() async {
|
||||
List<ScriptItem> scripts = [];
|
||||
var file = await _path;
|
||||
print("reloadScript ${file.path}");
|
||||
if (await file.exists()) {
|
||||
var content = await file.readAsString();
|
||||
if (content.isEmpty) {
|
||||
return;
|
||||
}
|
||||
var config = jsonDecode(content);
|
||||
enabled = config['enabled'] == true;
|
||||
for (var entry in config['list']) {
|
||||
scripts.add(ScriptItem.fromJson(entry));
|
||||
}
|
||||
}
|
||||
list = scripts;
|
||||
_scriptMap.clear();
|
||||
}
|
||||
|
||||
static String? _homePath;
|
||||
|
||||
static Future<String> homePath() async {
|
||||
if (_homePath != null) {
|
||||
return _homePath!;
|
||||
}
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
_homePath = await DesktopMultiWindow.invokeMethod(0, "getApplicationSupportDirectory");
|
||||
} else {
|
||||
_homePath = await getApplicationSupportDirectory().then((it) => it.path);
|
||||
}
|
||||
return _homePath!;
|
||||
}
|
||||
|
||||
static Future<File> get _path async {
|
||||
final path = await homePath();
|
||||
var file = File('$path${separator}script.json');
|
||||
if (!await file.exists()) {
|
||||
await file.create();
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
Future<String> getScript(ScriptItem item) async {
|
||||
if (_scriptMap.containsKey(item)) {
|
||||
return _scriptMap[item]!;
|
||||
}
|
||||
var script = await File(item.scriptPath!).readAsString();
|
||||
_scriptMap[item] = script;
|
||||
return script;
|
||||
}
|
||||
|
||||
///添加脚本
|
||||
Future<void> addScript(ScriptItem item, String script) async {
|
||||
final path = await homePath();
|
||||
var file = File('$path${separator}scripts$separator${DateTime.now().millisecondsSinceEpoch}.js');
|
||||
await file.create(recursive: true);
|
||||
file.writeAsString(script);
|
||||
item.scriptPath = file.path;
|
||||
list.add(item);
|
||||
_scriptMap[item] = script;
|
||||
}
|
||||
|
||||
///更新脚本
|
||||
Future<void> updateScript(ScriptItem item, String script) async {
|
||||
if (_scriptMap[item] == script) {
|
||||
return;
|
||||
}
|
||||
|
||||
File(item.scriptPath!).writeAsString(script);
|
||||
_scriptMap[item] = script;
|
||||
}
|
||||
|
||||
///删除脚本
|
||||
Future<void> removeScript(int index) async {
|
||||
var item = list.removeAt(index);
|
||||
File(item.scriptPath!).delete();
|
||||
flushConfig();
|
||||
}
|
||||
|
||||
///刷新配置
|
||||
Future<void> flushConfig() async {
|
||||
_path.then((value) => value.writeAsString(jsonEncode({'enabled': enabled, 'list': list})));
|
||||
}
|
||||
|
||||
///脚本上下文
|
||||
Map<String, dynamic> scriptContext(ScriptItem item) {
|
||||
return {
|
||||
'scriptName': item.name,
|
||||
'os': Platform.operatingSystem,
|
||||
};
|
||||
}
|
||||
|
||||
///运行脚本
|
||||
Future<HttpRequest> runScript(HttpRequest request) async {
|
||||
if (!enabled) {
|
||||
return request;
|
||||
}
|
||||
var url = request.requestUrl;
|
||||
for (var item in list) {
|
||||
if (item.enabled && item.match(url)) {
|
||||
var context = jsonEncode(scriptContext(item));
|
||||
var jsRequest = jsonEncode(convertJsRequest(request));
|
||||
String script = await getScript(item);
|
||||
|
||||
var jsResult = await flutterJs.evaluateAsync(
|
||||
"""var request = $jsRequest, context = $context; request['context'] = context; $script\n onRequest(context, request)""");
|
||||
var result = await jsResultResolve(jsResult);
|
||||
request.attributes['scriptContext'] = result['context'];
|
||||
return convertHttpRequest(request, result);
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
///运行脚本
|
||||
Future<HttpResponse> runResponseScript(HttpResponse response) async {
|
||||
if (!enabled || response.request == null) {
|
||||
return response;
|
||||
}
|
||||
|
||||
var request = response.request!;
|
||||
var url = request.requestUrl;
|
||||
for (var item in list) {
|
||||
if (item.enabled && item.match(url)) {
|
||||
var context = jsonEncode(request.attributes['scriptContext'] ?? scriptContext(item));
|
||||
var jsRequest = jsonEncode(convertJsRequest(request));
|
||||
var jsResponse = jsonEncode(convertJsResponse(response));
|
||||
print(context);
|
||||
String script = await getScript(item);
|
||||
var jsResult = await flutterJs.evaluateAsync("""$script\n onResponse($context, $jsRequest,$jsResponse);""");
|
||||
var result = await jsResultResolve(jsResult);
|
||||
// print("response: ${jsResult.isPromise} ${jsResult.isError} $result");
|
||||
return convertHttpResponse(response, result);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<dynamic> jsResultResolve(JsEvalResult jsResult) async {
|
||||
if (jsResult.isPromise) {
|
||||
jsResult = await flutterJs.handlePromise(jsResult);
|
||||
}
|
||||
var result = jsResult.rawResult;
|
||||
if (Platform.isMacOS) {
|
||||
result = flutterJs.convertValue(jsResult);
|
||||
}
|
||||
if (result is Future) {
|
||||
result = await (jsResult.rawResult as Future);
|
||||
}
|
||||
if (result is String) {
|
||||
result = jsonDecode(result);
|
||||
}
|
||||
if (jsResult.isError) {
|
||||
throw SignalException(jsResult.stringResult);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//转换js request
|
||||
Map<String, dynamic> convertJsRequest(HttpRequest request) {
|
||||
var requestUri = request.requestUri;
|
||||
return {
|
||||
'host': requestUri?.host,
|
||||
'url': request.requestUrl,
|
||||
'path': requestUri?.path,
|
||||
'queries': requestUri?.queryParameters,
|
||||
'headers': request.headers.toMap(),
|
||||
'method': request.method.name,
|
||||
'body': request.bodyAsString
|
||||
};
|
||||
}
|
||||
|
||||
//转换js response
|
||||
Map<String, dynamic> convertJsResponse(HttpResponse response) {
|
||||
return {'headers': response.headers.toMap(), 'statusCode': response.status.code, 'body': response.bodyAsString};
|
||||
}
|
||||
|
||||
//http request
|
||||
HttpRequest convertHttpRequest(HttpRequest request, Map<dynamic, dynamic> map) {
|
||||
request.headers.clean();
|
||||
request.method = HttpMethod.values.firstWhere((element) => element.name == map['method']);
|
||||
String query = '';
|
||||
map['queries']?.forEach((key, value) {
|
||||
query += '$key=$value&';
|
||||
});
|
||||
|
||||
request.uri = Uri.parse('${request.remoteDomain()}${map['path']}?$query').toString();
|
||||
|
||||
map['headers'].forEach((key, value) {
|
||||
request.headers.add(key, value);
|
||||
});
|
||||
request.body = map['body']?.toString().codeUnits;
|
||||
return request;
|
||||
}
|
||||
|
||||
//http response
|
||||
HttpResponse convertHttpResponse(HttpResponse response, Map<dynamic, dynamic> map) {
|
||||
response.headers.clean();
|
||||
response.status = HttpStatus.valueOf(map['statusCode']);
|
||||
map['headers'].forEach((key, value) {
|
||||
response.headers.add(key, value);
|
||||
});
|
||||
response.body = map['body']?.toString().codeUnits;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptItem {
|
||||
bool enabled = true;
|
||||
String? name;
|
||||
String url;
|
||||
String? scriptPath;
|
||||
RegExp? urlReg;
|
||||
|
||||
ScriptItem(this.enabled, this.name, this.url, {this.scriptPath});
|
||||
|
||||
//匹配url
|
||||
bool match(String url) {
|
||||
if (!this.url.startsWith('http://') && !this.url.startsWith('https://')) {
|
||||
//不是http开头的url 需要去掉协议
|
||||
url = url.substring(url.indexOf('://') + 3);
|
||||
}
|
||||
urlReg ??= RegExp(this.url.replaceAll("*", ".*"));
|
||||
return urlReg!.hasMatch(url);
|
||||
}
|
||||
|
||||
factory ScriptItem.fromJson(Map<String, dynamic> json) {
|
||||
return ScriptItem(json['enabled'], json['name'], json['url'], scriptPath: json['scriptPath']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'enabled': enabled, 'name': name, 'url': url, 'scriptPath': scriptPath};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ScriptItem{enabled: $enabled, name: $name, url: $url, scriptPath: $scriptPath}';
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
class FavoriteStorage {
|
||||
static Queue<HttpRequest>? _requests;
|
||||
|
||||
/// 获取收藏列表
|
||||
static Future<Queue<HttpRequest>> get favorites async {
|
||||
if (_requests == null) {
|
||||
var file = await _path;
|
||||
|
||||
@@ -2,11 +2,49 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/network/util/script_manager.dart';
|
||||
import 'package:network_proxy/ui/component/encoder.dart';
|
||||
import 'package:network_proxy/ui/content/body.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/request_editor.dart';
|
||||
import 'package:network_proxy/ui/desktop/toolbar/setting/script.dart';
|
||||
import 'package:network_proxy/utils/platform.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
///多窗口
|
||||
Widget multiWindow(int windowId, Map<dynamic, dynamic> argument) {
|
||||
if (argument['name'] == 'RequestEditor') {
|
||||
return RequestEditor(
|
||||
windowController: WindowController.fromWindowId(windowId),
|
||||
request: argument['request'] == null ? null : HttpRequest.fromJson(argument['request']));
|
||||
}
|
||||
//请求体
|
||||
if (argument['name'] == 'HttpBodyWidget') {
|
||||
return HttpBodyWidget(
|
||||
windowController: WindowController.fromWindowId(windowId),
|
||||
httpMessage: HttpMessage.fromJson(argument['httpMessage']),
|
||||
inNewWindow: true,
|
||||
hideRequestRewrite: true);
|
||||
}
|
||||
//编码
|
||||
if (argument['name'] == 'EncoderWidget') {
|
||||
return EncoderWidget(
|
||||
type: EncoderType.nameOf(argument['type']),
|
||||
text: argument['text'],
|
||||
windowController: WindowController.fromWindowId(windowId));
|
||||
}
|
||||
//脚本
|
||||
if (argument['name'] == 'ScriptWidget') {
|
||||
return ScriptWidget(windowId: windowId);
|
||||
}
|
||||
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
//打开编码窗口
|
||||
encodeWindow(EncoderType type, BuildContext context, [String? text]) async {
|
||||
if (Platforms.isMobile()) {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => EncoderWidget(type: type, text: text)));
|
||||
@@ -17,7 +55,6 @@ encodeWindow(EncoderType type, BuildContext context, [String? text]) async {
|
||||
if (Platform.isWindows) {
|
||||
ratio = WindowManager.instance.getDevicePixelRatio();
|
||||
}
|
||||
|
||||
final window = await DesktopMultiWindow.createWindow(jsonEncode(
|
||||
{'name': 'EncoderWidget', 'type': type.name, 'text': text},
|
||||
));
|
||||
@@ -27,3 +64,50 @@ encodeWindow(EncoderType type, BuildContext context, [String? text]) async {
|
||||
..center()
|
||||
..show();
|
||||
}
|
||||
|
||||
bool _registerHandler = false;
|
||||
|
||||
void methodHandler() {
|
||||
if (_registerHandler) {
|
||||
return;
|
||||
}
|
||||
_registerHandler = true;
|
||||
DesktopMultiWindow.setMethodHandler((call, fromWindowId) async {
|
||||
print('${call.method} ${call.arguments} $fromWindowId');
|
||||
|
||||
if (call.method == 'getApplicationSupportDirectory') {
|
||||
return getApplicationSupportDirectory().then((it) => it.path);
|
||||
}
|
||||
if (call.method == 'getSaveLocation') {
|
||||
return (await getSaveLocation(suggestedName: call.arguments))?.path;
|
||||
}
|
||||
if (call.method == 'openFile') {
|
||||
XTypeGroup typeGroup = XTypeGroup(extensions: <String>[call.arguments]);
|
||||
final XFile? file = await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
|
||||
return file?.path;
|
||||
}
|
||||
if (call.method == 'refreshScript') {
|
||||
await ScriptManager.instance.then((value) {
|
||||
return value.reloadScript();
|
||||
});
|
||||
}
|
||||
return 'done';
|
||||
});
|
||||
}
|
||||
|
||||
///打开脚本窗口
|
||||
openScriptWindow() async {
|
||||
var ratio = 1.0;
|
||||
if (Platform.isWindows) {
|
||||
ratio = WindowManager.instance.getDevicePixelRatio();
|
||||
}
|
||||
methodHandler();
|
||||
final window = await DesktopMultiWindow.createWindow(jsonEncode(
|
||||
{'name': 'ScriptWidget'},
|
||||
));
|
||||
window.setTitle('脚本');
|
||||
window
|
||||
..setFrame(const Offset(30, 0) & Size(800 * ratio, 690 * ratio))
|
||||
..center()
|
||||
..show();
|
||||
}
|
||||
|
||||
@@ -28,3 +28,52 @@ class _CustomPopupMenuItemState<T> extends PopupMenuItemState<T, CustomPopupMenu
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchWidget extends StatefulWidget {
|
||||
final String? title;
|
||||
final String? subtitle;
|
||||
final bool value;
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
const SwitchWidget({super.key, this.title, this.subtitle, required this.value, required this.onChanged});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _SwitchState();
|
||||
}
|
||||
|
||||
class _SwitchState extends State<SwitchWidget> {
|
||||
bool value = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
value = widget.value;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.title == null) {
|
||||
return Switch(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
this.value = value;
|
||||
});
|
||||
widget.onChanged(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
return SwitchListTile(
|
||||
title: widget.title == null ? null : Text(widget.title!),
|
||||
subtitle: widget.subtitle == null ? null : Text(widget.subtitle!),
|
||||
value: value,
|
||||
dense: true,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
this.value = value;
|
||||
});
|
||||
widget.onChanged(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:network_proxy/network/bin/server.dart';
|
||||
import 'package:network_proxy/network/channel.dart';
|
||||
import 'package:network_proxy/network/handler.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/network/util/logger.dart';
|
||||
import 'package:network_proxy/storage/histories.dart';
|
||||
import 'package:network_proxy/ui/component/utils.dart';
|
||||
import 'package:network_proxy/ui/component/widgets.dart';
|
||||
@@ -98,7 +99,6 @@ class _HistoryState extends State<_HistoryWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print("_HistoryState build");
|
||||
List<Widget> children = [];
|
||||
if (!_sessionSaved) {
|
||||
//当前会话未保存,是否保存当前会话
|
||||
@@ -127,16 +127,12 @@ class _HistoryState extends State<_HistoryWidget> {
|
||||
|
||||
//导入har
|
||||
import() async {
|
||||
const XTypeGroup typeGroup = XTypeGroup(
|
||||
label: 'Har',
|
||||
extensions: <String>['har'],
|
||||
);
|
||||
const XTypeGroup typeGroup = XTypeGroup(label: 'Har', extensions: <String>['har']);
|
||||
final XFile? file = await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
print(file);
|
||||
try {
|
||||
var historyItem = await storage.addHarFile(file);
|
||||
setState(() {
|
||||
@@ -144,8 +140,7 @@ class _HistoryState extends State<_HistoryWidget> {
|
||||
FlutterToastr.show("导入成功", context);
|
||||
});
|
||||
} catch (e, t) {
|
||||
print(e);
|
||||
print(t);
|
||||
logger.e('导入失败 $file', error: e, stackTrace: t);
|
||||
if (context.mounted) {
|
||||
FlutterToastr.show("导入失败 $e", context);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
|
||||
463
lib/ui/desktop/toolbar/setting/script.dart
Normal file
463
lib/ui/desktop/toolbar/setting/script.dart
Normal file
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||
import 'package:flutter_highlight/themes/monokai-sublime.dart';
|
||||
import 'package:flutter_toastr/flutter_toastr.dart';
|
||||
import 'package:highlight/languages/javascript.dart';
|
||||
import 'package:network_proxy/network/util/logger.dart';
|
||||
import 'package:network_proxy/network/util/script_manager.dart';
|
||||
import 'package:network_proxy/ui/component/utils.dart';
|
||||
import 'package:network_proxy/ui/component/widgets.dart';
|
||||
|
||||
bool _refresh = false;
|
||||
|
||||
/// 刷新脚本
|
||||
void _refreshScript() {
|
||||
if (_refresh) {
|
||||
return;
|
||||
}
|
||||
_refresh = true;
|
||||
Future.delayed(const Duration(milliseconds: 1500), () async {
|
||||
_refresh = false;
|
||||
(await ScriptManager.instance).flushConfig();
|
||||
await DesktopMultiWindow.invokeMethod(0, "refreshScript");
|
||||
});
|
||||
}
|
||||
|
||||
class ScriptWidget extends StatefulWidget {
|
||||
final int windowId;
|
||||
|
||||
const ScriptWidget({super.key, required this.windowId});
|
||||
|
||||
@override
|
||||
State<ScriptWidget> createState() => _ScriptWidgetState();
|
||||
}
|
||||
|
||||
class _ScriptWidgetState extends State<ScriptWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
RawKeyboard.instance.addListener(onKeyEvent);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
RawKeyboard.instance.removeListener(onKeyEvent);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void onKeyEvent(RawKeyEvent event) async {
|
||||
if ((event.isKeyPressed(LogicalKeyboardKey.metaLeft) || event.isControlPressed) &&
|
||||
event.isKeyPressed(LogicalKeyboardKey.keyW)) {
|
||||
RawKeyboard.instance.removeListener(onKeyEvent);
|
||||
WindowController.fromWindowId(widget.windowId).close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).dialogBackgroundColor,
|
||||
appBar: AppBar(
|
||||
title: const Text("脚本", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
toolbarHeight: 36,
|
||||
centerTitle: true),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(left: 15, right: 10),
|
||||
child: futureWidget(
|
||||
ScriptManager.instance,
|
||||
loading: true,
|
||||
(data) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: SwitchWidget(
|
||||
title: '启用脚本工具',
|
||||
subtitle: "使用 JavaScript 修改请求和响应",
|
||||
value: data.enabled,
|
||||
onChanged: (value) {
|
||||
data.enabled = value;
|
||||
_refreshScript();
|
||||
},
|
||||
)),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
FilledButton(
|
||||
style: ElevatedButton.styleFrom(padding: const EdgeInsets.only(left: 20, right: 20)),
|
||||
onPressed: scriptEdit,
|
||||
child: const Text("添加"),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
OutlinedButton(
|
||||
style: ElevatedButton.styleFrom(padding: const EdgeInsets.only(left: 20, right: 20)),
|
||||
onPressed: import,
|
||||
child: const Text("导入"),
|
||||
)
|
||||
],
|
||||
)),
|
||||
const SizedBox(width: 15)
|
||||
]),
|
||||
const SizedBox(height: 5),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
constraints: const BoxConstraints(maxHeight: 500, minHeight: 300),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
||||
color: Colors.white,
|
||||
backgroundBlendMode: BlendMode.colorBurn),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 200, padding: const EdgeInsets.only(left: 10), child: const Text("名称")),
|
||||
const SizedBox(width: 50, child: Text("启用", textAlign: TextAlign.center)),
|
||||
const VerticalDivider(),
|
||||
const Expanded(child: Text("URL")),
|
||||
],
|
||||
),
|
||||
const Divider(thickness: 0.5),
|
||||
ScriptList(scripts: data.list, windowId: widget.windowId),
|
||||
]))),
|
||||
]))));
|
||||
}
|
||||
|
||||
//导入js
|
||||
import() async {
|
||||
String? file = Platform.isMacOS
|
||||
? await DesktopMultiWindow.invokeMethod(0, 'openFile', 'json')
|
||||
: await openFile(acceptedTypeGroups: [
|
||||
const XTypeGroup(extensions: ['json'])
|
||||
]).then((it) => it?.path);
|
||||
WindowController.fromWindowId(widget.windowId).show();
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var json = jsonDecode(await File(file).readAsString());
|
||||
var scriptItem = ScriptItem.fromJson(json);
|
||||
(await ScriptManager.instance).addScript(scriptItem, json['script']);
|
||||
_refreshScript();
|
||||
if (context.mounted) {
|
||||
FlutterToastr.show("导入成功", context);
|
||||
}
|
||||
setState(() {});
|
||||
} catch (e, t) {
|
||||
logger.e('导入失败 $file', error: e, stackTrace: t);
|
||||
if (context.mounted) {
|
||||
FlutterToastr.show("导入失败 $e", context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 添加脚本
|
||||
scriptEdit() async {
|
||||
showDialog(barrierDismissible: false, context: context, builder: (_) => const ScriptEdit()).then((value) {
|
||||
if (value != null) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 编辑脚本
|
||||
class ScriptEdit extends StatefulWidget {
|
||||
final ScriptItem? scriptItem;
|
||||
final String? script;
|
||||
|
||||
const ScriptEdit({Key? key, this.scriptItem, this.script}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ScriptEditState();
|
||||
}
|
||||
|
||||
class _ScriptEditState extends State<ScriptEdit> {
|
||||
static String template = """
|
||||
// 在请求到达服务器之前,调用此函数,您可以在此处修改请求数据
|
||||
// 例如Add/Update/Remove:Queries、Headers、Body
|
||||
async function onRequest(context, request) {
|
||||
console.log(request.url);
|
||||
//URL参数
|
||||
//request.queries["name"] = "value";
|
||||
// 更新或添加新标头
|
||||
//request.headers["X-New-Headers"] = "My-Value";
|
||||
|
||||
//var body = JSON.parse(response.body);
|
||||
//body['key'] = "value";
|
||||
//response.body = JSON.stringify(body);
|
||||
return request;
|
||||
}
|
||||
|
||||
// 在将响应数据发送到客户端之前,调用此函数,您可以在此处修改响应数据
|
||||
async function onResponse(context, request, response) {
|
||||
// 更新或添加新标头
|
||||
// response.headers["Name"] = "Value";
|
||||
// response.statusCode = 200;
|
||||
|
||||
// Update Body
|
||||
//response.body = await fetch('https://www.baidu.com/').then(response => response.text());
|
||||
return response;
|
||||
}
|
||||
""";
|
||||
|
||||
late CodeController script;
|
||||
late TextEditingController nameController;
|
||||
late TextEditingController urlController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
script = CodeController(language: javascript, text: widget.script ?? template);
|
||||
nameController = TextEditingController(text: widget.scriptItem?.name);
|
||||
urlController = TextEditingController(text: widget.scriptItem?.url);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
script.dispose();
|
||||
nameController.dispose();
|
||||
urlController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
GlobalKey formKey = GlobalKey<FormState>();
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
titlePadding: const EdgeInsets.only(left: 15, top: 5, right: 15),
|
||||
title: const Row(children: [
|
||||
Text("编辑脚本", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(width: 10),
|
||||
Tooltip(message: "使用 JavaScript 修改请求和响应", child: Icon(Icons.help_outline, size: 20)),
|
||||
Expanded(child: Align(alignment: Alignment.topRight, child: CloseButton()))
|
||||
]),
|
||||
actionsPadding: const EdgeInsets.only(right: 10, bottom: 10),
|
||||
actions: [
|
||||
ElevatedButton(onPressed: () => Navigator.of(context).pop(), child: const Text("取消")),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
if (!(formKey.currentState as FormState).validate()) {
|
||||
FlutterToastr.show("名称和URL不能为空", context, position: FlutterToastr.top);
|
||||
return;
|
||||
}
|
||||
//新增
|
||||
if (widget.scriptItem == null) {
|
||||
var scriptItem = ScriptItem(true, nameController.text, urlController.text);
|
||||
(await ScriptManager.instance).addScript(scriptItem, script.text);
|
||||
} else {
|
||||
widget.scriptItem?.name = nameController.text;
|
||||
widget.scriptItem?.url = urlController.text;
|
||||
(await ScriptManager.instance).updateScript(widget.scriptItem!, script.text);
|
||||
}
|
||||
|
||||
_refreshScript();
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).maybePop(true);
|
||||
}
|
||||
},
|
||||
child: const Text("保存")),
|
||||
],
|
||||
content: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
textField("名称:", nameController, "请输入名称"),
|
||||
const SizedBox(height: 10),
|
||||
textField("URL:", urlController, "github.com/api/*", keyboardType: TextInputType.url),
|
||||
const SizedBox(height: 10),
|
||||
const Text("脚本:"),
|
||||
const SizedBox(height: 5),
|
||||
SizedBox(
|
||||
width: 850,
|
||||
height: 360,
|
||||
child: CodeTheme(
|
||||
data: CodeThemeData(styles: monokaiSublimeTheme),
|
||||
child: SingleChildScrollView(
|
||||
child: CodeField(textStyle: const TextStyle(fontSize: 13), controller: script))))
|
||||
],
|
||||
)));
|
||||
}
|
||||
|
||||
Widget textField(String label, TextEditingController controller, String hint, {TextInputType? keyboardType}) {
|
||||
return Row(children: [
|
||||
SizedBox(width: 50, child: Text(label)),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
validator: (val) => val?.isNotEmpty == true ? null : "",
|
||||
keyboardType: keyboardType,
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
errorStyle: const TextStyle(height: 0, fontSize: 0),
|
||||
focusedBorder: focusedBorder(),
|
||||
isDense: true,
|
||||
constraints: const BoxConstraints(maxHeight: 35),
|
||||
border: const OutlineInputBorder()),
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
||||
InputBorder focusedBorder() {
|
||||
return OutlineInputBorder(borderSide: BorderSide(color: Theme.of(context).primaryColor, width: 2));
|
||||
}
|
||||
}
|
||||
|
||||
/// 脚本列表
|
||||
class ScriptList extends StatefulWidget {
|
||||
final int windowId;
|
||||
final List<ScriptItem> scripts;
|
||||
|
||||
const ScriptList({Key? key, required this.scripts, required this.windowId}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ScriptList> createState() => _ScriptListState();
|
||||
}
|
||||
|
||||
class _ScriptListState extends State<ScriptList> {
|
||||
Map<int, bool> selected = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: rows(widget.scripts));
|
||||
}
|
||||
|
||||
List<Widget> rows(List<ScriptItem> list) {
|
||||
var primaryColor = Theme.of(context).primaryColor;
|
||||
|
||||
return List.generate(list.length, (index) {
|
||||
return InkWell(
|
||||
// onTap: () {
|
||||
// selected[index] = !(selected[index] ?? false);
|
||||
// setState(() {});
|
||||
// },
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
hoverColor: primaryColor.withOpacity(0.3),
|
||||
onDoubleTap: () async {
|
||||
String script = await (await ScriptManager.instance).getScript(list[index]);
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (_) => ScriptEdit(scriptItem: list[index], script: script)).then((value) {
|
||||
if (value != null) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
},
|
||||
onSecondaryTapDown: (details) {
|
||||
showContextMenu(context, details.globalPosition, items: [
|
||||
PopupMenuItem(
|
||||
height: 35,
|
||||
child: const Text("编辑"),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (_) => ScriptEdit(scriptItem: list[index])).then((value) {
|
||||
if (value != null) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}),
|
||||
PopupMenuItem(height: 35, child: const Text("导出"), onTap: () => export(list[index])),
|
||||
PopupMenuItem(
|
||||
height: 35,
|
||||
child: list[index].enabled ? const Text("禁用") : const Text("启用"),
|
||||
onTap: () {
|
||||
list[index].enabled = !list[index].enabled;
|
||||
_refreshScript();
|
||||
setState(() {});
|
||||
}),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
height: 35,
|
||||
child: const Text("删除"),
|
||||
onTap: () async {
|
||||
(await ScriptManager.instance).removeScript(index);
|
||||
setState(() {});
|
||||
if (context.mounted) FlutterToastr.show('删除成功', context);
|
||||
}),
|
||||
]);
|
||||
},
|
||||
child: Container(
|
||||
color: selected[index] == true
|
||||
? primaryColor.withOpacity(0.8)
|
||||
: index.isEven
|
||||
? Colors.grey.withOpacity(0.1)
|
||||
: null,
|
||||
height: 30,
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 200, child: Text(list[index].name!, style: const TextStyle(fontSize: 13))),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: Transform.scale(
|
||||
scale: 0.65,
|
||||
child: SwitchWidget(
|
||||
value: list[index].enabled,
|
||||
onChanged: (val) {
|
||||
list[index].enabled = val;
|
||||
_refreshScript();
|
||||
}))),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(child: Text(list[index].url, style: const TextStyle(fontSize: 13))),
|
||||
],
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
//导出js
|
||||
export(ScriptItem item) async {
|
||||
//文件名称
|
||||
String fileName = '${item.name}.json';
|
||||
String? saveLocation = Platform.isMacOS
|
||||
? await DesktopMultiWindow.invokeMethod(0, 'getSaveLocation', fileName)
|
||||
: (await getSaveLocation(suggestedName: fileName))?.path;
|
||||
WindowController.fromWindowId(widget.windowId).show();
|
||||
if (saveLocation == null) {
|
||||
return;
|
||||
}
|
||||
var json = item.toJson();
|
||||
json.remove("scriptPath");
|
||||
json['script'] = await (await ScriptManager.instance).getScript(item);
|
||||
final XFile xFile = XFile.fromData(utf8.encode(jsonEncode(json)), mimeType: 'json');
|
||||
await xFile.saveTo(saveLocation);
|
||||
if (context.mounted) FlutterToastr.show("导出成功", context);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ 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/util/system_proxy.dart';
|
||||
import 'package:network_proxy/ui/component/multi_window.dart';
|
||||
import 'package:network_proxy/ui/desktop/toolbar/setting/external_proxy.dart';
|
||||
import 'package:network_proxy/ui/desktop/toolbar/setting/request_rewrite.dart';
|
||||
import 'package:network_proxy/ui/desktop/toolbar/setting/theme.dart';
|
||||
@@ -59,9 +60,10 @@ class _SettingState extends State<Setting> {
|
||||
},
|
||||
menuChildren: [
|
||||
_ProxyMenu(proxyServer: widget.proxyServer),
|
||||
const ThemeSetting(),
|
||||
item("域名过滤", onPressed: hostFilter),
|
||||
item("请求重写", onPressed: requestRewrite),
|
||||
const ThemeSetting(),
|
||||
item("脚本", onPressed: () => openScriptWindow()),
|
||||
item("外部代理设置", onPressed: setExternalProxy),
|
||||
item("Github", onPressed: () => launchUrl(Uri.parse("https://github.com/wanghongenpin/network_proxy_flutter"))),
|
||||
],
|
||||
@@ -181,7 +183,7 @@ class _ProxyMenuState extends State<_ProxyMenu> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text("代理忽略域名"),
|
||||
const Text("代理忽略域名", style: TextStyle(fontSize: 14)),
|
||||
const SizedBox(height: 3),
|
||||
Text("多个使用;分割", style: TextStyle(fontSize: 11, color: Colors.grey.shade600)),
|
||||
],
|
||||
@@ -210,7 +212,7 @@ class _ProxyMenuState extends State<_ProxyMenu> {
|
||||
minLines: 1)),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
child: const Padding(padding: EdgeInsets.only(left: 10), child: Text("代理")),
|
||||
child: const Padding(padding: EdgeInsets.only(left: 10), child: Text("代理",style: TextStyle(fontSize: 14))),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class ThemeSetting extends StatelessWidget {
|
||||
themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.light);
|
||||
}),
|
||||
],
|
||||
child: const Padding(padding: EdgeInsets.only(left: 10), child: Text("主题")),
|
||||
child: const Padding(padding: EdgeInsets.only(left: 10), child: Text("主题",style: TextStyle(fontSize: 14))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class _SocketLaunchState extends State<SocketLaunch> with WindowListener, Widget
|
||||
color: started ? Colors.red : Colors.green, size: widget.size.toDouble()),
|
||||
onPressed: () async {
|
||||
if (started) {
|
||||
if (widget.serverLaunch) {
|
||||
if (!widget.serverLaunch) {
|
||||
setState(() {
|
||||
widget.onStop?.call();
|
||||
started = !started;
|
||||
@@ -103,7 +103,7 @@ class _SocketLaunchState extends State<SocketLaunch> with WindowListener, Widget
|
||||
|
||||
///启动代理服务器
|
||||
start() {
|
||||
if (widget.serverLaunch) {
|
||||
if (!widget.serverLaunch) {
|
||||
setState(() {
|
||||
widget.onStart?.call();
|
||||
started = true;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <desktop_multi_window/desktop_multi_window_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_js/flutter_js_plugin.h>
|
||||
#include <proxy_manager/proxy_manager_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
@@ -20,6 +21,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_js_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterJsPlugin");
|
||||
flutter_js_plugin_register_with_registrar(flutter_js_registrar);
|
||||
g_autoptr(FlPluginRegistrar) proxy_manager_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ProxyManagerPlugin");
|
||||
proxy_manager_plugin_register_with_registrar(proxy_manager_registrar);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_multi_window
|
||||
file_selector_linux
|
||||
flutter_js
|
||||
proxy_manager
|
||||
screen_retriever
|
||||
url_launcher_linux
|
||||
|
||||
@@ -7,6 +7,7 @@ import Foundation
|
||||
|
||||
import desktop_multi_window
|
||||
import file_selector_macos
|
||||
import flutter_js
|
||||
import path_provider_foundation
|
||||
import proxy_manager
|
||||
import screen_retriever
|
||||
@@ -17,6 +18,7 @@ import window_manager
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterMultiWindowPlugin.register(with: registry.registrar(forPlugin: "FlutterMultiWindowPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
ProxyManagerPlugin.register(with: registry.registrar(forPlugin: "ProxyManagerPlugin"))
|
||||
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
"filename" : "icon_16x16.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
|
||||
136
pubspec.lock
136
pubspec.lock
@@ -9,6 +9,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
autotrie:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: autotrie
|
||||
sha256: "55da6faefb53cfcb0abb2f2ca8636123fb40e35286bb57440d2cf467568188f8"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
basic_utils:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -41,6 +49,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -69,10 +85,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: fd832b5384d0d6da4f6df60b854d33accaaeb63aa9e10e736a87381f08dee2cb
|
||||
sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.3+5"
|
||||
version: "0.3.3+6"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -114,6 +130,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -215,6 +239,30 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
flutter_code_editor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_code_editor
|
||||
sha256: "2e48e2a09c4205991787f299cd101f66f28e6845f882df543ae0f4260f9e2c67"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.9"
|
||||
flutter_highlight:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_highlight
|
||||
sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_js:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_js
|
||||
sha256: "5bf5db354fe78fe24cb90a5fa6b4423d38712440c88e3445c3dc88bc134c452f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -249,6 +297,22 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
highlight:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: highlight
|
||||
sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hive
|
||||
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -289,6 +353,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
linked_scroll_controller:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: linked_scroll_controller
|
||||
sha256: e6020062bcf4ffc907ee7fd090fa971e65d8dfaac3c62baf601a3ced0b37986a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -301,10 +373,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
sha256: ba3bc83117b2b49bdd723c0ea7848e8285a0fbc597ba09203b20d329d020c24a
|
||||
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.0.2+1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -405,10 +477,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
|
||||
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.3"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -465,22 +537,30 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.1.9"
|
||||
scrollable_positioned_list:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: scrollable_positioned_list
|
||||
sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.8"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11"
|
||||
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
version: "7.2.1"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7"
|
||||
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
version: "3.3.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -494,6 +574,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -518,6 +606,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
sync_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sync_http
|
||||
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -534,6 +630,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -610,10 +714,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
||||
sha256: b715b8d3858b6fa9f68f87d20d98830283628014750c2b09b6f516c1da4af2a7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
version: "4.1.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -634,18 +738,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa"
|
||||
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.0.7"
|
||||
version: "5.0.9"
|
||||
window_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: window_manager
|
||||
sha256: "6ee795be9124f90660ea9d05e581a466de19e1c89ee74fc4bf528f60c8600edd"
|
||||
sha256: dcc865277f26a7dad263a47d0e405d77e21f12cb71f30333a52710a408690bd7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.6"
|
||||
version: "0.3.7"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -15,7 +15,7 @@ dependencies:
|
||||
basic_utils: ^5.6.1
|
||||
logger: ^2.0.1
|
||||
date_format: ^2.0.7
|
||||
window_manager: ^0.3.6
|
||||
window_manager: ^0.3.7
|
||||
desktop_multi_window:
|
||||
git:
|
||||
url: https://gitee.com/wanghongenpin/flutter-plugins.git
|
||||
@@ -28,10 +28,12 @@ dependencies:
|
||||
qrscan: ^0.3.3
|
||||
flutter_barcode_scanner: ^2.0.0
|
||||
flutter_toastr: ^1.0.3
|
||||
share_plus: ^7.1.0
|
||||
share_plus: ^7.2.1
|
||||
brotli: ^0.6.0
|
||||
installed_apps: ^1.3.1
|
||||
file_selector: ^1.0.1
|
||||
flutter_js: ^0.8.0
|
||||
flutter_code_editor:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
34
test/js_test.dart
Normal file
34
test/js_test.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_js/flutter_js.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
|
||||
//转换js request
|
||||
Map<String, dynamic> convertJsRequest(HttpRequest request) {
|
||||
return {
|
||||
'url': request.requestUrl,
|
||||
'path': request.path(),
|
||||
'headers': request.headers.toMap(),
|
||||
'method': request.method.name,
|
||||
'body': request.bodyAsString
|
||||
};
|
||||
}
|
||||
|
||||
main() {
|
||||
var flutterJs = getJavascriptRuntime();
|
||||
var httpRequest = HttpRequest(HttpMethod.get, "https://www.v2ex.com");
|
||||
httpRequest.headers.set('user-agent', 'Dart/3.0 (dart:io)');
|
||||
|
||||
const code = """
|
||||
function httpRequest(request) {
|
||||
console.log(request);
|
||||
request.headers['heelo']='world';
|
||||
request.url = 'https://www.baidu.com';
|
||||
return null;
|
||||
}
|
||||
""";
|
||||
var jsRequest = jsonEncode(convertJsRequest(httpRequest));
|
||||
|
||||
var evaluate = flutterJs.evaluate("""$code\n httpRequest($jsRequest);""");
|
||||
print(flutterJs.convertValue(evaluate));
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
void main() {
|
||||
var iso8601string = DateTime.now().toUtc().toIso8601String();
|
||||
var parse = DateTime.parse(iso8601string).toLocal();
|
||||
print(DateTime.now().toIso8601String());
|
||||
print(parse.hour);
|
||||
print(DateTime.parse(iso8601string));
|
||||
print(DateTime.now().toUtc().toIso8601String());
|
||||
print(Platform.version);
|
||||
print(Platform.localHostname);
|
||||
print(Platform.operatingSystem);
|
||||
print(Platform.localeName);
|
||||
print(Platform.script);
|
||||
print(Platform.environment);
|
||||
print(Platform.packageConfig);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <desktop_multi_window/desktop_multi_window_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_js/flutter_js_plugin.h>
|
||||
#include <proxy_manager/proxy_manager_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
@@ -19,6 +20,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("DesktopMultiWindowPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterJsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterJsPlugin"));
|
||||
ProxyManagerPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ProxyManagerPlugin"));
|
||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_multi_window
|
||||
file_selector_windows
|
||||
flutter_js
|
||||
proxy_manager
|
||||
screen_retriever
|
||||
share_plus
|
||||
|
||||
Reference in New Issue
Block a user