dsh-remote-hub 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +1 -1
- package/lib/pairing-routes.js +13 -9
- package/lib/supervisor.js +16 -16
- package/lib/tunnel.js +1 -10
- package/package.json +1 -1
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.
|
|
19
|
+
export const version = '0.4.0'
|
|
20
20
|
|
|
21
21
|
/** No host services are required: the plugin only needs Node builtins. */
|
|
22
22
|
export const inject = []
|
package/lib/pairing-routes.js
CHANGED
|
@@ -22,11 +22,13 @@ export const PAIR_PREFIX = '/_dsh-remote/pair'
|
|
|
22
22
|
* fastest one at connect time: `l` = LAN (private address), `u` = public tunnel. Either may be null
|
|
23
23
|
* when that transport is unavailable that moment.
|
|
24
24
|
*/
|
|
25
|
-
export function buildPairPayload({ lan,
|
|
26
|
-
//
|
|
27
|
-
//
|
|
25
|
+
export function buildPairPayload({ lan, relay, tunnel, code, expiresAt }) {
|
|
26
|
+
// v3 ships all three transports so one scan can fill every tab:
|
|
27
|
+
// l = LAN addresses (list; only one adapter may be reachable from the phone)
|
|
28
|
+
// r = self-hosted relay (Aliyun/Caddy + reverse SSH)
|
|
29
|
+
// u = auto tunnel (ngrok / cloudflared)
|
|
28
30
|
const lanList = Array.isArray(lan) ? lan.filter(Boolean) : lan ? [lan] : []
|
|
29
|
-
return JSON.stringify({ v:
|
|
31
|
+
return JSON.stringify({ v: 3, l: lanList, r: relay ?? null, u: tunnel ?? null, c: code, e: expiresAt })
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -76,10 +78,12 @@ export async function handlePairing({ req, res, current, store }) {
|
|
|
76
78
|
res.end(
|
|
77
79
|
JSON.stringify({
|
|
78
80
|
ok: true,
|
|
79
|
-
// `url`
|
|
80
|
-
url: snapshot.
|
|
81
|
+
// `url`/`public` keep the old single-address shape working; new clients read the three.
|
|
82
|
+
url: snapshot.relay ?? snapshot.tunnel ?? null,
|
|
81
83
|
lan: Array.isArray(snapshot.lan) ? snapshot.lan : snapshot.lan ? [snapshot.lan] : [],
|
|
82
|
-
|
|
84
|
+
relay: snapshot.relay ?? null,
|
|
85
|
+
tunnel: snapshot.tunnel ?? null,
|
|
86
|
+
public: snapshot.relay ?? snapshot.tunnel ?? null,
|
|
83
87
|
cookie: result.cookie,
|
|
84
88
|
issuedAt: result.issuedAt,
|
|
85
89
|
}),
|
|
@@ -89,7 +93,7 @@ export async function handlePairing({ req, res, current, store }) {
|
|
|
89
93
|
|
|
90
94
|
// Any other GET under the prefix hands out a fresh code.
|
|
91
95
|
const { code, expiresAt, ttlMs } = store.issue(snapshot.cookie)
|
|
92
|
-
const payload = buildPairPayload({ lan: snapshot.lan,
|
|
96
|
+
const payload = buildPairPayload({ lan: snapshot.lan, relay: snapshot.relay, tunnel: snapshot.tunnel, code, expiresAt })
|
|
93
97
|
|
|
94
98
|
if (url === PAIR_PREFIX + '/svg') {
|
|
95
99
|
const svg = await QRCode.toString(payload, { type: 'svg', margin: 1, width: 320, errorCorrectionLevel: 'M' })
|
|
@@ -122,7 +126,7 @@ export async function handlePairing({ req, res, current, store }) {
|
|
|
122
126
|
<p>配对码 <code>${code}</code> · ${minutes} 分钟内有效,且只能用一次</p>
|
|
123
127
|
<div class="qr">${svg}</div>
|
|
124
128
|
<p class="ok">扫码后自动填入地址并换取登录凭据</p>
|
|
125
|
-
<p class="addr">${(Array.isArray(snapshot.lan) ? snapshot.lan : snapshot.lan ? [snapshot.lan] : []).map((u) => `局域网:${u}`).join('<br>')}${snapshot.
|
|
129
|
+
<p class="addr">${(Array.isArray(snapshot.lan) ? snapshot.lan : snapshot.lan ? [snapshot.lan] : []).map((u) => `局域网:${u}`).join('<br>')}${snapshot.relay ? `<br>中转:${snapshot.relay}` : ''}${snapshot.tunnel ? `<br>隧道:${snapshot.tunnel}` : ''}</p>
|
|
126
130
|
<p class="addr">扫一次会同时存下局域网与公网地址,之后自动优先走局域网</p>
|
|
127
131
|
</div></body></html>`
|
|
128
132
|
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' })
|
package/lib/supervisor.js
CHANGED
|
@@ -284,7 +284,8 @@ export async function start(config = {}) {
|
|
|
284
284
|
// Read at redeem time so a tunnel URL that appears later is still handed to the phone.
|
|
285
285
|
current: () => ({
|
|
286
286
|
lan: lanUrlsFor(actualBridgePort()),
|
|
287
|
-
|
|
287
|
+
relay: relayUrl,
|
|
288
|
+
tunnel: tunnelRef?.publicUrl ?? null,
|
|
288
289
|
cookie,
|
|
289
290
|
}),
|
|
290
291
|
}
|
|
@@ -385,7 +386,6 @@ export async function start(config = {}) {
|
|
|
385
386
|
|
|
386
387
|
const tunnel = await startTunnel({
|
|
387
388
|
kind: tunnelKind,
|
|
388
|
-
publicUrl,
|
|
389
389
|
bridgePort: bridge.port,
|
|
390
390
|
authority,
|
|
391
391
|
domain,
|
|
@@ -405,14 +405,15 @@ export async function start(config = {}) {
|
|
|
405
405
|
},
|
|
406
406
|
})
|
|
407
407
|
|
|
408
|
-
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
+
lastPublicUrl = relayUrl ?? tunnel.publicUrl
|
|
412
|
+
let relayReachable = null
|
|
413
|
+
if (relayUrl) {
|
|
414
|
+
relayReachable = await probeHealthUrl(`${relayUrl}/_dsh-remote/health`)
|
|
415
|
+
if (relayReachable === false) {
|
|
416
|
+
log(`dsh-remote: WARNING ${relayUrl} does not answer — is the relay's SSH tunnel up?`)
|
|
416
417
|
}
|
|
417
418
|
}
|
|
418
419
|
const ip = ipForPairing
|
|
@@ -423,12 +424,11 @@ export async function start(config = {}) {
|
|
|
423
424
|
authority,
|
|
424
425
|
upstreamPort: discovery.port || null,
|
|
425
426
|
lan: { ip, url: ip ? `http://${ip}:${bridge.port}` : null },
|
|
426
|
-
public:
|
|
427
|
-
url:
|
|
428
|
-
kind: tunnel.kind,
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
},
|
|
427
|
+
public: relayUrl
|
|
428
|
+
? { url: relayUrl, kind: 'relay', ...(relayReachable === null ? {} : { reachable: relayReachable }) }
|
|
429
|
+
: { url: tunnel.publicUrl, kind: tunnel.kind, ...(tunnel.note ? { note: tunnel.note } : {}) },
|
|
430
|
+
relay: relayUrl ? { url: relayUrl, ...(relayReachable === null ? {} : { reachable: relayReachable }) } : null,
|
|
431
|
+
tunnel: tunnel.publicUrl ? { url: tunnel.publicUrl, kind: tunnel.kind, ...(tunnel.note ? { note: tunnel.note } : {}) } : null,
|
|
432
432
|
cookie,
|
|
433
433
|
cookieExpiresAt: expiresAt ?? cookieExpiry(cookie),
|
|
434
434
|
pairing: {
|
package/lib/tunnel.js
CHANGED
|
@@ -148,16 +148,7 @@ export async function ensureCloudflared({ log = () => {} } = {}) {
|
|
|
148
148
|
* @param {(msg: string) => void} [options.log]
|
|
149
149
|
* @returns {Promise<{publicUrl: string|null, kind: string, close: () => void, note?: string}>}
|
|
150
150
|
*/
|
|
151
|
-
export async function startTunnel({ kind = 'auto', bridgePort, authority, domain = '', publicUrl = '', log = () => {} }) {
|
|
152
|
-
// An explicitly configured public URL (a self-hosted relay, e.g. the Aliyun box) wins over any
|
|
153
|
-
// detected tunnel: for that setup there is nothing to launch here, the relay is kept alive by its
|
|
154
|
-
// own reverse SSH tunnel.
|
|
155
|
-
if (publicUrl) {
|
|
156
|
-
const trimmed = String(publicUrl).trim().replace(/\/+$/, '')
|
|
157
|
-
log(`using the configured public URL: ${trimmed}`)
|
|
158
|
-
return { publicUrl: trimmed, kind: 'relay', close: () => {} }
|
|
159
|
-
}
|
|
160
|
-
if (kind === 'off') return { publicUrl: null, kind: 'none', close: () => {}, note: 'tunnel disabled by config' }
|
|
151
|
+
export async function startTunnel({ kind = 'auto', bridgePort, authority, domain = '', publicUrl = '', log = () => {} }) { if (kind === 'off') return { publicUrl: null, kind: 'none', close: () => {}, note: 'tunnel disabled by config' }
|
|
161
152
|
|
|
162
153
|
const wantNgrok = kind === 'auto' || kind === 'ngrok'
|
|
163
154
|
const wantCloudflared = kind === 'auto' || kind === 'cloudflared'
|
package/package.json
CHANGED