@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/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: '127.0.0.1',
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, '127.0.0.1', () => {
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
- * @returns {Promise<{running: boolean, hosts: string[], port: number, failures: Array<{host: string, error: string}>}>}
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 wanted = [...new Set((lanHosts || []).filter(Boolean))]
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 { running: false, hosts: [], port: 0, failures: [{ host: '', error: '没有可用的局域网 IPv4 地址' }] }
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 (listeners.length > 0 && listenPort === port && hosts.join(',') === wanted.join(',')) {
183
- return { running: true, hosts: hosts.slice(), port: listenPort, failures: [] }
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 { running: opened.length > 0, hosts: hosts.slice(), port: listenPort, failures }
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": "1.9.1",
4
- "description": "DeepSeek Harness mobile remote-control plugin: LAN access through a side-car listener (dsh 0.2.1+ rejects wildcard binds, so the host listener is never touched: no restart, no port drift, local window keeps working), external tunnel with real status detection, QR / on-off toggle / remote-access password gate, rich telemetry and mobile-first UI polish.",
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",