block-proxy 0.1.11 → 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/.agents/skills/commit/skill.md +40 -0
- package/.claude/settings.local.json +29 -1
- package/.claude/skills/build-client/skill.md +24 -0
- package/.claude/skills/commit/skill.md +34 -26
- package/.claude/skills/release-client/skill.md +68 -0
- package/CLAUDE.md +109 -47
- package/Dockerfile +1 -1
- package/README.md +69 -60
- 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 +82 -5
- 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 +70 -18
- package/server/express.js +17 -1
- package/skills-lock.json +11 -0
- package/socks5/server.js +2 -2
- 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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import pytest
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
8
|
+
from config import Config, DEFAULT_CONFIG
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestConfig:
|
|
12
|
+
def setup_method(self):
|
|
13
|
+
self.tmp_dir = tempfile.mkdtemp()
|
|
14
|
+
self.config_path = os.path.join(self.tmp_dir, "config.json")
|
|
15
|
+
self.config = Config(config_path=self.config_path)
|
|
16
|
+
|
|
17
|
+
def test_default_config_created_on_first_load(self):
|
|
18
|
+
data = self.config.load()
|
|
19
|
+
assert data["server"]["port"] == 8002
|
|
20
|
+
assert data["server"]["tls"] is True
|
|
21
|
+
assert data["server"]["allowInsecure"] is True
|
|
22
|
+
assert data["local"]["socks_port"] == 1080
|
|
23
|
+
assert data["local"]["http_port"] == 1087
|
|
24
|
+
assert data["local"]["udp"] is True
|
|
25
|
+
assert data["mode"] == "global"
|
|
26
|
+
assert os.path.exists(self.config_path)
|
|
27
|
+
|
|
28
|
+
def test_save_and_load_roundtrip(self):
|
|
29
|
+
self.config.load()
|
|
30
|
+
self.config.data["server"]["address"] = "10.0.0.1"
|
|
31
|
+
self.config.data["server"]["port"] = 9002
|
|
32
|
+
self.config.data["server"]["username"] = "user1"
|
|
33
|
+
self.config.data["server"]["password"] = "pass1"
|
|
34
|
+
self.config.save()
|
|
35
|
+
|
|
36
|
+
config2 = Config(config_path=self.config_path)
|
|
37
|
+
data = config2.load()
|
|
38
|
+
assert data["server"]["address"] == "10.0.0.1"
|
|
39
|
+
assert data["server"]["port"] == 9002
|
|
40
|
+
assert data["server"]["username"] == "user1"
|
|
41
|
+
assert data["server"]["password"] == "pass1"
|
|
42
|
+
|
|
43
|
+
def test_load_existing_config(self):
|
|
44
|
+
existing = {
|
|
45
|
+
"server": {
|
|
46
|
+
"address": "example.com",
|
|
47
|
+
"port": 443,
|
|
48
|
+
"username": "abc",
|
|
49
|
+
"password": "def",
|
|
50
|
+
"tls": False,
|
|
51
|
+
"allowInsecure": False,
|
|
52
|
+
},
|
|
53
|
+
"local": {"socks_port": 2080, "http_port": 2087, "udp": False},
|
|
54
|
+
"mode": "manual",
|
|
55
|
+
}
|
|
56
|
+
with open(self.config_path, "w") as f:
|
|
57
|
+
json.dump(existing, f)
|
|
58
|
+
|
|
59
|
+
data = self.config.load()
|
|
60
|
+
assert data["server"]["address"] == "example.com"
|
|
61
|
+
assert data["server"]["tls"] is False
|
|
62
|
+
assert data["local"]["socks_port"] == 2080
|
|
63
|
+
assert data["mode"] == "manual"
|
|
64
|
+
|
|
65
|
+
def test_is_configured_false_when_no_address(self):
|
|
66
|
+
self.config.load()
|
|
67
|
+
assert self.config.is_configured() is False
|
|
68
|
+
|
|
69
|
+
def test_is_configured_true_when_address_set(self):
|
|
70
|
+
self.config.load()
|
|
71
|
+
self.config.data["server"]["address"] = "10.0.0.1"
|
|
72
|
+
assert self.config.is_configured() is True
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
from unittest.mock import patch, call, MagicMock
|
|
5
|
+
|
|
6
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
7
|
+
from system_proxy import SystemProxy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestSystemProxy:
|
|
11
|
+
def setup_method(self):
|
|
12
|
+
self.proxy = SystemProxy()
|
|
13
|
+
|
|
14
|
+
@patch("system_proxy.SystemProxy._get_active_interfaces")
|
|
15
|
+
@patch("system_proxy.subprocess.run")
|
|
16
|
+
def test_enable_sets_all_proxy_types(self, mock_run, mock_interfaces):
|
|
17
|
+
mock_interfaces.return_value = ["Wi-Fi"]
|
|
18
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="Enabled: Yes\n", stderr="")
|
|
19
|
+
|
|
20
|
+
self.proxy.enable(socks_port=1080, http_port=1087)
|
|
21
|
+
|
|
22
|
+
expected_calls = [
|
|
23
|
+
call(["networksetup", "-setsocksfirewallproxy", "Wi-Fi", "127.0.0.1", "1080"], capture_output=True, text=True),
|
|
24
|
+
call(["networksetup", "-setsocksfirewallproxystate", "Wi-Fi", "on"], capture_output=True, text=True),
|
|
25
|
+
call(["networksetup", "-setwebproxy", "Wi-Fi", "127.0.0.1", "1087"], capture_output=True, text=True),
|
|
26
|
+
call(["networksetup", "-setwebproxystate", "Wi-Fi", "on"], capture_output=True, text=True),
|
|
27
|
+
call(["networksetup", "-setsecurewebproxy", "Wi-Fi", "127.0.0.1", "1087"], capture_output=True, text=True),
|
|
28
|
+
call(["networksetup", "-setsecurewebproxystate", "Wi-Fi", "on"], capture_output=True, text=True),
|
|
29
|
+
]
|
|
30
|
+
mock_run.assert_has_calls(expected_calls, any_order=True)
|
|
31
|
+
|
|
32
|
+
@patch("system_proxy.SystemProxy._get_active_interfaces")
|
|
33
|
+
@patch("system_proxy.subprocess.run")
|
|
34
|
+
def test_disable_clears_all_proxy_types(self, mock_run, mock_interfaces):
|
|
35
|
+
mock_interfaces.return_value = ["Wi-Fi"]
|
|
36
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
37
|
+
|
|
38
|
+
self.proxy.disable()
|
|
39
|
+
|
|
40
|
+
expected_calls = [
|
|
41
|
+
call(["networksetup", "-setsocksfirewallproxystate", "Wi-Fi", "off"], capture_output=True, text=True),
|
|
42
|
+
call(["networksetup", "-setwebproxystate", "Wi-Fi", "off"], capture_output=True, text=True),
|
|
43
|
+
call(["networksetup", "-setsecurewebproxystate", "Wi-Fi", "off"], capture_output=True, text=True),
|
|
44
|
+
]
|
|
45
|
+
mock_run.assert_has_calls(expected_calls, any_order=True)
|
|
46
|
+
|
|
47
|
+
@patch("system_proxy.SystemProxy._get_active_interfaces")
|
|
48
|
+
@patch("system_proxy.subprocess.run")
|
|
49
|
+
def test_enable_multiple_interfaces(self, mock_run, mock_interfaces):
|
|
50
|
+
mock_interfaces.return_value = ["Wi-Fi", "Ethernet"]
|
|
51
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="Enabled: Yes\n", stderr="")
|
|
52
|
+
|
|
53
|
+
self.proxy.enable(socks_port=1080, http_port=1087)
|
|
54
|
+
|
|
55
|
+
# 6 set calls per interface x 2 + 3 verify calls per interface x 2 = 18
|
|
56
|
+
assert mock_run.call_count == 18
|
|
57
|
+
|
|
58
|
+
@patch("system_proxy.subprocess.run")
|
|
59
|
+
def test_get_active_interfaces(self, mock_run):
|
|
60
|
+
mock_run.return_value = MagicMock(
|
|
61
|
+
stdout="An asterisk (*) denotes that a network service is disabled.\nWi-Fi\n*Bluetooth PAN\nEthernet\n",
|
|
62
|
+
returncode=0,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
interfaces = self.proxy._get_active_interfaces()
|
|
66
|
+
|
|
67
|
+
assert "Wi-Fi" in interfaces
|
|
68
|
+
assert "Ethernet" in interfaces
|
|
69
|
+
assert "Bluetooth PAN" not in interfaces
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const ICONS_DIR = path.join(__dirname, "icons");
|
|
6
|
+
const SOURCES = ["socks_on_G.png", "socks_on_M.png"];
|
|
7
|
+
|
|
8
|
+
function resize(source) {
|
|
9
|
+
const name = path.basename(source, ".png");
|
|
10
|
+
const dest = path.join(ICONS_DIR, `${name}_bar.png`);
|
|
11
|
+
execSync(`sips -z 40 39 "${source}" --out "${dest}"`, { stdio: "ignore" });
|
|
12
|
+
console.log(`${new Date().toLocaleTimeString()} regenerated ${name}_bar.png`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function regenAll() {
|
|
16
|
+
SOURCES.forEach((f) => resize(path.join(ICONS_DIR, f)));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const cmd = process.argv[2];
|
|
20
|
+
if (cmd === "--watch") {
|
|
21
|
+
console.log("Watching icons for changes...");
|
|
22
|
+
regenAll();
|
|
23
|
+
SOURCES.forEach((f) => {
|
|
24
|
+
const filePath = path.join(ICONS_DIR, f);
|
|
25
|
+
fs.watch(filePath, (eventType) => {
|
|
26
|
+
if (eventType === "change") resize(filePath);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
regenAll();
|
|
31
|
+
}
|
package/config.json
CHANGED
|
@@ -135,16 +135,93 @@
|
|
|
135
135
|
"web_interface_port": 8003,
|
|
136
136
|
"your_domain": "yui.cool",
|
|
137
137
|
"vpn_proxy": "",
|
|
138
|
-
"auth_username": "",
|
|
139
|
-
"auth_password": "",
|
|
138
|
+
"auth_username": "lijing",
|
|
139
|
+
"auth_password": "lijing",
|
|
140
140
|
"enable_express": "1",
|
|
141
141
|
"enable_socks5": "1",
|
|
142
142
|
"socks5_port": 8002,
|
|
143
|
-
"enable_webinterface": "
|
|
143
|
+
"enable_webinterface": "0",
|
|
144
144
|
"devices": [
|
|
145
145
|
{
|
|
146
146
|
"ip": "192.168.124.1",
|
|
147
147
|
"mac": "FA:27:3C:E5:31:5F"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"ip": "192.168.124.2",
|
|
151
|
+
"mac": "7C:DE:78:A9:83:A0"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"ip": "192.168.124.6",
|
|
155
|
+
"mac": "0:DD:B6:EB:26:5C"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"ip": "192.168.124.10",
|
|
159
|
+
"mac": "FA:27:3C:E5:31:5F"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"ip": "192.168.124.34",
|
|
163
|
+
"mac": "D6:A0:61:69:67:F6"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"ip": "192.168.124.37",
|
|
167
|
+
"mac": "DE:1E:87:ED:17:8B"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"ip": "192.168.124.49",
|
|
171
|
+
"mac": "62:A1:CC:42:55:E4"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"ip": "192.168.124.107",
|
|
175
|
+
"mac": "6:26:EA:5A:9E:6C"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"ip": "192.168.124.111",
|
|
179
|
+
"mac": "74:3F:C2:67:74:98"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"ip": "192.168.124.125",
|
|
183
|
+
"mac": "14:C0:50:14:6E:A5"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"ip": "192.168.124.128",
|
|
187
|
+
"mac": "48:F3:F3:CA:1D:E"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"ip": "192.168.124.200",
|
|
191
|
+
"mac": "54:52:84:95:DF:5E"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"ip": "192.168.124.240",
|
|
195
|
+
"mac": "F4:6B:8C:90:29:5"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"ip": "192.168.124.251",
|
|
199
|
+
"mac": "6E:FB:18:4D:9C:3E"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"ip": "192.168.124.3",
|
|
203
|
+
"mac": "0:DD:B6:EA:E6:2A"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"ip": "192.168.124.64",
|
|
207
|
+
"mac": "76:39:29:EF:3:94"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"ip": "192.168.124.66",
|
|
211
|
+
"mac": "E2:4D:6E:1:21:6E"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"ip": "192.168.124.91",
|
|
215
|
+
"mac": "BA:5A:91:BE:7D:ED"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"ip": "192.168.124.232",
|
|
219
|
+
"mac": "DC:97:58:D:FD:9F"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"ip": "192.168.124.252",
|
|
223
|
+
"mac": "82:41:4F:6F:89:C2"
|
|
148
224
|
}
|
|
149
|
-
]
|
|
150
|
-
|
|
225
|
+
],
|
|
226
|
+
"enable_mitm": "1"
|
|
227
|
+
}
|