block-proxy 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +33 -1
- package/.claude/skills/build-client/skill.md +24 -0
- package/.claude/skills/release-client/skill.md +68 -0
- package/CLAUDE.md +69 -67
- package/Dockerfile +1 -1
- package/README.md +38 -24
- package/build/asset-manifest.json +6 -6
- package/build/index.html +1 -1
- package/build/static/css/main.3f317ce6.css +2 -0
- package/build/static/css/main.3f317ce6.css.map +1 -0
- package/build/static/js/{main.2247fb80.js → main.af1923ea.js} +3 -3
- package/build/static/js/main.af1923ea.js.map +1 -0
- package/client/app.py +312 -0
- package/client/build.sh +84 -0
- package/client/config.py +49 -0
- package/client/config_window.py +155 -0
- package/client/icons/app.icns +0 -0
- package/client/icons/app_example.png +0 -0
- package/client/icons/app_icon.png +0 -0
- package/client/icons/backup/app_example.png +0 -0
- package/client/icons/backup/christmas-sock_dark.png +0 -0
- package/client/icons/backup/christmas-sock_light.png +0 -0
- package/client/icons/backup/socks_on_G.png +0 -0
- package/client/icons/backup/socks_on_M.png +0 -0
- package/client/icons/christmas-sock_dark.png +0 -0
- package/client/icons/christmas-sock_light.png +0 -0
- package/client/icons/christmas-sock_light_bar.png +0 -0
- package/client/icons/socks_on_G.png +0 -0
- package/client/icons/socks_on_G_bar.png +0 -0
- package/client/icons/socks_on_M.png +0 -0
- package/client/icons/socks_on_M_bar.png +0 -0
- package/client/main.py +28 -0
- package/client/proxy_core.py +475 -0
- package/client/requirements.txt +3 -0
- package/client/scripts/download_xray.sh +30 -0
- package/client/setup.py +30 -0
- package/client/system_proxy.py +94 -0
- package/client/tests/__init__.py +0 -0
- package/client/tests/test_config.py +72 -0
- package/client/tests/test_system_proxy.py +69 -0
- package/client/watch-icons.js +31 -0
- package/config.json +4 -199
- package/docs/superpowers/plans/2026-05-27-blockproxyclient.md +1274 -0
- package/docs/superpowers/specs/2026-05-27-blockproxyclient-design.md +264 -0
- package/package.json +11 -5
- package/proxy/proxy.js +19 -34
- package/server/express.js +17 -1
- package/src/App.css +596 -276
- package/src/App.js +25 -32
- package/src/index.css +3 -4
- package/test/lib/mock-server.js +133 -0
- package/test/proxy-tests.js +708 -0
- package/test/run.js +330 -0
- package/build/static/css/main.8bfa3d5f.css +0 -2
- package/build/static/css/main.8bfa3d5f.css.map +0 -1
- package/build/static/js/main.2247fb80.js.map +0 -1
- package/hack-of-anyproxy/lib/requestHandler.js +0 -1060
- /package/build/static/js/{main.2247fb80.js.LICENSE.txt → main.af1923ea.js.LICENSE.txt} +0 -0
package/src/App.js
CHANGED
|
@@ -20,7 +20,6 @@ function App() {
|
|
|
20
20
|
block_hosts: [],
|
|
21
21
|
proxy_port: 8001,
|
|
22
22
|
socks5_port:8002,
|
|
23
|
-
web_interface_port: 8003,
|
|
24
23
|
auth_username: "",
|
|
25
24
|
auth_password: "",
|
|
26
25
|
});
|
|
@@ -78,7 +77,7 @@ function App() {
|
|
|
78
77
|
} else if (data.ips.length >= 1) {
|
|
79
78
|
localIp = data.ips[0].address;
|
|
80
79
|
}
|
|
81
|
-
QRCode.toCanvas(document.getElementById('qrcode'), `http://${localIp}
|
|
80
|
+
QRCode.toCanvas(document.getElementById('qrcode'), `http://${localIp}:8003/fetchCrtFile`);
|
|
82
81
|
} else {
|
|
83
82
|
showToast('获取服务器IP失败', 'error');
|
|
84
83
|
}
|
|
@@ -580,15 +579,6 @@ function App() {
|
|
|
580
579
|
onChange={(e) => setConfig({...config, socks5_port: parseInt(e.target.value) || 8002})}
|
|
581
580
|
/>
|
|
582
581
|
</div>
|
|
583
|
-
|
|
584
|
-
<div className="setting-row">
|
|
585
|
-
<label>Anyproxy 监控端口:</label>
|
|
586
|
-
<input
|
|
587
|
-
type="number"
|
|
588
|
-
value={config.web_interface_port}
|
|
589
|
-
onChange={(e) => setConfig({...config, web_interface_port: parseInt(e.target.value) || 8003})}
|
|
590
|
-
/>
|
|
591
|
-
</div>
|
|
592
582
|
|
|
593
583
|
<div className="setting-row">
|
|
594
584
|
<label>代理用户名:</label>
|
|
@@ -624,7 +614,17 @@ function App() {
|
|
|
624
614
|
value={config.vpn_proxy}
|
|
625
615
|
onChange={(e) => setConfig({...config, vpn_proxy: e.target.value || ""})}
|
|
626
616
|
/>
|
|
627
|
-
<div className="help-text"
|
|
617
|
+
<div className="help-text">(格式:"127.0.0.1:1087",仅调试用)</div>
|
|
618
|
+
</div>
|
|
619
|
+
<div className="setting-row">
|
|
620
|
+
<label>HTTPS MITM 解密:</label>
|
|
621
|
+
<select
|
|
622
|
+
value={config.enable_mitm || "1"}
|
|
623
|
+
onChange={(e) => setConfig({...config, enable_mitm: e.target.value})}
|
|
624
|
+
>
|
|
625
|
+
<option value="1">开启(需安装证书,支持 URL 路径过滤 + 广告重写)</option>
|
|
626
|
+
<option value="0">关闭(纯隧道转发,不拦截,零证书错误)</option>
|
|
627
|
+
</select>
|
|
628
628
|
</div>
|
|
629
629
|
<div className="setting-row actions">
|
|
630
630
|
<button
|
|
@@ -635,11 +635,10 @@ function App() {
|
|
|
635
635
|
{loading ? '保存中...' : '保存配置'}
|
|
636
636
|
</button>
|
|
637
637
|
|
|
638
|
-
<button
|
|
639
|
-
onClick={handleUpdateDevices}
|
|
638
|
+
<button
|
|
639
|
+
onClick={handleUpdateDevices}
|
|
640
640
|
disabled={loading}
|
|
641
|
-
className="
|
|
642
|
-
style={{ backgroundColor: '#17a2b8', color: 'white' }}
|
|
641
|
+
className="refresh-btn"
|
|
643
642
|
>
|
|
644
643
|
{loading ? '刷新中...' : '刷新路由表'}
|
|
645
644
|
</button>
|
|
@@ -680,6 +679,12 @@ function App() {
|
|
|
680
679
|
config.enable_socks5 === "1" ? "开启" : "关闭"
|
|
681
680
|
}</span>
|
|
682
681
|
</p>
|
|
682
|
+
<p>
|
|
683
|
+
<b>HTTPS MITM 解密:</b>
|
|
684
|
+
<span>{
|
|
685
|
+
(config.enable_mitm || "1") === "1" ? "开启(需安装证书,支持 URL 路径过滤 + 广告重写)" : "关闭(纯隧道转发,不拦截)"
|
|
686
|
+
}</span>
|
|
687
|
+
</p>
|
|
683
688
|
<p>
|
|
684
689
|
<b>用户名</b>:{config.auth_username === "" ? (
|
|
685
690
|
<span>无</span>
|
|
@@ -694,22 +699,10 @@ function App() {
|
|
|
694
699
|
config.auth_password
|
|
695
700
|
)}
|
|
696
701
|
</p>
|
|
697
|
-
<p>
|
|
698
|
-
<b>AnyProxy 监控地址:</b>
|
|
699
|
-
{serverIPs.length > 0 ? (
|
|
700
|
-
<span>
|
|
701
|
-
<a href={`http://${getIpAddress()}:${config.web_interface_port}`} target="_blank" rel="noopener noreferrer">
|
|
702
|
-
{`http://${getIpAddress()}:${config.web_interface_port}`}
|
|
703
|
-
</a>
|
|
704
|
-
</span>
|
|
705
|
-
) : (
|
|
706
|
-
<span>正在获取服务器IP地址...</span>
|
|
707
|
-
)}
|
|
708
|
-
</p>
|
|
709
702
|
<p>
|
|
710
703
|
<span>
|
|
711
704
|
扫码安装证书:
|
|
712
|
-
<a href={`http://${getIpAddress()}
|
|
705
|
+
<a href={`http://${getIpAddress()}:8003/fetchCrtFile`} target="_blank">下载</a>
|
|
713
706
|
</span><br />
|
|
714
707
|
<canvas id="qrcode"></canvas>
|
|
715
708
|
</p>
|
|
@@ -750,10 +743,10 @@ function App() {
|
|
|
750
743
|
) : (
|
|
751
744
|
<p>暂无设备信息</p>
|
|
752
745
|
)}
|
|
753
|
-
<button
|
|
754
|
-
onClick={fetchConfig}
|
|
746
|
+
<button
|
|
747
|
+
onClick={fetchConfig}
|
|
755
748
|
disabled={loading}
|
|
756
|
-
|
|
749
|
+
className="refresh-table-btn"
|
|
757
750
|
>
|
|
758
751
|
{loading ? '刷新中...' : '更新路由表'}
|
|
759
752
|
</button>
|
package/src/index.css
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
body {
|
|
2
2
|
margin: 0;
|
|
3
3
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
4
|
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
5
|
-
sans-serif;
|
|
4
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
6
5
|
-webkit-font-smoothing: antialiased;
|
|
7
6
|
-moz-osx-font-smoothing: grayscale;
|
|
7
|
+
background: #f8fafc;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
code {
|
|
11
|
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
12
|
-
monospace;
|
|
11
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
|
13
12
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
|
|
3
|
+
function getLanIp() {
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const interfaces = os.networkInterfaces();
|
|
6
|
+
for (const name of Object.keys(interfaces)) {
|
|
7
|
+
for (const iface of interfaces[name]) {
|
|
8
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
9
|
+
return iface.address;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return '127.0.0.1';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class MockServer {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.server = null;
|
|
19
|
+
this.port = 0;
|
|
20
|
+
this.host = '0.0.0.0';
|
|
21
|
+
this.lanIp = getLanIp();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get baseUrl() {
|
|
25
|
+
return `http://${this.lanIp}:${this.port}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async start() {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
this.server = http.createServer((req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
this._handle(req, res);
|
|
33
|
+
} catch (_err) {
|
|
34
|
+
if (!res.headersSent) {
|
|
35
|
+
res.writeHead(500);
|
|
36
|
+
res.end('Internal Server Error');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.server.on('error', reject);
|
|
42
|
+
this.server.listen(0, this.host, () => {
|
|
43
|
+
this.port = this.server.address().port;
|
|
44
|
+
resolve(this.port);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_handle(req, res) {
|
|
50
|
+
const url = new URL(req.url, `http://${this.host}`);
|
|
51
|
+
const path = url.pathname;
|
|
52
|
+
|
|
53
|
+
if (path === '/ping') {
|
|
54
|
+
res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': '4' });
|
|
55
|
+
res.end('pong');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (path.startsWith('/size/')) {
|
|
60
|
+
const bytes = parseInt(path.split('/')[2], 10);
|
|
61
|
+
if (isNaN(bytes) || bytes < 0 || bytes > 100 * 1024 * 1024) {
|
|
62
|
+
res.writeHead(400);
|
|
63
|
+
res.end('Invalid size (0-104857600)');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const buf = Buffer.alloc(bytes, 0x58);
|
|
67
|
+
res.writeHead(200, {
|
|
68
|
+
'Content-Type': 'application/octet-stream',
|
|
69
|
+
'Content-Length': String(bytes),
|
|
70
|
+
});
|
|
71
|
+
res.end(buf);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (path.startsWith('/delay/')) {
|
|
76
|
+
const ms = parseInt(path.split('/')[2], 10);
|
|
77
|
+
if (isNaN(ms) || ms < 0 || ms > 30000) {
|
|
78
|
+
res.writeHead(400);
|
|
79
|
+
res.end('Invalid delay (0-30000)');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
84
|
+
res.end(`delayed ${ms}ms`);
|
|
85
|
+
}, ms);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (path.startsWith('/status/')) {
|
|
90
|
+
const code = parseInt(path.split('/')[2], 10) || 200;
|
|
91
|
+
res.writeHead(code);
|
|
92
|
+
res.end(`Status: ${code}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (path === '/echo' && req.method === 'POST') {
|
|
97
|
+
const body = [];
|
|
98
|
+
req.on('data', (c) => body.push(c));
|
|
99
|
+
req.on('end', () => {
|
|
100
|
+
const buf = Buffer.concat(body);
|
|
101
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
102
|
+
res.end(JSON.stringify({
|
|
103
|
+
method: req.method,
|
|
104
|
+
url: req.url,
|
|
105
|
+
headers: req.headers,
|
|
106
|
+
bodyLength: buf.length,
|
|
107
|
+
}));
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// default: dump request info
|
|
113
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
114
|
+
res.end(JSON.stringify({
|
|
115
|
+
method: req.method,
|
|
116
|
+
url: req.url,
|
|
117
|
+
path,
|
|
118
|
+
headers: req.headers,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async stop() {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
if (this.server) {
|
|
125
|
+
this.server.close(() => resolve());
|
|
126
|
+
} else {
|
|
127
|
+
resolve();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = MockServer;
|