mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-03-15 04:23:17 +08:00
ios IP Layer Proxy, Support capturing Flutter applications (#215)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
## 开源免费抓包工具,支持Windows、Mac、Android、IOS、Linux 全平台系统
|
||||
|
||||
您可以使用它来拦截、检查和重写HTTP(S)流量,ProxyPin基于Flutter开发,UI美观易用。
|
||||
您可以使用它来拦截、检查和重写HTTP(S)流量,支持Flutter应用抓包,ProxyPin基于Flutter开发,UI美观易用。
|
||||
|
||||
## 核心特性
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
|
||||
国内下载地址: https://gitee.com/wanghongenpin/network-proxy-flutter/releases
|
||||
|
||||
AppStore下载地址: https://apps.apple.com/app/proxypin/id6450932949
|
||||
|
||||
iOS国内TF下载地址(有1万名额限制): https://testflight.apple.com/join/gURGH6B4
|
||||
iOS AppStore下载地址: https://apps.apple.com/app/proxypin/id6450932949
|
||||
|
||||
TG: https://t.me/proxypin_tg
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.md)
|
||||
## Open source free packet capture tool,Support Windows、Mac、Android、IOS、Linux Full platform system
|
||||
You can use it to intercept, inspect & rewrite HTTP(S) traffic, ProxyPin is based on Flutter develop, and the UI is beautiful
|
||||
You can use it to intercept, inspect & rewrite HTTP(S) traffic, Support capturing Flutter app traffic, ProxyPin is based on Flutter develop, and the UI is beautiful
|
||||
and easy to use.
|
||||
## Features
|
||||
* Mobile scan code connection: no need to manually configure WiFi proxy, including configuration synchronization. All terminals can scan codes to connect and forward traffic to each other.
|
||||
|
||||
@@ -24,7 +24,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
let proxyPort = conf["proxyPort"] as! Int
|
||||
let ipProxy = conf["ipProxy"] as! Bool? ?? false
|
||||
|
||||
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
|
||||
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: host)
|
||||
// let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: host)
|
||||
NSLog(conf.debugDescription)
|
||||
//http代理
|
||||
@@ -39,7 +39,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
networkSettings.mtu = 1480
|
||||
|
||||
let ipv4Settings = NEIPv4Settings(addresses: ["10.0.0.2"], subnetMasks: ["255.255.255.255"])
|
||||
|
||||
|
||||
if (ipProxy){
|
||||
ipv4Settings.includedRoutes = [NEIPv4Route.default()]
|
||||
// ipv4Settings.excludedRoutes = [
|
||||
|
||||
@@ -37,7 +37,7 @@ class Connection{
|
||||
self.connectionCloser = connectionCloser
|
||||
}
|
||||
|
||||
//发送缓冲区,用于存储要从vpn客户端发送到目标主机的数据
|
||||
//发送缓冲区,用于存储要从vpn客户端发送到目标主机的数据
|
||||
var sendBuffer = Data()
|
||||
|
||||
var hasReceivedLastSegment = false
|
||||
@@ -73,30 +73,35 @@ class Connection{
|
||||
}
|
||||
|
||||
func addSendData(data: Data) {
|
||||
QueueFactory.instance.getQueue().sync {
|
||||
// sendBuffer.append(data)
|
||||
// if (channel?.state != .ready) {
|
||||
// return
|
||||
// }
|
||||
self.sendToDestination(data: data)
|
||||
|
||||
QueueFactory.instance.getQueue().async(flags: .barrier) {
|
||||
self.sendBuffer.append(data)
|
||||
|
||||
if (self.nwProtocol == .TCP && self.channel?.state != .ready) {
|
||||
return
|
||||
}
|
||||
self.sendToDestination()
|
||||
}
|
||||
}
|
||||
|
||||
//发送到目标服务器的数据
|
||||
func sendToDestination(data: Data) {
|
||||
// QueueFactory.instance.getQueue().sync {
|
||||
// os_log("Sending data to destination key %{public}@", log: OSLog.default, type: .debug, self.description)
|
||||
//发送数据, 数据包大拆分
|
||||
|
||||
|
||||
self.channel?.send(content: data, completion: .contentProcessed { error in
|
||||
func sendToDestination() {
|
||||
QueueFactory.instance.getQueue().async(flags: .barrier) {
|
||||
os_log("Sending data to destination key %{public}@", log: OSLog.default, type: .debug, self.description)
|
||||
if (self.sendBuffer.count == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let data = self.sendBuffer
|
||||
self.sendBuffer.removeAll()
|
||||
|
||||
self.channel?.send(content: data, completion: .contentProcessed({ error in
|
||||
if let error = error {
|
||||
os_log("Error sending data to destination %{public}@: %{public}@", log: OSLog.default, type: .error, self.description, error.localizedDescription)
|
||||
return
|
||||
os_log("Failed to send data to destination key %{public}@ error: %{public}@", log: OSLog.default, type: .error, self.description, error.localizedDescription)
|
||||
self.closeConnection()
|
||||
}
|
||||
})
|
||||
|
||||
// }
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
|
||||
@@ -127,7 +127,7 @@ class ConnectionHandler {
|
||||
// os_log("Handling TCP packet for %{public}@ flags:%d", log: OSLog.default, type: .default, Connection.getConnectionKey(nwProtocol: .TCP, destIp: destinationIP, destPort: destinationPort, sourceIp: sourceIP, sourcePort: sourcePort), tcpHeader.flags)
|
||||
|
||||
if (tcpHeader.isSYN()) {
|
||||
os_log("Received SYN packet %{public}@ seq:%u", log: OSLog.default, type: .default, Connection.getConnectionKey(nwProtocol: .TCP, destIp: destinationIP, destPort: destinationPort, sourceIp: sourceIP, sourcePort: sourcePort), tcpHeader.sequenceNumber)
|
||||
// os_log("Received SYN packet %{public}@ seq:%u", log: OSLog.default, type: .default, Connection.getConnectionKey(nwProtocol: .TCP, destIp: destinationIP, destPort: destinationPort, sourceIp: sourceIP, sourcePort: sourcePort), tcpHeader.sequenceNumber)
|
||||
// 3-way handshake + create new session
|
||||
replySynAck(ipHeader: ipHeader, tcpHeader: tcpHeader)
|
||||
} else if (tcpHeader.isACK()) {
|
||||
|
||||
@@ -18,9 +18,7 @@ class ConnectionManager : CloseableConnection{
|
||||
|
||||
public var proxyAddress: NWEndpoint?
|
||||
|
||||
private let defaultPorts: [UInt16] = [80, 443]
|
||||
|
||||
// private init() {}
|
||||
private let defaultPorts: [UInt16] = [80, 8080, 8888, 443]
|
||||
|
||||
|
||||
func getConnection(nwProtocol: NWProtocol, ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) -> Connection? {
|
||||
|
||||
@@ -10,7 +10,7 @@ import NetworkExtension
|
||||
import os.log
|
||||
|
||||
class SocketIOService {
|
||||
//private static let maxReceiveBufferSize = 16384
|
||||
// private static let maxReceiveBufferSize = 16384
|
||||
private static let maxReceiveBufferSize = 1024
|
||||
|
||||
private let queue: DispatchQueue = DispatchQueue(label: "ProxyPin.SocketIOService", attributes: .concurrent)
|
||||
@@ -40,7 +40,7 @@ class SocketIOService {
|
||||
connection.isConnected = true
|
||||
os_log("Connected to %{public}@ on receiveMessage", log: OSLog.default, type: .default, connection.description)
|
||||
//接受远程服务器的数据
|
||||
// connection.sendToDestination()
|
||||
connection.sendToDestination()
|
||||
self.receiveMessage(connection: connection)
|
||||
case .cancelled:
|
||||
connection.isConnected = false
|
||||
@@ -90,8 +90,8 @@ class SocketIOService {
|
||||
return
|
||||
}
|
||||
|
||||
channel.receive(minimumIncompleteLength: 1, maximumLength: Self.maxReceiveBufferSize) { (data, context, isComplete, error) in
|
||||
// os_log("Received TCP data packet %{public}@ length %d", log: OSLog.default, type: .default, connection.description, data?.count ?? 0)
|
||||
channel.receive(minimumIncompleteLength: 0, maximumLength: Self.maxReceiveBufferSize) { (data, context, isComplete, error) in
|
||||
// os_log("Received TCP data packet %{public}@ length %d", log: OSLog.default, type: .default, connection.description, data?.count ?? 0)
|
||||
if let error = error {
|
||||
os_log("Failed to read from TCP socket: %@", log: OSLog.default, type: .error, error as CVarArg)
|
||||
self.sendFin(connection: connection)
|
||||
|
||||
@@ -20,8 +20,8 @@ import NetworkExtension
|
||||
result(Bool(VpnManager.shared.isRunning()))
|
||||
} else if ("restartVpn" == call.method){
|
||||
let arguments = call.arguments as? Dictionary<String, AnyObject>
|
||||
// VpnManager.shared.disconnect()
|
||||
VpnManager.shared.connect(host: arguments?["proxyHost"] as? String ,port: arguments?["proxyPort"] as? Int, ipProxy: arguments?["ipProxy"] as? Bool)
|
||||
// VpnManager.shared.disconnect()
|
||||
VpnManager.shared.restartConnect(host: arguments?["proxyHost"] as? String ,port: arguments?["proxyPort"] as? Int, ipProxy: arguments?["ipProxy"] as? Bool)
|
||||
} else {
|
||||
let arguments = call.arguments as? Dictionary<String, AnyObject>
|
||||
VpnManager.shared.connect(host: arguments?["proxyHost"] as? String ,port: arguments?["proxyPort"] as? Int, ipProxy: arguments?["ipProxy"] as? Bool)
|
||||
|
||||
@@ -156,6 +156,19 @@ extension VpnManager{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func restartConnect(host: String?, port: Int?, ipProxy: Bool? = false) {
|
||||
self.proxyHost = host ?? self.proxyHost
|
||||
self.proxyPort = port ?? self.proxyPort
|
||||
self.ipProxy = ipProxy ?? false
|
||||
|
||||
if (activeVPN != nil) {
|
||||
activeVPN?.connection.stopVPNTunnel()
|
||||
activeVPN = nil
|
||||
}
|
||||
|
||||
self.connect(host: host, port: port, ipProxy: ipProxy)
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
if (activeVPN != nil) {
|
||||
|
||||
@@ -237,6 +237,7 @@
|
||||
"notConnected": "Not connected",
|
||||
"disconnect": "Disconnect",
|
||||
"ipLayerProxy": "IP Layer Proxy(Beta)",
|
||||
"ipLayerProxyDesc": "IP layer proxy can capture Flutter app requests, currently not very stable",
|
||||
"inputAddress": "Input Address",
|
||||
"syncConfig": "Sync configuration",
|
||||
"pullConfigFail": "Failed to pull configuration, please check the network connection",
|
||||
|
||||
@@ -238,6 +238,7 @@
|
||||
"inputAddress": "输入地址",
|
||||
"disconnect": "断开连接",
|
||||
"ipLayerProxy": "IP层代理(Beta)",
|
||||
"ipLayerProxyDesc": "IP层代理可抓取Flutter应用请求,目前不是很稳定",
|
||||
"syncConfig": "同步配置",
|
||||
"pullConfigFail": "拉取配置失败, 请检查网络连接",
|
||||
"sync": "同步",
|
||||
|
||||
@@ -17,95 +17,111 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
///类似于netty ByteBuf
|
||||
|
||||
class ByteBuf {
|
||||
final BytesBuilder _buffer = BytesBuilder();
|
||||
late Uint8List _buffer;
|
||||
int readerIndex = 0;
|
||||
|
||||
Uint8List get bytes => _buffer.toBytes();
|
||||
|
||||
int get length => _buffer.length;
|
||||
int writerIndex = 0;
|
||||
|
||||
ByteBuf([List<int>? bytes]) {
|
||||
if (bytes != null) _buffer.add(bytes);
|
||||
if (bytes != null) {
|
||||
_buffer = Uint8List.fromList(bytes);
|
||||
writerIndex = bytes.length;
|
||||
} else {
|
||||
_buffer = Uint8List(256); // Initial buffer size
|
||||
}
|
||||
}
|
||||
|
||||
///添加
|
||||
int get length => writerIndex;
|
||||
|
||||
Uint8List get bytes => Uint8List.sublistView(_buffer, 0, writerIndex);
|
||||
|
||||
void add(List<int> bytes) {
|
||||
_buffer.add(bytes);
|
||||
_ensureCapacity(writerIndex + bytes.length);
|
||||
_buffer.setRange(writerIndex, writerIndex + bytes.length, bytes);
|
||||
writerIndex += bytes.length;
|
||||
}
|
||||
|
||||
///清空
|
||||
clear() {
|
||||
_buffer.clear();
|
||||
void clear() {
|
||||
readerIndex = 0;
|
||||
writerIndex = 0;
|
||||
}
|
||||
|
||||
///释放
|
||||
clearRead() {
|
||||
var takeBytes = _buffer.takeBytes();
|
||||
_buffer.add(Uint8List.sublistView(takeBytes, readerIndex, takeBytes.length));
|
||||
readerIndex = 0;
|
||||
void clearRead() {
|
||||
if (readerIndex > 0) {
|
||||
_buffer.setRange(0, writerIndex - readerIndex, _buffer, readerIndex);
|
||||
writerIndex -= readerIndex;
|
||||
readerIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool isReadable() => readerIndex < _buffer.length;
|
||||
bool isReadable() => readerIndex < writerIndex;
|
||||
|
||||
///可读字节数
|
||||
int readableBytes() {
|
||||
return _buffer.length - readerIndex;
|
||||
}
|
||||
int readableBytes() => writerIndex - readerIndex;
|
||||
|
||||
///读取所有可用字节
|
||||
Uint8List readAvailableBytes() {
|
||||
return readBytes(readableBytes());
|
||||
}
|
||||
Uint8List readAvailableBytes() => readBytes(readableBytes());
|
||||
|
||||
///读取字节
|
||||
Uint8List readBytes(int length) {
|
||||
Uint8List list = bytes.sublist(readerIndex, readerIndex + length);
|
||||
if (readerIndex + length > writerIndex) {
|
||||
throw Exception("Not enough readable bytes");
|
||||
}
|
||||
Uint8List result = Uint8List.sublistView(_buffer, readerIndex, readerIndex + length);
|
||||
readerIndex += length;
|
||||
return list;
|
||||
return result;
|
||||
}
|
||||
|
||||
///跳过
|
||||
skipBytes(int length) {
|
||||
void skipBytes(int length) {
|
||||
if (readerIndex + length > writerIndex) {
|
||||
throw Exception("Not enough readable bytes");
|
||||
}
|
||||
readerIndex += length;
|
||||
}
|
||||
|
||||
///读取字节
|
||||
int read() {
|
||||
return bytes[readerIndex++];
|
||||
}
|
||||
int read() => _buffer[readerIndex++];
|
||||
|
||||
///读取字节
|
||||
int readByte() {
|
||||
return bytes[readerIndex++];
|
||||
}
|
||||
int readByte() => _buffer[readerIndex++];
|
||||
|
||||
int readShort() {
|
||||
int value = bytes[readerIndex++] << 8 | bytes[readerIndex++];
|
||||
int value = (_buffer[readerIndex] << 8) | _buffer[readerIndex + 1];
|
||||
readerIndex += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
int readInt() {
|
||||
int value =
|
||||
bytes[readerIndex++] << 24 | bytes[readerIndex++] << 16 | bytes[readerIndex++] << 8 | bytes[readerIndex++];
|
||||
int value = (_buffer[readerIndex] << 24) |
|
||||
(_buffer[readerIndex + 1] << 16) |
|
||||
(_buffer[readerIndex + 2] << 8) |
|
||||
_buffer[readerIndex + 3];
|
||||
readerIndex += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
int get(int index) {
|
||||
return bytes[index];
|
||||
}
|
||||
int get(int index) => _buffer[index];
|
||||
|
||||
void truncate(int len) {
|
||||
if (len > readableBytes()) throw Exception("insufficient data");
|
||||
var takeBytes = _buffer.takeBytes();
|
||||
_buffer.add(takeBytes.sublist(0, readerIndex + len));
|
||||
if (len > readableBytes()) {
|
||||
throw Exception("Insufficient data");
|
||||
}
|
||||
writerIndex = readerIndex + len;
|
||||
}
|
||||
|
||||
ByteBuf dup() {
|
||||
ByteBuf buf = ByteBuf();
|
||||
buf.add(bytes);
|
||||
buf._buffer = Uint8List.fromList(_buffer);
|
||||
buf.readerIndex = readerIndex;
|
||||
buf.writerIndex = writerIndex;
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
void _ensureCapacity(int required) {
|
||||
if (_buffer.length < required) {
|
||||
int newSize = _buffer.length * 2;
|
||||
while (newSize < required) {
|
||||
newSize *= 2;
|
||||
}
|
||||
Uint8List newBuffer = Uint8List(newSize);
|
||||
newBuffer.setRange(0, writerIndex, _buffer);
|
||||
_buffer = newBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ class AppConfiguration {
|
||||
Locale? _language;
|
||||
|
||||
//是否显示更新内容公告
|
||||
bool upgradeNoticeV13 = true;
|
||||
bool upgradeNoticeV14 = true;
|
||||
|
||||
/// 是否启用画中画
|
||||
ValueNotifier<bool> pipEnabled = ValueNotifier(true);
|
||||
@@ -174,7 +174,7 @@ class AppConfiguration {
|
||||
_theme = ThemeModel(mode: mode, useMaterial3: config['useMaterial3'] ?? true);
|
||||
_theme.color = config['themeColor'] ?? "Blue";
|
||||
|
||||
upgradeNoticeV13 = config['upgradeNoticeV13'] ?? true;
|
||||
upgradeNoticeV14 = config['upgradeNoticeV14'] ?? true;
|
||||
_language = config['language'] == null ? null : Locale.fromSubtags(languageCode: config['language']);
|
||||
pipEnabled.value = config['pipEnabled'] ?? true;
|
||||
pipIcon.value = config['pipIcon'] ?? false;
|
||||
@@ -218,7 +218,7 @@ class AppConfiguration {
|
||||
'mode': _theme.mode.name,
|
||||
'themeColor': _theme.color,
|
||||
'useMaterial3': _theme.useMaterial3,
|
||||
'upgradeNoticeV13': upgradeNoticeV13,
|
||||
'upgradeNoticeV14': upgradeNoticeV14,
|
||||
"language": _language?.languageCode,
|
||||
"headerExpanded": headerExpanded,
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
|
||||
proxyServer.addListener(this);
|
||||
panel = NetworkTabController(tabStyle: const TextStyle(fontSize: 16), proxyServer: proxyServer);
|
||||
|
||||
if (widget.appConfiguration.upgradeNoticeV13) {
|
||||
if (widget.appConfiguration.upgradeNoticeV14) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showUpgradeNotice();
|
||||
});
|
||||
@@ -136,7 +136,7 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
widget.appConfiguration.upgradeNoticeV13 = false;
|
||||
widget.appConfiguration.upgradeNoticeV14 = false;
|
||||
widget.appConfiguration.flushConfig();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
@@ -149,10 +149,10 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
|
||||
isCN
|
||||
? '提示:默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n'
|
||||
'点击HTTPS抓包(加锁图标),选择安装根证书,按照提示操作即可。\n\n'
|
||||
'1. 支持多种主题颜色选择;\n'
|
||||
'2. 外部代理支持身份验证;\n'
|
||||
'3. 双击列表tab滚动到顶部;\n'
|
||||
'4. 修复部分p12证书导入失败的问题;\n'
|
||||
'1. 手机端增加底部导航,可在设置中切换;\n'
|
||||
'2. 增加远程设备管理,可快速连接设备;\n'
|
||||
'3. iOS支持抓取Flutter应用,需要通过设备管理连接到电脑开启IP层代理(Beta);\n'
|
||||
'4. 工具箱支持Unicode编码;\n'
|
||||
'5. 修复Transfer-Encoding有空格解析错误问题;\n'
|
||||
'6. 脚本增加rawBody原始字节参数, body支持字节数组修改;\n'
|
||||
'7. 修复脚本消息体编码错误导致错误响应;\n'
|
||||
@@ -160,10 +160,10 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
|
||||
'9. 修复Websocket Response不展示;\n'
|
||||
: 'Tips:By default, HTTPS packet capture will not be enabled. Please install the certificate before enabling HTTPS packet capture。\n'
|
||||
'Click HTTPS Capture packets(Lock icon),Choose to install the root certificate and follow the prompts to proceed。\n\n'
|
||||
'1. Support multiple theme colors;\n'
|
||||
'2. External proxy support authentication;\n'
|
||||
'3. Double-click the list tab to scroll to the top;\n'
|
||||
'4. Fix the issue of partial p12 certificate import failure;\n'
|
||||
'1. Mobile: Add bottom navigation bar,which can be switched in settings;\n'
|
||||
'2. Support remote device management to quickly connect to devices;\n'
|
||||
'3. IOS supports capturing Flutter applications, You need to connect to the computer through device management to enable IP layer proxy (Beta);\n'
|
||||
'4. Toolbox supports Unicode encode;\n'
|
||||
'5. Fix header Transfer-Encoding with spaces;\n'
|
||||
'6. The script add rawBody raw byte parameter, body supports byte array modification;\n'
|
||||
'7. Fix script message body encoding error causing incorrect response;\n'
|
||||
|
||||
@@ -109,7 +109,7 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener, Li
|
||||
proxyServer.addListener(this);
|
||||
proxyServer.start();
|
||||
|
||||
if (widget.appConfiguration.upgradeNoticeV13) {
|
||||
if (widget.appConfiguration.upgradeNoticeV14) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showUpgradeNotice();
|
||||
});
|
||||
@@ -250,8 +250,8 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener, Li
|
||||
? '提示:默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n\n'
|
||||
'1. 手机端增加底部导航,可在设置中切换;\n'
|
||||
'2. 增加远程设备管理,可快速连接设备;\n'
|
||||
'3. 双击列表tab滚动到顶部;\n'
|
||||
'4. 修复部分p12证书导入失败的问题;\n'
|
||||
'3. iOS支持抓取Flutter应用,需要通过设备管理连接到电脑开启IP层代理(Beta);\n'
|
||||
'4. 工具箱支持Unicode编码;\n'
|
||||
'5. 脚本增加rawBody原始字节参数, body支持字节数组修改;\n'
|
||||
'6. 修复脚本消息体编码错误导致错误响应;\n'
|
||||
'7. 修复扫码链接多个IP优先级问题;\n'
|
||||
@@ -262,8 +262,8 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener, Li
|
||||
'Click HTTPS Capture packets(Lock icon),Choose to install the root certificate and follow the prompts to proceed。\n\n'
|
||||
'1. Mobile: Add bottom navigation bar,which can be switched in settings;\n'
|
||||
'2. Support remote device management to quickly connect to devices;\n'
|
||||
'3. Double-click the list tab to scroll to the top;\n'
|
||||
'4. Fix the issue of partial p12 certificate import failure;\n'
|
||||
'3. IOS supports capturing Flutter applications, You need to connect to the computer through device management to enable IP layer proxy (Beta);\n'
|
||||
'4. Toolbox supports Unicode encode;\n'
|
||||
'5. The script add rawBody raw byte parameter, body supports byte array modification;\n'
|
||||
'6. Fix script message body encoding error causing incorrect response;\n'
|
||||
'7. Fix the issue of scanning QR code to connect to multiple IP priorities;\n'
|
||||
@@ -271,8 +271,8 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener, Li
|
||||
'9. Fix export HAR serverIPAddress incorrect;\n'
|
||||
'10. Fix Websocket Response not displayed;\n'
|
||||
'';
|
||||
showAlertDialog(isCN ? '更新内容V1.1.3' : "Update content V1.1.3", content, () {
|
||||
widget.appConfiguration.upgradeNoticeV13 = false;
|
||||
showAlertDialog(isCN ? '更新内容V1.1.4' : "Update content V1.1.4", content, () {
|
||||
widget.appConfiguration.upgradeNoticeV14 = false;
|
||||
widget.appConfiguration.flushConfig();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,13 +60,7 @@ class RemoteModel {
|
||||
|
||||
factory RemoteModel.fromJson(Map<String, dynamic> json) {
|
||||
return RemoteModel(
|
||||
connect: json['connect'],
|
||||
host: json['host'],
|
||||
port: json['port'],
|
||||
os: json['os'],
|
||||
hostname: json['hostname'],
|
||||
ipProxy: json['ipProxy'],
|
||||
);
|
||||
connect: json['connect'], host: json['host'], port: json['port'], os: json['os'], hostname: json['hostname']);
|
||||
}
|
||||
|
||||
RemoteModel copyWith({
|
||||
@@ -95,14 +89,7 @@ class RemoteModel {
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'connect': connect,
|
||||
'host': host,
|
||||
'port': port,
|
||||
'os': os,
|
||||
'hostname': hostname,
|
||||
'ipProxy': ipProxy,
|
||||
};
|
||||
return {'connect': connect, 'host': host, 'port': port, 'os': os, 'hostname': hostname};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,32 +230,38 @@ class _RemoteDevicePageState extends State<RemoteDevicePage> {
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Icons.check_circle_outline_outlined, size: 55, color: Colors.green),
|
||||
Row(
|
||||
children: [
|
||||
if (Platform.isIOS) Expanded(child: ListTile(title: Text(localizations.ipLayerProxy))),
|
||||
const SizedBox(height: 6),
|
||||
SwitchWidget(
|
||||
value: widget.remoteDevice.value.ipProxy ?? false,
|
||||
scale: 0.85,
|
||||
onChanged: (val) async {
|
||||
widget.remoteDevice.value = widget.remoteDevice.value.copyWith(ipProxy: val);
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
var remoteDeviceList = getRemoteDeviceList(prefs);
|
||||
remoteDeviceList.removeWhere((it) => it.equals(widget.remoteDevice.value));
|
||||
remoteDeviceList.insert(0, widget.remoteDevice.value);
|
||||
const SizedBox(height: 6),
|
||||
if (Platform.isIOS)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
title: Text(localizations.ipLayerProxy), subtitle: Text(localizations.ipLayerProxyDesc))),
|
||||
SwitchWidget(
|
||||
value: widget.remoteDevice.value.ipProxy ?? false,
|
||||
scale: 0.85,
|
||||
onChanged: (val) async {
|
||||
widget.remoteDevice.value = widget.remoteDevice.value.copyWith(ipProxy: val);
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
var remoteDeviceList = getRemoteDeviceList(prefs);
|
||||
remoteDeviceList.removeWhere((it) => it.equals(widget.remoteDevice.value));
|
||||
remoteDeviceList.insert(0, widget.remoteDevice.value);
|
||||
|
||||
setRemoteDeviceList(prefs, remoteDeviceList);
|
||||
});
|
||||
setRemoteDeviceList(prefs, remoteDeviceList);
|
||||
});
|
||||
|
||||
if ((await Vpn.isRunning())) {
|
||||
print('重启VPN');
|
||||
Vpn.restartVpn(widget.remoteDevice.value.host!, widget.remoteDevice.value.port!,
|
||||
widget.proxyServer.configuration,
|
||||
ipProxy: val);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
if ((await Vpn.isRunning())) {
|
||||
Vpn.stopVpn();
|
||||
Future.delayed(const Duration(milliseconds: 1500), () {
|
||||
Vpn.startVpn(widget.remoteDevice.value.host!, widget.remoteDevice.value.port!,
|
||||
widget.proxyServer.configuration,
|
||||
ipProxy: val);
|
||||
});
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text('${localizations.connected}:${widget.remoteDevice.value.hostname}',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
const SizedBox(height: 6),
|
||||
@@ -524,7 +517,7 @@ class ConfigSyncState extends State<ConfigSyncWidget> {
|
||||
title: Text(localizations.syncConfig, style: const TextStyle(fontSize: 16)),
|
||||
content: Wrap(children: [
|
||||
SwitchWidget(
|
||||
title: "${localizations.sync}${localizations.domainWhitelist}",
|
||||
title: "${localizations.sync} ${localizations.domainWhitelist}",
|
||||
value: syncWhiteList,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
@@ -533,7 +526,7 @@ class ConfigSyncState extends State<ConfigSyncWidget> {
|
||||
}),
|
||||
const SizedBox(height: 5),
|
||||
SwitchWidget(
|
||||
title: "${localizations.sync}${localizations.domainBlacklist}",
|
||||
title: "${localizations.sync} ${localizations.domainBlacklist}",
|
||||
value: syncBlackList,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
@@ -542,7 +535,7 @@ class ConfigSyncState extends State<ConfigSyncWidget> {
|
||||
}),
|
||||
const SizedBox(height: 5),
|
||||
SwitchWidget(
|
||||
title: "${localizations.sync}${localizations.requestRewrite}",
|
||||
title: "${localizations.sync} ${localizations.requestRewrite}",
|
||||
value: syncRewrite,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
@@ -551,7 +544,7 @@ class ConfigSyncState extends State<ConfigSyncWidget> {
|
||||
}),
|
||||
const SizedBox(height: 5),
|
||||
SwitchWidget(
|
||||
title: "${localizations.sync}${localizations.script}",
|
||||
title: "${localizations.sync} ${localizations.script}",
|
||||
value: syncScript,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
@@ -566,7 +559,7 @@ class ConfigSyncState extends State<ConfigSyncWidget> {
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
TextButton(
|
||||
child: Text('${localizations.start}${localizations.sync}'),
|
||||
child: Text('${localizations.start} ${localizations.sync}'),
|
||||
onPressed: () async {
|
||||
if (syncWhiteList) {
|
||||
HostFilter.whitelist.load(widget.config['whitelist']);
|
||||
|
||||
Reference in New Issue
Block a user