comate-ui-inspector 0.1.2 → 0.1.4-alpha.1
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/README.md +20 -4
- package/bridge/server.mjs +4 -2
- package/cli.mjs +62 -48
- package/extension/background.js +19 -6
- package/extension/inspector-main.js +31 -2
- package/extension/popup.html +5 -11
- package/extension/popup.js +7 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,20 +42,36 @@ ui-inspector install # 注册 native host + 个人级 hook,并打印
|
|
|
42
42
|
```bash
|
|
43
43
|
# 远程机上(和 Comate hook/MCP 同机):装 hook/MCP 并后台起 bridge
|
|
44
44
|
ui-inspector install --remote # --port 默认 8917;跳过无浏览器的 native host
|
|
45
|
-
#
|
|
46
|
-
ui-inspector link <remote-ssh-目标>
|
|
45
|
+
# 本地笔记本:把远程 bridge 端口转发到本地 127.0.0.1(dev server 端口自行转发)
|
|
46
|
+
ui-inspector link <remote-ssh-目标> # 本地/远程 bridge 都用默认 8917
|
|
47
|
+
# 本地与远程 bridge 端口不同时才两个都填:
|
|
48
|
+
ui-inspector link <remote-ssh-目标> --bridge-port <本地> --remote-bridge-port <远程>
|
|
47
49
|
```
|
|
48
50
|
|
|
49
|
-
`install --remote` 会打印 MCP 配置(贴进**远程**的 Comate 并 reload),并把 bridge 后台拉起(日志在 `~/.ui-inspector/bridge.log`)。`link` 本质是 `ssh -N -L <本地bridge端口>:127.0.0.1:<远程bridge端口>
|
|
51
|
+
`install --remote` 会打印 MCP 配置(贴进**远程**的 Comate 并 reload),并把 bridge 后台拉起(日志在 `~/.ui-inspector/bridge.log`)。`link` 本质是 `ssh -N -L <本地bridge端口>:127.0.0.1:<远程bridge端口> <remote>`,保持前台、Ctrl-C 断开。端口参数只有两个:本地 `--bridge-port`、远程 `--remote-bridge-port`(默认相同,相同时只填 `--bridge-port`、省略即默认 8917);弹窗里填的是**本地**那个。dev server 端口由你的远程开发工具自行转发,`link` 不再代管。
|
|
50
52
|
|
|
51
53
|
链路断了(ssh 掉线 / 远程 bridge 或 dev server 停了)时,先在本地自检:
|
|
52
54
|
|
|
53
55
|
```bash
|
|
54
|
-
ui-inspector doctor <remote> --port <dev端口> # 定位断在哪层并打印对应恢复命令
|
|
56
|
+
ui-inspector doctor <remote> --dev-port <dev端口> # 定位断在哪层并打印对应恢复命令
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
恢复顺序:先确保远程 bridge 活(`ui-inspector bridge` / `install --remote`)→ 再接本地隧道(`ui-inspector link`)→ 再看远程 dev server。
|
|
58
60
|
|
|
61
|
+
### 直连模式(笔记本能直达开发机内网 IP、堡垒机禁转发时用)
|
|
62
|
+
|
|
63
|
+
如果你的笔记本本来就能直连开发机的内网 IP(比如 `/etc/hosts` 把 `devlocal.oreateai.com` 指到 `10.25.64.64`,浏览器直接就能打开 dev 页),那连 SSH 隧道都不用——把 bridge 暴露到开发机网卡,浏览器直连即可,正好绕开「堡垒机禁止 SSH 转发」:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 开发机上:bridge 绑 0.0.0.0(install --remote 顺带起,或单独跑 bridge)
|
|
67
|
+
ui-inspector install --remote --host 0.0.0.0
|
|
68
|
+
# 或:ui-inspector bridge --host 0.0.0.0 --port 8917
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
然后扩展弹窗「传输通路」选 `远程(HTTP bridge)`,地址填**完整地址** `devlocal.oreateai.com:8917`(或 `10.25.64.64:8917`),不用 `ui-inspector link`。
|
|
72
|
+
|
|
73
|
+
> 注意:`--host 0.0.0.0` 会把 bridge 暴露在内网,谁能访问到这个端口谁就能写 pending;仅在可信内网用。dev 页若是 https、扩展连 http bridge 被浏览器拦,则退回隧道模式或给 bridge 上 https。
|
|
74
|
+
|
|
59
75
|
本地仍需一次:加载扩展(`chrome://extensions` 选 `extension` 目录),在弹窗把传输切到 `远程(HTTP bridge)`、填本地端口、加上你的 dev 域名。bridge 只绑 127.0.0.1、不暴露到网络,访问全程走 SSH 转发,所以 https 页面连 `127.0.0.1` 不触发混合内容拦截。
|
|
60
76
|
|
|
61
77
|
## 卸载 / 状态
|
package/bridge/server.mjs
CHANGED
|
@@ -17,6 +17,8 @@ import {homedir} from 'node:os';
|
|
|
17
17
|
const storeFile = resolve(homedir(), '.ui-inspector/pending.json');
|
|
18
18
|
const statusFile = resolve(homedir(), '.ui-inspector/status.json');
|
|
19
19
|
const PORT = Number(process.env.UI_INSPECTOR_BRIDGE_PORT) || 8917;
|
|
20
|
+
// 默认只绑 127.0.0.1(走 SSH 隧道);直连模式(笔记本能直达开发机内网 IP)时用 0.0.0.0 暴露到网卡。
|
|
21
|
+
const HOST = process.env.UI_INSPECTOR_BRIDGE_HOST || '127.0.0.1';
|
|
20
22
|
|
|
21
23
|
function readStore() {
|
|
22
24
|
try {
|
|
@@ -83,6 +85,6 @@ const server = createServer((req, res) => {
|
|
|
83
85
|
json(res, 404, {ok: false, error: 'not found'});
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
server.listen(PORT,
|
|
87
|
-
process.stdout.write(`ui-inspector bridge listening on http
|
|
88
|
+
server.listen(PORT, HOST, () => {
|
|
89
|
+
process.stdout.write(`ui-inspector bridge listening on http://${HOST}:${PORT}\n`);
|
|
88
90
|
});
|
package/cli.mjs
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* npm i -g comate-ui-inspector
|
|
5
5
|
* ui-inspector install # 注册 native host + 写个人级 hook + 打印 MCP 配置与扩展目录
|
|
6
6
|
* ui-inspector install --remote # 远程开发机:装 hook/MCP 并后台起 bridge(跳过无浏览器的 native host)
|
|
7
|
-
* ui-inspector link <remote> --port
|
|
8
|
-
* ui-inspector doctor <remote> --port <
|
|
7
|
+
* ui-inspector link <remote> --bridge-port <本地> [--remote-bridge-port <远程>] # 本地:SSH 把 bridge 端口转发到本地 127.0.0.1
|
|
8
|
+
* ui-inspector doctor <remote> --bridge-port <本地> [--dev-port <dev端口>] # 本地:自检远程链路断在哪层并打印恢复命令
|
|
9
9
|
* ui-inspector uninstall # 移除 native host 清单 + 个人级 hook(--purge 连 ~/.ui-inspector 一起删)
|
|
10
10
|
* ui-inspector status # 查看安装状态
|
|
11
11
|
* ui-inspector bridge # 启动 HTTP bridge(远程开发用,--port 默认 8917)
|
|
@@ -61,7 +61,10 @@ function out(text) {
|
|
|
61
61
|
process.stdout.write(`${text}\n`);
|
|
62
62
|
}
|
|
63
63
|
function installHookAndMcp() {
|
|
64
|
-
|
|
64
|
+
// 用绝对路径的 node(当前运行 install 的这个),而不是裸 `node`:
|
|
65
|
+
// Comate 拉起 hook/MCP 时 PATH 极简(不含 nvm/fnm 目录),裸 `node` 会「未找到命令」。
|
|
66
|
+
const nodeBin = process.execPath;
|
|
67
|
+
const hookCommand = `"${nodeBin}" "${hookPath}"`;
|
|
65
68
|
const config = readHooks() || {hooks: {}};
|
|
66
69
|
config.hooks = config.hooks || {};
|
|
67
70
|
const list = Array.isArray(config.hooks.UserPromptSubmit) ? config.hooks.UserPromptSubmit : [];
|
|
@@ -75,7 +78,7 @@ function installHookAndMcp() {
|
|
|
75
78
|
out('hook already present, skip');
|
|
76
79
|
}
|
|
77
80
|
out('\n== 把下面加进 Comate 的 MCP 配置(stdio),然后 reload Comate ==');
|
|
78
|
-
out(JSON.stringify({mcpServers: {'ui-inspector': {command:
|
|
81
|
+
out(JSON.stringify({mcpServers: {'ui-inspector': {command: nodeBin, args: [serverPath]}}}, null, 4));
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
function installNativeHost() {
|
|
@@ -104,26 +107,34 @@ function installNativeHost() {
|
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
// 后台拉起 bridge 并与本进程脱钩,install --remote 用:装完 hook/MCP 顺手把 bridge 跑起来。
|
|
107
|
-
function startBridgeDetached(port) {
|
|
110
|
+
function startBridgeDetached(port, host) {
|
|
108
111
|
mkdirSync(pendingDir, {recursive: true});
|
|
109
112
|
const logFile = resolve(pendingDir, 'bridge.log');
|
|
110
113
|
const fd = openSync(logFile, 'a');
|
|
114
|
+
const env = {...process.env, UI_INSPECTOR_BRIDGE_PORT: String(port)};
|
|
115
|
+
if (host) {
|
|
116
|
+
env.UI_INSPECTOR_BRIDGE_HOST = String(host);
|
|
117
|
+
}
|
|
111
118
|
const child = spawn(process.execPath, [resolve(pkgRoot, 'bridge/server.mjs')], {
|
|
112
119
|
detached: true,
|
|
113
120
|
stdio: ['ignore', fd, fd],
|
|
114
|
-
env
|
|
121
|
+
env,
|
|
115
122
|
});
|
|
116
123
|
child.unref();
|
|
117
|
-
out(`bridge 已后台启动(pid ${child.pid}
|
|
124
|
+
out(`bridge 已后台启动(pid ${child.pid},监听 ${host || '127.0.0.1'}:${port}),日志:${logFile}`);
|
|
118
125
|
out(` 停止:kill ${child.pid}`);
|
|
119
126
|
}
|
|
120
127
|
|
|
121
|
-
function install({remote, port}) {
|
|
128
|
+
function install({remote, port, host}) {
|
|
122
129
|
installHookAndMcp();
|
|
123
130
|
if (remote) {
|
|
124
131
|
out('\n== 远程开发机:跳过 native host/扩展(这台没浏览器),后台启动 bridge ==');
|
|
125
|
-
startBridgeDetached(port);
|
|
126
|
-
|
|
132
|
+
startBridgeDetached(port, host);
|
|
133
|
+
if (host && host !== '127.0.0.1') {
|
|
134
|
+
out(`\n直连模式:浏览器可直接访问本机 ${host}:${port},弹窗里填该地址,无需 ui-inspector link。`);
|
|
135
|
+
} else {
|
|
136
|
+
out(`\n本地笔记本上执行:ui-inspector link <这台机器的 ssh 目标>`);
|
|
137
|
+
}
|
|
127
138
|
return;
|
|
128
139
|
}
|
|
129
140
|
installNativeHost();
|
|
@@ -133,39 +144,41 @@ function install({remote, port}) {
|
|
|
133
144
|
out('完成后:按住 Alt 点元素多选 → 回 Comate 写修改意见。');
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
// 本地笔记本:把远程 bridge
|
|
137
|
-
//
|
|
138
|
-
|
|
147
|
+
// 本地笔记本:把远程 bridge 端口转发到本地 127.0.0.1。端口参数只有两个:
|
|
148
|
+
// --bridge-port <本地>(默认 8917)、--remote-bridge-port <远程>(默认与本地相同)。
|
|
149
|
+
// 本地=远程时只填 --bridge-port;不同才两个都填。dev server 端口自行转发。
|
|
150
|
+
function link({remote, bridgePort, remoteBridgePort}) {
|
|
139
151
|
if (!remote) {
|
|
140
|
-
out(
|
|
141
|
-
|
|
142
|
-
);
|
|
143
|
-
out('
|
|
152
|
+
out('用法:ui-inspector link <remote-ssh-目标> [--bridge-port <本地端口>] [--remote-bridge-port <远程端口>]');
|
|
153
|
+
out('例: ui-inspector link dev-box # 本地/远程都用默认 8917');
|
|
154
|
+
out(' ui-inspector link dev-box --bridge-port 9000 # 本地=远程=9000');
|
|
155
|
+
out(' ui-inspector link dev-box --bridge-port 9000 --remote-bridge-port 8917 # 本地 9000 → 远程 8917');
|
|
144
156
|
return;
|
|
145
157
|
}
|
|
146
158
|
const rbp = remoteBridgePort || bridgePort;
|
|
147
|
-
const args = ['-N', '-L', `${bridgePort}:127.0.0.1:${rbp}
|
|
148
|
-
if (devPort) {
|
|
149
|
-
args.push('-L', `${devPort}:127.0.0.1:${devPort}`);
|
|
150
|
-
}
|
|
151
|
-
args.push(remote);
|
|
159
|
+
const args = ['-N', '-L', `${bridgePort}:127.0.0.1:${rbp}`, remote];
|
|
152
160
|
out(`转发中(Ctrl-C 断开):ssh ${args.join(' ')}`);
|
|
153
161
|
out(
|
|
154
|
-
` bridge → 本地 127.0.0.1:${bridgePort} ⇢ 远程 127.0.0.1:${rbp}${
|
|
155
|
-
);
|
|
156
|
-
out(
|
|
157
|
-
` 提示:弹窗里的端口填本地的 ${bridgePort};远程 bridge 需在 ${rbp} 上监听(ui-inspector bridge --port ${rbp})。`
|
|
162
|
+
` bridge → 本地 127.0.0.1:${bridgePort} ⇢ 远程 127.0.0.1:${rbp}(弹窗里填本地 ${bridgePort};远程 bridge 需在 ${rbp} 监听)`
|
|
158
163
|
);
|
|
164
|
+
out(` 注:dev server 端口请自行转发(远程开发工具通常已转发,或另跑 ssh -L <dev>:127.0.0.1:<dev> ${remote})`);
|
|
159
165
|
const child = spawn('ssh', args, {stdio: 'inherit'});
|
|
160
166
|
child.on('exit', code => process.exit(code ?? 0));
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
function bridge() {
|
|
164
|
-
const
|
|
165
|
-
const
|
|
170
|
+
const port = getOpt('--port', '8917');
|
|
171
|
+
const host = getOpt('--host', null);
|
|
166
172
|
process.env.UI_INSPECTOR_BRIDGE_PORT = String(port);
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
if (host) {
|
|
174
|
+
process.env.UI_INSPECTOR_BRIDGE_HOST = String(host);
|
|
175
|
+
}
|
|
176
|
+
out(`启动 HTTP bridge:监听 ${host || '127.0.0.1'}:${port}`);
|
|
177
|
+
if (host && host !== '127.0.0.1') {
|
|
178
|
+
out('直连模式:浏览器可直接访问本机该地址(弹窗里填完整地址),无需 SSH 隧道。');
|
|
179
|
+
} else {
|
|
180
|
+
out('隧道模式:本地经 SSH 转发访问,ssh -L ' + port + ':127.0.0.1:' + port + ' <remote>');
|
|
181
|
+
}
|
|
169
182
|
import(resolve(pkgRoot, 'bridge/server.mjs'));
|
|
170
183
|
}
|
|
171
184
|
|
|
@@ -226,22 +239,22 @@ function getOpt(name, def) {
|
|
|
226
239
|
function parseLink() {
|
|
227
240
|
const rest = process.argv.slice(3);
|
|
228
241
|
let remote;
|
|
229
|
-
let devPort;
|
|
230
242
|
let bridgePort = '8917';
|
|
231
243
|
let remoteBridgePort;
|
|
244
|
+
let devPort;
|
|
232
245
|
for (let i = 0; i < rest.length; i++) {
|
|
233
246
|
const a = rest[i];
|
|
234
|
-
if (a === '--port') {
|
|
235
|
-
devPort = rest[++i];
|
|
236
|
-
} else if (a === '--bridge-port') {
|
|
247
|
+
if (a === '--bridge-port') {
|
|
237
248
|
bridgePort = rest[++i];
|
|
238
249
|
} else if (a === '--remote-bridge-port') {
|
|
239
250
|
remoteBridgePort = rest[++i];
|
|
251
|
+
} else if (a === '--dev-port') {
|
|
252
|
+
devPort = rest[++i]; // 仅 doctor 用;放这里一并消费,避免其值被当成 remote
|
|
240
253
|
} else if (!a.startsWith('-')) {
|
|
241
254
|
remote = a;
|
|
242
255
|
}
|
|
243
256
|
}
|
|
244
|
-
return {remote,
|
|
257
|
+
return {remote, bridgePort, remoteBridgePort, devPort};
|
|
245
258
|
}
|
|
246
259
|
|
|
247
260
|
// 探测本地某端口的 http 可达性(2s 超时)。用 node:http 而非 fetch,兼容 node 18+。
|
|
@@ -262,9 +275,12 @@ function probe(port, path) {
|
|
|
262
275
|
|
|
263
276
|
// 一键自检:定位远程开发链路(本地隧道 → 远程 bridge → 远程 dev server)断在哪层,并打印恢复命令。
|
|
264
277
|
async function doctor() {
|
|
265
|
-
const {remote,
|
|
278
|
+
const {remote, bridgePort, remoteBridgePort, devPort} = parseLink();
|
|
266
279
|
const rm = remote || '<remote>';
|
|
267
|
-
const
|
|
280
|
+
const linkCmd =
|
|
281
|
+
`ui-inspector link ${rm}` +
|
|
282
|
+
(bridgePort !== '8917' ? ` --bridge-port ${bridgePort}` : '') +
|
|
283
|
+
(remoteBridgePort && remoteBridgePort !== bridgePort ? ` --remote-bridge-port ${remoteBridgePort}` : '');
|
|
268
284
|
out('ui-inspector doctor(远程开发链路自检):');
|
|
269
285
|
|
|
270
286
|
const b = await probe(bridgePort, '/state');
|
|
@@ -272,7 +288,7 @@ async function doctor() {
|
|
|
272
288
|
out(` [1] bridge ✓ 127.0.0.1:${bridgePort} 可达——本地→隧道→远程 bridge 全通`);
|
|
273
289
|
} else if (b.state === 'refused') {
|
|
274
290
|
out(` [1] bridge ✗ 本地 127.0.0.1:${bridgePort} 无人监听 → SSH 隧道断了(或没起)`);
|
|
275
|
-
out(`
|
|
291
|
+
out(` 恢复:${linkCmd}`);
|
|
276
292
|
} else {
|
|
277
293
|
out(` [1] bridge ✗ 隧道在、但远程 bridge 无响应(${b.state})`);
|
|
278
294
|
out(` 恢复:登远程机跑 ui-inspector bridge(或 install --remote),看 ~/.ui-inspector/bridge.log`);
|
|
@@ -281,35 +297,33 @@ async function doctor() {
|
|
|
281
297
|
if (devPort) {
|
|
282
298
|
const d = await probe(devPort, '/');
|
|
283
299
|
if (d.state === 'refused') {
|
|
284
|
-
out(` [2] dev ✗ 本地 127.0.0.1:${devPort} 不可达 →
|
|
285
|
-
out(` 恢复:① 远程起 dev server;② 本地 ui-inspector link ${rm} --port ${devPort}`);
|
|
300
|
+
out(` [2] dev ✗ 本地 127.0.0.1:${devPort} 不可达 → 该端口没转发,或远程 dev server 没起`);
|
|
286
301
|
} else if (d.state === 'timeout') {
|
|
287
302
|
out(` [2] dev ✗ 127.0.0.1:${devPort} 超时 → 远程 dev server 可能挂了`);
|
|
288
|
-
out(` 恢复:远程重启 dev server`);
|
|
289
303
|
} else {
|
|
290
304
|
out(` [2] dev ✓ 127.0.0.1:${devPort} 可达(HTTP ${d.code})`);
|
|
291
305
|
}
|
|
292
306
|
} else {
|
|
293
|
-
out(' [2] dev - 未传 --port,跳过 dev server 检查');
|
|
307
|
+
out(' [2] dev - 未传 --dev-port,跳过 dev server 检查');
|
|
294
308
|
}
|
|
295
309
|
|
|
296
|
-
out('\n恢复顺序:先确保远程 bridge 活 → 再接本地隧道(link)→ 再看远程 dev server
|
|
310
|
+
out('\n恢复顺序:先确保远程 bridge 活 → 再接本地隧道(link)→ 再看远程 dev server(自行转发)。');
|
|
297
311
|
}
|
|
298
312
|
|
|
299
313
|
function help() {
|
|
300
314
|
out('用法:ui-inspector <install|uninstall|status|bridge|link|doctor>');
|
|
301
315
|
out(' install 注册 native host + 个人级 hook,并打印 MCP 配置与扩展目录');
|
|
302
|
-
out(' install --remote 远程开发机:装 hook/MCP 并后台起 bridge
|
|
303
|
-
out(' link <remote> 本地:SSH 把 bridge
|
|
304
|
-
out(' doctor <remote>
|
|
316
|
+
out(' install --remote 远程开发机:装 hook/MCP 并后台起 bridge(--port 默认 8917,--host 0.0.0.0 可直连免隧道)');
|
|
317
|
+
out(' link <remote> 本地:SSH 把 bridge 端口转发到本地(--bridge-port <本地> [--remote-bridge-port <远程>])');
|
|
318
|
+
out(' doctor <remote> 本地:自检远程链路断在哪层并打印恢复命令(--bridge-port <本地> [--dev-port <dev端口>])');
|
|
305
319
|
out(' uninstall 移除上述注册(加 --purge 连 ~/.ui-inspector 数据一起删)');
|
|
306
320
|
out(' status 查看安装状态');
|
|
307
|
-
out(' bridge 启动 HTTP bridge
|
|
321
|
+
out(' bridge 启动 HTTP bridge(--port 默认 8917,--host 默认 127.0.0.1;直连填 0.0.0.0)');
|
|
308
322
|
}
|
|
309
323
|
|
|
310
324
|
const cmd = process.argv[2] || 'help';
|
|
311
325
|
if (cmd === 'install') {
|
|
312
|
-
install({remote: process.argv.includes('--remote'), port: getOpt('--port', '8917')});
|
|
326
|
+
install({remote: process.argv.includes('--remote'), port: getOpt('--port', '8917'), host: getOpt('--host', null)});
|
|
313
327
|
} else if (cmd === 'link') {
|
|
314
328
|
link(parseLink());
|
|
315
329
|
} else if (cmd === 'doctor') {
|
package/extension/background.js
CHANGED
|
@@ -4,24 +4,37 @@
|
|
|
4
4
|
* 两件事:
|
|
5
5
|
* 1) 传输适配:把内容脚本的 ingest/state 转发给本地后端。
|
|
6
6
|
* - native(默认,本地开发):chrome.runtime.sendNativeMessage 按需拉起 host.mjs。
|
|
7
|
-
* - http(远程开发):fetch
|
|
7
|
+
* - http(远程开发):fetch bridge 的 /ingest、/state。bridgeTarget 为端口(→127.0.0.1:端口)或完整地址。
|
|
8
8
|
* 2) 动态注册内容脚本:目标域名不再硬编码在 manifest,改由弹窗(popup)写入 storage,
|
|
9
9
|
* 这里按配置用 chrome.scripting 注册(MAIN + ISOLATED 两份)。
|
|
10
10
|
*/
|
|
11
11
|
const HOST = 'com.oreate.ui_inspector';
|
|
12
12
|
|
|
13
13
|
// localhost / 127.0.0.1 是通用开发地址,作默认种子;项目专属域名(如 local.oreateai.com)在弹窗里加。
|
|
14
|
-
//
|
|
14
|
+
// bridgeTarget:隧道模式填端口(→ 127.0.0.1:端口);直连模式填 host:port 或完整 URL。
|
|
15
15
|
const DEFAULTS = {
|
|
16
16
|
domains: ['http://localhost/*', 'http://127.0.0.1/*'],
|
|
17
17
|
transport: 'native',
|
|
18
|
-
|
|
18
|
+
bridgeTarget: '8917',
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
function getConfig() {
|
|
22
22
|
return chrome.storage.local.get(DEFAULTS);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// 把弹窗里的 bridgeTarget 归一成请求 base:
|
|
26
|
+
// 纯数字 → http://127.0.0.1:<端口>;带 http(s):// → 原样;其余 → 补 http://
|
|
27
|
+
function bridgeBase(target) {
|
|
28
|
+
const t = String(target || '').trim();
|
|
29
|
+
if (/^\d+$/.test(t)) {
|
|
30
|
+
return `http://127.0.0.1:${t}`;
|
|
31
|
+
}
|
|
32
|
+
if (/^https?:\/\//i.test(t)) {
|
|
33
|
+
return t.replace(/\/+$/, '');
|
|
34
|
+
}
|
|
35
|
+
return `http://${t}`.replace(/\/+$/, '');
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
// ---------- 传输 ----------
|
|
26
39
|
function viaNative(message) {
|
|
27
40
|
return new Promise(resolve => {
|
|
@@ -35,8 +48,8 @@ function viaNative(message) {
|
|
|
35
48
|
});
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
async function viaHttp(
|
|
39
|
-
const base =
|
|
51
|
+
async function viaHttp(target, message) {
|
|
52
|
+
const base = bridgeBase(target);
|
|
40
53
|
try {
|
|
41
54
|
if (message.type === 'ingest') {
|
|
42
55
|
const r = await fetch(`${base}/ingest`, {
|
|
@@ -55,7 +68,7 @@ async function viaHttp(port, message) {
|
|
|
55
68
|
|
|
56
69
|
async function dispatch(message) {
|
|
57
70
|
const cfg = await getConfig();
|
|
58
|
-
return cfg.transport === 'http' ? viaHttp(cfg.
|
|
71
|
+
return cfg.transport === 'http' ? viaHttp(cfg.bridgeTarget, message) : viaNative(message);
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
@@ -126,9 +126,30 @@
|
|
|
126
126
|
box.style.height = rect.height + 'px';
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// 元素还在页面上渲染吗:被移出 DOM 或 display:none 时 getClientRects() 为空。
|
|
130
|
+
function isRendered(el) {
|
|
131
|
+
return el && el.isConnected && el.getClientRects().length > 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 每帧跟随元素位置;同时剔除已消失(被删除/隐藏)的选中项,连同高亮框一起移除。
|
|
129
135
|
function repositionBoxes() {
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
var removed = 0;
|
|
137
|
+
for (var i = selected.length - 1; i >= 0; i--) {
|
|
138
|
+
if (isRendered(selected[i].el)) {
|
|
139
|
+
placeBox(selected[i].box, selected[i].el);
|
|
140
|
+
} else {
|
|
141
|
+
selected[i].box.remove();
|
|
142
|
+
selected.splice(i, 1);
|
|
143
|
+
removed++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (removed) {
|
|
147
|
+
postSelection();
|
|
148
|
+
showHint(
|
|
149
|
+
selected.length
|
|
150
|
+
? '有选中元素已消失,已移除(当前 ' + selected.length + ' 个)'
|
|
151
|
+
: '选中元素已消失,已全部移除'
|
|
152
|
+
);
|
|
132
153
|
}
|
|
133
154
|
}
|
|
134
155
|
|
|
@@ -233,6 +254,14 @@
|
|
|
233
254
|
window.addEventListener('scroll', repositionBoxes, true);
|
|
234
255
|
window.addEventListener('resize', repositionBoxes, true);
|
|
235
256
|
|
|
257
|
+
// 每帧跟随并清理:元素被删/隐藏后,repositionBoxes 会移除它的高亮框(scroll/resize 不一定触发)。
|
|
258
|
+
(function raf() {
|
|
259
|
+
if (selected.length) {
|
|
260
|
+
repositionBoxes();
|
|
261
|
+
}
|
|
262
|
+
window.requestAnimationFrame(raf);
|
|
263
|
+
})();
|
|
264
|
+
|
|
236
265
|
// bridge-content 轮询回发的消息:cleared(撤高亮)/ status(toast 提示)
|
|
237
266
|
window.addEventListener('message', function (event) {
|
|
238
267
|
if (event.source !== window || !event.data) {
|
package/extension/popup.html
CHANGED
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
<input
|
|
107
107
|
id="domain-input"
|
|
108
108
|
type="text"
|
|
109
|
-
placeholder="如 https://
|
|
109
|
+
placeholder="如 https://example.com"
|
|
110
110
|
/>
|
|
111
111
|
<button
|
|
112
112
|
id="add-domain"
|
|
@@ -142,21 +142,15 @@
|
|
|
142
142
|
class="row"
|
|
143
143
|
style="margin-top: 6px"
|
|
144
144
|
>
|
|
145
|
-
<span style="color: #656d76">127.0.0.1 :</span>
|
|
146
145
|
<input
|
|
147
|
-
id="bridge-
|
|
146
|
+
id="bridge-target"
|
|
148
147
|
type="text"
|
|
149
|
-
|
|
150
|
-
placeholder="8917"
|
|
151
|
-
style="max-width: 90px"
|
|
148
|
+
placeholder="8917 或 devlocal.oreateai.com:8917"
|
|
152
149
|
/>
|
|
153
150
|
</div>
|
|
154
151
|
<div class="hint">
|
|
155
|
-
|
|
156
|
-
<code>
|
|
157
|
-
,
|
|
158
|
-
<code>ui-inspector link</code>
|
|
159
|
-
把端口转发到本地。
|
|
152
|
+
隧道模式填端口(如 <code>8917</code>,默认连本地 127.0.0.1);直连模式填完整地址(如
|
|
153
|
+
<code>devlocal.oreateai.com:8917</code>)。
|
|
160
154
|
</div>
|
|
161
155
|
</div>
|
|
162
156
|
</section>
|
package/extension/popup.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const DEFAULTS = {
|
|
3
3
|
domains: ['http://localhost/*', 'http://127.0.0.1/*'],
|
|
4
4
|
transport: 'native',
|
|
5
|
-
|
|
5
|
+
bridgeTarget: '8917',
|
|
6
6
|
};
|
|
7
7
|
const PROTECTED = new Set(DEFAULTS.domains); // 默认项不可删、不回收权限
|
|
8
8
|
|
|
@@ -106,23 +106,22 @@ function toggleBridge() {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
async function renderTransport() {
|
|
109
|
-
const {transport,
|
|
109
|
+
const {transport, bridgeTarget} = await getConfig();
|
|
110
110
|
for (const r of document.querySelectorAll('input[name="transport"]')) {
|
|
111
111
|
r.checked = r.value === transport;
|
|
112
112
|
}
|
|
113
|
-
$('bridge-
|
|
113
|
+
$('bridge-target').value = bridgeTarget;
|
|
114
114
|
toggleBridge();
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
async function saveTransport() {
|
|
118
118
|
const transport = document.querySelector('input[name="transport"]:checked')?.value || 'native';
|
|
119
|
-
const
|
|
120
|
-
if (transport === 'http' &&
|
|
121
|
-
msg('
|
|
119
|
+
const bridgeTarget = $('bridge-target').value.trim() || DEFAULTS.bridgeTarget;
|
|
120
|
+
if (transport === 'http' && !bridgeTarget) {
|
|
121
|
+
msg('请填 bridge 端口或地址', false);
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
await chrome.storage.local.set({transport, bridgePort});
|
|
124
|
+
await chrome.storage.local.set({transport, bridgeTarget});
|
|
126
125
|
msg('已保存传输设置');
|
|
127
126
|
}
|
|
128
127
|
|
package/package.json
CHANGED