From 956c1fb9b9de2384f219f084788dbad05b31629f Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Sun, 19 Apr 2026 02:12:47 -0700 Subject: [PATCH] fix(canvas): add 15s fetch timeout on API calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-launch audit flagged api.ts as missing a timeout on every fetch. A slow or hung CP response would leave the UI spinning indefinitely with no way for the user to abort — effectively a client-side DoS. 15s is long enough for real CP queries (slowest observed is Stripe portal redirect at ~3s) and short enough that a stalled backend surfaces as a clear error with a retry affordance. Uses AbortSignal.timeout (widely supported since 2023) so the abort propagates through React Query / SWR consumers cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) --- canvas/src/lib/api.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/canvas/src/lib/api.ts b/canvas/src/lib/api.ts index 6bb091b1..3721dce6 100644 --- a/canvas/src/lib/api.ts +++ b/canvas/src/lib/api.ts @@ -8,6 +8,12 @@ import { getTenantSlug } from "./tenant"; export const PLATFORM_URL = process.env.NEXT_PUBLIC_PLATFORM_URL ?? "http://localhost:8080"; +// 15s is long enough for slow CP queries but short enough that a +// hung backend doesn't leave the UI spinning forever. The abort +// propagates through AbortController so React components can observe +// the error and render a retry affordance. +const DEFAULT_TIMEOUT_MS = 15_000; + async function request( method: string, path: string, @@ -28,6 +34,7 @@ async function request( headers, body: body ? JSON.stringify(body) : undefined, credentials: "include", + signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS), }); if (!res.ok) { const text = await res.text();