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.
Files changed (58) hide show
  1. package/.claude/settings.local.json +26 -1
  2. package/.claude/skills/build-client/skill.md +24 -0
  3. package/.claude/skills/release-client/skill.md +68 -0
  4. package/CLAUDE.md +69 -67
  5. package/Dockerfile +1 -1
  6. package/README.md +38 -24
  7. package/build/asset-manifest.json +6 -6
  8. package/build/index.html +1 -1
  9. package/build/static/css/main.3f317ce6.css +2 -0
  10. package/build/static/css/main.3f317ce6.css.map +1 -0
  11. package/build/static/js/{main.2247fb80.js → main.68f66be0.js} +3 -3
  12. package/build/static/js/main.68f66be0.js.map +1 -0
  13. package/client/app.py +312 -0
  14. package/client/build.sh +84 -0
  15. package/client/config.py +49 -0
  16. package/client/config_window.py +155 -0
  17. package/client/icons/app.icns +0 -0
  18. package/client/icons/app_example.png +0 -0
  19. package/client/icons/app_icon.png +0 -0
  20. package/client/icons/backup/app_example.png +0 -0
  21. package/client/icons/backup/christmas-sock_dark.png +0 -0
  22. package/client/icons/backup/christmas-sock_light.png +0 -0
  23. package/client/icons/backup/socks_on_G.png +0 -0
  24. package/client/icons/backup/socks_on_M.png +0 -0
  25. package/client/icons/christmas-sock_dark.png +0 -0
  26. package/client/icons/christmas-sock_light.png +0 -0
  27. package/client/icons/christmas-sock_light_bar.png +0 -0
  28. package/client/icons/socks_on_G.png +0 -0
  29. package/client/icons/socks_on_G_bar.png +0 -0
  30. package/client/icons/socks_on_M.png +0 -0
  31. package/client/icons/socks_on_M_bar.png +0 -0
  32. package/client/main.py +28 -0
  33. package/client/proxy_core.py +475 -0
  34. package/client/requirements.txt +3 -0
  35. package/client/scripts/download_xray.sh +30 -0
  36. package/client/setup.py +30 -0
  37. package/client/system_proxy.py +94 -0
  38. package/client/tests/__init__.py +0 -0
  39. package/client/tests/test_config.py +72 -0
  40. package/client/tests/test_system_proxy.py +69 -0
  41. package/client/watch-icons.js +31 -0
  42. package/config.json +28 -3
  43. package/docs/superpowers/plans/2026-05-27-blockproxyclient.md +1274 -0
  44. package/docs/superpowers/specs/2026-05-27-blockproxyclient-design.md +264 -0
  45. package/package.json +10 -4
  46. package/proxy/proxy.js +19 -15
  47. package/server/express.js +17 -1
  48. package/src/App.css +596 -276
  49. package/src/App.js +25 -22
  50. package/src/index.css +3 -4
  51. package/test/lib/mock-server.js +133 -0
  52. package/test/proxy-tests.js +708 -0
  53. package/test/run.js +330 -0
  54. package/build/static/css/main.8bfa3d5f.css +0 -2
  55. package/build/static/css/main.8bfa3d5f.css.map +0 -1
  56. package/build/static/js/main.2247fb80.js.map +0 -1
  57. package/hack-of-anyproxy/lib/requestHandler.js +0 -1060
  58. /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}:${config.web_interface_port}/fetchCrtFile`);
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">(格式:“127.0.0.1:1087”,仅调试用)</div>
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="restart-btn"
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()}:${config.web_interface_port}/fetchCrtFile`} target="_blank">下载</a>
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
- style={{ marginTop: '10px' }}
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;