@feiyang666/dsh-mobile-remote 1.9.1 → 2.0.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/CHANGELOG.md +37 -0
- package/README.md +3 -2
- package/README.zh.md +6 -8
- package/RELEASE_NOTES.md +23 -33
- package/cordis.patch.yml +4 -1
- package/lib/client.js +42 -5
- package/lib/external.js +97 -8
- package/lib/index.js +143 -956
- package/lib/lanproxy.js +40 -10
- package/package.json +2 -2
package/lib/lanproxy.js
CHANGED
|
@@ -59,6 +59,12 @@ export function createLanProxy(options = {}) {
|
|
|
59
59
|
let listenPort = 0
|
|
60
60
|
/** 转发目标端口(宿主端口;监听建立前就已确定,避免启动窗口内 503)。 */
|
|
61
61
|
let targetPort = 0
|
|
62
|
+
/**
|
|
63
|
+
* 转发目标地址。宿主可能只绑定了某块局域网网卡(例如官方桌面端用
|
|
64
|
+
* `--host 192.168.x.x` 启动),此时 127.0.0.1 上没有任何监听,转发必须打到
|
|
65
|
+
* 宿主真实绑定的那块网卡,否则旁挂听众自己也会 502。
|
|
66
|
+
*/
|
|
67
|
+
let upstreamHost = '127.0.0.1'
|
|
62
68
|
/** 当前对外提供的局域网地址列表。 */
|
|
63
69
|
let hosts = []
|
|
64
70
|
/** 每次 start/stop 递增,用于丢弃过期的启动结果。 */
|
|
@@ -68,6 +74,7 @@ export function createLanProxy(options = {}) {
|
|
|
68
74
|
running: listeners.length > 0,
|
|
69
75
|
hosts: hosts.slice(),
|
|
70
76
|
port: listenPort,
|
|
77
|
+
upstream: `${upstreamHost}:${listenPort || targetPort || 0}`,
|
|
71
78
|
})
|
|
72
79
|
|
|
73
80
|
function targetPortOf() {
|
|
@@ -92,7 +99,7 @@ export function createLanProxy(options = {}) {
|
|
|
92
99
|
headers['x-forwarded-proto'] = 'http'
|
|
93
100
|
if (realHost) headers['x-forwarded-host'] = realHost
|
|
94
101
|
const upstream = http.request({
|
|
95
|
-
host:
|
|
102
|
+
host: upstreamHost,
|
|
96
103
|
port,
|
|
97
104
|
method: req.method,
|
|
98
105
|
path: req.url,
|
|
@@ -125,7 +132,7 @@ export function createLanProxy(options = {}) {
|
|
|
125
132
|
headers['x-forwarded-proto'] = 'http'
|
|
126
133
|
if (realHost) headers['x-forwarded-host'] = realHost
|
|
127
134
|
|
|
128
|
-
const upstream = net.connect(port,
|
|
135
|
+
const upstream = net.connect(port, upstreamHost, () => {
|
|
129
136
|
const lines = [`${req.method || 'GET'} ${req.url || '/'} HTTP/1.1`]
|
|
130
137
|
for (const [key, value] of Object.entries(headers)) {
|
|
131
138
|
if (value === undefined) continue
|
|
@@ -172,21 +179,37 @@ export function createLanProxy(options = {}) {
|
|
|
172
179
|
* 在给定局域网地址上开启监听。
|
|
173
180
|
* @param {string[]} lanHosts - 局域网 IPv4 列表(按优先级排序)
|
|
174
181
|
* @param {number} port - 宿主当前监听端口
|
|
175
|
-
* @
|
|
182
|
+
* @param {{upstreamHost?: string, skipHosts?: string[]}} [startOptions]
|
|
183
|
+
* upstreamHost:转发目标(宿主真实监听的地址,未绑回环时是网卡 IP);
|
|
184
|
+
* skipHosts:宿主已经自己占用的地址(旁挂听众不能再绑,否则 EADDRINUSE)。
|
|
185
|
+
* @returns {Promise<{running: boolean, hosts: string[], port: number, upstream: string, failures: Array<{host: string, error: string}>}>}
|
|
176
186
|
*/
|
|
177
|
-
async function start(lanHosts, port) {
|
|
178
|
-
const
|
|
187
|
+
async function start(lanHosts, port, startOptions = {}) {
|
|
188
|
+
const skip = new Set((startOptions.skipHosts || []).filter(Boolean))
|
|
189
|
+
const wanted = [...new Set((lanHosts || []).filter((h) => h && !skip.has(h)))]
|
|
190
|
+
const nextUpstream = String(startOptions.upstreamHost || '127.0.0.1')
|
|
179
191
|
if (!wanted.length) {
|
|
180
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
running: false,
|
|
194
|
+
hosts: [],
|
|
195
|
+
port: 0,
|
|
196
|
+
upstream: `${nextUpstream}:${port || 0}`,
|
|
197
|
+
failures: [],
|
|
198
|
+
skipped: [...skip],
|
|
199
|
+
}
|
|
181
200
|
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
201
|
+
if (
|
|
202
|
+
listeners.length > 0 && listenPort === port && upstreamHost === nextUpstream
|
|
203
|
+
&& hosts.join(',') === wanted.join(',')
|
|
204
|
+
) {
|
|
205
|
+
return { running: true, hosts: hosts.slice(), port: listenPort, upstream: state().upstream, failures: [] }
|
|
184
206
|
}
|
|
185
207
|
await stop()
|
|
186
208
|
// stop() 已推进代次;此刻记录本轮的代次,后续任何 stop() 都会让它过期
|
|
187
209
|
const mark = generation
|
|
188
210
|
// 先确定转发目标:监听建立期间到达的请求也能正确转发
|
|
189
211
|
targetPort = port
|
|
212
|
+
upstreamHost = nextUpstream
|
|
190
213
|
const opened = []
|
|
191
214
|
const failures = []
|
|
192
215
|
for (const host of wanted) {
|
|
@@ -201,13 +224,19 @@ export function createLanProxy(options = {}) {
|
|
|
201
224
|
for (const item of opened) { try { item.server.close() } catch { /* ignore */ } }
|
|
202
225
|
return state().running
|
|
203
226
|
? { ...state(), failures }
|
|
204
|
-
: { running: false, hosts: [], port: 0, failures }
|
|
227
|
+
: { running: false, hosts: [], port: 0, upstream: `${nextUpstream}:${port || 0}`, failures }
|
|
205
228
|
}
|
|
206
229
|
listeners = opened
|
|
207
230
|
listenPort = opened.length > 0 ? port : 0
|
|
208
231
|
hosts = opened.map((item) => item.host)
|
|
209
232
|
if (!opened.length) log('lan proxy: no listener could bind', failures)
|
|
210
|
-
return {
|
|
233
|
+
return {
|
|
234
|
+
running: opened.length > 0,
|
|
235
|
+
hosts: hosts.slice(),
|
|
236
|
+
port: listenPort,
|
|
237
|
+
upstream: `${nextUpstream}:${listenPort || port || 0}`,
|
|
238
|
+
failures,
|
|
239
|
+
}
|
|
211
240
|
}
|
|
212
241
|
|
|
213
242
|
/** 关闭全部监听。 */
|
|
@@ -217,6 +246,7 @@ export function createLanProxy(options = {}) {
|
|
|
217
246
|
listeners = []
|
|
218
247
|
listenPort = 0
|
|
219
248
|
targetPort = 0
|
|
249
|
+
upstreamHost = '127.0.0.1'
|
|
220
250
|
hosts = []
|
|
221
251
|
await Promise.all(closing.map(({ server }) => new Promise((resolve) => {
|
|
222
252
|
try { server.close(() => resolve()) } catch { resolve() }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feiyang666/dsh-mobile-remote",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "DeepSeek Harness
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "DeepSeek Harness remote-access plugin: LAN access that adapts to how dsh binds (loopback side-car listener, or directly reusing the `--host <nic>` listener that dsh already owns), external tunnel through a relay server with real reachability probing (502 detection), QR code / on-off toggle / remote-access password gate and device telemetry. No mobile UI patches: dsh 0.2.2+ ships its own responsive/mobile layout.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek",
|
|
7
7
|
"harness",
|