dsh-mobile-plugin 0.1.82 → 0.1.84

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.
@@ -160,6 +160,41 @@ function snapshotRead(info) {
160
160
  }
161
161
  return body
162
162
  }
163
+ /**
164
+ * 「这份配置里哪几项决定隧道要重建」。
165
+ *
166
+ * 只有决定**连到哪台、以谁的身份连**的字段在这里。配对码、名字、局域网候选都
167
+ * 不在此列——它们能在现有连接上重报一次注册信息就生效(见 reannounceRegistration)。
168
+ *
169
+ * 为什么要把这条显式写出来:以前 `configure()` 是无条件 `stopServices()` 把隧道和
170
+ * 局域网代理一起拆了重建,于是每次配置下发(哪怕一个字段没变)都是一次全断。真机
171
+ * 现场就是「切个网络,接下来几秒点什么都失败」,宿主日志里是一串「隧道关闭 + 局域网
172
+ * 代理开始监听」,跑几小时后内存堆到几个 G 把宿主撑爆。
173
+ */
174
+ function tunnelSignature(c) {
175
+ return JSON.stringify([Boolean(c.enabled), c.relayUrl || '', c.instanceId || '', c.upstreamPort || 0, c.registerSecret || ''])
176
+ }
177
+ /** 「这份配置里哪几项决定局域网代理要重建」。 */
178
+ function lanSignature(c) {
179
+ return JSON.stringify([Boolean(c.enabled), Boolean(c.lanEnabled), c.lanPort || 0, c.upstreamPort || 0, c.instanceId || '', c.pairingCode || '', c.lanSecret || ''])
180
+ }
181
+ /** 「这份配置里哪几项决定网络监视器要不要重排」。 */
182
+ function monitorSignature(c) {
183
+ return JSON.stringify([Boolean(c.enabled), c.relayUrl || '', Boolean(c.lanEnabled)])
184
+ }
185
+ /** 当前应该上报给中转的局域网候选。 */
186
+ function currentLanUrls() { return config.lanEnabled ? lanUrls(lanUrlPort()) : [] }
187
+ /**
188
+ * 局域网候选变了:只重报注册信息,**不重建隧道**。
189
+ *
190
+ * 重建一次要把手机所有在途请求连同局域网监听一起拆掉,而这条路径(地址变化、实际
191
+ * 端口与首选端口不一致)本来就只是「名片上的地址要更新」。隧道还没连上时不用额外
192
+ * 做事:下一次 startTunnel 会按当前状态现算候选。
193
+ */
194
+ function reannounceRegistration() {
195
+ if (!tunnel) return
196
+ tunnel.updateRegistration({ lanUrls: currentLanUrls() })
197
+ }
163
198
  function stopNetworkMonitor() {
164
199
  if (networkTimer) { clearInterval(networkTimer); networkTimer = null }
165
200
  }
@@ -178,8 +213,7 @@ function startNetworkMonitor() {
178
213
  const current = lanUrls(lanUrlPort())
179
214
  if (JSON.stringify(current) === JSON.stringify(previous)) return
180
215
  previous = current
181
- // lanUrls is sent during register; reconnect only after a real change.
182
- startTunnel()
216
+ reannounceRegistration()
183
217
  }, NETWORK_POLL_MS)
184
218
  }
185
219
  function stopLan() {
@@ -196,7 +230,7 @@ function startTunnel() {
196
230
  relayUrl: core.tunnelWsUrl(config.relayUrl), instanceId: config.instanceId, pairingCode: config.pairingCode,
197
231
  name: config.name, upstream: upstream(),
198
232
  registerSecret: config.registerSecret,
199
- lanUrls: config.lanEnabled ? lanUrls(lanUrlPort()) : [],
233
+ lanUrls: currentLanUrls(),
200
234
  readSnapshot: snapshotRead,
201
235
  // 实例身份守卫:只有还活着的那个隧道实例才许改状态。onClose / onPairRequest /
202
236
  // start().catch 都有这道闸,只有 onRegistered 漏了——而它恰恰是最容易迟到的一个
@@ -251,9 +285,9 @@ function startLan() {
251
285
  if (bound > 0) lanBoundPort = bound
252
286
  publishState()
253
287
  // 实际端口和首选端口不同时,中转侧那份 lanUrls(注册时随 metadata 上报的)
254
- // 带的就是首选端口,手机从公网拿到的局域网候选全是死地址。重连一次隧道,让
255
- // 中转拿到真正的端口。
256
- if (changed && config.enabled && config.relayUrl) startTunnel()
288
+ // 带的就是首选端口,手机从公网拿到的局域网候选全是死地址。重报一次注册信息就
289
+ // 够了——重建隧道会把手机在途请求全掐掉,代价远大于这条名片更新。
290
+ if (changed) reannounceRegistration()
257
291
  }).catch(error => { if (lan === current) { lanState = 'error'; lanError = error instanceof Error ? error.message : String(error); publishState() } })
258
292
  }
259
293
  async function configure(next) {
@@ -265,12 +299,34 @@ async function configure(next) {
265
299
  if (typeof next.upstreamPort !== 'number' || !Number.isInteger(next.upstreamPort) || next.upstreamPort < 1 || next.upstreamPort > 65535) throw new Error('invalid upstreamPort')
266
300
  if (next.lanPort !== undefined && (!Number.isInteger(next.lanPort) || next.lanPort < 1024 || next.lanPort > 65535)) throw new Error('invalid lanPort')
267
301
  if (next.registerSecret !== undefined && (typeof next.registerSecret !== 'string' || next.registerSecret.length > 512)) throw new Error('invalid registerSecret')
268
- await stopServices()
302
+ // 只重建真正变了的那一半:
303
+ // - 决定隧道「连到哪台、以谁的身份连」的字段变了才重建隧道;
304
+ // - 决定局域网监听的字段变了才重建局域网代理;
305
+ // - 其余(配对码 / 名字 / 局域网候选)在现有连接上重报一次注册信息即可。
306
+ // 无条件全拆全建就是「每隔几分钟断一次 + 内存一直涨」的根因,别改回去。
307
+ const previous = config
308
+ const tunnelNeedsRebuild = tunnelSignature(previous) !== tunnelSignature(next)
309
+ const lanNeedsRebuild = lanSignature(previous) !== lanSignature(next)
310
+ const monitorNeedsRestart = monitorSignature(previous) !== monitorSignature(next)
269
311
  config = { ...next }
270
- if (config.enabled) {
312
+ if (tunnelNeedsRebuild) {
313
+ stopTunnel()
271
314
  startTunnel()
315
+ }
316
+ if (lanNeedsRebuild) {
317
+ stopLan()
318
+ startLan()
319
+ }
320
+ if (!tunnelNeedsRebuild && tunnel) {
321
+ const patch = {}
322
+ if (previous.pairingCode !== config.pairingCode) patch.pairingCode = config.pairingCode
323
+ if (previous.name !== config.name) patch.name = config.name
324
+ if (lanNeedsRebuild) patch.lanUrls = currentLanUrls()
325
+ if (Object.keys(patch).length) tunnel.updateRegistration(patch)
326
+ }
327
+ if (monitorNeedsRestart) {
328
+ stopNetworkMonitor()
272
329
  startNetworkMonitor()
273
- if (config.lanEnabled) startLan()
274
330
  }
275
331
  return status()
276
332
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile-plugin",
3
- "version": "0.1.82",
3
+ "version": "0.1.84",
4
4
  "description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (0.2.0-rc.2 compatible, peers resolved by the host)",
5
5
  "license": "MIT",
6
6
  "type": "module",