dsh-mobile 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,9 +2,27 @@
2
2
 
3
3
  Notable changes to DSH Mobile are recorded here. GitHub Releases remain the source for downloadable packages and complete generated commit notes.
4
4
 
5
- ## Unreleased
6
-
7
- No unreleased changes.
5
+ ## 0.4.2 - 2026-09-16
6
+
7
+ - Add an Own reverse proxy provider under Remote → Self-hosted for an existing user-managed HTTPS proxy. It provides a separate authenticated private HTTP origin (default 3444), strict private bind/source-CIDR validation, custom public HTTPS ports, local configuration and safe settings-only purge.
8
+ - Distinguish backend listening from unverified public HTTPS/certificate/WebSocket reachability, with localized setup, errors and diagnostics; retain the existing LAN gateway, remote pairing and Android protocol.
9
+ - Cover the HTTPS proxy → private HTTP origin → DSH path with real loopback pairing, authenticated HTTP and WebSocket tests, including Host/Origin/forwarded-header rejection and LAN independence.
10
+ - Permanently remove revoked devices from durable storage and the desktop list, terminate their active Sessions, and compact legacy `revokedAt` rows on startup. Deleted credentials are rejected as `authentication_failed`; no revocation tombstones are retained. An offline revoked device is therefore re-paired rather than shown as revoked when it reconnects.
11
+ - Load a bundled Iterator compatibility script before DSH boot on the dedicated mobile frontend, preventing `Iterator is not defined` on WebViews without Iterator helpers. The script is served locally behind the existing gateway authentication and uses feature detection to preserve or repair native helpers without weakening CSP or dropping script nonces.
12
+ - Add a cloudflared remote provider (Windows x64) in two modes. The pinned official client is downloaded from the official release page and SHA-256 verified only after confirmation, is launched with automatic updates disabled, and does not add a system service, startup item, registry entry, or PATH entry. **Quick mode** needs no account, token, or DNS record: cloudflared allocates a temporary `*.trycloudflare.com` address, which the gateway validates before adopting it — including rejecting the reserved control-plane hosts the banner can print first, such as `api.trycloudflare.com`, which would otherwise have put Cloudflare's API in the pairing QR code. **Named mode** uses a connector token, a public hostname, and a local forward port: the hostname stays the same across restarts, the token is stored only in the DSH Mobile private directory and handed to cloudflared through `TUNNEL_TOKEN` rather than the command line, and it is never returned to any client. Because Cloudflare routes the hostname to that exact port, the port is bound as configured and a taken port is reported as a hard failure instead of silently moving; readiness comes from the connector's own `Registered tunnel connection` line, since a named tunnel prints no banner. Switching back to a quick tunnel removes the stored token.
13
+ - Let the Android app classify `*.trycloudflare.com` as a supported remote tunnel host, so a scanned cloudflared pairing link selects remote access on its own instead of being rejected while no flow has been chosen yet. A named tunnel uses a domain the operator owns, which the app cannot classify by itself, so it is accepted only while the Remote flow is active. Both require an app build that includes this change; earlier builds still pair from the Remote access flow.
14
+ - Keep the mobile gateway usable when its broadcast discovery socket cannot bind the UDP port: Windows keeps separate TCP and UDP port-exclusion tables, so the port the operating system handed the TCP listener can be refused for UDP, and another process may already hold it. Discovery now degrades on its own while mDNS, HTTP and WebSocket service continue, instead of failing the whole listener.
15
+ - Make the remote provider chooser readable now that it offers three providers: the cards list one per row instead of leaving the third orphaned in half a row, the card description — the copy that decides the choice — is no longer the smallest text in the panel, and the destructive inline actions are no longer its smallest targets.
16
+ - Fix the on-demand component download, so the cloudflared component can actually install: a pinned GitHub release URL answers with a redirect, and the downloader refused every redirect, which failed the install in under a second with `TypeError: fetch failed` and no bytes transferred. One redirect hop is now followed after validating its scheme and a release-asset host, and a transport failure is retried once, because a 55 MB transfer through a TUN proxy can reset mid-stream.
17
+ - Keep a failed provider action readable: the status line now holds its message through the repaint that follows, instead of being replaced by the server snapshot within a frame, which made a failed download look like a click that did nothing.
18
+ - Replace the panel's hardcoded colours and its smallest type with DSH tokens and one 11px floor, and reduce interactive targets to two tiers (36px inline, 44px primary). An earlier attempt at the colour work referenced four DSH aliases that do not exist, so every var() fell back to its light literal and the dark theme never adapted; the panel now uses the real border-l1/l2/l3 and state tokens, and a guard test parses the stylesheet so a stray brace can no longer silently drop declarations.
19
+ - Show a failed provider request in the user's own language. A rejection carries its error code, and the panel stringified the whole error, so a rejected tunnel hostname or a failed component download printed the raw code in every locale while the translated sentence sat unreachable; provider failures now resolve through one code-to-copy table.
20
+ - Keep broadcast discovery observable when its UDP port cannot be bound: the failure is reported through the management status and logged once at startup, instead of vanishing while the phone simply cannot find the computer. A missing bundled mobile asset is reported the same way rather than appearing only as a failed subresource.
21
+ - Never let the compatibility bundle break the mobile frontend. An index without a script tag used to fail the whole page, markup inside an HTML comment was accepted as the injection point (so the fix silently never ran), and an already-injected bundle went unrecognised when the document used single quotes.
22
+ - Reserve DSH's own WebServer port (3080) for the reverse-proxy backend and keep that port in the unprivileged range, matching the LAN gateway reservation that already refused 3443.
23
+ - Export the cloudflared tunnel configuration surface so installation tooling can pre-seed a named tunnel through the same validated path instead of writing JSON by hand.
24
+ - Document that a named tunnel must be scanned from inside the app's Remote flow, and that device credentials are bound to their exact origin, so moving a remote channel to a fixed hostname requires pairing the phone again.
25
+ - Reorganize the shipped guides into a bilingual index and remove their broken relative links.
8
26
 
9
27
  ## 0.4.1 - 2026-09-15
10
28
 
@@ -27,7 +45,7 @@ No unreleased changes.
27
45
  - Use a bounded session-free native probe for list reachability checks, with a renewal fallback for older plugins so status refreshes cannot evict an active DSH session.
28
46
  - Preserve a local row after computer-side revocation, stop automatic retries for revoked credentials, and expose a short-lived undo action for local deletion without restoring a computer-side authorization.
29
47
  - Follow the DSH conversation's actual nested scroll container when showing or hiding the Android toolbar, keeping task-notification settings reachable on current DSH Web layouts.
30
- - Let cpolar choose its default route instead of forcing `cn`; explicit region settings remain available for advanced deployments.
48
+ - Let cpolar choose its default route instead of forcing `cn`: the plugin no longer passes `-region`, so cpolar selects its own tunnel server. No profile or environment setting exposes an explicit region.
31
49
  - Add Android task-completion and pending-input reminders through authenticated Host events and the exact-origin native bridge; notification permission is enabled explicitly from the foreground app menu, lock-screen text stays generic, and each completed turn keeps a separate reminder (thanks @qzyqmzn for PR #75).
32
50
  - Show and re-copy remote pairing links without invalidating the QR code's active one-time pairing window (thanks @qzyqmzn for PR #75).
33
51
  - Detect plugin-market installations that have not completed LAN setup, prevent the loopback-only `127.0.0.1` fallback from being presented as phone access, and provide a localized in-panel network picker that creates private TLS material and LAN-only Windows firewall rules after explicit confirmation. The configured gateway starts after one DSH restart (thanks @cangming99 for #72).
package/README.en.md CHANGED
@@ -30,17 +30,17 @@
30
30
 
31
31
  > DSH Mobile is a DeepSeek Harness community plugin; the native app supports Android only.
32
32
  >
33
- > **0.4.1 update**: adapts to DeepSeek Harness 0.1.6-alpha.1, fixes extension action requests and LAN route inspection, hardens admin CSRF checks, HTTP iframe warnings, and the frontend compatibility gate, and adds a complete English self-hosted FRP guide. [Details](CHANGELOG.md).
33
+ > **0.4.2 update**: adds an own HTTPS reverse proxy and the cloudflared remote channel (quick and named tunnels) with on-demand component installation, plus one-command frps + Caddy deployment. The provider chooser and panel copy were re-measured at its real 380 px width, and the on-demand download no longer dies on a redirect or hides its failure message. [Details](CHANGELOG.md).
34
34
  >
35
- > **Upgrade reminder**: the 0.4.1 plugin continues to work with the 0.4.0 Android app and existing devices do not need re-pairing. Install the 0.4.1 app as well if you want this Android build. [Compatibility notes](#compatibility).
35
+ > **Upgrade reminder**: the 0.4.2 plugin continues to work with the 0.4.0 Android app and existing devices do not need re-pairing; a named tunnel must be scanned from inside the app's Remote flow. Install the 0.4.2 app as well if you want this Android build. [Compatibility notes](#compatibility).
36
36
 
37
37
  <p align="center">
38
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.1/dsh-mobile-android-v0.4.1.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
39
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.1/dsh-mobile-android-v0.4.1.apk"><strong>Download Android app 0.4.1</strong></a><br>
40
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.1">Release notes and checksums</a></sub>
38
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.2/dsh-mobile-android-v0.4.2.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
39
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.2/dsh-mobile-android-v0.4.2.apk"><strong>Download Android app 0.4.2</strong></a><br>
40
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.2">Release notes and checksums</a></sub>
41
41
  </p>
42
42
 
43
- DSH Mobile is a DeepSeek Harness plugin that lets a mobile browser or the Android app connect over a protected LAN or an optional Tailscale Funnel, cpolar, or self-hosted FRP remote path. Local and remote access keep the same sessions, Workspaces, messages, and tools while using separate switches and paired-device stores without modifying DeepSeek Harness source.
43
+ DSH Mobile is a DeepSeek Harness plugin that lets a mobile browser or the Android app connect over a protected LAN or an optional Tailscale Funnel, cpolar, cloudflared, self-hosted FRP, or own reverse-proxy remote path. Local and remote access keep the same sessions, Workspaces, messages, and tools while using separate switches and paired-device stores without modifying DeepSeek Harness source.
44
44
 
45
45
  Mobile access runs on its own HTTPS origin with pinned certificates; only paired devices pass validation.
46
46
 
@@ -120,9 +120,9 @@ Browser pairing and reauthentication pages use the browser's `Accept-Language` t
120
120
 
121
121
  ### Remote access
122
122
 
123
- Use this after the phone leaves the computer's network. Remote access is disabled by default, and the phone needs no separate Tailscale, cpolar, or FRP app.
123
+ Use this after the phone leaves the computer's network. Remote access is disabled by default, and the phone needs no separate Tailscale, cpolar, cloudflared, or FRP app.
124
124
 
125
- Remote providers may impose bandwidth and connection limits: the [cpolar Free plan](https://svip.cpolar.com/pricing) currently lists 1 Mbps, while [Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel#requirements-and-limitations) has non-configurable bandwidth limits. DSH Mobile reduces transfer and waiting with 10-message pages, load-on-scroll history, gzip, and a persistent WebSocket, but it cannot raise provider quotas.
125
+ Remote providers may impose bandwidth and connection limits: the [cpolar Free plan](https://svip.cpolar.com/pricing) currently lists 1 Mbps, while [Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel#requirements-and-limitations) has non-configurable bandwidth limits, and a cloudflared quick tunnel is a free Cloudflare address with randomized hostnames and rate limiting. DSH Mobile reduces transfer and waiting with 10-message pages, load-on-scroll history, gzip, and a persistent WebSocket, but it cannot raise provider quotas.
126
126
 
127
127
  <p align="center">
128
128
  <img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/screenshots/remote-access-en.png" width="82%" alt="DSH Mobile remote access and provider selection">
@@ -132,17 +132,21 @@ Remote providers may impose bandwidth and connection limits: the [cpolar Free pl
132
132
  - **Tailscale Funnel**: select **Enable remote access**, complete the one-time Tailscale sign-in on the official page, follow the panel prompt to allow Funnel, then return to DSH and wait until the connection is ready.
133
133
  - **cpolar**: select **Install official component**, sign in to the cpolar dashboard and obtain an Authtoken, paste it, then select **Save and connect**. The component is downloaded into the plugin's private directory only after confirmation; free temporary addresses may change after DSH or cpolar restarts.
134
134
  - **Self-hosted FRP (advanced)**: expand **Self-hosted connection**, enter the VPS, frps port, shared token, and public HTTPS origin. The origin may use your domain or the VPS public IPv4 address (for example, `https://203.0.113.10` — substitute your own real address; documentation ranges are rejected). Apply the restricted template manually or enter an SSH user, port, and local private-key path for automatic deployment. Automatic deployment supports Ubuntu/Debian with systemd, uses OpenSSH keys or an agent, refuses password auth, and does not overwrite Caddy configuration it does not manage; both deployment and server cleanup display the SSH host keys for verification against the VPS console before continuing. IPv4 mode obtains a roughly six-day Let's Encrypt IP certificate and installs daily automatic renewal. Install the official `frpc` on demand and verify the path afterward. A reviewable uninstall script or one-click server cleanup removes only DSH Mobile-owned services and configs. This requires Android app 0.3.3 or later. See the [English self-hosted FRP guide](docs/SELF_HOSTED_FRP.en.md) for the complete procedure.
135
- 2. When the panel reports that remote access is ready, select **Create remote pairing QR code**.
135
+ - **Own reverse proxy**: open **Self-hosted connection → Own reverse proxy**, enter the public HTTPS origin (custom ports supported), private listen IPv4, separate HTTP backend port (default 3444), and allowed proxy source CIDRs, then select **Save and start backend**. This uses your existing Lucky/Nginx/Caddy without a tunnel component and requires Android app 0.4.0 or later. See the [own reverse proxy guide](docs/SELF_HOSTED_ORIGIN.en.md).
136
+ - **cloudflared**: select **Install official component**. After you confirm, the plugin downloads a pinned build from the official release page into its private directory and requests a temporary public address (a quick tunnel) with **no sign-up or sign-in**. Choose this when you would rather not create an account. The quick-tunnel hostname changes on every reconnect, and Cloudflare positions quick tunnels for testing: they are rate-limited and carry no uptime guarantee, so do not rely on one for production access that must stay reachable. With a Cloudflare account and domain, switch **Tunnel type** to **Named**, then supply a connector token, a public hostname and a local forward port to get an address that survives restarts. The token is stored only in the private directory and reaches cloudflared through the environment rather than the command line; see [Cloudflare named tunnel](docs/CLOUDFLARE_TUNNEL.en.md).
137
+ 2. When the panel reports that remote access is ready, select **Create remote pairing QR code**. Own reverse proxy reports only **Backend listening**: public HTTPS, its certificate and WebSocket still require verification from your phone.
136
138
  3. In the Android app, open **Remote access** and scan the QR code to create its separate pairing.
137
139
  4. The app saves the current address and device credential for automatic reconnection. If a free cpolar address changes, scan the computer's current remote QR code to verify the connection again; clearing app data is unnecessary. A stored device token is sent only to its exact saved Origin, never to a new QR-code domain.
138
140
 
139
141
  > **Remote notifications**: browser `Notification` permission is granted per Origin, and a web-page system toast appears only on the device running that page. Android task reminders are a separate 0.4.0 feature: enable them from the app's foreground menu, and keep the WebView page alive; they are not a general background push service. For reliable background delivery, use a server-side webhook or bot channel you have configured.
140
142
 
141
- Tailscale Funnel has broad reach but may be unreliable from mainland China. Its runtime ties the public listener to the parent process and a bounded control channel; parent exit, channel closure, or an explicit stop ends the current generation and cleans up its resources. cpolar is better suited to mainland networks, while self-hosted FRP fits users who already have a VPS and want to avoid public-provider bandwidth quotas. An unregistered domain on a mainland-China VPS may be intercepted by the cloud provider; public IPv4 mode avoids that dependency. The plugin validates pinned on-demand components, stores their configuration and programs entirely under `$DSH_HOME/mobile-access/`, and can remove them completely from the panel.
143
+ Tailscale Funnel has broad reach but may be unreliable from mainland China. Its runtime ties the public listener to the parent process and a bounded control channel; parent exit, channel closure, or an explicit stop ends the current generation and cleans up its resources. cpolar is better suited to mainland networks, while self-hosted FRP fits users who already have a VPS and want to avoid public-provider bandwidth quotas. cloudflared runs in two modes: a quick tunnel needs no account or sign-in, but its hostname is random, changes on every reconnect, and is positioned by Cloudflare for testing with no uptime guarantee, so it suits temporary or verification use rather than a permanent channel; a named tunnel uses a Cloudflare account token and keeps one fixed public hostname across restarts. An unregistered domain on a mainland-China VPS may be intercepted by the cloud provider; public IPv4 mode avoids that dependency. The plugin validates pinned on-demand components, stores their configuration and programs entirely under `$DSH_HOME/mobile-access/`, and can remove them completely from the panel.
142
144
 
143
145
  Self-hosted FRP generates only one HTTP vhost to the DSH loopback gateway. It exposes no arbitrary FRP configuration, TCP/UDP proxy, or FRP plugin. The VPS plaintext vhost must bind to `127.0.0.1`, with Caddy providing public HTTPS; the plugin rejects a publicly reachable plaintext port and reports readiness only after public discovery identifies the current computer.
144
146
 
145
- The public remote origin still requires DSH device pairing. The bundled Funnel and managed cpolar components currently support Windows x64; on-demand FRP 0.70.1 supports Windows, Linux, and macOS on x64 and arm64.
147
+ The own-proxy HTTP backend must remain on a trusted private network: **never port-forward it publicly or bypass it by proxying to DSH or the existing LAN 3443 gateway**. CIDRs match the proxy's direct TCP peer, not forwarded headers. Preserve the external Host (including port), Origin, cookies and WebSocket. Clearing proxy settings keeps paired remote devices.
148
+
149
+ The public remote origin still requires DSH device pairing. The bundled Funnel and the managed cpolar and cloudflared components currently support Windows x64; on-demand FRP 0.70.1 supports Windows, Linux, and macOS on x64 and arm64.
146
150
 
147
151
  ## Extend and customize
148
152
 
@@ -179,14 +183,16 @@ The examples above, applied:
179
183
 
180
184
  ### Device management
181
185
 
182
- The Android app keeps LAN, cpolar, Tailscale Funnel, and self-hosted FRP pairings in one **Paired computers** list. The first upgrade migrates the legacy LAN and remote credentials without requiring another pairing; when an address changes, the app merges the row by the DSH installation's stable `instanceId` and keeps its custom name. Device tokens and LAN CAs remain encrypted by Android Keystore and never appear in the list or QR code.
186
+ The Android app keeps LAN, cpolar, cloudflared, Tailscale Funnel, and self-hosted FRP pairings in one **Paired computers** list. The first upgrade migrates the legacy LAN and remote credentials without requiring another pairing; when an address changes, the app merges the row by the DSH installation's stable `instanceId` and keeps its custom name. Device tokens and LAN CAs remain encrypted by Android Keystore and never appear in the list or QR code.
183
187
 
184
188
  Each row shows its custom name, transport, Origin, live reachability, and last connection time. A green dot means **Reachable**; a gray dot means **Checking**, **Temporarily unreachable**, **Pairing expired**, or **Removed on computer**. The check validates the DSH Gateway over HTTPS instead of using ICMP, so a temporary network outage is not mistaken for computer-side revocation.
185
189
 
186
190
  - **Startup behavior → Open DSH directly** (default): one device connects directly; with multiple devices, the app tries the last-used device first, then the still-valid device with the most recent connection. A bounded connection budget returns to the list instead of spinning forever.
187
191
  - **Startup behavior → Show device list**: choose a computer on every launch, which is useful when switching between several machines. The option is in the list's top-right settings button and is saved immediately.
188
192
  - Tap a row to connect. The overflow button and long press open the same action sheet for rename, check now, pair again, or delete the local record. Deletion has a second confirmation and a short undo window; undo restores only the local row and never restores a computer-side revocation.
189
- - Open DSH **Settings → General** in the WebView and select **Switch computer** to return to the paired-device list; this action appears only in the Android app. If the computer revokes a device, the app keeps its row as **Removed on computer**, stops automatic reconnection, and offers **Pair again** or **Delete device**.
193
+ - Open DSH **Settings → General** in the WebView and select **Switch computer** to return to the paired-device list; this action appears only in the Android app. When an online device receives the computer's revocation notification, the app keeps its row as **Removed on computer**, stops automatic reconnection, and offers **Pair again** or **Delete device**.
194
+
195
+ Revoking a device permanently deletes its durable record and token digest instead of retaining a `revokedAt` tombstone. Startup also removes legacy revoked rows. A deleted token receives `401 authentication_failed`, just like an unknown token. Existing apps checking a device that was revoked while offline may therefore show **Pairing expired** and require pairing again; online Sessions still receive the revocation notification and disconnect immediately.
190
196
 
191
197
  <table>
192
198
  <tr>
@@ -236,6 +242,8 @@ Proxied pages allow HTTP frames for compatibility with some community plugins; t
236
242
 
237
243
  The Android app is a thin Kotlin WebView shell and contains no frontend copy; mobile browsers load the same page. For compatibility diagnosis, append `?frontend=stock` to the browser URL to temporarily use the previous desktop-page adaptation.
238
244
 
245
+ The dedicated mobile page synchronously loads the authenticated, same-origin `/mobile-access/compat.js` before the first DSH boot script. This bundled core-js compatibility layer supplies `Iterator` / Iterator helpers to WebViews that lack them, preventing the startup error `Iterator is not defined`. It uses feature detection to preserve or repair native helpers, needs no CDN, and does not weaken CSP. It does not change the Android APK, desktop page, or `?frontend=stock` page. This is not a promise to support every old engine: the frontend still targets ES2022. Update Android System WebView / Chrome first if other compatibility errors remain.
246
+
239
247
  > **Community client (unofficial)**: [WeChat Mini-Program client](https://github.com/StrawberryAO/dsh-mobile-minapp)
240
248
  > A native WeChat Mini-Program that reuses the Mobile Access pairing and Remote stream protocol (requires dsh-mobile ≥ 0.3.8).
241
249
  > Because WeChat release builds enforce a domain allow-list (ICP-registered HTTPS origins only), it currently works via WeChat DevTools / real-device debugging; see its README.
@@ -258,6 +266,7 @@ Three layers: the Host face for discovery, pairing, HTTPS, loopback proxying, an
258
266
  - Use the LAN listener only on a trusted home, office, or hotspot network; do not add your own port forwarding.
259
267
  - A remote origin is publicly reachable, but unpaired requests cannot enter DSH; turn the remote switch off when it is not needed.
260
268
  - cpolar downloads a pinned official build only after confirmation and verifies its size and SHA-256. It installs no system service, PATH entry, or startup task, and plugin cleanup removes its managed files.
269
+ - cloudflared likewise downloads a pinned build from the official GitHub Release only after confirmation and verifies the exact size and SHA-256, and it launches the client with automatic updates disabled so the running binary is always the verified one. It needs no account, token, or DNS record, stores no credential, and cleanup deletes every file it manages.
261
270
  - Self-hosted FRP downloads pinned official `frpc` only after confirmation and verifies the origin, exact size, SHA-256, archive paths, and executable version. The shared token never appears in status, diagnostics, or logs. Copying the server template places it on the system clipboard, so clear the clipboard after use; local cleanup removes only plugin-managed files, while the VPS is cleaned separately with the uninstall script or one-click server cleanup. Automatic deployment and server cleanup both display the SSH host keys, which must be verified against the VPS console before continuing.
262
271
  - A paired device is a fully trusted DeepSeek Harness operator and can run tools on the computer; revoke lost devices from the computer.
263
272
  - The LAN gateway listens only while Mobile Access is enabled; with it off, DSH keeps running normally on the computer.
@@ -270,6 +279,7 @@ The table below lists, for each plugin version, the DeepSeek Harness version it
270
279
 
271
280
  | DSH Mobile plugin | Verified DeepSeek Harness version |
272
281
  | --- | --- |
282
+ | `0.4.2` | `0.1.6-alpha.1` (local source and renderer-v2 contract check) |
273
283
  | `0.4.1` | `0.1.6-alpha.1` (local source and renderer-v2 contract check) |
274
284
  | `0.3.15`, `0.3.16`, `0.4.0` | `0.1.5-rc.2` (contract check); `0.1.5-rc.1` (@idoall LAN verification) |
275
285
  | `0.3.14` | `0.1.3-alpha.2` |
@@ -303,4 +313,4 @@ npm ci
303
313
  npm run verify
304
314
  ```
305
315
 
306
- See the [Android guide](apps/mobile/README.md). Licensed under [Apache-2.0](LICENSE).
316
+ See the [Android guide](https://github.com/saya-ch/dsh-mobile/blob/main/apps/mobile/README.md). Licensed under [Apache-2.0](LICENSE).
package/README.md CHANGED
@@ -30,17 +30,17 @@
30
30
 
31
31
  > DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
32
32
  >
33
- > **0.4.1 更新**:适配 DeepSeek Harness 0.1.6-alpha.1,修复扩展动作请求和局域网路由检测,强化管理请求 CSRF、HTTP iframe 警告与前端兼容性检查,并补齐中英文 FRP 文档。[详细记录](CHANGELOG.md)。
33
+ > **0.4.2 更新**:新增自有 HTTPS 反向代理与 cloudflared 远程通道(快速隧道 + 命名隧道),cloudflared 组件改为按需安装;新增一键部署 frps + Caddy。远程提供方选择器与面板文字在真实 380px 宽度下重新校对,并修好了组件下载被重定向卡死、失败提示被刷新覆盖等问题。[详细记录](CHANGELOG.md)。
34
34
  >
35
- > **升级提醒**:0.4.1 插件可继续使用 0.4.0 Android App;已有设备无需重新配对。若要使用本次 Android 构建,请同时安装 0.4.1 App。[兼容说明](#兼容性)。
35
+ > **升级提醒**:0.4.2 插件可继续使用 0.4.0 Android App,已有设备无需重新配对;命名隧道要求 App 侧处于「远程」流程内扫码。若要使用本次 Android 构建,请同时安装 0.4.2 App。[兼容说明](#兼容性)。
36
36
 
37
37
  <p align="center">
38
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.1/dsh-mobile-android-v0.4.1.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
39
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.1/dsh-mobile-android-v0.4.1.apk"><strong>下载 Android App 0.4.1</strong></a><br>
40
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.1">版本说明与校验文件</a></sub>
38
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.2/dsh-mobile-android-v0.4.2.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
39
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.4.2/dsh-mobile-android-v0.4.2.apk"><strong>下载 Android App 0.4.2</strong></a><br>
40
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.4.2">版本说明与校验文件</a></sub>
41
41
  </p>
42
42
 
43
- DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、自建 FRP 远程通道连接电脑,继续使用同一份会话、工作区、消息和工具。局域网与远程访问分别启停、分别管理设备,且都不修改 DeepSeek Harness 源码。
43
+ DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、cloudflared、自建 FRP 或自有反向代理远程通道连接电脑,继续使用同一份会话、工作区、消息和工具。局域网与远程访问分别启停、分别管理设备,且都不修改 DeepSeek Harness 源码。
44
44
 
45
45
  移动访问使用独立的 HTTPS 与证书固定,只有配对过的设备能通过校验接入。
46
46
 
@@ -120,9 +120,9 @@ dsh plugin --profile web add dshmarket
120
120
 
121
121
  ### 远程访问
122
122
 
123
- 适合手机离开电脑所在网络后使用。远程访问默认关闭,手机不需要另外安装 Tailscale、cpolar 或 FRP。
123
+ 适合手机离开电脑所在网络后使用。远程访问默认关闭,手机不需要另外安装 Tailscale、cpolar、cloudflared 或 FRP。
124
124
 
125
- 远程服务可能受带宽和连接限额影响:[cpolar 免费方案](https://svip.cpolar.com/pricing) 当前为 1 Mbps,[Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel#requirements-and-limitations) 也存在不可配置的带宽限制。DSH Mobile 通过 10 条分页、顶部按需加载、gzip 和 WebSocket 长连接减少流量与等待,但无法突破服务商限额。
125
+ 远程服务可能受带宽和连接限额影响:[cpolar 免费方案](https://svip.cpolar.com/pricing) 当前为 1 Mbps,[Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel#requirements-and-limitations) 也存在不可配置的带宽限制,cloudflared 的 quick tunnel 由 Cloudflare 免费提供、地址随机且有限流。DSH Mobile 通过 10 条分页、顶部按需加载、gzip 和 WebSocket 长连接减少流量与等待,但无法突破服务商限额。
126
126
 
127
127
  <p align="center">
128
128
  <img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/screenshots/remote-access.png" width="82%" alt="DSH Mobile 远程访问与通道选择">
@@ -132,17 +132,21 @@ dsh plugin --profile web add dshmarket
132
132
  - **Tailscale Funnel**:点击 **启用远程访问**,在打开的官方页面完成一次 Tailscale 登录;按面板提示继续允许 Funnel,然后返回 DSH 等待连接就绪。
133
133
  - **cpolar**:点击 **安装官方组件**,登录 cpolar 控制台取得 Authtoken,粘贴后点击 **保存并连接**。组件只会在确认后下载到插件私有目录;免费临时地址可能在 DSH 或 cpolar 重启后变化。
134
134
  - **自建 FRP(高级)**:展开 **自建连接**,填写 VPS、frps 端口、共享 Token 和公开 HTTPS 地址;公开地址可以是自己的域名,也可以直接是 VPS 公网 IPv4(例如 `https://203.0.113.10`,请换成你自己的真实地址,文档示例网段会被拒绝)。可以复制受限模板手动部署,也可以填写 SSH 用户、SSH 端口和本机私钥路径,点击 **部署 frps + Caddy** 自动部署。自动部署支持 Ubuntu/Debian + systemd,使用 OpenSSH 密钥或 ssh-agent,不接受密码,也不会覆盖非 DSH Mobile 管理的 Caddyfile;部署与清理前都会展示服务器主机指纹,需到 VPS 控制台核对后才能继续。公网 IP 模式会申请约 6 天有效的 Let’s Encrypt IP 证书并配置每日自动续期。部署完成后再安装官方 `frpc` 并验证连接。不再需要服务器时可用“复制 VPS 卸载脚本”或一键清理,只删除 DSH Mobile 自己的服务与配置。需要 Android App 0.3.3 或更高版本。详见 [自建 FRP 使用指南](docs/SELF_HOSTED_FRP.md)。
135
- 2. 状态变为“远程访问已就绪”后,点击 **生成远程配对二维码**。
135
+ - **自有反向代理**:展开 **自建连接 → 自有反向代理**,填写公网 HTTPS 地址(支持自定义端口)、私有监听 IPv4、独立 HTTP 后端端口(默认 3444)和代理来源 CIDR,再点击 **保存并启动后端**。适合已有 Lucky/Nginx/Caddy 的用户,无需隧道组件;需要 Android App 0.4.0 或更高版本。详见 [自有反向代理指南](docs/SELF_HOSTED_ORIGIN.md)。
136
+ - **cloudflared**:点击 **安装官方组件**,插件在确认后从官方发布页下载固定版本到插件私有目录,随后自动申请一个临时公网地址(quick tunnel),**无需注册或登录**。适合不想注册账号的用户;quick tunnel 地址每次重连都会变化,官方定位为测试用途、有限流且无可用性保证,请勿用于必须长期可达的生产访问。已有 Cloudflare 账号和域名时,可把隧道类型切到 **命名隧道**,填入连接器令牌、公网域名与本机转发端口,即可获得重启后不变的固定地址(令牌只存私有目录、只经环境变量传给 cloudflared)。步骤见 [Cloudflare 命名隧道](docs/CLOUDFLARE_TUNNEL.md)。
137
+ 2. 状态变为“远程访问已就绪”后,点击 **生成远程配对二维码**。自有反向代理仅显示“后端已监听”:它不验证公网连通性,仍需检查代理 HTTPS、证书与 WebSocket 并用手机验收。
136
138
  3. 在 Android App 中进入 **远程访问**,扫描二维码完成独立配对。
137
139
  4. 此后 App 会保存当前地址和设备凭据并自动重连。若 cpolar 免费临时地址发生变化,请扫描电脑端当前远程二维码重新验证连接;无需清除 App 数据。旧设备 token 只会发送到原先保存的精确 Origin,不会发送给二维码中的新域名。
138
140
 
139
141
  > **远程通知说明**:浏览器的 `Notification` 权限按 Origin 分别授权,网页系统通知只显示在运行该网页的设备上。Android App 的任务提醒是独立的 0.4.0 功能,需要在 App 前台菜单中主动开启,且依赖 WebView 页面仍存活;它不是通用的后台推送。需要可靠的后台推送时,请使用你已配置的服务端 webhook 或机器人通道。
140
142
 
141
- Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。其运行组件把公开监听生命周期绑定到父进程和受限控制通道;父进程退出、控制通道关闭或显式停止时会结束当前代次并清理资源。cpolar 更适合国内网络;自建 FRP 适合已有 VPS、希望避开公共服务带宽限制的用户。中国大陆 VPS 上的未备案域名可能被云厂商拦截,此时可使用公网 IPv4 模式。插件会校验按需下载的固定版本组件,配置与程序均保存在 `$DSH_HOME/mobile-access/`,可随时在面板中彻底清除。
143
+ Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。其运行组件把公开监听生命周期绑定到父进程和受限控制通道;父进程退出、控制通道关闭或显式停止时会结束当前代次并清理资源。cpolar 更适合国内网络;自建 FRP 适合已有 VPS、希望避开公共服务带宽限制的用户。cloudflared 有两种模式:快速隧道不需要账号或登录,但地址随机、每次重连都会变化,官方定位为测试用途且无可用性保证,因此只适合临时或验证场景;命名隧道使用 Cloudflare 账号令牌,能保留重启后不变的固定公网域名。中国大陆 VPS 上的未备案域名可能被云厂商拦截,此时可使用公网 IPv4 模式。插件会校验按需下载的固定版本组件,配置与程序均保存在 `$DSH_HOME/mobile-access/`,可随时在面板中彻底清除。
142
144
 
143
145
  自建 FRP 只生成一个指向 DSH 回环网关的 HTTP vhost,不提供任意 FRP 配置、TCP/UDP 代理或 FRP 插件。VPS 的明文 vhost 必须只监听 `127.0.0.1`,由 Caddy 提供公网 HTTPS;插件会拒绝可从公网访问的明文端口,并在公开发现接口确认连接到当前电脑后才显示“已就绪”。
144
146
 
145
- 远程公开地址仍受 DSH 设备配对保护。内置 Funnel 与托管 cpolar 当前支持 Windows x64;按需安装的 FRP 0.70.1 支持 Windows、Linux、macOS 的 x64 与 arm64。
147
+ 自有反向代理的 HTTP 后端只允许留在可信私网;**不要把它映射到公网,也不要绕过它直连 DSH 或现有 LAN 3443**。来源 CIDR 匹配代理的直接 TCP 来源,不信任转发头;反代须保留外部 Host(含端口)、Origin、Cookie 和 WebSocket。清除代理配置不会删除已配对远程设备。
148
+
149
+ 远程公开地址仍受 DSH 设备配对保护。内置 Funnel 与托管 cpolar、cloudflared 当前支持 Windows x64;按需安装的 FRP 0.70.1 支持 Windows、Linux、macOS 的 x64 与 arm64。
146
150
 
147
151
  ## 扩展与自定义
148
152
 
@@ -179,14 +183,16 @@ Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。
179
183
 
180
184
  ### 设备管理
181
185
 
182
- Android App 将局域网、cpolar、Tailscale Funnel 和自建 FRP 统一整理到“已配对设备”列表。首次升级会自动迁移旧版局域网与远程凭据,不要求重新配对;地址变化时按 DSH 安装的稳定 `instanceId` 合并原记录,保留自定义名称。设备 Token 和局域网 CA 继续由 Android Keystore 加密保存,不会显示在列表或二维码中。
186
+ Android App 将局域网、cpolar、cloudflared、Tailscale Funnel 和自建 FRP 统一整理到“已配对设备”列表。首次升级会自动迁移旧版局域网与远程凭据,不要求重新配对;地址变化时按 DSH 安装的稳定 `instanceId` 合并原记录,保留自定义名称。设备 Token 和局域网 CA 继续由 Android Keystore 加密保存,不会显示在列表或二维码中。
183
187
 
184
188
  每条记录显示自定义名称、连接方式、Origin、实时可达状态和最近连接时间。绿色状态点表示“可达”,灰色状态点表示“检测中”“暂不可达”“配对已过期”或“电脑端已移除”;可达性检查直接验证 DSH Gateway,不依赖 ICMP,也不会把暂时断网误判成电脑端撤销。
185
189
 
186
190
  - **启动时打开 → 直接进入 DSH**(默认):单设备直接连接;多设备优先连接上次使用的设备,其次按最近连接时间选择仍有效的设备。连接超过有限重试预算后自动回到列表,不会无限转圈。
187
191
  - **启动时打开 → 显示设备列表**:每次启动先选择电脑,适合经常在多台设备之间切换。该选项位于列表右上角的设置按钮中,修改后立即保存。
188
192
  - 点按设备行可连接;右侧“…”和长按提供相同的操作面板,可编辑名称、立即检测、重新配对或删除本地记录。删除前会二次确认,并提供短暂撤销;撤销只恢复本机记录,不恢复电脑端已经撤销的授权。
189
- - 在 WebView 页面打开 DSH **设置 → 通用**,选择 **切换电脑** 可回到已配对设备列表;该动作仅在 Android App 中显示。电脑端撤销设备后,App 保留该条目并显示“电脑端已移除”,停止自动重连,同时提供 **重新配对** 和 **删除本地记录**。
193
+ - 在 WebView 页面打开 DSH **设置 → 通用**,选择 **切换电脑** 可回到已配对设备列表;该动作仅在 Android App 中显示。在线收到电脑端撤销通知后,App 保留该条目并显示“电脑端已移除”,停止自动重连,同时提供 **重新配对** 和 **删除本地记录**。
194
+
195
+ 电脑端撤销会永久删除持久化存储中的设备记录及令牌摘要,而不是保留 `revokedAt` 标记;启动时也会清理旧版留下的已撤销记录。删除后,旧令牌与未知令牌一样返回 `401 authentication_failed`。现有 App 若在离线期间被撤销,再次检测时可能显示“配对已过期”,需要重新配对;在线会话仍会收到撤销通知并立即断开。
190
196
 
191
197
  <table>
192
198
  <tr>
@@ -237,6 +243,8 @@ Android App 将局域网、cpolar、Tailscale Funnel 和自建 FRP 统一整理
237
243
 
238
244
  Android App 只是 Kotlin WebView 薄壳,不内置另一份网页;手机浏览器访问的是同一页面。需要排查兼容性时,可在浏览器地址后追加 `?frontend=stock`,临时回到旧的桌面页面适配模式。
239
245
 
246
+ 专用移动页面会在 DSH 的第一个启动脚本之前,同步加载经网关鉴权的同源 `/mobile-access/compat.js`,为缺少 `Iterator` / Iterator helpers 的 WebView 提供随插件打包的 core-js 兼容实现,避免启动时出现 `Iterator is not defined`。兼容层按能力检测保留或修正原生 helper,不依赖 CDN,也不会放宽 CSP;不修改 Android APK、桌面页面或 `?frontend=stock` 页面。此修复并不承诺支持所有旧内核,前端仍以 ES2022 为构建目标;若还有其他兼容错误,请优先更新 Android System WebView / Chrome。
247
+
240
248
  > **社区客户端(非官方)**:[微信小程序客户端](https://github.com/StrawberryAO/dsh-mobile-minapp)
241
249
  > 原生微信小程序实现,复用「移动访问」的配对与 Remote 流协议(需 dsh-mobile ≥ 0.3.8)。
242
250
  > 因微信正式版强制「合法域名」(需 ICP 备案的自有 HTTPS 域名),目前需通过微信开发者工具 / 真机调试使用,详见其 README。
@@ -260,6 +268,7 @@ flowchart LR
260
268
  - 局域网监听只用于可信家庭、办公网络或可信热点;不要自行做端口转发。
261
269
  - 远程地址可从公网到达,但未配对请求无法进入 DSH;不使用时应关闭远程开关。
262
270
  - cpolar 仅在用户确认后下载固定官方版本并校验大小和 SHA-256;不会安装系统服务、写入 PATH 或设置开机启动,插件清理会删除其托管文件。
271
+ - cloudflared 同样仅在用户确认后从官方 GitHub Release 下载固定版本并校验精确大小和 SHA-256,且启动时关闭自动更新,以保证运行的始终是已校验的那份二进制;不需要账号、Token 或 DNS 记录,不写入任何凭据,清理时删除插件托管的全部文件。
263
272
  - 自建 FRP 仅在用户确认后从官方 Release 下载固定版本 `frpc`,校验来源、精确大小、SHA-256、压缩包路径和可执行文件版本;共享 Token 不会出现在状态、诊断或日志中。复制服务器模板时 Token 会进入系统剪贴板,请粘贴后及时清除;本机清理只删除插件管理的文件,VPS 需要用面板提供的卸载脚本或一键清理单独清除。自动部署与一键清理前都会展示 SSH 主机指纹,必须到 VPS 控制台核对后才能继续。
264
273
  - 配对设备拥有控制电脑端 DeepSeek Harness 的能力,应视为完全可信设备;丢失手机后应在电脑端撤销设备。
265
274
  - 移动网关开启时才监听局域网;关闭后 DeepSeek Harness 仍正常在电脑本机运行。
@@ -273,6 +282,7 @@ flowchart LR
273
282
 
274
283
  | DSH Mobile 插件 | 验证支持的 DeepSeek Harness 版本 |
275
284
  | ----------------------------------------- | -------------------------------------------------------------- |
285
+ | `0.4.2` | `0.1.6-alpha.1`(本机源码与 renderer-v2 契约检查) |
276
286
  | `0.4.1` | `0.1.6-alpha.1`(本机源码与 renderer-v2 契约检查) |
277
287
  | `0.3.15`、`0.3.16`、`0.4.0` | `0.1.5-rc.2`(契约检查);`0.1.5-rc.1`(@idoall 局域网实测) |
278
288
  | `0.3.14` | `0.1.3-alpha.2` |
@@ -306,6 +316,6 @@ npm ci
306
316
  npm run verify
307
317
  ```
308
318
 
309
- Android 构建见 [App 文档](apps/mobile/README.zh-CN.md)。
319
+ Android 构建见 [App 文档](https://github.com/saya-ch/dsh-mobile/blob/main/apps/mobile/README.zh-CN.md)。
310
320
 
311
321
  Apache-2.0,详见 [LICENSE](LICENSE)。
@@ -6,6 +6,35 @@ The optional cpolar component is not included in the npm package or Android App.
6
6
 
7
7
  The optional [FRP](https://github.com/fatedier/frp) client is not included in the npm package or Android App. When a user explicitly chooses self-hosted FRP installation, DSH Mobile downloads the pinned official `frpc` 0.70.1 archive, verifies its origin, exact size, SHA-256 digest, archive paths, and executable version, then stores only `frpc` under the user's DSH Mobile data directory. FRP is distributed under the Apache License 2.0.
8
8
 
9
+ The optional [cloudflared](https://github.com/cloudflare/cloudflared) client is not included in the npm package or Android App. When a user explicitly chooses cloudflared installation, DSH Mobile downloads the pinned official `cloudflared-windows-amd64.exe` release asset (version recorded in `src/cloudflared-component.ts`), verifies its exact size and SHA-256 digest, and stores it only under the user's DSH Mobile data directory. The client is started with automatic updates disabled and no account credential is requested or stored. cloudflared is distributed under the Apache License 2.0; use of Cloudflare's quick tunnels remains subject to Cloudflare's [website terms](https://www.cloudflare.com/website-terms/).
10
+
9
11
  The npm package declares direct runtime dependencies on [Schemastery](https://github.com/shigma/schemastery) `^3.18.1`, [bonjour-service](https://github.com/onlxltd/bonjour-service) `^1.4.4`, [qrcode](https://github.com/soldair/node-qrcode) `^1.5.4`, and [selfsigned](https://github.com/jfromaniello/selfsigned) `^5.5.0`. These packages are distributed under the MIT License; the release checkout's exact transitive versions appear in the attached CycloneDX npm SBOM.
10
12
 
11
13
  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.12.1`, their AndroidX transitive components, [Guava listenablefuture](https://github.com/google/guava) `1.0`, and [ZXing Core](https://github.com/zxing/zxing) `3.5.4`. These runtime libraries are distributed under the Apache License 2.0; the exact resolved tree is attached to each release as `dsh-mobile-android-v<version>-dependencies.txt`.
14
+
15
+ ## core-js browser compatibility bundle
16
+
17
+ The standalone `lib/mobile-compat.js` bundles selected Iterator modules from [core-js](https://github.com/zloirock/core-js) `^3.50.0` (exact version recorded in `package-lock.json`). It is built into the npm package; browsers do not download a polyfill from a CDN. core-js is distributed under the following MIT License:
18
+
19
+ ```text
20
+ Copyright (c) 2013–2025 Denis Pushkarev (zloirock.ru)
21
+ Copyright (c) 2025–2026 CoreJS Company (core-js.io)
22
+
23
+ Permission is hereby granted, free of charge, to any person obtaining a copy
24
+ of this software and associated documentation files (the "Software"), to deal
25
+ in the Software without restriction, including without limitation the rights
26
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27
+ copies of the Software, and to permit persons to whom the Software is
28
+ furnished to do so, subject to the following conditions:
29
+
30
+ The above copyright notice and this permission notice shall be included in
31
+ all copies or substantial portions of the Software.
32
+
33
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39
+ THE SOFTWARE.
40
+ ```
@@ -0,0 +1,113 @@
1
+ # Cloudflare named tunnel (a stable public hostname)
2
+
3
+ The built-in cloudflared provider runs in one of two modes, chosen in the panel under **Mobile access → Remote → cloudflared → Tunnel type**:
4
+
5
+ | | Quick tunnel (default) | Named tunnel |
6
+ | --- | --- | --- |
7
+ | Account | Not needed | Cloudflare account required |
8
+ | Address | Random `*.trycloudflare.com` on every start | A fixed hostname under your own domain |
9
+ | After a restart | Address changes; pair again | Address is unchanged; paired devices keep working |
10
+ | Availability | Officially for testing: rate-limited, no uptime guarantee | Carried by your own Cloudflare account |
11
+ | Settings | None | Connector token, public hostname, local forward port |
12
+
13
+ Cloudflare terminates DNS and TLS for the public hostname. The plugin only runs `cloudflared` locally and hands traffic to its authenticated private gateway, so **the phone still pairs through the app's Remote access flow** — a tunnel does not change how devices pair.
14
+
15
+ ## Prerequisites
16
+
17
+ 1. A domain already on Cloudflare. Its nameservers must point at the pair Cloudflare assigned, changed at your registrar; that usually takes minutes and up to 24 hours.
18
+ 2. Cloudflare Zero Trust (a team domain) enabled, because the tunnel console lives inside it.
19
+ 3. The cloudflared component installed in the panel. Named and quick tunnels share the same official client.
20
+
21
+ ## Create the tunnel in the Cloudflare dashboard
22
+
23
+ 1. Open **Zero Trust → Networks → Tunnels** and choose **Create a tunnel** → **Cloudflared**.
24
+ 2. Name it, for example `dsh-mobile`, and save; the dashboard then shows a connector install command.
25
+ 3. On the **Public Hostname** tab add one entry:
26
+ - Subdomain `dsh`, and pick your domain, giving `dsh.example.com`
27
+ - Service: **HTTP** → `127.0.0.1:3444`
28
+ 4. On the **Overview** tab copy the connector token — the long string starting with `eyJ`. It already contains the account, tunnel id and tunnel secret, so **it is a credential**.
29
+
30
+ > `127.0.0.1:3444` is the panel's "Local forward port". Cloudflare routes the public hostname to exactly that port, so it must match what you enter in the panel and must not change afterwards.
31
+
32
+ ## Fill in the DSH Mobile panel
33
+
34
+ 1. **Mobile access → Remote → cloudflared**.
35
+ 2. Set the tunnel type to **Named**.
36
+ 3. Enter:
37
+ - **Public hostname**: `dsh.example.com`
38
+ - **Local forward port**: `3444`, matching the Service port from step 3
39
+ - **Connector token**: the token you copied
40
+ 4. Choose **Save and connect**.
41
+
42
+ 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.
43
+
44
+ ## Connect the phone to a named tunnel
45
+
46
+ Moving to a fixed hostname means **an already paired phone must pair again**: a DSH Mobile device credential is only ever sent to the exact origin that first received it, which is the design that stops a swapped-in domain from harvesting it. A credential issued for the old address therefore never authenticates at the new one.
47
+
48
+ 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.
49
+ 2. In the app, **open the Remote entry first** (the remote access setup page in the connection center), then scan the code.
50
+
51
+ > **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.
52
+ >
53
+ > Scanning is optional: the full link (`https://your-domain/mobile-access/pair#instance=…&token=…`) can be copied to the phone and pasted, because the app accepts a complete link in its input field.
54
+
55
+ The old remote entry in the device list will show **Address may have changed** or unreachable; delete it once the new pairing succeeds.
56
+
57
+ ## Security boundary
58
+
59
+ - The token is written only into the DSH Mobile private directory (`~/.dsh/mobile-access/remote/cloudflared/tunnel.json`, mode 0600) and is passed to `cloudflared` **only** through the `TUNNEL_TOKEN` environment variable, never on the command line, which any local process can read.
60
+ - The token is never returned to a browser or a phone. Panel status carries only whether it is configured, plus the hostname and port.
61
+ - Behind the tunnel sits DSH Mobile's own authenticated gateway: the public hostname exposes that gateway, and DSH still requires paired-device credentials.
62
+ - The plugin adds no system service, startup item, registry entry or PATH entry. Disabling the channel ends the process.
63
+
64
+ ## Error codes
65
+
66
+ | Panel message | Code | Meaning and remedy |
67
+ | --- | --- | --- |
68
+ | Local forward port unavailable | `cloudflared_tunnel_port_unavailable` | The configured port is taken. A named tunnel cannot move to another port, because Cloudflare routes to that exact one: free the port, or pick another and update the Service in Cloudflare to match. |
69
+ | Invalid public hostname | `cloudflared_tunnel_hostname_invalid` | Must be a real hostname under a domain on this account. IP literals, wildcards, `.trycloudflare.com` and `.cfargotunnel.com` are refused. |
70
+ | Invalid forward port | `cloudflared_tunnel_port_invalid` | The port must be between 1024 and 65535. |
71
+ | Reserved forward port | `cloudflared_tunnel_port_reserved` | **3443** cannot be used: it is the DSH Mobile LAN gateway's HTTPS port, held for as long as DSH runs. It is not a transient conflict, so pick 3444 or 3445. |
72
+ | Invalid token | `cloudflared_tunnel_token_invalid` | Copy the whole token from the dashboard, with no spaces or newlines. |
73
+ | Settings rejected | `cloudflared_tunnel_settings_invalid` | The request carried a field that does not belong to the mode, such as a port in quick mode. |
74
+ | Token, hostname and port are all required | `cloudflared_tunnel_config_missing` | First-time named-tunnel setup needs all three. |
75
+ | Saved configuration is unreadable | `cloudflared_tunnel_config_invalid` | The stored file is corrupt or malformed. The provider falls back to a quick tunnel; save the settings again. |
76
+ | Saved configuration is not a regular file | `cloudflared_tunnel_target_invalid` | `tunnel.json` became a symlink or a non-regular file. Remove it and save the settings again. |
77
+ | Could not reserve a local port | `cloudflared_port_reservation_failed` | Reserving the loopback port failed for a reason other than the port being busy. Retry, and check system resources if it persists. |
78
+ | Component download failed verification | `cloudflared_download_hash_mismatch` / `cloudflared_download_size_mismatch` | The downloaded binary does not match the pinned size or SHA-256. Install again; repeated failures mean something is rewriting the transfer. |
79
+ | Installed component failed verification | `cloudflared_executable_hash_mismatch` | The local cloudflared no longer matches the verified build. Remove it completely and install again. |
80
+ | This build cannot run the component | `cloudflared_component_unsupported` | The platform is outside the supported set (currently Windows x64 only). |
81
+ | Timed out waiting for the tunnel | `cloudflared_start_timeout` | The connector did not print `Registered tunnel connection` within 60 seconds, usually because it cannot reach a Cloudflare edge. |
82
+ | Component not installed | `cloudflared_component_missing` | The official component is absent. Complete the preparation steps above. |
83
+ | Component verification failed | `cloudflared_component_invalid` | The local component does not match the verified build. Remove it completely and install again. |
84
+ | Could not allocate a port | `cloudflared_port_unavailable` | Quick mode could not allocate the loopback gateway port. Retry. |
85
+ | Client failed to launch | `cloudflared_launch_failed` | The cloudflared process did not start. Check the local logs. |
86
+ | Connection stopped or exited | `cloudflared_stopped` / `cloudflared_exited` | The channel dropped. Reconnect. |
87
+ | Unrecognized output | `cloudflared_invalid_output` / `cloudflared_invalid_origin` | cloudflared output, or the public address it printed, failed validation. Reconnect and copy the diagnostic report. |
88
+ | Gateway start failed | `gateway_start_failed` | The authentication gateway behind the tunnel did not start; this is not a port conflict. Check the local logs. |
89
+ | Download redirect problem | `cloudflared_download_redirect_missing` / `_invalid` / `_rejected` | The official download page did not redirect to a release asset, or the redirect did not point at a GitHub release asset. Retry later, or install from the official page manually. |
90
+
91
+ ## Troubleshooting
92
+
93
+ - **Stuck on "connecting"**: a named tunnel prints no banner, so it only becomes ready once the connector registers. Read the cloudflared output in the DSH log; the `CONNECTIVITY PRE-CHECKS` table says whether DNS, UDP/QUIC or TCP is the problem.
94
+ - **Public access returns 1033**: Cloudflare believes no connector is healthy for that hostname. Check the tunnel shows Healthy in Zero Trust, and that its ingress port matches the panel.
95
+ - **`cloudflared` reports `Unauthorized`, or the tunnel id does not exist**: the token belongs to a different tunnel, for example one that was deleted and recreated. Copy the token again.
96
+ - **With a TUN or transparent proxy running (Clash, Mihomo, Clash Verge), the phone sticks on "Loading plugins" and retries forever.** This is a real failure that was measured, and it is easy to misread as a pairing or plugin problem:
97
+ - `cloudflared`'s connection to `region*.v2.argotunnel.com` has no dedicated rule, so it falls through to the catch-all `Match`/`MATCH` rule and is sent into a proxy node. The core's own API shows it plainly:
98
+ ```
99
+ cloudflared.exe -> region2.v2.argotunnel.com
100
+ rule=Match chains=["<some airport node>","漏网之鱼"] up/down = 103 MB / 2.1 MB
101
+ ```
102
+ - Downstream throughput then collapses to **8–100 KB/s**. The phone's DSH boot pulls a **4.5 MB** (compressed) boot bundle in one shot, the loader gives up, and the app shows "load, fail, retry".
103
+ - The same file took **0.2 s** on the LAN gateway, and **9.9 s / 454 KB/s** through the tunnel once routing was fixed — two orders of magnitude apart.
104
+ - Fix: route cloudflared and its edge domains directly. In Clash Verge's **global merge profile** (`profiles/Merge.yaml`, which subscription updates do not overwrite):
105
+ ```yaml
106
+ prepend-rules:
107
+ - PROCESS-NAME,cloudflared.exe,DIRECT
108
+ - DOMAIN-SUFFIX,argotunnel.com,DIRECT
109
+ - DOMAIN-SUFFIX,trycloudflare.com,DIRECT
110
+ ```
111
+ Then make cloudflared **reconnect**: hot-reloading the core does not migrate long-lived connections (use the panel's reconnect action, or toggle the provider off and on).
112
+ - To confirm it is this and not the phone: fetch the boot bundle from the computer with the same session and watch the rate. If the computer is just as slow, the phone and pairing are innocent.
113
+ - **The domain still resolves to the old address**: after the nameserver change Cloudflare must move the zone from Pending to Active, and records in a pending zone are not served publicly.
@@ -0,0 +1,113 @@
1
+ # Cloudflare 命名隧道(固定公网域名)
2
+
3
+ 内置的 cloudflared 通道有两种模式,在面板 **移动访问 → 远程 → cloudflared → 隧道类型** 中切换:
4
+
5
+ | | 快速隧道(默认) | 命名隧道 |
6
+ | --- | --- | --- |
7
+ | 账号 | 不需要 | 需要 Cloudflare 账号 |
8
+ | 地址 | 每次启动随机分配 `*.trycloudflare.com` | 你自己域名下的固定主机名 |
9
+ | 重启后 | 地址变化,需要重新扫码 | 地址不变,已配对设备继续可用 |
10
+ | 可用性 | 官方定位为测试用途:有限流、无可用性保证 | 由你的 Cloudflare 账号承载 |
11
+ | 配置项 | 无 | 连接器令牌、公网域名、本机转发端口 |
12
+
13
+ 命名隧道的公网域名由 Cloudflare 负责解析与 TLS,插件只负责在本机运行 `cloudflared` 并把流量交给已认证的私有网关;**手机端仍然要走 App 的远程访问扫码流程**,隧道不改变配对方式。
14
+
15
+ ## 前置条件
16
+
17
+ 1. 一个已接入 Cloudflare 的域名(该域名的 NS 必须指向 Cloudflare 分配的名称服务器,在域名注册商处修改;改完通常几分钟到 24 小时生效)。
18
+ 2. Cloudflare Zero Trust(团队域名)已启用——隧道控制台位于其中。
19
+ 3. 面板中 cloudflared 组件已安装(命名隧道和快速隧道使用同一个官方客户端)。
20
+
21
+ ## 在 Cloudflare 控制台创建隧道
22
+
23
+ 1. 打开 **Zero Trust → Networks → Tunnels**,选择 **Create a tunnel**,类型选 **Cloudflared**。
24
+ 2. 命名(例如 `dsh-mobile`),保存后会显示 connector 安装命令。
25
+ 3. 在 **Public Hostname** 页签添加一条:
26
+ - Subdomain:`dsh`,Domain:选你的域名(得到 `dsh.example.com`)
27
+ - Service:**HTTP** → `127.0.0.1:3444`
28
+ 4. 回到 **Overview** 页签复制连接器令牌(一长串以 `eyJ` 开头的字符串)。令牌里已经包含账号、隧道 ID 和隧道密钥,**等同于密码**。
29
+
30
+ > `127.0.0.1:3444` 就是面板里的「本机转发端口」。Cloudflare 把公网主机名固定转发到这个端口,所以它必须与面板中填写的端口一致,并且长期不变。
31
+
32
+ ## 在 DSH Mobile 面板中填写
33
+
34
+ 1. **移动访问 → 远程 → cloudflared**。
35
+ 2. 隧道类型选 **命名隧道**。
36
+ 3. 依次填写:
37
+ - **公网域名**:`dsh.example.com`
38
+ - **本机转发端口**:`3444`(与第 3 步的 Service 端口一致)
39
+ - **连接器令牌**:粘贴上一步复制的令牌
40
+ 4. 点击 **保存并连接**。
41
+
42
+ 已保存后令牌输入框留空表示不更改;输入框里不会再回显已保存的令牌。「移除已保存的令牌」会**同时停止 cloudflared 通道**并把配置改回快速隧道。不会自动重连,之后需要手动重新打开开关。
43
+
44
+ ## 用手机连上命名隧道
45
+
46
+ 切到固定域名后,**已经配对过的手机必须重新配对**:DSH Mobile 的设备凭据只发给它当初保存的那个精确 Origin(这是防止换域名骗走凭据的安全设计),所以旧地址上的凭据在新域名上一律无效。
47
+
48
+ 1. 电脑面板 → **远程** → 点 **生成远程配对二维码**。这一步才会打开配对窗口,窗口有时限;没打开时任何设备都进不来。
49
+ 2. 手机 App → **先进入「远程」入口**(连接中心的远程访问设置页)→ 再扫描二维码。
50
+
51
+ > **命名隧道必须在「远程」入口里扫码。** App 只把 `.ts.net`、cpolar、`.trycloudflare.com` 这类平台后缀当作「无需询问即为远程」;你自己的域名同样会被识别为可远程的候选地址,但「局域网」流程只接受非候选地址,所以在局域网界面扫码会被判为无效并提示**无效二维码 / Invalid QR code**,看起来就像「连不上」。先进入「远程」流程再扫即可。
52
+ >
53
+ > 不想扫码时,完整链接(`https://你的域名/mobile-access/pair#instance=…&token=…`)可以整条复制到手机上粘贴,App 的输入框接受完整链接。
54
+
55
+ 设备列表里原来那条远程记录会显示「地址可能已变化 / Address may have changed」或不可达,重新配对成功后删掉即可。
56
+
57
+ ## 安全边界
58
+
59
+ - 令牌只写入 DSH Mobile 私有目录(`~/.dsh/mobile-access/remote/cloudflared/tunnel.json`,权限 0600),并且**只**通过子进程环境变量 `TUNNEL_TOKEN` 传给 `cloudflared`,不出现在命令行里(本机任何进程都能读到命令行)。
60
+ - 令牌从不回传给浏览器或手机端;面板状态里只有「是否已配置」、域名和端口。
61
+ - 隧道背后是 DSH Mobile 自己的认证网关:公网主机名只暴露该网关,DSH 本体仍然需要配对设备凭据。
62
+ - 插件不会创建系统服务、开机启动项、注册表项或 PATH 项;关闭通道即结束进程。
63
+
64
+ ## 错误码
65
+
66
+ | 面板提示 | 实际错误码 | 含义与处理 |
67
+ | --- | --- | --- |
68
+ | 本机转发端口不可用 | `cloudflared_tunnel_port_unavailable` | 配置的端口已被占用。命名隧道不能改用其他端口(Cloudflare 固定转发到该端口),请释放端口或换一个端口并同步修改 Cloudflare 的 Service。 |
69
+ | 公网域名无效 | `cloudflared_tunnel_hostname_invalid` | 必须是本账号下域名的真实主机名;不接受 IP、通配符、`.trycloudflare.com` 与 `.cfargotunnel.com`。 |
70
+ | 转发端口无效 | `cloudflared_tunnel_port_invalid` | 端口需在 1024–65535 之间。 |
71
+ | 转发端口被保留 | `cloudflared_tunnel_port_reserved` | 不能填 **3443**:那是 DSH Mobile 局域网网关的 HTTPS 端口,只要 DSH 在运行就一直被占用,不是"临时被别的程序占了"。请换 3444 或 3445。 |
72
+ | 令牌无效 | `cloudflared_tunnel_token_invalid` | 令牌需从控制台完整复制,不能有空格或换行。 |
73
+ | 设置未通过校验 | `cloudflared_tunnel_settings_invalid` | 请求里带了不该有的字段(例如快速隧道模式下携带端口)。 |
74
+ | 需要同时填写令牌、域名和端口 | `cloudflared_tunnel_config_missing` | 首次配置命名隧道时三项都必填。 |
75
+ | 已保存配置无法读取 | `cloudflared_tunnel_config_invalid` | 配置文件损坏或不符合格式;插件会退回快速隧道,重新保存即可。 |
76
+ | 已保存配置不是普通文件 | `cloudflared_tunnel_target_invalid` | `tunnel.json` 变成了符号链接或非常规文件。移除它并重新保存设置。 |
77
+ | 无法预留本机端口 | `cloudflared_port_reservation_failed` | 本机端口预留本身失败(不是端口被占)。重试;若持续出现请检查系统资源。 |
78
+ | 组件下载校验失败 | `cloudflared_download_hash_mismatch` / `cloudflared_download_size_mismatch` | 下载到的二进制与固定版本的大小或 SHA-256 不符。重新安装;若反复失败说明中间链路在改包。 |
79
+ | 已安装组件校验失败 | `cloudflared_executable_hash_mismatch` | 本机那份 cloudflared 与校验过的版本不一致。彻底移除后重新安装。 |
80
+ | 当前构建不支持该组件 | `cloudflared_component_unsupported` | 当前平台不在支持范围内(目前仅 Windows x64)。 |
81
+ | 等待隧道可用超时 | `cloudflared_start_timeout` | connector 在 60 秒内没有打印 `Registered tunnel connection`。常见原因是网络到 Cloudflare 边缘不通。 |
82
+ | 组件未安装 | `cloudflared_component_missing` | 还没安装官方组件。按上面的准备步骤安装。 |
83
+ | 组件校验失败 | `cloudflared_component_invalid` | 本机组件与校验过的版本不符。彻底移除后重新安装。 |
84
+ | 无法分配端口 | `cloudflared_port_unavailable` | 快速隧道模式下无法分配本机网关端口。重试。 |
85
+ | 客户端启动失败 | `cloudflared_launch_failed` | cloudflared 进程没能启动。检查本地日志。 |
86
+ | 连接已停止 / 意外退出 | `cloudflared_stopped` / `cloudflared_exited` | 通道已断开,点重新连接。 |
87
+ | 返回内容无法识别 | `cloudflared_invalid_output` / `cloudflared_invalid_origin` | cloudflared 输出或公网地址未通过校验。重新连接并复制诊断报告。 |
88
+ | 网关启动失败 | `gateway_start_failed` | 隧道背后的认证网关没能启动(不是端口冲突)。检查本地日志。 |
89
+ | 下载跳转异常 | `cloudflared_download_redirect_missing` / `_invalid` / `_rejected` | 官方下载页没有跳到发布资源,或跳转目标不是 GitHub 发布资源。稍后重试,或按官方页面手动安装。 |
90
+
91
+ ## 排错
92
+
93
+ - **连接一直停在「正在连接」**:命名隧道没有横幅,只有 connector 真正注册后才算就绪。查看 DSH 日志里 cloudflared 的输出;预检表(`CONNECTIVITY PRE-CHECKS`)会指出是 DNS、UDP/QUIC 还是 TCP 不通。
94
+ - **公网访问返回 1033**:Cloudflare 认为该主机名没有健康的 connector。确认隧道在 Zero Trust 里显示 Healthy,且 ingress 指向的端口与面板一致。
95
+ - **`cloudflared` 报 `Unauthorized` 或隧道 ID 不存在**:令牌与控制台里的隧道不匹配(例如隧道被删除后重建)。重新复制令牌。
96
+ - **本机开着 TUN/透明代理(Clash、Mihomo、Clash Verge 等):手机端会卡在「正在加载插件」并反复重试。** 这是实测到过的一类真实故障,症状很容易被误判成配对或插件问题:
97
+ - `cloudflared` 到 `region*.v2.argotunnel.com` 的连接没有任何专属规则,于是落到兜底的 `Match`/`MATCH` 规则,被送进代理节点。用内核 API 看得很清楚:
98
+ ```
99
+ cloudflared.exe -> region2.v2.argotunnel.com
100
+ rule=Match chains=["<某个机场节点>","漏网之鱼"] up/down = 103 MB / 2.1 MB
101
+ ```
102
+ - 结果下行被压到 **8–100 KB/s**。而手机端 DSH 启动要一次拉 **4.5 MB**(压缩后)的 boot bundle,加载器等不到就报 `bundle script failed to load`,App 表现为「加载 → 失败 → 重试」。
103
+ - 同一份文件在局域网网关上是 **0.2 秒**,加上直连规则后走隧道是 **9.9 秒 / 454 KB/s** —— 差了两个数量级。
104
+ - 修法:让 cloudflared 与其边缘域名走直连。Clash Verge 的**全局扩展配置**(`profiles/Merge.yaml`,订阅更新不会覆盖):
105
+ ```yaml
106
+ prepend-rules:
107
+ - PROCESS-NAME,cloudflared.exe,DIRECT
108
+ - DOMAIN-SUFFIX,argotunnel.com,DIRECT
109
+ - DOMAIN-SUFFIX,trycloudflare.com,DIRECT
110
+ ```
111
+ 改完要让 cloudflared **重连**才会换路由:内核热重载不会迁移已建立的长连接(面板里点重连,或开关一次提供方)。
112
+ - 判断方法:如果手机端一直卡在加载插件,先在**本机**用同一份会话拉一次 boot bundle 看速率。若本机也很慢,就不是手机或配对的问题。
113
+ - **域名解析还是旧地址**:改完 NS 后 Cloudflare 需要把 zone 从 Pending 变为 Active;A/CNAME 在 zone 激活前不会对外生效。