dsh-remote-hub 0.4.0 → 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/README.md CHANGED
@@ -19,16 +19,16 @@ Kotlin/Compose 直连 DSH 自带的 HTTP API + WebSocket 网关。
19
19
 
20
20
  ## 下载手机端 App
21
21
 
22
- **DSH Remote v4.21.0** · Android 8.0 (API 26) 及以上 · 2.8 MB
22
+ **DSH Remote v4.21.2** · Android 8.0 (API 26) 及以上 · 2.8 MB
23
23
 
24
- [⬇️ 下载 APK](https://github.com/liaoguogang-hub/dsh-remote-public/releases/download/v4.21.0/DSH-Remote-v4.21.0.apk) ·
24
+ [⬇️ 下载 APK](https://github.com/liaoguogang-hub/dsh-remote-public/releases/download/v4.21.2/DSH-Remote-v4.21.2.apk) ·
25
25
  [全部版本](https://github.com/liaoguogang-hub/dsh-remote-public/releases)
26
26
 
27
27
  安装后应用名「DSH Remote」,包名 `com.dsh.remote.ngrok`(可与作者的 `DSH` 自用版共存)。
28
28
  手机端不通过 npm 分发——npm 包 `dsh-remote-hub` 是电脑侧的 DSH 插件,两者配合使用:
29
29
 
30
30
  ```
31
- 电脑:dsh plugin --profile desktop add dsh-remote-hub → 起桥接 / 开隧道 / 签 Cookie
31
+ 电脑:dsh plugin --profile desktop add dsh-remote-hub@0.4.2 → 起桥接 / 开隧道 / 签 Cookie
32
32
  手机:装上面这个 APK → 填电脑给出的地址与 Cookie → 连接
33
33
  ```
34
34
 
@@ -184,8 +184,8 @@ node mint.mjs "127.0.0.1:1815" 30 # 签发 30 天 Cookie
184
184
  ```bash
185
185
  # 从 NPM 安装(推荐,会出现在 DSH 内的「插件中心」)
186
186
  # https://www.npmjs.com/package/dsh-remote-hub
187
- dsh plugin --profile desktop add dsh-remote-hub
188
- dsh plugin --profile web add dsh-remote-hub # web profile 同样可用
187
+ dsh plugin --profile desktop add dsh-remote-hub@0.4.2
188
+ dsh plugin --profile web add dsh-remote-hub@0.4.2 # web profile 同样可用
189
189
 
190
190
  # 或直接从本仓库源码安装(不经过 NPM)
191
191
  dsh plugin --profile web add github:liaoguogang-hub/dsh-remote-public
@@ -222,6 +222,10 @@ dsh-remote start # 不开 DSH 也能单独前台跑(Ctrl+C 退出)
222
222
  > 公网隧道受网络影响:`api.trycloudflare.com` 不可达时插件会跳过公网、只给局域网地址,
223
223
  > 并在 `status.json` 里写明原因,不会假装成功。
224
224
 
225
+ > **安装时必须带版本号**(如 `@0.4.1`)。pnpm 11 默认开启 `minimumReleaseAge` 安全策略,
226
+ > 新发布的版本在一段时间内不会被 `@latest` 解析到,会**回退安装到很旧的版本**(0.1.0 没有扫码配对,
227
+ > 表现就是「扫码页打不开」)。显式写版本号可绕过该策略。
228
+
225
229
  ### 装完之后
226
230
 
227
231
  插件随 DSH 启动自动工作,**不需要手动跑 connect.cmd**;装的时候没有提示是正常的,日志里会出现:
@@ -238,7 +242,7 @@ dsh-remote: ready — 手机端扫码:dsh-remote qr(或浏览器打开 http:
238
242
  ### 升级插件(重要:必须完全重启 DSH)
239
243
 
240
244
  ```bash
241
- dsh plugin --profile desktop add dsh-remote-hub # 或 --profile web
245
+ dsh plugin --profile desktop add dsh-remote-hub@0.4.2 # 或 --profile web
242
246
  ```
243
247
 
244
248
  **升级后请完全退出 DSH 再重新打开,不要只靠插件热重载。** 原因是旧版本的桥接监听可能不会被热重载回收,
@@ -313,3 +317,7 @@ dsh-remote qr
313
317
  | GET | `/_dsh-remote/pair` | 配对页(二维码 HTML) |
314
318
  | GET | `/_dsh-remote/pair.svg` | 只要二维码 SVG |
315
319
  | POST | `/_dsh-remote/pair/redeem` | `{code}` → `{url, cookie}`;用过即失效 |
320
+
321
+ ---
322
+
323
+ 发版流程(版本号约定、tag 命名、发布 checklist、踩过的坑):见 [RELEASING.md](RELEASING.md)
@@ -5,7 +5,7 @@
5
5
  * dsh-remote status show LAN URL, public URL, cookie and upstream port
6
6
  * dsh-remote connect print the two lines to paste into the phone
7
7
  * dsh-remote cookie print only the cookie
8
- * dsh-remote start [--open] [--public-url=URL] run the supervisor; --open 打印地址并打开配对页
8
+ * dsh-remote start [--open] [--restart] [--public-url=URL] --restart 会先停掉配置不同的旧桥接
9
9
  * dsh-remote stop ask a foreground `start` to stop (best effort, via status file)
10
10
  * dsh-remote doctor check every prerequisite in one pass
11
11
  * dsh-remote qr open the pairing page (QR) in the default browser
@@ -23,6 +23,7 @@ import { credentialsPath, mintCookie, readSigningSecret } from '../lib/cookie.js
23
23
  import { listeningPorts, probePort } from '../lib/discover.js'
24
24
  import { findCloudflared, findNgrok } from '../lib/tunnel.js'
25
25
  import { CONNECT_FILE, STATUS_FILE, readStatus, start } from '../lib/supervisor.js'
26
+ import { version as VERSION } from '../lib/index.js'
26
27
 
27
28
  const args = process.argv.slice(2)
28
29
  const command = args[0] || 'status'
@@ -42,6 +43,7 @@ function printStatus() {
42
43
  const remaining = status.cookieExpiresAt ? Math.round((status.cookieExpiresAt - Date.now()) / 86400000) : null
43
44
  console.log('')
44
45
  console.log(' DSH Remote 状态')
46
+ console.log(` 插件版本 v${VERSION}`)
45
47
  console.log(' ' + '-'.repeat(52))
46
48
  console.log(` 局域网地址 ${status.lan?.url ?? '(未检测到局域网 IP)'}`)
47
49
  console.log(` 公网地址 ${status.public?.url ?? `无(${status.public?.note ?? '未开启'})`}`)
@@ -58,6 +60,7 @@ function printStatus() {
58
60
  } else if (status.error) {
59
61
  console.log(` 错误 ${status.error}`)
60
62
  }
63
+ console.log(` ✔ 插件版本 v${VERSION}`)
61
64
  console.log(' ' + '-'.repeat(52))
62
65
  console.log('')
63
66
  }
@@ -260,9 +263,26 @@ async function main() {
260
263
  break
261
264
  case 'start': {
262
265
  const wantOpen = process.argv.includes('--open')
266
+ const wantRestart = process.argv.includes('--restart')
263
267
  // --public-url=https://relay.example advertises a self-hosted relay in the QR payload.
264
268
  const publicArg = process.argv.find((a) => a.startsWith('--public-url='))
265
269
  const publicUrl = publicArg ? publicArg.slice('--public-url='.length) : process.env.DSH_REMOTE_PUBLIC_URL || ''
270
+ // A launcher that carries its own transport config must not silently adopt a bridge started
271
+ // with different config (that is how a public launcher ended up advertising a self-use relay).
272
+ if (wantRestart) {
273
+ const live = await bridgeHealthy(Number(process.env.DSH_REMOTE_PORT || 6300))
274
+ const mismatch = live && (live.relay || null) !== (publicUrl || null)
275
+ if (mismatch && live.pid) {
276
+ console.log(`已有桥接配置不同(relay=${live.relay ?? '无'}),先停止它(PID ${live.pid})再按当前配置启动`)
277
+ try {
278
+ process.kill(Number(live.pid))
279
+ } catch (err) {
280
+ console.log(`停止失败:${err.message}`)
281
+ }
282
+ await new Promise((r) => setTimeout(r, 1200))
283
+ }
284
+ }
285
+
266
286
  let handle
267
287
  try {
268
288
  handle = await start({ publicUrl, log: (m) => console.log(m) })
package/lib/bridge.js CHANGED
@@ -68,7 +68,7 @@ function trimSessionList(body) {
68
68
  * @param {(msg: string) => void} [options.log]
69
69
  * @returns {Promise<{close: () => Promise<void>, port: number}>}
70
70
  */
71
- export function startBridge({ listenPort, authority, upstream, pairing = null, log = () => {} }) {
71
+ export function startBridge({ listenPort, authority, upstream, pairing = null, relayUrl = null, log = () => {} }) {
72
72
  const forward = (req, res, attempt = 0) =>
73
73
  new Promise((resolve) => {
74
74
  upstream.resolve(attempt > 0).then((port) => {
@@ -155,6 +155,10 @@ export function startBridge({ listenPort, authority, upstream, pairing = null, l
155
155
  const body = JSON.stringify({
156
156
  ok: true,
157
157
  service: 'dsh-remote-bridge',
158
+ pid: process.pid,
159
+ // Whether this bridge advertises a self-hosted relay; a launcher that expects none must not
160
+ // adopt a bridge that has one, or the public app would show the self-use relay.
161
+ relay: relayUrl || null,
158
162
  // A bridge built before pairing existed has no such route; callers use this flag to decide
159
163
  // whether an already-listening port can be adopted or must be bypassed.
160
164
  pairing: pairing != null,
package/lib/index.js CHANGED
@@ -16,7 +16,7 @@ import { start } from './supervisor.js'
16
16
 
17
17
  export const name = 'dsh-remote-hub'
18
18
 
19
- export const version = '0.4.0'
19
+ export const version = '0.4.2'
20
20
 
21
21
  /** No host services are required: the plugin only needs Node builtins. */
22
22
  export const inject = []
@@ -56,7 +56,7 @@ export function apply(ctx, config = {}) {
56
56
  // Make the next step discoverable from the log: after installing the plugin there is no
57
57
  // other place telling the user how to get the phone connected.
58
58
  log(
59
- `dsh-remote: ready — 手机端扫码:dsh-remote qr(或浏览器打开 http://127.0.0.1:${started.status.bridgePort}/_dsh-remote/pair);` +
59
+ `dsh-remote: ready (v${version}) — 手机端扫码:dsh-remote qr(或浏览器打开 http://127.0.0.1:${started.status.bridgePort}/_dsh-remote/pair);` +
60
60
  '不想装插件时也可以直接双击仓库里的 connect.cmd',
61
61
  )
62
62
  }
package/lib/supervisor.js CHANGED
@@ -274,6 +274,10 @@ export async function start(config = {}) {
274
274
  const lanUrlsFor = (bridgePort) =>
275
275
  lanAddresses().map((address) => `http://${address}:${bridgePort}`)
276
276
 
277
+ // The self-hosted relay (if configured) and the auto tunnel are reported SEPARATELY: a self-use
278
+ // setup wants both (relay for 4G, ngrok as a second path), and the phone decides which to use.
279
+ const relayUrl = String(publicUrl || '').trim().replace(/\/+$/, '') || null
280
+
277
281
  let bridgeInstance = null
278
282
  const actualBridgePort = () => (bridgeInstance ? bridgeInstance.port : Number(port) || 6300)
279
283
 
@@ -312,6 +316,7 @@ export async function start(config = {}) {
312
316
  authority,
313
317
  upstream: discovery,
314
318
  pairing,
319
+ relayUrl,
315
320
  log,
316
321
  })
317
322
  } catch (err) {
@@ -337,7 +342,13 @@ export async function start(config = {}) {
337
342
  authority: existing.authority ?? authority,
338
343
  upstreamPort: existing.upstreamPort ?? null,
339
344
  lan: { ip, url: ip ? `http://${ip}:${livePort}` : null },
340
- public: prev.public ?? { url: null, kind: 'none', note: 'served by an existing bridge' },
345
+ // Never inherit the previous run's public address: it may describe a different transport
346
+ // (e.g. a self-use relay) than the launcher that is running now.
347
+ public: existing.relay
348
+ ? { url: existing.relay, kind: 'relay' }
349
+ : { url: null, kind: 'none', note: 'served by an existing bridge' },
350
+ relay: existing.relay ? { url: existing.relay } : null,
351
+ tunnel: null,
341
352
  cookie: prev.cookie ?? null,
342
353
  cookieExpiresAt: prev.cookieExpiresAt ?? null,
343
354
  pairing: { url: prev.public?.url ?? (ip ? `http://${ip}:${livePort}` : null), page: '/_dsh-remote/pair' },
@@ -405,9 +416,6 @@ export async function start(config = {}) {
405
416
  },
406
417
  })
407
418
 
408
- // The self-hosted relay (if configured) and the auto tunnel are reported SEPARATELY: a self-use
409
- // setup wants both (relay for 4G, ngrok as a second path), and the phone decides which to use.
410
- const relayUrl = String(publicUrl || '').trim().replace(/\/+$/, '') || null
411
419
  lastPublicUrl = relayUrl ?? tunnel.publicUrl
412
420
  let relayReachable = null
413
421
  if (relayUrl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-hub",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "DSH 插件:把电脑上的 DeepSeek Harness 变成手机可访问的服务——起桥接、自动开隧道(ngrok / cloudflared)、签发 30 天 Cookie,并输出地址与凭据,配合 DSH Remote Android App 使用。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",