diff --git a/apps/api/src/integrations/parts-catalogs/parts-catalogs.service.ts b/apps/api/src/integrations/parts-catalogs/parts-catalogs.service.ts index 5cb0c0a..ba99657 100644 --- a/apps/api/src/integrations/parts-catalogs/parts-catalogs.service.ts +++ b/apps/api/src/integrations/parts-catalogs/parts-catalogs.service.ts @@ -24,6 +24,13 @@ const MAX_RETRIES = Number(process.env.PCAT_MAX_RETRIES) || 2; // timeout before the retry rotates to a fresh port — three of those blow the // caller's 25s decode budget. Fail fast so retries reach a live port in time. const PROXY_CONNECT_TIMEOUT = Number(process.env.PCAT_PROXY_CONNECT_TIMEOUT_MS) || 6_000; +// VIN decode (/car/info) answers in <1s on a healthy proxy, so a long timeout only +// prolongs dead-port connects. undici's ProxyAgent connect.timeout does NOT bound the +// connection to the proxy itself (it stays at undici's 10s default), so we bound it at +// the fetch level: a short per-call timeout + extra retries makes a stuck DataImpulse +// port abort fast and rotate to a live one within the caller's 25s decode budget. +const DECODE_REQUEST_TIMEOUT = Number(process.env.PCAT_DECODE_TIMEOUT_MS) || 6_000; +const DECODE_MAX_RETRIES = Number(process.env.PCAT_DECODE_MAX_RETRIES) || 4; @Injectable() export class PartsCatalogsService { @@ -52,7 +59,10 @@ export class PartsCatalogsService { outcome?: { transient: boolean }, ): Promise { try { - const data = await this.fetchWithAuth("/car/info", { q: vin }, signal); + const data = await this.fetchWithAuth("/car/info", { q: vin }, signal, { + timeoutMs: DECODE_REQUEST_TIMEOUT, + maxRetries: DECODE_MAX_RETRIES, + }); if (!data || typeof data !== "object") { return null; @@ -192,8 +202,10 @@ export class PartsCatalogsService { endpoint: string, params?: Record, externalSignal?: AbortSignal, + opts?: { timeoutMs?: number; maxRetries?: number }, ): Promise { - const maxRetries = MAX_RETRIES; + const maxRetries = opts?.maxRetries ?? MAX_RETRIES; + const timeoutMs = opts?.timeoutMs ?? REQUEST_TIMEOUT; let session: PcatSession | null = null; @@ -211,7 +223,7 @@ export class PartsCatalogsService { } try { - const signals = [AbortSignal.timeout(REQUEST_TIMEOUT)]; + const signals = [AbortSignal.timeout(timeoutMs)]; if (externalSignal) signals.push(externalSignal); const fetchOptions: RequestInit & { dispatcher?: any } = { method: "GET",