comate-ui-inspector 0.1.2-alpha.4 → 0.1.2-alpha.6
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 +3 -3
- package/cli.mjs +17 -5
- package/extension/background.js +6 -5
- package/extension/popup.html +63 -10
- package/extension/popup.js +20 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ ui-inspector install # 注册 native host + 个人级 hook,并打印
|
|
|
33
33
|
- **生效域名**:默认已含 `localhost` / `127.0.0.1`;项目域名(如 `https://local.oreateai.com`)自己加,添加时浏览器会弹权限申请,允许后即在该域名注入选取脚本。
|
|
34
34
|
- **传输通路**:
|
|
35
35
|
- `本地(Native Messaging)`——默认,浏览器按需拉起 `host.mjs`,零常驻。
|
|
36
|
-
- `远程(HTTP bridge
|
|
36
|
+
- `远程(HTTP bridge)`——只填**本地端口**(host 固定 127.0.0.1,默认 `8917`),用于远程开发(见下)。
|
|
37
37
|
|
|
38
38
|
## 远程开发(HTTP bridge)
|
|
39
39
|
|
|
@@ -46,7 +46,7 @@ ui-inspector install --remote # --port 默认 8917;跳过无浏览
|
|
|
46
46
|
ui-inspector link <remote-ssh-目标> --port <dev端口>
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
`install --remote` 会打印 MCP 配置(贴进**远程**的 Comate 并 reload),并把 bridge 后台拉起(日志在 `~/.ui-inspector/bridge.log`)。`link` 本质是 `ssh -N -L
|
|
49
|
+
`install --remote` 会打印 MCP 配置(贴进**远程**的 Comate 并 reload),并把 bridge 后台拉起(日志在 `~/.ui-inspector/bridge.log`)。`link` 本质是 `ssh -N -L <本地bridge端口>:127.0.0.1:<远程bridge端口> -L <dev端口>:127.0.0.1:<dev端口> <remote>`,保持前台、Ctrl-C 断开。远程 bridge 端口不必和本地一致:本地用 `--bridge-port`、远程用 `--remote-bridge-port`(默认两者相同);弹窗里填的是**本地**那个端口。
|
|
50
50
|
|
|
51
51
|
链路断了(ssh 掉线 / 远程 bridge 或 dev server 停了)时,先在本地自检:
|
|
52
52
|
|
|
@@ -56,7 +56,7 @@ ui-inspector doctor <remote> --port <dev端口> # 定位断在哪层并打印
|
|
|
56
56
|
|
|
57
57
|
恢复顺序:先确保远程 bridge 活(`ui-inspector bridge` / `install --remote`)→ 再接本地隧道(`ui-inspector link`)→ 再看远程 dev server。
|
|
58
58
|
|
|
59
|
-
本地仍需一次:加载扩展(`chrome://extensions` 选 `extension` 目录),在弹窗把传输切到 `远程(HTTP bridge
|
|
59
|
+
本地仍需一次:加载扩展(`chrome://extensions` 选 `extension` 目录),在弹窗把传输切到 `远程(HTTP bridge)`、填本地端口、加上你的 dev 域名。bridge 只绑 127.0.0.1、不暴露到网络,访问全程走 SSH 转发,所以 https 页面连 `127.0.0.1` 不触发混合内容拦截。
|
|
60
60
|
|
|
61
61
|
## 卸载 / 状态
|
|
62
62
|
|
package/cli.mjs
CHANGED
|
@@ -134,19 +134,28 @@ function install({remote, port}) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// 本地笔记本:把远程 bridge 端口 + dev server 端口都转发到本地 127.0.0.1。
|
|
137
|
-
|
|
137
|
+
// 远程 bridge 监听端口可与本地不同:-L <本地bridge端口>:127.0.0.1:<远程bridge端口>。
|
|
138
|
+
function link({remote, devPort, bridgePort, remoteBridgePort}) {
|
|
138
139
|
if (!remote) {
|
|
139
|
-
out(
|
|
140
|
+
out(
|
|
141
|
+
'用法:ui-inspector link <remote-ssh-目标> --port <dev端口> [--bridge-port 8917] [--remote-bridge-port <远程端口>]'
|
|
142
|
+
);
|
|
140
143
|
out('例: ui-inspector link dev-box --port 5173');
|
|
141
144
|
return;
|
|
142
145
|
}
|
|
143
|
-
const
|
|
146
|
+
const rbp = remoteBridgePort || bridgePort;
|
|
147
|
+
const args = ['-N', '-L', `${bridgePort}:127.0.0.1:${rbp}`];
|
|
144
148
|
if (devPort) {
|
|
145
149
|
args.push('-L', `${devPort}:127.0.0.1:${devPort}`);
|
|
146
150
|
}
|
|
147
151
|
args.push(remote);
|
|
148
152
|
out(`转发中(Ctrl-C 断开):ssh ${args.join(' ')}`);
|
|
149
|
-
out(
|
|
153
|
+
out(
|
|
154
|
+
` bridge → 本地 127.0.0.1:${bridgePort} ⇢ 远程 127.0.0.1:${rbp}${devPort ? `;dev → 127.0.0.1:${devPort}` : ''}`
|
|
155
|
+
);
|
|
156
|
+
out(
|
|
157
|
+
` 提示:弹窗里的端口填本地的 ${bridgePort};远程 bridge 需在 ${rbp} 上监听(ui-inspector bridge --port ${rbp})。`
|
|
158
|
+
);
|
|
150
159
|
const child = spawn('ssh', args, {stdio: 'inherit'});
|
|
151
160
|
child.on('exit', code => process.exit(code ?? 0));
|
|
152
161
|
}
|
|
@@ -219,17 +228,20 @@ function parseLink() {
|
|
|
219
228
|
let remote;
|
|
220
229
|
let devPort;
|
|
221
230
|
let bridgePort = '8917';
|
|
231
|
+
let remoteBridgePort;
|
|
222
232
|
for (let i = 0; i < rest.length; i++) {
|
|
223
233
|
const a = rest[i];
|
|
224
234
|
if (a === '--port') {
|
|
225
235
|
devPort = rest[++i];
|
|
226
236
|
} else if (a === '--bridge-port') {
|
|
227
237
|
bridgePort = rest[++i];
|
|
238
|
+
} else if (a === '--remote-bridge-port') {
|
|
239
|
+
remoteBridgePort = rest[++i];
|
|
228
240
|
} else if (!a.startsWith('-')) {
|
|
229
241
|
remote = a;
|
|
230
242
|
}
|
|
231
243
|
}
|
|
232
|
-
return {remote, devPort, bridgePort};
|
|
244
|
+
return {remote, devPort, bridgePort, remoteBridgePort};
|
|
233
245
|
}
|
|
234
246
|
|
|
235
247
|
// 探测本地某端口的 http 可达性(2s 超时)。用 node:http 而非 fetch,兼容 node 18+。
|
package/extension/background.js
CHANGED
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
* 两件事:
|
|
5
5
|
* 1) 传输适配:把内容脚本的 ingest/state 转发给本地后端。
|
|
6
6
|
* - native(默认,本地开发):chrome.runtime.sendNativeMessage 按需拉起 host.mjs。
|
|
7
|
-
* - http(远程开发):fetch
|
|
7
|
+
* - http(远程开发):fetch http://127.0.0.1:<bridgePort>(/ingest、/state),远程机上跑 bridge/server.mjs。
|
|
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
|
+
// 远程模式下扩展永远连本地 SSH 转发出来的 127.0.0.1,host 固定,只需配端口。
|
|
14
15
|
const DEFAULTS = {
|
|
15
16
|
domains: ['http://localhost/*', 'http://127.0.0.1/*'],
|
|
16
17
|
transport: 'native',
|
|
17
|
-
|
|
18
|
+
bridgePort: '8917',
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
function getConfig() {
|
|
@@ -34,8 +35,8 @@ function viaNative(message) {
|
|
|
34
35
|
});
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
async function viaHttp(
|
|
38
|
-
const base =
|
|
38
|
+
async function viaHttp(port, message) {
|
|
39
|
+
const base = `http://127.0.0.1:${port}`;
|
|
39
40
|
try {
|
|
40
41
|
if (message.type === 'ingest') {
|
|
41
42
|
const r = await fetch(`${base}/ingest`, {
|
|
@@ -54,7 +55,7 @@ async function viaHttp(bridgeUrl, message) {
|
|
|
54
55
|
|
|
55
56
|
async function dispatch(message) {
|
|
56
57
|
const cfg = await getConfig();
|
|
57
|
-
return cfg.transport === 'http' ? viaHttp(cfg.
|
|
58
|
+
return cfg.transport === 'http' ? viaHttp(cfg.bridgePort, message) : viaNative(message);
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
package/extension/popup.html
CHANGED
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
<html lang="zh">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
-
<meta
|
|
5
|
+
<meta
|
|
6
|
+
name="viewport"
|
|
7
|
+
content="width=device-width, initial-scale=1"
|
|
8
|
+
/>
|
|
6
9
|
<style>
|
|
7
10
|
body {
|
|
8
11
|
width: 320px;
|
|
9
12
|
margin: 0;
|
|
10
13
|
padding: 14px;
|
|
11
|
-
font:
|
|
14
|
+
font:
|
|
15
|
+
13px/1.5 -apple-system,
|
|
16
|
+
BlinkMacSystemFont,
|
|
17
|
+
'Segoe UI',
|
|
18
|
+
sans-serif;
|
|
12
19
|
color: #1f2328;
|
|
13
20
|
}
|
|
14
21
|
h1 {
|
|
@@ -96,8 +103,17 @@
|
|
|
96
103
|
<section>
|
|
97
104
|
<div class="label">生效域名</div>
|
|
98
105
|
<div class="row">
|
|
99
|
-
<input
|
|
100
|
-
|
|
106
|
+
<input
|
|
107
|
+
id="domain-input"
|
|
108
|
+
type="text"
|
|
109
|
+
placeholder="如 https://local.oreateai.com"
|
|
110
|
+
/>
|
|
111
|
+
<button
|
|
112
|
+
id="add-domain"
|
|
113
|
+
class="primary"
|
|
114
|
+
>
|
|
115
|
+
添加
|
|
116
|
+
</button>
|
|
101
117
|
</div>
|
|
102
118
|
<ul id="domain-list"></ul>
|
|
103
119
|
<div class="hint">添加时浏览器会请求该域名的访问权限,允许后即在该域名注入选取脚本。</div>
|
|
@@ -105,15 +121,52 @@
|
|
|
105
121
|
|
|
106
122
|
<section>
|
|
107
123
|
<div class="label">传输通路</div>
|
|
108
|
-
<label class="radio"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
<label class="radio">
|
|
125
|
+
<input
|
|
126
|
+
type="radio"
|
|
127
|
+
name="transport"
|
|
128
|
+
value="native"
|
|
129
|
+
/>
|
|
130
|
+
本地(Native Messaging,默认)
|
|
131
|
+
</label>
|
|
132
|
+
<label class="radio">
|
|
133
|
+
<input
|
|
134
|
+
type="radio"
|
|
135
|
+
name="transport"
|
|
136
|
+
value="http"
|
|
137
|
+
/>
|
|
138
|
+
远程(HTTP bridge)
|
|
139
|
+
</label>
|
|
140
|
+
<div id="bridge-config">
|
|
141
|
+
<div
|
|
142
|
+
class="row"
|
|
143
|
+
style="margin-top: 6px"
|
|
144
|
+
>
|
|
145
|
+
<span style="color: #656d76">127.0.0.1 :</span>
|
|
146
|
+
<input
|
|
147
|
+
id="bridge-port"
|
|
148
|
+
type="text"
|
|
149
|
+
inputmode="numeric"
|
|
150
|
+
placeholder="8917"
|
|
151
|
+
style="max-width: 90px"
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
<div class="hint">
|
|
155
|
+
只填本地端口(host 固定 127.0.0.1)。远程开发:远程跑
|
|
156
|
+
<code>ui-inspector bridge</code>
|
|
157
|
+
,
|
|
158
|
+
<code>ui-inspector link</code>
|
|
159
|
+
把端口转发到本地。
|
|
160
|
+
</div>
|
|
112
161
|
</div>
|
|
113
|
-
<div class="hint">远程开发:在远程机跑 <code>ui-inspector bridge</code>,SSH 把端口转发到本地 127.0.0.1。</div>
|
|
114
162
|
</section>
|
|
115
163
|
|
|
116
|
-
<button
|
|
164
|
+
<button
|
|
165
|
+
id="save"
|
|
166
|
+
class="primary"
|
|
167
|
+
>
|
|
168
|
+
保存传输设置
|
|
169
|
+
</button>
|
|
117
170
|
<div id="msg"></div>
|
|
118
171
|
|
|
119
172
|
<script src="popup.js"></script>
|
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
|
+
bridgePort: '8917',
|
|
6
6
|
};
|
|
7
7
|
const PROTECTED = new Set(DEFAULTS.domains); // 默认项不可删、不回收权限
|
|
8
8
|
|
|
@@ -99,23 +99,38 @@ async function removeDomain(match) {
|
|
|
99
99
|
msg(`已删除 ${match}`);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// 「本地」模式不需要 bridge 地址,隐藏输入框;「远程」才显示。
|
|
103
|
+
function toggleBridge() {
|
|
104
|
+
const transport = document.querySelector('input[name="transport"]:checked')?.value || 'native';
|
|
105
|
+
$('bridge-config').style.display = transport === 'http' ? 'block' : 'none';
|
|
106
|
+
}
|
|
107
|
+
|
|
102
108
|
async function renderTransport() {
|
|
103
|
-
const {transport,
|
|
109
|
+
const {transport, bridgePort} = await getConfig();
|
|
104
110
|
for (const r of document.querySelectorAll('input[name="transport"]')) {
|
|
105
111
|
r.checked = r.value === transport;
|
|
106
112
|
}
|
|
107
|
-
$('bridge-
|
|
113
|
+
$('bridge-port').value = bridgePort;
|
|
114
|
+
toggleBridge();
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
async function saveTransport() {
|
|
111
118
|
const transport = document.querySelector('input[name="transport"]:checked')?.value || 'native';
|
|
112
|
-
const
|
|
113
|
-
|
|
119
|
+
const raw = $('bridge-port').value.trim();
|
|
120
|
+
if (transport === 'http' && !/^\d+$/.test(raw)) {
|
|
121
|
+
msg('端口要是数字', false);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const bridgePort = raw || DEFAULTS.bridgePort;
|
|
125
|
+
await chrome.storage.local.set({transport, bridgePort});
|
|
114
126
|
msg('已保存传输设置');
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
$('add-domain').onclick = addDomain;
|
|
118
130
|
$('save').onclick = saveTransport;
|
|
131
|
+
for (const r of document.querySelectorAll('input[name="transport"]')) {
|
|
132
|
+
r.addEventListener('change', toggleBridge);
|
|
133
|
+
}
|
|
119
134
|
$('domain-input').addEventListener('keydown', e => {
|
|
120
135
|
if (e.key === 'Enter') {
|
|
121
136
|
addDomain();
|
package/package.json
CHANGED