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

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 (48) hide show
  1. package/LICENSE +202 -28
  2. package/README.i18n.yaml +2 -2
  3. package/README.md +14 -7
  4. package/README.zh.md +3 -2
  5. package/lib/client.js +40 -8
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +34 -24
  8. package/lib/mobile.js +39 -21
  9. package/lib/mobile.js.map +1 -1
  10. package/lib/types/client/RemoteEntry.js +3 -3
  11. package/lib/types/client/UpdateEntry.d.ts.map +1 -1
  12. package/lib/types/client/UpdateEntry.js +1 -1
  13. package/lib/types/client/index.d.ts.map +1 -1
  14. package/lib/types/client/index.js +28 -14
  15. package/lib/types/client/pair-api.d.ts +6 -0
  16. package/lib/types/client/pair-api.d.ts.map +1 -1
  17. package/lib/types/client/pair-api.js +11 -0
  18. package/lib/types/client/remote-channel.d.ts +10 -0
  19. package/lib/types/client/remote-channel.d.ts.map +1 -1
  20. package/lib/types/client/remote-channel.js +11 -0
  21. package/lib/types/index.d.ts.map +1 -1
  22. package/lib/types/index.js +16 -5
  23. package/lib/types/mobile/messages.d.ts.map +1 -1
  24. package/lib/types/mobile/messages.js +5 -3
  25. package/lib/types/mobile/messages.test.js +20 -0
  26. package/lib/types/mobile/mux.d.ts +7 -4
  27. package/lib/types/mobile/mux.d.ts.map +1 -1
  28. package/lib/types/mobile/mux.js +43 -15
  29. package/lib/types/mobile/mux.test.js +57 -0
  30. package/lib/types/remote-api.d.ts +13 -4
  31. package/lib/types/remote-api.d.ts.map +1 -1
  32. package/lib/types/remote-api.js +27 -17
  33. package/lib/types/routes.d.ts +2 -0
  34. package/lib/types/routes.d.ts.map +1 -1
  35. package/lib/types/routes.js +5 -2
  36. package/package.json +15 -12
  37. package/src/client/RemoteEntry.tsx +3 -2
  38. package/src/client/UpdateEntry.tsx +1 -0
  39. package/src/client/index.ts +30 -13
  40. package/src/client/pair-api.ts +16 -0
  41. package/src/client/remote-channel.ts +21 -0
  42. package/src/index.ts +16 -5
  43. package/src/mobile/messages.test.ts +23 -0
  44. package/src/mobile/messages.ts +5 -3
  45. package/src/mobile/mux.test.ts +64 -0
  46. package/src/mobile/mux.ts +44 -17
  47. package/src/remote-api.ts +34 -17
  48. package/src/routes.ts +7 -2
package/LICENSE CHANGED
@@ -1,28 +1,202 @@
1
- BSD 3-Clause License
2
-
3
- Copyright (c) 2026, zhu1090093659
4
-
5
- Redistribution and use in source and binary forms, with or without
6
- modification, are permitted provided that the following conditions are met:
7
-
8
- 1. Redistributions of source code must retain the above copyright notice, this
9
- list of conditions and the following disclaimer.
10
-
11
- 2. Redistributions in binary form must reproduce the above copyright notice,
12
- this list of conditions and the following disclaimer in the documentation
13
- and/or other materials provided with the distribution.
14
-
15
- 3. Neither the name of the copyright holder nor the names of its
16
- contributors may be used to endorse or promote products derived from
17
- this software without specific prior written permission.
18
-
19
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
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: d3195d3b8a3dc58cf077538b0e18455b7d1706a6
6
+ README.zh.md: c0a06bf7ae0b49460e6f4aa250b42f2c227b4e0a
package/README.md CHANGED
@@ -44,7 +44,8 @@ that probes and runs the update.
44
44
  their next request. Pairing is this plugin's access control for its own
45
45
  remote channels: `/m/api` for the phone and `/remote` for a desktop
46
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
47
+ are refused before the request body is read while `requirePairingForLan` is
48
+ on (default). Loopback (127.0.0.1) keeps using
48
49
  `/api` directly. The default remote-desktop path does not use
49
50
  `--trusted-host`: the connection plugin's `/api` fence stays closed for
50
51
  public and LAN hosts, and the paired PC rides `/remote` instead.
@@ -244,12 +245,14 @@ over Server-Sent Events on `/m/api/events.mux`. The canonical `/m/` page owns a
244
245
  - 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.
245
246
  - Installing this plugin routes fenced non-loopback desktop traffic onto the
246
247
  gated `/remote` channel (see `requirePairingForLan` in `src/index.ts`).
247
- A desktop browser opened via the LAN URL or the tunnel must pair like any
248
- remote device — the unpaired state shows a persistent blocking page instead of
249
- data; loopback (127.0.0.1) is unaffected and keeps `/api`. Set
250
- `requirePairingForLan: false` in the profile patch to keep the desktop on
251
- plain `/api` (only useful with the open-LAN stance) while keeping
252
- tokens/status/revocation. Note the underlying `/api` route itself is the
248
+ While the flag is on (default), a desktop browser opened via the LAN URL or
249
+ the tunnel must pair like any remote device — the unpaired state shows a
250
+ persistent blocking page instead of data; loopback (127.0.0.1) is unaffected
251
+ and keeps `/api`. Set `requirePairingForLan: false` in the profile patch to
252
+ keep the desktop on plain `/api` and have a leftover `/remote` rewrite
253
+ proxy unpaired calls (only useful with the open-LAN stance; loopback-only
254
+ paths stay denied) while keeping tokens/status/revocation. Note the
255
+ underlying `/api` route itself is the
253
256
  SDK's: on a `--host 0.0.0.0` bind the SDK auto-trusts LAN literals, so a
254
257
  LAN client bypassing the UI can still reach `/api` directly — the posture
255
258
  probe reports that stance on the panel.
@@ -476,6 +479,10 @@ opens at `http://127.0.0.1`.
476
479
  unpair one at a time. Credential-bearing device ids and raw User-Agent
477
480
  values are not rendered. `/api/pair/status` never returns the device id
478
481
  list, even to a paired phone.
482
+ - **The desktop gate policy is public**: `/api/pair/status` exposes only the
483
+ boolean `requirePairingForLan` policy so a remote desktop can choose the
484
+ correct transport before its settings scope is available. This field is
485
+ not a credential and does not expose tokens, devices, counts, or tunnel URLs.
479
486
  - **Quick-tunnel hostnames change per run**: a `trycloudflare.com` URL is
480
487
  random on every `cloudflared` start, so `publicBaseUrl` must be updated
481
488
  whenever the tunnel restarts. A named tunnel (fixed hostname) avoids the
package/README.zh.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - **面板**:「远程访问」标题、「通过手机或另一台电脑配对,远程访问当前工作区」副标题、「设备配对」卡片(含状态区「等待设备连接」+ 状态徽标)、大号二维码、各带复制按钮的手机链接(`/m/?pair=...`)与电脑链接(`/?pair=...`)、停止 / 刷新二维码操作,以及已授权设备列表(根据 User-Agent 推断的设备名称、在线/离线、最近活动时间、单台取消配对)。界面不渲染作为会话凭据的设备 id 或原始 User-Agent。两条链接共用一枚一次性令牌,任意设备配对成功后另一条立即失效。
13
13
  - **手机侧**:扫码将手机与一次性、限时令牌绑定,并落地到 **`/m/` 独立移动端界面**——一款专为小屏设计的轻客户端(见[截图](#截图)),而不是把桌面 UI 塞进手机。该页面可安装为 PWA;每个已安装应用使用自己的已配对设备 cookie,存储隔离的移动端 Web App 需要在该上下文打开新的二维码链接,或粘贴桌面端新复制的链接完成配对。链接携带 `workspace` 参数,手机落地到桌面正在查看的同一工作区。
14
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` 无需配对即可到达时,下方姿态探测会报告该姿态。
15
+ - **安全**:一个有效的一次性令牌(刷新会使旧链接失效;已接受的令牌不可复用;令牌会过期)。停止会撤销每一台已配对设备与当前令牌——已配对设备在下一次请求时被切断。配对是本插件对自己远程通道的访问控制:手机走 `/m/api`,非 loopback origin 打开的桌面浏览器走 `/remote`。未配对调用方在 `requirePairingForLan` 开启(默认)时会在读取请求体之前就被 `/remote` 拒绝。loopback(127.0.0.1)继续直接使用 `/api`。默认的远程桌面路径不使用 `--trusted-host`:连接插件的 `/api` 围栏对公网和局域网主机保持关闭,已配对 PC 改走 `/remote`。`--trusted-host` 是 SDK 的另一种用法,会让该主机被信任访问 `/api` 本身——配对不门控 `/api`(任何插件都无法做到;围栏是 SDK 自己的接缝)。当 `/api` 无需配对即可到达时,下方姿态探测会报告该姿态。
16
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)。阻断页在任一门控调用成功后,或通道本身被拆除时退出——把 `requirePairingForLan` 关掉(或关闭插件)也会清掉通道短暂激活期间弹出的提示。
17
17
  - **姿态探测**:插件用伪造 Host 头(公网 base 与每个 LAN base)探测 SDK 的 `/api` 围栏。403 是默认姿态(围栏关闭,远程访问走配对)。任何非 403 结果——`--trusted-host`,或 `--host 0.0.0.0` 下 SDK 的局域网自动信任——都会以 CRITICAL 日志与配对面板红色横幅呈现,因此 SDK 的 `/api` 信任姿态是被看见的,而不是被假设的。探测失败会丢掉进行中的目标缓存,相同 origin 会在下次触发时重试。
18
18
  - **实时状态**:桌面面板经 SSE 流实时镜像配对状态(等待 → 已连接 → 已断开),并展示已授权设备名单。
@@ -77,7 +77,7 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
77
77
 
78
78
  - 移动端输入框默认 Enter 发送(Shift+Enter 换行)。在插件设置卡片(或 profile patch)把 `mobileEnterToSend` 设为 false 后,普通 Enter 改为插入换行,只有「发送」按钮会发送;手机打开聊天时经自己的 `/m/api` 偏好方法读取该开关。在支持 `field-sizing: content` 的浏览器上,输入框随草稿自动增高,最高 120px 封顶(两种模式一致)。
79
79
  - `/m/` Worker 对静态壳使用 network-first 回退,并等待当前页面关闭后才激活更新。它旁路 `/m/api`、`/api`、SSE 与所有写请求。
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`——姿态探测会在面板上报告该姿态。
80
+ - 安装本插件后,非 loopback 桌面中受围栏保护的流量会改走门控的 `/remote` 通道(见 `src/index.ts` 的 `requirePairingForLan`)。在该开关开启(默认)时,经局域网 URL 或隧道打开的桌面浏览器必须像任何远程设备一样配对——未配对状态显示完整阻断页而不是数据;loopback(127.0.0.1)不受影响,继续使用原始路径。把 profile patch 里 `requirePairingForLan` 设为 false 可让桌面继续走普通 `/api`,残留的 `/remote` 改写也会放行未配对调用(仅在开放局域网姿态下有意义;loopback-only 路径仍被拒绝),同时保留令牌/状态/撤销。注意 `/api` 路由本体属于 SDK:`--host 0.0.0.0` 绑定时 SDK 自动信任局域网字面量,绕过 UI 的局域网客户端仍能直接访问 `/api`——姿态探测会在面板上报告该姿态。
81
81
  - `/api` 之外的兄弟 host 路由(`/pet/*`、`/git/*`、右侧面板的 `/aionui-panel/*`)可查询本插件的 `remoteWebUiPairing` 服务:有效的已配对设备 cookie 是放行路径,`stop()` 仍会切断它们;未安装本插件时该服务不存在。
82
82
  - 二维码链接基于机器的非内部 IPv4 字面量构建;多宿主主机(Wi-Fi + 有线,或代理/VPN 虚拟适配器)会显示单选器供你发布手机实际可达的网络。第一个字面量是默认值。设 `publicBaseUrl` 后,单选器在顶部额外加一项 公网地址——默认二维码改用公网 base,选中局域网字面量会重新铸一枚网内链接。
83
83
  - 配置的 `publicBaseUrl` 本身满足可达绑定需求:`dsh web` 绑定 `127.0.0.1`(不带 `--host 0.0.0.0`)仍能经隧道铸出可用的公网二维码链接。
@@ -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;