refactor(gateway): centralize handshake timeout parsing

This commit is contained in:
Peter Steinberger
2026-05-29 05:56:16 -04:00
parent 7708e8c7ef
commit ed62aefeee
4 changed files with 37 additions and 92 deletions

View File

@@ -55,6 +55,19 @@ describe("gateway client handshake timeouts", () => {
).toBe(MAX_SAFE_TIMEOUT_DELAY_MS);
});
it("accepts existing strict timeout env integer forms", () => {
expect(
getPreauthHandshakeTimeoutMsFromEnv({
OPENCLAW_HANDSHAKE_TIMEOUT_MS: " +75000 ",
}),
).toBe(75_000);
expect(
getConnectChallengeTimeoutMsFromEnv({
OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS: " 015000 ",
}),
).toBe(15_000);
});
it("caps connect challenge timeout env and explicit values to the safe timer range", () => {
expect(
getConnectChallengeTimeoutMsFromEnv({

View File

@@ -1,9 +1,10 @@
function parseStrictPositiveInteger(value: string): number | undefined {
if (!/^[1-9]\d*$/u.test(value)) {
const trimmed = value.trim();
if (!/^\+?\d+$/u.test(trimmed)) {
return undefined;
}
const parsed = Number(value);
return Number.isSafeInteger(parsed) ? parsed : undefined;
const parsed = Number(trimmed);
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
}
export const MAX_SAFE_TIMEOUT_DELAY_MS = 2_147_483_647;

View File

@@ -37,6 +37,13 @@ describe("gateway handshake timeouts", () => {
VITEST: "1",
}),
).toBe(20);
expect(
getPreauthHandshakeTimeoutMsFromEnv({
OPENCLAW_HANDSHAKE_TIMEOUT_MS: " +75 ",
OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS: "20",
VITEST: "1",
}),
).toBe(75);
});
test("resolves preauth handshake timeout with env over config over default", () => {
@@ -126,6 +133,9 @@ describe("gateway handshake timeouts", () => {
expect(
getConnectChallengeTimeoutMsFromEnv({ OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS: "15000" }),
).toBe(15_000);
expect(
getConnectChallengeTimeoutMsFromEnv({ OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS: " 015000 " }),
).toBe(15_000);
expect(
getConnectChallengeTimeoutMsFromEnv({ OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS: "garbage" }),
).toBeUndefined();

View File

@@ -1,89 +1,10 @@
import { parseStrictPositiveInteger } from "../infra/parse-finite-number.js";
import { resolveSafeTimeoutDelayMs } from "../utils/timer-delay.js";
export const DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 15_000;
export const MIN_CONNECT_CHALLENGE_TIMEOUT_MS = 250;
export const MAX_CONNECT_CHALLENGE_TIMEOUT_MS = DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
export function clampConnectChallengeTimeoutMs(
timeoutMs: number,
maxTimeoutMs = MAX_CONNECT_CHALLENGE_TIMEOUT_MS,
): number {
return Math.max(
MIN_CONNECT_CHALLENGE_TIMEOUT_MS,
Math.min(Math.max(MIN_CONNECT_CHALLENGE_TIMEOUT_MS, maxTimeoutMs), timeoutMs),
);
}
export function getConnectChallengeTimeoutMsFromEnv(
env: NodeJS.ProcessEnv = process.env,
): number | undefined {
const raw = env.OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS;
if (raw) {
const parsed = parseStrictPositiveInteger(raw);
if (parsed !== undefined) {
return resolveSafeTimeoutDelayMs(parsed);
}
}
return undefined;
}
function normalizePositiveTimeoutMs(timeoutMs: unknown): number | undefined {
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0
? resolveSafeTimeoutDelayMs(timeoutMs)
: undefined;
}
export function resolveConnectChallengeTimeoutMs(
timeoutMs?: number | null,
params?: {
env?: NodeJS.ProcessEnv;
configuredTimeoutMs?: number | null;
},
): number {
const configuredPreauthTimeoutMs = resolvePreauthHandshakeTimeoutMs({
env: params?.env,
configuredTimeoutMs: params?.configuredTimeoutMs,
});
const maxTimeoutMs = Math.max(DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, configuredPreauthTimeoutMs);
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
return clampConnectChallengeTimeoutMs(timeoutMs, maxTimeoutMs);
}
const envOverride = getConnectChallengeTimeoutMsFromEnv(params?.env);
if (envOverride !== undefined) {
return clampConnectChallengeTimeoutMs(envOverride, Math.max(maxTimeoutMs, envOverride));
}
return clampConnectChallengeTimeoutMs(configuredPreauthTimeoutMs, maxTimeoutMs);
}
export function getPreauthHandshakeTimeoutMsFromEnv(env: NodeJS.ProcessEnv = process.env): number {
const configuredTimeout =
env.OPENCLAW_HANDSHAKE_TIMEOUT_MS || (env.VITEST && env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS);
if (configuredTimeout) {
const parsed = parseStrictPositiveInteger(configuredTimeout);
if (parsed !== undefined) {
return resolveSafeTimeoutDelayMs(parsed);
}
}
return DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
}
export function resolvePreauthHandshakeTimeoutMs(params?: {
env?: NodeJS.ProcessEnv;
configuredTimeoutMs?: number | null;
}): number {
const env = params?.env ?? process.env;
const configuredTimeout =
env.OPENCLAW_HANDSHAKE_TIMEOUT_MS || (env.VITEST && env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS);
if (configuredTimeout) {
const parsed = parseStrictPositiveInteger(configuredTimeout);
if (parsed !== undefined) {
return resolveSafeTimeoutDelayMs(parsed);
}
}
const configured = normalizePositiveTimeoutMs(params?.configuredTimeoutMs);
if (configured !== undefined) {
return configured;
}
return DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
}
export {
clampConnectChallengeTimeoutMs,
DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS,
getConnectChallengeTimeoutMsFromEnv,
getPreauthHandshakeTimeoutMsFromEnv,
MAX_CONNECT_CHALLENGE_TIMEOUT_MS,
MIN_CONNECT_CHALLENGE_TIMEOUT_MS,
resolveConnectChallengeTimeoutMs,
resolvePreauthHandshakeTimeoutMs,
} from "../../packages/gateway-client/src/timeouts.js";