@linxin666/dsh-remote-web-ui 0.3.13 → 0.3.15
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 +3 -3
- package/README.zh.md +3 -3
- package/lib/client.js +55 -21
- package/lib/client.js.map +1 -1
- package/lib/index.js +174 -39
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +11 -1
- package/lib/types/client/RemotePanel.d.ts +5 -1
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +2 -2
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +8 -0
- package/lib/types/client/mobile-adapt.d.ts.map +1 -1
- package/lib/types/client/mobile-adapt.js +7 -12
- package/lib/types/client/remote-channel.d.ts +0 -2
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +0 -4
- package/lib/types/index.d.ts +9 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +54 -27
- package/lib/types/loopback-proxy.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +16 -0
- package/lib/types/remote-api.d.ts +1 -1
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +1 -1
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +0 -1
- package/lib/types/remote-channel-rules.d.ts +0 -1
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +0 -1
- package/lib/types/remote-methods.d.ts +1 -4
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +1 -5
- package/lib/types/routes.d.ts +26 -2
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +150 -5
- package/package.json +18 -18
- package/src/client/RemoteEntry.tsx +12 -0
- package/src/client/RemotePanel.tsx +37 -2
- package/src/client/locales.ts +8 -0
- package/src/client/mobile-adapt.ts +5 -10
- package/src/client/remote-channel.ts +0 -3
- package/src/index.ts +59 -26
- package/src/loopback-proxy.ts +15 -0
- package/src/remote-api.ts +0 -1
- package/src/remote-channel-boot.ts +0 -1
- package/src/remote-channel-rules.ts +0 -2
- package/src/remote-methods.ts +1 -6
- package/src/routes.ts +141 -7
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: 8e93c9e36c4c1cb042df29d494419347181142b0
|
|
6
|
+
README.zh.md: fb6955a0be096fe3d04cff2600484409c8b62df9
|
package/README.md
CHANGED
|
@@ -24,10 +24,10 @@ The official desktop layout already auto-collapses the sidebar below 1024px. On
|
|
|
24
24
|
- a **draggable whale button** as the entry to the collapsed sidebar (position persisted; wired to the official `ctx.layout.toggleSidebar()`), hidden while the sidebar is expanded;
|
|
25
25
|
- **gestures**: left-swipe collapses the sidebar, right-swipe in the conversation opens it; long-pressing a session row opens the same action menu as the desktop ellipsis; tapping a session row or anywhere outside the sidebar collapses it again;
|
|
26
26
|
- **input behavior for touch**: Enter only inserts a newline (send goes through the send button), programmatic composer focus is suppressed (no spurious keyboards), and official tooltip bubbles are hidden because taps leave them stuck;
|
|
27
|
-
- **mobile plugin scope**: while the layer is active, the right-hand details column and the desktop-oriented tool surfaces (SSH terminal, skill explorer, task board, git graph, pet,
|
|
27
|
+
- **mobile plugin scope**: while the layer is active, the right-hand details column and the desktop-oriented tool surfaces (SSH terminal, skill explorer, task board, git graph, pet, usage) are hidden - keyed on the L2 semantic roots (`data-dsh-plugin`), so ownership stays with the declaring plugin and class churn cannot resurrect them. These are render suppressions; the client bundles still load. Activation also closes the details panel through the official `ctx.layout.closeDetails()`.
|
|
28
28
|
- a **manual opt-out**: `sessionStorage.dsh-remote-force-desktop = 1` disables the whole layer; landscape, desktop, and wide viewports are never touched.
|
|
29
29
|
|
|
30
|
-
The paired remote desktop also runs in **host mode**: on this harness line the "configuration plane is local" behavior is a client-side branch on `connection.isLoopback`, and the channel boot script installs the transport hook (`__DSH_TRANSPORT__.ownsHost = true`) on non-loopback origins before any boot entry. Settings, credentials, agent presets, and deliverables therefore work on the phone exactly as on the desktop — every call still rides the gated `/remote` channel.
|
|
30
|
+
The paired remote desktop also runs in **host mode**: on this harness line the "configuration plane is local" behavior is a client-side branch on `connection.isLoopback`, and the channel boot script installs the transport hook (`__DSH_TRANSPORT__.ownsHost = true`) on non-loopback origins before any boot entry. Settings, credentials, agent presets, and deliverables therefore work on the phone exactly as on the desktop — every call still rides the gated `/remote` channel. Three control planes stay physically local: `/api/pair/*`, `/api/update/*`, and `/api/plugin-manager/*`.
|
|
31
31
|
|
|
32
32
|
## Requirements
|
|
33
33
|
|
|
@@ -156,7 +156,7 @@ The public path is the same round trip through a tunnel (see "Remote access over
|
|
|
156
156
|
- **Pairing is the access control for the `/remote` channel**: while `requirePairingForLan` is on (default), every request must carry a live paired-device cookie, enforced before any bytes are forwarded. A missing or revoked session receives HTTP 403 with a JSON rejection carrying `error.code: "unpaired"`; the browser's `EventSource` API exposes only the stream failure, not that response body.
|
|
157
157
|
- **The channel carries the process's own inner credential.** The harness browser-auth cookie is authority-bound (minted for the exact `host:port` a browser visited) and has no loopback exemption, so a proxied re-issue to `127.0.0.1` cannot reuse a device's cookie. The plugin therefore redeems its own launch token once — the same exchange a first browser visit performs — and attaches that cookie to re-issued requests. The credential is only ever exercised behind the pairing gate above; 停止/取消配对 immediately stop exercising it.
|
|
158
158
|
- **Cohort reality: pairing does not gate direct `/api`.** On the pinned 0.1.2-alpha.2 line nothing emits the `api/gate` seam, so a direct `/api` call from a LAN origin is governed solely by the harness fence (which auto-trusts LAN literals under a `0.0.0.0` bind) plus the harness browser-auth cookie. A browser credential a device has already redeemed therefore survives 停止/取消配对 until its natural expiry (30 days) — revocation binds the `/remote` channel and the pairing cookie, not that credential. The plugin probes the `/api` posture and warns loudly; treat a LAN-exposed bind as a deliberate decision, and prefer loopback plus tunnels when the machine is shared.
|
|
159
|
-
- **A paired device is a full-control credential.** With host mode active it reaches the complete host API — chat, sessions, settings, credentials, agent presets, deliverables — mirroring the SDK's own stance that a loopback desktop is trusted. Only the
|
|
159
|
+
- **A paired device is a full-control credential.** With host mode active it reaches the complete host API — chat, sessions, settings, credentials, agent presets, deliverables — mirroring the SDK's own stance that a loopback desktop is trusted. Only the three control planes (pairing, self-update, plugin install/remove) stay physically local. Only pair devices you control; 停止 or per-device 取消配对 revokes immediately.
|
|
160
160
|
- **Control endpoints stay loopback-only**: mint/stop/revoke, the device roster, the lan-bind status, and the update endpoints answer only to loopback. A LAN-origin browser sees the "配对面板仅限本机使用" banner.
|
|
161
161
|
- **The app landing is cookieless-optional.** After pairing, the QR lands the device on `/pair-app`, served by this plugin: the official shell is delivered without passing the harness index gate, and the device credential rides a `x-dsh-remote-device` header (fetch) / `device` query (WebSocket upgrades) that the boot patch attaches from sessionStorage. The flow therefore works when the device browser blocks all cookies; the pairing cookie remains the primary credential where the browser stores it, and the harness browser-auth cookie is no longer required on the phone path at all.
|
|
162
162
|
- **Reopens are owned by a service worker (https origins).** A paired phone returning from history, a bookmark, or tab restore navigates to bare `/` — a path the plugin does not own, where the harness fallback seat answers with its browser-auth 401 (the cookieless flow never holds that credential). The app shell therefore registers `/pair-app.sw.js` (fenced like `/pair-app`; the script is inert logic with no secrets): it intercepts navigations to `/` only, re-serves the shell network-first through `/pair-app` — which re-validates the device cookie and refreshes its presence, so every reopen also keeps the session alive — falls back to the cached shell offline, and passes the navigation through when the plugin no longer answers (a revoked device then sees the harness response or the bilingual re-scan page). Plain-HTTP LAN origins are not secure contexts and never register the worker; there a reopen means scanning a fresh QR.
|
package/README.zh.md
CHANGED
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
- **可拖拽小鲸鱼按钮**作为折叠侧栏的展开入口(位置记忆;接到官方 `ctx.layout.toggleSidebar()`),侧栏展开时隐藏;
|
|
25
25
|
- **手势**:左滑收起侧栏、会话区右滑展开;长按会话行打开与桌面省略号相同的操作菜单;点会话行或侧栏外任意位置再次收起;
|
|
26
26
|
- **触控输入行为**:Enter 只换行(发送走发送按钮)、抑制程序化 composer 聚焦(不乱弹键盘)、隐藏官方 tooltip 气泡(触屏上会残留);
|
|
27
|
-
- **移动端插件范围**:适配层激活时,右侧详情列与面向桌面的工具界面(SSH 终端、技能中心、任务看板、git graph
|
|
27
|
+
- **移动端插件范围**:适配层激活时,右侧详情列与面向桌面的工具界面(SSH 终端、技能中心、任务看板、git graph、宠物、使用统计)一律隐藏——以 L2 语义根(`data-dsh-plugin`)为键,归属由声明方插件负责,官方类名变动也不会复活它们。这些是渲染抑制;客户端 bundle 仍会加载。激活时还会经官方 `ctx.layout.closeDetails()` 真正关闭详情面板。
|
|
28
28
|
- **手动退出**:`sessionStorage.dsh-remote-force-desktop = 1` 关闭整个适配层;横屏、桌面与宽视口永不触碰。
|
|
29
29
|
|
|
30
|
-
配对远程桌面同时运行在 **host 模式**:在本 harness 线上,「配置面仅限本机」的行为是客户端分支(`connection.isLoopback`),通道 boot 脚本在一切 boot entry 之前对非回环源安装传输钩子(`__DSH_TRANSPORT__.ownsHost = true`)。设置、凭据、Agent 预设与产出物在手机上与桌面完全一致——所有调用仍走门控 `/remote`
|
|
30
|
+
配对远程桌面同时运行在 **host 模式**:在本 harness 线上,「配置面仅限本机」的行为是客户端分支(`connection.isLoopback`),通道 boot 脚本在一切 boot entry 之前对非回环源安装传输钩子(`__DSH_TRANSPORT__.ownsHost = true`)。设置、凭据、Agent 预设与产出物在手机上与桌面完全一致——所有调用仍走门控 `/remote` 通道。三个控制面保持物理本地:`/api/pair/*`、`/api/update/*` 与 `/api/plugin-manager/*`。
|
|
31
31
|
|
|
32
32
|
## 环境要求
|
|
33
33
|
|
|
@@ -156,7 +156,7 @@ pnpm run build
|
|
|
156
156
|
- **配对是 `/remote` 通道的访问控制**:`requirePairingForLan` 开启(默认)时,每个请求必须携带有效配对设备 cookie,在任何字节转发之前强制。缺失或被撤销的会话收到 HTTP 403,JSON 拒绝携带 `error.code: "unpaired"`;浏览器 `EventSource` API 只暴露流失败,不暴露响应体。
|
|
157
157
|
- **通道携带进程自己的内部凭据。** harness 浏览器认证 cookie 与 authority 绑定(为浏览器访问过的确切 `host:port` 签发)且没有回环豁免,因此转发到 `127.0.0.1` 的再发起请求无法复用设备的 cookie。插件因此自行兑换一次自己的启动令牌——与浏览器首次访问执行的是同一次交换——并把所得 cookie 附到再发起请求上。该凭据只在上面的配对门之后被使用;停止/取消配对会立即停止对它的使用。
|
|
158
158
|
- **本 cohort 的现实:配对不门控直连 `/api`。** 在锚定的 0.1.2-alpha.2 线上,没有任何组件发出 `api/gate` seam,因此来自局域网源头的直连 `/api` 仅由 harness 围栏(`0.0.0.0` 绑定下自动信任局域网字面量)加 harness 浏览器认证 cookie 约束。设备已经兑换过的浏览器凭据在停止/取消配对后仍然有效,直到其自然过期(30 天)——撤销约束的是 `/remote` 通道与配对 cookie,而不是那个凭据。插件会对 `/api` 姿态做探测并大声告警;请把局域网绑定当作深思熟虑的决定,在共享机器上优先回环加隧道。
|
|
159
|
-
- **配对设备是完全控制凭据。** host 模式下它可达完整 host API——聊天、会话、设置、凭据、Agent 预设、产出物——与 SDK
|
|
159
|
+
- **配对设备是完全控制凭据。** host 模式下它可达完整 host API——聊天、会话、设置、凭据、Agent 预设、产出物——与 SDK 对回环桌面的信任一致。只有三个控制面(配对、自更新、插件安装/卸载)保持物理本地。只配对你控制的设备;停止或逐设备取消配对立即撤销。
|
|
160
160
|
- **控制端点仅限回环**:铸造/停止/撤销、设备列表、lan-bind 状态与更新端点只应答回环。局域网源浏览器看到「配对面板仅限本机使用」横幅。
|
|
161
161
|
- **应用落地页不依赖 cookie。** 配对后二维码把设备带到 `/pair-app`——由本插件直接交付官方应用壳,不经过 harness 索引认证门;设备凭据经 `x-dsh-remote-device` 请求头(fetch)与 `device` 查询参数(WebSocket 升级)由引导补丁从 sessionStorage 挂载。因此手机浏览器完全禁用 cookie 时链路依然成立;有 cookie 时配对 cookie 仍是主凭据,手机路径不再需要 harness 浏览器认证 cookie。
|
|
162
162
|
- **重开由 service worker 接管(仅 https 源)。** 配对过的手机从历史、书签或标签恢复回来时导航到裸 `/`——插件不拥有的路径,harness 兜底座会用浏览器认证 401 应答(不依赖 cookie 的流程永远拿不到那份凭据)。应用壳因此注册 `/pair-app.sw.js`(与 `/pair-app` 同一栅栏;脚本是不含任何秘密的惰性逻辑):只拦截对 `/` 的导航,经 `/pair-app` 网络优先地重发应用壳——同时校验设备 cookie 并刷新其活跃时间,每次重开也在为会话续期——离线时回退缓存的壳,插件不再应答时把导航原样放行(被撤销的设备随后看到 harness 应答或双语重扫页)。纯 HTTP 的局域网源不是安全上下文,永远不会注册该 worker;那里的重开意味着重新扫码。
|
package/lib/client.js
CHANGED
|
@@ -1514,7 +1514,7 @@ window.__ModuleLoader__.load({
|
|
|
1514
1514
|
* @param props - copy, state, and actions.
|
|
1515
1515
|
* @returns the panel element tree.
|
|
1516
1516
|
*/
|
|
1517
|
-
function RemotePanel({ t, state, copied, onClose, onStop, onRefresh, onCopy, onPickAddress, onPickPublic, onRevoke }) {
|
|
1517
|
+
function RemotePanel({ t, state, copied, copiedToken, onClose, onStop, onRefresh, onCopy, onCopyToken, onPickAddress, onPickPublic, onRevoke }) {
|
|
1518
1518
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1519
1519
|
className: remote_module_css_default.panel,
|
|
1520
1520
|
role: "dialog",
|
|
@@ -1623,9 +1623,9 @@ window.__ModuleLoader__.load({
|
|
|
1623
1623
|
className: remote_module_css_default.hint,
|
|
1624
1624
|
children: state.public ? t("pair.publicHint") : t("pair.hint")
|
|
1625
1625
|
}),
|
|
1626
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1627
1627
|
className: remote_module_css_default.pairLinks,
|
|
1628
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1628
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1629
1629
|
className: remote_module_css_default.pairLinkRow,
|
|
1630
1630
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1631
1631
|
className: remote_module_css_default.pairLinkText,
|
|
@@ -1643,11 +1643,33 @@ window.__ModuleLoader__.load({
|
|
|
1643
1643
|
onClick: () => onCopy(state.url),
|
|
1644
1644
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 }), copied ? t("action.copied") : t("action.copyLink")]
|
|
1645
1645
|
})]
|
|
1646
|
-
})
|
|
1646
|
+
}), state.token !== void 0 && state.token !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1647
|
+
className: remote_module_css_default.pairLinkRow,
|
|
1648
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1649
|
+
className: remote_module_css_default.pairLinkText,
|
|
1650
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1651
|
+
className: remote_module_css_default.pairLinkLabel,
|
|
1652
|
+
children: t("pair.tokenLabel")
|
|
1653
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1654
|
+
className: remote_module_css_default.link,
|
|
1655
|
+
title: state.token,
|
|
1656
|
+
children: state.token
|
|
1657
|
+
})]
|
|
1658
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1659
|
+
type: "button",
|
|
1660
|
+
className: remote_module_css_default.copyLink,
|
|
1661
|
+
onClick: () => onCopyToken ? onCopyToken(state.token) : onCopy(state.token),
|
|
1662
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 }), copiedToken ? t("action.copiedToken") : t("action.copyToken")]
|
|
1663
|
+
})]
|
|
1664
|
+
})]
|
|
1647
1665
|
}),
|
|
1648
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1666
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1649
1667
|
className: remote_module_css_default.oneTimeHint,
|
|
1650
|
-
children:
|
|
1668
|
+
children: [
|
|
1669
|
+
t("pair.oneTimeHint"),
|
|
1670
|
+
" ",
|
|
1671
|
+
t("pair.dockerHint")
|
|
1672
|
+
]
|
|
1651
1673
|
}),
|
|
1652
1674
|
state.phase === "stopped" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1653
1675
|
className: remote_module_css_default.stoppedHint,
|
|
@@ -2306,6 +2328,7 @@ window.__ModuleLoader__.load({
|
|
|
2306
2328
|
stateRef.current = state;
|
|
2307
2329
|
}, [state]);
|
|
2308
2330
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
2331
|
+
const [copiedToken, setCopiedToken] = (0, react.useState)(false);
|
|
2309
2332
|
const eventSource = (0, react.useRef)(void 0);
|
|
2310
2333
|
const openSeq = (0, react.useRef)(0);
|
|
2311
2334
|
const closeEventSource = (0, react.useCallback)(() => {
|
|
@@ -2328,6 +2351,7 @@ window.__ModuleLoader__.load({
|
|
|
2328
2351
|
return {
|
|
2329
2352
|
kind: "ready",
|
|
2330
2353
|
url: result.url,
|
|
2354
|
+
token: result.token,
|
|
2331
2355
|
expiresAt: result.expiresAt,
|
|
2332
2356
|
expired: Date.now() > result.expiresAt,
|
|
2333
2357
|
phase: "waiting",
|
|
@@ -2424,6 +2448,15 @@ window.__ModuleLoader__.load({
|
|
|
2424
2448
|
}, 1500);
|
|
2425
2449
|
});
|
|
2426
2450
|
}, []);
|
|
2451
|
+
const handleCopyToken = (0, react.useCallback)((token) => {
|
|
2452
|
+
copyText(token).then((ok) => {
|
|
2453
|
+
if (!ok) return;
|
|
2454
|
+
setCopiedToken(true);
|
|
2455
|
+
window.setTimeout(() => {
|
|
2456
|
+
setCopiedToken(false);
|
|
2457
|
+
}, 1500);
|
|
2458
|
+
});
|
|
2459
|
+
}, []);
|
|
2427
2460
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2428
2461
|
className: remote_module_css_default.entryRow,
|
|
2429
2462
|
"data-rail": wide ? void 0 : "rail",
|
|
@@ -2447,10 +2480,12 @@ window.__ModuleLoader__.load({
|
|
|
2447
2480
|
t,
|
|
2448
2481
|
state,
|
|
2449
2482
|
copied,
|
|
2483
|
+
copiedToken,
|
|
2450
2484
|
onClose: closePanel,
|
|
2451
2485
|
onStop: handleStop,
|
|
2452
2486
|
onRefresh: handleRefresh,
|
|
2453
2487
|
onCopy: handleCopy,
|
|
2488
|
+
onCopyToken: handleCopyToken,
|
|
2454
2489
|
onPickAddress: handlePickAddress,
|
|
2455
2490
|
onPickPublic: handlePickPublic,
|
|
2456
2491
|
onRevoke: handleRevoke
|
|
@@ -3647,6 +3682,8 @@ window.__ModuleLoader__.load({
|
|
|
3647
3682
|
"pair.expires": "二维码有效至 {time}",
|
|
3648
3683
|
"pair.expired": "二维码已过期,请刷新",
|
|
3649
3684
|
"pair.linkLabel": "配对链接",
|
|
3685
|
+
"pair.tokenLabel": "配对令牌",
|
|
3686
|
+
"pair.dockerHint": "Docker 或反向代理环境下,可直接复制此令牌并在目标设备配对页面输入。",
|
|
3650
3687
|
"pair.oneTimeHint": "链接含一次性令牌;任一设备配对成功后立即失效,配对下一台设备请刷新二维码。",
|
|
3651
3688
|
"pair.failed.title": "配对失败",
|
|
3652
3689
|
"pair.failed.detail": "链接无效或已使用,请回到电脑端刷新二维码后重新扫码。",
|
|
@@ -3670,7 +3707,9 @@ window.__ModuleLoader__.load({
|
|
|
3670
3707
|
"action.refresh": "刷新二维码",
|
|
3671
3708
|
"action.copy": "复制链接",
|
|
3672
3709
|
"action.copyLink": "复制链接",
|
|
3710
|
+
"action.copyToken": "复制令牌",
|
|
3673
3711
|
"action.copied": "已复制",
|
|
3712
|
+
"action.copiedToken": "已复制令牌",
|
|
3674
3713
|
"devices.title": "已授权设备",
|
|
3675
3714
|
"devices.empty": "还没有已配对的设备。扫码或打开链接后会出现在这里。",
|
|
3676
3715
|
"devices.unknown": "未知设备",
|
|
@@ -3808,6 +3847,8 @@ window.__ModuleLoader__.load({
|
|
|
3808
3847
|
"pair.expires": "QR code valid until {time}",
|
|
3809
3848
|
"pair.expired": "QR code expired — refresh it",
|
|
3810
3849
|
"pair.linkLabel": "Pairing link",
|
|
3850
|
+
"pair.tokenLabel": "Pairing token",
|
|
3851
|
+
"pair.dockerHint": "In Docker or reverse proxy environments, copy this token to pair directly on the target device.",
|
|
3811
3852
|
"pair.oneTimeHint": "The link carries one single-use token; it dies as soon as any device pairs. Refresh the QR to pair the next device.",
|
|
3812
3853
|
"pair.failed.title": "Pairing failed",
|
|
3813
3854
|
"pair.failed.detail": "The link is invalid or was already used. Refresh the QR code on your computer and scan again.",
|
|
@@ -3831,7 +3872,9 @@ window.__ModuleLoader__.load({
|
|
|
3831
3872
|
"action.refresh": "Refresh QR",
|
|
3832
3873
|
"action.copy": "Copy link",
|
|
3833
3874
|
"action.copyLink": "Copy link",
|
|
3875
|
+
"action.copyToken": "Copy token",
|
|
3834
3876
|
"action.copied": "Copied",
|
|
3877
|
+
"action.copiedToken": "Token copied",
|
|
3835
3878
|
"devices.title": "Authorized devices",
|
|
3836
3879
|
"devices.empty": "No paired devices yet. Scan the QR or open the link to add one.",
|
|
3837
3880
|
"devices.unknown": "Unknown device",
|
|
@@ -4015,7 +4058,6 @@ window.__ModuleLoader__.load({
|
|
|
4015
4058
|
apiPrefix: "/api/",
|
|
4016
4059
|
pairPrefix: "/api/pair/",
|
|
4017
4060
|
updatePrefix: "/api/update/",
|
|
4018
|
-
desktopLauncherPrefix: "/api/dsh-desktop-launcher",
|
|
4019
4061
|
settingsBridgePrefix: "/api/dsh-web-ui-settings",
|
|
4020
4062
|
sidebarPrefix: "/sidebar/",
|
|
4021
4063
|
gitPrefix: "/git/",
|
|
@@ -4047,8 +4089,6 @@ window.__ModuleLoader__.load({
|
|
|
4047
4089
|
* - the pairing routes (`/api/pair/*`) stay where they are — accept must
|
|
4048
4090
|
* work BEFORE a device is paired;
|
|
4049
4091
|
* - the update endpoints (`/api/update/*`) stay loopback-only;
|
|
4050
|
-
* - desktop-launcher create/shutdown (`/api/dsh-desktop-launcher/*`) stay
|
|
4051
|
-
* loopback-only (host-local files and process exit);
|
|
4052
4092
|
* - the family settings bridge (`/api/dsh-web-ui-settings/*`) stays
|
|
4053
4093
|
* loopback-only (same plane as SDK settings.*);
|
|
4054
4094
|
* - `/api/*` (SDK methods and `/api/<plugin>/...` plugin namespaces),
|
|
@@ -4085,7 +4125,6 @@ window.__ModuleLoader__.load({
|
|
|
4085
4125
|
function shouldRewriteFetchPath(pathname) {
|
|
4086
4126
|
if (pathname.startsWith(RULES.pairPrefix)) return false;
|
|
4087
4127
|
if (pathname.startsWith(RULES.updatePrefix)) return false;
|
|
4088
|
-
if (pathname === RULES.desktopLauncherPrefix || pathname.startsWith(`${RULES.desktopLauncherPrefix}/`)) return false;
|
|
4089
4128
|
if (pathname === RULES.settingsBridgePrefix || pathname.startsWith(`${RULES.settingsBridgePrefix}/`)) return false;
|
|
4090
4129
|
if (pathname.startsWith(RULES.apiPrefix)) return true;
|
|
4091
4130
|
if (pathname.startsWith(RULES.sidebarPrefix) || pathname === "/sidebar") return true;
|
|
@@ -4414,7 +4453,7 @@ window.__ModuleLoader__.load({
|
|
|
4414
4453
|
/** The building package's version, when the bundle carries it. */
|
|
4415
4454
|
function bakedVersion() {
|
|
4416
4455
|
try {
|
|
4417
|
-
return "0.3.
|
|
4456
|
+
return "0.3.15";
|
|
4418
4457
|
} catch {
|
|
4419
4458
|
return;
|
|
4420
4459
|
}
|
|
@@ -4573,7 +4612,6 @@ window.__ModuleLoader__.load({
|
|
|
4573
4612
|
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"task-board\"],`,
|
|
4574
4613
|
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"git-graph\"],`,
|
|
4575
4614
|
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"pet\"],`,
|
|
4576
|
-
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"perf\"],`,
|
|
4577
4615
|
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"usage\"]{display:none !important}`,
|
|
4578
4616
|
`body.${ACTIVE_CLASS} [class$=\"_overlayLayer\"] [class$=\"_workbench\"]{display:none !important}`,
|
|
4579
4617
|
"[class$=\"_overlay\"] [class$=\"_panel\"]{flex-direction:column;max-height:calc(100dvh - 32px)}",
|
|
@@ -5164,15 +5202,11 @@ window.__ModuleLoader__.load({
|
|
|
5164
5202
|
if (active && (this.tagName === "TEXTAREA" || this.tagName === "INPUT") && this.closest("[class$=\"_composerSeat\"]") !== null && Date.now() - lastComposerTap >= 800) return;
|
|
5165
5203
|
lanOrigFocus.call(this, options);
|
|
5166
5204
|
};
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
const t = e.target;
|
|
5173
|
-
if (t instanceof Element && (t.tagName === "TEXTAREA" || t.tagName === "INPUT") && t.closest("[class$=\"_composerSeat\"]") !== null) lastComposerTap = Date.now();
|
|
5174
|
-
}, true);
|
|
5175
|
-
}
|
|
5205
|
+
document.addEventListener("pointerdown", (e) => {
|
|
5206
|
+
if (!active) return;
|
|
5207
|
+
const t = e.target;
|
|
5208
|
+
if (t instanceof Element && (t.tagName === "TEXTAREA" || t.tagName === "INPUT") && t.closest("[class$=\"_composerSeat\"]") !== null) lastComposerTap = Date.now();
|
|
5209
|
+
}, true);
|
|
5176
5210
|
w.__dshRemoteAdapt = {
|
|
5177
5211
|
evaluate,
|
|
5178
5212
|
toggleSidebar: null,
|