@linxin666/dsh-remote-web-ui 0.2.7 → 0.2.8

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 (42) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +4 -0
  3. package/README.zh.md +1 -0
  4. package/lib/client.js +40 -8
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +12 -8
  7. package/lib/mobile.js +39 -21
  8. package/lib/mobile.js.map +1 -1
  9. package/lib/types/client/RemoteEntry.js +3 -3
  10. package/lib/types/client/UpdateEntry.d.ts.map +1 -1
  11. package/lib/types/client/UpdateEntry.js +1 -1
  12. package/lib/types/client/index.d.ts.map +1 -1
  13. package/lib/types/client/index.js +28 -14
  14. package/lib/types/client/pair-api.d.ts +6 -0
  15. package/lib/types/client/pair-api.d.ts.map +1 -1
  16. package/lib/types/client/pair-api.js +11 -0
  17. package/lib/types/client/remote-channel.d.ts +10 -0
  18. package/lib/types/client/remote-channel.d.ts.map +1 -1
  19. package/lib/types/client/remote-channel.js +11 -0
  20. package/lib/types/index.js +1 -1
  21. package/lib/types/mobile/messages.d.ts.map +1 -1
  22. package/lib/types/mobile/messages.js +5 -3
  23. package/lib/types/mobile/messages.test.js +20 -0
  24. package/lib/types/mobile/mux.d.ts +7 -4
  25. package/lib/types/mobile/mux.d.ts.map +1 -1
  26. package/lib/types/mobile/mux.js +43 -15
  27. package/lib/types/mobile/mux.test.js +57 -0
  28. package/lib/types/routes.d.ts +2 -0
  29. package/lib/types/routes.d.ts.map +1 -1
  30. package/lib/types/routes.js +5 -2
  31. package/package.json +15 -12
  32. package/src/client/RemoteEntry.tsx +3 -2
  33. package/src/client/UpdateEntry.tsx +1 -0
  34. package/src/client/index.ts +30 -13
  35. package/src/client/pair-api.ts +16 -0
  36. package/src/client/remote-channel.ts +21 -0
  37. package/src/index.ts +1 -1
  38. package/src/mobile/messages.test.ts +23 -0
  39. package/src/mobile/messages.ts +5 -3
  40. package/src/mobile/mux.test.ts +64 -0
  41. package/src/mobile/mux.ts +44 -17
  42. package/src/routes.ts +7 -2
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: dac3fd0ab51d68ce43474d04407c10ff67596b64
6
- README.zh.md: 035d14c5d69ff95c86fc5f5540df9cf5ec547b4f
5
+ README.md: dc31fab1b49b32fe325ead616d52846435fa1180
6
+ README.zh.md: e5ccaf0f52d01e9b8f92e265947c22dff68e2a97
package/README.md CHANGED
@@ -476,6 +476,10 @@ opens at `http://127.0.0.1`.
476
476
  unpair one at a time. Credential-bearing device ids and raw User-Agent
477
477
  values are not rendered. `/api/pair/status` never returns the device id
478
478
  list, even to a paired phone.
479
+ - **The desktop gate policy is public**: `/api/pair/status` exposes only the
480
+ boolean `requirePairingForLan` policy so a remote desktop can choose the
481
+ correct transport before its settings scope is available. This field is
482
+ not a credential and does not expose tokens, devices, counts, or tunnel URLs.
479
483
  - **Quick-tunnel hostnames change per run**: a `trycloudflare.com` URL is
480
484
  random on every `cloudflared` start, so `publicBaseUrl` must be updated
481
485
  whenever the tunnel restarts. A named tunnel (fixed hostname) avoids the
package/README.zh.md CHANGED
@@ -173,6 +173,7 @@ pnpm run build
173
173
  - **撤销是逐请求的**:已配对手机请求已在 停止 落地时在途,完成该请求;下一个 403。
174
174
  - **已配对设备会话默认落盘**:设备会话(不含一次性二维码 token)写入 `$DSH_HOME/remote-web-ui-devices.json`(0600,临时文件加原子 rename)。已配对 cookie 在重启 `dsh web` 后仍然有效。刷新二维码仍会签发新 token;重启不会恢复当前二维码。点「停止」或单台「取消配对」会立即撤销并同步落盘。超过 `idleExpireMs`(默认 7 天,无心跳或门控请求)的会话会被删除,必须重新扫码。设备 id 即会话凭证(网关凭 cookie 中的设备 id 放行请求)。需要时可用 `devicesFile` 覆盖为其他绝对路径。变更 `cookieName` 会使旧设备失效(预期行为)。
175
175
  - **设备名单仅本机可见**:配对面板根据 User-Agent 显示精简设备名称(例如 `Windows · Chrome`)、在线/离线状态与最近活动时间,并可单台取消配对。界面不渲染作为会话凭据的设备 id 或原始 User-Agent。`/api/pair/status` 即使对已配对手机也不返回设备 id 名单。
176
+ - **桌面门控策略是公开字段**:`/api/pair/status` 只公开布尔值 `requirePairingForLan`,让远程桌面在设置作用域不可用前选择正确传输通道。该字段不是凭据,也不暴露令牌、设备、计数或隧道 URL。
176
177
  - **Quick-tunnel hostname 每次运行变化**:`trycloudflare.com` URL 每次 `cloudflared` 启动随机,所以隧道重启时 `publicBaseUrl` 必须随之更新。named tunnel(固定 hostname)避免这种抖动,也是持久安装 PWA 地址所必需的。
177
178
  - **PWA 为在线优先**:只缓存静态壳和离线页。全部移动端远程控制能力仍要求运行中的 DSH host;离线时不提供会话、API 响应或命令。
178
179
  - **开发 HMR**:`dsh web --dev` 按路径轮询每个 roster bundle,因此重建本包(其自己的 `tsdown --watch`)会热重载 client bundle;无 harness 侧 watcher。
package/lib/client.js CHANGED
@@ -1230,6 +1230,14 @@ window.__ModuleLoader__.load({
1230
1230
  QRCodeSVG.displayName = "QRCodeSVG";
1231
1231
  //#endregion
1232
1232
  //#region src/client/pair-api.ts
1233
+ /** Read the host-authoritative desktop pairing policy. */
1234
+ async function readPairGatePolicy() {
1235
+ const response = await fetch("/api/pair/status");
1236
+ if (!response.ok) throw new Error(`remote-web-ui: status failed with ${String(response.status)}`);
1237
+ const value = await response.json();
1238
+ if (typeof value.requirePairingForLan !== "boolean") throw new Error("remote-web-ui: status omitted requirePairingForLan");
1239
+ return { requirePairingForLan: value.requirePairingForLan };
1240
+ }
1233
1241
  /**
1234
1242
  * Mint a fresh pairing token (one active token at a time — this invalidates
1235
1243
  * any previous link).
@@ -2162,6 +2170,7 @@ window.__ModuleLoader__.load({
2162
2170
  "data-wide": wide ? void 0 : "rail",
2163
2171
  "data-update-available": updateAvailable ? "true" : void 0,
2164
2172
  "aria-label": updateLabel,
2173
+ "aria-expanded": open,
2165
2174
  title: updateLabel,
2166
2175
  onClick: openPanel,
2167
2176
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { size: wide ? 16 : 18 })
@@ -2358,7 +2367,8 @@ window.__ModuleLoader__.load({
2358
2367
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipAnchor, {
2359
2368
  wide,
2360
2369
  label: t("entry.label"),
2361
- onClick: openPanel
2370
+ onClick: openPanel,
2371
+ expanded: open
2362
2372
  })]
2363
2373
  }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2364
2374
  className: remote_module_css_default.overlay,
@@ -2382,12 +2392,13 @@ window.__ModuleLoader__.load({
2382
2392
  }), document.body)] });
2383
2393
  }
2384
2394
  /** The trigger: an icon-only control with a persistent accessible label. */
2385
- function TooltipAnchor({ wide, label, onClick }) {
2395
+ function TooltipAnchor({ wide, label, onClick, expanded }) {
2386
2396
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2387
2397
  type: "button",
2388
2398
  className: remote_module_css_default.trigger,
2389
2399
  "data-wide": wide ? "wide" : "rail",
2390
2400
  "aria-label": label,
2401
+ "aria-expanded": expanded,
2391
2402
  title: label,
2392
2403
  onClick,
2393
2404
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhoneIcon, { size: wide ? 16 : 18 })
@@ -3813,6 +3824,12 @@ window.__ModuleLoader__.load({
3813
3824
  "/sidebar/ws/agent-terminals",
3814
3825
  "/api/dsh-ssh/terminal"
3815
3826
  ]);
3827
+ /** Decide whether a remote desktop channel is required from local or host policy. */
3828
+ function remoteChannelRequired(hostname, snapshot, hostPairingPolicy) {
3829
+ if (isLoopbackHostname(hostname)) return false;
3830
+ if (snapshot.status === "ready") return (snapshot.value?.enabled ?? true) && (snapshot.value?.requirePairingForLan ?? true);
3831
+ return hostPairingPolicy !== false;
3832
+ }
3816
3833
  /**
3817
3834
  * Browser-safe loopback classification for the page origin (the SDK client
3818
3835
  * exports its own; this copy keeps the module dependency-free).
@@ -4165,6 +4182,8 @@ window.__ModuleLoader__.load({
4165
4182
  settingsScope.subscribe(syncRuntime);
4166
4183
  syncRuntime();
4167
4184
  let disposeChannel;
4185
+ let hostPairingPolicy;
4186
+ let unpairedWhilePolicyPending = false;
4168
4187
  let fenceNotice;
4169
4188
  const showFenceNotice = () => {
4170
4189
  if (fenceNotice !== void 0) return;
@@ -4189,17 +4208,19 @@ window.__ModuleLoader__.load({
4189
4208
  fenceNotice?.unmount();
4190
4209
  fenceNotice = void 0;
4191
4210
  };
4192
- const channelActive = () => {
4193
- if (isLoopbackHostname(window.location.hostname)) return false;
4194
- const snapshot = settingsScope.getSnapshot();
4195
- const value = snapshot.status === "ready" ? snapshot.value : void 0;
4196
- return (value?.enabled ?? true) && (value?.requirePairingForLan ?? true);
4211
+ const handleUnpaired = () => {
4212
+ if (settingsScope.getSnapshot().status !== "ready" && hostPairingPolicy === void 0) {
4213
+ unpairedWhilePolicyPending = true;
4214
+ return;
4215
+ }
4216
+ showFenceNotice();
4197
4217
  };
4218
+ const channelActive = () => remoteChannelRequired(window.location.hostname, settingsScope.getSnapshot(), hostPairingPolicy);
4198
4219
  const syncChannel = () => {
4199
4220
  const transition = channelTransition(channelActive(), disposeChannel !== void 0);
4200
4221
  if (transition === "install") disposeChannel = ctx.effect(() => {
4201
4222
  return installRemoteChannel(window, {
4202
- onUnpaired: showFenceNotice,
4223
+ onUnpaired: handleUnpaired,
4203
4224
  onPaired: hideFenceNotice
4204
4225
  });
4205
4226
  }, "remote-web-ui: remote desktop channel");
@@ -4211,6 +4232,17 @@ window.__ModuleLoader__.load({
4211
4232
  };
4212
4233
  settingsScope.subscribe(syncChannel);
4213
4234
  syncChannel();
4235
+ if (!isLoopbackHostname(window.location.hostname) && settingsScope.getSnapshot().status !== "ready") readPairGatePolicy().then((policy) => {
4236
+ hostPairingPolicy = policy.requirePairingForLan;
4237
+ syncChannel();
4238
+ if (hostPairingPolicy && unpairedWhilePolicyPending) showFenceNotice();
4239
+ unpairedWhilePolicyPending = false;
4240
+ }).catch(() => {
4241
+ hostPairingPolicy = true;
4242
+ syncChannel();
4243
+ if (unpairedWhilePolicyPending) showFenceNotice();
4244
+ unpairedWhilePolicyPending = false;
4245
+ });
4214
4246
  ctx.effect(() => {
4215
4247
  const timer = window.setTimeout(() => {
4216
4248
  if (sessionStorage.getItem("dsh-remote-pair-failed") === null) return;