dsh-mobile-plugin 0.4.6 → 0.4.7

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.
@@ -87,6 +87,10 @@ class RelayTunnel {
87
87
  this.httpPumpCursor = 0;
88
88
  this.pendingHttpResponseBytes = 0;
89
89
  this.lastPong = 0;
90
+ // 对端存不存镜像:注册回执里明确说了才改,没说(老中转)按「存」处理,行为与以前一字
91
+ // 不差。纯转发节点(`MIRROR_CACHE=0`)收下整份镜像也只解析完丢掉,worker 据此只把
92
+ // 摘要推上去(lib/sidecar-facade-worker.cjs 的 digestFrame)。
93
+ this.relayMirror = true;
90
94
  // 清除所有授权的等待回调(一次一个);由 requestRevokeAll 注入,revokeAllAck 触发。
91
95
  this._revokeAllAck = null;
92
96
  }
@@ -462,6 +466,9 @@ class RelayTunnel {
462
466
 
463
467
  /** 建立连接并注册;resolve 于收到 registered,reject 于连接/注册失败 */
464
468
  start() {
469
+ // 每条新连接都从「对端存镜像」重新判定:能走到推送的那一步一定已经收到过回执,
470
+ // 而回执缺这个字段的老中转本来就该按「存」处理。
471
+ this.relayMirror = true;
465
472
  return new Promise((resolve, reject) => {
466
473
  let settled = false;
467
474
  const done = (fn, arg) => {
@@ -482,6 +489,9 @@ class RelayTunnel {
482
489
 
483
490
  const push = createFrameDecoder((frame) => {
484
491
  if (frame.type === T.registered) {
492
+ // 回执里带 `mirror`:对端说不存镜像,就只推摘要(凭据 + 两个 revision),正文不推。
493
+ // 老中转回的是空载荷,`parseJson` 给空对象,于是判定为「存」——与以前完全一样。
494
+ this.relayMirror = parseJson(frame.payload).mirror !== false;
485
495
  clearTimeout(timer);
486
496
  done(resolve);
487
497
  this.onRegistered();
@@ -41,6 +41,9 @@ let tunnel = null
41
41
  let lan = null
42
42
  let tunnelState = 'off'
43
43
  let tunnelError = null
44
+ // 当前这条隧道的对端存不存镜像(注册回执里报的)。false ⇒ 宿主只推摘要帧,正文不推。
45
+ // 未知与老中转一律按 true:那是原来的行为,别让一次漏字段把镜像功能悄悄降级。
46
+ let relayMirror = true
44
47
  let lanState = 'off'
45
48
  let lanError = null
46
49
  // 局域网代理**实际**在监听的端口。0 = 还没起来。首选端口被占时会退到内核分配的
@@ -62,7 +65,7 @@ function lanUrlPort() { return lanBoundPort || config.lanPort }
62
65
  function publishState() {
63
66
  process.send?.({
64
67
  type: 'state',
65
- tunnel: { state: tunnelState, error: tunnelError },
68
+ tunnel: { state: tunnelState, error: tunnelError, mirror: relayMirror },
66
69
  lan: { enabled: Boolean(config.lanEnabled), port: lanUrlPort(), requestedPort: config.lanPort, state: lanState, error: lanError },
67
70
  })
68
71
  }
@@ -122,11 +125,40 @@ const TUNNEL_SEND_MAX_BODY = 8 * 1024 * 1024 - 64 * 1024
122
125
  * 裁字段而不是改推送策略:宿主那侧「推什么、什么时候推」一个字都不用改,两侧版本
123
126
  * 不必同时上线(老 worker + 新宿主也照旧工作,只是白推;新 worker + 老宿主也照旧)。
124
127
  */
125
- function frameForTunnel(body) {
128
+ /**
129
+ * 对端不存镜像时上隧道的那一帧:只留中转还会真的读的东西。
130
+ *
131
+ * 纯转发节点(`MIRROR_CACHE=0`)的 `mirror::remember` 只留凭据摘要与时间戳,两份清单正文
132
+ * 解析完就丢——那是每台宿主每 5 秒几十 KB、一天上百 GB 的白推。所以对端在注册回执里说
133
+ * `mirror:false` 之后,清单与工作区各只留 `ok` 和 `revision`:
134
+ *
135
+ * - `ok` 必须留着,中转用它判「这份镜像是不是畸形」;
136
+ * - `revision` 必须留着,它是中转镜像账本里那一版的身份;
137
+ * - `creds` 必须留着,`/push-token` 的鉴权真源就是这本账里的摘要,丢了它上报全 403。
138
+ *
139
+ * 本地快照不受影响:`localSnapshot` 在这一帧被裁之前就已经赋值,局域网直连的读取隔离
140
+ * 读的是它(见 `/tunnel/send` 那一段)。
141
+ */
142
+ function digestFrame(body) {
143
+ const pick = (value) => {
144
+ const out = { ok: true }
145
+ if (value && typeof value === 'object' && typeof value.revision === 'string') out.revision = value.revision
146
+ return out
147
+ }
148
+ return {
149
+ kind: 'sync',
150
+ at: body.at,
151
+ sessions: pick(body.sessions),
152
+ workspaces: pick(body.workspaces),
153
+ creds: Array.isArray(body.creds) ? body.creds : [],
154
+ }
155
+ }
156
+
157
+ function frameForTunnel(body, mirrorOff = false) {
126
158
  if (!body || typeof body !== 'object') return { kind: 'raw', data: body }
127
159
  if (body.kind !== 'sync') return body
128
160
  const { messages, stats, models, ...rest } = body
129
- return rest
161
+ return mirrorOff ? digestFrame(rest) : rest
130
162
  }
131
163
  // /configure 只送 relay.json 那几十个字段,给 64KB 绰绰有余;放宽只会让误用更难发现。
132
164
  const CONFIGURE_MAX_BODY = 64 * 1024
@@ -255,6 +287,8 @@ function stopTunnel() {
255
287
  reconnect.cancel()
256
288
  if (tunnel) tunnel.stop()
257
289
  tunnel = null; tunnelState = 'off'; tunnelError = null
290
+ // 断开即忘掉上一台中转的口径:下次连的可能是另一台,别拿旧的判断去决定推不推正文。
291
+ relayMirror = true
258
292
  publishState()
259
293
  }
260
294
  function startNetworkMonitor() {
@@ -297,8 +331,10 @@ function openTunnel() {
297
331
  // 「bg queue: sidecar facade request failed: 503」刷屏,而隧道其实在正常转发会话)。
298
332
  onRegistered: () => {
299
333
  // 注册成功 = 链路真的通了:连续失败账清零,下次掉线重新从 5s 起算。
334
+ // 回执里的镜像口径(`relayMirror`)跟着状态一起改、一起播,同样只在这道身份闸之内:
335
+ // 旧实例迟到的回执不许改新连接的口径。
300
336
  if (tunnel !== current) return; reconnect.reset()
301
- tunnelState = 'connected'; tunnelError = null; publishState()
337
+ tunnelState = 'connected'; tunnelError = null; relayMirror = current.relayMirror !== false; publishState()
302
338
  },
303
339
  onClose: () => { if (tunnel === current && config.enabled) { tunnelState = 'error'; tunnelError = '连接断开'; publishState(); scheduleReconnect() } },
304
340
  onPairRequest: async (message) => {
@@ -444,7 +480,7 @@ function status() {
444
480
  // 量不到的一律报 null、不拿 0 顶替:0 是「一条都没有」,与「不知道」是两件事。
445
481
  const lanStreams = lan && typeof lan.stats === 'function' ? lan.stats() : null
446
482
  const relaySseOpen = tunnel ? (typeof tunnel.upstreamSseCount === 'number' ? tunnel.upstreamSseCount : null) : 0
447
- return { ok: true, sidecar: true, enabled: Boolean(config.enabled), relayUrl: config.relayUrl || '', instanceId: config.instanceId, name: config.name, localPort: config.upstreamPort, tunnel: { state: tunnelState, error: tunnelError }, lan: { enabled: Boolean(config.lanEnabled), port: lanUrlPort(), requestedPort: config.lanPort, state: lanState, error: lanError }, process: { memoryUsage: process.memoryUsage() }, streams: { lan: lanStreams, relaySseOpen } }
483
+ return { ok: true, sidecar: true, enabled: Boolean(config.enabled), relayUrl: config.relayUrl || '', instanceId: config.instanceId, name: config.name, localPort: config.upstreamPort, tunnel: { state: tunnelState, error: tunnelError, mirror: relayMirror }, lan: { enabled: Boolean(config.lanEnabled), port: lanUrlPort(), requestedPort: config.lanPort, state: lanState, error: lanError }, process: { memoryUsage: process.memoryUsage() }, streams: { lan: lanStreams, relaySseOpen } }
448
484
  }
449
485
  const server = http.createServer(async (req, res) => {
450
486
  try {
@@ -470,7 +506,7 @@ const server = http.createServer(async (req, res) => {
470
506
  const frameType = body && typeof body === 'object' && body.kind === 'push'
471
507
  ? (body.pushFrame === 'register' ? T.pushRegister : T.pushSend)
472
508
  : T.sync;
473
- tunnel.sendJson(frameType, 0, frameForTunnel(body));
509
+ tunnel.sendJson(frameType, 0, frameForTunnel(body, relayMirror === false));
474
510
  return send(res, 202, { ok: true });
475
511
  }
476
512
  return send(res, 503, { ok: false, error: 'tunnel not connected' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile-plugin",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (dsh 0.2.x, peers resolved by the host)",
5
5
  "license": "MIT",
6
6
  "engines": {