dsh-mobile 0.6.11 → 0.6.13
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 +17 -0
- package/CONTRIBUTORS.md +9 -3
- package/README.en.md +8 -4
- package/README.md +8 -4
- package/SECURITY.md +8 -0
- package/apps/mobile/README.md +2 -0
- package/apps/mobile/README.zh-CN.md +2 -0
- package/docs/SELF_HOSTED_ORIGIN.en.md +2 -0
- package/docs/SELF_HOSTED_ORIGIN.md +2 -0
- package/docs/SLOW_CONNECTIONS.en.md +14 -0
- package/docs/SLOW_CONNECTIONS.md +14 -0
- package/lib/index.d.mts +42 -3
- package/lib/index.mjs +292 -80
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-compat.js +5 -1
- package/lib/mobile-compat.js.map +1 -1
- package/package.json +4 -1
- package/packages/question-fixes/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes are recorded here. GitHub Releases provide the downloadable packages and generated commit notes.
|
|
4
4
|
|
|
5
|
+
## 0.6.13 - 2026-10-19
|
|
6
|
+
|
|
7
|
+
- Distinguish inbound HTTP receive timeouts (408 `request_timeout`) and oversized request headers (431 `headers_too_large`) from other malformed HTTP requests (400 `bad_request`). Preserve the existing receive limits and single-attempt POST forwarding; do not automatically retry messages.
|
|
8
|
+
- Add sampled HTTP rejection diagnostics that distinguish parser errors, invalid targets and authenticated upstream prompt rejections without recording request contents, raw packets, Cookies or arbitrary URLs. Connect diagnostic sinks for all Gateway factories, including own-origin and self-signed FRP entries.
|
|
9
|
+
- Add real-page prompt-failure checks for restored text/images and visible errors. Separately reproduce a DSH 0.2.0-rc.2 and 0.2.1-rc.2 lifecycle defect: leaving a Session during a pending send cancels that request and discards its recovery state, including in pure stock profiles without Mobile installed. DSH source remains unchanged; this upstream defect and the exact cause of the reported #234 rejection are not claimed fixed. Thanks @Void-LifeStream for [#234](https://github.com/saya-ch/dsh-mobile/issues/234).
|
|
10
|
+
- Distinguish HTTP receive timeouts and oversized request headers in the Gateway response and add bounded, privacy-preserving rejection diagnostics across all Gateway factories. Existing request limits, authentication checks and single-attempt POST forwarding remain unchanged; this release does not add automatic message retries.
|
|
11
|
+
- Add a packed prompt-failure smoke covering text/image recovery and visible DSH errors on DSH 0.2.0-rc.2 and 0.2.1-rc.2. The optional navigation diagnostic records an upstream Session-lifecycle failure but is not a passing regression lane.
|
|
12
|
+
- Keep DSH source and installed frontend artifacts read-only. The proposed iroh P2P channel in [#235](https://github.com/saya-ch/dsh-mobile/issues/235) is intentionally outside this repository and this release; it requires a separate host-side proof of concept.
|
|
13
|
+
|
|
14
|
+
## 0.6.12 - 2026-10-18
|
|
15
|
+
|
|
16
|
+
- Send explicit non-navigation Fetch Metadata during the Gateway's internal DSH cookie exchange. This avoids the HTML navigation bounce produced by community authentication plugins without accepting arbitrary HTTP 200 pages, following redirects, or forwarding the upstream Cookie to the phone. Thanks @jamorzzz for [#230](https://github.com/saya-ch/dsh-mobile/issues/230).
|
|
17
|
+
- Separate invalid pairing/renewal/probe attempts from valid credentials behind a shared proxy. Invalid requests retain direct-peer limits; valid pairing secrets receive bounded admission, while renewal and probe budgets belong to the authenticated device. Keep operations accounted for until they settle even when the caller disconnects, and recheck credentials inside serialized writes. Forwarded IP headers remain untrusted. Thanks @Akiyuki1130 for [#231](https://github.com/saya-ch/dsh-mobile/issues/231).
|
|
18
|
+
- Extend the pre-boot compatibility script with identified missing array, string, object and structured-clone APIs. Add Chinese, English and Italian startup-failure feedback with explicit reload/wait actions when DSH has not rendered; mounted DSH and its own loader are not replaced, and no automatic reload is added.
|
|
19
|
+
- Identify unsupported class static-block syntax in DSH 0.2.1-rc.2's published frontend: Chromium 92 cannot parse that bundle. API shims and the existing old-engine advisory do not establish support for that engine. The feedback script itself targets Chromium 92; it does not transpile DSH. Thanks @yunyunlyk for the detailed engine and entry-mode comparison in [#232](https://github.com/saya-ch/dsh-mobile/issues/232).
|
|
20
|
+
- Add DSH 0.2.1-rc.2 to the packed-browser CI matrix and distinguish plugin-update SSE from conversation transport in troubleshooting. Android App 0.6.5 (build 81) remains independently published; no native change or new APK is part of this release.
|
|
21
|
+
|
|
5
22
|
## 0.6.11 - 2026-10-17
|
|
6
23
|
|
|
7
24
|
- Stabilize earlier-history loading by requiring fresh touch or wheel input before an automatic load near the top. DSH's own scroll-position compensation cannot re-arm loading; switching sessions or scroll containers withdraws the previous input. Track only touch contacts started on the owned scroller so another finger elsewhere cannot keep an old loading intent active.
|
package/CONTRIBUTORS.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# 贡献者 / Contributors
|
|
2
2
|
|
|
3
|
-
GitHub 的 [Contributors 图表](https://github.com/saya-ch/dsh-mobile/graphs/contributors) 按进入默认分支的提交自动统计,无法手动加入仅提交 issue 或 PR 提案的成员。本页按截至 2026-10-
|
|
3
|
+
GitHub 的 [Contributors 图表](https://github.com/saya-ch/dsh-mobile/graphs/contributors) 按进入默认分支的提交自动统计,无法手动加入仅提交 issue 或 PR 提案的成员。本页按截至 2026-10-18 的实际参与方式感谢社区成员;仓库维护者与自动依赖更新仍可在 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-10-
|
|
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-10-18. The repository maintainer and automated dependency updates remain visible in GitHub's history.
|
|
6
|
+
|
|
7
|
+
## 0.6.12 问题定位 / Issue findings for 0.6.12
|
|
8
|
+
|
|
9
|
+
感谢 [@jamorzzz](https://github.com/jamorzzz) 在 [#230](https://github.com/saya-ch/dsh-mobile/issues/230) 提供社区认证插件的导航响应与网关握手分析,[@Akiyuki1130](https://github.com/Akiyuki1130) 在 [#231](https://github.com/saya-ch/dsh-mobile/issues/231) 复现共享代理下的匿名限流耗尽,以及 [@yunyunlyk](https://github.com/yunyunlyk) 在 [#232](https://github.com/saya-ch/dsh-mobile/issues/232) 提供具体华为内核版本与 App/浏览器对照。这些报告分别促成认证握手、凭据限流和旧内核启动反馈改进;未将报告者真机写成已完成修复验收。
|
|
10
|
+
|
|
11
|
+
Thanks [@jamorzzz](https://github.com/jamorzzz) for the community-auth navigation and Gateway exchange analysis in [#230](https://github.com/saya-ch/dsh-mobile/issues/230), [@Akiyuki1130](https://github.com/Akiyuki1130) for reproducing anonymous rate-limit exhaustion behind a shared proxy in [#231](https://github.com/saya-ch/dsh-mobile/issues/231), and [@yunyunlyk](https://github.com/yunyunlyk) for the exact Huawei engine version and App/browser comparison in [#232](https://github.com/saya-ch/dsh-mobile/issues/232). These reports informed authentication, credential-admission and old-engine startup feedback improvements; they are not recorded as completed acceptance on the reporters' devices.
|
|
6
12
|
|
|
7
13
|
## 0.6.11 代码贡献 / Code contributions in 0.6.11
|
|
8
14
|
|
|
@@ -130,7 +136,7 @@ Thanks [@xhwxt](https://github.com/xhwxt) for the document-picker proposal in [#
|
|
|
130
136
|
| [@sortjiajun](https://github.com/sortjiajun) | [#90](https://github.com/saya-ch/dsh-mobile/issues/90) |
|
|
131
137
|
| [@StrawberryAO](https://github.com/StrawberryAO) | [#39](https://github.com/saya-ch/dsh-mobile/issues/39) |
|
|
132
138
|
| [@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) |
|
|
133
|
-
| [@Void-LifeStream](https://github.com/Void-LifeStream) | [#205](https://github.com/saya-ch/dsh-mobile/issues/205), [#219](https://github.com/saya-ch/dsh-mobile/issues/219), [#225](https://github.com/saya-ch/dsh-mobile/issues/225) |
|
|
139
|
+
| [@Void-LifeStream](https://github.com/Void-LifeStream) | [#205](https://github.com/saya-ch/dsh-mobile/issues/205), [#219](https://github.com/saya-ch/dsh-mobile/issues/219), [#225](https://github.com/saya-ch/dsh-mobile/issues/225), [#234](https://github.com/saya-ch/dsh-mobile/issues/234) |
|
|
134
140
|
| [@WalkerJay666](https://github.com/WalkerJay666) | [#201](https://github.com/saya-ch/dsh-mobile/issues/201) |
|
|
135
141
|
| [@up-and-down-0618](https://github.com/up-and-down-0618) | [#115](https://github.com/saya-ch/dsh-mobile/issues/115) |
|
|
136
142
|
| [@taki-ohh](https://github.com/taki-ohh) | [#19](https://github.com/saya-ch/dsh-mobile/issues/19) |
|
package/README.en.md
CHANGED
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
<a href="#compatibility">Compatibility</a> ·
|
|
27
27
|
<a href="docs/README.md">Documentation</a> ·
|
|
28
28
|
<a href="#contributors">Contributors</a> ·
|
|
29
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.
|
|
29
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.13">Release notes</a> ·
|
|
30
30
|
<a href="README.md">简体中文</a>
|
|
31
31
|
</p>
|
|
32
32
|
|
|
33
33
|
> DSH Mobile is a DeepSeek Harness community plugin; the native app supports Android only.
|
|
34
34
|
>
|
|
35
|
-
> **Current stable release: 0.6.
|
|
35
|
+
> **Current stable release: 0.6.13**. Distinguish Gateway receive timeouts and oversized headers, add privacy-bounded rejection diagnostics, and cover packed text/image prompt failures. Android App **0.6.5** is unchanged and needs no matching update. [Release notes and downloads](https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.13) · [Full changelog](CHANGELOG.md).
|
|
36
36
|
>
|
|
37
37
|
> **Version policy**: since plugin **0.6.2**, the Android app and plugin use independent version numbers and releases. Their numbers do not need to match, and a plugin update does not imply an app update. [Compatibility notes](#compatibility).
|
|
38
38
|
|
|
@@ -336,9 +336,11 @@ Selection priority is device choice → computer defaults → plugin configurati
|
|
|
336
336
|
|
|
337
337
|
The Android app is a thin Kotlin WebView shell and contains no frontend copy; mobile browsers load the same page. Developers can append `?frontend=stock` to an authenticated mobile URL to diagnose boot and layout behavior. This entry skips dedicated mobile boot rewriting and module selection, but installed client adaptation and authenticated customizations may still apply. It is not a plugin-free control; some API operations may be rejected, so it does not replace normal mobile access. See [skins and mobile layout](docs/SKIN_COMPATIBILITY.en.md).
|
|
338
338
|
|
|
339
|
-
|
|
339
|
+
The dedicated mobile page synchronously loads the authenticated, same-origin `/mobile-access/compat.js` before the first DSH boot script. This bundled compatibility layer supplies missing `Iterator` / Iterator helpers, `Promise.withResolvers` and `AbortSignal.any`. It uses feature detection to preserve or repair native implementations, needs no CDN, and does not weaken CSP. The desktop and `?frontend=stock` pages do not load this gateway script. On WebViews that support document-start injection, the Android App also supplies `Promise.withResolvers` early for the paired exact Origin.
|
|
340
340
|
|
|
341
|
-
|
|
341
|
+
**Old-engine limits**: API shims cannot repair JavaScript syntax an engine cannot parse. The published **DSH 0.2.1-rc.2** frontend contains class static blocks, which Chromium **92** cannot parse. This syntax became enabled by default in Chrome 94, but that does not establish full DSH compatibility on 94. [V8 announcement](https://v8.dev/blog/v8-release-94). The App's below-100 advisory is not a compatibility guarantee; choosing Continue does not make the current DSH runnable. If system WebView cannot be updated, use a phone browser already confirmed to work with the computer's mobile address, without clearing existing App pairings.
|
|
342
|
+
|
|
343
|
+
**Plugin 0.6.13** retains the 0.6.12 compatibility layer and startup feedback, distinguishes Gateway receive timeouts from oversized headers, and adds packed text/image prompt-failure checks. It does not transpile or modify DSH, and it does not automatically replay messages; acceptance on the physical Huawei Chromium 92 App remains unverified. Reports should include DSH/plugin/App and WebView versions, entry mode and the first redacted console error, never tokens or complete pairing URLs.
|
|
342
344
|
|
|
343
345
|
> **Community client (unofficial)**: [WeChat Mini-Program client](https://github.com/StrawberryAO/dsh-mobile-minapp)
|
|
344
346
|
> A native WeChat Mini-Program that reuses the Mobile Access pairing and Remote stream protocol (requires dsh-mobile ≥ 0.3.8).
|
|
@@ -405,6 +407,8 @@ On macOS, local network, cloudflared, self-hosted FRP and the own reverse proxy
|
|
|
405
407
|
| DSH Mobile plugin | Verified DeepSeek Harness version |
|
|
406
408
|
| --- | --- |
|
|
407
409
|
| `0.6.11` | Isolated packed-browser CI on `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`, `0.2.1-alpha.2` and `0.2.1-alpha.3`; additional real history-pagination, viewport-preservation and manual-loading checks on `0.2.0-rc.2` and `0.2.1-alpha.3`. Browser checks do not establish acceptance of every phone engine, third-party plugin or public route |
|
|
410
|
+
| `0.6.12` | Packed installation, mobile boot, missing-API simulation, Workspace streaming, retained-page transport recovery and mobile settings on `0.2.1-rc.2`; authentication/resource regression with the actual community `dsh-web-startup-auth@0.1.12` on `0.2.0-rc.2`. This does not establish physical Chromium 92, arbitrary auth-plugin or public-route acceptance |
|
|
411
|
+
| `0.6.13` | Packed text/image HTTP 400 recovery, Host refusal guard and error-feedback checks on `0.2.0-rc.2` and `0.2.1-rc.2`; Gateway 408/431 classification, bounded diagnostics and four Gateway factory wiring passed local tests. DSH source, iroh P2P and Android native code are outside this release |
|
|
408
412
|
|
|
409
413
|
<details>
|
|
410
414
|
<summary>Historical version checks</summary>
|
package/README.md
CHANGED
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
<a href="#兼容性">兼容性</a> ·
|
|
27
27
|
<a href="docs/README.md">文档</a> ·
|
|
28
28
|
<a href="#贡献者">贡献者</a> ·
|
|
29
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.
|
|
29
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.13">更新记录</a> ·
|
|
30
30
|
<a href="README.en.md">English</a>
|
|
31
31
|
</p>
|
|
32
32
|
|
|
33
33
|
> DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
|
|
34
34
|
>
|
|
35
|
-
> **当前正式版本:0.6.
|
|
35
|
+
> **当前正式版本:0.6.13**。区分网关 HTTP 接收超时与请求头过大错误,增加隐私受限的拒绝诊断,并补充图文发送失败回归。Android App **0.6.5** 保持不变,无需同步升级。[更新与下载](https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.13) · [完整更新记录](CHANGELOG.md)。
|
|
36
36
|
>
|
|
37
37
|
> **版本说明**:自插件 **0.6.2** 起,Android App 与插件独立编号、独立发布,版本号无需一致;插件升级不代表 App 需要同步升级。[兼容说明](#兼容性)。
|
|
38
38
|
|
|
@@ -336,9 +336,11 @@ excludedClientModules:
|
|
|
336
336
|
|
|
337
337
|
Android App 只是 Kotlin WebView 薄壳,不内置另一份网页;手机浏览器访问的是同一页面。开发者可在已认证移动地址后追加 `?frontend=stock` 诊断启动与布局行为;该入口跳过专用移动启动改写和模块选择,但仍可能加载客户端移动适配及已认证定制,并非不含插件的对照页。部分 API 操作可能被拒绝,不能代替正常移动入口。详见[皮肤与移动布局](docs/SKIN_COMPATIBILITY.md)。
|
|
338
338
|
|
|
339
|
-
|
|
339
|
+
专用移动页面会在 DSH 的第一个启动脚本之前,同步加载经网关鉴权的同源 `/mobile-access/compat.js`,为缺少 `Iterator` / Iterator helpers、`Promise.withResolvers` 和 `AbortSignal.any` 的 WebView 提供随插件打包的兼容实现。兼容层按能力检测保留或修正原生实现,不依赖 CDN,也不会放宽 CSP;桌面页面与 `?frontend=stock` 页面不会加载这个网关脚本。Android App 还会在支持 document-start 注入的 WebView 中,为配对的精确 Origin 提前补齐 `Promise.withResolvers`。
|
|
340
340
|
|
|
341
|
-
|
|
341
|
+
**旧内核限制**:API 兼容实现不能修复内核无法解析的 JavaScript 语法。DSH **0.2.1-rc.2** 的正式前端包含 class static blocks,Chromium **92** 无法解析;该语法从 Chrome 94 默认可用,但这不代表 94 已通过整个 DSH 的兼容验收。[V8 说明](https://v8.dev/blog/v8-release-94)。App 的“内核低于 100”提醒只是建议,点击继续不代表当前 DSH 可以运行。若系统 WebView 无法更新,可使用已确认正常的手机浏览器访问电脑端提供的移动地址,无需清除原有 App 配对。
|
|
342
|
+
|
|
343
|
+
插件 **0.6.13** 在保留 0.6.12 兼容层和启动反馈的基础上,区分网关接收超时与请求头过大,并增加图文发送失败的打包回归。它不转译 DSH、不修改 DSH 源码,也不自动重发消息;华为 Chromium 92 的实际 App 尚未完成验收。反馈问题时请提供 DSH/插件/App 与 WebView 版本、进入方式和脱敏后的首个控制台错误,不要发送令牌或完整配对地址。
|
|
342
344
|
|
|
343
345
|
> **社区客户端(非官方)**:[微信小程序客户端](https://github.com/StrawberryAO/dsh-mobile-minapp)
|
|
344
346
|
> 原生微信小程序实现,复用「移动访问」的配对与 Remote 流协议(需 dsh-mobile ≥ 0.3.8)。
|
|
@@ -406,6 +408,8 @@ macOS 上局域网、cloudflared、自建 FRP 与自有反向代理可用;Funn
|
|
|
406
408
|
| DSH Mobile 插件 | 已验证的 DeepSeek Harness 版本与范围 |
|
|
407
409
|
| --- | --- |
|
|
408
410
|
| `0.6.11` | `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`、`0.2.1-alpha.2`、`0.2.1-alpha.3` 的隔离打包浏览器 CI;`0.2.0-rc.2` 与 `0.2.1-alpha.3` 另通过真实历史分页、滚动位置保持和手动加载检查。浏览器检查不代表所有手机内核、第三方插件或公网线路已验收 |
|
|
411
|
+
| `0.6.12` | `0.2.1-rc.2` 的打包安装、移动启动、缺失 API 模拟、工作区实时读取、保留页面的断线恢复及移动设置;`0.2.0-rc.2` 搭配实际社区认证插件 `dsh-web-startup-auth@0.1.12` 的认证与资源回归。未将 Chromium 92 真机、任意认证插件或公网线路写成已验收 |
|
|
412
|
+
| `0.6.13` | `0.2.0-rc.2`、`0.2.1-rc.2` 的打包图文 HTTP 400 恢复、Host 拒绝护栏与错误提示回归;网关 408/431 分类、三类受限诊断及四种 Gateway 工厂接线通过本地测试。DSH 源码、iroh P2P 和 Android 原生代码不在本版范围 |
|
|
409
413
|
|
|
410
414
|
<details>
|
|
411
415
|
<summary>历史版本验证记录</summary>
|
package/SECURITY.md
CHANGED
|
@@ -28,6 +28,13 @@ The maintainer will acknowledge a complete report within seven days. Publication
|
|
|
28
28
|
- Keep pairing closed except during a short local onboarding action.
|
|
29
29
|
- Revoke a lost device immediately and rotate the device registry if credential theft is suspected.
|
|
30
30
|
|
|
31
|
+
### Authentication admission and proxy peers
|
|
32
|
+
|
|
33
|
+
- Invalid pairing guesses, malformed renewal/probe requests and invalid device credentials are limited by the direct TCP peer. `X-Forwarded-For`, `X-Real-IP` and `Forwarded` do not identify an authenticated client. A reverse proxy can therefore give anonymous requests a shared failure budget; this is not a reason to trust forwarded headers without an explicit proxy policy.
|
|
34
|
+
- From plugin 0.6.12, a current valid one-time pairing secret does not consume that anonymous budget. At most one valid pairing candidate can wait for a durable mutation; it is rechecked inside the serialized operation. Renewal and native reachability probes use separate per-device budgets after credential verification, with at most one in-flight operation of each kind per device. Device capacity is independent of the anonymous peer-table capacity. Expiry and revocation are rechecked before mutation; a disconnected caller does not release work that is still pending.
|
|
35
|
+
- These limits protect bounded application work and prevent anonymous failures from exhausting valid credentials' budgets. They do not make an exposed listener immune to connection, bandwidth or CPU exhaustion. Keep pairing windows short, protect proxy ingress, and retain the existing Host/Origin, device-token and CSRF checks.
|
|
36
|
+
- The Gateway's internal upstream cookie exchange requires the configured loopback DSH Origin, its expected redirect status and a valid authentication Cookie. Explicit non-navigation Fetch Metadata avoids community authentication plugins' navigation HTML response; no arbitrary successful HTML page is accepted and no redirect is followed. The upstream Cookie remains server-side.
|
|
37
|
+
|
|
31
38
|
### Installation identity and device-state upgrades
|
|
32
39
|
|
|
33
40
|
- Automatically identified entries without a pairing CA use the random `installation-id` file in the active Mobile state directory for new pairings, not a hash of the state-file path. Explicit operator IDs and CA-bound LAN/self-signed FRP identities remain unchanged. The identifier is public, not an authentication secret. Copying the entire state directory also copies its identity and credentials; this fix does not distinguish cloned installations. Do not delete the identity file to troubleshoot a connection.
|
|
@@ -43,6 +50,7 @@ The maintainer will acknowledge a complete report within seven days. Publication
|
|
|
43
50
|
- 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.
|
|
44
51
|
- 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.
|
|
45
52
|
- Plugin 0.6.11 and later record limited HTTP authorization and CSRF rejection metadata. It omits credential values and query strings, categorizes arbitrary paths, normalizes Origin and admits only recognized fetch-site values. Each Gateway keeps two fixed diagnostic categories with 30-second repeat aggregation, not an unbounded per-client/path history. Diagnostic-callback failures do not change the rejection response. These records are not complete WebSocket or native-pairing traces, and absence of a record does not establish successful authentication. Review even bounded metadata before sharing logs.
|
|
53
|
+
- The unreleased HTTP-diagnostic change adds three fixed per-Gateway categories: parser rejection, invalid target, and an authenticated exact `POST /api/session/prompt` rejected by the upstream. Each has 30-second sampling with suppressed counts. Parser codes are allowlisted; raw packets, headers, request bodies, error messages, arbitrary URLs and query strings are excluded. A diagnostic failure cannot change the response or retry a write. Request timeout and header overflow remain rejected, using 408 and 431 rather than a generic 400; receive limits are not relaxed.
|
|
46
54
|
- 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.
|
|
47
55
|
- 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.
|
|
48
56
|
- 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.
|
package/apps/mobile/README.md
CHANGED
|
@@ -79,6 +79,8 @@ The native soft-keyboard adapter uses plain Enter for a line break only with a v
|
|
|
79
79
|
|
|
80
80
|
System bars, cutouts and keyboard occupancy are reserved once. WebView 151+ receives a minimum 64 MiB HTTP-cache quota for reusable versioned DSH resources; older engines retain their default quota. A Chromium-below-100 reminder is advisory, not a promise that every DSH API is supported on that engine.
|
|
81
81
|
|
|
82
|
+
A successful App connection does not establish that its engine can execute the page. The DSH 0.2.1-rc.2 frontend contains syntax Chromium 92 cannot parse. If system WebView cannot be updated, use a browser confirmed to work without clearing pairings. See the [old-engine notes](../../README.en.md#app-or-mobile-browser) for the plugin's startup feedback and support limits.
|
|
83
|
+
|
|
82
84
|
### Voice
|
|
83
85
|
|
|
84
86
|
DSH records audio through `getUserMedia` and sends it to the configured computer-side speech provider, not browser SpeechRecognition or the extension bridge. Android asks for microphone permission and accepts audio-only capture from the paired HTTPS Origin. Provider components, models and network access must work; granting recording permission does not verify transcription. Same-origin frontend plugins share that page permission.
|
|
@@ -79,6 +79,8 @@ DSH 的文件入口仍在 **添加** 组;当前附件 owner 支持图片时,
|
|
|
79
79
|
|
|
80
80
|
系统栏、屏幕缺口和键盘空间只预留一次。WebView 151+ 为 App 的可复用版本化 DSH 资源提供至少 64 MiB HTTP 缓存配额;旧内核保持默认配额。Chromium 低于 100 的提醒不阻断使用,也不承诺旧内核支持全部 DSH 接口。
|
|
81
81
|
|
|
82
|
+
App 连接成功不代表网页脚本能够运行。DSH 0.2.1-rc.2 的前端含有 Chromium 92 无法解析的语法;系统内核无法更新时可使用已验证可用的浏览器,不必清除配对。插件的启动反馈与支持范围见[旧内核说明](../../README.md#app-与手机浏览器)。
|
|
83
|
+
|
|
82
84
|
### 语音
|
|
83
85
|
|
|
84
86
|
DSH 通过 `getUserMedia` 录音并交给电脑已配置的语音提供方,不使用浏览器 SpeechRecognition 或扩展 Bridge。Android 申请麦克风权限,仅接受配对 HTTPS Origin 的纯音频请求;电脑组件、模型和网络仍需可用,录音授权不代表转写成功。同源前端插件共享该页面权限。
|
|
@@ -37,6 +37,8 @@ Bind to an explicit loopback or RFC1918 IPv4 already assigned to the DSH compute
|
|
|
37
37
|
|
|
38
38
|
CIDRs match the proxy's **direct TCP peer**, not the phone or `X-Forwarded-For` / `X-Real-IP` / `Forwarded`. Docker or NAT may change that peer. Prefer the actual single-host /32, not an entire subnet. Up to 16 canonical private/loopback CIDRs are accepted, separated by spaces or commas; explicit private proxy sources are required for a LAN bind.
|
|
39
39
|
|
|
40
|
+
Phones behind a proxy may share that direct peer. From plugin 0.6.12, invalid credentials still share its failure budget, but anonymous failures cannot exhaust a valid one-time pairing secret's admission. Authenticated renewals and probes have per-device limits and bounded pending work. Adding or trusting forwarded-IP headers is unnecessary. For `429 rate_limited` on older versions, update the plugin while retaining pairings; do not widen source CIDRs or expose the ordinary DSH listener.
|
|
41
|
+
|
|
40
42
|
Select **Save and start backend**. An active backend reloads its settings. The panel separates the public HTTPS origin from the actual listening HTTP backend; saved settings remain visible after stopping.
|
|
41
43
|
|
|
42
44
|
## Proxy requirements, including Lucky
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
|
|
39
39
|
CIDR 限制针对 TCP 连接的**直接来源地址**,不是手机 IP,也不是 `X-Forwarded-For`、`X-Real-IP` 或 `Forwarded`。Docker、桥接或 NAT 可能改变这个地址,应填写后端实际看到的私网来源。优先用单地址 `/32`,不要为了排错放宽整个局域网。最多 16 项,用空格或逗号分隔;只允许回环/私网网段,网络地址不能带主机位。LAN 监听必须显式列出私网代理来源。
|
|
40
40
|
|
|
41
|
+
代理后的手机可能共享一个直接来源地址。插件 0.6.12 起,错误凭据仍共享该来源的失败限额,但有效的一次性配对码不会被匿名失败耗尽;已认证设备的续期和探测按设备分别限频,并限制未完成操作。不需要为此添加或信任转发 IP 请求头。旧版出现 `429 rate_limited` 时先升级插件并保留配对记录,不要放宽来源网段或公开裸 DSH 端口。
|
|
42
|
+
|
|
41
43
|
点击 **保存并启动后端**。已经开启时会重新载入配置。面板会分别显示公网 HTTPS 地址与**正在监听的 HTTP 后端**;停止后仍可查看、复制已保存的后端地址。
|
|
42
44
|
|
|
43
45
|
## 反向代理要求(含 Lucky)
|
|
@@ -20,6 +20,20 @@ Both [#126](https://github.com/saya-ch/dsh-mobile/issues/126) and [#146](https:/
|
|
|
20
20
|
|
|
21
21
|
Plugin 0.6.11 and later write limited `auth failure` HTTP diagnostics to the plugin log (`$DSH_HOME/mobile-access/logs/dsh-mobile.log`): authorization versus CSRF rejection, status, method, a safe route or route category, related Cookie/header presence and counts, normalized Origin and recognized fetch-site values. It omits tokens, Cookie values, query strings and arbitrary original paths. Each Gateway rate-limits its two categories separately: the first event is immediate and same-category rejections aggregate within a 30-second window; `suppressed` counts previously suppressed records of that category, not all requests or only the current path. This helps investigate missing credentials and CSRF failures; it does not automatically repair a 401/403. The absence of a record does not establish successful authentication: earlier Host/Origin rejections, WebSocket upgrades and native pairing/renewal are outside these two recording points. Review logs before sharing them.
|
|
22
22
|
|
|
23
|
+
## HTTP 400, 408 and 431 (in development)
|
|
24
|
+
|
|
25
|
+
The unpublished plugin correction distinguishes request receive timeouts as `408 request_timeout` and oversized headers as `431 headers_too_large`; other malformed HTTP requests remain `400 bad_request`. Older versions may return 400 with the same short JSON for all of them, so a status and a 24-byte body do not identify the cause.
|
|
26
|
+
|
|
27
|
+
New `http failure` diagnostics distinguish three per-Gateway categories: `parser` for HTTP parsing/receive failures, `target` for invalid request targets, and `upstream` for an authenticated `POST /api/session/prompt` rejected upstream. The first event of each kind is immediate; repeats aggregate for 30 seconds, with `suppressed` counting earlier suppressed events of that kind. Logs exclude raw errors, request bodies, raw packets, Cookies, Tokens, queries and arbitrary URLs. No record does not establish success.
|
|
28
|
+
|
|
29
|
+
`upstreamTimeoutMs` governs upstream transport waits and also configures the complete inbound-request receive deadline. A continuously active upload can still exceed that total deadline, and Node's periodic checks affect the observed firing time. Do not delete pairings, clear App data or automatically resend because of HTTP 400 alone. Identify the rejection layer using the actual response body, Content-Type, exact versions and redacted proxy upstream status. Check the Session result before retrying an operation whose execution outcome is unknown.
|
|
30
|
+
|
|
31
|
+
## Keep event streams distinct
|
|
32
|
+
|
|
33
|
+
In DSH 0.2.0-rc.2, `/plugins/events` is the SSE stream for plugin-module graph and build updates, not conversation-message delivery. Its `200` response, small body or repeated reconnects alone cannot establish successful delivery, failure or a missing processing animation. Inspect the `/api/remote.mux` WebSocket and the actual HTTP request for that operation as well.
|
|
34
|
+
|
|
35
|
+
An ordinary message bubble and cleared draft can precede `POST /api/session/prompt`: they are an optimistic local echo, not Host acceptance. If no corresponding request appears, retain redacted request timing, status codes and DSH logs to distinguish a request never started by the browser, proxy rejection and Host acceptance. The plugin does not automatically replay messages, approvals or other writes; check the Session before retrying an operation with an unknown outcome.
|
|
36
|
+
|
|
23
37
|
## Slow transfers can exceed the default deadlines
|
|
24
38
|
|
|
25
39
|
DSH 0.2.0-rc.2's API gateway sends a WebSocket Ping every **2 seconds** by default and ends the connection after two missed Pongs, allowing roughly **4–6 seconds** in practice. The client separately allows **15 seconds** for live-connection initialization. Large Session data, a slow uplink, or congestion can delay a Pong or the first synchronization and trigger disconnection and another synchronization.
|
package/docs/SLOW_CONNECTIONS.md
CHANGED
|
@@ -20,6 +20,20 @@
|
|
|
20
20
|
|
|
21
21
|
插件 0.6.11 起会在插件日志(`$DSH_HOME/mobile-access/logs/dsh-mobile.log`)记录有限的 `auth failure` HTTP 诊断:区分授权与 CSRF 拒绝,包含状态、方法、安全路径或路径类别、相关 Cookie/请求头是否存在及计数、规范化 Origin 和受支持的 fetch-site 值,不记录令牌、Cookie 值、查询参数或任意原始路径。每个网关的两类诊断分别限频,首条立即记录,30 秒窗口内同类拒绝聚合;`suppressed` 表示此前被抑制的同类记录数量,不是全部请求数,也不限定于当前路径。它帮助排查凭据未携带或 CSRF 校验失败,不会自动修复 401/403。没有日志也不代表认证成功:更早的 Host/Origin 拒绝、WebSocket 升级和原生配对/续期不在这两处记录范围内。分享前仍应检查日志。
|
|
22
22
|
|
|
23
|
+
## HTTP 400、408 与 431 的区别(开发中)
|
|
24
|
+
|
|
25
|
+
尚未发布的插件修正会把请求接收超时区分为 `408 request_timeout`,请求头过大区分为 `431 headers_too_large`;其他非法 HTTP 请求仍为 `400 bad_request`。旧版可能把这几类情况都返回成 400 和相同的短 JSON,因此状态码与“24 字节”不足以定位原因。
|
|
26
|
+
|
|
27
|
+
新的 `http failure` 日志按网关区分 `parser`、`target`、`upstream` 三类:分别表示 HTTP 解析/接收失败、非法请求目标、已认证的 `POST /api/session/prompt` 被上游拒绝。每类首条立即记录,后续 30 秒内聚合,下一条的 `suppressed` 是此前被抑制的同类数量。日志不保留原始报错、请求正文、原始包、Cookie、Token、查询参数或任意 URL;不记录某条也不能证明它成功。
|
|
28
|
+
|
|
29
|
+
`upstreamTimeoutMs` 既用于上游传输等待,也配置了入站完整请求的接收期限;持续上传不保证避开这个总期限,Node 的定期检查会影响实际触发时刻。不要仅凭 HTTP 400 就删除配对、清空 App 数据或自动重发。先区分拒绝来源,并查看实际响应正文、Content-Type、准确版本和脱敏的代理上游状态;消息或工具是否已执行不明时,先核对会话结果。
|
|
30
|
+
|
|
31
|
+
## 不同事件流不能混为一谈
|
|
32
|
+
|
|
33
|
+
在 DSH 0.2.0-rc.2 中,`/plugins/events` 是插件模块图和构建更新的 SSE,不是对话消息投递通道。它返回 `200`、传输很少字节或反复重连,不能单独证明消息发送成功、失败或处理中动画丢失;同时检查 `/api/remote.mux` WebSocket 与那次操作实际发出的 HTTP 请求。
|
|
34
|
+
|
|
35
|
+
普通消息的气泡和清空草稿可能先于 `POST /api/session/prompt`,属于本地乐观回显,不是 Host 受理确认。若找不到对应请求,请保留脱敏后的请求时序、状态码及 DSH 日志,区分浏览器未发起、代理拒绝与 Host 已受理。插件不会自动重放消息、审批或其他写请求;结果不明时先确认会话,避免重复副作用。
|
|
36
|
+
|
|
23
37
|
## 慢速传输可能超过默认等待时间
|
|
24
38
|
|
|
25
39
|
DSH 0.2.0-rc.2 的 API 网关默认每 **2 秒**发送一次 WebSocket Ping,连续两次未收到 Pong 后会结束连接,实际容忍时间约为 **4–6 秒**。客户端另有 **15 秒**的实时连接初始化期限。较大的会话数据、低速上行或拥塞可能延迟 Pong 或首次数据同步,触发断开和重新同步。
|
package/lib/index.d.mts
CHANGED
|
@@ -124,6 +124,8 @@ export declare class BoundedRateLimiter {
|
|
|
124
124
|
constructor(limit: number, windowMs: number, maximumKeys: number);
|
|
125
125
|
/** Consume one attempt; unknown keys fail closed when the bounded table is full. */
|
|
126
126
|
take(key: string, now: number): boolean;
|
|
127
|
+
/** Discard entries that no longer identify an eligible key in a trusted, finite key set. */
|
|
128
|
+
retainKeys(eligible: ReadonlySet<string>): void;
|
|
127
129
|
/** Current table size, exposed for bounded-state assertions. */
|
|
128
130
|
get size(): number;
|
|
129
131
|
}
|
|
@@ -135,6 +137,7 @@ export declare class AccessController {
|
|
|
135
137
|
private readonly pairLimiter;
|
|
136
138
|
private devices;
|
|
137
139
|
private pairingWindow;
|
|
140
|
+
private pairPending;
|
|
138
141
|
private readonly sessions;
|
|
139
142
|
private readonly sessionEndedListeners;
|
|
140
143
|
private mutation;
|
|
@@ -157,6 +160,8 @@ export declare class AccessController {
|
|
|
157
160
|
token: string;
|
|
158
161
|
expiresAt: number;
|
|
159
162
|
}>;
|
|
163
|
+
/** Limit invalid guesses by peer without letting them consume a valid one-time secret's admission. */
|
|
164
|
+
private rejectPairingAttempt;
|
|
160
165
|
/** Consume the pairing window exactly once and persist only the device-token digest. */
|
|
161
166
|
pair(sourceKey: string, token: string, label?: string): Promise<PairingResult>;
|
|
162
167
|
/** Exchange a valid persistent device credential for a new short Session. */
|
|
@@ -535,6 +540,33 @@ interface AuthFailureEntry {
|
|
|
535
540
|
readonly suppressed?: number;
|
|
536
541
|
}
|
|
537
542
|
//#endregion
|
|
543
|
+
//#region src/http-failure-log.d.ts
|
|
544
|
+
/** Fixed parser details safe to record without Node's raw packet, message or request headers. */
|
|
545
|
+
type HttpParserCode = 'ERR_HTTP_REQUEST_TIMEOUT' | 'HPE_HEADER_OVERFLOW' | 'HPE_INVALID_CHUNK_SIZE' | 'HPE_INVALID_CONTENT_LENGTH' | 'HPE_INVALID_EOF_STATE' | 'HPE_INVALID_HEADER_TOKEN' | 'HPE_INVALID_TRANSFER_ENCODING' | 'HPE_UNEXPECTED_CONTENT_LENGTH' | 'other';
|
|
546
|
+
/** A rejected inbound HTTP message's public response and bounded diagnostic category. */
|
|
547
|
+
interface HttpParserFailure {
|
|
548
|
+
readonly status: 400 | 408 | 431;
|
|
549
|
+
readonly code: 'bad_request' | 'request_timeout' | 'headers_too_large';
|
|
550
|
+
readonly parserCode: HttpParserCode;
|
|
551
|
+
}
|
|
552
|
+
/** One sampled rejection. Arbitrary paths, query strings, body data and credentials are excluded. */
|
|
553
|
+
type HttpFailureEntry = ({
|
|
554
|
+
readonly kind: 'parser';
|
|
555
|
+
readonly status: HttpParserFailure['status'];
|
|
556
|
+
readonly code: HttpParserCode;
|
|
557
|
+
} | {
|
|
558
|
+
readonly kind: 'target';
|
|
559
|
+
readonly status: 400;
|
|
560
|
+
readonly code: 'bad_request';
|
|
561
|
+
} | {
|
|
562
|
+
readonly kind: 'upstream';
|
|
563
|
+
readonly status: number;
|
|
564
|
+
readonly method: 'POST';
|
|
565
|
+
readonly path: '/api/session/prompt';
|
|
566
|
+
}) & {
|
|
567
|
+
readonly suppressed?: number;
|
|
568
|
+
};
|
|
569
|
+
//#endregion
|
|
538
570
|
//#region src/http-security.d.ts
|
|
539
571
|
export declare const DEVICE_COOKIE = "dsh_ma_device";
|
|
540
572
|
export declare const SESSION_COOKIE = "dsh_ma_session";
|
|
@@ -986,16 +1018,21 @@ export declare class MobileAccessGateway {
|
|
|
986
1018
|
private closeTask;
|
|
987
1019
|
private readonly removeSessionListener;
|
|
988
1020
|
private readonly removeExtensionContentListener;
|
|
989
|
-
private readonly
|
|
990
|
-
private readonly
|
|
1021
|
+
private readonly renewFailureLimiter;
|
|
1022
|
+
private readonly renewDeviceLimiter;
|
|
1023
|
+
private readonly renewInFlight;
|
|
1024
|
+
private readonly probeFailureLimiter;
|
|
1025
|
+
private readonly probeDeviceLimiter;
|
|
1026
|
+
private readonly probeInFlight;
|
|
991
1027
|
private readonly clientModulePreferences;
|
|
992
1028
|
private readonly clientModuleOperations;
|
|
993
1029
|
private readonly clientModuleTasks;
|
|
994
1030
|
private readonly clientModuleRevocations;
|
|
995
1031
|
private readonly authFailures;
|
|
1032
|
+
private readonly httpFailures;
|
|
996
1033
|
constructor(config: ResolvedGatewayConfig, store: DeviceStore, extensions?: MobileAccessService | undefined, upstreamAuthenticatedUrl?: string | undefined, extraWebSocketPaths?: {
|
|
997
1034
|
has(pathname: string): boolean;
|
|
998
|
-
} | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined, onDiscoveryDegraded?: ((source: 'broadcast' | 'mdns', code: string) => void) | undefined, clientModulePreferences?: ClientModulePreferenceStore, authFailureLog?: (entry: AuthFailureEntry) => void);
|
|
1035
|
+
} | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined, onDiscoveryDegraded?: ((source: 'broadcast' | 'mdns', code: string) => void) | undefined, clientModulePreferences?: ClientModulePreferenceStore, authFailureLog?: (entry: AuthFailureEntry) => void, httpFailureLog?: (entry: HttpFailureEntry) => void);
|
|
999
1036
|
/** Initialize durable state, validate TLS, and bind the externally reachable listener. */
|
|
1000
1037
|
start(): Promise<void>;
|
|
1001
1038
|
/** Install a newly signed provided leaf for future TLS handshakes without dropping sessions. */
|
|
@@ -1032,6 +1069,8 @@ export declare class MobileAccessGateway {
|
|
|
1032
1069
|
private requireCsrf;
|
|
1033
1070
|
private setSessionCookies;
|
|
1034
1071
|
private setBrowserDeviceBinding;
|
|
1072
|
+
/** Separate malformed or invalid requests from device-authenticated work without trusting forwarded addresses. */
|
|
1073
|
+
private withDeviceRequest;
|
|
1035
1074
|
private handlePair;
|
|
1036
1075
|
private handleRenew;
|
|
1037
1076
|
/** CA-backed entries retain their pin; public entries project the authenticated credential's identity. */
|