dsh-mobile 0.5.1 → 0.5.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 +6 -0
- package/CONTRIBUTORS.md +2 -2
- package/README.en.md +12 -8
- package/README.md +12 -8
- package/docs/ATTACH_EXISTING_FRPS.en.md +3 -1
- package/docs/ATTACH_EXISTING_FRPS.md +3 -1
- package/docs/SELF_HOSTED_FRP.en.md +3 -1
- package/docs/SELF_HOSTED_FRP.md +3 -1
- package/lib/client.js +69 -7
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +53 -3
- package/lib/index.mjs +141 -9
- package/lib/index.mjs.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to DSH Mobile are recorded here. GitHub Releases remain the source for downloadable packages and complete generated commit notes.
|
|
4
4
|
|
|
5
|
+
## 0.5.2 - 2026-09-29
|
|
6
|
+
|
|
7
|
+
- Give each DSH Mobile installation a stable FRP proxy name on a shared frps and report proxy-name or entry-port conflicts directly instead of waiting for a generic timeout (thanks @liudasheng for [PR #130](https://github.com/saya-ch/dsh-mobile/pull/130)). Keep that name through first-time setup and restarts, and use it in copied attach instructions.
|
|
8
|
+
- Keep the four DSH peer names for host-side module resolution, but remove their per-release version ranges so a newly published DSH prerelease is not rejected solely by plugin metadata. Source-contract and isolated browser-startup checks now include DSH `0.2.0-rc.2`.
|
|
9
|
+
- Synchronize Android App version metadata with the plugin; native behavior and the pairing protocol are unchanged from 0.5.1.
|
|
10
|
+
|
|
5
11
|
## 0.5.1 - 2026-09-29
|
|
6
12
|
|
|
7
13
|
- Restore the Android system Back key on devices that deliver the legacy Activity callback even on Android 13 and later. The DSH page still uses its WebView history when available and exits the app at the root.
|
package/CONTRIBUTORS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
GitHub 的 [Contributors 图表](https://github.com/saya-ch/dsh-mobile/graphs/contributors) 按进入默认分支的提交自动统计,无法手动加入仅提交 issue 或 PR 提案的成员。本页按实际参与方式感谢社区成员;仓库维护者与自动依赖更新仍可在 GitHub 历史记录中查看。
|
|
4
4
|
|
|
5
|
-
GitHub's Contributors graph counts commits on the default branch; issue reports and PR proposals cannot be manually added to it. This page credits community members by how they contributed, through 2026-09-
|
|
5
|
+
GitHub's Contributors graph counts commits on the default branch; issue reports and PR proposals cannot be manually added to it. This page credits community members by how they contributed, through 2026-09-29. The repository maintainer and automated dependency updates remain visible in GitHub's history.
|
|
6
6
|
|
|
7
7
|
## 已合并 PR / Merged PRs
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ GitHub's Contributors graph counts commits on the default branch; issue reports
|
|
|
14
14
|
| [@chintoleung](https://github.com/chintoleung) | [#104](https://github.com/saya-ch/dsh-mobile/pull/104) |
|
|
15
15
|
| [@idoall](https://github.com/idoall) | [#61](https://github.com/saya-ch/dsh-mobile/pull/61), [#66](https://github.com/saya-ch/dsh-mobile/pull/66), [#67](https://github.com/saya-ch/dsh-mobile/pull/67), [#77](https://github.com/saya-ch/dsh-mobile/pull/77) |
|
|
16
16
|
| [@JackRushante](https://github.com/JackRushante) | [#16](https://github.com/saya-ch/dsh-mobile/pull/16) |
|
|
17
|
-
| [@liudasheng](https://github.com/liudasheng) | [#100](https://github.com/saya-ch/dsh-mobile/pull/100) |
|
|
17
|
+
| [@liudasheng](https://github.com/liudasheng) | [#100](https://github.com/saya-ch/dsh-mobile/pull/100), [#130](https://github.com/saya-ch/dsh-mobile/pull/130) |
|
|
18
18
|
| [@longisland-icetea](https://github.com/longisland-icetea) | [#59](https://github.com/saya-ch/dsh-mobile/pull/59), [#60](https://github.com/saya-ch/dsh-mobile/pull/60), [#63](https://github.com/saya-ch/dsh-mobile/pull/63) |
|
|
19
19
|
| [@qzyqmzn](https://github.com/qzyqmzn) | [#38](https://github.com/saya-ch/dsh-mobile/pull/38), [#75](https://github.com/saya-ch/dsh-mobile/pull/75) |
|
|
20
20
|
| [@Sanksu](https://github.com/Sanksu) | [#118](https://github.com/saya-ch/dsh-mobile/pull/118) |
|
package/README.en.md
CHANGED
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
|
|
32
32
|
> DSH Mobile is a DeepSeek Harness community plugin; the native app supports Android only.
|
|
33
33
|
>
|
|
34
|
-
> **Current version: 0.5.
|
|
34
|
+
> **Current version: 0.5.2**. It gives installations stable, distinct proxy names on shared frps servers, reports name or port conflicts directly, and removes the DSH prerelease peer-version gate. [Release notes](CHANGELOG.md#052---2026-09-29).
|
|
35
35
|
>
|
|
36
|
-
> **Upgrade reminder**:
|
|
36
|
+
> **Upgrade reminder**: the plugin and Android app can both be updated to 0.5.2. This APK only synchronizes version metadata; the 0.5.1 app and existing pairings do not need reinstalling or re-pairing. [Compatibility notes](#compatibility).
|
|
37
37
|
|
|
38
38
|
<p align="center">
|
|
39
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.
|
|
40
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.
|
|
41
|
-
<sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.5.
|
|
39
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.2/dsh-mobile-android-v0.5.2.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
|
|
40
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.2/dsh-mobile-android-v0.5.2.apk"><strong>Download Android app 0.5.2</strong></a><br>
|
|
41
|
+
<sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.5.2">Release notes and checksums</a></sub>
|
|
42
42
|
</p>
|
|
43
43
|
|
|
44
44
|
DSH Mobile is a DeepSeek Harness plugin that lets a mobile browser or the Android app connect over a protected LAN or an optional Tailscale Funnel, cpolar, cloudflared, self-hosted FRP, or own reverse-proxy remote path. Both routes reach the same sessions, Workspaces, messages, and tools. The computer manages their switches and pairing authorizations separately; the Android app lists paired computers together. The plugin does not modify DeepSeek Harness source.
|
|
@@ -218,7 +218,8 @@ Compatibility and WebSocket rules:
|
|
|
218
218
|
|
|
219
219
|
Proxied pages allow HTTP frames for compatibility with some community plugins; those pages are unencrypted and can be altered, and browsers may still block them as mixed content. Use HTTPS for sensitive work. The same warning appears at the top of the remote panel when it is opened over HTTPS.
|
|
220
220
|
|
|
221
|
-
- Version 0.5.
|
|
221
|
+
- Version 0.5.2 no longer blocks installation merely because a DSH version is absent from the peer range; source-contract, isolated pairing, page boot, and Workspace checks have covered `0.2.0-rc.2`. Future releases still require verification. The Mobile administration surface in official DSH Desktop continues to require an authenticated DSH session and does not loosen ordinary Web administration requests.
|
|
222
|
+
- Version 0.5.1 continued contract and boot checks against DSH `0.1.7-alpha.2`, `0.1.7-rc.1`, and `0.1.7-rc.2`, and supported `0.2.0-rc.1`. Official DSH Desktop `0.1.7-rc.2` uses a `dsh-app://app/` page.
|
|
222
223
|
- The gateway allows first-party DSH WebSocket paths by default, including `/sidebar/ws/terminal`. Other paths used by community sidebar plugins are blocked by default and appear in Diagnostics; the `/sidebar/ws/agent-opens` and `/sidebar/ws/agent-terminals` paths in the image are examples that must be reviewed for the actual plugin.
|
|
223
224
|
- In **Connection diagnostics → Third-party WebSocket paths**, select **Allow** only for an exact path you have verified. Query strings and fuzzy prefixes are rejected; **Allow all** is not recommended. Approved paths can be removed at any time, and the same policy applies to LAN and remote connections.
|
|
224
225
|
- Approval only lets that path pass through the authenticated, same-origin DSH Mobile gateway. It does not open arbitrary TCP/UDP ports or bypass device pairing. If a community plugin still fails, check the path recorded by Diagnostics and approve one path at a time.
|
|
@@ -298,6 +299,8 @@ See [SECURITY.md](SECURITY.md).
|
|
|
298
299
|
|
|
299
300
|
The table records tested DSH and plugin version combinations; it does not imply automatic compatibility with other versions. Since 0.3.6 the plugin has not rejected DSH solely by version number. If mobile access breaks after upgrading DSH, check this table and update the plugin first. History lives in [CHANGELOG.md](CHANGELOG.md).
|
|
300
301
|
|
|
302
|
+
Version 0.5.2 keeps the DSH host peer names but does not block installation by DSH version. New releases still need the source-contract and paired-browser checks below; an unlisted version is not automatically verified.
|
|
303
|
+
|
|
301
304
|
### OS support matrix
|
|
302
305
|
|
|
303
306
|
| Channel | Windows x64 | Linux x64 | Linux arm64 | macOS |
|
|
@@ -313,6 +316,7 @@ On macOS, local network, self-hosted FRP, and the own reverse proxy work; the th
|
|
|
313
316
|
|
|
314
317
|
| DSH Mobile plugin | Verified DeepSeek Harness version |
|
|
315
318
|
| --- | --- |
|
|
319
|
+
| `0.5.2` | `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, and `0.2.0-rc.1` (carried forward from earlier verification); `0.2.0-rc.2` (source contract, isolated pairing, mobile-page boot, and WebSocket Workspace baseline) |
|
|
316
320
|
| `0.5.1` | `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, and `0.2.0-rc.1`; official Desktop `0.1.7-rc.2` (administration surface) |
|
|
317
321
|
| `0.5.0` | `0.1.7-alpha.2`, `0.1.7-rc.1`, and `0.1.7-rc.2`; official Desktop `0.1.7-rc.2` (administration surface) |
|
|
318
322
|
| `0.4.7` | `0.1.7-alpha.2`, `0.1.7-rc.1`, and `0.1.7-rc.2` (source contract, isolated pairing, and workspace baseline over WebSocket) |
|
|
@@ -364,11 +368,11 @@ npm ci
|
|
|
364
368
|
npm run verify
|
|
365
369
|
```
|
|
366
370
|
|
|
367
|
-
The real browser-startup smoke uses a temporary DSH home, OS-assigned loopback ports, and Chromium pairing. It neither reads an existing user profile nor sends a model request. CI tests DSH `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, and `0.2.0-rc.
|
|
371
|
+
The real browser-startup smoke uses a temporary DSH home, OS-assigned loopback ports, and Chromium pairing. It neither reads an existing user profile nor sends a model request. Development-branch CI tests DSH `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, `0.2.0-rc.1`, and `0.2.0-rc.2`; install DSH `0.2.0-rc.2` in a separate directory so it does not replace the plugin's development dependencies:
|
|
368
372
|
|
|
369
373
|
```powershell
|
|
370
374
|
$dshMobileTestRuntime = Join-Path $env:TEMP 'dsh-mobile-test-runtime'
|
|
371
|
-
npm install --prefix $dshMobileTestRuntime --no-save --package-lock=false @deepseek-ai/dsh@0.
|
|
375
|
+
npm install --prefix $dshMobileTestRuntime --no-save --package-lock=false @deepseek-ai/dsh@0.2.0-rc.2
|
|
372
376
|
$env:DSH_BOOT_SMOKE_BIN = Join-Path $dshMobileTestRuntime 'node_modules/@deepseek-ai/dsh/lib/bin.js'
|
|
373
377
|
npx playwright install chromium --only-shell
|
|
374
378
|
npm run smoke:dsh-boot
|
package/README.md
CHANGED
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
|
|
32
32
|
> DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
|
|
33
33
|
>
|
|
34
|
-
> **当前版本:0.5.
|
|
34
|
+
> **当前版本:0.5.2**。共享 frps 时使用稳定的独立代理名,并明确提示代理名或端口冲突;解除 DSH 预发布版的依赖版本拦截。[更新记录](CHANGELOG.md#052---2026-09-29)。
|
|
35
35
|
>
|
|
36
|
-
>
|
|
36
|
+
> **升级提醒**:插件与 Android App 可同步更新至 0.5.2。App 本次只同步版本信息;0.5.1 App 与现有配对无需重装或重新配对。[兼容说明](#兼容性)。
|
|
37
37
|
|
|
38
38
|
<p align="center">
|
|
39
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.
|
|
40
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.
|
|
41
|
-
<sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.5.
|
|
39
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.2/dsh-mobile-android-v0.5.2.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
|
|
40
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.5.2/dsh-mobile-android-v0.5.2.apk"><strong>下载 Android App 0.5.2</strong></a><br>
|
|
41
|
+
<sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.5.2">版本说明与校验文件</a></sub>
|
|
42
42
|
</p>
|
|
43
43
|
|
|
44
44
|
DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、cloudflared、自建 FRP 或自有反向代理远程通道连接电脑,继续使用同一份会话、工作区、消息和工具。电脑端分别启停局域网与远程访问、分别管理配对授权;Android App 统一显示已配对电脑。插件不修改 DeepSeek Harness 源码。
|
|
@@ -218,7 +218,8 @@ Android App 用一个“已配对设备”列表同时显示多台电脑:局
|
|
|
218
218
|
|
|
219
219
|
代理页面为兼容部分社区插件允许嵌入 HTTP 页面;这类内容未加密,可能被篡改,浏览器也可能因混合内容策略拦截。处理敏感内容时请使用 HTTPS。通过 HTTPS 管理入口打开远程面板时,页面顶部会显示相同提醒。
|
|
220
220
|
|
|
221
|
-
- 0.5.
|
|
221
|
+
- 0.5.2 不再因 DSH 版本号未列入依赖范围而阻止安装;已对 `0.2.0-rc.2` 进行源码契约、隔离配对、页面启动及工作区读取检查。未来新版本仍需实际验证。官方 DSH Desktop 的移动访问管理入口继续要求已认证的 DSH 会话,不放宽普通 Web 页面的管理请求。
|
|
222
|
+
- 0.5.1 延续对 DSH `0.1.7-alpha.2`、`0.1.7-rc.1` 和 `0.1.7-rc.2` 的契约及启动检查,并适配 `0.2.0-rc.1`。官方 DSH Desktop `0.1.7-rc.2` 使用 `dsh-app://app/` 页面。
|
|
222
223
|
- 网关默认只允许 DSH 内置的第一方 WebSocket 路径,包括 `/sidebar/ws/terminal`。社区侧边栏插件使用的其他路径默认拦截,通常会在诊断页显示为待处理项目;截图中的 `/sidebar/ws/agent-opens` 和 `/sidebar/ws/agent-terminals` 就属于这类需要按实际插件确认的路径。
|
|
223
224
|
- 在 **连接诊断 → 第三方 WebSocket 路径** 中,只对确认过的精确路径点击 **允许**。系统不接受带查询字符串或模糊前缀的路径;不建议使用“全部允许”。已允许的路径可以随时移除,局域网和远程连接使用同一套规则。
|
|
224
225
|
- 放行只代表该路径可以通过已认证、同源的 DSH Mobile 网关,不会开放任意 TCP/UDP 端口,也不会绕过设备配对。若社区插件仍然连接失败,先看诊断页的实际拦截路径,再按一条路径放行。
|
|
@@ -299,6 +300,8 @@ flowchart LR
|
|
|
299
300
|
|
|
300
301
|
下表记录已验证的 DSH 与插件版本组合,不表示其他版本自动兼容。0.3.6 起插件不会仅凭版本号拒绝启动;升级 DSH 后若遇到移动端异常,请先核对兼容表并更新插件。历史记录见 [CHANGELOG.md](CHANGELOG.md)。
|
|
301
302
|
|
|
303
|
+
0.5.2 保留 DSH 宿主依赖名称,但不按 DSH 版本号拦截安装。新版本仍需经过下方的源码契约与配对启动检查;不在表中的版本不代表已验证。
|
|
304
|
+
|
|
302
305
|
### 系统支持矩阵
|
|
303
306
|
|
|
304
307
|
| 通道 | Windows x64 | Linux x64 | Linux arm64 | macOS |
|
|
@@ -314,6 +317,7 @@ macOS 上局域网、自建 FRP 与自有反向代理可用;三个托管组件
|
|
|
314
317
|
|
|
315
318
|
| DSH Mobile 插件 | 验证支持的 DeepSeek Harness 版本 |
|
|
316
319
|
| ----------------------------------------- | -------------------------------------------------------------- |
|
|
320
|
+
| `0.5.2` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`(延续既有验证);`0.2.0-rc.2`(源码契约、隔离配对、移动页面启动和 WebSocket 工作区读取) |
|
|
317
321
|
| `0.5.1` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`;官方 Desktop `0.1.7-rc.2`(桌面管理入口) |
|
|
318
322
|
| `0.5.0` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`;官方 Desktop `0.1.7-rc.2`(桌面管理入口) |
|
|
319
323
|
| `0.4.7` | `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`(源码契约、隔离配对及 WebSocket 工作区读取) |
|
|
@@ -365,11 +369,11 @@ npm ci
|
|
|
365
369
|
npm run verify
|
|
366
370
|
```
|
|
367
371
|
|
|
368
|
-
真实启动冒烟另用临时 DSH Home、随机回环端口和 Chromium
|
|
372
|
+
真实启动冒烟另用临时 DSH Home、随机回环端口和 Chromium 配对,不访问现有用户配置,也不发送模型请求。开发分支的 CI 分别测试 DSH `0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1` 与 `0.2.0-rc.2`;本机可把 DSH `0.2.0-rc.2` 装在独立目录,避免替换插件的开发依赖:
|
|
369
373
|
|
|
370
374
|
```powershell
|
|
371
375
|
$dshMobileTestRuntime = Join-Path $env:TEMP 'dsh-mobile-test-runtime'
|
|
372
|
-
npm install --prefix $dshMobileTestRuntime --no-save --package-lock=false @deepseek-ai/dsh@0.
|
|
376
|
+
npm install --prefix $dshMobileTestRuntime --no-save --package-lock=false @deepseek-ai/dsh@0.2.0-rc.2
|
|
373
377
|
$env:DSH_BOOT_SMOKE_BIN = Join-Path $dshMobileTestRuntime 'node_modules/@deepseek-ai/dsh/lib/bin.js'
|
|
374
378
|
npx playwright install chromium --only-shell
|
|
375
379
|
npm run smoke:dsh-boot
|
|
@@ -41,6 +41,8 @@ The computer-side gateway terminates TLS with a self-signed CA valid for five ye
|
|
|
41
41
|
| `frp_attach_cert_unknown` | The self-signed certificate is absent or unreadable. Inspect the panel's certificate status and the private `remote/ingress/` files; do not bypass fingerprint validation. |
|
|
42
42
|
| `frp_ingress_ca_expired` | The pinned CA expired. Reconfigure the self-signed entry and pair the phone again after checking the new fingerprint. |
|
|
43
43
|
| Browser reports an untrusted certificate | Expected for the self-signed entry. Use the 0.4.6 Android app and explicit pairing; use public-CA mode for a browser. |
|
|
44
|
+
| `frp_proxy_name_in_use` | frps keeps proxy names unique across **all** its clients, and this installation's own name (`dsh-mobile-<first 12 characters of the installation identity>`) is already registered — usually another DSH computer, or this computer's previous session before the frps heartbeat expires it. Disable self-hosted FRP there, or remove that proxy on the frps. frpc keeps running without publishing the entry port, so before this code the failure only ever appeared as a start timeout. |
|
|
45
|
+
| `frp_remote_port_in_use` | Another proxy on the same frps already owns the chosen entry port. Ports are first-come, first-served per server, so give this computer a different `publicPort` and allow it in the firewall. |
|
|
44
46
|
| Public entry unreachable | Check the chosen port, cloud and host firewalls, frps listener address, running `frpc`, and the external-network probe. A computer-side probe is not proof that a phone can connect. |
|
|
45
47
|
|
|
46
|
-
**Remove FRP completely** deletes only plugin-managed files on the computer, including the local `frpc` configuration and self-signed ingress material. The attach path never deletes or restarts your existing frps, Caddy site, certificates, or firewall rules on the VPS. Managed deployment and its separate server cleanup are described in the [self-hosted FRP guide](SELF_HOSTED_FRP.en.md).
|
|
48
|
+
**Remove FRP completely** deletes only plugin-managed FRP files on the computer, including the local `frpc` configuration and self-signed ingress material. The non-secret plugin `installation-id` remains so a later FRP connection keeps the same proxy name; removing all DSH Mobile private state removes that identifier too. The attach path never deletes or restarts your existing frps, Caddy site, certificates, or firewall rules on the VPS. Managed deployment and its separate server cleanup are described in the [self-hosted FRP guide](SELF_HOSTED_FRP.en.md).
|
|
@@ -92,6 +92,8 @@ curl -k -sS -o /dev/null -w '%{http_code}\n' https://YOUR_PUBLIC_IPV4:33080/mobi
|
|
|
92
92
|
| `frp_ingress_ca_expired` | 自签 CA 已过期,插件不会静默替换受信任 CA;重新生成 CA 后,手机须核对新指纹并重新配对。 |
|
|
93
93
|
| 手机浏览器提示证书不受信任 | 自签档的正常现象;请在 App 内配对使用 |
|
|
94
94
|
| 自检显示「公网入口不可达」 | 端口未放行、frpc 未启动,或 frps 未把该 `remotePort` 转发出来 |
|
|
95
|
+
| `frp_proxy_name_in_use` | frps 的代理名在**整台服务器**上唯一。本机安装名(`dsh-mobile-<安装标识前 12 位>`)已被占用:多半是另一台 DSH 电脑,或本机上一次连接尚未等到 frps 心跳超时。到那台机器上关闭自建 FRP,或在 frps 上移除该代理。frpc 此时仍在运行、只是入口端口没被发布,所以在此之前只会表现为「公网端点未能就绪」。 |
|
|
96
|
+
| `frp_remote_port_in_use` | 同一台 frps 上该入口端口已被别的代理占用(端口先到先得)。给这台电脑换一个 `publicPort`,并在防火墙/安全组放行。 |
|
|
95
97
|
| App 报「连接到另一台 DSH」 | 公网入口指向了别的 DSH:检查 `remotePort` 与 frpc 是否在本机运行 |
|
|
96
98
|
|
|
97
99
|
## 与其他通道的关系
|
|
@@ -99,4 +101,4 @@ curl -k -sS -o /dev/null -w '%{http_code}\n' https://YOUR_PUBLIC_IPV4:33080/mobi
|
|
|
99
101
|
- **`origin` 自有反代通道**(默认 3444)是另一条独立路径,TLS 由**你自己的外部反代**终止,与本档互不影响。
|
|
100
102
|
- **deploy 模式**(由插件安装 frps + Caddy)保持不变,仍需要 SSH;attach 模式**零 SSH**。
|
|
101
103
|
|
|
102
|
-
本机的「彻底移除 FRP」只清理插件管理的 frpc
|
|
104
|
+
本机的「彻底移除 FRP」只清理插件管理的 FRP 文件,包括 frpc、私有配置和自签入口材料。不含密钥的插件 `installation-id` 会保留,以便以后重连时沿用同一代理名;清理全部 DSH Mobile 私有状态时也会移除它。此操作不会删除或重启 VPS 上你已有的 frps、Caddy、证书或防火墙规则。托管部署及其独立的服务器清理步骤见[自建 FRP 使用指南](SELF_HOSTED_FRP.md)。
|
|
@@ -41,7 +41,7 @@ The SSH user, port, and private-key path are kept only in this browser's `localS
|
|
|
41
41
|
|
|
42
42
|
## Clean up the VPS
|
|
43
43
|
|
|
44
|
-
The local **Remove FRP completely** action removes only the computer's managed frpc, token, and configuration; it does not touch the VPS. Server cleanup has two equivalent paths, and both remove only DSH Mobile-owned files, services, and tagged firewall rules:
|
|
44
|
+
The local **Remove FRP completely** action removes only the computer's managed frpc, token, and configuration; it does not touch the VPS. The non-secret plugin `installation-id` remains so a later FRP connection keeps the same proxy name; removing all DSH Mobile private state removes that identifier too. Server cleanup has two equivalent paths, and both remove only DSH Mobile-owned files, services, and tagged firewall rules:
|
|
45
45
|
|
|
46
46
|
1. **Copy the uninstall script**: select **Copy VPS uninstall script**, review it, and run it as root on the VPS. The script stops and removes the frps service and certificate-renewal timer, deletes DSH Mobile configuration, binaries, certificates, and the Caddy snippet, removes the import line from the main Caddyfile while preserving your content, and removes UFW rules carrying the DSH Mobile marker. It deletes the `dsh-mobile` system user only when this deployment created it; an existing user is kept and reported.
|
|
47
47
|
2. **One-click cleanup**: select **Remove DSH Mobile from the VPS**. The panel verifies the host keys again and runs the same script over pinned SSH after confirmation.
|
|
@@ -63,6 +63,8 @@ Only public IPv4 mode installs `dsh-mobile-cert-renew.timer`; in that mode, also
|
|
|
63
63
|
|
|
64
64
|
On the computer, start with the plugin log at `$DSH_HOME/mobile-access/logs/dsh-mobile.log` (JSONL, 5 MB rotation, tokens and keys redacted) and the panel diagnostic report. Windows antivirus software can quarantine frpc; if it does, create the smallest possible exception for the verified component directory only.
|
|
65
65
|
|
|
66
|
+
When several computers share one frps, remember that proxy names are unique across the **whole server** and entry ports are first-come, first-served. The plugin registers an installation-derived name (`dsh-mobile-<first 12 characters of the installation identity>`), so two computers no longer compete for the same name. "A proxy with this installation name is already in use" means that name belongs to another computer, or to this computer's previous session before the frps heartbeat expired it; "the entry port is already occupied" means another proxy holds that port, so give this computer a different one. In both cases frpc stays alive without ever publishing the entry port, which is why the panel previously reported only "the public endpoint did not become ready".
|
|
67
|
+
|
|
66
68
|
## Limits
|
|
67
69
|
|
|
68
70
|
- An unregistered domain on a mainland-China VPS may be intercepted by the cloud provider; use public IPv4 mode in that case.
|
package/docs/SELF_HOSTED_FRP.md
CHANGED
|
@@ -45,7 +45,7 @@ SSH 用户、端口和私钥路径只保存在当前浏览器的 `localStorage`
|
|
|
45
45
|
|
|
46
46
|
## 清理 VPS
|
|
47
47
|
|
|
48
|
-
本机“彻底移除 FRP”只删除电脑端的 frpc、Token 与配置,不动 VPS
|
|
48
|
+
本机“彻底移除 FRP”只删除电脑端的 frpc、Token 与配置,不动 VPS。不含密钥的插件 `installation-id` 会保留,方便以后重连时沿用同一代理名;清理全部 DSH Mobile 私有状态时也会移除它。清理 VPS 有两种方式,都只删除 DSH Mobile 明确拥有的文件、服务与带标记的防火墙规则:
|
|
49
49
|
|
|
50
50
|
1. **复制卸载脚本**:点“复制 VPS 卸载脚本”,审阅后以 root 身份在 VPS 上执行。脚本会停用并删除 frps 服务与证书续期定时器、删除配置/二进制/证书文件与 Caddy 片段、从主 Caddyfile 里删掉那一行 import(你自己的内容原样保留;文件删空时留一句占位注释)、删除带 DSH Mobile 标记的 UFW 规则。只有本次部署创建的 `dsh-mobile` 系统用户才会被删除,之前就存在的会被保留并明确告知。
|
|
51
51
|
2. **一键清理**:点“清理 VPS 上的 DSH Mobile”,同样先核对主机指纹,确认后通过 pinned SSH 执行同样的脚本。
|
|
@@ -67,6 +67,8 @@ curl -v https://PUBLIC_HOST/mobile-access/discovery
|
|
|
67
67
|
|
|
68
68
|
本机优先看插件日志(`$DSH_HOME/mobile-access/logs/dsh-mobile.log`,JSONL,超 5 MB 轮转,自动脱敏 Token 与密钥)与面板诊断报告。Windows 杀毒软件可能隔离 frpc;如确有拦截,只为已校验的组件目录设置最小范围例外。
|
|
69
69
|
|
|
70
|
+
一台 frps 被多台电脑共用时:代理名在**整台服务器**内唯一,公网入口端口同样先到先得。插件按本机安装标识注册代理名(`dsh-mobile-<安装标识前 12 位>`),不同电脑之间不会再抢同一个名字;若报「已有本机安装名对应的 FRP 代理在使用中」,说明该名字被另一台电脑占用,或本机上一次连接尚未等到 frps 心跳超时,处理后再重连;若报「入口端口已被占用」,为这台电脑换一个入口端口即可。frpc 遇到这两种冲突时并不会退出,只是入口端口从未发布,修复前面板只会显示「公网端点未能就绪」。
|
|
71
|
+
|
|
70
72
|
## 限制
|
|
71
73
|
|
|
72
74
|
- 中国大陆 VPS 上的未备案域名可能被云厂商拦截,此时改用公网 IPv4 模式。
|
package/lib/client.js
CHANGED
|
@@ -664,6 +664,9 @@ window.__ModuleLoader__.load({
|
|
|
664
664
|
frpVhostProbeFailed: "The VPS address could not be checked. Confirm DNS and retry.",
|
|
665
665
|
frpLaunchFailed: "frpc failed to start.",
|
|
666
666
|
frpTimeout: "The public endpoint did not become ready. Check frps, Caddy, DNS, and firewall.",
|
|
667
|
+
frpProxyNameInUse: "The frps already has a proxy with this installation name in use — usually another DSH computer, or a session that has not expired yet. Disable self-hosted FRP on that machine, or remove that proxy on the frps, then retry.",
|
|
668
|
+
frpRemotePortInUse: "Another proxy on the frps already owns this public entry port. Choose a different entry port and retry.",
|
|
669
|
+
frpProxyStartFailed: "The frps refused frpc's proxy registration. Check its allowPorts, the ports already in use, and its log.",
|
|
667
670
|
frpDiscoveryMismatch: "The public domain points to another DSH computer. Check the Caddy and frps mappings.",
|
|
668
671
|
frpDiscoveryInvalid: "The public domain did not return a valid DSH Mobile discovery response.",
|
|
669
672
|
frpStopped: "The FRP connection stopped.",
|
|
@@ -751,6 +754,9 @@ window.__ModuleLoader__.load({
|
|
|
751
754
|
frpVhostProbeFailed: "Impossibile controllare il VPS. Verifica il DNS e riprova.",
|
|
752
755
|
frpLaunchFailed: "Avvio di frpc non riuscito.",
|
|
753
756
|
frpTimeout: "Endpoint pubblico non pronto. Controlla frps, Caddy, DNS e firewall.",
|
|
757
|
+
frpProxyNameInUse: "Sul frps è già in uso un proxy con il nome di questa installazione — di solito un altro computer DSH o una sessione non ancora scaduta. Disattiva l’FRP autogestito su quella macchina, oppure rimuovi quel proxy sul frps, e riprova.",
|
|
758
|
+
frpRemotePortInUse: "Un altro proxy sul frps occupa già questa porta di ingresso pubblica. Scegli un’altra porta di ingresso e riprova.",
|
|
759
|
+
frpProxyStartFailed: "Il frps ha rifiutato la registrazione del proxy da parte di frpc. Controlla allowPorts, le porte già in uso e il log del server.",
|
|
754
760
|
frpDiscoveryMismatch: "Il dominio pubblico punta a un altro computer DSH. Controlla Caddy e frps.",
|
|
755
761
|
frpDiscoveryInvalid: "Il dominio pubblico non ha restituito una risposta DSH Mobile valida.",
|
|
756
762
|
frpStopped: "Connessione FRP arrestata.",
|
|
@@ -838,6 +844,9 @@ window.__ModuleLoader__.load({
|
|
|
838
844
|
frpVhostProbeFailed: "无法检查 VPS 地址,请确认 DNS 后重试。",
|
|
839
845
|
frpLaunchFailed: "frpc 未能启动。",
|
|
840
846
|
frpTimeout: "公网端点未能就绪,请检查 frps、Caddy、DNS 和防火墙。",
|
|
847
|
+
frpProxyNameInUse: "该 frps 上已有本机安装名对应的 FRP 代理在使用中——通常来自另一台 DSH 电脑,或尚未过期的旧连接。请在那台机器上关闭自建 FRP,或在 frps 上移除该代理后重试。",
|
|
848
|
+
frpRemotePortInUse: "该公网入口端口已被 frps 上的其他代理占用,请改用其他入口端口后重试。",
|
|
849
|
+
frpProxyStartFailed: "frps 拒绝了 frpc 的代理注册,请检查 frps 的 allowPorts、端口占用与服务器日志。",
|
|
841
850
|
frpDiscoveryMismatch: "公开域名连接到了另一台 DSH 电脑,请检查 Caddy 与 frps 映射。",
|
|
842
851
|
frpDiscoveryInvalid: "公开域名没有返回有效的 DSH Mobile 发现信息。",
|
|
843
852
|
frpStopped: "FRP 连接已停止。",
|
|
@@ -2040,13 +2049,39 @@ window.__ModuleLoader__.load({
|
|
|
2040
2049
|
return JSON.stringify(value);
|
|
2041
2050
|
}
|
|
2042
2051
|
/**
|
|
2052
|
+
* Legacy proxy name, kept only as the default of {@link createFrpcToml}.
|
|
2053
|
+
*
|
|
2054
|
+
* A shared frps accepts one proxy per name across *all* its clients, so a fixed
|
|
2055
|
+
* name let any other DSH installation — or a not yet reaped session of this one —
|
|
2056
|
+
* own `dsh-mobile` first. frpc then logged `start error: proxy name [dsh-mobile]
|
|
2057
|
+
* is already in use`, kept running without publishing the entry port, and the
|
|
2058
|
+
* start-up self-check ended in `frp_start_timeout` ("the public endpoint did not
|
|
2059
|
+
* become ready"). Product callers therefore pass an installation-derived name
|
|
2060
|
+
* from {@link frpProxyName}; this constant only keeps the exported API and the
|
|
2061
|
+
* byte-for-byte compatibility test working for legacy callers.
|
|
2062
|
+
*/
|
|
2063
|
+
const FRP_DEFAULT_PROXY_NAME = "dsh-mobile";
|
|
2064
|
+
/**
|
|
2065
|
+
* Validate a proxy name for the generated frpc configuration.
|
|
2066
|
+
*
|
|
2067
|
+
* frps accepts one proxy per name on the whole server, so the name must be a
|
|
2068
|
+
* single safe token: no whitespace, no quotes, no path or bracket characters.
|
|
2069
|
+
*/
|
|
2070
|
+
function validateFrpProxyName(value) {
|
|
2071
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 128 || !/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error("frp_proxy_name_invalid");
|
|
2072
|
+
return value;
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2043
2075
|
* Build the single-purpose frpc configuration for the current loopback gateway.
|
|
2044
2076
|
*
|
|
2045
2077
|
* The default (public-CA) entry is an HTTP vhost behind Caddy and is emitted byte
|
|
2046
2078
|
* for byte as before. The self-signed entry cannot use a vhost at all: frps only
|
|
2047
2079
|
* forwards raw TCP, and the gateway terminates TLS on that connection.
|
|
2080
|
+
*
|
|
2081
|
+
* `proxyName` defaults to the legacy fixed name only for legacy callers; the
|
|
2082
|
+
* provider and the attach preview always pass {@link frpProxyName}.
|
|
2048
2083
|
*/
|
|
2049
|
-
function createFrpcToml(settings, localPort) {
|
|
2084
|
+
function createFrpcToml(settings, localPort, proxyName = FRP_DEFAULT_PROXY_NAME) {
|
|
2050
2085
|
if (!Number.isSafeInteger(localPort) || localPort < 1 || localPort > 65535) throw new Error("frp_local_port_invalid");
|
|
2051
2086
|
const header = [
|
|
2052
2087
|
`serverAddr = ${tomlString(settings.serverAddress)}`,
|
|
@@ -2056,7 +2091,7 @@ window.__ModuleLoader__.load({
|
|
|
2056
2091
|
"transport.tls.enable = true",
|
|
2057
2092
|
"",
|
|
2058
2093
|
"[[proxies]]",
|
|
2059
|
-
|
|
2094
|
+
`name = ${tomlString(validateFrpProxyName(proxyName))}`
|
|
2060
2095
|
];
|
|
2061
2096
|
if (isFrpSelfSignedIngress(settings)) return [
|
|
2062
2097
|
...header,
|
|
@@ -2125,7 +2160,7 @@ window.__ModuleLoader__.load({
|
|
|
2125
2160
|
* the provider writes stays byte-for-byte identical to the upstream output.
|
|
2126
2161
|
*/
|
|
2127
2162
|
function createFrpAttachFrpcToml(settings, options = {}) {
|
|
2128
|
-
const frpcToml = createFrpcToml(settings, attachLocalPort(options));
|
|
2163
|
+
const frpcToml = createFrpcToml(settings, attachLocalPort(options), options.proxyName ?? "dsh-mobile");
|
|
2129
2164
|
return options.revealToken === true ? frpcToml : redactFrpcTomlToken(frpcToml);
|
|
2130
2165
|
}
|
|
2131
2166
|
const DEFAULT_ATTACH_CONFIG_FILE = "<DSH Mobile 私有目录>/remote/frp/config/frpc.toml";
|
|
@@ -4365,6 +4400,9 @@ window.__ModuleLoader__.load({
|
|
|
4365
4400
|
frp_vhost_probe_failed: "frpVhostProbeFailed",
|
|
4366
4401
|
frp_launch_failed: "frpLaunchFailed",
|
|
4367
4402
|
frp_start_timeout: "frpTimeout",
|
|
4403
|
+
frp_proxy_name_in_use: "frpProxyNameInUse",
|
|
4404
|
+
frp_remote_port_in_use: "frpRemotePortInUse",
|
|
4405
|
+
frp_proxy_start_failed: "frpProxyStartFailed",
|
|
4368
4406
|
frp_discovery_mismatch: "frpDiscoveryMismatch",
|
|
4369
4407
|
frp_discovery_invalid: "frpDiscoveryInvalid",
|
|
4370
4408
|
frp_stopped: "frpStopped",
|
|
@@ -4426,6 +4464,9 @@ window.__ModuleLoader__.load({
|
|
|
4426
4464
|
"frp_vhost_probe_failed",
|
|
4427
4465
|
"frp_launch_failed",
|
|
4428
4466
|
"frp_start_timeout",
|
|
4467
|
+
"frp_proxy_name_in_use",
|
|
4468
|
+
"frp_remote_port_in_use",
|
|
4469
|
+
"frp_proxy_start_failed",
|
|
4429
4470
|
"frp_discovery_mismatch",
|
|
4430
4471
|
"frp_discovery_invalid",
|
|
4431
4472
|
"frp_stopped",
|
|
@@ -4972,10 +5013,12 @@ window.__ModuleLoader__.load({
|
|
|
4972
5013
|
frpCopyAttachPlan.type = "button";
|
|
4973
5014
|
frpCopyAttachPlan.textContent = t("frpAttachPlan");
|
|
4974
5015
|
frpCopyAttachPlan.hidden = true;
|
|
5016
|
+
frpCopyAttachPlan.disabled = true;
|
|
4975
5017
|
const frpCopyAttachToken = element("button", "dsh-mobile-control__secondary dsh-mobile-control__frp-action");
|
|
4976
5018
|
frpCopyAttachToken.type = "button";
|
|
4977
5019
|
frpCopyAttachToken.textContent = t("frpAttachToken");
|
|
4978
5020
|
frpCopyAttachToken.hidden = true;
|
|
5021
|
+
frpCopyAttachToken.disabled = true;
|
|
4979
5022
|
const frpSelfCheckButton = element("button", "dsh-mobile-control__secondary dsh-mobile-control__frp-action");
|
|
4980
5023
|
frpSelfCheckButton.type = "button";
|
|
4981
5024
|
frpSelfCheckButton.textContent = t("frpAttachSelfCheck");
|
|
@@ -5555,6 +5598,8 @@ window.__ModuleLoader__.load({
|
|
|
5555
5598
|
let configuredFrpOrigin = "";
|
|
5556
5599
|
let configuredFrpVhostPort = 7080;
|
|
5557
5600
|
let configuredFrpPublicPort = 33080;
|
|
5601
|
+
/** Proxy name the host writes into its frpc.toml; the local clipboard copy must match. */
|
|
5602
|
+
let configuredFrpProxyName;
|
|
5558
5603
|
let frpConfiguredMode = "deploy";
|
|
5559
5604
|
let frpConfiguredEntryTls = "public-ip-cert";
|
|
5560
5605
|
let frpModeDraft = false;
|
|
@@ -5985,6 +6030,7 @@ window.__ModuleLoader__.load({
|
|
|
5985
6030
|
if (frpConfigured && !frpEntryTlsDraft) frpEntryTls.value = configuredFrpEntryTls;
|
|
5986
6031
|
if (typeof frpConfiguration.vhostHttpPort === "number") configuredFrpVhostPort = frpConfiguration.vhostHttpPort;
|
|
5987
6032
|
if (typeof frpConfiguration.publicPort === "number") configuredFrpPublicPort = frpConfiguration.publicPort;
|
|
6033
|
+
configuredFrpProxyName = typeof frpConfiguration.proxyName === "string" && frpConfiguration.proxyName !== "" ? frpConfiguration.proxyName : void 0;
|
|
5988
6034
|
if (!frpVhostDraft) {
|
|
5989
6035
|
if (frpMode.value === "attach" && configuredFrpMode === "attach" && configuredFrpEntryTls === "public-ip-cert") {
|
|
5990
6036
|
frpVhostPort.value = String(configuredFrpVhostPort);
|
|
@@ -5995,12 +6041,14 @@ window.__ModuleLoader__.load({
|
|
|
5995
6041
|
const attachSelected = frpMode.value === "attach";
|
|
5996
6042
|
const selfSignedSelected = attachSelected && frpEntryTls.value === "self-signed";
|
|
5997
6043
|
frpStep2Title.textContent = t(attachSelected ? "frpStep2TitleAttach" : "frpStep2Title");
|
|
5998
|
-
frpStep2Text.textContent = t(attachSelected ? "frpStep2TextAttach" : "frpStep2Text");
|
|
6044
|
+
frpStep2Text.textContent = attachSelected && configuredFrpProxyName === void 0 ? t("loadingRemoteStatus") : t(attachSelected ? "frpStep2TextAttach" : "frpStep2Text");
|
|
5999
6045
|
frpStep4Text.textContent = t(attachSelected ? "frpStep4TextAttach" : "frpStep4Text");
|
|
6000
6046
|
frpAppRequirement.textContent = t(selfSignedSelected ? "frpAppRequirementSelfSigned" : "frpAppRequirement");
|
|
6001
6047
|
frpCopyTemplate.hidden = attachSelected;
|
|
6002
6048
|
frpCopyAttachPlan.hidden = !attachSelected;
|
|
6003
6049
|
frpCopyAttachToken.hidden = !attachSelected;
|
|
6050
|
+
frpCopyAttachPlan.disabled = remoteProviderBusy || configuredFrpProxyName === void 0;
|
|
6051
|
+
frpCopyAttachToken.disabled = remoteProviderBusy || configuredFrpProxyName === void 0;
|
|
6004
6052
|
frpSelfCheckButton.hidden = !frpConfigured;
|
|
6005
6053
|
frpVhostPort.disabled = selfSignedSelected;
|
|
6006
6054
|
frpVhostLabel.hidden = selfSignedSelected;
|
|
@@ -6478,6 +6526,13 @@ window.__ModuleLoader__.load({
|
|
|
6478
6526
|
}
|
|
6479
6527
|
remoteStatus.textContent = message;
|
|
6480
6528
|
};
|
|
6529
|
+
const frpClipboardProxyName = () => {
|
|
6530
|
+
if (configuredFrpProxyName === void 0) {
|
|
6531
|
+
remoteStatus.textContent = t("loadingRemoteStatus");
|
|
6532
|
+
loadRemote();
|
|
6533
|
+
}
|
|
6534
|
+
return configuredFrpProxyName;
|
|
6535
|
+
};
|
|
6481
6536
|
frpCopyAttachPlan.addEventListener("click", () => {
|
|
6482
6537
|
const form = frpForm();
|
|
6483
6538
|
if (!validFrpServer(form.serverAddress) || !Number.isSafeInteger(form.serverPort) || form.serverPort < 1 || form.serverPort > 65535 || form.publicOrigin === "") {
|
|
@@ -6491,7 +6546,9 @@ window.__ModuleLoader__.load({
|
|
|
6491
6546
|
showFrpAttachError(code);
|
|
6492
6547
|
return;
|
|
6493
6548
|
}
|
|
6494
|
-
|
|
6549
|
+
const proxyName = frpClipboardProxyName();
|
|
6550
|
+
if (proxyName === void 0) return;
|
|
6551
|
+
navigator.clipboard.writeText(createFrpAttachTemplateForClipboard(attachForm, { proxyName })).then(() => {
|
|
6495
6552
|
remoteStatus.textContent = t("frpAttachPlanCopied");
|
|
6496
6553
|
}, () => {
|
|
6497
6554
|
remoteStatus.textContent = t("frpAttachPlanFailed", { error: t("templateCopyFailed") });
|
|
@@ -6519,7 +6576,7 @@ window.__ModuleLoader__.load({
|
|
|
6519
6576
|
remoteStatus.textContent = t("frpAttachPlanFailed", { error: String(error) });
|
|
6520
6577
|
}).finally(() => {
|
|
6521
6578
|
remoteProviderBusy = false;
|
|
6522
|
-
frpCopyAttachPlan.disabled =
|
|
6579
|
+
frpCopyAttachPlan.disabled = configuredFrpProxyName === void 0;
|
|
6523
6580
|
});
|
|
6524
6581
|
});
|
|
6525
6582
|
/** Copy a token-bearing frpc.toml only from the token the user just typed. */
|
|
@@ -6536,7 +6593,12 @@ window.__ModuleLoader__.load({
|
|
|
6536
6593
|
showFrpAttachError(code);
|
|
6537
6594
|
return;
|
|
6538
6595
|
}
|
|
6539
|
-
|
|
6596
|
+
const proxyName = frpClipboardProxyName();
|
|
6597
|
+
if (proxyName === void 0) return;
|
|
6598
|
+
navigator.clipboard.writeText(createFrpAttachFrpcTomlForClipboard(attachForm, {
|
|
6599
|
+
revealToken: true,
|
|
6600
|
+
proxyName
|
|
6601
|
+
})).then(() => {
|
|
6540
6602
|
remoteStatus.textContent = t("frpAttachTokenCopied");
|
|
6541
6603
|
}, () => {
|
|
6542
6604
|
remoteStatus.textContent = t("frpAttachTokenFailed", { error: t("templateCopyFailed") });
|