@linxin666/dsh-remote-web-ui 0.2.4 → 0.2.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.i18n.yaml +2 -2
- package/README.md +148 -77
- package/README.zh.md +32 -26
- package/lib/client.js +603 -68
- package/lib/client.js.map +1 -1
- package/lib/index.js +667 -32
- package/lib/mobile.js +3 -1
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/FenceNotice.d.ts +21 -0
- package/lib/types/client/FenceNotice.d.ts.map +1 -0
- package/lib/types/client/FenceNotice.js +10 -0
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +18 -11
- package/lib/types/client/RemotePanel.d.ts +10 -4
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +7 -6
- package/lib/types/client/RemoteSettingsCard.d.ts +4 -0
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +3 -1
- package/lib/types/client/device-name.d.ts +3 -0
- package/lib/types/client/device-name.d.ts.map +1 -0
- package/lib/types/client/device-name.js +30 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +48 -0
- package/lib/types/client/locales.d.ts +51 -1
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +79 -29
- package/lib/types/client/pair-api.d.ts +35 -0
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +39 -0
- package/lib/types/client/remote-channel.d.ts +93 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -0
- package/lib/types/client/remote-channel.js +246 -0
- package/lib/types/dsh-home.d.ts +17 -0
- package/lib/types/dsh-home.d.ts.map +1 -0
- package/lib/types/dsh-home.js +34 -0
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +5 -2
- package/lib/types/index.d.ts +19 -12
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +73 -5
- package/lib/types/loopback-proxy.d.ts +28 -0
- package/lib/types/loopback-proxy.d.ts.map +1 -0
- package/lib/types/loopback-proxy.js +112 -0
- package/lib/types/mobile/views/SessionListView.d.ts.map +1 -1
- package/lib/types/mobile/views/SessionListView.js +6 -1
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +6 -2
- package/lib/types/mobile-routes.js +1 -1
- package/lib/types/pairing.d.ts +63 -11
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +137 -20
- package/lib/types/pairing.persist.test.js +60 -5
- package/lib/types/posture.d.ts +75 -0
- package/lib/types/posture.d.ts.map +1 -0
- package/lib/types/posture.js +116 -0
- package/lib/types/remote-api.d.ts +60 -0
- package/lib/types/remote-api.d.ts.map +1 -0
- package/lib/types/remote-api.js +181 -0
- package/lib/types/remote-methods.d.ts +33 -0
- package/lib/types/remote-methods.d.ts.map +1 -0
- package/lib/types/remote-methods.js +54 -0
- package/lib/types/routes.d.ts +4 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +33 -4
- package/package.json +2 -2
- package/src/client/FenceNotice.tsx +43 -0
- package/src/client/RemoteEntry.tsx +19 -9
- package/src/client/RemotePanel.tsx +83 -10
- package/src/client/RemoteSettingsCard.tsx +16 -0
- package/src/client/device-name.ts +31 -0
- package/src/client/index.ts +46 -0
- package/src/client/locales.ts +79 -29
- package/src/client/pair-api.ts +60 -0
- package/src/client/remote-channel.ts +269 -0
- package/src/client/remote.module.css +240 -16
- package/src/client/settings-card.module.css +2 -0
- package/src/dsh-home.ts +36 -0
- package/src/gate.ts +5 -2
- package/src/index.ts +89 -17
- package/src/loopback-proxy.ts +126 -0
- package/src/mobile/views/SessionListView.tsx +6 -1
- package/src/mobile-api.ts +6 -2
- package/src/mobile-routes.ts +1 -1
- package/src/pairing.persist.test.ts +64 -5
- package/src/pairing.ts +167 -20
- package/src/posture.ts +154 -0
- package/src/remote-api.ts +217 -0
- package/src/remote-methods.ts +62 -0
- package/src/routes.ts +33 -4
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 290224ef41cca3f8d8820fc37b985edbfe2d6549
|
|
6
|
+
README.zh.md: 10b01e05a61885c53b48b2e9542a269119674f8a
|
package/README.md
CHANGED
|
@@ -2,24 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
English | [中文](README.zh.md)
|
|
5
|
-
>
|
|
5
|
+
> Remote access for phones and computers + one-click updates: pair a phone to use the current dsh web workspace remotely, or pair a computer browser through the same token to run the full Web GUI from another device; the sidebar checks for a newer dsh-web-ui release after it loads and marks the update button when one is available; clicking it updates the family.
|
|
6
6
|
|
|
7
7
|
This repository is an external plugin package for DeepSeek Harness (DSH):
|
|
8
|
-
|
|
9
|
-
self-update for the dsh-web-ui family. It is a single
|
|
10
|
-
host half owns pairing tokens, device sessions, the
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
pairing-based remote access for the dsh web GUI on phones and computers, plus
|
|
9
|
+
a one-click self-update for the dsh-web-ui family. It is a single
|
|
10
|
+
dual-face package — the host half owns pairing tokens, device sessions, the
|
|
11
|
+
`/api/pair` route family, the gated `/remote` desktop channel, and the
|
|
12
|
+
`/api/update` surface; the browser half renders the sidebar-foot entries (the
|
|
13
|
+
download trigger and an icon-only remote-access entry beside the settings
|
|
14
|
+
button), the pairing panel with a QR code, live device status, an
|
|
15
|
+
authorized-device list, and stop/refresh/copy actions, and the update panel
|
|
16
|
+
that probes and runs the update.
|
|
15
17
|
|
|
16
18
|
## What it does
|
|
17
19
|
|
|
18
|
-
- **Entry**: a phone icon beside the settings button in both the expanded sidebar and narrow rail; its tooltip and accessible label say "
|
|
19
|
-
- **Panel**: "
|
|
20
|
-
subtitle, a "手机扫码连接" card with the status area ("等待手机连接" + status
|
|
21
|
-
badge), a large QR code, the "无法扫码?可以在手机上打开链接" hint, and three
|
|
22
|
-
buttons: 停止 / 刷新二维码 / 复制链接.
|
|
20
|
+
- **Entry**: a phone icon beside the settings button in both the expanded sidebar and narrow rail; its tooltip and accessible label say "Remote access".
|
|
21
|
+
- **Panel**: "Remote access" title, "Pair a phone or another computer to access this workspace remotely" subtitle, a "Pair a device" card with the status area ("Waiting for a device" + status badge), a large QR code, separate phone (`/m/?pair=...`) and computer (`/?pair=...`) links with their own copy buttons, plus Stop / Refresh QR actions and an authorized-device list (a device name inferred from User-Agent, online/offline, last active time, per-device unpair). Credential-bearing device ids and raw User-Agent values are never rendered. Both links share one single-use token, so pairing either device invalidates the other link.
|
|
23
22
|
- **Phone side**: scanning the QR binds the phone with a one-time,
|
|
24
23
|
time-limited token and lands it on the **standalone mobile surface at
|
|
25
24
|
`/m/`** — a thin client purpose-built for a small screen (see
|
|
@@ -29,14 +28,55 @@ actions, and the update panel that probes and runs the update.
|
|
|
29
28
|
opening a fresh QR link there or pasting a fresh desktop-issued link.
|
|
30
29
|
The link carries a `workspace` parameter so the phone lands in the same
|
|
31
30
|
workspace the desktop was looking at.
|
|
31
|
+
- **PC side (remote desktop pairing)**: the same QR link and pairing token
|
|
32
|
+
also pair a PC browser — the phone flow extended to the desktop Web GUI.
|
|
33
|
+
Copy the link from the panel and open it in a browser on another
|
|
34
|
+
computer (LAN URL or tunnel URL); after the accept round trip the full
|
|
35
|
+
Web GUI runs there — workspaces, sessions, chat, model switching — with
|
|
36
|
+
its fenced same-origin traffic on the gated `/remote` channel instead of
|
|
37
|
+
calling loopback-only host routes directly. The
|
|
38
|
+
phone gets `/m/`, the PC gets the desktop UI: one token, one pairing
|
|
39
|
+
flow, two surfaces. An unpaired PC sees a full blocking page with computer
|
|
40
|
+
pairing steps and a retry action, with no workspace data rendered behind it.
|
|
32
41
|
- **Security**: one active one-time token (a refresh invalidates the old
|
|
33
42
|
link; an accepted token cannot be reused; tokens expire). 停止 revokes
|
|
34
43
|
every paired device and the current token — paired devices are cut off on
|
|
35
|
-
their next request.
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
their next request. Pairing is this plugin's access control for its own
|
|
45
|
+
remote channels: `/m/api` for the phone and `/remote` for a desktop
|
|
46
|
+
browser opened at a non-loopback origin. Unpaired `/remote` callers
|
|
47
|
+
are refused before the request body is read. Loopback (127.0.0.1) keeps using
|
|
48
|
+
`/api` directly. The default remote-desktop path does not use
|
|
49
|
+
`--trusted-host`: the connection plugin's `/api` fence stays closed for
|
|
50
|
+
public and LAN hosts, and the paired PC rides `/remote` instead.
|
|
51
|
+
`--trusted-host` is a different SDK usage that trusts that host for `/api`
|
|
52
|
+
itself — pairing does not gate `/api` (no plugin can; the fence is the
|
|
53
|
+
SDK's own seam). The posture probe below reports that stance when `/api`
|
|
54
|
+
is reachable without pairing.
|
|
55
|
+
- **Remote desktop channel**: with `requirePairingForLan` on (default), a
|
|
56
|
+
desktop Web GUI opened at the LAN URL or through the tunnel transparently
|
|
57
|
+
rides `/remote` — the same UI, gated by the same pairing cookie. Browser
|
|
58
|
+
requests under `/api`, `/sidebar`, `/git`, and `/pet`, including the known
|
|
59
|
+
event, terminal, and SSH WebSockets, are re-issued to the local web server
|
|
60
|
+
without forwarding the remote Origin or pairing cookie; the authenticated
|
|
61
|
+
proxy supplies its own same-origin browser marker for sibling route fences. The
|
|
62
|
+
SDK's loopback-only privileged methods (native dialogs, the settings and
|
|
63
|
+
credentials plane) stay unreachable from a paired remote desktop, and the
|
|
64
|
+
`/api/pair/*`, `/api/update/*`, `/api/plugin-manager/*`,
|
|
65
|
+
`/api/dsh-desktop-launcher/*` and `/api/dsh-web-ui-settings/*` control
|
|
66
|
+
endpoints stay loopback-only. Unpaired desktop browsers get a persistent
|
|
67
|
+
full-page block instead of data (the page keys off the `unpaired` error
|
|
68
|
+
code, not every 403).
|
|
69
|
+
- **Posture probe**: the plugin probes the SDK `/api` fence with forged
|
|
70
|
+
Host headers (the public base and every LAN base). A 403 is the default
|
|
71
|
+
stance (fence closed; remote access goes through pairing). Anything other
|
|
72
|
+
than a 403 — `--trusted-host`, or the SDK's LAN auto-trust under
|
|
73
|
+
`--host 0.0.0.0` — is surfaced as a CRITICAL log line and a red banner on
|
|
74
|
+
the pairing panel, so the SDK `/api` trust stance is visible rather than
|
|
75
|
+
assumed. A failed probe round drops the in-flight target key so the same
|
|
76
|
+
origins are retried.
|
|
38
77
|
- **Live status**: the desktop panel mirrors the pairing state in real time
|
|
39
|
-
(waiting → connected → disconnected) over an SSE stream
|
|
78
|
+
(waiting → connected → disconnected) over an SSE stream, including the
|
|
79
|
+
authorized-device list.
|
|
40
80
|
- **Remote update**: the download trigger in the sidebar foot (left of the
|
|
41
81
|
phone icon) performs a silent status probe after the sidebar loads. When a
|
|
42
82
|
newer registry release is available, the trigger shows a dot and the "New
|
|
@@ -160,12 +200,18 @@ mounts both halves.
|
|
|
160
200
|
sheet with persistent toggles for 工具调用 (tool-call disclosures) and
|
|
161
201
|
系统提示词 (injected system messages), and a 上下文 usage chip that
|
|
162
202
|
shows the latest assistant answer's context-fill percentage.
|
|
163
|
-
4. On a secure origin, install the `/m/` page from the browser. The installed app retains the same mobile remote capabilities; when its browser context has no paired-device cookie, paste a fresh desktop-issued pairing link into its pairing screen.
|
|
164
|
-
5.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
203
|
+
4. On a secure origin, install the `/m/` page from the browser. The installed app retains the same mobile remote capabilities; when its browser context has no paired-device cookie, paste a fresh desktop-issued pairing link into its pairing screen.
|
|
204
|
+
5. **To pair a PC instead**: copy the same link and open it in a browser on
|
|
205
|
+
the other computer — the desktop URL form (`/?pair=<token>`, not `/m/`).
|
|
206
|
+
After the accept round trip the full Web GUI runs there over the gated
|
|
207
|
+
`/remote/api` channel; unpaired PCs see the guided blocking page and no
|
|
208
|
+
data. One active token pairs one device; mint a fresh QR for the
|
|
209
|
+
next device.
|
|
210
|
+
6. The desktop badge flips to 已连接 in real time; it falls back to
|
|
211
|
+
offline/断开 when the phone leaves.
|
|
212
|
+
7. 刷新二维码 invalidates the old link and issues a new one. 停止 revokes
|
|
213
|
+
mobile access: paired devices 403 on their next request, including their
|
|
214
|
+
live stream.
|
|
169
215
|
|
|
170
216
|
The mobile surface is fully self-contained in this plugin: the `/m` page
|
|
171
217
|
and its data channel (`/m/api`) are served by the plugin's own routes and
|
|
@@ -193,12 +239,18 @@ over Server-Sent Events on `/m/api/events.mux`. The canonical `/m/` page owns a
|
|
|
193
239
|
browsers that support `field-sizing: content`, the input grows with the
|
|
194
240
|
draft up to its 120px cap in either mode.
|
|
195
241
|
- The `/m/` worker uses network-first static-shell fallback and waits for current pages to close before an updated worker activates. It bypasses `/m/api`, `/api`, SSE, and every write request.
|
|
196
|
-
- Installing this plugin
|
|
197
|
-
(see `requirePairingForLan` in `src/index.ts`).
|
|
198
|
-
via the LAN URL must pair like any
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
242
|
+
- Installing this plugin routes fenced non-loopback desktop traffic onto the
|
|
243
|
+
gated `/remote` channel (see `requirePairingForLan` in `src/index.ts`).
|
|
244
|
+
A desktop browser opened via the LAN URL or the tunnel must pair like any
|
|
245
|
+
remote device — the unpaired state shows a persistent blocking page instead of
|
|
246
|
+
data; loopback (127.0.0.1) is unaffected and keeps `/api`. Set
|
|
247
|
+
`requirePairingForLan: false` in the profile patch to keep the desktop on
|
|
248
|
+
plain `/api` (only useful with the open-LAN stance) while keeping
|
|
249
|
+
tokens/status/revocation. Note the underlying `/api` route itself is the
|
|
250
|
+
SDK's: on a `--host 0.0.0.0` bind the SDK auto-trusts LAN literals, so a
|
|
251
|
+
LAN client bypassing the UI can still reach `/api` directly — the posture
|
|
252
|
+
probe reports that stance on the panel.
|
|
253
|
+
- Sibling host routes outside `/api` (`/pet/*`, `/git/*`, the right-panel `/aionui-panel/*`
|
|
202
254
|
family) can consult this plugin's `remoteWebUiPairing` service: a live
|
|
203
255
|
paired-device cookie is an allow path, `stop()` still cuts them off, and
|
|
204
256
|
the service is absent when this plugin is not installed.
|
|
@@ -229,34 +281,38 @@ no install, account, or domain needed — and wires everything itself:
|
|
|
229
281
|
The QR stays LAN-only until the tunnel reports its URL, and a tunnel
|
|
230
282
|
restart mints a NEW hostname — the plugin clears the old link and mints a
|
|
231
283
|
fresh one, so users never touch configuration. Note that a quick tunnel is
|
|
232
|
-
public: anyone with the URL can load the static page; the
|
|
233
|
-
the real fence
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
284
|
+
public: anyone with the URL can load the static page; the paired-device
|
|
285
|
+
cookie gates are the real fence (the phone's `/m/api` with its method
|
|
286
|
+
allowlist, the remote desktop's `/remote/api`). The connection plugin's
|
|
287
|
+
`/api` fence refuses the tunneled host outright — the tunneled Host never
|
|
288
|
+
enters the connection trust fence — so **no profile or harness
|
|
289
|
+
customization is required for the auto tunnel to work**. `--trusted-host`
|
|
290
|
+
is not part of this path: a paired PC uses `/remote/api`.
|
|
237
291
|
|
|
238
292
|
### Manual tunnels (bring your own)
|
|
239
293
|
|
|
240
294
|
The QR link is normally a LAN URL, so a phone outside the house cannot use
|
|
241
295
|
it. Point a tunnel at the dsh web port and tell the plugin its public
|
|
242
|
-
address — the QR is then built from the tunnel URL
|
|
243
|
-
|
|
296
|
+
address — the QR is then built from the tunnel URL. One knob is involved;
|
|
297
|
+
`--trusted-host` is a separate SDK flag, not part of this pairing path:
|
|
244
298
|
|
|
245
299
|
- **`publicBaseUrl`** (plugin config, in the profile patch or the settings
|
|
246
300
|
card): the public origin, e.g. `https://foo.trycloudflare.com`. The QR
|
|
247
301
|
link is built from it, and `accept`/`heartbeat`/`status` accept its host.
|
|
302
|
+
A paired desktop browser rides the gated `/remote/api` channel through
|
|
303
|
+
the same origin, so the desktop Web GUI works from anywhere too.
|
|
248
304
|
Malformed values are ignored with a warning (LAN-only behavior kept).
|
|
249
305
|
Unpaired callers of `status` see only the pairing-relevant fields (phase /
|
|
250
306
|
LAN bases); token expiry, device roster, and the tunnel URL require a live
|
|
251
307
|
device cookie. The accept rate limit partitions its buckets by the
|
|
252
308
|
client-visible `X-Forwarded-For` hop so one internet client cannot
|
|
253
309
|
exhaust the shared bucket behind the tunnel.
|
|
254
|
-
- **`--trusted-host <
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
310
|
+
- **`--trusted-host <tunnel-domain>`** (optional dsh web flag, not recommended
|
|
311
|
+
for this plugin on security grounds): prefer device pairing so phones use
|
|
312
|
+
`/m/api` and PCs use `/remote/api`. This flag makes the SDK trust the host
|
|
313
|
+
for `/api` itself, so unpaired callers can reach the ungated host API;
|
|
314
|
+
pairing still gates `/m/api` and `/remote/api`, but not `/api`. If it is
|
|
315
|
+
set, the posture probe reports the open `/api` fence.
|
|
260
316
|
|
|
261
317
|
### Cloudflare Tunnel (quick tunnel — no account, no domain)
|
|
262
318
|
|
|
@@ -269,21 +325,25 @@ releases). Then:
|
|
|
269
325
|
cloudflared tunnel --url http://127.0.0.1:3080
|
|
270
326
|
# prints something like: https://xxxx-xxxx-xxxx.trycloudflare.com
|
|
271
327
|
|
|
272
|
-
# 2. Start dsh web
|
|
273
|
-
#
|
|
274
|
-
|
|
328
|
+
# 2. Start dsh web as usual. Do not add --trusted-host for the tunnel
|
|
329
|
+
# domain unless you intentionally want the SDK to trust that host for
|
|
330
|
+
# /api (pairing does not cover /api; remote desktop uses /remote/api).
|
|
331
|
+
# Use --host 0.0.0.0 only when LAN access should stay available:
|
|
332
|
+
dsh web
|
|
275
333
|
```
|
|
276
334
|
|
|
277
335
|
Then set `publicBaseUrl: https://xxxx-xxxx-xxxx.trycloudflare.com` in the
|
|
278
336
|
profile patch (or the plugin settings card — it hot-reloads). Open the
|
|
279
337
|
phone icon at `http://127.0.0.1`, scan the QR from anywhere: the phone
|
|
280
|
-
binds, reloads into the mobile surface, and heartbeats keep it online.
|
|
338
|
+
binds, reloads into the mobile surface, and heartbeats keep it online. A
|
|
339
|
+
desktop browser opened at the same tunnel URL pairs the same way and then
|
|
340
|
+
runs the full Web GUI over the gated `/remote/api` channel.
|
|
281
341
|
|
|
282
342
|
Notes:
|
|
283
343
|
|
|
284
344
|
- Quick tunnels are free and need no login, but the hostname is random per
|
|
285
|
-
run: every `cloudflared` restart changes it, so update
|
|
286
|
-
|
|
345
|
+
run: every `cloudflared` restart changes it, so update `publicBaseUrl`
|
|
346
|
+
with it. Cloudflare documents no uptime guarantees;
|
|
287
347
|
in-flight-request concurrency is capped (HTTP 429 past it), and **Quick
|
|
288
348
|
Tunnels do not forward Server-Sent Events**. `Tailscale Serve` (and
|
|
289
349
|
`tailscale serve` on a single port) behaves the same way. SSE is how the
|
|
@@ -296,14 +356,18 @@ Notes:
|
|
|
296
356
|
For true real-time push, point the QR at a tunnel that forwards SSE — a
|
|
297
357
|
Cloudflare **named tunnel** (domain hosted on Cloudflare, see below), or a
|
|
298
358
|
plain TCP port forward (LAN address, the `tailscale up` virtual-interface
|
|
299
|
-
address, or a manual `ssh -L` / cloudflared TCP tunnel to the port).
|
|
359
|
+
address, or a manual `ssh -L` / cloudflared TCP tunnel to the port). A
|
|
360
|
+
remote desktop browser is unaffected: its event streams ride WebSocket
|
|
361
|
+
upgrades, which quick tunnels forward.
|
|
300
362
|
- A quick tunnel is public: anyone with the URL can load the static page.
|
|
301
|
-
The
|
|
302
|
-
`/api` call —
|
|
363
|
+
The paired-device cookie gates are the real fence (unpaired devices get
|
|
364
|
+
403 on every `/m/api` and `/remote/api` call) — keep `requirePairingForLan`
|
|
365
|
+
on. `--trusted-host` is not needed for this path and opens SDK `/api`
|
|
366
|
+
outside pairing.
|
|
303
367
|
- For a stable hostname, create a named tunnel from the Cloudflare
|
|
304
|
-
dashboard (Networking → Tunnels; the domain must be hosted on
|
|
305
|
-
|
|
306
|
-
|
|
368
|
+
dashboard (Networking → Tunnels; the domain must be hosted on Cloudflare)
|
|
369
|
+
and use its hostname as `publicBaseUrl` (only). Reachability from
|
|
370
|
+
mainland China is not guaranteed by Cloudflare; verify locally.
|
|
307
371
|
- Tailscale is an alternative for personal use that needs no plugin
|
|
308
372
|
changes at all: its virtual-interface address (`100.x.y.z`) shows up in
|
|
309
373
|
the QR's address picker automatically, and a phone on the same tailnet
|
|
@@ -341,9 +405,12 @@ pnpm run build
|
|
|
341
405
|
This plugin rides three harness seams that may not exist in older checkouts:
|
|
342
406
|
|
|
343
407
|
- **`api/gate` waterfall** (packages/client/connection): the /api route and
|
|
344
|
-
event WebSocket upgrades emit this event after the trust
|
|
345
|
-
can enforce application-level access control.
|
|
346
|
-
|
|
408
|
+
event WebSocket upgrades are *designed* to emit this event after the trust
|
|
409
|
+
fence so plugins can enforce application-level access control. Current
|
|
410
|
+
published SDK lines do NOT emit it (the listener stays mounted for
|
|
411
|
+
deployments that gain the seam). Pairing for the phone and the PC therefore
|
|
412
|
+
lives on this plugin's own routes (`/m/api`, `/remote/api`); the posture
|
|
413
|
+
probe reports the SDK `/api` fence stance rather than assuming it.
|
|
347
414
|
- **`sidebar.remote` foot seat** (packages/client/ui-sidebar): the sidebar
|
|
348
415
|
declares and renders the seat the phone entry occupies.
|
|
349
416
|
- **LAN runtime connection fixes** (host-apiproxy `mintRpcId` fallback for
|
|
@@ -380,32 +447,36 @@ Repeat this after any change to the wire contract or the connection loop:
|
|
|
380
447
|
(reconnect loops retry until a fresh QR re-pairs).
|
|
381
448
|
|
|
382
449
|
The public path is the same round trip through a tunnel (see "Remote access
|
|
383
|
-
over the internet"): loopback mint → phone opens the public QR URL →
|
|
384
|
-
accept →
|
|
385
|
-
`--trusted-host`
|
|
386
|
-
|
|
450
|
+
over the internet"): loopback mint → phone or PC opens the public QR URL →
|
|
451
|
+
accept → UI. Only `publicBaseUrl` (plugin config) names the tunneled host;
|
|
452
|
+
`--trusted-host` is not part of this pairing flow. The desktop panel still
|
|
453
|
+
opens at `http://127.0.0.1`.
|
|
387
454
|
|
|
388
455
|
## Known Limitations and Deferred Work
|
|
389
456
|
|
|
390
457
|
- **Revocation is per-request**: a paired phone whose request is already in
|
|
391
458
|
flight when 停止 lands completes that request; the next one 403s.
|
|
392
|
-
- **
|
|
393
|
-
|
|
394
|
-
(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
immediately and the revocation is persisted.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
459
|
+
- **Paired device sessions persist by default**: device sessions (not the
|
|
460
|
+
one-time QR token) are written to `$DSH_HOME/remote-web-ui-devices.json`
|
|
461
|
+
(0600, temp file + atomic rename). A paired cookie still works after a
|
|
462
|
+
`dsh web` restart. Refreshing the QR still mints a new token; restarting
|
|
463
|
+
does not restore the current QR. An explicit 停止 (stop) or per-device
|
|
464
|
+
取消配对 still revokes immediately and the revocation is persisted.
|
|
465
|
+
Sessions idle for `idleExpireMs` (default 7 days, no heartbeat or gated
|
|
466
|
+
request) are deleted and must pair again. Device ids are session
|
|
467
|
+
credentials (the gate authorizes requests by the device id in the cookie).
|
|
468
|
+
Override `devicesFile` with another absolute path when needed. Changing
|
|
469
|
+
`cookieName` invalidates existing devices (expected).
|
|
470
|
+
- **Device roster is loopback-only**: the pairing panel lists authorized
|
|
471
|
+
devices using a short name inferred from User-Agent (for example,
|
|
472
|
+
`Windows · Chrome`), online/offline state, and last active time, and can
|
|
473
|
+
unpair one at a time. Credential-bearing device ids and raw User-Agent
|
|
474
|
+
values are not rendered. `/api/pair/status` never returns the device id
|
|
475
|
+
list, even to a paired phone.
|
|
405
476
|
- **Quick-tunnel hostnames change per run**: a `trycloudflare.com` URL is
|
|
406
|
-
random on every `cloudflared` start, so
|
|
407
|
-
|
|
408
|
-
|
|
477
|
+
random on every `cloudflared` start, so `publicBaseUrl` must be updated
|
|
478
|
+
whenever the tunnel restarts. A named tunnel (fixed hostname) avoids the
|
|
479
|
+
churn and is required for a durable installed-PWA address.
|
|
409
480
|
- **PWA is online-first**: only the static shell and offline page are cached. The running DSH host remains required for all mobile remote capabilities; no session, API response, or command is available offline.
|
|
410
481
|
- **Dev HMR**: `dsh web --dev` polls every roster bundle by path, so
|
|
411
482
|
rebuilding this package (its own `tsdown --watch`) hot-reloads the client
|
package/README.zh.md
CHANGED
|
@@ -2,17 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 中文
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> 手机与电脑远程访问 + 一键远程更新:扫码配对后用手机远程使用当前 dsh web 工作区,同一枚令牌也可配对电脑浏览器、在其他设备上运行完整 Web GUI;侧边栏加载后静默检查 dsh-web-ui 全家桶新版本,发现时标记更新按钮;点击按钮后自动更新全家桶。
|
|
6
6
|
|
|
7
|
-
本仓库是 DeepSeek Harness(DSH)的外部插件包:为 dsh web GUI
|
|
7
|
+
本仓库是 DeepSeek Harness(DSH)的外部插件包:为 dsh web GUI 提供基于配对的手机与电脑远程访问,外加 dsh-web-ui 全家桶的一键自更新。它是单一双半区包——host 半区持有配对令牌、设备会话、`/api/pair` 路由族、门控的 `/remote` 桌面通道与 `/api/update` 面板;浏览器半区渲染侧边栏底部入口(下载触发按钮与设置按钮旁的手机图标入口)、带二维码的配对面板、实时设备状态、已授权设备列表,以及停止/刷新/复制操作,还渲染探测并执行更新的更新面板。
|
|
8
8
|
|
|
9
9
|
## 功能
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
11
|
+
- **入口**:设置按钮旁始终显示手机图标;悬停时显示“远程访问”提示。
|
|
12
|
+
- **面板**:「远程访问」标题、「通过手机或另一台电脑配对,远程访问当前工作区」副标题、「设备配对」卡片(含状态区「等待设备连接」+ 状态徽标)、大号二维码、各带复制按钮的手机链接(`/m/?pair=...`)与电脑链接(`/?pair=...`)、停止 / 刷新二维码操作,以及已授权设备列表(根据 User-Agent 推断的设备名称、在线/离线、最近活动时间、单台取消配对)。界面不渲染作为会话凭据的设备 id 或原始 User-Agent。两条链接共用一枚一次性令牌,任意设备配对成功后另一条立即失效。
|
|
13
13
|
- **手机侧**:扫码将手机与一次性、限时令牌绑定,并落地到 **`/m/` 独立移动端界面**——一款专为小屏设计的轻客户端(见[截图](#截图)),而不是把桌面 UI 塞进手机。该页面可安装为 PWA;每个已安装应用使用自己的已配对设备 cookie,存储隔离的移动端 Web App 需要在该上下文打开新的二维码链接,或粘贴桌面端新复制的链接完成配对。链接携带 `workspace` 参数,手机落地到桌面正在查看的同一工作区。
|
|
14
|
-
-
|
|
15
|
-
-
|
|
14
|
+
- **PC 侧(远程桌面配对)**:同一份二维码链接和配对令牌也能配对 PC 浏览器——把手机侧配对流扩展到桌面 Web GUI。从面板复制链接,在另一台电脑的浏览器打开(局域网 URL 或隧道 URL 均可);接受往返后,完整 Web GUI 在那台设备上运行——工作区、会话、聊天、切模型——受围栏保护的同源流量走门控的 `/remote` 通道,而不是直接调用仅 loopback 的 host 路由。手机进 `/m/`,PC 进桌面 UI:一枚令牌、一套配对流、两种界面。未配对的 PC 只看到不渲染底层数据的完整阻断页,其中提供电脑配对步骤与「重新检测」操作。
|
|
15
|
+
- **安全**:一个有效的一次性令牌(刷新会使旧链接失效;已接受的令牌不可复用;令牌会过期)。停止会撤销每一台已配对设备与当前令牌——已配对设备在下一次请求时被切断。配对是本插件对自己远程通道的访问控制:手机走 `/m/api`,非 loopback origin 打开的桌面浏览器走 `/remote`。未配对调用方在读取请求体之前就被 `/remote` 拒绝。loopback(127.0.0.1)继续直接使用 `/api`。默认的远程桌面路径不使用 `--trusted-host`:连接插件的 `/api` 围栏对公网和局域网主机保持关闭,已配对 PC 改走 `/remote`。`--trusted-host` 是 SDK 的另一种用法,会让该主机被信任访问 `/api` 本身——配对不门控 `/api`(任何插件都无法做到;围栏是 SDK 自己的接缝)。当 `/api` 无需配对即可到达时,下方姿态探测会报告该姿态。
|
|
16
|
+
- **远程桌面通道**:`requirePairingForLan` 开启(默认)时,经局域网 URL 或隧道打开的桌面 Web GUI 透明地改走 `/remote`——同一套 UI,同一枚配对 cookie 门控。浏览器中 `/api`、`/sidebar`、`/git`、`/pet` 下的请求,以及已知的事件、终端与 SSH WebSocket,会在不转发远程 Origin 或配对 cookie 的前提下重新发给本机 Web Server;认证反代会为兄弟路由围栏提供自己生成的同源浏览器标记。SDK 的 loopback 专属特权方法(原生对话框、settings 与 credentials 面)对已配对远程桌面保持不可达,`/api/pair/*`、`/api/update/*`、`/api/plugin-manager/*`、`/api/dsh-desktop-launcher/*` 与 `/api/dsh-web-ui-settings/*` 控制端点保持仅 loopback。未配对的桌面浏览器看到持久的全页阻断界面而不是数据(阻断页按错误码 `unpaired` 触发,不是任意 403)。
|
|
17
|
+
- **姿态探测**:插件用伪造 Host 头(公网 base 与每个 LAN base)探测 SDK 的 `/api` 围栏。403 是默认姿态(围栏关闭,远程访问走配对)。任何非 403 结果——`--trusted-host`,或 `--host 0.0.0.0` 下 SDK 的局域网自动信任——都会以 CRITICAL 日志与配对面板红色横幅呈现,因此 SDK 的 `/api` 信任姿态是被看见的,而不是被假设的。探测失败会丢掉进行中的目标缓存,相同 origin 会在下次触发时重试。
|
|
18
|
+
- **实时状态**:桌面面板经 SSE 流实时镜像配对状态(等待 → 已连接 → 已断开),并展示已授权设备名单。
|
|
16
19
|
- **远程更新**:侧边栏加载后,底部的下载触发按钮(手机图标左侧)会静默探测 npm registry 上已安装的 `@linxin666/dsh-*` 全家桶版本;发现可自动更新的新版本时,按钮显示圆点并提供“发现新版本,检查更新”提示。点击按钮打开更新面板;未安装聚合包时,检查和更新覆盖 profile 中 registry 管理的全部 `@linxin666/*` 直接依赖,本地 link / file 开发依赖会被跳过。当存在较新版本时,面板展示版本对比并等待确认——点击「开始更新」后执行更新(在所属 dsh profile 内 `pnpm update --latest`;pnpm 缺失时依次回退 `corepack pnpm`、`npx --yes pnpm`,Windows 上经 `cmd.exe` 执行以解析 npm 安装的 `.cmd` shim;由仅 loopback 的 `/api/update/status` + `/api/update/run` 端点驱动)并请求重启 dsh web 以生效。pnpm 绿色退出后还会对照 registry 复核已装版本:绿色退出但版本纹丝不动(例如 pnpm 的 `minimumReleaseAge` 门禁静默跳过同日发布的新版本)会报告为「未更新成功」并附配置指引,而不是误报成功。锚点自身是本地 link 安装(开发模式)时,只报告 npm 状态而不更新。
|
|
17
20
|
|
|
18
21
|
## 截图
|
|
@@ -63,9 +66,10 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
|
|
|
63
66
|
- **亮色优先主题**:界面默认亮色调色板;每个页头内的日/月切换翻到暗色调色板,选择跨访问持久(localStorage),
|
|
64
67
|
- 消息按桌面折叠纪律渲染:推理隐藏在被折叠的 深度思考 揭示下面,工具调用隐藏在被折叠的 工具 行下面(点击查看每个调用的参数),超长回答藏在显式 展开全文 切换下面,每行带时间,并且 assistant 回复按 GFM Markdown 渲染(标题 / 加粗 / 斜体 / 行内码 / 代码块 / 列表 / 表格 / 引用 / 链接 / 图片;零依赖自写渲染器,先转义再白名单协议,移动端 bundle 体积几乎不变;KaTeX 公式暂不支持,后续单独评估),用户消息保持纯文本,
|
|
65
68
|
- 输入栏工具条带 **模型** 选择器(provider 分组目录 + 每模型 思考强度 effort 区)与 **权限** 选择器(权限预设;完全权限 需要显式确认步骤)。两者都走 host 自己的 `session.models` / `session.selectModel` RPC 与 `/permission` 命令——手机改的与桌面改的是同一个会话设置——外加 **显示** 弹层(含 工具调用 与 系统提示词 两个持久开关)和一个 上下文 用量 chip(显示最近一次助手回答的上下文占用百分比)。
|
|
66
|
-
4. 在安全 origin 上从浏览器安装 `/m/` 页面。已安装应用保留相同的移动端远程控制能力;其浏览器上下文没有已配对设备 cookie 时,把桌面端新复制的配对链接粘贴进配对页面。
|
|
67
|
-
5.
|
|
68
|
-
6.
|
|
69
|
+
4. 在安全 origin 上从浏览器安装 `/m/` 页面。已安装应用保留相同的移动端远程控制能力;其浏览器上下文没有已配对设备 cookie 时,把桌面端新复制的配对链接粘贴进配对页面。
|
|
70
|
+
5. **改为配对 PC**:复制同一份链接,在另一台电脑的浏览器打开——用桌面 URL 形态(`/?pair=<token>`,不是 `/m/`)。接受往返后,完整 Web GUI 在那台设备上经门控的 `/remote/api` 通道运行;未配对的 PC 只看到带操作指引的阻断页且无数据。一枚有效令牌配对一台设备;给下一台设备配对请刷新出新的二维码。
|
|
71
|
+
6. 桌面徽标实时翻到 已连接;手机离开时回落到离线/断开。
|
|
72
|
+
7. 刷新二维码 使旧链接失效并铸一枚新的。停止 撤销移动端访问:已配对设备下一次请求 403,包括其实时流。
|
|
69
73
|
|
|
70
74
|
该移动端界面完全自包含在本插件内:`/m/` 页面及其数据通道(`/m/api`)由插件自己的路由伺服,**无需任何 harness 源码改动**——手机的 RPC 调用走插件的 `/m/api` 代理(它委托给 host 的 ApiProxy 服务并自己分页 `session.list`),因此被隧道化的 Host 永远不必进入连接插件的信任围栏。手机受其已配对设备 cookie 与显式方法白名单门控(settings/credentials/host-action 域手机永远不可达;模型读写限制于建议性的 `session.models` / `session.selectModel` 对,Agent 预设访问限制于只读 `agentPreset.list`,创建限制于 `session.create`(工作区 id 加清单中的可选 id——手机绝不自命名工作目录),权限选择器只通过已放行的 `session.prompt` 发送模式无关的 `/permission` 命令);实时流在 `/m/api/events.mux` 上经 Server-Sent Events 送达。规范的 `/m/` 页面拥有同 scope 的 manifest 与 Service Worker;其缓存仅包含静态壳和离线页,绝不包含移动端 API 响应、会话数据或命令。
|
|
71
75
|
|
|
@@ -73,8 +77,8 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
|
|
|
73
77
|
|
|
74
78
|
- 移动端输入框默认 Enter 发送(Shift+Enter 换行)。在插件设置卡片(或 profile patch)把 `mobileEnterToSend` 设为 false 后,普通 Enter 改为插入换行,只有「发送」按钮会发送;手机打开聊天时经自己的 `/m/api` 偏好方法读取该开关。在支持 `field-sizing: content` 的浏览器上,输入框随草稿自动增高,最高 120px 封顶(两种模式一致)。
|
|
75
79
|
- `/m/` Worker 对静态壳使用 network-first 回退,并等待当前页面关闭后才激活更新。它旁路 `/m/api`、`/api`、SSE 与所有写请求。
|
|
76
|
-
-
|
|
77
|
-
- `/api` 之外的兄弟 host
|
|
80
|
+
- 安装本插件后,非 loopback 桌面中受围栏保护的流量会改走门控的 `/remote` 通道(见 `src/index.ts` 的 `requirePairingForLan`)。经局域网 URL 或隧道打开的桌面浏览器必须像任何远程设备一样配对——未配对状态显示完整阻断页而不是数据;loopback(127.0.0.1)不受影响,继续使用原始路径。把 profile patch 里 `requirePairingForLan` 设为 false 可让桌面继续走普通 `/api`(仅在开放局域网姿态下有意义),同时保留令牌/状态/撤销。注意 `/api` 路由本体属于 SDK:`--host 0.0.0.0` 绑定时 SDK 自动信任局域网字面量,绕过 UI 的局域网客户端仍能直接访问 `/api`——姿态探测会在面板上报告该姿态。
|
|
81
|
+
- `/api` 之外的兄弟 host 路由(`/pet/*`、`/git/*`、右侧面板的 `/aionui-panel/*`)可查询本插件的 `remoteWebUiPairing` 服务:有效的已配对设备 cookie 是放行路径,`stop()` 仍会切断它们;未安装本插件时该服务不存在。
|
|
78
82
|
- 二维码链接基于机器的非内部 IPv4 字面量构建;多宿主主机(Wi-Fi + 有线,或代理/VPN 虚拟适配器)会显示单选器供你发布手机实际可达的网络。第一个字面量是默认值。设 `publicBaseUrl` 后,单选器在顶部额外加一项 公网地址——默认二维码改用公网 base,选中局域网字面量会重新铸一枚网内链接。
|
|
79
83
|
- 配置的 `publicBaseUrl` 本身满足可达绑定需求:`dsh web` 绑定 `127.0.0.1`(不带 `--host 0.0.0.0`)仍能经隧道铸出可用的公网二维码链接。
|
|
80
84
|
|
|
@@ -86,14 +90,14 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
|
|
|
86
90
|
|
|
87
91
|
- 铸出的 `https://xxx.trycloudflare.com` URL 成为二维码 base,因此任意地点的手机都能配对。面板显示隧道状态(starting / running / failed 带原因),崩溃按退避自动重启。
|
|
88
92
|
|
|
89
|
-
二维码在隧道报告其 URL 前保持仅局域网,且隧道重启会铸一枚**新的** hostname——插件清除旧链接并铸一枚新的,用户永远不必触碰配置。注意 quick tunnel 是公网的:任何拿到 URL
|
|
93
|
+
二维码在隧道报告其 URL 前保持仅局域网,且隧道重启会铸一枚**新的** hostname——插件清除旧链接并铸一枚新的,用户永远不必触碰配置。注意 quick tunnel 是公网的:任何拿到 URL 的人都能加载静态页;已配对设备 cookie 门才是真正的围栏(手机的 `/m/api` 带方法白名单、远程桌面的 `/remote/api`)。连接插件的 `/api` 围栏则直接拒绝隧道化主机——被隧道化的 Host 永远不进入连接信任围栏——因此 **auto tunnel 工作无需任何 profile 或 harness 定制**。`--trusted-host` 不属于这条路径:已配对 PC 走 `/remote/api`。
|
|
90
94
|
|
|
91
95
|
### 手动隧道(自带)
|
|
92
96
|
|
|
93
|
-
二维码链接通常是局域网 URL,所以家外的手机无法使用。把隧道指向 dsh web 端口,并告知插件其公网地址——二维码随后由隧道 URL
|
|
97
|
+
二维码链接通常是局域网 URL,所以家外的手机无法使用。把隧道指向 dsh web 端口,并告知插件其公网地址——二维码随后由隧道 URL 构建。涉及一个钮;`--trusted-host` 是独立的 SDK flag,不属于这条配对流:
|
|
94
98
|
|
|
95
|
-
- **`publicBaseUrl`**(插件配置,在 profile patch 或设置卡片里):公网 origin,如 `https://foo.trycloudflare.com`。二维码链接由它构建,`accept`/`heartbeat`/`status`
|
|
96
|
-
- **`--trusted-host
|
|
99
|
+
- **`publicBaseUrl`**(插件配置,在 profile patch 或设置卡片里):公网 origin,如 `https://foo.trycloudflare.com`。二维码链接由它构建,`accept`/`heartbeat`/`status` 接受它的主机。已配对的桌面浏览器经同一 origin 走门控的 `/remote/api` 通道,因此桌面 Web GUI 也能从任意地点使用。畸形值被忽略并告警(保持仅局域网行为)。未配对调用方的 `status` 只看到配对相关字段(phase / 局域网地址);token 过期时间、设备列表与隧道 URL 需要有效设备 cookie。accept 限速按客户端可见的 `X-Forwarded-For` 跳点分桶,避免隧道背后的单个来源耗尽共享桶。
|
|
100
|
+
- **`--trusted-host <隧道域名>`**(可选的 dsh web flag,安全上不建议用于本插件):优先使用设备配对,让手机走 `/m/api`、PC 走 `/remote/api`。该 flag 会让主机被 SDK 信任访问 `/api` 本身,因此未配对调用方可以到达无门控的 host API;配对仍门控 `/m/api` 与 `/remote/api`,但不门控 `/api`。若仍设置,姿态探测会报告敞开的 `/api` 围栏。
|
|
97
101
|
|
|
98
102
|
### Cloudflare 隧道(quick tunnel——无账号、无域名)
|
|
99
103
|
|
|
@@ -104,17 +108,19 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
|
|
|
104
108
|
cloudflared tunnel --url http://127.0.0.1:3080
|
|
105
109
|
# 打印类似:https://xxxx-xxxx-xxxx.trycloudflare.com
|
|
106
110
|
|
|
107
|
-
# 2.
|
|
108
|
-
|
|
111
|
+
# 2. 照常启动 dsh web。不必为隧道域名加 --trusted-host,除非你有意让 SDK
|
|
112
|
+
# 信任该主机访问 /api(配对不管 /api;远程桌面走 /remote/api)。
|
|
113
|
+
# 仅需保留局域网访问时才加 --host 0.0.0.0:
|
|
114
|
+
dsh web
|
|
109
115
|
```
|
|
110
116
|
|
|
111
|
-
然后在 profile patch(或插件设置卡片——它会热重载)里设 `publicBaseUrl: https://xxxx-xxxx-xxxx.trycloudflare.com`。在 `http://127.0.0.1`
|
|
117
|
+
然后在 profile patch(或插件设置卡片——它会热重载)里设 `publicBaseUrl: https://xxxx-xxxx-xxxx.trycloudflare.com`。在 `http://127.0.0.1` 打开手机图标,从任意处扫码:手机绑定、重载进移动端界面,心跳保持其在线。在同一隧道 URL 打开的桌面浏览器以同样方式配对,随后完整 Web GUI 经门控的 `/remote/api` 通道运行。
|
|
112
118
|
|
|
113
119
|
说明:
|
|
114
120
|
|
|
115
|
-
- Quick tunnel 免费无需登录,但 hostname 每次运行随机:每次 `cloudflared` 重启都变,所以
|
|
116
|
-
- Quick tunnel 是公网的:任何拿到 URL
|
|
117
|
-
- 稳定 hostname 可从 Cloudflare 控制台创建 named tunnel(Networking → Tunnels;域名必须托管在 Cloudflare
|
|
121
|
+
- Quick tunnel 免费无需登录,但 hostname 每次运行随机:每次 `cloudflared` 重启都变,所以 `publicBaseUrl` 要随之更新。Cloudflare 不保证 uptime;在途请求并发受限(超过返回 HTTP 429),且 **Quick Tunnels 不转发 Server-Sent Events**。`Tailscale Serve`(以及单端口的 `tailscale serve`)行为相同。SSE 是手机**实时接收消息**的方式,所以在 quick tunnel 或 Tailscale Serve 上移动端聊天回退到轮询:手机仍收发消息(其余都走普通 HTTP,可转发),只是新消息可能晚几秒而非即时。SSE 通道一旦静默,插件按短间隔轮询 `session.history`,SSE 恢复后立即恢复流式。要真正实时推送,把二维码指向能转发 SSE 的隧道——Cloudflare **named tunnel**(域名托管在 Cloudflare,见下),或普通 TCP 端口转发(局域网地址、`tailscale up` 虚拟接口地址,或手动 `ssh -L` / 指向端口的 cloudflared TCP 隧道)。远程桌面浏览器不受影响:其事件流走 WebSocket upgrade,quick tunnel 可转发。
|
|
122
|
+
- Quick tunnel 是公网的:任何拿到 URL 的人都能加载静态页。已配对设备 cookie 门才是真正的围栏——未配对设备每个 `/m/api` 与 `/remote/api` 调用都 403——所以请保持 `requirePairingForLan` 开启。`--trusted-host` 这条路径用不到,且会在配对之外打开 SDK 的 `/api`。
|
|
123
|
+
- 稳定 hostname 可从 Cloudflare 控制台创建 named tunnel(Networking → Tunnels;域名必须托管在 Cloudflare),其 hostname 只用作 `publicBaseUrl`。Cloudflare 不保证中国大陆可达性;请本地验证。
|
|
118
124
|
- Tailscale 是无需任何插件改动的个人替代:其虚拟接口地址(`100.x.y.z`)自动出现在二维码的地址选择器中,同一 tailnet 的手机像局域网主机一样到达它。
|
|
119
125
|
|
|
120
126
|
## 开发
|
|
@@ -144,7 +150,7 @@ pnpm run build
|
|
|
144
150
|
|
|
145
151
|
本插件依托三个在较老 checkout 里可能不存在的 harness seam:
|
|
146
152
|
|
|
147
|
-
- **`api/gate` 瀑布**(packages/client/connection):/api 路由与事件 WebSocket
|
|
153
|
+
- **`api/gate` 瀑布**(packages/client/connection):/api 路由与事件 WebSocket 升级被*设计*为在信任围栏后发出该事件,插件可据此实施应用层访问控制。当前已发布的 SDK 线**并不**发出它(监听器为将来具备该 seam 的部署保持挂载)。因此手机与 PC 的配对都落在本插件自己的路由上(`/m/api`、`/remote/api`);姿态探测报告 SDK 的 `/api` 围栏姿态,而不是假设它。
|
|
148
154
|
- **`sidebar.remote` 底部座位**(packages/client/ui-sidebar):侧边栏声明并渲染手机入口占据的座位。
|
|
149
155
|
- **局域网运行时连接修复**(host-apiproxy 为不安全上下文 origin 的 `mintRpcId` 回退;20260808 分支在 mux 流之后打开 host 流的连接循环):没有它们,浏览器 runtime 根本无法在纯 HTTP 局域网页上运行(本特性的移动端侧)。
|
|
150
156
|
|
|
@@ -160,14 +166,14 @@ pnpm run build
|
|
|
160
166
|
4. 桌面徽标实时翻到 已连接;局域网 origin 的桌面页则显示 配对面板仅限本机使用 横幅且不打开状态流。
|
|
161
167
|
5. 桌面 停止 切断手机:其下一个 `/api` 请求 403(重连循环重试直到新二维码重新配对)。
|
|
162
168
|
|
|
163
|
-
公网路径是经隧道的同一往返(见「通过互联网远程访问」):loopback mint →
|
|
169
|
+
公网路径是经隧道的同一往返(见「通过互联网远程访问」):loopback mint → 手机或 PC 打开公网二维码 URL → accept → UI。只有 `publicBaseUrl`(插件配置)命名隧道主机;`--trusted-host` 不属于这条配对流。桌面面板仍在 `http://127.0.0.1` 打开。
|
|
164
170
|
|
|
165
171
|
## 已知限制与待办
|
|
166
172
|
|
|
167
173
|
- **撤销是逐请求的**:已配对手机请求已在 停止 落地时在途,完成该请求;下一个 403。
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
- **Quick-tunnel hostname 每次运行变化**:`trycloudflare.com` URL 每次 `cloudflared` 启动随机,所以隧道重启时
|
|
174
|
+
- **已配对设备会话默认落盘**:设备会话(不含一次性二维码 token)写入 `$DSH_HOME/remote-web-ui-devices.json`(0600,临时文件加原子 rename)。已配对 cookie 在重启 `dsh web` 后仍然有效。刷新二维码仍会签发新 token;重启不会恢复当前二维码。点「停止」或单台「取消配对」会立即撤销并同步落盘。超过 `idleExpireMs`(默认 7 天,无心跳或门控请求)的会话会被删除,必须重新扫码。设备 id 即会话凭证(网关凭 cookie 中的设备 id 放行请求)。需要时可用 `devicesFile` 覆盖为其他绝对路径。变更 `cookieName` 会使旧设备失效(预期行为)。
|
|
175
|
+
- **设备名单仅本机可见**:配对面板根据 User-Agent 显示精简设备名称(例如 `Windows · Chrome`)、在线/离线状态与最近活动时间,并可单台取消配对。界面不渲染作为会话凭据的设备 id 或原始 User-Agent。`/api/pair/status` 即使对已配对手机也不返回设备 id 名单。
|
|
176
|
+
- **Quick-tunnel hostname 每次运行变化**:`trycloudflare.com` URL 每次 `cloudflared` 启动随机,所以隧道重启时 `publicBaseUrl` 必须随之更新。named tunnel(固定 hostname)避免这种抖动,也是持久安装 PWA 地址所必需的。
|
|
171
177
|
- **PWA 为在线优先**:只缓存静态壳和离线页。全部移动端远程控制能力仍要求运行中的 DSH host;离线时不提供会话、API 响应或命令。
|
|
172
178
|
- **开发 HMR**:`dsh web --dev` 按路径轮询每个 roster bundle,因此重建本包(其自己的 `tsdown --watch`)会热重载 client bundle;无 harness 侧 watcher。
|
|
173
179
|
|