Files
OmniRoute/src/shared/utils/fetchTimeout.ts
diegosouzapw 46932961d4 refactor(env): promote hardcoded URLs and circuit-breaker timeouts to environment
Centralizes a handful of hardcoded URLs, fetch timeouts, and circuit-breaker
constants behind opt-in environment variables. Behavior is unchanged when
the variables are unset because every call site keeps its current default.

- open-sse/services/usage.ts: extracts CROF_USAGE_URL,
  GEMINI_CLI_USAGE_URL, CODEWHISPERER_BASE_URL constants backed by
  OMNIROUTE_CROF_USAGE_URL, OMNIROUTE_GEMINI_CLI_USAGE_URL, and
  OMNIROUTE_CODEWHISPERER_BASE_URL. Lets operators redirect quota probes
  through corporate mirrors or a test fixture.
- open-sse/config/constants.ts: PROVIDER_PROFILES circuit-breaker
  thresholds and reset timeouts now honor OMNIROUTE_CIRCUIT_BREAKER_*
  env vars (oauth/api-key/local) with the same defaults as before.
- src/shared/utils/fetchTimeout.ts: DEFAULT_TIMEOUT_MS reads
  OMNIROUTE_DEFAULT_FETCH_TIMEOUT_MS (fallback 120000) so deployments can
  raise the global fallback without changing FETCH_TIMEOUT_MS semantics.
- open-sse/services/chatgptTlsClient.ts: DEFAULT_TIMEOUT_MS and
  HARD_TIMEOUT_GRACE_MS now honor OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS and
  OMNIROUTE_CHATGPT_TLS_GRACE_MS (defaults 60000 / 10000).
- .env.example: documents the 11 new variables in the URLs and TIMEOUT
  sections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:55:33 -03:00

75 lines
1.9 KiB
TypeScript

/**
* Fetch Timeout — T-25
*
* Wraps fetch() with an AbortController-based timeout.
* Default timeout is 120 seconds (FETCH_TIMEOUT_MS env var).
*
* @module shared/utils/fetchTimeout
*/
const DEFAULT_TIMEOUT_MS =
parseInt(process.env.OMNIROUTE_DEFAULT_FETCH_TIMEOUT_MS || "", 10) || 120000; // 2 minutes
const FETCH_TIMEOUT_MS = parseInt(process.env.FETCH_TIMEOUT_MS || "", 10) || DEFAULT_TIMEOUT_MS;
interface FetchTimeoutOptions extends RequestInit {
timeoutMs?: number;
}
export async function fetchWithTimeout(url: string | URL, options: FetchTimeoutOptions = {}) {
const { timeoutMs = FETCH_TIMEOUT_MS, signal: externalSignal, ...fetchOptions } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
// If an external signal was provided, wire it to abort our controller too
if (externalSignal) {
if (externalSignal.aborted) {
controller.abort();
} else {
externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
}
}
try {
const response = await fetch(url, {
...fetchOptions,
signal: controller.signal,
});
return response;
} catch (error: any) {
if (error.name === "AbortError") {
throw new FetchTimeoutError(
`Request to ${url} timed out after ${timeoutMs}ms`,
timeoutMs,
String(url)
);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
/**
* Error thrown on fetch timeout.
*/
export class FetchTimeoutError extends Error {
timeoutMs: number;
url: string;
constructor(message: string, timeoutMs: number, url: string) {
super(message);
this.name = "FetchTimeoutError";
this.timeoutMs = timeoutMs;
this.url = url;
}
}
/**
* Get the configured timeout value.
* @returns {number} Timeout in milliseconds
*/
export function getConfiguredTimeout() {
return FETCH_TIMEOUT_MS;
}