






















@@ -50,6 +50,7 @@ import {
5050validateRequestFrame,
5151validateResponseFrame,
5252} from "./protocol/index.js";
53+import { resolveGatewayStartupRetryAfterMs } from "./protocol/startup-unavailable.js";
53545455type Pending = {
5556resolve: (value: unknown) => void;
@@ -168,6 +169,7 @@ export const GATEWAY_CLOSE_CODE_HINTS: Readonly<Record<number, string>> = {
1681691006: "abnormal closure (no close frame)",
1691701008: "policy violation",
1701711012: "service restart",
172+1013: "try again later",
171173};
172174173175export function describeGatewayCloseCode(code: number): string | undefined {
@@ -227,6 +229,7 @@ export class GatewayClient {
227229private reconnectTimer: NodeJS.Timeout | null = null;
228230private pendingDeviceTokenRetry = false;
229231private deviceTokenRetryBudgetUsed = false;
232+private pendingStartupReconnectDelayMs: number | null = null;
230233private pendingConnectErrorDetailCode: string | null = null;
231234// Track last tick to detect silent stalls.
232235private lastTick: number | null = null;
@@ -350,6 +353,10 @@ export class GatewayClient {
350353}
351354this.socketOpened = false;
352355this.resolvePendingStop(ws);
356+if (this.pendingStartupReconnectDelayMs !== null) {
357+this.scheduleReconnect();
358+return;
359+}
353360// Clear persisted device auth state only when device-token auth was active.
354361// Shared token/password failures can return the same close reason but should
355362// not erase a valid cached device token.
@@ -429,6 +436,7 @@ export class GatewayClient {
429436this.closed = true;
430437this.pendingDeviceTokenRetry = false;
431438this.deviceTokenRetryBudgetUsed = false;
439+this.pendingStartupReconnectDelayMs = null;
432440this.pendingConnectErrorDetailCode = null;
433441this.clearReconnectTimer();
434442if (this.tickTimer) {
@@ -576,6 +584,7 @@ export class GatewayClient {
576584.then((helloOk) => {
577585this.pendingDeviceTokenRetry = false;
578586this.deviceTokenRetryBudgetUsed = false;
587+this.pendingStartupReconnectDelayMs = null;
579588this.pendingConnectErrorDetailCode = null;
580589const authInfo = helloOk?.auth;
581590if (authInfo?.deviceToken && this.opts.deviceIdentity) {
@@ -626,6 +635,13 @@ export class GatewayClient {
626635this.deviceTokenRetryBudgetUsed = true;
627636this.backoffMs = Math.min(this.backoffMs, 250);
628637}
638+const startupRetryAfterMs = resolveGatewayStartupRetryAfterMs(err);
639+if (startupRetryAfterMs !== null) {
640+this.pendingStartupReconnectDelayMs = startupRetryAfterMs;
641+logDebug(`gateway connect failed: ${String(err)}`);
642+this.ws?.close(1013, "gateway starting");
643+return;
644+}
629645this.opts.onConnectError?.(err instanceof Error ? err : new Error(String(err)));
630646const msg = `gateway connect failed: ${String(err)}`;
631647if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(err)) {
@@ -916,8 +932,12 @@ export class GatewayClient {
916932this.tickTimer = null;
917933}
918934this.clearReconnectTimer();
919-const delay = this.backoffMs;
920-this.backoffMs = Math.min(this.backoffMs * 2, 30_000);
935+const startupDelay = this.pendingStartupReconnectDelayMs;
936+this.pendingStartupReconnectDelayMs = null;
937+const delay = startupDelay ?? this.backoffMs;
938+if (startupDelay === null) {
939+this.backoffMs = Math.min(this.backoffMs * 2, 30_000);
940+}
921941this.reconnectTimer = setTimeout(() => {
922942this.reconnectTimer = null;
923943this.start();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。