dsh-remote-hub 0.4.2 → 0.4.3
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 +6 -4
- package/bin/dsh-remote.mjs +12 -0
- package/lib/bridge.js +3 -0
- package/lib/discover.js +67 -8
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Kotlin/Compose 直连 DSH 自带的 HTTP API + WebSocket 网关。
|
|
|
28
28
|
手机端不通过 npm 分发——npm 包 `dsh-remote-hub` 是电脑侧的 DSH 插件,两者配合使用:
|
|
29
29
|
|
|
30
30
|
```
|
|
31
|
-
电脑:dsh plugin --profile desktop add dsh-remote-hub@0.4.
|
|
31
|
+
电脑:dsh plugin --profile desktop add dsh-remote-hub@0.4.3 --registry=https://registry.npmjs.org → 起桥接 / 开隧道 / 签 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@0.4.
|
|
188
|
-
dsh plugin --profile web add dsh-remote-hub@0.4.
|
|
187
|
+
dsh plugin --profile desktop add dsh-remote-hub@0.4.3 --registry=https://registry.npmjs.org
|
|
188
|
+
dsh plugin --profile web add dsh-remote-hub@0.4.3 --registry=https://registry.npmjs.org
|
|
189
189
|
|
|
190
190
|
# 或直接从本仓库源码安装(不经过 NPM)
|
|
191
191
|
dsh plugin --profile web add github:liaoguogang-hub/dsh-remote-public
|
|
@@ -242,7 +242,9 @@ dsh-remote: ready — 手机端扫码:dsh-remote qr(或浏览器打开 http:
|
|
|
242
242
|
### 升级插件(重要:必须完全重启 DSH)
|
|
243
243
|
|
|
244
244
|
```bash
|
|
245
|
-
dsh plugin --profile desktop add dsh-remote-hub@0.4.
|
|
245
|
+
dsh plugin --profile desktop add dsh-remote-hub@0.4.3 --registry=https://registry.npmjs.org
|
|
246
|
+
# web profile 把 desktop 换成 web
|
|
247
|
+
|
|
246
248
|
```
|
|
247
249
|
|
|
248
250
|
**升级后请完全退出 DSH 再重新打开,不要只靠插件热重载。** 原因是旧版本的桥接监听可能不会被热重载回收,
|
package/bin/dsh-remote.mjs
CHANGED
|
@@ -273,6 +273,18 @@ async function main() {
|
|
|
273
273
|
const live = await bridgeHealthy(Number(process.env.DSH_REMOTE_PORT || 6300))
|
|
274
274
|
const mismatch = live && (live.relay || null) !== (publicUrl || null)
|
|
275
275
|
if (mismatch && live.pid) {
|
|
276
|
+
// NEVER signal a bridge that lives inside the desktop app: health.pid is then the DSH
|
|
277
|
+
// host process, and killing it takes the whole app down (this actually happened).
|
|
278
|
+
if (live.runtime === 'electron') {
|
|
279
|
+
console.log('')
|
|
280
|
+
console.log(' 桥接由 DSH 桌面端内部托管(PID ' + live.pid + ' 就是 DSH 本体),不能在这里停止。')
|
|
281
|
+
console.log(' 它的公网配置来自 profile 的 cordis.patch.yml:')
|
|
282
|
+
console.log(` 当前 relay = ${live.relay ?? '(未配置)'},本次启动想要 ${publicUrl || '(不用中转)'}`)
|
|
283
|
+
console.log(' 要改配置:编辑 cordis.patch.yml 里的 publicUrl 后重启 DSH;')
|
|
284
|
+
console.log(' 要临时用本启动器的配置:先完全退出 DSH,再双击 connect.cmd。')
|
|
285
|
+
console.log('')
|
|
286
|
+
return
|
|
287
|
+
}
|
|
276
288
|
console.log(`已有桥接配置不同(relay=${live.relay ?? '无'}),先停止它(PID ${live.pid})再按当前配置启动`)
|
|
277
289
|
try {
|
|
278
290
|
process.kill(Number(live.pid))
|
package/lib/bridge.js
CHANGED
|
@@ -156,6 +156,9 @@ export function startBridge({ listenPort, authority, upstream, pairing = null, r
|
|
|
156
156
|
ok: true,
|
|
157
157
|
service: 'dsh-remote-bridge',
|
|
158
158
|
pid: process.pid,
|
|
159
|
+
// 'electron' when the bridge lives inside the DSH desktop host (plugin mode). Callers must
|
|
160
|
+
// NEVER signal that pid: it is the app itself, not a disposable node helper.
|
|
161
|
+
runtime: process.versions.electron ? 'electron' : 'node',
|
|
159
162
|
// Whether this bridge advertises a self-hosted relay; a launcher that expects none must not
|
|
160
163
|
// adopt a bridge that has one, or the public app would show the self-use relay.
|
|
161
164
|
relay: relayUrl || null,
|
package/lib/discover.js
CHANGED
|
@@ -108,6 +108,57 @@ export function probePort(port) {
|
|
|
108
108
|
})
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/** Map local listening port -> owning PID (Windows only). */
|
|
112
|
+
async function portOwners() {
|
|
113
|
+
if (process.platform !== 'win32') return new Map()
|
|
114
|
+
const out = await run('netstat', ['-ano', '-p', 'tcp'])
|
|
115
|
+
const map = new Map()
|
|
116
|
+
for (const line of splitLines(out)) {
|
|
117
|
+
const m = /^\s*TCP\s+\S+:(\d+)\s+\S+\s+LISTENING\s+(\d+)/i.exec(line)
|
|
118
|
+
if (m) map.set(Number(m[1]), Number(m[2]))
|
|
119
|
+
}
|
|
120
|
+
return map
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Process name for a PID, or '' when unknown (uses tasklist; cheap enough at discovery time). */
|
|
124
|
+
const NAME_CACHE = new Map()
|
|
125
|
+
async function processName(pid) {
|
|
126
|
+
if (process.platform !== 'win32') return ''
|
|
127
|
+
if (NAME_CACHE.has(pid)) return NAME_CACHE.get(pid)
|
|
128
|
+
const out = await run('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'])
|
|
129
|
+
const line = splitLines(out).find((l) => l.trim().length > 0)
|
|
130
|
+
if (!line) return ''
|
|
131
|
+
const m = /^"([^"]+)"/.exec(line.trim())
|
|
132
|
+
const name = m ? m[1] : ''
|
|
133
|
+
NAME_CACHE.set(pid, name)
|
|
134
|
+
return name
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Pick the best DSH host when several profile hosts are running.
|
|
139
|
+
*
|
|
140
|
+
* A machine can run the desktop app and a CLI host (e.g. a Lark profile) at the same time; both
|
|
141
|
+
* answer the fingerprint. The phone wants the DESKTOP one, so a host whose image is not node.exe
|
|
142
|
+
* (the Electron app) wins over a plain `node` host.
|
|
143
|
+
*/
|
|
144
|
+
async function rankHosts(ports) {
|
|
145
|
+
if (process.platform !== 'win32' || ports.length <= 1) return ports
|
|
146
|
+
const owners = await portOwners()
|
|
147
|
+
const ranked = []
|
|
148
|
+
for (const port of ports) {
|
|
149
|
+
const pid = owners.get(port)
|
|
150
|
+
let name = ''
|
|
151
|
+
if (pid) {
|
|
152
|
+
// eslint-disable-next-line no-await-in-loop
|
|
153
|
+
name = await processName(pid)
|
|
154
|
+
}
|
|
155
|
+
const desktop = name && !/^node(\.exe)?$/i.test(name)
|
|
156
|
+
ranked.push({ port, desktop })
|
|
157
|
+
}
|
|
158
|
+
const preferred = ranked.filter((r) => r.desktop)
|
|
159
|
+
return (preferred.length ? preferred : ranked).map((r) => r.port)
|
|
160
|
+
}
|
|
161
|
+
|
|
111
162
|
/**
|
|
112
163
|
* A resolver that remembers the last known-good port.
|
|
113
164
|
*
|
|
@@ -128,16 +179,24 @@ export function createDiscovery({ pinned = 0, ttlMs = 30_000, exclude = [] } = {
|
|
|
128
179
|
inFlight = (async () => {
|
|
129
180
|
const skip = new Set(exclude.filter((p) => Number.isInteger(p) && p > 0))
|
|
130
181
|
const ports = (await listeningPorts()).filter((p) => !skip.has(p))
|
|
131
|
-
// Probe
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
182
|
+
// Probe in parallel: a full sweep touches dozens of ports, and a serial walk made the first
|
|
183
|
+
// request after startup take 10+ seconds.
|
|
184
|
+
const CONCURRENCY = 12
|
|
185
|
+
let cursor = 0
|
|
186
|
+
const hits = []
|
|
187
|
+
async function worker() {
|
|
188
|
+
while (cursor < ports.length) {
|
|
189
|
+
const candidate = ports[cursor++]
|
|
190
|
+
// eslint-disable-next-line no-await-in-loop
|
|
191
|
+
if (await probePort(candidate)) hits.push(candidate)
|
|
139
192
|
}
|
|
140
193
|
}
|
|
194
|
+
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, ports.length) }, worker))
|
|
195
|
+
if (hits.length === 0) return port
|
|
196
|
+
// Keep the remembered port when it is still a hit; otherwise prefer the desktop host.
|
|
197
|
+
const ranked = hits.includes(port) ? [port, ...hits.filter((p) => p !== port)] : await rankHosts(hits)
|
|
198
|
+
port = ranked[0]
|
|
199
|
+
resolvedAt = Date.now()
|
|
141
200
|
return port
|
|
142
201
|
})()
|
|
143
202
|
try {
|
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.
|
|
19
|
+
export const version = '0.4.3'
|
|
20
20
|
|
|
21
21
|
/** No host services are required: the plugin only needs Node builtins. */
|
|
22
22
|
export const inject = []
|
package/package.json
CHANGED