rdsh-gateway 0.8.2 → 0.8.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/dist/join.d.ts CHANGED
@@ -62,6 +62,16 @@ export interface StartJoinOptions {
62
62
  name?: string;
63
63
  /** 宿主代持的 dsh 浏览器会话 cookie(`dsh-auth-*`,0.1.2+);有值时注入隧道→本地转发 */
64
64
  dshAuthCookieHeader?: string | null;
65
+ /**
66
+ * 心跳间隔(缺省 30s,协议 PROTOCOL.md「心跳与重连」)。
67
+ * 测试可注入毫秒级小值以快速验证超时判定。
68
+ */
69
+ heartbeatMs?: number;
70
+ /**
71
+ * 发出 PING 后等待对端任何帧的上限(缺省 10s);超时即判连接已死并主动断开。
72
+ * 测试可注入小值。
73
+ */
74
+ pongTimeoutMs?: number;
65
75
  }
66
76
  /** 可停止的 join 隧道句柄。 */
67
77
  export interface JoinHandle {
package/dist/join.js CHANGED
@@ -41,6 +41,8 @@ export async function detectInsecure(hubUrl) {
41
41
  }
42
42
  }
43
43
  const HEARTBEAT_MS = 30_000;
44
+ /** 发出 PING 后等待对端任何帧的上限:超时即判连接已死并主动断开(协议 PROTOCOL.md)。 */
45
+ const PONG_TIMEOUT_MS = 10_000;
44
46
  const RECONNECT_BASE_MS = 1_000;
45
47
  const RECONNECT_MAX_MS = 60_000;
46
48
  /** hub HTTP 调用(node:https 支持自签跳过校验 —— undici fetch 不受 NODE_TLS_REJECT_UNAUTHORIZED 影响)。 */
@@ -212,7 +214,13 @@ export function startJoin(opts) {
212
214
  let shuttingDown = false;
213
215
  let reconnectDelay = RECONNECT_BASE_MS;
214
216
  let heartbeat;
217
+ /** 发出 PING 后的存活死线:期间收到**任何**入站帧即撤销(PROTOCOL.md 心跳与重连)。 */
218
+ let livenessDeadline;
215
219
  let currentClient;
220
+ const heartbeatMs = opts.heartbeatMs ?? HEARTBEAT_MS;
221
+ const pongTimeoutMs = opts.pongTimeoutMs ?? PONG_TIMEOUT_MS;
222
+ /** 日志用人类可读单位:生产 30000ms → "30s";测试注入的毫秒值原样显示。 */
223
+ const heartbeatLabel = heartbeatMs % 1000 === 0 ? `${heartbeatMs / 1000}s` : `${heartbeatMs}ms`;
216
224
  /** 发送一个隧道帧(走当前隧道 WS;flags 由调用方在 encodeFrame 时给定)。 */
217
225
  function sendTunnelFrame(frame) {
218
226
  if (currentClient !== undefined && currentClient.readyState === currentClient.OPEN) {
@@ -597,6 +605,10 @@ export function startJoin(opts) {
597
605
  clearInterval(heartbeat);
598
606
  heartbeat = undefined;
599
607
  }
608
+ if (livenessDeadline !== undefined) {
609
+ clearTimeout(livenessDeadline);
610
+ livenessDeadline = undefined;
611
+ }
600
612
  plainDispatcher.cleanup();
601
613
  for (const raw of rawStreams.values())
602
614
  raw.inner.cleanup();
@@ -626,18 +638,38 @@ export function startJoin(opts) {
626
638
  client.on("open", () => {
627
639
  reconnectDelay = RECONNECT_BASE_MS;
628
640
  setState("connected");
629
- log("info", "tunnel established (heartbeat 30s)");
641
+ log("info", `tunnel established (heartbeat ${heartbeatLabel})`);
630
642
  if (heartbeat !== undefined)
631
643
  clearInterval(heartbeat);
632
644
  heartbeat = setInterval(() => {
633
645
  if (client.readyState === client.OPEN) {
634
646
  client.send(encodeFrame(FRAME_TYPE.PING, 0, jsonPayload({ ts: Date.now() })));
647
+ // 发出 PING 后武装死线:pongTimeoutMs 内收到任何入站帧即撤销(下方 message 处理)。
648
+ // 只在**没有未决死线**时武装——上一发 PING 未获回应时原死线继续计时,不重置。
649
+ if (livenessDeadline === undefined) {
650
+ livenessDeadline = setTimeout(() => {
651
+ livenessDeadline = undefined;
652
+ log("info", `heartbeat timeout — no frame from hub within ${pongTimeoutMs}ms; reconnecting`);
653
+ try {
654
+ client.terminate();
655
+ }
656
+ catch {
657
+ /* 已关闭 */
658
+ }
659
+ }, pongTimeoutMs);
660
+ livenessDeadline.unref?.();
661
+ }
635
662
  }
636
- }, HEARTBEAT_MS);
663
+ }, heartbeatMs);
637
664
  });
638
665
  client.on("message", (data, isBinary) => {
639
666
  if (!isBinary)
640
667
  return;
668
+ // 任何入站帧都是存活证据 → 撤销死线(不限于 PONG)
669
+ if (livenessDeadline !== undefined) {
670
+ clearTimeout(livenessDeadline);
671
+ livenessDeadline = undefined;
672
+ }
641
673
  const chunk = Array.isArray(data)
642
674
  ? Buffer.concat(data)
643
675
  : Buffer.isBuffer(data)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rdsh-gateway",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "rdsh host-side component: LAN auth gateway + outbound tunnel endpoint (spawns dsh web)",
5
5
  "license": "MIT",
6
6
  "type": "module",