dsh-mobile 0.4.4 → 0.4.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  Notable changes to DSH Mobile are recorded here. GitHub Releases remain the source for downloadable packages and complete generated commit notes.
4
4
 
5
+ ## 0.4.5 - 2026-09-23
6
+
7
+ - Support the document-relative plugin URLs introduced in DSH 0.1.7 without weakening the same-origin `/plugins/` check. This fixes mobile boot batches returning `502 upstream_unavailable`; local and remote gateways were verified against DSH 0.1.7-alpha.2 (thanks @azri57806-design for [#97](https://github.com/saya-ch/dsh-mobile/issues/97)).
8
+ - Keep failed revisioned plugin scripts and hashed assets on `no-store` instead of caching their 404 responses as immutable for a year (thanks @abworks-dev for [PR #98](https://github.com/saya-ch/dsh-mobile/pull/98)).
9
+ - Guard WebSocket upgrade sockets before asynchronous authentication so disconnects during reconnect cannot emit an unhandled socket error (follow-up to [#95](https://github.com/saya-ch/dsh-mobile/issues/95)).
10
+ - Give Android connection restoration an exit after a short wait: direct-start Retry stays bound to that paired computer, while manual and legacy flows offer the Device list without retrying a different computer. Existing credentials remain intact (thanks @sznyhgm for [#96](https://github.com/saya-ch/dsh-mobile/issues/96)).
11
+ - Restore the task-notification permission entry in the Android App's DSH General settings without bringing back the hidden native toolbar; the action uses the exact-origin, main-frame native bridge and has Chinese, English, and Italian copy.
12
+ - Align development dependencies and optional peer ranges with DSH 0.1.7-alpha.2, and use the producer-owned message source for `/mobile` guidance.
13
+
5
14
  ## 0.4.4 - 2026-09-20
6
15
 
7
16
  - Keep DSH running if mDNS response callbacks, the multicast-dns emitter, or the already-bound UDP discovery socket reports a network error. LAN discovery records the degraded channel and logs the error code while the authenticated HTTP and WebSocket gateway remains available (thanks @KMGTPEZY for [#95](https://github.com/saya-ch/dsh-mobile/issues/95)).
package/CONTRIBUTORS.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  GitHub 的 [Contributors 图表](https://github.com/saya-ch/dsh-mobile/graphs/contributors) 按进入默认分支的提交自动统计,无法手动加入仅提交 issue 或 PR 提案的成员。本页按实际参与方式感谢社区成员;仓库维护者与自动依赖更新仍可在 GitHub 历史记录中查看。
4
4
 
5
- GitHub's Contributors graph counts commits on the default branch; issue reports and PR proposals cannot be manually added to it. This page credits community members by how they contributed, through 2026-09-20. The repository maintainer and automated dependency updates remain visible in GitHub's history.
5
+ GitHub's Contributors graph counts commits on the default branch; issue reports and PR proposals cannot be manually added to it. This page credits community members by how they contributed, through 2026-09-23. The repository maintainer and automated dependency updates remain visible in GitHub's history.
6
6
 
7
7
  ## 已合并 PR / Merged pull requests
8
8
 
9
9
  | 社区成员 / Community member | 已合并 PR / Merged PRs |
10
10
  | --- | --- |
11
11
  | [@1624318455](https://github.com/1624318455) | [#93](https://github.com/saya-ch/dsh-mobile/pull/93) |
12
- | [@abworks-dev](https://github.com/abworks-dev) | [#91](https://github.com/saya-ch/dsh-mobile/pull/91) |
12
+ | [@abworks-dev](https://github.com/abworks-dev) | [#91](https://github.com/saya-ch/dsh-mobile/pull/91), [#98](https://github.com/saya-ch/dsh-mobile/pull/98) |
13
13
  | [@idoall](https://github.com/idoall) | [#61](https://github.com/saya-ch/dsh-mobile/pull/61), [#66](https://github.com/saya-ch/dsh-mobile/pull/66), [#67](https://github.com/saya-ch/dsh-mobile/pull/67), [#77](https://github.com/saya-ch/dsh-mobile/pull/77) |
14
14
  | [@JackRushante](https://github.com/JackRushante) | [#16](https://github.com/saya-ch/dsh-mobile/pull/16) |
15
15
  | [@longisland-icetea](https://github.com/longisland-icetea) | [#59](https://github.com/saya-ch/dsh-mobile/pull/59), [#60](https://github.com/saya-ch/dsh-mobile/pull/60), [#63](https://github.com/saya-ch/dsh-mobile/pull/63) |
@@ -32,6 +32,7 @@ GitHub's Contributors graph counts commits on the default branch; issue reports
32
32
  | 反馈贡献者 / Issue reporter | 提交的问题 / Issues |
33
33
  | --- | --- |
34
34
  | [@1624318455](https://github.com/1624318455) | [#92](https://github.com/saya-ch/dsh-mobile/issues/92) |
35
+ | [@azri57806-design](https://github.com/azri57806-design) | [#97](https://github.com/saya-ch/dsh-mobile/issues/97) |
35
36
  | [@CESAEREE](https://github.com/CESAEREE) | [#1](https://github.com/saya-ch/dsh-mobile/issues/1) |
36
37
  | [@CharlesLueng](https://github.com/CharlesLueng) | [#68](https://github.com/saya-ch/dsh-mobile/issues/68) |
37
38
  | [@chmzs](https://github.com/chmzs) | [#73](https://github.com/saya-ch/dsh-mobile/issues/73) |
@@ -44,6 +45,7 @@ GitHub's Contributors graph counts commits on the default branch; issue reports
44
45
  | [@rgshendong](https://github.com/rgshendong) | [#87](https://github.com/saya-ch/dsh-mobile/issues/87) |
45
46
  | [@RonaldinhoL](https://github.com/RonaldinhoL) | [#89](https://github.com/saya-ch/dsh-mobile/issues/89) |
46
47
  | [@shengyvself](https://github.com/shengyvself) | [#13](https://github.com/saya-ch/dsh-mobile/issues/13), [#14](https://github.com/saya-ch/dsh-mobile/issues/14) |
48
+ | [@sznyhgm](https://github.com/sznyhgm) | [#96](https://github.com/saya-ch/dsh-mobile/issues/96) |
47
49
  | [@sortjiajun](https://github.com/sortjiajun) | [#90](https://github.com/saya-ch/dsh-mobile/issues/90) |
48
50
  | [@StrawberryAO](https://github.com/StrawberryAO) | [#39](https://github.com/saya-ch/dsh-mobile/issues/39) |
49
51
  | [@Surpass567](https://github.com/Surpass567) | [#80](https://github.com/saya-ch/dsh-mobile/issues/80), [#81](https://github.com/saya-ch/dsh-mobile/issues/81) |
package/README.en.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/dsh-mobile"><img src="https://img.shields.io/npm/v/dsh-mobile?label=npm&amp;color=CB3837" alt="npm version"></a>
11
- <a href="https://www.npmjs.com/package/dsh-mobile"><img src="https://img.shields.io/npm/dm/dsh-mobile?label=downloads&amp;color=2563EB" alt="monthly npm downloads"></a>
11
+ <a href="https://www.npmjs.com/package/dsh-mobile"><img src="https://img.shields.io/npm/dt/dsh-mobile?label=downloads&amp;color=2563EB" alt="total npm downloads"></a>
12
12
  <a href="https://github.com/saya-ch/dsh-mobile/actions/workflows/ci.yml"><img src="https://github.com/saya-ch/dsh-mobile/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
13
13
  <a href="https://github.com/saya-ch/dsh-mobile/releases"><img src="https://img.shields.io/badge/Android-10%2B-3DDC84?logo=android&amp;logoColor=white" alt="Android 10+"></a>
14
14
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-0F172A" alt="Apache-2.0"></a>
@@ -31,14 +31,14 @@
31
31
 
32
32
  > DSH Mobile is a DeepSeek Harness community plugin; the native app supports Android only.
33
33
  >
34
- > **0.4.4 update**: runtime mDNS or UDP discovery errors no longer take down DSH, and component downloads accept proxy responses without `Content-Length`. [Details](CHANGELOG.md).
34
+ > **0.4.5 update**: support DSH `0.1.7-alpha.2` mobile boot URLs, fix reconnect failures and long-lived caching of failed resources, and add an exit from App connection restoration. [Details](CHANGELOG.md).
35
35
  >
36
- > **Upgrade reminder**: the 0.4.4 plugin continues to work with existing Android apps and paired devices do not need re-pairing. Install the 0.4.4 app if you want this Android build. [Compatibility notes](#compatibility).
36
+ > **Upgrade reminder**: update both the plugin and Android app to 0.4.5 when practical. Existing pairings are retained and older apps can still connect, but Retry and Device list during connection restoration require the new app. [Compatibility notes](#compatibility).
37
37
 
38
38
  <p align="center">
39
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.4/dsh-mobile-android-v0.4.4.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
40
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.4/dsh-mobile-android-v0.4.4.apk"><strong>Download Android app 0.4.4</strong></a><br>
41
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.4">Release notes and checksums</a></sub>
39
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.5/dsh-mobile-android-v0.4.5.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
40
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.5/dsh-mobile-android-v0.4.5.apk"><strong>Download Android app 0.4.5</strong></a><br>
41
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.5">Release notes and checksums</a></sub>
42
42
  </p>
43
43
 
44
44
  DSH Mobile is a DeepSeek Harness plugin that lets a mobile browser or the Android app connect over a protected LAN or an optional Tailscale Funnel, cpolar, cloudflared, self-hosted FRP, or own reverse-proxy remote path. Local and remote access keep the same sessions, Workspaces, messages, and tools while using separate switches and paired-device stores without modifying DeepSeek Harness source.
@@ -55,7 +55,7 @@ It also lets you customize the phone from a DSH conversation: `/mobile <what you
55
55
  - **Every remote path covered**: Tailscale, cpolar, cloudflared quick/named tunnels, self-hosted FRP, or your own reverse proxy.
56
56
  - **Pairing and multi-device**: pair once via QR code, link, or key; Wi-Fi, hotspot, or IP changes normally recover automatically; the app shows all paired computers together in one device list (LAN and every remote), each with live reachability — switch, re-pair, or delete in one tap.
57
57
  - **One-click diagnostics and approval**: check versions, gateway, network interface, firewall, and the remote path with a redacted report; approve blocked third-party plugin connections per exact path.
58
- - **Task system notifications**: completion and pending-input alerts via Android system notifications, enabled from the app foreground menu, with redacted lock-screen text.
58
+ - **Task system notifications**: completion and pending-input alerts via Android system notifications, enabled from DSH General settings inside the app, with redacted lock-screen text.
59
59
  - **Defense in depth**: dedicated HTTPS with pinned certificates, Keystore-backed credentials, device tokens sent only to their exact Origin, third-party WebSockets blocked by default.
60
60
 
61
61
  A paired device is fully trusted and can operate the DSH on the computer. Use this only on a trusted home or office LAN, or a trusted VPN.
@@ -216,7 +216,7 @@ Compatibility and WebSocket rules:
216
216
 
217
217
  Proxied pages allow HTTP frames for compatibility with some community plugins; those pages are unencrypted and can be altered, and browsers may still block them as mixed content. Use HTTPS for sensitive work. The same warning appears at the top of the remote panel when it is opened over HTTPS.
218
218
 
219
- - Version 0.4.4 is contract-checked against DSH `0.1.6-alpha.2` (renderer-v2). The DSH page must expose the standard session, `main`/`panelInfo`, and `rightbar` slots; the community plugin must register its panel or sidebar content through DSH's standard entry points.
219
+ - Version 0.4.5 has been contract-checked and boot-tested against DSH `0.1.7-alpha.2` (renderer-v2). The DSH page must expose the standard session, `main`/`panelInfo`, and `rightbar` slots; the community plugin must register its panel or sidebar content through DSH's standard entry points.
220
220
  - The gateway allows first-party DSH WebSocket paths by default, including `/sidebar/ws/terminal`. Other paths used by community sidebar plugins are blocked by default and appear in Diagnostics; the `/sidebar/ws/agent-opens` and `/sidebar/ws/agent-terminals` paths in the image are examples that must be reviewed for the actual plugin.
221
221
  - In **Connection diagnostics → Third-party WebSocket paths**, select **Allow** only for an exact path you have verified. Query strings and fuzzy prefixes are rejected; **Allow all** is not recommended. Approved paths can be removed at any time, and the same policy applies to LAN and remote connections.
222
222
  - Approval only lets that path pass through the authenticated, same-origin DSH Mobile gateway. It does not open arbitrary TCP/UDP ports or bypass device pairing. If a community plugin still fails, check the path recorded by Diagnostics and approve one path at a time.
@@ -286,7 +286,7 @@ See [SECURITY.md](SECURITY.md).
286
286
 
287
287
  ## Compatibility
288
288
 
289
- The table below lists, for each plugin version, the DeepSeek Harness version it is verified to support (earlier 0.1.x releases are compatible as well). Starting with 0.3.6 the plugin no longer rejects a DSH version by number alone; newer unlisted versions are covered by CI's contract checks. History lives in [CHANGELOG.md](CHANGELOG.md).
289
+ The table records tested DSH and plugin version combinations; it does not imply automatic compatibility with other versions. Since 0.3.6 the plugin has not rejected DSH solely by version number. If mobile access breaks after upgrading DSH, check this table and update the plugin first. History lives in [CHANGELOG.md](CHANGELOG.md).
290
290
 
291
291
  ### OS support matrix
292
292
 
@@ -303,6 +303,7 @@ On macOS, local network, self-hosted FRP, and the own reverse proxy work; the th
303
303
 
304
304
  | DSH Mobile plugin | Verified DeepSeek Harness version |
305
305
  | --- | --- |
306
+ | `0.4.5` | `0.1.7-alpha.2` (source contract check, local and remote gateway integration) |
306
307
  | `0.4.4` | `0.1.6-alpha.2` (local source and renderer-v2 contract check) |
307
308
  | `0.4.3` | `0.1.6-alpha.2` (local source and renderer-v2 contract check) |
308
309
  | `0.4.2` | `0.1.6-alpha.1` (local source and renderer-v2 contract check) |
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/dsh-mobile"><img src="https://img.shields.io/npm/v/dsh-mobile?label=npm&color=CB3837" alt="npm 版本"></a>
11
- <a href="https://www.npmjs.com/package/dsh-mobile"><img src="https://img.shields.io/npm/dm/dsh-mobile?label=downloads&color=2563EB" alt="npm 月下载量"></a>
11
+ <a href="https://www.npmjs.com/package/dsh-mobile"><img src="https://img.shields.io/npm/dt/dsh-mobile?label=downloads&color=2563EB" alt="npm 总下载量"></a>
12
12
  <a href="https://github.com/saya-ch/dsh-mobile/actions/workflows/ci.yml"><img src="https://github.com/saya-ch/dsh-mobile/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
13
13
  <a href="https://github.com/saya-ch/dsh-mobile/releases"><img src="https://img.shields.io/badge/Android-10%2B-3DDC84?logo=android&logoColor=white" alt="Android 10+"></a>
14
14
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-0F172A" alt="Apache-2.0"></a>
@@ -31,14 +31,14 @@
31
31
 
32
32
  > DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
33
33
  >
34
- > **0.4.4 更新**:局域网发现遇到运行期 mDNS/UDP 错误时不再带崩 DSH;组件下载也能处理缺少 `Content-Length` 的代理响应。[详细记录](CHANGELOG.md)。
34
+ > **0.4.5 更新**:适配 DSH `0.1.7-alpha.2` 的移动端启动脚本地址,修复重连异常与失败资源的长期缓存,并为 App 的连接恢复增加退出入口。[详细记录](CHANGELOG.md)。
35
35
  >
36
- > **升级提醒**:0.4.4 插件可继续使用现有 Android App,已有设备无需重新配对。若要使用本次 Android 构建,请安装 0.4.4 App。[兼容说明](#兼容性)。
36
+ > **升级提醒**:建议插件与 Android App 同步更新至 0.4.5;已有配对会保留,旧版 App 仍可连接,但需要新版 App 才能使用连接恢复时的重试和设备列表入口。[兼容说明](#兼容性)。
37
37
 
38
38
  <p align="center">
39
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.4/dsh-mobile-android-v0.4.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.4.4/dsh-mobile-android-v0.4.4.apk"><strong>下载 Android App 0.4.4</strong></a><br>
41
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.4">版本说明与校验文件</a></sub>
39
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.5/dsh-mobile-android-v0.4.5.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.4.5/dsh-mobile-android-v0.4.5.apk"><strong>下载 Android App 0.4.5</strong></a><br>
41
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.5">版本说明与校验文件</a></sub>
42
42
  </p>
43
43
 
44
44
  DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、cloudflared、自建 FRP 或自有反向代理远程通道连接电脑,继续使用同一份会话、工作区、消息和工具。局域网与远程访问分别启停、分别管理设备,且都不修改 DeepSeek Harness 源码。
@@ -55,7 +55,7 @@ DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App
55
55
  - **多种远程通道**:Tailscale、cpolar、cloudflared 快速/命名隧道、自建 FRP、自有反向代理,按网络任选。
56
56
  - **配对与多设备**:扫码、链接或密钥配对一次;切换 Wi-Fi、热点或 IP 后通常自动恢复;App 在一个设备列表中同时显示多台已配对电脑(局域网与全部远程),每台实时显示可达状态,一键切换、重新配对或删除。
57
57
  - **一键诊断与放行**:检查版本、网关、网卡、防火墙和远程通道,生成脱敏报告;被拦截的第三方插件连接按确切路径一键放行。
58
- - **任务系统通知**:任务完成与待输入以 Android 系统通知推送,在 App 前台菜单开启,锁屏文本脱敏。
58
+ - **任务系统通知**:任务完成与待输入以 Android 系统通知提醒,可在 App 内的 DSH 常规设置中开启,锁屏文本脱敏。
59
59
  - **纵深安全**:独立 HTTPS 与证书固定,凭据存 Keystore,设备令牌只发往精确 Origin,第三方 WS 默认拦截。
60
60
 
61
61
  配对设备被视为完全信任,可以操作电脑上的 DSH;建议只在可信的家庭、办公局域网或可信 VPN 中使用。
@@ -216,7 +216,7 @@ Android App 用一个“已配对设备”列表同时显示多台电脑:局
216
216
 
217
217
  代理页面为兼容部分社区插件允许嵌入 HTTP 页面;这类内容未加密,可能被篡改,浏览器也可能因混合内容策略拦截。处理敏感内容时请使用 HTTPS。通过 HTTPS 管理入口打开远程面板时,页面顶部会显示相同提醒。
218
218
 
219
- - 0.4.4 按 DSH `0.1.6-alpha.2` 做 renderer-v2 契约检查。DSH 页面需要提供标准的会话、`main`/`panelInfo` 和 `rightbar` 插槽;插件自身需要通过 DSH 的标准面板或侧边栏入口注册内容。
219
+ - 0.4.5 已按 DSH `0.1.7-alpha.2` 做 renderer-v2 契约检查与启动包联调。DSH 页面需要提供标准的会话、`main`/`panelInfo` 和 `rightbar` 插槽;插件自身需要通过 DSH 的标准面板或侧边栏入口注册内容。
220
220
  - 网关默认只允许 DSH 内置的第一方 WebSocket 路径。社区侧边栏插件使用的其他路径默认拦截,通常会在诊断页显示为待处理项目;截图中的`/sidebar/ws/agent-opens` 和`/sidebar/ws/agent-terminals` 就属于这类需要按实际插件确认的路径。
221
221
  - 在 **连接诊断 → 第三方 WebSocket 路径** 中,只对确认过的精确路径点击 **允许**。系统不接受带查询字符串或模糊前缀的路径;不建议使用“全部允许”。已允许的路径可以随时移除,局域网和远程连接使用同一套规则。
222
222
  - 放行只代表该路径可以通过已认证、同源的 DSH Mobile 网关,不会开放任意 TCP/UDP 端口,也不会绕过设备配对。若社区插件仍然连接失败,先看诊断页的实际拦截路径,再按一条路径放行。
@@ -286,7 +286,7 @@ flowchart LR
286
286
 
287
287
  ## 兼容性
288
288
 
289
- 下表列出各插件版本验证支持到的 DeepSeek Harness 版本(早于该版本的 0.1.x 均兼容)。0.3.6 起插件不再按版本号拒绝启动,未列出的更新版本由 CI 契约检查兜底。历史记录见 [CHANGELOG.md](CHANGELOG.md)。
289
+ 下表记录已验证的 DSH 与插件版本组合,不表示其他版本自动兼容。0.3.6 起插件不会仅凭版本号拒绝启动;升级 DSH 后若遇到移动端异常,请先核对兼容表并更新插件。历史记录见 [CHANGELOG.md](CHANGELOG.md)。
290
290
 
291
291
  ### 系统支持矩阵
292
292
 
@@ -301,9 +301,9 @@ flowchart LR
301
301
 
302
302
  macOS 上局域网、自建 FRP 与自有反向代理可用;三个托管组件暂未提供 macOS 包。诊断页的防火墙检查目前仅覆盖 Windows,其他系统显示“不适用”。
303
303
 
304
-
305
304
  | DSH Mobile 插件 | 验证支持的 DeepSeek Harness 版本 |
306
305
  | ----------------------------------------- | -------------------------------------------------------------- |
306
+ | `0.4.5` | `0.1.7-alpha.2`(源码契约检查、本机局域网及远程网关联调) |
307
307
  | `0.4.4` | `0.1.6-alpha.2`(本机源码与 renderer-v2 契约检查) |
308
308
  | `0.4.3` | `0.1.6-alpha.2`(本机源码与 renderer-v2 契约检查) |
309
309
  | `0.4.2` | `0.1.6-alpha.1`(本机源码与 renderer-v2 契约检查) |
package/SECURITY.md CHANGED
@@ -19,10 +19,10 @@ The maintainer will acknowledge a complete report within seven days. Publication
19
19
  - 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 must carry a same-origin `Origin` header.
20
20
  - 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
21
  - 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
- - Public remote origins provided by Funnel, cpolar, or self-hosted FRP 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.
22
+ - Public remote origins provided by Funnel, cpolar, cloudflared, self-hosted FRP, 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
23
  - Keep pairing closed except during a short local onboarding action.
24
24
  - Revoke a lost device immediately and rotate the device registry if credential theft is suspected.
25
- - Do not expose the LAN gateway through router port forwarding. Optional remote access uses a separate loopback gateway behind the selected Tailscale Funnel, cpolar, or self-hosted FRP path. The outer provider or Caddy terminates public TLS, while DSH pairing, device authentication, CSRF checks, and session revocation remain enforced by the plugin gateway.
25
+ - Do not expose the LAN gateway through router port forwarding. Funnel, cpolar, cloudflared, and self-hosted FRP use a separate loopback gateway behind the 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. The outer provider or proxy terminates public TLS, while DSH pairing, device authentication, CSRF checks, and session revocation remain enforced by the plugin gateway.
26
26
  - 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.
27
27
  - 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.
28
28
  - Self-hosted FRP accepts only a VPS address, frps control port, operator-generated high-entropy shared token, and standard-port public HTTPS origin. The plugin validates the token's length and format; the operator remains responsible for its entropy. It generates one HTTP vhost pointed at the plugin's ephemeral loopback gateway; arbitrary FRP configuration, TCP/UDP forwarding, FRP plugins, services, PATH entries, and startup tasks are not supported.
package/lib/client.js CHANGED
@@ -1851,8 +1851,10 @@ window.__ModuleLoader__.load({
1851
1851
  .dsh-mobile-settings_rowText { flex:1; min-width:0; display:flex; flex-direction:column; gap:4px; padding-right:48px; }
1852
1852
  .dsh-mobile-settings_title { color:var(--dsw-alias-label-primary); font-size:14px; font-weight:400; line-height:22px; }
1853
1853
  .dsh-mobile-settings_desc { color:var(--dsw-alias-label-tertiary); font-size:12px; font-weight:400; line-height:18px; }
1854
- .dsh-mobile-settings_selector { display:inline-flex; align-items:center; justify-content:center; min-width:max-content; height:36px; padding:0 14px; border:0; border-radius:18px; background:var(--dsw-alias-bg-module-platform); color:var(--dsw-alias-label-primary); font:inherit; font-size:14px; line-height:22px; cursor:pointer; }
1854
+ .dsh-mobile-settings_selector { display:inline-flex; align-items:center; justify-content:center; min-width:max-content; min-height:48px; padding:0 14px; border:0; border-radius:24px; background:var(--dsw-alias-bg-module-platform); color:var(--dsw-alias-label-primary); font:inherit; font-size:14px; line-height:22px; cursor:pointer; }
1855
1855
  .dsh-mobile-settings_selector:hover:not(:disabled) { background:var(--dsw-alias-interactive-bg-hover); }
1856
+ .dsh-mobile-settings_selector:active:not(:disabled) { background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover)); }
1857
+ .dsh-mobile-settings_selector:focus-visible { outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb); outline-offset:2px; }
1856
1858
  .dsh-mobile-settings_selector:disabled { cursor:wait; opacity:.55; }
1857
1859
  @media ${NATIVE_MOBILE_OVERLAY_QUERY} {
1858
1860
  html.dsh-native-mobile-active,html.dsh-native-mobile-active body { width:100%; height:100%; overflow:hidden; }
@@ -1923,7 +1925,7 @@ window.__ModuleLoader__.load({
1923
1925
  [data-dsh-mobile-settings-options] > * { width:100% !important; min-width:0 !important; }
1924
1926
  [data-dsh-mobile-settings-options] [data-slot="settings.general.item"] > [class*="_row"] { flex-direction:column !important; align-items:stretch !important; gap:12px !important; }
1925
1927
  [data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_rowText"] { width:100% !important; padding-right:0 !important; }
1926
- [data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_selector"] { box-sizing:border-box !important; align-self:flex-start !important; justify-content:space-between !important; min-width:0 !important; min-height:44px !important; max-width:100% !important; }
1928
+ [data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_selector"] { box-sizing:border-box !important; align-self:flex-start !important; justify-content:space-between !important; min-width:0 !important; min-height:48px !important; max-width:100% !important; }
1927
1929
  [data-dsh-mobile-settings-options] :is(input,select,textarea,button) { max-width:100%; }
1928
1930
  [data-dsh-mobile-settings-options] :is(input,select,textarea) { box-sizing:border-box; width:100%; min-width:0; }
1929
1931
  [data-dsh-mobile-settings-options] [class*="_head"] { min-width:0; flex-wrap:wrap; }
@@ -2904,6 +2906,90 @@ window.__ModuleLoader__.load({
2904
2906
  onClick: invoke
2905
2907
  }, state === "busy" ? copy.busy : state === "error" ? copy.retry : copy.action));
2906
2908
  }
2909
+ function mobileTaskNotificationCopy(locale) {
2910
+ if (locale === "zh") return {
2911
+ title: "任务通知",
2912
+ description: "在 Android 系统中开启任务完成和待确认提醒。",
2913
+ action: "通知设置",
2914
+ busy: "正在打开…",
2915
+ success: "请在系统提示或设置中确认任务通知权限。",
2916
+ reopen: "重新打开",
2917
+ error: "无法打开通知设置,请重试。",
2918
+ retry: "重试"
2919
+ };
2920
+ if (locale === "it") return {
2921
+ title: "Notifiche delle attività",
2922
+ description: "Attiva gli avvisi Android per le attività completate o in attesa di conferma.",
2923
+ action: "Impostazioni notifiche",
2924
+ busy: "Apertura…",
2925
+ success: "Conferma il permesso nella richiesta o nelle impostazioni di sistema.",
2926
+ reopen: "Riapri",
2927
+ error: "Impossibile aprire le impostazioni delle notifiche. Riprova.",
2928
+ retry: "Riprova"
2929
+ };
2930
+ return {
2931
+ title: "Task notifications",
2932
+ description: "Enable Android reminders for completed tasks and pending confirmation.",
2933
+ action: "Notification settings",
2934
+ busy: "Opening…",
2935
+ success: "Confirm notification access in the system prompt or settings.",
2936
+ reopen: "Open again",
2937
+ error: "Could not open notification settings. Try again.",
2938
+ retry: "Retry"
2939
+ };
2940
+ }
2941
+ /** Render the Android-only notification permission entry in DSH General settings. */
2942
+ function MobileTaskNotificationRow() {
2943
+ const [nativeReady, setNativeReady] = (0, react.useState)(false);
2944
+ const [state, setState] = (0, react.useState)("idle");
2945
+ (0, react.useEffect)(() => {
2946
+ let disposed = false;
2947
+ const checkNativeAction = () => {
2948
+ const bridge = window.__DSH_MOBILE_NATIVE__;
2949
+ if (bridge === void 0) {
2950
+ setNativeReady(false);
2951
+ return;
2952
+ }
2953
+ Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
2954
+ if (!disposed) setNativeReady(capabilities.includes("notification.settings"));
2955
+ }, () => {
2956
+ if (!disposed) setNativeReady(false);
2957
+ });
2958
+ };
2959
+ checkNativeAction();
2960
+ window.addEventListener("dsh-mobile-native-ready", checkNativeAction);
2961
+ return () => {
2962
+ disposed = true;
2963
+ window.removeEventListener("dsh-mobile-native-ready", checkNativeAction);
2964
+ };
2965
+ }, []);
2966
+ if (!nativeReady) return null;
2967
+ const locale = selectedMobileControlLocale();
2968
+ const copy = mobileTaskNotificationCopy(locale);
2969
+ const invoke = () => {
2970
+ const bridge = window.__DSH_MOBILE_NATIVE__;
2971
+ if (bridge === void 0 || state === "busy") return;
2972
+ setState("busy");
2973
+ bridge.invoke("notification.settings", {}).then(() => {
2974
+ setState("success");
2975
+ }, () => {
2976
+ setState("error");
2977
+ });
2978
+ };
2979
+ return (0, react.createElement)("div", {
2980
+ className: "dsh-mobile-settings_row",
2981
+ lang: locale
2982
+ }, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
2983
+ className: "dsh-mobile-settings_desc",
2984
+ "aria-live": "polite"
2985
+ }, state === "success" ? copy.success : state === "error" ? copy.error : copy.description)), (0, react.createElement)("button", {
2986
+ type: "button",
2987
+ className: "dsh-mobile-settings_selector",
2988
+ disabled: state === "busy",
2989
+ "aria-busy": state === "busy" ? "true" : void 0,
2990
+ onClick: invoke
2991
+ }, state === "busy" ? copy.busy : state === "success" ? copy.reopen : state === "error" ? copy.retry : copy.action));
2992
+ }
2907
2993
  const CONTROL_REQUEST_TIMEOUT_MS = 15e3;
2908
2994
  const LONG_CONTROL_REQUEST_TIMEOUT_MS = 21e4;
2909
2995
  const GITHUB_RELEASES_URL = "https://github.com/saya-ch/dsh-mobile/releases";
@@ -7068,11 +7154,22 @@ window.__ModuleLoader__.load({
7068
7154
  style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
7069
7155
  document.head.append(style);
7070
7156
  if (!desktopAdmin) {
7071
- const removeSettingsAction = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
7072
- name: "settings.general.item",
7073
- id: "dsh-mobile-switch-computer",
7074
- order: 100
7075
- }, MobileSwitchComputerRow));
7157
+ const removeSettingsAction = ctx.slots.inject("settings.general.item", () => {
7158
+ const removeSwitchComputer = ctx.slots.register({
7159
+ name: "settings.general.item",
7160
+ id: "dsh-mobile-switch-computer",
7161
+ order: 100
7162
+ }, MobileSwitchComputerRow);
7163
+ const removeTaskNotifications = ctx.slots.register({
7164
+ name: "settings.general.item",
7165
+ id: "dsh-mobile-task-notifications",
7166
+ order: 110
7167
+ }, MobileTaskNotificationRow);
7168
+ return () => {
7169
+ removeTaskNotifications();
7170
+ removeSwitchComputer();
7171
+ };
7172
+ });
7076
7173
  const removeCustom = installCustomAssets();
7077
7174
  const removeSurface = installDshLanguageBoundSurface(installNativeMobileSurface);
7078
7175
  return () => {