dsh-plugin-mobile-gateway 0.1.17 → 0.4.0

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/PROTOCOL.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # dsh Mobile Gateway — WebSocket 协议参考
2
2
 
3
- 移动端通过一个 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送消息、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现(v0.1.17)。
3
+ 移动端通过一个经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送消息、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现(v0.3.0)。
4
4
 
5
- - **端点**:`ws://127.0.0.1:3080/ws/mobile`(与 dsh web GUI 同端口)
5
+ - **本机端点**:`ws://127.0.0.1:3080/ws/mobile`(与 dsh web GUI 同端口)
6
+ - **公网端点**:必须由 TLS 反向代理提供 `wss://<域名>/ws/mobile`
6
7
  - **帧格式**:全部为 JSON 文本帧(UTF-8)
7
8
  - **连接即推送**:连上后服务端立刻发送一条 `hello`,之后 agent 输出以 `event` 帧实时推送
8
9
 
@@ -32,6 +33,103 @@
32
33
  ### 会话 ID 获取
33
34
  `{"type":"sessions"}` 列表取 `sessionId`,或 `{"type":"message"}` 省略 sessionId 自动创建后从 `sent` 响应拿。
34
35
 
36
+ ### 设备配对与鉴权
37
+
38
+ #### 网关与鉴权状态
39
+
40
+ WebUI 中有两个互相独立的开关。它们是本机管理设置,iOS 客户端不应调用 `/mgw/*`:
41
+
42
+ | 移动网关 | 设备鉴权 | iOS 连接结果 |
43
+ |---|---|---|
44
+ | 关闭 | 任意 | WebSocket Upgrade 返回 `503 Service Unavailable` |
45
+ | 开启 | 开启(默认) | 必须使用一次性配对码或长期设备 token,否则返回 `401 Unauthorized` |
46
+ | 开启 | 关闭(仅 Debug) | 允许无凭证连接,`hello.authenticated` 为 `false` |
47
+
48
+ - 移动网关默认关闭。手动开启后,默认 5 分钟内没有客户端成功建立连接就自动关闭。
49
+ - 关闭移动网关会关闭现有连接,WebSocket close code 为 `4004`。
50
+ - 从 Debug 模式重新开启鉴权时,所有无凭证连接会被关闭,close code 为 `4003`。
51
+ - Debug 鉴权开关只在当前 DSH 进程中生效;重启后恢复配置中的 `requireAuth: true`。
52
+
53
+ #### 首次配对
54
+
55
+ 二维码与手动复制内容都严格使用**无 padding 的 Base64URL 字符串**。解码后的 UTF-8 内容是以下 JSON,而不是长期凭证:
56
+
57
+ ```json
58
+ {
59
+ "version": 2,
60
+ "publicUrl": "wss://gateway.example.com/ws/mobile",
61
+ "pairingCode": "<一次性 256-bit 配对码>",
62
+ "expiresAt": 1787112000000
63
+ }
64
+ ```
65
+
66
+ 编码方式(唯一受支持的配对载荷格式):
67
+
68
+ ```js
69
+ const pairingText = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')
70
+ ```
71
+
72
+ 客户端必须先执行严格 Base64URL 解码(只允许 `A-Z a-z 0-9 - _`,不接受 `=` padding、原始 JSON 或普通 Base64),再解析 JSON 并检查 `version` 与 `expiresAt`。
73
+
74
+ 首次连接必须请求子协议 `dsh-mobile-v1, dsh-pair.<pairingCode>`,并携带 `X-DSH-Device-ID` 请求头;缺少稳定设备 ID 的配对请求会被拒绝,避免每次重连都创建新的可信设备。该值应为客户端在 Keychain 中持久保存的安装级随机 UUID,仅用于重新配对时复用可信设备记录,不能替代配对码或设备 token 完成鉴权。兼容实现也可把一次性配对码放在 `?pairingCode=`;但子协议不会进入常见的 URL access log,因此优先使用子协议。成功后服务端依次发送:
75
+
76
+ ```json
77
+ { "kind": "paired", "token": "<长期设备 token>",
78
+ "device": { "id": "...", "name": "iPhone", "createdAt": 1787111700000 } }
79
+ { "kind": "hello", "protocol": 2, "authenticated": true,
80
+ "device": { "id": "...", "name": "iPhone" }, "port": 3080, "clients": 1 }
81
+ ```
82
+
83
+ `paired` 只发送一次。iOS 必须把 token 存入 Keychain,之后使用以下任一方式连接:
84
+
85
+ - 推荐:HTTP 请求头 `Authorization: Bearer <token>`
86
+ - WebSocket 子协议:`dsh-mobile-v1, dsh-auth.<token>`
87
+
88
+ 长期 token 默认禁止放在 URL query 中,避免被代理日志、浏览器历史和监控系统记录。缺少凭证、凭证无效、配对码过期或重复使用时,HTTP Upgrade 返回 `401 Unauthorized`。
89
+
90
+ #### iOS 对接示例
91
+
92
+ 首次配对时,先对二维码/手动字符串执行 Base64URL 解码,再从 JSON 解析 `publicUrl`、`pairingCode` 和 `expiresAt`,并在过期前连接:
93
+
94
+ ```swift
95
+ func connectForPairing(publicURL: URL, pairingCode: String) -> URLSessionWebSocketTask {
96
+ var request = URLRequest(url: publicURL)
97
+ request.setValue(stableInstallationUUID, forHTTPHeaderField: "X-DSH-Device-ID")
98
+ request.setValue(
99
+ "dsh-mobile-v1, dsh-pair.\(pairingCode)",
100
+ forHTTPHeaderField: "Sec-WebSocket-Protocol"
101
+ )
102
+ let task = URLSession.shared.webSocketTask(with: request)
103
+ task.resume()
104
+ return task
105
+ }
106
+ ```
107
+
108
+ 成功后第一条业务帧为 `paired`。客户端必须立即将 `token` 写入 Keychain;该 token 不会再次下发。随后还会收到 `hello`。
109
+
110
+ 后续连接推荐使用 `Authorization`:
111
+
112
+ ```swift
113
+ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketTask {
114
+ var request = URLRequest(url: publicURL)
115
+ request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
116
+ request.setValue("dsh-mobile-v1", forHTTPHeaderField: "Sec-WebSocket-Protocol")
117
+ let task = URLSession.shared.webSocketTask(with: request)
118
+ task.resume()
119
+ return task
120
+ }
121
+ ```
122
+
123
+ 客户端连接状态机建议如下:
124
+
125
+ 1. 收到 HTTP `503`:网关尚未开启,停止高频重连;等待用户在 WebUI 开启后再手动重试,或使用有上限的退避。
126
+ 2. 收到 HTTP `401`:token 缺失、错误或已被吊销;删除 Keychain 中的旧 token,进入重新配对流程。
127
+ 3. 收到 `paired`:保存 token,记录 device id,然后等待 `hello`。
128
+ 4. 收到 `hello.authenticated == true`:进入正常业务通信。
129
+ 5. Debug 模式收到 `hello.authenticated == false`:允许调试通信,但不得把该连接方式用于公网构建。
130
+ 6. 收到 close code `4003`:服务端已重新开启鉴权,使用 token 重连或重新配对。
131
+ 7. 收到 close code `4004`:移动网关已关闭,停止自动重连。
132
+
35
133
  ---
36
134
 
37
135
  ## 2. 连接管理
@@ -219,7 +317,8 @@
219
317
 
220
318
  | kind | 触发时机 |
221
319
  |---|---|
222
- | `hello` | 连接成功:`{ "kind":"hello", "protocol":1, "port":3080, "clients":1 }` |
320
+ | `paired` | 首次配对成功;仅此一次返回长期设备 token |
321
+ | `hello` | 连接成功:`{ "kind":"hello", "protocol":2, "authenticated":true, "port":3080, "clients":1 }` |
223
322
  | `event` | 任意会话的 agent 输出(见下) |
224
323
  | `pong` / `subscribed` / `sent` | 对应请求的回复 |
225
324
 
@@ -253,8 +352,11 @@
253
352
 
254
353
  ## 13. 安全注意
255
354
 
256
- - 当前 `/ws/mobile` **无认证**:任何能连到端口的人都能读会话、发消息、改配置(含全局默认值)
257
- - 仅限本机/受信局域网使用;**暴露公网前必须加 token 认证**
355
+ - `/ws/mobile` 的移动网关默认关闭;本机 WebUI 手动开启后,若 5 分钟内没有设备成功连接会自动关闭
356
+ - 网关开启后仍要求已配对设备凭证;不要把 `requireAuth` 设为 `false` 后暴露到网络
357
+ - `/mgw/*` 是配对/吊销管理面,默认只允许本机访问;公网代理只应转发 `/ws/mobile`
358
+ - DSH HTTP Server 本身没有 TLS、认证或 Origin policy;公网必须使用 TLS 反向代理和 `wss://`
359
+ - 长期 token 只保存在 iOS Keychain;服务端磁盘仅保存摘要
258
360
  - `set-default` / `save-default-model` 是全局写操作,客户端 UI 应加确认
259
361
 
260
362
  ---
@@ -276,7 +378,8 @@
276
378
  | v0.1.15 | save-default-model |
277
379
  | v0.1.16 | fork(新对话分支) |
278
380
  | v0.1.17 | models 支持无 sessionId 全局目录;新增 providers |
381
+ | v0.3.0 | 默认设备鉴权;一次性二维码配对;摘要化凭证存储;WebUI 设备面板;在线状态和即时吊销 |
279
382
 
280
383
  ---
281
384
 
282
- *协议与插件源码同源维护:`dsh-plugin-mobile-gateway/lib/index.js` 顶部注释即协议摘要。*
385
+ *协议与插件源码同源维护:`dsh-plugin-mobile-gateway/lib/index.mjs` 顶部注释即协议摘要。*
package/README.md CHANGED
@@ -1,56 +1,252 @@
1
1
  # dsh-plugin-mobile-gateway
2
2
 
3
- dsh 的持久化 WebSocket 网关:在 web 服务器上注册 `/ws/mobile`,转发 agent 实时输出给移动端,并提供会话/工作区/模型/权限/默认配置/分支等 24 个信令。
3
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 提供经过设备鉴权的持久化 WebSocket 网关,让 iOS 等移动客户端能够查看工作区和历史会话、接收 Agent 实时输出、发送任务,以及调整会话模型与权限。
4
4
 
5
- - **协议文档**:[`PROTOCOL.md`](PROTOCOL.md)(与源码 `lib/index.js` 顶部注释同源)
6
- - **日志**:`/tmp/mobile-gateway.log`(连接/查询/错误),宿主 stdout 同步输出
5
+ ![DeepSeek Harness 移动设备管理面板](docs/assets/mobile-device-management.png)
7
6
 
8
- ## 它是如何生效的
7
+ - WebSocket 端点:`/ws/mobile`
8
+ - 管理入口:Harness WebUI 左侧边栏底部的“移动设备”
9
+ - 默认安全策略:网关默认关闭、设备鉴权默认开启、管理接口仅允许本机访问
10
+ - 完整通信格式:[`PROTOCOL.md`](PROTOCOL.md)
9
11
 
12
+ ## 直接安装(推荐,无需下载源码)
13
+
14
+ ### 1. 安装插件
15
+
16
+ 确保本机已经安装并能正常运行 DeepSeek Harness,然后执行:
17
+
18
+ ```bash
19
+ dsh plugin --profile web add dsh-plugin-mobile-gateway
20
+ ```
21
+
22
+ 这条命令会直接从 npm 获取插件、安装依赖,并将插件加入 `web` profile 的 bundle 列表。用户不需要 clone 仓库,也不需要运行 `pnpm install`。
23
+
24
+ 如果希望固定版本,可以在包名后指定版本号:
25
+
26
+ ```bash
27
+ dsh plugin --profile web add dsh-plugin-mobile-gateway@0.4.0
28
+ ```
29
+
30
+ 也可以不经过 npm,直接安装 GitHub 版本:
31
+
32
+ ```bash
33
+ dsh plugin --profile web add github:Clarklevis1995/dsh-plugin-mobile-gateway
34
+ ```
35
+
36
+ ### 2. 重启 Harness WebUI
37
+
38
+ 插件组合树只在 WebUI 启动时加载。安装完成后,停止当前的 `dsh web` 进程并重新启动它。刷新浏览器本身不足以加载新安装的服务端插件。
39
+
40
+ 可以在启动前检查插件是否进入组合配置:
41
+
42
+ ```bash
43
+ dsh --profile web --dump-config | grep -A3 mobile-gateway
44
+ ```
45
+
46
+ 启动 WebUI 后,左侧边栏底部应出现“移动设备”入口。
47
+
48
+ ### 3. 一条命令配置公网 IP(推荐,无需域名)
49
+
50
+ 如果 Harness 部署在带固定公网 IPv4 的腾讯云 Ubuntu/Debian 服务器上,先在腾讯云安全组中放行入站 TCP `80` 和 `443`,然后在服务器执行:
51
+
52
+ ```bash
53
+ sudo npx --yes dsh-plugin-mobile-gateway setup
10
54
  ```
11
- ① package.json 声明 dsh.bundle.patch(门槛:有它才算 dsh 插件)
12
- dsh plugin --profile web add <路径> → pnpm 安装 + 自动加入 dsh.profile.bundles
13
- ③ 启动 dsh web → 按 bundles 顺序叠加各 bundle 的 cordis.patch.yml → 插件生效
55
+
56
+ 安装器会自动从腾讯云实例元数据读取公网 IPv4,并完成:
57
+
58
+ - 安装 Nginx 和独立 Python 虚拟环境中的 Certbot
59
+ - 为公网 IP 申请受系统信任的短期 TLS 证书
60
+ - 只把 `wss://公网IP/ws/mobile` 代理到 `127.0.0.1:3080`
61
+ - 对普通 HTTP、WebUI、`/mgw/*` 管理接口和其他路径返回 `404`
62
+ - 创建每天两次运行的 systemd 证书续期任务
63
+ - 把公网地址写入 `/etc/dsh-mobile-gateway/public-url`,插件启动时自动读取
64
+
65
+ 因此 DSH 仍然只需监听 `127.0.0.1:3080`,不需要把 3080 端口暴露到公网,也不需要 Tunnel、域名或手写 Nginx 配置。
66
+
67
+ 邮箱是可选项;希望接收 Let's Encrypt 账户通知时可追加 `--email you@example.com`。如果无法从腾讯云元数据识别公网地址,或 DSH 使用了其他本地端口:
68
+
69
+ ```bash
70
+ sudo npx --yes dsh-plugin-mobile-gateway setup \
71
+ --ip 203.0.113.10 \
72
+ --port 3080 \
73
+ --email you@example.com
14
74
  ```
15
75
 
16
- `dsh.bundle.patch` 缺失时 `dsh plugin add` 只装依赖、**不生效**。
76
+ 执行完成后重启 `dsh web`,打开 WebUI 的“移动设备”面板。公网 WebSocket 地址会自动显示为 `wss://公网IP/ws/mobile`;开启移动网关并生成二维码即可。
17
77
 
18
- ## 管理命令速查
78
+ 查看状态或移除安装器生成的公网入口:
19
79
 
20
80
  ```bash
21
- # 安装(file: 复制安装,自包含,依赖解析最稳)
22
- dsh plugin --profile web add file:/Users/lichaofan/DeepseekHarnessProject/dsh-plugin-mobile-gateway
81
+ sudo npx --yes dsh-plugin-mobile-gateway status
82
+ sudo npx --yes dsh-plugin-mobile-gateway remove
83
+ ```
84
+
85
+ `remove` 只删除本插件生成的 Nginx 配置、地址文件和续期任务,不卸载软件,也不删除已有证书。IP 证书有效期约 6 天,必须保持 80 端口可达以便自动续期。相关能力来自 [Let's Encrypt 的短期 IP 地址证书](https://letsencrypt.org/2026/03/11/shorter-certs-certbot/);公网 IP 自动识别使用[腾讯云实例元数据](https://cloud.tencent.com/document/product/213/17940)。
86
+
87
+ #### 已有域名或反向代理
88
+
89
+ 打开“移动设备”面板:
90
+
91
+ 1. 开启“允许移动设备连接”。
92
+ 2. 保持“设备鉴权”开启。
93
+ 3. 在“公网 WebSocket 地址”中填写手机可以访问的地址。
94
+
95
+ 本机浏览器调试可以使用:
96
+
97
+ ```text
98
+ ws://127.0.0.1:3080/ws/mobile
99
+ ```
100
+
101
+ 真机不能使用 `127.0.0.1`,因为它在手机上指向手机自身。局域网或公网真机连接应通过 TLS 反向代理提供:
102
+
103
+ ```text
104
+ wss://gateway.example.com/ws/mobile
105
+ ```
106
+
107
+ 非 localhost 的配对地址会强制要求 `wss://`,以免一次性配对信息和长期连接暴露在明文网络中。
108
+
109
+ ### 4. 配对 iOS 客户端
110
+
111
+ 1. 在 WebUI 中填写设备名称,例如 `iPhone`。
112
+ 2. 点击“生成配对二维码”。
113
+ 3. 在 iOS 客户端首页点击认证按钮:
114
+ - 选择“扫描二维码”,扫码后自动连接;或
115
+ - 选择“手动输入配对信息”,粘贴 WebUI 中复制的 Base64URL 配对 Token,再点击连接。
116
+ 4. iOS 首次连接成功后会把长期设备凭证保存到 Keychain。配对二维码只能使用一次,并会在 5 分钟后过期。
117
+ 5. WebUI 的可信设备列表显示“在线”,iOS 首页连接状态变为绿色,即表示连接完成。
118
+
119
+ 后续启动 iOS 客户端时会使用 Keychain 中的长期凭证重新连接,不需要再次扫码。重新配对同一套 iOS 安装也会复用其设备身份,不会重复创建可信设备。
120
+
121
+ ## 日常使用
122
+
123
+ 连接成功后,移动客户端可以:
124
+
125
+ - 浏览工作区、未分组会话和服务端目录
126
+ - 创建工作区与新会话
127
+ - 加载历史消息与轨迹
128
+ - 实时接收思考、工具调用和最终回答
129
+ - 向远端 Agent 发送任务
130
+ - 查询或切换会话模型、推理等级和访问权限
131
+ - 查看上下文用量与会话统计
132
+
133
+ 关闭“允许移动设备连接”会立即断开移动端,但不会影响普通 Harness WebUI。网关开启后,如果默认 5 分钟内没有可信设备成功连接,会自动关闭。
134
+
135
+ ## 可信设备管理
136
+
137
+ 长期设备凭证只在首次配对成功时返回一次,移动端应保存在 Keychain。服务端仅保存凭证的 SHA-256 摘要:
23
138
 
24
- # 更新(file: 是复制安装——改源码后必须 remove + add 才会刷新副本)
139
+ ```text
140
+ ~/.dsh/mobile-gateway-devices.json
141
+ ```
142
+
143
+ 在 WebUI 的“可信设备”列表中点击“吊销”会:
144
+
145
+ - 从可信设备列表删除该设备
146
+ - 立即关闭该设备现有的 WebSocket 连接
147
+ - 使该设备保存的长期凭证永久失效
148
+
149
+ 被吊销的设备再次连接会收到 `401 Unauthorized`,需要重新配对。
150
+
151
+ ## 更新插件(无需源码)
152
+
153
+ 插件会被安装到 profile 中,不会自动更新。升级时重新安装最新 npm 版本并重启 WebUI:
154
+
155
+ ```bash
25
156
  dsh plugin --profile web remove dsh-plugin-mobile-gateway
26
- dsh plugin --profile web add file:/Users/lichaofan/DeepseekHarnessProject/dsh-plugin-mobile-gateway
157
+ dsh plugin --profile web add dsh-plugin-mobile-gateway
158
+ ```
159
+
160
+ 然后停止并重新启动 `dsh web`。
27
161
 
28
- # 卸载
162
+ ## 卸载
163
+
164
+ ```bash
29
165
  dsh plugin --profile web remove dsh-plugin-mobile-gateway
30
- # 并从 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 里删除该包名
166
+ ```
31
167
 
32
- # 验证组合树(不启动)
33
- dsh --profile web --dump-config | grep -A3 mobile-gateway
168
+ 如果旧版 DSH 没有自动清理 bundle,再从 `~/.dsh/profiles/web/package.json` 的 `dsh.profile.bundles` 中删除 `dsh-plugin-mobile-gateway`,随后重启 WebUI。
169
+
170
+ 卸载插件不会自动删除 `~/.dsh/mobile-gateway-devices.json`。
171
+
172
+ ## 手动公网部署
173
+
174
+ 不使用一键安装器时,公网入口应由 Nginx、Caddy 或其他反向代理提供 HTTPS/WSS,并且只公开 WebSocket 路由,不要公开 `/mgw` 管理接口或整个未鉴权 WebUI。
175
+
176
+ Nginx 核心配置示例:
177
+
178
+ ```nginx
179
+ location = /ws/mobile {
180
+ proxy_pass http://127.0.0.1:3080;
181
+ proxy_http_version 1.1;
182
+ proxy_set_header Upgrade $http_upgrade;
183
+ proxy_set_header Connection "upgrade";
184
+ }
185
+ ```
186
+
187
+ TLS 证书、域名、防火墙、访问日志保护和代理层速率限制由部署环境负责。
188
+
189
+ 如需把公网地址写入 profile,可在 `cordis.patch.yml` 的 `mobile-gateway` 配置段覆盖:
190
+
191
+ ```yaml
192
+ - id: mobile-gateway
193
+ config:
194
+ path: /ws/mobile
195
+ gatewayEnabled: false
196
+ gatewayWaitTimeoutMs: 300000
197
+ requireAuth: true
198
+ adminLoopbackOnly: true
199
+ publicUrl: wss://gateway.example.com/ws/mobile
200
+ publicUrlFile: /etc/dsh-mobile-gateway/public-url
201
+ pairingTtlMs: 300000
202
+ allowQueryToken: false
34
203
  ```
35
204
 
36
- ## 重要注意事项
205
+ 后置 patch 会替换整个配置段,因此覆盖时应保留所有需要的字段。
37
206
 
38
- | 事项 | 说明 |
207
+ ## 常见问题
208
+
209
+ | 现象 | 原因与处理方式 |
39
210
  |---|---|
40
- | **改代码必须重装** | `file:` 是复制,改 `lib/index.js` 后需 remove + add(或 bump package.json 版本) |
41
- | **重装后必须重启** | composition 启动时解析,web profile HMR 被禁用:`dsh web` 重启才生效 |
42
- | **不要用 link:** | `link:` 符号链接会从源码真实路径解析依赖(`ws` 会找不到);`file:` 复制进 profile node_modules 才稳 |
43
- | **补丁覆盖是整段替换** | 覆盖现有行要重述全部 config key;行 id 不能与现有行冲突 |
44
- | **认证缺失** | `/ws/mobile` 无鉴权,暴露公网前必须加 token |
211
+ | WebUI 没有“移动设备”入口 | 确认使用 `--profile web` 安装;执行 `--dump-config` 检查组合树,然后完整重启 `dsh web` |
212
+ | iOS 连接提示 `503` | 移动网关尚未开启,回到 WebUI 开启“允许移动设备连接” |
213
+ | iOS 连接提示 `401` | 配对码过期、长期凭证无效或设备已被吊销;删除客户端旧凭证后重新配对 |
214
+ | 真机无法连接 `127.0.0.1` | `127.0.0.1` 在手机上不是电脑;配置手机可访问的 `wss://` 地址 |
215
+ | 一键配置无法识别公网 IP | 确认命令运行在腾讯云 CVM 内,或通过 `--ip <公网 IPv4>` 显式指定 |
216
+ | Certbot 申请或续期失败 | 确认腾讯云安全组和服务器防火墙都允许入站 TCP 80/443,并确认公网 IP 没有变化 |
217
+ | 想检查自动续期 | 执行 `sudo systemctl status dsh-mobile-gateway-cert-renew.timer` 和 `sudo npx --yes dsh-plugin-mobile-gateway status` |
218
+ | 二维码无法再次使用 | 配对码设计为一次性且 5 分钟过期,重新生成即可 |
219
+ | 修改配置后没有生效 | 插件与 profile composition 在启动时加载,需要重启 WebUI 进程 |
220
+ | 需要排查服务端原因 | 查看 `/tmp/mobile-gateway.log`,其中包含连接、鉴权、查询与错误记录 |
221
+
222
+ ## 安全说明
223
+
224
+ - 不建议关闭设备鉴权。该开关仅用于本机 Debug,重启后会恢复安全默认值。
225
+ - `/mgw/*` 默认只接受 loopback 请求,不应通过公网反向代理暴露。
226
+ - 长期 token 默认禁止通过 URL query 传输,避免进入代理日志、浏览器历史或监控系统。
227
+ - 配对二维码和长期 token 不会明文写入服务端磁盘。
45
228
 
46
- ## 本地测试
229
+ ## 源码开发
230
+
231
+ 只有需要修改插件本身时才需要源码安装:
47
232
 
48
233
  ```bash
49
- # 完整分发链路测试(mock harness + 真实 ws 客户端,当前 34 个断言)
50
- NODE_PATH=/Users/lichaofan/.npm/_npx/1e7f6d9597241db0/node_modules \
51
- node test/gateway.test.js
234
+ dsh plugin --profile web add file:/absolute/path/to/dsh-plugin-mobile-gateway
52
235
  ```
53
236
 
54
- ## 版本历史
237
+ `file:` 是复制安装。修改源码后必须先 remove、再 add,并重启 WebUI;不要使用 `link:`,否则依赖可能从源码目录解析而导致 `ws` 等包无法找到。
238
+
239
+ 测试命令:
240
+
241
+ ```bash
242
+ # 鉴权、配对与吊销
243
+ NODE_PATH=/path/to/dsh/node_modules node test/auth.test.mjs
244
+
245
+ # mock Harness + 真实 WebSocket 客户端的完整协议链路
246
+ NODE_PATH=/path/to/dsh/node_modules node test/gateway.test.mjs
247
+
248
+ # 公网 IP 安装器的参数、Nginx 隔离与续期配置
249
+ node test/setup-ip.test.mjs
250
+ ```
55
251
 
56
- `PROTOCOL.md` §14
252
+ 版本历史与全部 WebSocket 消息类型见 [`PROTOCOL.md`](PROTOCOL.md)