dsh-mobile 0.7.0 → 0.8.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/SECURITY.md CHANGED
@@ -61,6 +61,13 @@ The maintainer will acknowledge a complete report within seven days. Publication
61
61
  - 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.
62
62
  - 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.
63
63
 
64
+ ### Experimental iroh transport
65
+
66
+ - iroh carries traffic over encrypted direct connections or relays to the computer's loopback Mobile Gateway. Its endpoint identity and short-lived transport-pairing code do not replace the Gateway's one-time pairing token, device authorization, CSRF checks or revocation. Transport and Gateway device authorizations remain distinct; revoking a DSH Mobile device still prevents authenticated Gateway access.
67
+ - The full Android App pins the Gateway CA supplied through explicit iroh pairing and uses an App-local `*.iroh.localhost` entry. This local entry is not a public hostname or a browser-compatible tunnel. Keep its loopback traffic out of remote proxies; never bypass certificate validation to repair VPN or DNS interference. The compatibility APK and ordinary mobile browsers do not contain this native transport.
68
+ - The computer component is downloaded only after confirmation, from the fixed source with pinned size and SHA-256. It runs under the current user's authority without a system service. Component removal requires iroh remote access to be disabled and deletes only the plugin-downloaded program, retaining saved identity, Gateway pairings and external companion installations. Uninstalling a binary is not revoking authorization.
69
+ - Current component targets are Windows/Linux x64; the full App packages arm64-v8a and x86_64. Network reachability, direct versus relayed traffic and background delivery are not guaranteed. VPN bypass is an operator choice, not a reason to disable system security policy. See [iroh setup and troubleshooting](docs/IROH.en.md).
70
+
64
71
  ### Device authority, Android and extensions
65
72
 
66
73
  - 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.
@@ -75,6 +82,12 @@ The maintainer will acknowledge a complete report within seven days. Publication
75
82
  - 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.
76
83
  - Proxied GUI responses allow same-origin and Blob audio/video through `media-src 'self' blob:`; Gateway-owned login and pairing pages do not gain Blob media permission. External media and connections remain restricted. This permits a trusted page plugin to play a same-origin file fetched into an object URL; it does not bypass browser codec support or autoplay rules. Android retains its media user-gesture requirement. Media plugins should provide a visible poster and a user-initiated playback fallback rather than assume autoplay always succeeds.
77
84
 
85
+ ## iPhone and iPad Web Push
86
+
87
+ Web Push is opt-in per paired device, browser installation and exact HTTPS Origin. Subscription endpoints, VAPID keys and notification state are private credentials stored in the Mobile state directory; do not include them in reports or backups shared publicly. The sender encrypts generic notification text and accepts only validated Apple HTTPS endpoints, with DNS pinning, certificate verification, no redirects and bounded sends. Notification references never authorize a request, reveal conversation content, or bypass the Gateway's device authentication.
88
+
89
+ Disable, logout and device revocation stop future eligible sends; already accepted notifications may still arrive. Logout must not report success while enabled consent cannot be durably disabled, including temporary loss of the optional push Host services. The explicit administrator reset affects the whole notification registry, not pairings, and is never an automatic repair. See [Web Push setup and recovery](docs/WEB_PUSH.en.md).
90
+
78
91
  ## Known limitation
79
92
 
80
93
  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.
@@ -18,9 +18,11 @@ The optional managed Caddy component is not included in the npm package or Andro
18
18
 
19
19
  The experimental Rust transport is adapted from [dsh-mobile-iroh](https://github.com/saya-ch/dsh-mobile-iroh), copyright 2026 Guangqi Zhang, under the MIT License, and the host baseline preserves zexadev's MIT-licensed dsh-tether attribution. The source notices and complete licenses are retained under `native/iroh-host/` and included with the npm package. The optional sidecar is not bundled: explicit installation reuses a verified companion copy or downloads the size- and SHA-256-pinned companion release payload. Enabling the channel alone does not download it.
20
20
 
21
- The npm package declares direct runtime dependencies on [Schemastery](https://github.com/shigma/schemastery) `^3.18.1`, [@noble/hashes](https://github.com/paulmillr/noble-hashes) `1.8.0`, [bonjour-service](https://github.com/onlxltd/bonjour-service) `1.4.4`, [eventsource-parser](https://github.com/rexxars/eventsource-parser) `3.1.0`, [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.
21
+ The npm package declares direct runtime dependencies on [Schemastery](https://github.com/shigma/schemastery) `^3.18.1`, [@noble/hashes](https://github.com/paulmillr/noble-hashes) `1.8.0`, [bonjour-service](https://github.com/onlxltd/bonjour-service) `1.4.4`, [eventsource-parser](https://github.com/rexxars/eventsource-parser) `3.1.0`, [qrcode](https://github.com/soldair/node-qrcode) `^1.5.4`, [selfsigned](https://github.com/jfromaniello/selfsigned) `^5.5.0`, [ws](https://github.com/websockets/ws) `8.22.0`, and [Zod](https://zod.dev/) `4.6.7`. These packages are distributed under the MIT License; the release checkout's exact transitive versions appear in the attached CycloneDX npm SBOM.
22
22
 
23
- 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`.
23
+ Browser Web Push uses [web-push](https://github.com/web-push-libs/web-push) `3.6.7`, distributed under the Mozilla Public License 2.0 (MPL-2.0), for payload encryption and VAPID signing/key generation. It remains an unmodified, external Node runtime dependency, not part of the browser bundle; its npm package supplies the covered source and license. DSH Mobile owns the bounded HTTPS transport and endpoint validation. Exact transitive versions appear in the [npm lockfile](<package-lock.json>) and release SBOM.
24
+
25
+ The Android App runtime contains [Kotlin standard library](https://github.com/JetBrains/kotlin) `2.2.10`, [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.18.0`, 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`.
24
26
 
25
27
  The full Android APK packages `libdsh_mobile_iroh_android_transport.so` for arm64-v8a and x86_64, from `native/iroh-android-transport` and `native/iroh-core`. It includes the matching `org.rustls:rustls-platform-verifier` JVM component from an immutable official Maven archive, selecting its version from the Android shim entry in `native/Cargo.lock`. These transport sources retain their MIT license; the surrounding Android shell remains Apache-2.0. The compatibility APK uses `-PirohNative=false` and contains neither native libraries nor the JVM verifier dependency; it retains installation support for 32-bit devices. Both builds include the third-party notices asset.
26
28
 
@@ -4,14 +4,14 @@
4
4
 
5
5
  DeepSeek Harness is the display name of this community-maintained Android WebView app. It opens the plugin's authenticated HTTPS page rather than bundling another DSH frontend. The app and mobile browsers receive the same DSH features and computer-side mobile customizations.
6
6
 
7
- The current stable app is **0.7.0** (build 85). The npm plugin and Android App have independent versions: a plugin update does not automatically require another APK. See the [0.7.0 changes](#070-update).
7
+ The current stable app is **0.7.1** (build 86). The npm plugin and Android App have independent versions: a plugin update does not automatically require another APK. See the [0.7.1 changes](#071-update).
8
8
 
9
- Android 10 or later is supported. The local iOS experiment is not a published or supported client.
9
+ This APK supports Android 10 or later. iPhone and iPad use the [Safari Home Screen web app and optional Web Push](../../docs/WEB_PUSH.en.md), not this Android package; no native iOS app is distributed.
10
10
 
11
11
  ## Install and pair
12
12
 
13
13
  1. Install and enable the plugin using the [project quick start](../../README.en.md#quick-start). Complete LAN setup from the command line or the computer's **Mobile Access** panel.
14
- 2. Install a signed Android APK from [GitHub Releases](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.0). Choose the compatibility `.apk` for existing HTTPS channels, or the full `-iroh.apk` for experimental iroh on arm64-v8a/x86_64. Both have the same package ID and official signer; install only one and upgrade in place to retain pairings. A differently signed Debug APK cannot replace it; do not uninstall or clear data just to bypass that mismatch.
14
+ 2. Install a signed Android APK from [GitHub Releases](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.1). Choose the compatibility `.apk` for existing HTTPS channels, or the full `-iroh.apk` for experimental iroh on arm64-v8a/x86_64. Both have the same package ID and official signer; install only one and upgrade in place to retain pairings. A differently signed Debug APK cannot replace it; do not uninstall or clear data just to bypass that mismatch.
15
15
  3. On the computer, generate a pairing key/link under **Mobile Access → Local network**, or configure a remote provider and generate its remote pairing QR code. In the app, choose the matching connection mode and scan or paste the link.
16
16
  4. Name the computer. The app stores each LAN or remote pairing as its own record. The phone does not need a cpolar, Cloudflare or Tailscale app.
17
17
 
@@ -36,11 +36,15 @@ Page zoom changes the whole page and is separate from per-address text preferenc
36
36
 
37
37
  Deleting a local record removes its saved token and pinned CA, not the computer's authorization or every short-lived WebView cookie. Use **Mobile Access** on the computer to revoke access immediately. A full Android clear-app-data reset removes all pairings and web data and is not a normal reconnection step.
38
38
 
39
+ ## 0.7.1 update
40
+
41
+ Update AndroidX WebKit to 1.18.0 and the build toolchain to AGP 9.4.1 / Gradle 9.8.2, retaining Android API 36 and the existing pairing format. Both APK variants keep the established signer and upgrade in place. Plugin 0.8.0's iPhone/iPad Web Push is a computer-side feature, not a reason to update the Android app.
42
+
39
43
  ## 0.7.0 update
40
44
 
41
45
  App 0.7.0/build 85 adds optional experimental iroh pairing and native transport in the full APK, working with plugin 0.7.0 or later. The compatibility APK retains existing HTTPS channels without native libraries, including on 32-bit devices. Legacy updates download compatibility by default; choose the full APK explicitly when using iroh. Both retain the device list, HTTPS pairings and established signer. Records from the separate experimental App do not migrate automatically.
42
46
 
43
- iroh is off by default. The computer installs a verified component, then generates a dedicated pairing QR code under **Mobile access → Remote → iroh (Experimental)**. Direct/relayed connectivity depends on both networks; mobile browsers cannot use this transport directly. See the [iroh guide](../../docs/IROH.en.md).
47
+ iroh is off by default. Select **iroh (Experimental)** in the computer's **Mobile access** connection list, install the verified component, then enable it and generate a dedicated pairing QR code. Direct/relayed connectivity depends on both networks; mobile browsers cannot use this transport directly. See the [iroh guide](../../docs/IROH.en.md).
44
48
 
45
49
  ## 0.6.8 update
46
50
 
@@ -148,7 +152,7 @@ Computer-side `host.mjs` extensions are trusted Node.js code, separate from this
148
152
 
149
153
  ## Build
150
154
 
151
- Use Android Studio or Android SDK 36 and JDK 17. The repository includes the Gradle 9.7.1 Wrapper.
155
+ Use Android Studio or Android SDK 36 and JDK 17. The repository includes the Gradle 9.8.2 Wrapper.
152
156
 
153
157
  ```powershell
154
158
  Set-Location apps/mobile/android
@@ -170,11 +174,11 @@ Native builds also include the JVM TLS verifier matching `native/Cargo.lock`, fr
170
174
 
171
175
  Current-source Debug builds use package `io.github.sayach.dshmobile.debug` with a `-debug` version suffix. They install beside the official App with separate storage and pairings. They do not upgrade or migrate an older unsuffixed Debug installation; preserve that app and its data rather than uninstalling or clearing it to switch packages. Release builds keep `io.github.sayach.dshmobile` and the established signer.
172
176
 
173
- The verified stable release is [Android 0.7.0](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.0), build 85. The compatibility and full APKs each have a matching `.sha256` asset. Both public downloads retain the established release signer and package `io.github.sayach.dshmobile`. Earlier release history belongs in [CHANGELOG](../../CHANGELOG.md).
177
+ The verified stable release is [Android 0.7.1](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.1), build 86. The compatibility and full APKs each have a matching `.sha256` asset. Both public downloads retain the established release signer and package `io.github.sayach.dshmobile`. Earlier release history belongs in [CHANGELOG](../../CHANGELOG.md).
174
178
 
175
179
  ## Acceptance and known limits
176
180
 
177
- - Both public App 0.7.0 APKs passed checksum, manifest, signer, notice and alignment checks. The full APK additionally passed ABI and retained JVM TLS/JNI entry checks; compatibility contains no native libraries. Earlier arm64 development evidence covers native iroh Gateway/custom-resource traffic without ADB forwarding. The final JVM TLS dependency correction has not had a new physical-device/public-relay run; a relay socket alone does not prove relayed payload traffic.
181
+ - Both public App 0.7.1 APKs passed checksum, manifest, established-signer, notice and alignment checks. The full APK additionally passed ABI and retained JVM TLS/JNI entry checks; compatibility contains no native libraries. There is no new physical-device or public-relay run for 0.7.1. Earlier arm64 development traffic remains historical evidence, not proof that a current connection uses the relay.
178
182
 
179
183
  - App 0.6.8's public APK passed identity, checksum and established-signer verification. A matching-signer Debug upgrade opened DSH and retained the saved device; controlled slow-link fault injection was interrupted before pairing and is not counted as passed.
180
184
 
@@ -4,14 +4,14 @@
4
4
 
5
5
  DeepSeek Harness 是这个社区维护的 Android WebView App 的显示名称。它直接打开插件提供的已认证 HTTPS 页面,不打包另一套 DSH 前端;App 与手机浏览器获得相同的 DSH 功能,以及电脑端配置的移动页面定制。
6
6
 
7
- 当前正式 App 为 **0.7.0**;构建号为 85。npm 插件与 Android App 独立编号、独立更新;插件升级不代表需要重新安装 APK。此次改进见 [0.7.0 更新](#070-更新)。
7
+ 当前正式 App 为 **0.7.1**;构建号为 86。npm 插件与 Android App 独立编号、独立更新;插件升级不代表需要重新安装 APK。此次改进见 [0.7.1 更新](#071-更新)。
8
8
 
9
- 支持 Android 10 及以上系统。iOS 本地实验不属于已发布或受支持的客户端。
9
+ 此 APK 支持 Android 10 及以上系统。iPhone 和 iPad 使用 [Safari 主屏幕 Web App 与可选网页推送](../../docs/WEB_PUSH.md),不安装 Android 包;本项目不分发原生 iOS App。
10
10
 
11
11
  ## 安装与配对
12
12
 
13
13
  1. 按[项目快速开始](../../README.md#快速开始)安装并启用插件。局域网配置可通过命令行或电脑端的 **移动访问** 面板完成。
14
- 2. 从 [GitHub Releases](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.0) 安装已签名 APK。原有 HTTPS 通道可选兼容版 `.apk`;arm64-v8a/x86_64 设备使用实验性 iroh 时选完整版 `-iroh.apk`。两包使用相同包名和正式签名,选一个原位升级即可保留配对;不同签名的 Debug 包不能直接覆盖正式版,不要为绕过签名不匹配而卸载或清除数据。
14
+ 2. 从 [GitHub Releases](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.1) 安装已签名 APK。原有 HTTPS 通道可选兼容版 `.apk`;arm64-v8a/x86_64 设备使用实验性 iroh 时选完整版 `-iroh.apk`。两包使用相同包名和正式签名,选一个原位升级即可保留配对;不同签名的 Debug 包不能直接覆盖正式版,不要为绕过签名不匹配而卸载或清除数据。
15
15
  3. 在电脑 **移动访问 → 局域网访问** 中生成密钥/配对链接,或配置远程通道并生成远程配对二维码。在 App 选择对应方式,扫码或粘贴链接。
16
16
  4. 为电脑命名。每条局域网或远程配对单独保存;手机无需安装 cpolar、Cloudflare 或 Tailscale App。
17
17
 
@@ -36,11 +36,15 @@ DeepSeek Harness 是这个社区维护的 Android WebView App 的显示名称。
36
36
 
37
37
  删除本机记录会移除保存的设备令牌和固定 CA,不等于撤销电脑授权或清除所有 WebView 短期 Cookie。需要立即终止访问时,请在电脑的 **移动访问** 中撤销设备。Android“清除应用数据”会删除全部配对和网页数据,不是普通重连的处理步骤。
38
38
 
39
+ ## 0.7.1 更新
40
+
41
+ 更新 AndroidX WebKit 至 1.18.0,构建工具更新为 AGP 9.4.1 / Gradle 9.8.2;保留 Android API 36 和既有配对格式。两种 APK 均沿用正式签名,可原位升级。插件 0.8.0 的 iPhone/iPad 网页推送属于电脑端功能,不要求为此升级 Android App。
42
+
39
43
  ## 0.7.0 更新
40
44
 
41
45
  App 0.7.0/构建 85 在完整版中增加可选的实验性 iroh 配对和原生传输,配合插件 0.7.0 及以上版本使用。兼容版不含原生库,保留现有 HTTPS 通道并继续支持 32 位设备。旧版更新入口默认下载兼容版;使用 iroh 时请主动选择完整版。两包均保留设备列表、HTTPS 配对和原有正式签名。独立实验 App 的记录不会自动迁移。
42
46
 
43
- iroh 默认关闭。电脑端安装经过校验的组件后,在 **移动访问 → 远程 → iroh(实验性)** 中生成专属配对二维码。直连和中继可用性取决于两端网络,手机浏览器不能直接使用这条传输,详见 [iroh 使用说明](../../docs/IROH.md)。
47
+ iroh 默认关闭。电脑端在 **移动访问** 的连接列表选择 **iroh(实验性)**,安装经过校验的组件并开启通道后,生成专属配对二维码。直连和中继可用性取决于两端网络,手机浏览器不能直接使用这条传输,详见 [iroh 使用说明](../../docs/IROH.md)。
44
48
 
45
49
  ## 0.6.8 更新
46
50
 
@@ -148,7 +152,7 @@ Bridge 文件/相机操作串行执行,最长等待五分钟。取消、文
148
152
 
149
153
  ## 构建
150
154
 
151
- 使用 Android Studio 或 Android SDK 36 与 JDK 17;仓库包含 Gradle 9.7.1 Wrapper。
155
+ 使用 Android Studio 或 Android SDK 36 与 JDK 17;仓库包含 Gradle 9.8.2 Wrapper。
152
156
 
153
157
  ```powershell
154
158
  Set-Location apps/mobile/android
@@ -170,11 +174,11 @@ Set-Location apps/mobile/android
170
174
 
171
175
  当前源码的 Debug 构建使用包名 `io.github.sayach.dshmobile.debug` 与 `-debug` 版本后缀,可与正式 App 并存,存储和配对彼此独立。它不会升级或迁移旧版无后缀 Debug 应用;切换包名时应保留旧应用和数据,不要为此卸载或清除。Release 构建继续使用 `io.github.sayach.dshmobile` 与既定正式签名。
172
176
 
173
- 已核验的正式版本为 [Android 0.7.0](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.0),构建 85。兼容版与完整版分别附有对应的 `.sha256` 校验文件;两个公开下载均沿用正式发布签名及包名 `io.github.sayach.dshmobile`。旧版本历史集中在 [CHANGELOG](../../CHANGELOG.md)。
177
+ 已核验的正式版本为 [Android 0.7.1](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.1),构建 86。兼容版与完整版分别附有对应的 `.sha256` 校验文件;两个公开下载均沿用正式发布签名及包名 `io.github.sayach.dshmobile`。旧版本历史集中在 [CHANGELOG](../../CHANGELOG.md)。
174
178
 
175
179
  ## 验收与已知限制
176
180
 
177
- - App 0.7.0 的两份公开 APK 均通过校验值、Manifest、正式签名、许可文本和对齐检查;完整版另检查两个 ABI 及保留的 JVM TLS/JNI 入口,兼容版确认没有原生库。此前的 arm64 开发构建有不依赖 ADB 转发的原生 iroh 网关/定制资源真机链路证据;最后补齐 JVM TLS 依赖后尚未新增真机公网中继回归,不能仅凭中继连接存在宣称数据已走中继。
181
+ - App 0.7.1 的两份公开 APK 均通过校验值、Manifest、既定正式签名、许可文本和对齐检查;完整版另检查两个 ABI 及保留的 JVM TLS/JNI 入口,兼容版确认没有原生库。本版未新增真机或公网中继回归。较早 arm64 开发构建的链路记录仍属历史证据,不能据此断言当前连接的数据已经过中继。
178
182
 
179
183
  - App 0.6.8 的公开 APK 已核验身份、校验值和既定正式签名。Debug 包同签名原位升级后可打开 DSH,并保留原设备记录;受控慢链路故障注入在配对前中断,不计为通过。
180
184
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.7.0",
3
- "versionCode": 85,
4
- "releaseTag": "android-v0.7.0"
2
+ "version": "0.7.1",
3
+ "versionCode": 86,
4
+ "releaseTag": "android-v0.7.1"
5
5
  }
Binary file
Binary file
Binary file
@@ -24,7 +24,7 @@ Configure the public HTTPS origin, DNS-01 credentials and local HTTPS port separ
24
24
 
25
25
  ## Installation, startup, and readiness
26
26
 
27
- 1. On the computer, open **Mobile Access → Remote → Self-hosted connection → Own reverse proxy** and choose **Managed Caddy**.
27
+ 1. On the computer, open **Mobile Access → Own reverse proxy** and choose **Managed Caddy**.
28
28
  2. Choose **Install managed Caddy** and confirm the download. The plugin downloads the fixed [DSH Mobile component distribution](https://github.com/saya-ch/dsh-mobile/releases/tag/caddy-component-2.11.7-tencentcloud-0.4.3-r1), checking its exact length, SHA-256, core and DNS module versions. Unavailable or invalid assets are not executed.
29
29
  3. Enter the public HTTPS origin, such as `https://dsh.example.com`, select Tencent Cloud DNSPod, supply a `SecretId` and `SecretKey` with the required zone permissions, and set the local HTTPS port, such as `8443`. Point the domain at the public ingress and, where needed, forward public TCP 443 to port 8443 on the computer.
30
30
  4. Choose **Save and start Caddy**. Installed is not ready: the panel reports readiness only after a computer-side public HTTPS probe validates the certificate and confirms that the entry belongs to the current DSH instance. That probe still does not prove phone-network reachability.
@@ -24,7 +24,7 @@ Android App / 浏览器 → 公网 HTTPS 域名(Caddy 终止 TLS)
24
24
 
25
25
  ## 安装、启动与就绪
26
26
 
27
- 1. 在电脑端打开 **移动访问 → 远程 → 自建连接 → 自有反向代理**,选择 **托管 Caddy**。
27
+ 1. 在电脑端打开 **移动访问 → 自有反向代理**,选择 **托管 Caddy**。
28
28
  2. 点击 **安装托管 Caddy** 并确认下载。插件从固定的 [DSH Mobile 组件分发](https://github.com/saya-ch/dsh-mobile/releases/tag/caddy-component-2.11.7-tencentcloud-0.4.3-r1) 下载,核对完整大小、SHA-256、Caddy 核心与 DNS 模块版本;来源不可用或校验失败时不会执行文件。
29
29
  3. 填写公网 HTTPS 地址,例如 `https://dsh.example.com`,选择腾讯云 DNSPod,填写具有所需区域权限的 `SecretId` 和 `SecretKey`,再设置本机 HTTPS 端口,例如 `8443`。让域名解析到你的公网入口,并按需将公网 TCP 443 映射至电脑的 8443。
30
30
  4. 点击 **保存并启动 Caddy**。组件已安装不等于连接就绪:只有电脑端经公网 HTTPS 验证证书,并确认入口属于当前 DSH 实例后,面板才显示已就绪。这个探测仍不能证明手机网络可达。
@@ -4,7 +4,7 @@
4
4
 
5
5
  > Already running cloudflared or a system service? Use the [own-reverse-proxy mode](SELF_HOSTED_ORIGIN.en.md#when-you-already-run-cloudflared-yourself) and route that tunnel to the protected HTTP backend shown in the panel, without another component installation or a tunnel Token stored by the plugin. The instructions below describe the separate plugin-managed option.
6
6
 
7
- The built-in cloudflared provider runs in one of two modes, chosen in the panel under **Mobile access → Remote → cloudflared → Tunnel type**:
7
+ The built-in cloudflared provider runs in one of two modes, chosen through the Quick/Named tabs under **Mobile access → cloudflared**:
8
8
 
9
9
  | | Quick tunnel (default) | Named tunnel |
10
10
  | --- | --- | --- |
@@ -43,13 +43,13 @@ These labels follow [Cloudflare's current official guide](https://developers.clo
43
43
 
44
44
  ## Fill in the DSH Mobile panel
45
45
 
46
- 1. **Mobile access → Remote → cloudflared**.
47
- 2. Set the tunnel type to **Named**.
46
+ 1. Select **cloudflared** in the **Mobile access** connection list.
47
+ 2. Select **Named**, then **Show settings** to expand the form. For an existing saved configuration, choose **Edit** first.
48
48
  3. Enter:
49
49
  - **Public hostname**: `dsh.example.com`
50
50
  - **Local forward port**: `3444`, matching the Service port from step 3
51
51
  - **Connector token**: the token you copied
52
- 4. Choose **Save and connect**.
52
+ 4. Choose **Save and connect** and read the actual connection status above. Saved settings do not establish public reachability. Switching tunnel type while connected first requires confirming disconnection.
53
53
 
54
54
  Once saved, leaving the token field blank keeps the stored token, and the field never echoes a saved token back. **Remove the saved token** also **stops the cloudflared channel** and returns the provider to a quick tunnel. Nothing reconnects on its own: switch the channel back on afterwards.
55
55
 
@@ -57,7 +57,7 @@ Once saved, leaving the token field blank keeps the stored token, and the field
57
57
 
58
58
  Moving to a fixed hostname means **an already paired phone must pair again**: the App does not send a saved device token to a changed Origin without new pairing. This client-side exact-address policy does not mean the server token automatically expires by hostname; remote channels share the device registry. Use a trusted entry you control and revoke a device on the computer if credential theft is suspected.
59
59
 
60
- 1. On the computer, open **Remote** in the panel and choose **Create remote pairing QR code**. That is what opens the pairing window, which is time-limited; while it is closed nothing can pair.
60
+ 1. On the computer, open **Mobile access → cloudflared**, wait for readiness, then choose **Generate pairing QR code**. The panel shows the expiry time; generate a new code after expiry. Closing the pairing window does not revoke existing devices.
61
61
  2. In the app, **open the Remote entry first** (the remote access setup page in the connection center), then scan the code.
62
62
 
63
63
  > **A named tunnel must be scanned from inside the Remote flow.** The app treats platform suffixes such as `.ts.net`, cpolar and `.trycloudflare.com` as remote without asking. A domain you own is recognised as a remote candidate too, but the Local network flow accepts only non-candidate addresses, so scanning there is rejected as **Invalid QR code** and looks exactly like "cannot connect". Enter the Remote flow first, then scan.
@@ -4,7 +4,7 @@
4
4
 
5
5
  > 已经自己运行 cloudflared 或使用系统服务?可改用[自备反向代理模式](SELF_HOSTED_ORIGIN.md#已经自己运行-cloudflared-时),将现有隧道指向面板展示的受保护 HTTP 后端,无需再安装组件或在插件保存隧道 Token。以下步骤介绍由插件管理 cloudflared 的另一种方式。
6
6
 
7
- 内置的 cloudflared 通道有两种模式,在面板 **移动访问 → 远程 → cloudflared → 隧道类型** 中切换:
7
+ 内置的 cloudflared 通道有两种模式,在 **移动访问 → cloudflared** 的快速/命名隧道标签中切换:
8
8
 
9
9
  | | 快速隧道(默认) | 命名隧道 |
10
10
  | --- | --- | --- |
@@ -43,13 +43,13 @@ Cloudflare 提供公网域名与 TLS,插件在本机运行 `cloudflared`,把
43
43
 
44
44
  ## 在 DSH Mobile 面板中填写
45
45
 
46
- 1. **移动访问 → 远程 → cloudflared**。
47
- 2. 隧道类型选 **命名隧道**。
46
+ 1. 在 **移动访问** 的连接列表选择 **cloudflared**。
47
+ 2. 选择 **命名隧道**,点击 **展开配置** 打开表单;已保存的配置先点击 **编辑**。
48
48
  3. 依次填写:
49
49
  - **公网域名**:`dsh.example.com`
50
50
  - **本机转发端口**:`3444`(与第 3 步的 Service 端口一致)
51
51
  - **连接器令牌**:粘贴上一步复制的令牌
52
- 4. 点击 **保存并连接**。
52
+ 4. 点击 **保存并连接**,以上方实际连接状态判断是否就绪;保存成功不等于公网已经可达。切换隧道类型时若有连接,先确认断开当前通道。
53
53
 
54
54
  已保存后令牌输入框留空表示不更改;输入框里不会再回显已保存的令牌。「移除已保存的令牌」会**同时停止 cloudflared 通道**并把配置改回快速隧道。不会自动重连,之后需要手动重新打开开关。
55
55
 
@@ -57,7 +57,7 @@ Cloudflare 提供公网域名与 TLS,插件在本机运行 `cloudflared`,把
57
57
 
58
58
  切到固定域名后,**已经配对过的手机必须重新配对**:App 不会把已存设备令牌发送给未经新配对验证的 Origin。这是客户端的精确地址发送策略,不代表服务端令牌自动按域名失效;远程通道共用设备注册表。请使用可信且由你控制的入口,怀疑凭据泄漏时在电脑端撤销设备。
59
59
 
60
- 1. 电脑面板 → **远程** → 点 **生成远程配对二维码**。这一步打开有时限的配对窗口;未打开时不能新增配对,已有配对不受影响。
60
+ 1. 电脑端在 **移动访问 → cloudflared** 中等待连接就绪,再点 **生成配对二维码**。面板显示有效截止时间;超时后须重新生成,已有设备授权不受配对窗口关闭影响。
61
61
  2. 手机 App → **先进入「远程」入口**(连接中心的远程访问设置页)→ 再扫描二维码。
62
62
 
63
63
  > **命名隧道必须在「远程」入口里扫码。** App 只把 `.ts.net`、cpolar、`.trycloudflare.com` 这类平台后缀当作「无需询问即为远程」;你自己的域名同样会被识别为可远程的候选地址,但「局域网」流程只接受非候选地址,所以在局域网界面扫码会被判为无效并提示**无效二维码 / Invalid QR code**,看起来就像「连不上」。先进入「远程」流程再扫即可。
@@ -12,7 +12,7 @@ The public entry still reaches the DSH Mobile Gateway with HTTPS, device pairing
12
12
 
13
13
  1. Check that your cpolar plan supports reserving a secondary subdomain. The [official tutorial](https://www.cpolar.com/blog/cpolar-quick-start-tutorial-raspberry-pi-series) uses Basic plans or above; your account dashboard is the authority for current eligibility, available regions and naming rules.
14
14
  2. Reserve a name under **Reserved → Reserve a secondary subdomain** in the [cpolar dashboard](https://dashboard.cpolar.com), and record the selected region.
15
- 3. Install the component and save the Authtoken under **Mobile access → Remote → cpolar** in DSH.
15
+ 3. Install the component and save the Authtoken under **Mobile access → cpolar** in DSH.
16
16
 
17
17
  The region must match the reservation. The official command example uses `cn_vip`, but do not copy it for a reservation in another region; confirm other identifiers against cpolar's current instructions. This guide does not maintain a fixed region or domain-suffix mapping.
18
18
 
@@ -52,7 +52,7 @@ If you already customize paths, listeners, TLS or other settings, **keep your cu
52
52
 
53
53
  Leading and trailing whitespace is removed. The plugin only bounds the requested name's syntax: it starts with a letter or digit, contains letters, digits, `.`, `_`, `-`, and is at most 63 characters long. This is not a DNS-label specification or a guarantee of server acceptance. The account, region and cpolar's actual rules determine whether the reservation is valid. The [official subdomain instructions](https://www.cpolar.com/docs#configure-secondary-subdomain) and tutorial describe the `-subdomain` and `-region` arguments.
54
54
 
55
- After checking that no tasks are running, quit DSH completely and reopen it, then connect under **Mobile access → Remote → cpolar**. Use the actual HTTPS address shown by the panel; do not construct a domain suffix yourself.
55
+ After checking that no tasks are running, quit DSH completely and reopen it, then connect under **Mobile access → cpolar**. Use the actual HTTPS address shown by the panel; do not construct a domain suffix yourself.
56
56
 
57
57
  ## Verification and pairing
58
58
 
@@ -12,7 +12,7 @@ cpolar 默认分配临时公网地址。账号预留子域名后,可请求使
12
12
 
13
13
  1. 确认 cpolar 套餐支持保留二级子域名。[官方教程](https://www.cpolar.com/blog/cpolar-quick-start-tutorial-raspberry-pi-series)以基础套餐及以上为例;当前资格、可用地区及命名规则以你的账号控制台为准。
14
14
  2. 在 [cpolar 控制台](https://dashboard.cpolar.com)的 **预留 → 保留二级子域名** 中保留名字,记录对应地区。
15
- 3. 在 DSH **移动访问 → 远程 → cpolar** 中安装组件并保存 Authtoken。
15
+ 3. 在 DSH **移动访问 → cpolar** 中安装组件并保存 Authtoken。
16
16
 
17
17
  地区必须与预留记录一致。官方命令示例使用 `cn_vip`,但不要将它照抄到其他地区的预留记录;其他地区的配置值请按 cpolar 当前说明确认。本指南不提供固定地区或域名后缀对照表。
18
18
 
@@ -52,7 +52,7 @@ DSH 加载器会整体替换条目的 `config`,不是逐键合并。因此不
52
52
 
53
53
  配置值会去除首尾空白。插件的名字检查仅限制请求格式:以字母或数字开头,只含字母、数字、`.`、`_`、`-`,长度不超过 63 字符;这不是 DNS 标签规范,也不代表 cpolar 服务端会接受。预留是否有效仍取决于账号、地区和 cpolar 的实际规则。[官方子域名配置说明](https://www.cpolar.com/docs#configure-secondary-subdomain)与教程说明了 `-subdomain`、`-region` 参数。
54
54
 
55
- 确认没有正在运行的任务后,完整退出并重开 DSH,再在 **移动访问 → 远程 → cpolar** 中连接。连接后使用面板实际显示的 HTTPS 地址,不要自行拼接域名后缀。
55
+ 确认没有正在运行的任务后,完整退出并重开 DSH,再在 **移动访问 → cpolar** 中连接。连接后使用面板实际显示的 HTTPS 地址,不要自行拼接域名后缀。
56
56
 
57
57
  ## 验证与配对
58
58
 
package/docs/IROH.en.md CHANGED
@@ -10,16 +10,20 @@ iroh replaces public domains and tunnel accounts with an encrypted direct connec
10
10
  - Windows x64 or Linux x64 (the only published sidecar targets).
11
11
  - Android 10+ with the full `-iroh.apk`, supporting arm64-v8a and x86_64. The ordinary `.apk` is the compatibility build without native libraries: it retains existing HTTPS connections and serves older automatic-update links, but cannot use iroh. Mobile browsers cannot connect directly through iroh either. Both APKs have the same application ID and official signer; install only one.
12
12
  - Press **Install official component** once to fetch the sidecar: the plugin downloads the pinned `dsh-mobile-iroh 0.1.0-alpha.1` release per platform (about 15 MB on Windows x64, 24 MB on Linux x64), verifies its SHA-256, and stores it in the plugin private directory. An installed companion plugin with a verified copy is reused instead, with no download. The Rust source lives with the main repository under `native/iroh-host` (shared core under `native/iroh-core`) and is built per platform in CI.
13
- - DSH Mobile stays enabled; the iroh channel is off by default and never disturbs LAN or other remote channels.
13
+ - DSH Mobile stays enabled; iroh is off by default. LAN runs independently, but only one remote provider is active: switching to iroh stops the previous remote channel.
14
14
 
15
15
  ## Usage
16
16
 
17
- 1. Under **Mobile access → Remote**, pick `iroh (Experimental)`.
18
- 2. Press **Prepare iroh → Install official component** (labelled with the download size): the first run downloads and verifies the sidecar; later runs reuse the verified copy. A hash mismatch or an unsupported platform stops here with a reason instead of a half-started channel. Merely switching the channel on never triggers a download.
17
+ 1. Under **Mobile access**, pick `iroh (Experimental)`.
18
+ 2. Press **Install official component** (labelled with the download size): the first run downloads and verifies the sidecar; later runs reuse the verified copy. A hash mismatch or an unsupported platform stops here with a reason instead of a half-started channel. Merely switching the channel on never triggers a download.
19
19
  3. Turn remote access on: the plugin resolves the endpoint identity, issues the endpoint gateway certificate (own CA, 90-day leaf, 30-day renewal headroom), starts a dedicated gateway, launches the sidecar, and waits for `ready`. `ready` means the sidecar is connected and the gateway serves its loopback socket.
20
20
  4. Press **Generate iroh pairing QR code** and scan it with the full App. The descriptor binds the sidecar's six-digit code, the Gateway's one-time token and pinned CA. Generating another descriptor replaces the previous pairing window; pairing a phone redeems the Gateway token through the normal pair endpoint over iroh.
21
21
  5. Switching off or switching providers stops the owned process and Gateway without deleting stored pairings. The independent App has a different package identity; its Keystore credentials are not automatically migrated into this App.
22
22
 
23
+ Main actions stay hidden before component installation. Afterwards, the toggle, QR generation and Copy pairing link buttons share one row. Copy stays visible but disabled without valid pairing data; copying an existing descriptor does not generate another pairing code.
24
+
25
+ To reclaim component storage, turn off iroh remote access, expand the component details and choose **Remove the downloaded iroh component**. This removes only the pinned program downloaded by this plugin, keeping identities, pairings and external companion packages. Later use can reinstall it or reuse a verified external copy. Removal while enabled is refused by the management API as well as the panel.
26
+
23
27
  ## Security boundaries
24
28
 
25
29
  - The sidecar proxies only to a `127.0.0.1`/`::1` loopback gateway with end-to-end TLS; commands and events are versioned, bounded, and rejected closed on unknown fields.
@@ -41,4 +45,21 @@ Before a component release, verify first use with the exact catalog-pinned execu
41
45
  | `iroh_start_timeout` | Inspect iroh errors in the DSH log and network connectivity, and verify that the pinned component runs; repeated pairing is not a substitute for process diagnosis |
42
46
  | Channel `ready` but no phone connects | Check that the App is the full build, the device supports a packaged ABI and the descriptor is fresh. Computer readiness does not establish phone connectivity; a relay-service connection alone does not establish that data uses the relay |
43
47
 
48
+ ## VPN, proxies and network environments
49
+
50
+ iroh prefers direct connections with relay fallback. Direct connections use QUIC/UDP; discovery and relays also need their network services to be reachable. The following environment factors **can** interfere; they are troubleshooting directions, not confirmed failure mechanisms for every VPN or network:
51
+
52
+ - **Global VPN/TUN** (system-level virtual adapters) may take over UDP and DNS so that neither direct nor relay handshakes complete. One user on Win11 + WSL2 mirrored networking over mobile data reported ~6 s to connect after disabling the global VPN, and recovery after excluding DSH Mobile from the VPN. This is community evidence, not a statement about every VPN/network combination or both ABIs.
53
+ - **DNS policy** (fake-IP, filtering): discovery depends on DNS resolution; fake-IP modes may resolve discovery domains to a proxy virtual IP.
54
+ - **Loopback entry**: the app reaches the local gateway via `*.iroh.localhost`; this loopback address should not be handed to a remote proxy/DNS.
55
+
56
+ Things to try (voluntary, subject to your own security policy):
57
+
58
+ 1. Add DSH Mobile (package `io.github.sayach.dshmobile`) to a per-app bypass/exclusion list in your VPN/proxy client; debug builds and the standalone experimental app use different package names. This is the community Android App, not the official DSH Desktop application; configure the one you actually installed.
59
+ 2. If your client supports per-domain rules, set discovery/relay domains to direct. Clash Meta example (illustrative; use your client's syntax): `DOMAIN-SUFFIX,iroh.link,DIRECT`; with fake-ip, add `+.iroh.link` to `fake-ip-filter`. Domain rules only cover client-side resolution — they **cannot** cover all direct peer IPs, relay services and loopback paths, and must not be summarized as "no UDP means it can never work".
60
+ 3. `*.iroh.localhost` is the app's local entry point and must not be sent to a remote proxy/DNS.
61
+ 4. Always-on/lockdown VPN policies may not allow per-app bypass; this plugin will not bypass system security policy, disable certificate verification, or force network switches.
62
+
63
+ A native "detect VPN and warn" is only an optional follow-up; the current version has no such feature, and whether to run a VPN remains the user's decision.
64
+
44
65
  Chinese version: [IROH.md](IROH.md).
package/docs/IROH.md CHANGED
@@ -10,16 +10,20 @@ iroh 用加密直连(打不通时走中继)代替公网域名和隧道账号
10
10
  - Windows x64 或 Linux x64(sidecar 只发布这两个目标)。
11
11
  - Android 10+,安装含 iroh 的完整版 `-iroh.apk`,支持 arm64-v8a 和 x86_64。普通 `.apk` 是无原生库的兼容版,保留原有 HTTPS 连接并供旧版自动更新使用;不支持 iroh,也不支持手机浏览器直接接入。两个 APK 使用相同包名和正式签名,只需安装一个。
12
12
  - 点 **安装官方组件** 一次下好 sidecar:插件从固定的 `dsh-mobile-iroh 0.1.0-alpha.1` 发行版按平台下载(Windows x64 约 15 MB,Linux x64 约 24 MB),校验 SHA-256 后放进插件私有目录;已装配套插件且副本校验通过时会直接复用,不再下载。Rust 源码随主仓在 `native/iroh-host`(公共 core 在 `native/iroh-core`)维护,CI 按平台构建校验。
13
- - DSH Mobile 保持启用;iroh 通道默认关闭,不影响局域网和其他远程通道。
13
+ - DSH Mobile 保持启用;iroh 默认关闭。局域网独立运行,远程通道为单选,切换到 iroh 会停止之前的远程通道。
14
14
 
15
15
  ## 使用
16
16
 
17
- 1. 在 **移动访问 → 远程** 选择 `iroh(实验性)`。
18
- 2. 点 **准备 iroh → 安装官方组件**(带下载大小):首次下载并校验 sidecar,通过才算就绪;再次点击会复用已校验的副本。哈希不对、平台不支持都会直接给出原因,不会启动半截通道。注意只是点开通道开关不会触发下载。
17
+ 1. 在 **移动访问** 选择 `iroh(实验性)`。
18
+ 2. 点 **安装官方组件**(带下载大小):首次下载并校验 sidecar,通过才算就绪;再次点击会复用已校验的副本。哈希不对、平台不支持都会直接给出原因,不会启动半截通道。注意只是点开通道开关不会触发下载。
19
19
  3. 打开远程开关:插件依次解析端点身份、签发端点专属网关证书(自有 CA,90 天叶子、30 天提前续)、起专用网关、拉起 sidecar 进程并等待 `ready`。`ready` 表示 sidecar 已连接且网关在回环监听。
20
20
  4. 点 **生成 iroh 配对二维码**,用完整版 App 扫描。描述符绑定 sidecar 的 6 位码、网关的一次性 token 和固定 CA;再次生成会替换旧配对窗口,手机通过 iroh 上的普通 pair 接口核销网关 token。
21
21
  5. 关闭或切换通道会停止自有进程与网关,不删除已保存的配对。独立实验 App 的包名不同,其 Keystore 凭据不会自动迁移到本 App。
22
22
 
23
+ 未安装组件时不显示主操作按钮;安装后,开关、生成二维码和复制配对链接三个按钮保持同排。复制按钮在没有有效配对内容时置灰,生成后才可用,重复复制不会重新生成配对码。
24
+
25
+ 需要回收组件占用空间时,先关闭 iroh 远程访问,再展开组件详情,选择 **移除已下载的 iroh 组件**。它只删除本插件下载的固定版本程序,不删除身份、配对或外部伴随包;再次使用时可重新安装或复用已校验的外部副本。运行中不允许清理,直接调用管理 API 也会被拒绝。
26
+
23
27
  ## 安全边界
24
28
 
25
29
  - sidecar 只允许代理到 `127.0.0.1`/`::1` 的回环网关,TLS 仍端到端;命令与事件都是版本化、定长、未知字段直接拒绝。
@@ -41,4 +45,21 @@ iroh 身份和证书必须存放在仅当前用户或受信系统账户可写的
41
45
  | `iroh_start_timeout` | 查看 DSH 日志中的 iroh 错误及网络连接,确认固定组件可运行;不要反复重新配对来替代进程排查 |
42
46
  | 通道 `ready` 但手机连不上 | 检查 App 是否为完整版、设备是否支持打包的 ABI,以及描述符是否有效;电脑就绪不等于手机已连通,连接中继服务也不等于数据一定经过中继 |
43
47
 
48
+ ## VPN/代理与网络环境
49
+
50
+ iroh 直连优先、中继兜底。直连使用 QUIC/UDP,发现和中继还要求相应网络服务可达。以下环境因素**可能**干扰连接,属于排查方向,并非每一种 VPN 或网络都已确认存在的故障机制:
51
+
52
+ - **全局 VPN/TUN**(系统级虚拟网卡)可能接管 UDP 与 DNS,使直连和中继握手都无法完成。有用户在 Win11 + WSL2 镜像网络、手机移动数据下反馈:关闭全局 VPN 后约 6 秒可用,把 DSH Mobile 加入 VPN 分应用排除后同样恢复。这是社区实测,不代表所有 VPN/网络组合或两种 ABI。
53
+ - **DNS 策略**(fake-IP、过滤规则):域名发现依赖 DNS 解析,fake-IP 模式可能把发现域名解析到代理虚拟 IP。
54
+ - **回环入口**:App 通过 `*.iroh.localhost` 访问本机网关,本机回环地址不应交给远程代理/DNS 处理。
55
+
56
+ 可尝试的排查(自愿进行,以本机安全策略为准):
57
+
58
+ 1. 在 VPN/代理客户端把 DSH Mobile(包名 `io.github.sayach.dshmobile`)加入分应用绕过/排除列表;调试包与独立实验 App 的包名不同。这指社区 Android App,不是官方 DSH Desktop,请按实际安装的包配置。
59
+ 2. 若客户端支持按域名规则,可将发现/中继相关域名设为直连。以 Clash Meta 为例(仅示意,语法以你的客户端为准):`DOMAIN-SUFFIX,iroh.link,DIRECT`;使用 fake-ip 时可在 `fake-ip-filter` 中加入 `+.iroh.link`。域名规则只能覆盖客户端解析相关的部分,**不能**保证覆盖全部直连 peer IP、中继服务与回环路径,也不能概括为“必须有 UDP 否则一定不可用”。
60
+ 3. `*.iroh.localhost` 是 App 的本机入口,不应送往远程代理/DNS。
61
+ 4. Always-on/锁定类 VPN 策略可能不允许分应用绕过;本插件不会绕过系统安全策略、关闭证书校验或强制切换网络。
62
+
63
+ 原生“检测 VPN 并提示”只是可选后续,当前版本不含该功能;是否开关 VPN 由用户自行决定。
64
+
44
65
  英文版见 [IROH.en.md](IROH.en.md)。
package/docs/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  | 你想做什么 | 中文 | English |
8
8
  | --- | --- | --- |
9
9
  | 使用 Android App、了解返回键、权限与升级 | [App 手册](../apps/mobile/README.zh-CN.md) | [Android app](../apps/mobile/README.md) |
10
+ | 用 iPhone/iPad 的 Safari 配对、添加到主屏幕(PWA) | [iPhone/iPad 入门](<../README.md#iphone-与-ipad主屏幕-web-apppwa>) | [iPhone/iPad quick start](<../README.en.md#iphone-and-ipad-home-screen-web-app-pwa>) |
11
+ | 在 iPhone/iPad 主屏幕 App 开启六类网页推送通知(0.8.0 起) | [网页推送](WEB_PUSH.md) | [Web Push](WEB_PUSH.en.md) |
10
12
  | 使用固定 Cloudflare 域名,而非临时随机地址 | [命名隧道](CLOUDFLARE_TUNNEL.md) | [Named tunnel](CLOUDFLARE_TUNNEL.en.md) |
11
13
  | 请求 cpolar 账号已预留的固定子域名(0.6.7 起) | [固定子域名](CPOLAR_SUBDOMAIN.md) | [Reserved subdomain](CPOLAR_SUBDOMAIN.en.md) |
12
14
  | 在已有 VPS 上新部署 FRP 与 HTTPS 入口 | [自建 FRP](SELF_HOSTED_FRP.md) | [Self-hosted FRP](SELF_HOSTED_FRP.en.md) |
@@ -18,7 +20,7 @@
18
20
  | 了解输入栏换行、附加控件与触摸目标 | [移动输入栏](MOBILE_COMPOSER_TOOLBAR.md) | [Mobile composer](MOBILE_COMPOSER_TOOLBAR.en.md) |
19
21
  | 保留电脑皮肤,排查移动页面的背景与可读性 | [皮肤与移动布局](SKIN_COMPATIBILITY.md) | [Skins and mobile layout](SKIN_COMPATIBILITY.en.md) |
20
22
 
21
- 在 Android App 中连接公开隧道或自建入口时请选择 **远程访问**。公开受信任证书也可用手机浏览器配对;既有 frps 的自签入口需要 **0.4.6 或更高版本 App** 固定远程 CA。各通道仍要求 DSH Mobile 配对,不能直接暴露普通 DSH 或私有 HTTP 后端。
23
+ 在 Android App 中连接公开隧道或自建入口时请选择 **远程访问**。公开受信任证书也可用手机浏览器配对;既有 frps 的自签入口需要 **0.4.6 或更高版本 App** 固定远程 CA。**iroh 则使用完整版 App 0.7.0 的独立配对流程**,不支持兼容版 APK 或手机浏览器。各通道仍要求 DSH Mobile 配对,不能直接暴露普通 DSH 或私有 HTTP 后端。
22
24
 
23
25
  ## 页面与网络设置 / Page and network settings
24
26
 
@@ -8,7 +8,7 @@ This guide covers the default **external proxy** mode, where you manage public H
8
8
 
9
9
  ## When to use it
10
10
 
11
- Use an existing Lucky, Nginx or Caddy HTTPS proxy rather than a tunnel. Under **Mobile Access → Remote → Self-hosted connection → Own reverse proxy**, DSH Mobile creates a separate private HTTP backend with device authentication:
11
+ Use an existing Lucky, Nginx or Caddy HTTPS proxy rather than a tunnel. Under **Mobile Access → Own reverse proxy**, DSH Mobile creates a separate private HTTP backend with device authentication:
12
12
 
13
13
  ```text
14
14
  phone → https://phone.example.com:8815 (your proxy terminates TLS)
@@ -57,7 +57,7 @@ For a separate proxy host, configure routing and a firewall rule limited to its
57
57
 
58
58
  If you already configured a named tunnel the official way (`cloudflared tunnel`, possibly as a systemd unit, a launchd job or a Windows service), **you do not need the plugin to install a second copy, and you do not need to store a tunnel Token in it**. Use the own-reverse-proxy provider above and point your existing tunnel at the plugin's HTTP backend:
59
59
 
60
- 1. In **Mobile access → Remote → Self-hosted → Own reverse proxy**, fill in: the public HTTPS address as your tunnel's public hostname (for example `https://dsh.example.com`), the private listen IP as `127.0.0.1`, an unused backend port (default `3444`), and the allowed source as `127.0.0.0/8`.
60
+ 1. In **Mobile access → Own reverse proxy**, fill in: the public HTTPS address as your tunnel's public hostname (for example `https://dsh.example.com`), the private listen IP as `127.0.0.1`, an unused backend port (default `3444`), and the allowed source as `127.0.0.0/8`.
61
61
  2. Select **Save and start backend** and copy the HTTP backend address the panel shows.
62
62
  3. For a locally managed tunnel, update the matching `ingress` entry's `service` while keeping your existing tunnel identity, credentials and other routes. This is an excerpt, not a replacement configuration:
63
63
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## 适用场景与安全边界
10
10
 
11
- 你已有 Lucky、Nginx、Caddy 等 HTTPS 反向代理,不需要 Funnel、cpolar 或 FRP 隧道。进入电脑端 **移动访问 → 远程 → 自建连接 → 自有反向代理**,让插件提供一个独立的、带设备配对认证的私有 HTTP 后端:
11
+ 你已有 Lucky、Nginx、Caddy 等 HTTPS 反向代理,不需要 Funnel、cpolar 或 FRP 隧道。进入电脑端 **移动访问 → 自有反向代理**,让插件提供一个独立的、带设备配对认证的私有 HTTP 后端:
12
12
 
13
13
  ```text
14
14
  手机 → https://phone.example.com:8815(你的代理终止 TLS)
@@ -58,7 +58,7 @@ CIDR 限制针对 TCP 连接的**直接来源地址**,不是手机 IP,也不
58
58
 
59
59
  如果你已经在用官方方式配置了命名隧道(`cloudflared tunnel`,可能是 systemd、launchd 或 Windows 服务),**不需要让插件再装一份 cloudflared,也不需要在插件里保存隧道 Token**。用上面的自备代理模式,把你已有的隧道指向插件的 HTTP 后端即可:
60
60
 
61
- 1. 在电脑端 **移动访问 → 远程 → 自建连接 → 自有反向代理** 里填写:公网 HTTPS 地址填你隧道的公开主机名(例如 `https://dsh.example.com`),私有监听 IP 填 `127.0.0.1`,后端端口填一个未占用的值(默认 `3444`),允许来源填 `127.0.0.0/8`。
61
+ 1. 在电脑端 **移动访问 → 自有反向代理** 里填写:公网 HTTPS 地址填你隧道的公开主机名(例如 `https://dsh.example.com`),私有监听 IP 填 `127.0.0.1`,后端端口填一个未占用的值(默认 `3444`),允许来源填 `127.0.0.0/8`。
62
62
  2. 点击 **保存并启动后端**,复制面板给出的 HTTP 后端地址。
63
63
  3. 本地管理的隧道只需修改对应 `ingress` 的 `service`,保留已有隧道身份、凭据及其他路由。以下仅为片段,不要覆盖整份配置:
64
64
 
@@ -111,7 +111,7 @@ location / {
111
111
 
112
112
  **“后端已监听”只证明本地 HTTP 监听成功,不代表公网 HTTPS、证书或 WebSocket 已成功。** 自备代理模式的连接诊断也只报告本地状态,不自动向公网地址探测。
113
113
 
114
- 在电脑端点击 **生成远程配对二维码**,再在 Android App **0.4.0 或更高版本**的 **远程访问** 流程扫码(不要使用 LAN 扫码流程)。使用受信任证书的自有域名也可在手机浏览器中完成配对;现有自有域名逻辑保留 HTTPS 自定义端口,无需为此功能修改 APK。
114
+ 在电脑端点击 **生成配对二维码**,再在 Android App **0.4.0 或更高版本**的 **远程访问** 流程扫码(不要使用 LAN 扫码流程)。使用受信任证书的自有域名也可在手机浏览器中完成配对;现有自有域名逻辑保留 HTTPS 自定义端口,无需为此功能修改 APK。
115
115
 
116
116
  上线前,必须用手机从目标外网实际检查:
117
117
 
@@ -84,9 +84,9 @@ Settings and defaults were checked against DSH 0.2.0-rc.2: [API gateway configur
84
84
 
85
85
  ## Optional WebSocket compression
86
86
 
87
- DSH Mobile 0.5.4 can compress selected live-connection paths. This suits limited bandwidth, metered links, or large Session data, trading extra CPU and memory use for reduced transfer volume. The path list is empty by default, retaining the existing uncompressed relay.
87
+ DSH Mobile has supported compression on selected live paths since 0.5.4; **0.8.0 enables it by default on `/api/remote.mux`**. This trades extra CPU and memory for reduced transfer volume on limited-bandwidth or metered links and large Sessions. Set `paths: []` to return to the uncompressed relay.
88
88
 
89
- Add the following property to the existing `config` of the current `mobile-access` entry. This is a **configuration fragment, not a complete profile patch**. Keep the entry's existing interface, certificate, pairing-state, and management settings; do not replace the whole `config` with the fragment.
89
+ It is enabled by default; to state or adjust it explicitly, add the following property to the existing `config` of the current `mobile-access` entry. This is a **configuration fragment, not a complete profile patch**. Keep the entry's existing interface, certificate, pairing-state, and management settings; do not replace the whole `config` with the fragment.
90
90
 
91
91
  ```yaml
92
92
  websocketCompression:
@@ -114,12 +114,16 @@ Compression is most useful when large, compressible live messages cross a bandwi
114
114
 
115
115
  With the default 1 KiB threshold and no-context-takeover negotiation, the tested 1,023-byte message stayed uncompressed. Incompressible 64 KiB data grew by 22 bytes and added about 1.1 ms to the local round trip. A local bandwidth-shaped test showed lower delivery latency for repetitive messages, but does not predict any particular cellular, relay or LAN result; sustained-load memory and physical-device behavior were not measured.
116
116
 
117
- Compare the same workload with and without compression before drawing a conclusion. HTTP history responses and `/api/remote.mux` frames are separate paths: a faster Session switch after several simultaneous upgrades cannot be attributed to WebSocket compression alone. Compression remains opt-in.
117
+ Compare the same workload with and without compression before drawing a conclusion. HTTP history responses and `/api/remote.mux` frames are separate paths: a faster Session switch after several simultaneous upgrades cannot be attributed to WebSocket compression alone. Since 0.8.0, compression defaults on for the main live path; other paths still need explicit configuration.
118
118
 
119
119
  ## Static-resource caching and repeated downloads
120
120
 
121
121
  Owned static resources (`/mobile-access/compat.js`, `mobile-layout.js` and custom CSS/JS) use quoted content ETags and `Cache-Control: private, no-cache`. A browser may store the body but must revalidate before reuse. The gateway checks device authorization and the current resource before processing conditional GET/HEAD requests: unchanged content receives `304`, while a file edit returns the new body. Actual cache reuse still depends on browser behavior.
122
122
 
123
+ Owned static assets of at least 1 KiB use gzip when the client accepts it, maintain `Vary: Accept-Encoding`, and use separate content ETags for gzip and identity representations. Smaller assets stay uncompressed. A `304` has no body and does not run compression. Savings depend on content and client cache behavior, not a guaranteed cellular speedup.
124
+
125
+ Since 0.8.0, the start document names the compatibility bundle by content hash (`/mobile-access/compat-<sha256>.js`) with `private, max-age=31536000, immutable`. A browser retaining its cache can reuse the same revision; changed bytes get a new URL, and a digest that no longer matches returns `404`. The older `/mobile-access/compat.js` retains conditional revalidation. Requests reaching the server still require authorization; a locally cached script contains no credentials and grants no API access.
126
+
123
127
  The Mobile start HTML includes a page identifier bound to the current authenticated Session and remains `no-store`; it does not reuse another page's or Session's start document. Pairing pages and API responses also remain `no-store`. When the gateway applies streaming gzip to an upstream static resource, a valid upstream ETag can remain usable as a weak validator with `Vary: Accept-Encoding`. Identity lengths and range metadata are not reused as compressed-representation metadata. Failed responses are not cached, and API requests receive no new caching policy.
124
128
 
125
129
  Every compressible text response uses that streaming pipeline: static assets, HTML documents and `/api/` JSON are gzipped when the phone advertises `Accept-Encoding: gzip`, with `Vary` maintained. Static assets under `/plugins/` and `/assets/` **keep their existing decision unchanged**, including paths the upstream already compressed. Outside static assets, `text/event-stream` stays identity so gzip blocks cannot buffer event frames, and a response that declares fewer than 1KB or no length at all stays identity because compressing it costs more than it saves or may be consumed incrementally. Range requests, already encoded responses and 304s keep their previous behavior, and a client that sends no `Accept-Encoding` receives the same bytes as before.
@@ -84,9 +84,9 @@ DSH 0.2.0-rc.2 的 API 网关默认每 **2 秒**发送一次 WebSocket Ping,
84
84
 
85
85
  ## 可选的 WebSocket 压缩
86
86
 
87
- 从 DSH Mobile 0.5.4 起,可以为指定的实时连接路径启用压缩。它适合带宽有限、计量网络或会话数据较大的场景,以额外 CPU 和内存开销换取较少的传输量;默认路径列表为空,仍使用原来的无压缩转发。
87
+ 从 DSH Mobile 0.5.4 起,可以为指定的实时连接路径启用压缩;**0.8.0 起默认压缩 `/api/remote.mux`**。它以额外 CPU 和内存开销换取较少的传输量,适合带宽有限、计量网络或会话数据较大的场景。如需回到不压缩的转发,把 `paths` 设为 `[]`。
88
88
 
89
- 在当前 `mobile-access` 条目的已有 `config` 中加入下面这段属性;这是**配置片段,不是完整 profile patch**。保留该条目已有的网卡、证书、配对状态与管理设置,不要用这个片段替换整个 `config`。
89
+ 默认已经启用;若要显式声明或调整,可在当前 `mobile-access` 条目的已有 `config` 中加入下面这段属性;这是**配置片段,不是完整 profile patch**。保留该条目已有的网卡、证书、配对状态与管理设置,不要用这个片段替换整个 `config`。
90
90
 
91
91
  ```yaml
92
92
  websocketCompression:
@@ -114,12 +114,16 @@ websocketCompression:
114
114
 
115
115
  使用默认 1 KiB 阈值并协商禁用上下文复用时,测试中的 1,023 字节消息没有压缩。不可压缩的 64 KiB 数据反而增加 22 字节,本地往返增加约 1.1 ms。局部限速测试中,重复文本的传递延迟有所下降,但不能据此预测具体蜂窝、转发或局域网体验;本轮未测量持续负载内存和真机行为。
116
116
 
117
- 请对相同工作负载分别开关压缩再比较。HTTP 历史响应与 `/api/remote.mux` 消息是不同路径,不能将同时升级多个组件后的会话切换改善全部归因于 WebSocket 压缩。该功能仍默认关闭、按需开启。
117
+ 请对相同工作负载分别开关压缩再比较。HTTP 历史响应与 `/api/remote.mux` 消息是不同路径,不能将同时升级多个组件后的会话切换改善全部归因于 WebSocket 压缩。0.8.0 起主实时路径默认开启,其他路径仍须明确配置。
118
118
 
119
119
  ## 静态资源缓存与重复下载
120
120
 
121
121
  网关自有静态资源(`/mobile-access/compat.js`、`mobile-layout.js`、自定义 CSS/JS)使用带引号的内容 ETag 和 `Cache-Control: private, no-cache`。浏览器可保存正文,但每次复用前必须重新校验;网关先检查设备授权和当前资源,再处理 GET/HEAD 条件请求,内容未变时返回 `304`,文件改动后返回新正文。实际缓存复用仍取决于浏览器行为。
122
122
 
123
+ 自有静态资源达到 1 KiB 且客户端接受 gzip 时会压缩,维护 `Vary: Accept-Encoding`,并为 gzip 和未压缩表示分别使用内容 ETag。低于阈值的资源保持未压缩。`304` 不携带正文,也不执行压缩;实际节省取决于文件内容和客户端缓存行为,不能据此保证蜂窝网络速度。
124
+
125
+ 0.8.0 起,启动文档按内容哈希引用兼容性脚本(`/mobile-access/compat-<sha256>.js`),该路径使用 `private, max-age=31536000, immutable`。浏览器保留缓存时可直接复用同版本脚本,内容变化则使用新地址;摘要与当前正文不符时返回 `404`。旧的 `/mobile-access/compat.js` 地址继续提供条件校验。服务器收到请求后仍先验证授权;本地已缓存的脚本不含凭据,也不赋予 API 访问权限。
126
+
123
127
  移动启动 HTML 包含绑定当前认证 Session 的页面标识,保持 `no-store`,不复用其他页面或 Session 的启动文档;配对页和 API 响应也保持 `no-store`。网关对上游静态资源做流式 gzip 时,可保留合法上游 ETag 的弱校验形式,并维护 `Vary: Accept-Encoding`;不会把原始内容的长度或范围元信息当作压缩内容的元信息,也不会缓存失败响应或为 API 请求新增缓存。
124
128
 
125
129
  可压缩的文本响应都走这条流式管道:静态资源、HTML 文档和 `/api/` 的 JSON 都会在手机声明 `Accept-Encoding: gzip` 时压缩,并维护 `Vary`。`/plugins/`、`/assets/` 下的静态资源**沿用原有判定,行为不变**(包括上游原本就会压缩的路径)。静态路径之外,`text/event-stream` 保持不压缩,否则逐帧事件会被 gzip 块缓冲;声明长度小于 1KB 或没有声明长度的响应也保持原样,压缩开销大于收益或可能被逐段消费。范围请求、已编码响应和 304 仍按原规则处理,不带 `Accept-Encoding` 的客户端收到的字节与以前一致。