block-proxy 0.1.12 → 0.1.13
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 +26 -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.68f66be0.js} +3 -3
- package/build/static/js/main.68f66be0.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 +28 -3
- 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 +10 -4
- package/proxy/proxy.js +19 -15
- package/server/express.js +17 -1
- package/src/App.css +596 -276
- package/src/App.js +25 -22
- 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.68f66be0.js.LICENSE.txt} +0 -0
package/src/App.js
CHANGED
|
@@ -78,7 +78,7 @@ function App() {
|
|
|
78
78
|
} else if (data.ips.length >= 1) {
|
|
79
79
|
localIp = data.ips[0].address;
|
|
80
80
|
}
|
|
81
|
-
QRCode.toCanvas(document.getElementById('qrcode'), `http://${localIp}
|
|
81
|
+
QRCode.toCanvas(document.getElementById('qrcode'), `http://${localIp}:8003/fetchCrtFile`);
|
|
82
82
|
} else {
|
|
83
83
|
showToast('获取服务器IP失败', 'error');
|
|
84
84
|
}
|
|
@@ -624,7 +624,17 @@ function App() {
|
|
|
624
624
|
value={config.vpn_proxy}
|
|
625
625
|
onChange={(e) => setConfig({...config, vpn_proxy: e.target.value || ""})}
|
|
626
626
|
/>
|
|
627
|
-
<div className="help-text"
|
|
627
|
+
<div className="help-text">(格式:"127.0.0.1:1087",仅调试用)</div>
|
|
628
|
+
</div>
|
|
629
|
+
<div className="setting-row">
|
|
630
|
+
<label>HTTPS MITM 解密:</label>
|
|
631
|
+
<select
|
|
632
|
+
value={config.enable_mitm || "1"}
|
|
633
|
+
onChange={(e) => setConfig({...config, enable_mitm: e.target.value})}
|
|
634
|
+
>
|
|
635
|
+
<option value="1">开启(需安装证书,支持 URL 路径过滤 + 广告重写)</option>
|
|
636
|
+
<option value="0">关闭(纯隧道转发,不拦截,零证书错误)</option>
|
|
637
|
+
</select>
|
|
628
638
|
</div>
|
|
629
639
|
<div className="setting-row actions">
|
|
630
640
|
<button
|
|
@@ -635,11 +645,10 @@ function App() {
|
|
|
635
645
|
{loading ? '保存中...' : '保存配置'}
|
|
636
646
|
</button>
|
|
637
647
|
|
|
638
|
-
<button
|
|
639
|
-
onClick={handleUpdateDevices}
|
|
648
|
+
<button
|
|
649
|
+
onClick={handleUpdateDevices}
|
|
640
650
|
disabled={loading}
|
|
641
|
-
className="
|
|
642
|
-
style={{ backgroundColor: '#17a2b8', color: 'white' }}
|
|
651
|
+
className="refresh-btn"
|
|
643
652
|
>
|
|
644
653
|
{loading ? '刷新中...' : '刷新路由表'}
|
|
645
654
|
</button>
|
|
@@ -680,6 +689,12 @@ function App() {
|
|
|
680
689
|
config.enable_socks5 === "1" ? "开启" : "关闭"
|
|
681
690
|
}</span>
|
|
682
691
|
</p>
|
|
692
|
+
<p>
|
|
693
|
+
<b>HTTPS MITM 解密:</b>
|
|
694
|
+
<span>{
|
|
695
|
+
(config.enable_mitm || "1") === "1" ? "开启(需安装证书,支持 URL 路径过滤 + 广告重写)" : "关闭(纯隧道转发,不拦截)"
|
|
696
|
+
}</span>
|
|
697
|
+
</p>
|
|
683
698
|
<p>
|
|
684
699
|
<b>用户名</b>:{config.auth_username === "" ? (
|
|
685
700
|
<span>无</span>
|
|
@@ -694,22 +709,10 @@ function App() {
|
|
|
694
709
|
config.auth_password
|
|
695
710
|
)}
|
|
696
711
|
</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
712
|
<p>
|
|
710
713
|
<span>
|
|
711
714
|
扫码安装证书:
|
|
712
|
-
<a href={`http://${getIpAddress()}
|
|
715
|
+
<a href={`http://${getIpAddress()}:8003/fetchCrtFile`} target="_blank">下载</a>
|
|
713
716
|
</span><br />
|
|
714
717
|
<canvas id="qrcode"></canvas>
|
|
715
718
|
</p>
|
|
@@ -750,10 +753,10 @@ function App() {
|
|
|
750
753
|
) : (
|
|
751
754
|
<p>暂无设备信息</p>
|
|
752
755
|
)}
|
|
753
|
-
<button
|
|
754
|
-
onClick={fetchConfig}
|
|
756
|
+
<button
|
|
757
|
+
onClick={fetchConfig}
|
|
755
758
|
disabled={loading}
|
|
756
|
-
|
|
759
|
+
className="refresh-table-btn"
|
|
757
760
|
>
|
|
758
761
|
{loading ? '刷新中...' : '更新路由表'}
|
|
759
762
|
</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;
|