dsh-mobile 0.5.4 → 0.6.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/README.md CHANGED
@@ -31,14 +31,14 @@
31
31
 
32
32
  > DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
33
33
  >
34
- > **当前版本:0.5.4**。修复配对 Cookie 冲突与第三方插件设置保存,优化输入字号和触屏发送后的键盘处理,新增可选 WebSocket 压缩与慢链路排查指南。[更新记录](CHANGELOG.md#054---2026-10-02)。
34
+ > **当前正式版本:0.6.0**。新增移动页面模块管理、App 图标选择与设备排序,升级扫码体验,改善移动布局、远程加载和旧 WebView 兼容。[更新记录](CHANGELOG.md#060---2026-10-07)。
35
35
  >
36
- > **升级提醒**:建议同步更新插件与 Android App 至 0.5.4,已有配对可继续使用。App 同步修正 WebView Cookie 写入,便于恢复受旧 Cookie 干扰的连接。[兼容说明](#兼容性)。
36
+ > **升级提醒**:请同步更新插件与 Android App 至 0.6.0,以获得完整功能与修复;同一正式签名的旧 App 可原位升级并保留配对。[兼容说明](#兼容性)。
37
37
 
38
38
  <p align="center">
39
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.4/dsh-mobile-android-v0.5.4.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
40
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.4/dsh-mobile-android-v0.5.4.apk"><strong>下载 Android App 0.5.4</strong></a><br>
41
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.5.4">版本说明与校验文件</a></sub>
39
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.6.0/dsh-mobile-android-v0.6.0.apk"><img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
40
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.6.0/dsh-mobile-android-v0.6.0.apk"><strong>下载 Android App 0.6.0</strong></a><br>
41
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.0">版本说明与校验文件</a></sub>
42
42
  </p>
43
43
 
44
44
  DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、cloudflared、自建 FRP 或自有反向代理远程通道连接电脑,继续使用同一份会话、工作区、消息和工具。电脑端分别启停局域网与远程访问、分别管理配对授权;Android App 统一显示已配对电脑。插件不修改 DeepSeek Harness 源码。
@@ -60,9 +60,42 @@ DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App
60
60
 
61
61
  配对设备可以操作电脑上的 DSH,应视为完全受信任的设备。局域网只在可信网络开启;远程访问应使用可靠的 HTTPS 通道。手机丢失时,请立即从电脑端撤销设备。
62
62
 
63
+ ## 0.5.5 移动体验改进
64
+
65
+ <details>
66
+ <summary>已发布版本的输入栏、返回键与旋转改进</summary>
67
+
68
+ 屏幕宽度不超过 720px 时,未编辑的输入栏保留可滚动的 72px 草稿预览,为会话留出更多空间;编辑区或工具栏获得焦点后恢复展开。文字、引用和附件保留,发送、排队、引导与停止按钮的触摸区域至少为 44px。提交和输入法行为继续由既有 DSH 与 App 机制处理。
69
+
70
+ 模型搜索框与配对页的输入字号已补齐,Android 返回键可逐级退出模型菜单。App 旋转和调整窗口大小时保留当前页面、引用与未发送图片,已收到的“电脑端已移除”状态不会被后续检测覆盖。请同步更新插件与 App;App 进程终止后,未发送附件仍可能丢失。
71
+
72
+ </details>
73
+
74
+ ## 0.6.0 更新
75
+
76
+ 本次更新聚焦模块管理、App 个性化与移动访问体验;完整记录见 [CHANGELOG](CHANGELOG.md#060---2026-10-07)。
77
+
78
+ - **连接与加载**:重试临时失败的静态资源请求,长时间 DSH API 操作不再被普通传输超时截断;不重放 API 或写入请求。[慢链路说明](docs/SLOW_CONNECTIONS.md)
79
+ - **移动布局**:输入栏更紧凑,改善键盘打开和大字号时的按钮可达性;支持独立移动字号、触屏浏览器 Enter 换行,以及窄屏面板“返回会话”。
80
+ - **Android 体验**:可选鲸鱼娘及六种鲸鱼标志配色;改进扫码预览、对焦与缩放。设备列表支持固定顺序、上移/置顶,长按左上角抽屉按钮可切换电脑;启动仍优先连接最近使用的电脑。
81
+ - **模块管理**:电脑默认与本设备选择分别保存,检查启动必需模块和依赖,不卸载插件、不自动刷新当前会话。旧选择失效时提供本设备恢复入口。[操作指南](docs/CLIENT_MODULES.md)
82
+ - **网络与兼容**:托管局域网可追加明确的可信来源网段;补齐部分旧 WebView 缺失 API,完善扩展调用超时与取消。[可信网段](docs/TRUSTED_NETWORKS.md) · [WebView 说明](#app-与手机浏览器)
83
+
84
+ > **托管 Caddy 尚未开放安装。**代码与隔离 TLS 验证已完成,但可信固定二进制尚未分发;现有自备代理和其他通道不受影响。[状态与条件](docs/CADDY_MANAGED.md)
85
+
63
86
  ## 快速开始
64
87
 
65
- 已经安装 `dsh` 命令:
88
+ 先在电脑安装插件,再用 Android App 或手机浏览器配对。按你使用的 DSH 选择安装方式。
89
+
90
+ ### 官方 DSH Desktop
91
+
92
+ 在应用的 **插件** 页面安装并启用 `dsh-mobile`,再打开 **移动访问** 配置局域网或远程通道。Desktop 的 profile 由应用管理,不适用下面的 `--profile web` 命令。
93
+
94
+ 安装或更新后,请从应用菜单正常退出并重新打开 Desktop。若提示 `dsh-mobile-question-fixes: failed to import`,先完成这次重启,让 Desktop 重新读取附带组件;无需清除会话或配对数据。
95
+
96
+ ### DSH Web
97
+
98
+ 已经安装 `dsh` 命令时,在终端执行:
66
99
 
67
100
  ```powershell
68
101
  dsh plugin --profile web add dsh-mobile@latest
@@ -70,17 +103,18 @@ dsh plugin --profile web exec dsh-mobile setup
70
103
  dsh --profile web
71
104
  ```
72
105
 
73
- 直接使用 DeepSeek Harness 源码:
106
+ <details>
107
+ <summary>使用 DeepSeek Harness 源码或社区插件市场</summary>
108
+
109
+ 在 DeepSeek Harness 源码目录中执行:
74
110
 
75
111
  ```powershell
76
- corepack enable; pnpm install
112
+ pnpm install
77
113
  pnpm dsh plugin --profile web add dsh-mobile@latest
78
114
  pnpm dsh plugin --profile web exec dsh-mobile setup
79
115
  pnpm dsh --profile web
80
116
  ```
81
117
 
82
- 使用官方 DSH Desktop:在应用的 **插件** 页面安装并启用 `dsh-mobile`,再打开 **移动访问** 完成局域网配置或选择远程通道。Desktop 的 profile 由应用管理,不适用上面的 `--profile web` 命令。
83
-
84
118
  也可以通过插件市场安装(可选):
85
119
 
86
120
  ```powershell
@@ -89,9 +123,13 @@ dsh plugin --profile web add dshmarket
89
123
 
90
124
  重启 DSH 后,在 **设置 → 插件市场** 里搜索 dsh-mobile 并安装。首次打开“移动访问”时,局域网页会列出电脑当前网络;确认网卡后由插件生成私有证书并配置局域网,按提示重启一次 DSH 即可使用,无需再打开终端运行 `setup`。
91
125
 
126
+ </details>
127
+
92
128
  `setup` 会自动选择并记住当前局域网,切换 Wi-Fi、热点或 IP 后通常自动恢复;仅在自动选择失败时使用 `--address 192.168.x.x`。设置、证书、设备和自定义文件保存在 `$DSH_HOME/mobile-access/`。
93
129
 
94
- 安装并启动 DSH 后,按照下一节选择局域网或远程连接。
130
+ ### 安装手机 App
131
+
132
+ 从上方链接下载正式 Android APK,或用手机浏览器打开电脑端生成的配对链接。随后按[连接教程](#连接教程)选择局域网或远程访问;手机不需要安装额外的隧道客户端。
95
133
 
96
134
  通过 npm 安装的插件会在桌面界面加载时检查新版本,有更新时在访问面板标题右侧显示“更新插件”,安装后需重启 DSH。App 下载入口展示最新版本;本地开发包不会被自动覆盖,Android App 不会主动检查或推送版本更新。
97
135
 
@@ -114,8 +152,15 @@ dsh plugin --profile web add dshmarket
114
152
 
115
153
  端口说明:`dsh web --port` 修改 DSH Web 上游端口(默认 3080),插件会自动跟随;`dsh-mobile setup --port` 修改 Mobile HTTPS 监听端口(默认 3443),配对二维码会包含实际端口。
116
154
 
155
+ <details>
156
+ <summary>无界面主机、内网代理与额外来源网段</summary>
157
+
117
158
  无图形界面的 Linux 主机,或通过局域网 IP / 反向代理打开 DSH Web 时,左下角 **移动访问** 管理口同样可用。管理 API 仍要求 TCP 对端是本机回环(例如本机 `socat` / 反向代理连到 `127.0.0.1`),不会把插件自己暴露到公网;浏览器 Host 可以是 `localhost`、RFC1918 或 IPv4 链路本地地址,公网 IP 和任意域名仍会返回 403。反向代理必须只对可信的本机或内网请求开放,不能把 `/api/mobile-access` 管理路径公开转发到互联网。手机走的独立 HTTPS 入口(默认 3443)仍是移动端界面,不会变成桌面管理口。
118
159
 
160
+ 已有 WireGuard 等受控路由网络时,0.6.0 提供[额外可信网段](docs/TRUSTED_NETWORKS.md)。它只追加明确的来源 CIDR,不建立 VPN 或路由,也不自动改 Windows 防火墙。
161
+
162
+ </details>
163
+
119
164
  不安装 App 也可以访问:点击 **复制配对链接**,在手机浏览器中打开。局域网使用私有证书,浏览器首次访问可能提示不受信任;只在确认这是自己的 DSH 网关后按浏览器指引继续。公开证书的远程入口不应出现证书警告,若出现请检查通道和证书,不要直接忽略。
120
165
 
121
166
  手机浏览器中的配对和重新连接页面会按浏览器的 `Accept-Language` 显示简体中文、英文或意大利文;Android App 则跟随系统语言。DSH 内的插件控制面板继续跟随 DSH 当前语言。
@@ -128,20 +173,39 @@ dsh plugin --profile web add dshmarket
128
173
 
129
174
  页面能打开但一直“重连中”,应先区分 WebSocket 升级失败、慢速数据同步和心跳超时,操作见[慢链路与反复重连指南](docs/SLOW_CONNECTIONS.md)。0.5.4 还提供按确切路径启用的 WebSocket 压缩,默认关闭;长会话或计量网络可按指南尝试,并对比实际传输量。
130
175
 
176
+ | 连接方式 | 需要准备 | 地址特点与注意事项 |
177
+ | --- | --- | --- |
178
+ | cpolar | cpolar 账号与 Authtoken | 国内网络可优先尝试;免费临时地址可能变化 |
179
+ | cloudflared 快速隧道 | 无需账号或域名 | 临时随机地址;不支持 SSE,[即时通知存在限制](docs/CLOUDFLARE_TUNNEL.md#快速隧道的功能限制) |
180
+ | cloudflared 命名隧道 | Cloudflare 账号与域名 | 固定域名;[配置指南](docs/CLOUDFLARE_TUNNEL.md) |
181
+ | Tailscale Funnel | Tailscale 登录与 Funnel 授权 | 中国大陆网络可能不稳定 |
182
+ | 自建 FRP | 已有 VPS,域名或真实公网 IPv4 | 需管理服务器;[部署](docs/SELF_HOSTED_FRP.md)或[接入既有 frps](docs/ATTACH_EXISTING_FRPS.md) |
183
+ | 自有反向代理 | 已有公开 HTTPS 代理 | 插件只提供私有认证后端;[配置指南](docs/SELF_HOSTED_ORIGIN.md) |
184
+
131
185
  <p align="center">
132
186
  <img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/screenshots/remote-access.png" width="82%" alt="DSH Mobile 远程访问与通道选择">
133
187
  </p>
134
188
 
135
- 1. 在 DeepSeek Harness 左下角打开 **移动访问 → 远程**,选择一种连接方式:
189
+ 1. 在 DeepSeek Harness 左下角打开 **移动访问 → 远程**,按上表选择通道,完成登录或配置。
190
+ 2. 状态变为“远程访问已就绪”后,点击 **生成远程配对二维码**。自有反向代理仅显示“后端已监听”,仍需确认公网 HTTPS 与 WebSocket 可用。
191
+ 3. 在 Android App 中选择 **远程访问**,扫描当前二维码完成配对;公开证书入口也可用手机浏览器打开配对链接。
192
+ 4. 以后 App 使用保存的地址与凭据自动重连。临时地址改变时,重新扫描电脑端当前二维码验证新地址,不需要清除 App 数据;旧设备 token 不会发送给新 Origin。
193
+
194
+ <details>
195
+ <summary>各通道的安装与登录步骤</summary>
196
+
197
+ 在面板选择对应通道后:
136
198
  - **Tailscale Funnel**:点击 **启用远程访问**,在打开的官方页面完成一次 Tailscale 登录;按面板提示继续允许 Funnel,然后返回 DSH 等待连接就绪。
137
199
  - **cpolar**:点击 **安装官方组件**,登录 cpolar 控制台取得 Authtoken,粘贴后点击 **保存并连接**。组件只会在确认后下载到插件私有目录;免费临时地址可能在 DSH 或 cpolar 重启后变化。
138
200
  - **自建 FRP(高级)**:已有 VPS 时,展开 **自建连接**,填写 frps 信息和公开 HTTPS 域名或真实公网 IPv4。可以复制受限的 frps + Caddy 模板手动部署,也可以在 Ubuntu/Debian + systemd 上用 SSH 密钥一键部署;不支持密码登录,也不会覆盖非 DSH Mobile 管理的 Caddy 配置。部署或清理前须到 VPS 控制台核对主机指纹。本机 `frpc` 按需下载校验,VPS 需单独清理。需要 Android App 0.3.3 或更新版本;证书、端口和操作步骤见 [自建 FRP 使用指南](docs/SELF_HOSTED_FRP.md)。
139
201
  - **自有反向代理**:展开 **自建连接 → 自有反向代理**,填写公网 HTTPS 地址(支持自定义端口)、私有监听 IPv4、独立 HTTP 后端端口(默认 3444)和代理来源 CIDR,再点击 **保存并启动后端**。适合已有 Lucky/Nginx/Caddy 的用户,无需隧道组件;需要 Android App 0.4.0 或更高版本。详见 [自有反向代理指南](docs/SELF_HOSTED_ORIGIN.md)。
140
202
  - **cloudflared 快速隧道**:点击 **安装官方组件**,插件在确认后从官方发布页下载固定版本到插件私有目录,随后自动申请一个临时公网地址(quick tunnel),**无需注册或登录**。适合不想注册账号的用户;quick tunnel 地址每次重连都会变化,官方定位为测试用途、有限流且无可用性保证,请勿用于必须长期可达的生产访问,只适合临时或验证场景。
141
203
  - **cloudflared 命名隧道**:已有 Cloudflare 账号和域名时,把隧道类型切到 **命名隧道**,填入连接器令牌、公网域名与本机转发端口,即可获得重启后不变的固定地址(令牌只存私有目录、只经环境变量传给 cloudflared)。步骤见 [Cloudflare 命名隧道](docs/CLOUDFLARE_TUNNEL.md)。
142
- 2. 状态变为“远程访问已就绪”后,点击 **生成远程配对二维码**。自有反向代理仅显示“后端已监听”:它不验证公网连通性,仍需检查代理 HTTPS、证书与 WebSocket 并用手机验收。
143
- 3. 在 Android App 中进入 **远程访问**,扫描二维码完成独立配对。
144
- 4. 此后 App 会保存当前地址和设备凭据并自动重连。若 cpolar 免费临时地址发生变化,请扫描电脑端当前远程二维码重新验证连接;无需清除 App 数据。旧设备 token 只会发送到原先保存的精确 Origin,不会发送给二维码中的新域名。
204
+
205
+ </details>
206
+
207
+ <details>
208
+ <summary>远程通知、服务器运维与安全限制</summary>
145
209
 
146
210
  > **远程通知说明**:浏览器的 `Notification` 权限按 Origin 分别授权,网页系统通知只显示在运行该网页的设备上。Android App 的任务提醒是独立的 0.4.0 功能,需要在 App 内 DSH 的 **设置 → 通用设置** 中主动开启,且依赖 WebView 页面仍存活;它不是通用的后台推送。需要可靠的后台推送时,请使用你已配置的服务端 webhook 或机器人通道。
147
211
 
@@ -151,7 +215,9 @@ Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。
151
215
 
152
216
  自有反向代理的 HTTP 后端只允许留在可信私网;**不要把它映射到公网,也不要绕过它直连 DSH 或现有 LAN 3443**。来源 CIDR 匹配代理的直接 TCP 来源,不信任转发头;反代须保留外部 Host(含端口)、Origin、Cookie 和 WebSocket。清除代理配置不会删除已配对远程设备。
153
217
 
154
- 远程公开地址仍受 DSH 设备配对保护。内置 Funnel 与托管 cpolar、cloudflared 支持 Windows x64 与 Linux x64/arm64;按需安装的 FRP 0.70.1 支持 Windows、Linux、macOS 的 x64 与 arm64。各通道的系统支持矩阵见[兼容性](#兼容性)。
218
+ 各通道的系统和组件支持范围见[兼容性](#兼容性)。本机托管的程序、凭据与配置可从面板清理;VPS 上的文件必须按对应指南单独清理。
219
+
220
+ </details>
155
221
 
156
222
  ## 扩展与自定义
157
223
 
@@ -188,14 +254,15 @@ Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。
188
254
 
189
255
  ## 设备管理
190
256
 
191
- Android App 用一个“已配对设备”列表同时显示多台电脑:局域网、cpolar、cloudflared、Tailscale Funnel 和自建 FRP 配对共处一处。首次升级会自动迁移旧版局域网与远程凭据,不要求重新配对;地址变化且 `instanceId` 与原记录一致时会合并条目,保留自定义名称。自签 FRP 使用独立 CA 指纹作为身份,首次与局域网配对时可能显示为另一条记录。设备 Token 和局域网 CA 由 Android Keystore 加密保存;0.4.6 App 还会加密保存自签 FRP 入口固定的远程 CA。这些信息不会显示在设备列表或二维码中。
257
+ Android App 在“已配对设备”中统一显示电脑及其局域网/远程连接记录。两种方式的授权彼此独立,同一台电脑可能有两条记录。升级会迁移旧凭据,无需重新配对;重新验证变化的地址后,相同身份的记录会合并并保留名称。自签 FRP 使用独立 CA 指纹,可能另成一条记录。Token 和固定的 CA 由 Android Keystore 加密;配对二维码含短期配对信息,不含长期设备 Token 或 CA 私钥。
192
258
 
193
259
  每条记录显示自定义名称、连接方式、Origin、定期更新的可达状态和最近连接时间。绿色状态点表示“可达”,灰色状态点表示“检测中”“暂不可达”“配对已过期”或“电脑端已移除”;可达性检查直接验证 DSH Gateway,不依赖 ICMP,也不会把暂时断网误判成电脑端撤销。
194
260
 
195
- - **启动时打开 → 直接进入 DSH**(默认):单设备直接连接;多设备优先连接上次使用的设备,其次按最近连接时间选择仍有效的设备。连接超过有限重试预算后自动回到列表,不会无限转圈。
196
- - **启动时打开 → 显示设备列表**:每次启动先选择电脑,适合经常在多台设备之间切换。该选项位于列表右上角的设置按钮中,修改后立即保存。
261
+ - **启动行为 → 直接进入 DSH**(默认):单设备直接连接;多设备优先连接上次使用的设备,其次按最近连接时间选择仍有效的设备。连接超过有限重试预算后自动回到列表,不会无限转圈。
262
+ - **启动行为 → 显示设备列表**:每次启动先选择电脑,适合经常在多台设备之间切换。入口是列表右上角 **设置 → 启动行为**,修改后立即保存。
197
263
  - 点按设备行可连接;右侧“…”和长按提供相同的操作面板,可编辑名称、立即检测、重新配对或删除本地记录。删除前会二次确认,并提供短暂撤销;撤销只恢复本机记录,不恢复电脑端已经撤销的授权。
198
- - 在 WebView 页面打开 DSH **设置 → 通用设置**,选择 **切换电脑** 可回到已配对设备列表;该动作仅在 Android App 中显示。在线收到电脑端撤销通知后,App 保留该条目并显示“电脑端已移除”,停止自动重连,同时提供 **重新配对** 和 **删除本地记录**。
264
+ - 在 WebView 页面打开 DSH **设置 → 通用设置 → 切换电脑** 可回到已配对设备列表。在线收到电脑端撤销通知后,App 保留条目并显示“电脑端已移除”,停止自动重连,提供重新配对和删除本地记录。
265
+ - **0.6.0 新增**:列表保持固定顺序,新配对追加到末尾,升级保留旧版可见顺序;操作面板提供 **上移/置顶**,顺序与启动选择分开。App 内还可长按左上角抽屉开关返回列表,短按仍开关抽屉;手机浏览器没有此原生动作。
199
266
 
200
267
  电脑端撤销会永久删除持久化存储中的设备记录及令牌摘要,而不是保留 `revokedAt` 标记;启动时也会清理旧版留下的已撤销记录。删除后,旧令牌与未知令牌一样返回 `401 authentication_failed`。现有 App 若在离线期间被撤销,再次检测时可能显示“配对已过期”,需要重新配对;在线会话仍会收到撤销通知并立即断开。
201
268
 
@@ -216,20 +283,27 @@ Android App 用一个“已配对设备”列表同时显示多台电脑:局
216
283
 
217
284
  0.5.3 包含默认启用、可独立启停的 `dsh-mobile-question-fixes` 提问卡片组件。在 DSH 插件详情的组件列表中关闭它,可立即恢复原卡片样式。长问题、选项和底部按钮在有限高度的卡片内共同滚动,收起时标题最多显示两行;手机浏览器的触屏 Enter 保留换行,Android App 则仅在软键盘已打开且未连接实体键盘时使用该行为。答案草稿仍由 DSH 管理,不另存到浏览器 localStorage。
218
285
 
219
- 移动适配保持 DSH 原有的工作区、任务管理、终端和文件面板入口,不会把第三方插件内容隔离成另一套页面。下面的宽屏截图展示 Android App 在宽屏下的布局。App 会根据屏幕宽度自适应:手机使用抽屉和浮层,宽屏使用并排面板;两种布局共享相同的功能和连接方式。第三方插件仍由 DSH 自己加载,移动层负责适配布局与连接,不修改 DeepSeek Harness 源码。
286
+ DSH Mobile 保留工作区、任务管理、终端和文件面板入口,第三方插件仍由 DSH 加载。移动层适配布局与连接,不修改 DeepSeek Harness 源码;具体插件在小屏上是否易用,仍取决于它自身的布局与交互。
220
287
 
221
- 兼容条件与 WebSocket 放行规则:
288
+ 第三方插件需要能在 DSH Web 页面中运行,并使用可达的 HTTP/WebSocket 接口。移动层不保证把所有插件的自定义布局自动改成手机布局,也不能替代插件依赖的桌面原生窗口。
222
289
 
223
- 代理页面为兼容部分社区插件允许嵌入 HTTP 页面;这类内容未加密,可能被篡改,浏览器也可能因混合内容策略拦截。处理敏感内容时请使用 HTTPS。通过 HTTPS 管理入口打开远程面板时,页面顶部会显示相同提醒。
290
+ ### WebSocket 放行
224
291
 
225
- - 0.5.2 不再因 DSH 版本号未列入依赖范围而阻止安装;已对 `0.2.0-rc.2` 进行源码契约、隔离配对、页面启动及工作区读取检查。未来新版本仍需实际验证。官方 DSH Desktop 的移动访问管理入口继续要求已认证的 DSH 会话,不放宽普通 Web 页面的管理请求。
226
- - 0.5.1 延续对 DSH `0.1.7-alpha.2`、`0.1.7-rc.1` 和 `0.1.7-rc.2` 的契约及启动检查,并适配 `0.2.0-rc.1`。官方 DSH Desktop `0.1.7-rc.2` 使用 `dsh-app://app/` 页面。
227
292
  - 网关默认只允许 DSH 内置的第一方 WebSocket 路径,包括 `/sidebar/ws/terminal`。社区侧边栏插件使用的其他路径默认拦截,通常会在诊断页显示为待处理项目;截图中的 `/sidebar/ws/agent-opens` 和 `/sidebar/ws/agent-terminals` 就属于这类需要按实际插件确认的路径。
228
293
  - 在 **连接诊断 → 第三方 WebSocket 路径** 中,只对确认过的精确路径点击 **允许**。系统不接受带查询字符串或模糊前缀的路径;不建议使用“全部允许”。已允许的路径可以随时移除,局域网和远程连接使用同一套规则。
229
294
  - 放行只代表该路径可以通过已认证、同源的 DSH Mobile 网关,不会开放任意 TCP/UDP 端口,也不会绕过设备配对。若社区插件仍然连接失败,先看诊断页的实际拦截路径,再按一条路径放行。
230
295
 
231
296
  若另一个远程访问插件在 DSH Mobile 页面上显示自己的“未配对”提示,那是两套不同的授权。可先在电脑端暂时关闭另一插件的远程访问,确认 Mobile 链路是否恢复;不要把 DSH Mobile 密钥输入它的配对页。仅用下面的模块排除选项不能保证移除该插件在页面启动前注入的请求改写脚本,参见 [#111](https://github.com/saya-ch/dsh-mobile/issues/111)。
232
297
 
298
+ 网关允许部分 HTTP iframe 不代表客户端会加载它:内容未加密,Android App 仍阻止 HTTPS 页面中的 HTTP 混合内容,浏览器也可能拦截。请优先配置 HTTPS;通过 HTTPS 管理入口打开远程面板时会显示相同风险提醒。
299
+
300
+ ### 可选模块与高级配置
301
+
302
+ 0.6.0 新增[移动页面模块设置](docs/CLIENT_MODULES.md),可分别设置电脑默认和当前设备选择,不卸载电脑插件。保存后下次手动打开生效,不自动刷新会话;已保存选择失效时提供恢复入口。
303
+
304
+ <details>
305
+ <summary>通过 profile 配置 excludedClientModules</summary>
306
+
233
307
  若某个不需要的客户端插件显著增加手机首次加载流量,高级用户可在当前 profile 的 `mobile-access` 插件配置中设置 `excludedClientModules`,填入启动清单中的**准确包名**,重启 DSH 后生效。它只改变经网关提供的专用移动页面;电脑端和 `?frontend=stock` 页面不变。例如,若当前启动清单同时包含以下两个包,下面的配置会移除文档预览及依赖它的“在应用中打开”功能:
234
308
 
235
309
  ```yaml
@@ -240,6 +314,10 @@ excludedClientModules:
240
314
 
241
315
  这是按安装环境选择的高级配置,没有默认排除列表。插件会拒绝不存在的包、启动必需模块,以及仍被保留模块通过 `inject` 或 `external` 引用的包;配置不匹配当前 DSH 或社区插件时,移动页面返回 `409 excluded_client_modules_invalid` 和冲突详情,电脑端也会提示,移除或调整配置即可恢复。模块内容与依赖会随 DSH 版本变化,不能把其他人的节省比例当作自己的预期。
242
316
 
317
+ 设置优先级为本设备选择 → 电脑默认 → 插件配置。详细生效与恢复规则见[模块指南](docs/CLIENT_MODULES.md)。
318
+
319
+ </details>
320
+
243
321
  <table>
244
322
  <tr>
245
323
  <td align="center" valign="top" width="50%">
@@ -262,7 +340,9 @@ excludedClientModules:
262
340
 
263
341
  Android App 只是 Kotlin WebView 薄壳,不内置另一份网页;手机浏览器访问的是同一页面。需要排查兼容性时,可在浏览器地址后追加 `?frontend=stock`,临时回到旧的桌面页面适配模式。
264
342
 
265
- 专用移动页面会在 DSH 的第一个启动脚本之前,同步加载经网关鉴权的同源 `/mobile-access/compat.js`,为缺少 `Iterator` / Iterator helpers 的 WebView 提供随插件打包的 core-js 兼容实现,避免启动时出现 `Iterator is not defined`。兼容层按能力检测保留或修正原生 helper,不依赖 CDN,也不会放宽 CSP;不修改 Android APK、桌面页面或 `?frontend=stock` 页面。此修复并不承诺支持所有旧内核,前端仍以 ES2022 为构建目标;若还有其他兼容错误,请优先更新 Android System WebView / Chrome。
343
+ 0.5.5 的专用移动页面会在 DSH 的第一个启动脚本之前,同步加载经网关鉴权的同源 `/mobile-access/compat.js`,为缺少 `Iterator` / Iterator helpers 的 WebView 提供随插件打包的 core-js 兼容实现,避免启动时出现 `Iterator is not defined`。兼容层按能力检测保留或修正原生 helper,不依赖 CDN,也不会放宽 CSP;桌面页面与 `?frontend=stock` 页面不会加载这个网关脚本。
344
+
345
+ 0.6.0 在同一个启动前脚本中增加 `Promise.withResolvers` 与 `AbortSignal.any` 的能力检测和兼容实现。Android App 还会在支持 document-start 注入的 WebView 中,为配对的精确 Origin 提前补齐 `Promise.withResolvers`。这只覆盖已识别的缺失 API,不是所有旧内核的支持承诺;前端仍以 ES2022 为构建目标,尚未在报告中的华为 WebView 114 真机上验收。出现其他兼容错误时,若设备允许更新,请优先更新 Android System WebView / Chrome;若系统组件不可更新,请保留完整错误、内核版本与进入方式供排查。
266
346
 
267
347
  > **社区客户端(非官方)**:[微信小程序客户端](https://github.com/StrawberryAO/dsh-mobile-minapp)
268
348
  > 原生微信小程序实现,复用「移动访问」的配对与 Remote 流协议(需 dsh-mobile ≥ 0.3.8)。
@@ -297,7 +377,7 @@ flowchart LR
297
377
 
298
378
  ## 故障排查
299
379
 
300
- - **配对成功后仍反复提示重新配对或续期 `401`**:同一域名上的其他服务或旧 Cookie 可能干扰认证。更新至 0.5.4 插件及 App,再从当前移动访问入口重试;浏览器重新扫描当前二维码可更新本插件的认证 Cookie,无需清除所有网站数据。若仍失败,查看失败请求与脱敏日志,其他认证或代理错误也可能返回 `401`。
380
+ - **配对成功后仍反复提示重新配对或续期 `401`**:同一域名上的其他服务或旧 Cookie 可能干扰认证。更新至最新插件及 App,再从当前移动访问入口重试;浏览器重新扫描当前二维码可更新本插件的认证 Cookie,无需清除所有网站数据。若仍失败,查看失败请求与脱敏日志,其他认证或代理错误也可能返回 `401`。
301
381
  - **内测声明无法确认或设置无法保存**:先在电脑端直接打开 DSH 的本机地址,查看终端日志或浏览器 Network 中失败的设置请求。连接诊断不检查设置写入;保留现有配置及 `settings.yaml.imported`,按具体错误排查。相同界面提示可能有不同原因,见 [DSH #860](https://github.com/deepseek-ai/deepseek-harness/discussions/860)。
302
382
  - **设置请求返回 HTTP `403`**:先检查 DSH 的 Host/Origin 信任校验及代理响应。检查 iframe、代理和浏览器扩展是否改变请求来源,使用本机直接访问验证;保持信任检查启用。
303
383
  - **错误包含 `profile reload requires the root Include entry`**:该错误发生在 DSH 重载 profile 时。[Issue #132](https://github.com/saya-ch/dsh-mobile/issues/132) 报告过加载两份 `dsh-app-boot` 导致此错误,但单凭提示不能确认原因。核对实际运行的 DSH 命令与当前 profile 的版本和安装路径,保留配置并按 [DSH 官方文档](https://deepseek-harness.github.io/deepseek-harness/)修复依赖;仍失败时将版本、模块路径及脱敏后的完整错误提交上游。
@@ -319,14 +399,22 @@ flowchart LR
319
399
  | 局域网 | 支持(自动配防火墙) | 支持(防火墙自理) | 支持 | 支持 |
320
400
  | Tailscale Funnel | 支持(随包提供) | 支持(随包提供) | 支持(随包提供) | 不支持 |
321
401
  | cpolar | 支持(按需下载) | 支持(按需下载) | 支持(按需下载) | 不支持 |
322
- | cloudflared 快速/命名隧道 | 支持(按需下载) | 支持(按需下载) | 支持(按需下载) | 不支持 |
402
+ | cloudflared 快速/命名隧道 | 支持(按需下载) | 支持(按需下载) | 支持(按需下载) | 支持 x64/arm64(按需下载) |
323
403
  | 自建 FRP | 支持(按需下载) | 支持(按需下载) | 支持(按需下载) | 支持(按需下载) |
324
404
  | 自有反向代理 | 支持(纯配置) | 支持(纯配置) | 支持(纯配置) | 支持(纯配置) |
325
405
 
326
- macOS 上局域网、自建 FRP 与自有反向代理可用;三个托管组件暂未提供 macOS 包。诊断页的防火墙检查目前仅覆盖 Windows,其他系统显示“不适用”。
406
+ macOS 上局域网、cloudflared、自建 FRP 与自有反向代理可用;Funnel 与 cpolar 暂未提供 macOS 组件。诊断页的防火墙检查目前仅覆盖 Windows,其他系统显示“不适用”。
327
407
 
328
408
  | DSH Mobile 插件 | 验证支持的 DeepSeek Harness 版本 |
329
409
  | ----------------------------------------- | -------------------------------------------------------------- |
410
+ | `0.5.5` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`(npm 打包安装、隔离配对、移动页面启动及 WebSocket 工作区读取) |
411
+ | `0.6.0` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`(打包安装、配对、移动页面启动与 WebSocket 工作区读取);另验证移动输入栏、模块设置及官方 Desktop 正常重启后的组件加载 |
412
+
413
+ <details>
414
+ <summary>历史版本验证记录</summary>
415
+
416
+ | DSH Mobile 插件 | 验证支持的 DeepSeek Harness 版本 |
417
+ | --- | --- |
330
418
  | `0.5.4` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`(npm 打包安装、隔离配对、移动页面启动及 WebSocket 工作区读取) |
331
419
  | `0.5.3` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`(源码契约、npm 打包安装、隔离配对、移动页面启动及 WebSocket 工作区读取) |
332
420
  | `0.5.2` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`(延续既有验证);`0.2.0-rc.2`(源码契约、隔离配对、移动页面启动和 WebSocket 工作区读取) |
@@ -347,9 +435,11 @@ macOS 上局域网、自建 FRP 与自有反向代理可用;三个托管组件
347
435
  | `0.3.0`-`0.3.3` | `0.1.2-alpha.1` |
348
436
  | `0.1.4`、`0.2.x` | `0.1.1-rc.2` |
349
437
 
438
+ </details>
439
+
350
440
  现有 App(0.3.3 及更新)无需重新配对;cpolar 用户应使用 0.3.15 或更新 App,较早版本可能在免费线路的慢速首次加载完成前超时;更早的 App 还使用不同的状态栏策略。App 0.4.0 才支持多设备列表、启动行为设置和电脑端撤销状态同步;旧版 App 仍可连接已保存的单台设备。App 0.1.3 及更早版本需卸载重装并重新配对。
351
441
 
352
- 0.5.4 的原生配对与续期协议保持兼容,旧 App 的正常连接可继续使用;建议同步升级至 0.5.4(build 74),以更新 WebView 中的认证 Cookie,特别是此前同一域名或 Cookie 干扰导致连接失败的设备。
442
+ 0.6.0 保持原生配对与续期协议兼容,旧 App 的正常连接可继续使用;建议同步升级至 0.6.0(build 76),以获得图标选择、扫码改进、设备排序和切换快捷方式,并保留此前的连接、旋转及认证修复。
353
443
 
354
444
  GitHub Release 的正式 APK 使用固定签名,可从同一签名的旧正式版原位升级并保留配对。自行构建的 Debug APK 若使用不同签名,不能直接覆盖安装正式版;切换前请准备重新配对。
355
445
 
@@ -383,18 +473,6 @@ npm ci
383
473
  npm run verify
384
474
  ```
385
475
 
386
- 修改提问卡片或移动布局时,完成构建后运行 `npx playwright install chromium --only-shell`、`npm run smoke:question-fixes` 和 `npm run smoke:native-layout`,检查实际客户端产物的滚动、键盘与布局行为。
387
-
388
- 真实启动冒烟另用临时 DSH Home、随机回环端口和 Chromium 配对,不访问现有用户配置,也不发送模型请求。开发分支的 CI 分别测试 DSH `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1` 与 `0.2.0-rc.2`;本机可把 DSH `0.2.0-rc.2` 装在独立目录,避免替换插件的开发依赖:
389
-
390
- ```powershell
391
- $dshMobileTestRuntime = Join-Path $env:TEMP 'dsh-mobile-test-runtime'
392
- npm install --prefix $dshMobileTestRuntime --no-save --package-lock=false @deepseek-ai/dsh@0.2.0-rc.2
393
- $env:DSH_BOOT_SMOKE_BIN = Join-Path $dshMobileTestRuntime 'node_modules/@deepseek-ai/dsh/lib/bin.js'
394
- npx playwright install chromium --only-shell
395
- npm run smoke:dsh-boot
396
- ```
397
-
398
- Android 构建见 [App 文档](https://github.com/saya-ch/dsh-mobile/blob/main/apps/mobile/README.zh-CN.md)。
476
+ 文档快速检查使用 `npm run check:docs`。按改动选择的浏览器测试、隔离 DSH 打包验收、截图和正式发布流程见 [CONTRIBUTING.md](CONTRIBUTING.md);Android 构建见 [App 手册](apps/mobile/README.zh-CN.md#构建)。不要用真实用户配置或正式 App 数据替代测试资料。
399
477
 
400
478
  Apache-2.0,详见 [LICENSE](LICENSE)。
package/SECURITY.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Security policy
2
2
 
3
- `dsh-mobile` exposes a control surface that can run tools on the host computer. Treat every paired device as security-sensitive.
3
+ `dsh-mobile` lets paired devices operate DSH and run tools with the computer user's authority. Pairing is not a read-only sandbox: trust each device and revoke it promptly if lost. Publish only the protected Mobile entry, never the ordinary DSH listener or a plaintext backend.
4
4
 
5
5
  ## Supported versions
6
6
 
@@ -14,34 +14,45 @@ The maintainer will acknowledge a complete report within seven days. Publication
14
14
 
15
15
  ## Deployment requirements
16
16
 
17
+ ### Listeners and desktop administration
18
+
17
19
  - Keep the ordinary DSH Web listener on loopback.
18
20
  - Expose only the plugin-owned HTTPS listener to the LAN.
19
21
  - The desktop admin API may accept an RFC1918 or IPv4 link-local Host only when the TCP peer is loopback, for a trusted local reverse proxy or headless host. Do not publicly forward `/api/mobile-access`; Host and loopback checks are not a replacement for proxy access control. Mutating admin requests require a same-origin `Origin` header, except official DSH Desktop requests: its forwarder strips `Origin` and `Sec-Fetch-Site` and supplies a signed Host browser-session Cookie, so the plugin client sends an exact non-simple marker that survives forwarding. The plugin requires both that marker and Connection's successful browser-session verification before accepting an Origin-less write.
22
+ ### Discovery, pairing and TLS
23
+
20
24
  - DNS-SD/mDNS, periodic UDP announcements, active UDP query replies, and HTTPS discovery return only the device name, public HTTPS origin, port, protocol version, and stable non-secret installation identifier. Discovery never returns the CA, a pairing key, a device token, Cookies, credentials, or private configuration.
21
25
  - For LAN pairing, only after a user selects a device and enters the fingerprint-bound pairing key may Android fetch the public CA from that exact HTTPS origin. The bootstrap GET sends no key or credential. The app retains the CA in its encrypted credential record and never adds it to Android's system trust settings. Native requests use a private trust store; WebView accepts only the otherwise-untrusted leaf signed by that CA, for the exact origin and validity period. Every other TLS error is cancelled.
22
26
  - Public remote origins provided by Funnel, cpolar, cloudflared, the FRP public-CA entry, or an operator-managed reverse proxy use platform-trusted HTTPS. Android stores no private CA for those credentials and cancels every TLS error. It sends a persisted device token only to the exact Origin that previously received it; a changed remote Origin requires a current one-time pairing token before the app replaces the saved credential. A QR-provided installation identifier alone never authorizes credential renewal. Browser clients likewise require a certificate trusted by their platform.
23
27
  - The 0.4.6 existing-frps self-signed entry instead forwards raw TCP to the computer-side HTTPS gateway. Its QR link and bare App key carry a versioned `dsh2` CA-required marker, which Android 0.4.5 and earlier reject. During explicit remote pairing, the 0.4.6 app retrieves the gateway's public CA without sending a credential, checks its validity and SHA-256 fingerprint against the pairing key, and pins it to the exact Origin; HTTP 404, a missing, changed, or untrusted CA cannot downgrade this flow to platform trust. Re-pairing an existing pinned identity retains that CA requirement. The existing exact-Origin device-token restriction still applies: a CA fingerprint alone cannot renew a device credential. The CA is not installed in Android's system trust store. The 0.4.5 app lacks this remote CA-pinning path, and ordinary phone browsers will not trust the self-signed certificate.
24
28
  - Keep pairing closed except during a short local onboarding action.
25
29
  - Revoke a lost device immediately and rotate the device registry if credential theft is suspected.
30
+ ### Remote components, diagnostics and cleanup
31
+
26
32
  - Do not expose the LAN gateway through router port forwarding. Funnel, cpolar, cloudflared, and FRP reach a separate loopback gateway through their selected remote path. The own reverse-proxy provider instead exposes a plugin-owned private HTTP origin restricted by its bind address and allowed source CIDRs; the operator's proxy must provide public HTTPS. Funnel, cpolar, cloudflared, Caddy, or the own proxy terminates public TLS in their respective modes; the 0.4.6 self-signed FRP entry terminates TLS on the computer. DSH pairing, device authentication, CSRF checks, and session revocation remain enforced by the plugin gateway.
27
33
  - The Funnel node stores its Tailscale login state under `$DSH_HOME/mobile-access/remote/tailscale/`. The plugin does not request or store a Tailscale password, Auth Key, or OAuth secret.
28
34
  - The proxy-assisted remote diagnostic probes only the public `/mobile-access/health` path. It tries direct HTTPS first and, only after direct failure, may use an HTTP CONNECT proxy named by the DSH process environment unless `NO_PROXY` excludes the target. The probe sends no device token or session Cookie; configured proxy credentials are sent to that proxy for CONNECT, not placed in the diagnostic report. A proxy-only success is a warning to test from the phone, not proof that the phone or the provider route is available. This diagnostic does not change the remote provider's traffic path.
29
- - cpolar is downloaded only after confirmation from a pinned official artifact whose size and SHA-256 are verified. Its Authtoken is stored in a private, self-update-disabled configuration under `$DSH_HOME/mobile-access/`, never returned by the admin API or written to logs. Cleanup removes the managed executable, configuration, logs, and independent remote device registry.
35
+ - cpolar is downloaded only after confirmation from a pinned official artifact whose size and SHA-256 are verified. Its Authtoken is stored in a private, self-update-disabled configuration under `$DSH_HOME/mobile-access/`, never returned by the admin API or written to logs. Component removal deletes its managed executable, Authtoken configuration, logs and staging files, but retains LAN and shared remote pairing records.
30
36
  - Self-hosted FRP accepts a VPS address, frps control port, operator-generated high-entropy shared token, and standard-port public HTTPS origin. The 0.4.6 self-signed entry additionally selects a non-reserved public TCP port on a globally routable IPv4 address. The plugin validates token length and format; the operator remains responsible for its entropy. Managed deployment creates one HTTP vhost and Caddy; existing-frps attachment changes no VPS files or services and allows either a restricted HTTP vhost with a public CA or one TCP passthrough to a computer-side HTTPS gateway. Arbitrary FRP configuration, general TCP/UDP forwarding, FRP plugins, PATH entries, and startup tasks are not supported.
31
37
  - For an HTTP-vhost entry, the generated managed-frps template binds the plaintext listener to `127.0.0.1` and uses Caddy for public HTTPS. An existing frps must have its actual `vhostHTTPPort` entered explicitly and its plaintext vhost kept off the public network; the plugin probes the configured port before starting `frpc`. frps `proxyBindAddr` governs proxy listeners globally: exposing a TCP passthrough on the same instance must not accidentally expose another plaintext vhost. A separate frps instance may be required. For self-signed ingress, the computer-side CA lasts five years and the leaf lasts 397 days; the leaf is renewed under the same CA, but an expired or replaced CA is not silently trusted and requires fresh pairing. A local readiness probe does not establish phone reachability; verify the public entry from an independent network.
32
38
  - The FRP token stays in a private file under `$DSH_HOME/mobile-access/`, is never returned by status, diagnostic, or attachment-plan responses, and is never logged. Copying a server template or an attach-mode `frpc.toml` after re-entering its token puts that token on the system clipboard; clear it after use and protect the VPS configuration. Local FRP cleanup removes the managed `frpc`, token, runtime configuration, ingress certificate material, staging files, and logs. Server-side cleanup scripts apply only to plugin-managed deployment and delete solely DSH Mobile-owned files, services, and tagged firewall rules; attach mode never deletes the operator's existing frps or Caddy files.
33
39
  - Automatic VPS deployment and one-click VPS cleanup first read the server's SSH host keys and require the operator to confirm every `SHA256:` fingerprint against the VPS console before any authenticated connection. All SSH/SCP channels pin `StrictHostKeyChecking=yes` to a temporary known_hosts file built from the confirmed keys; unknown, rotated, or extra keys abort the operation instead of being silently accepted. SSH credentials themselves are key-or-agent only, never passwords; the private key path is convenience state in the current browser profile, and key material is never uploaded or logged.
34
- - Disabling remote access stops the selected provider process without affecting LAN access. Resetting remote access also removes provider state and the independent remote device registry.
40
+ - Disabling remote access stops the selected provider without affecting LAN or pairing records. Resetting remote access also clears the shared remote-device registry, so all remote devices must pair again. A Funnel reset additionally removes the plugin's private Tailscale node login state and requires authorization again. Other providers retain settings and components until their separate removal actions are used.
41
+
42
+ ### Device authority, Android and extensions
43
+
35
44
  - Treat every paired device as a fully trusted operator. The authenticated proxy forwards ordinary DSH and community-plugin routes using GET, HEAD, POST, PUT, PATCH, or DELETE; writes require the gateway's exact Origin, valid Session, and Session-bound CSRF token. Upstream requests use DSH's loopback Host and the gateway's own DSH authentication, so a plugin cannot distinguish a paired phone from the computer user merely by checking that Host. Routes may read configuration, save settings, or run tools with the desktop user's authority; pairing is not a read-only sandbox. The separate `/api/mobile-access` admin API remains inaccessible through the mobile listener.
36
45
  - Android stores the paired-device list as an encrypted record. Removing a local row erases its local credential but does not revoke the phone at the computer; use the computer-side device controls to revoke access. A computer-side revocation invalidates the token even if the App retains the row's name for re-pairing. Network timeouts are not evidence of revocation.
37
46
  - Android grants WebView microphone access only for an audio-only request from the current paired HTTPS Origin, after Android's `RECORD_AUDIO` permission is granted. Other WebView resources and Origins remain denied. Client plugins within the authenticated page share that Origin and can invoke page APIs; install only trusted plugins before granting microphone access.
38
47
  - Treat `mobile.js` as application code with the paired page's same-origin authority. Restrict write access to trusted host-side DSH sessions and review generated API calls or browser-permission use.
39
48
  - Treat every extension `host.mjs` as a local program with the desktop user's Node.js privileges. It is never sandboxed and is not editable through the mobile gateway; only place code there that you trust.
40
49
  - Extension Actions and Routes receive filtered request data, a device identifier, and an abort signal. They cannot set proxy security headers or access the gateway's cookies, device tokens, CSRF tokens, or internal request headers.
41
- - Proxied GUI responses allow HTTP iframe sources for compatibility with community surfaces. HTTP content is unauthenticated and unencrypted; use HTTPS for sensitive work, and do not treat the compatibility allowance as a transport-security guarantee.
50
+ ### Framed content and browser policy
51
+
52
+ - Proxied GUI responses allow HTTP iframe sources for compatibility with community surfaces. HTTP content is unauthenticated and unencrypted; Android still blocks mixed HTTP content inside HTTPS pages, and browsers may block it too. Prefer HTTPS and do not treat a gateway framing allowance as a transport-security guarantee.
42
53
 
43
54
  ## Known limitation
44
55
 
45
56
  The current DSH HTML boot process contains inline JavaScript, revives Schemastery callbacks with `new Function`, and applies some styles dynamically. To keep the stock Web UI runnable, the gateway's Content Security Policy currently includes `script-src 'self' 'unsafe-inline' 'unsafe-eval'` and `style-src 'self' 'unsafe-inline'`. The remaining directives still restrict origins, connections, frames, objects, workers, images, and form targets, but this policy does not eliminate script-injection risk. Removing these allowances requires upstream DSH support for nonces, stable hashes, external boot resources, and a non-evaluating schema representation.
46
57
 
47
- This repository never accepts private keys, npm tokens, pairing values, device credentials, Cookies, or captured settings in issues, logs, fixtures, or example configuration.
58
+ Do not include live private keys, npm tokens, pairing values, device credentials, Cookies or captured private settings in issues, logs, committed fixtures or examples. Authentication tests use isolated temporary credentials, not real user state.
@@ -1,5 +1,11 @@
1
1
  # Third-party notices
2
2
 
3
+ ## Brand artwork
4
+
5
+ The Android launcher whale-mark variants were contributed by [@abworks-dev in PR #175](https://github.com/saya-ch/dsh-mobile/pull/175), which describes rendering the DeepSeek whale SVG path. They identify the upstream brand; DSH Mobile remains a community-maintained client, not an official DeepSeek Android application or an assertion of endorsement. This repository's code license does not assert ownership of third-party names or marks. The default character artwork and repository hero remain the project's existing brand assets.
6
+
7
+ ## Runtime components
8
+
3
9
  DSH Mobile includes platform-specific Windows and Linux Funnel helpers built from the source in `native/funnel-host`. They use [Tailscale tsnet](https://pkg.go.dev/tailscale.com/tsnet) `v1.102.3`, distributed under the BSD 3-Clause license. Their exact statically linked Go module versions and complete license and notice texts are generated from the bundled executables into `FUNNEL_THIRD_PARTY_LICENSES.txt`, which is included in every npm package.
4
10
 
5
11
  The optional cpolar component is not included in the npm package or Android App. When a user explicitly chooses cpolar installation, DSH Mobile downloads the pinned official archive shown in the UI, verifies its size and SHA-256 digest, and stores it only under the user's DSH Mobile data directory. cpolar remains subject to its [terms of service](https://www.cpolar.com/tos).
@@ -8,13 +14,15 @@ The optional [FRP](https://github.com/fatedier/frp) client is not included in th
8
14
 
9
15
  The optional [cloudflared](https://github.com/cloudflare/cloudflared) client is not included in the npm package or Android App. When a user explicitly chooses cloudflared installation, DSH Mobile downloads the pinned official release asset for the supported host platform (version recorded in `src/cloudflared-component.ts`), verifies its exact size and SHA-256 digest, and stores it only under the user's DSH Mobile data directory. The client is started with automatic updates disabled. Quick tunnels do not require account credentials; named tunnels use an operator-supplied connector token stored in the plugin's private directory. cloudflared is distributed under the Apache License 2.0; use of Cloudflare's quick tunnels remains subject to Cloudflare's [website terms](https://www.cloudflare.com/website-terms/).
10
16
 
17
+ The managed Caddy component is not included in the npm package or Android App. Version 0.6.0 has no published trusted pinned component, so installation remains unavailable. The reviewed build uses [Caddy v2.11.6](https://github.com/caddyserver/caddy/blob/v2.11.6/LICENSE), under Apache License 2.0, and [caddy-dns/tencentcloud v0.4.3](https://github.com/caddy-dns/tencentcloud/blob/v0.4.3/LICENSE), under the MIT License. Maintainer build artifacts record embedded dependency versions and licenses; they are not an automatically published or installed binary. Existing user-managed proxies are separate from this optional component.
18
+
11
19
  The npm package declares direct runtime dependencies on [Schemastery](https://github.com/shigma/schemastery) `^3.18.1`, [bonjour-service](https://github.com/onlxltd/bonjour-service) `1.4.4`, [qrcode](https://github.com/soldair/node-qrcode) `^1.5.4`, [selfsigned](https://github.com/jfromaniello/selfsigned) `^5.5.0`, and [ws](https://github.com/websockets/ws) `8.22.0`. These packages are distributed under the MIT License; the release checkout's exact transitive versions appear in the attached CycloneDX npm SBOM.
12
20
 
13
21
  The Android App runtime contains [Kotlin standard library](https://github.com/JetBrains/kotlin) `2.1.0`, [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) `1.6.4`, [JetBrains annotations](https://github.com/JetBrains/java-annotations) `13.0`, [AndroidX Core](https://developer.android.com/jetpack/androidx/releases/core) `1.15.0`, [AndroidX WebKit](https://developer.android.com/jetpack/androidx/releases/webkit) `1.17.1`, their AndroidX transitive components, [Guava listenablefuture](https://github.com/google/guava) `1.0`, and [ZXing Core](https://github.com/zxing/zxing) `3.5.4`. These runtime libraries are distributed under the Apache License 2.0; the exact resolved tree is attached to each release as `dsh-mobile-android-v<version>-dependencies.txt`.
14
22
 
15
23
  ## core-js browser compatibility bundle
16
24
 
17
- The standalone `lib/mobile-compat.js` bundles selected Iterator modules from [core-js](https://github.com/zloirock/core-js) `^3.50.0` (exact version recorded in `package-lock.json`). It is built into the npm package; browsers do not download a polyfill from a CDN. core-js is distributed under the following MIT License:
25
+ The standalone `lib/mobile-compat.js` bundles selected Iterator and `Promise.withResolvers` modules from [core-js](https://github.com/zloirock/core-js) `^3.50.0` (exact version recorded in `package-lock.json`). It is built into the npm package; browsers do not download a polyfill from a CDN. core-js is distributed under the following MIT License:
18
26
 
19
27
  ```text
20
28
  Copyright (c) 2013–2025 Denis Pushkarev (zloirock.ru)
@@ -2,7 +2,7 @@
2
2
 
3
3
  [中文指南](ATTACH_EXISTING_FRPS.md)
4
4
 
5
- > **Requires plugin 0.4.6**; the self-signed entry also requires the 0.4.6 Android app. Android apps 0.3.3–0.4.5 can use the public-CA entry but not the self-signed entry.
5
+ > The plugin requires **0.4.6 or later**; the self-signed entry also requires **Android app 0.4.6 or later**. Apps 0.3.3–0.4.5 can use the public-CA entry, not self-signed ingress. Do not downgrade a newer app.
6
6
 
7
7
  Use this path when you already run frps on a public VPS. The plugin does not install, edit, or restart that frps and does not automatically change your Caddyfile; public-CA mode requires **you** to add a Caddy snippet and import. Inspect the server's listeners before choosing an entry mode. The panel's **Copy attachment plan** action only produces a masked local `frpc.toml` preview and VPS instructions; it neither connects to the VPS nor writes configuration. To copy a token-bearing local config, re-enter the token and explicitly click its copy button; a saved token is never returned to the page.
8
8
 
@@ -11,7 +11,7 @@ Use this path when you already run frps on a public VPS. The plugin does not ins
11
11
  | Entry | Public route | VPS requirements | Client trust |
12
12
  | --- | --- | --- | --- |
13
13
  | **Public CA** (`public-ip-cert`, default) | HTTPS 443 → Caddy → loopback HTTP vhost → FRP | Enter the real `vhostHTTPPort`; keep that plaintext listener on loopback. A domain uses Caddy-managed certificates; public IPv4 needs a short-lived Let's Encrypt IP certificate and operator-managed renewal. | Phone browsers and Android app 0.3.3+ use the platform trust store and still require DSH pairing. |
14
- | **Self-signed passthrough** (`self-signed`) | HTTPS on `publicPort` (default 33080) → frps raw TCP proxy → computer-side HTTPS gateway | Use a globally routable public IPv4 address. The frps TCP listener must be publicly reachable; no new Caddy site, public certificate, or HTTP vhost is needed for this entry. | Requires the 0.4.6 Android app with remote CA pinning. Older apps cannot pair; ordinary phone browsers do not trust the self-signed certificate. |
14
+ | **Self-signed passthrough** (`self-signed`) | HTTPS on `publicPort` (default 33080) → frps raw TCP proxy → computer-side HTTPS gateway | Use a globally routable public IPv4 address. The frps TCP listener must be publicly reachable; no new Caddy site, public certificate or HTTP vhost is needed. | Requires Android app 0.4.6 or later with remote CA pinning. Older apps cannot pair; ordinary phone browsers do not trust this certificate. |
15
15
 
16
16
  frps `proxyBindAddr` is global to proxy listeners, not a per-entry switch. Binding it to `127.0.0.1` protects the HTTP vhost but also makes a TCP `remotePort` unreachable from the public network. Binding it publicly for the TCP entry may expose plaintext vhosts already running on that frps. Inspect every listener and firewall rule; use a separate frps instance or the public-CA mode when the requirements conflict. Neither mode permits arbitrary FRP proxy configuration or editing the operator's server from the plugin.
17
17
 
@@ -20,17 +20,18 @@ The [official frps configuration reference](https://gofrp.org/en/docs/reference/
20
20
  ## Public-CA mode
21
21
 
22
22
  1. In **Self-hosted FRP**, choose **Attach to my existing frps** and **Public CA certificate**. Enter the VPS address, frps control port, high-entropy shared token, public domain or IPv4 HTTPS origin, and the frps instance's actual `vhostHTTPPort`. Do not assume the managed deployment's default of 7080.
23
- 2. Copy the attachment plan. Confirm the plaintext vhost is loopback-only, then add the generated Caddy snippet and `import` manually. Caddy manages domain certificates. For public IPv4, the current plan pins Certbot 5.8.0 and uses `--standalone --preferred-profile shortlived --ip-address` for a [roughly six-day Let's Encrypt IP certificate](https://letsencrypt.org/2026/03/11/shorter-certs-certbot). Standalone issuance briefly stops Caddy to free port 80 and may interrupt other sites on the VPS; schedule a maintenance window, choose a domain, or use the 0.4.6 app's self-signed TCP mode if that interruption is unacceptable. The attach path only checks an existing `certbot.timer`; it does not install renewal or a certificate-copy/reload hook. Arrange renewal, installation into Caddy, and reload before expiry yourself. Copying the plan does not change the VPS.
23
+ 2. Copy the attachment plan. Confirm the plaintext vhost is loopback-only, then add the generated Caddy snippet and `import` manually. Caddy manages domain certificates. For public IPv4, the current plan pins Certbot 5.8.0 and uses `--standalone --preferred-profile shortlived --ip-address` for a [roughly six-day Let's Encrypt IP certificate](https://letsencrypt.org/2026/03/11/shorter-certs-certbot). Standalone issuance briefly stops Caddy to free port 80 and may interrupt other sites on the VPS; schedule a maintenance window, choose a domain, or use the self-signed TCP mode in app 0.4.6 or later if that interruption is unacceptable. The attach path only checks an existing `certbot.timer`; it does not install renewal or a certificate-copy/reload hook. Arrange renewal, installation into Caddy, and reload before expiry yourself. Copying the plan does not change the VPS.
24
24
  3. Install the pinned official `frpc` on the computer, save and verify the connection, and test `/mobile-access/discovery` from an independent external network with normal certificate validation. Confirm HTTP 200 and this computer's installation identifier before pairing through the app's **Remote access** flow or a phone browser. A local self-check alone does not verify public reachability.
25
25
 
26
26
  ## Self-signed passthrough mode
27
27
 
28
- The computer-side gateway terminates TLS with a self-signed CA valid for five years and a public-IPv4 leaf valid for 397 days. The leaf can be reissued under the same CA. An expired or replaced CA is not silently accepted: inspect the certificate status in the panel, confirm a new fingerprint, and pair the phone again. The QR link, copied link, and bare App key carry a `dsh2` marker requiring CA pinning; older apps reject that format. The CA is private to the 0.4.6 app's connection record, not installed in Android's system trust store.
28
+ The computer-side gateway terminates TLS with a five-year CA and 397-day public-IPv4 leaf. The leaf can renew under the same CA; an expired or replaced CA requires a new fingerprint check and pairing. QR links and App keys carry the CA-required `dsh2` marker, rejected by older apps. Supported apps keep this CA in their private connection records, never Android's system trust store.
29
29
 
30
30
  1. In **Self-hosted FRP**, enter the VPS address, frps control port, token, and `https://YOUR_PUBLIC_IPV4`. Choose **Attach to my existing frps**, **Self-signed passthrough**, and the public TCP port (default 33080; not 3080, 3443, or 3444).
31
- 2. Copy the attachment plan and review its masked local `frpc.toml` and VPS checklist. This is read-only. After you install `frpc` and select **Save and verify connection**, the plugin writes the real config under its private `remote/frp/config/` directory (Unix mode `0600`; restricted ACL on Windows). If you re-entered the token and explicitly copied a token-bearing config, clear the clipboard promptly.
31
+ 2. Copy and review the masked `frpc.toml` and VPS checklist. This is read-only: finish the server checks before selecting **Save and verify connection**.
32
32
  3. On the VPS, allow the chosen TCP entry port in its host firewall and cloud security group; the existing frps control port must already be reachable from the computer. Confirm frps is running and the TCP proxy listens on an address reachable from the public network. The plugin does not change the server's frps configuration or firewall.
33
- 4. From another network, check the entry with `curl -k -sS -o /dev/null -w '%{http_code}\n' https://YOUR_PUBLIC_IPV4:33080/mobile-access/discovery`. Replace the host and port first. HTTP 200 only shows transport reachability: `-k` bypasses curl's certificate verification and does **not** verify the gateway identity. Pair from the 0.4.6 Android app's **Remote access** flow, which fetches the public CA without a device credential, checks its fingerprint against the `dsh2` key, and pins it to that exact Origin. A 404 CA response cannot fall back to platform trust in this flow. A changed Origin or CA requires a new pairing.
33
+ 4. Install official frpc and select **Save and verify connection**. The plugin now writes the config in private `remote/frp/config/` (Unix `0600`, restricted Windows ACL), then runs `frpc verify` before startup. Clear the clipboard if you explicitly copied a token-bearing config. A public TCP proxy port normally appears only after frpc registers; check its actual listener then.
34
+ 5. From another network, check `curl -k -sS -o /dev/null -w '%{http_code}\n' https://YOUR_PUBLIC_IPV4:33080/mobile-access/discovery`, replacing the host/port. HTTP 200 proves reachability only: `-k` does not verify identity. Pair through **Remote access** in app 0.4.6 or later; it fetches the CA without device credentials, checks the `dsh2` fingerprint and pins the exact Origin. CA HTTP 404 cannot fall back to platform trust. An Origin or CA change requires new pairing.
34
35
 
35
36
  ## Troubleshooting and cleanup
36
37
 
@@ -40,8 +41,8 @@ The computer-side gateway terminates TLS with a self-signed CA valid for five ye
40
41
  | `frp_vhost_publicly_reachable` | The public-CA entry's plaintext vhost is reachable from outside. Restrict it without exposing another listener; a separate frps instance may be safer than changing global `proxyBindAddr`. |
41
42
  | `frp_attach_cert_unknown` | The self-signed certificate is absent or unreadable. Inspect the panel's certificate status and the private `remote/ingress/` files; do not bypass fingerprint validation. |
42
43
  | `frp_ingress_ca_expired` | The pinned CA expired. Reconfigure the self-signed entry and pair the phone again after checking the new fingerprint. |
43
- | Browser reports an untrusted certificate | Expected for the self-signed entry. Use the 0.4.6 Android app and explicit pairing; use public-CA mode for a browser. |
44
- | `frp_proxy_name_in_use` | frps keeps proxy names unique across **all** its clients, and this installation's own name (`dsh-mobile-<first 12 characters of the installation identity>`) is already registered — usually another DSH computer, or this computer's previous session before the frps heartbeat expires it. Disable self-hosted FRP there, or remove that proxy on the frps. frpc keeps running without publishing the entry port, so before this code the failure only ever appeared as a start timeout. |
44
+ | Browser reports an untrusted certificate | Expected for self-signed ingress. Use app 0.4.6 or later and explicit pairing, or public-CA mode for a browser. |
45
+ | `frp_proxy_name_in_use` | frps keeps proxy names unique across **all** its clients, and this installation's own name (`dsh-mobile-<first 12 characters of the installation identity>`) is already registered — usually another DSH computer, or this computer's previous session before the frps heartbeat expires it. Disable self-hosted FRP there, or remove that proxy on the frps. frpc keeps running without publishing the entry port, so check proxy registration rather than relying on process liveness. |
45
46
  | `frp_remote_port_in_use` | Another proxy on the same frps already owns the chosen entry port. Ports are first-come, first-served per server, so give this computer a different `publicPort` and allow it in the firewall. |
46
47
  | Public entry unreachable | Check the chosen port, cloud and host firewalls, frps listener address, running `frpc`, and the external-network probe. A computer-side probe is not proof that a phone can connect. |
47
48