fix(gateway): harden token fallback/reconnect behavior and docs (#42507)

* fix(gateway): harden token fallback and auth reconnect handling

* docs(gateway): clarify auth retry and token-drift recovery

* fix(gateway): tighten auth reconnect gating across clients

* fix: harden gateway token retry (#42507) (thanks @joshavant)
This commit is contained in:
Josh Avant
2026-03-10 17:05:57 -05:00
committed by GitHub
parent ff2e7a2945
commit a76e810193
21 changed files with 1188 additions and 80 deletions

View File

@@ -0,0 +1,42 @@
import { describe, expect, it } from "vitest";
import {
readConnectErrorDetailCode,
readConnectErrorRecoveryAdvice,
} from "./connect-error-details.js";
describe("readConnectErrorDetailCode", () => {
it("reads structured detail codes", () => {
expect(readConnectErrorDetailCode({ code: "AUTH_TOKEN_MISMATCH" })).toBe("AUTH_TOKEN_MISMATCH");
});
it("returns null for invalid detail payloads", () => {
expect(readConnectErrorDetailCode(null)).toBeNull();
expect(readConnectErrorDetailCode("AUTH_TOKEN_MISMATCH")).toBeNull();
});
});
describe("readConnectErrorRecoveryAdvice", () => {
it("reads retry advice fields when present", () => {
expect(
readConnectErrorRecoveryAdvice({
canRetryWithDeviceToken: true,
recommendedNextStep: "retry_with_device_token",
}),
).toEqual({
canRetryWithDeviceToken: true,
recommendedNextStep: "retry_with_device_token",
});
});
it("returns empty advice for invalid payloads", () => {
expect(readConnectErrorRecoveryAdvice(null)).toEqual({});
expect(readConnectErrorRecoveryAdvice("x")).toEqual({});
expect(readConnectErrorRecoveryAdvice({ canRetryWithDeviceToken: "yes" })).toEqual({});
expect(
readConnectErrorRecoveryAdvice({
canRetryWithDeviceToken: true,
recommendedNextStep: "retry_with_magic",
}),
).toEqual({ canRetryWithDeviceToken: true, recommendedNextStep: undefined });
});
});

View File

@@ -28,6 +28,26 @@ export const ConnectErrorDetailCodes = {
export type ConnectErrorDetailCode =
(typeof ConnectErrorDetailCodes)[keyof typeof ConnectErrorDetailCodes];
export type ConnectRecoveryNextStep =
| "retry_with_device_token"
| "update_auth_configuration"
| "update_auth_credentials"
| "wait_then_retry"
| "review_auth_configuration";
export type ConnectErrorRecoveryAdvice = {
canRetryWithDeviceToken?: boolean;
recommendedNextStep?: ConnectRecoveryNextStep;
};
const CONNECT_RECOVERY_NEXT_STEP_VALUES: ReadonlySet<ConnectRecoveryNextStep> = new Set([
"retry_with_device_token",
"update_auth_configuration",
"update_auth_credentials",
"wait_then_retry",
"review_auth_configuration",
]);
export function resolveAuthConnectErrorDetailCode(
reason: string | undefined,
): ConnectErrorDetailCode {
@@ -91,3 +111,26 @@ export function readConnectErrorDetailCode(details: unknown): string | null {
const code = (details as { code?: unknown }).code;
return typeof code === "string" && code.trim().length > 0 ? code : null;
}
export function readConnectErrorRecoveryAdvice(details: unknown): ConnectErrorRecoveryAdvice {
if (!details || typeof details !== "object" || Array.isArray(details)) {
return {};
}
const raw = details as {
canRetryWithDeviceToken?: unknown;
recommendedNextStep?: unknown;
};
const canRetryWithDeviceToken =
typeof raw.canRetryWithDeviceToken === "boolean" ? raw.canRetryWithDeviceToken : undefined;
const normalizedNextStep =
typeof raw.recommendedNextStep === "string" ? raw.recommendedNextStep.trim() : "";
const recommendedNextStep = CONNECT_RECOVERY_NEXT_STEP_VALUES.has(
normalizedNextStep as ConnectRecoveryNextStep,
)
? (normalizedNextStep as ConnectRecoveryNextStep)
: undefined;
return {
canRetryWithDeviceToken,
recommendedNextStep,
};
}