type JsonErrorPayload = { error?: string; message?: string; detail?: string; }; const MAX_ERROR_TEXT = 240; function summarizeResponseText(raw: string): string { const compact = raw.replace(/\s+/g, " ").trim(); if (!compact) return ""; if (compact.length <= MAX_ERROR_TEXT) return compact; return `${compact.slice(0, MAX_ERROR_TEXT - 3)}...`; } function defaultHttpMessage(response: Response): string { const suffix = response.statusText?.trim() ? ` ${response.statusText.trim()}` : ""; return `HTTP ${response.status}${suffix}`; } function extractErrorMessage( response: Response, raw: string, payload: JsonErrorPayload | null, ): string { const candidate = (typeof payload?.error === "string" && payload.error.trim()) || (typeof payload?.message === "string" && payload.message.trim()) || (typeof payload?.detail === "string" && payload.detail.trim()) || summarizeResponseText(raw); return candidate || defaultHttpMessage(response); } export async function fetchJsonResponse(url: string, init?: RequestInit): Promise { const response = await fetch(url, { cache: "no-store", ...init }); const raw = await response.text(); const trimmed = raw.trim(); let payload: (T & JsonErrorPayload) | null = null; if (trimmed) { try { payload = JSON.parse(trimmed) as T & JsonErrorPayload; } catch { if (!response.ok) { throw new Error(extractErrorMessage(response, trimmed, null)); } throw new Error(`Invalid JSON response from ${url}`); } } if (!response.ok) { throw new Error(extractErrorMessage(response, trimmed, payload)); } if (payload !== null) { return payload as T; } return {} as T; } export async function readErrorMessage(response: Response): Promise { const raw = (await response.text()).trim(); let payload: JsonErrorPayload | null = null; if (raw) { try { payload = JSON.parse(raw) as JsonErrorPayload; } catch { payload = null; } } return extractErrorMessage(response, raw, payload); }