| export class ApiError extends Error { |
| constructor( |
| message: string, |
| readonly status: number, |
| ) { |
| super(message); |
| } |
| } |
|
|
| let sessionReady: Promise<void> | null = null; |
|
|
| async function ensureSession(): Promise<void> { |
| if (!sessionReady) { |
| sessionReady = fetch("/api/session", { cache: "no-store" }).then((response) => { |
| if (!response.ok) throw new Error("Could not initialize the private MatchDay session"); |
| }); |
| } |
| try { |
| await sessionReady; |
| } catch (error) { |
| sessionReady = null; |
| throw error; |
| } |
| } |
|
|
| function readableDetail(detail: unknown, fallback: string): string { |
| if (typeof detail === "string" && detail.trim()) return detail; |
| if (Array.isArray(detail)) { |
| const messages = detail |
| .map((item) => { |
| if (!item || typeof item !== "object") return String(item || ""); |
| const record = item as { msg?: unknown; loc?: unknown }; |
| const message = typeof record.msg === "string" ? record.msg : "Invalid value"; |
| const location = Array.isArray(record.loc) |
| ? record.loc.filter((part) => part !== "body").join(".") |
| : ""; |
| return location ? `${location}: ${message}` : message; |
| }) |
| .filter(Boolean); |
| if (messages.length) return messages.join("; "); |
| } |
| if (detail && typeof detail === "object" && "msg" in detail) { |
| const message = (detail as { msg?: unknown }).msg; |
| if (typeof message === "string") return message; |
| } |
| return fallback; |
| } |
|
|
| export async function api<T>(path: string, init?: RequestInit): Promise<T> { |
| await ensureSession(); |
| const response = await fetch(`/api/backend${path}`, { |
| ...init, |
| headers: { |
| "content-type": "application/json", |
| ...(init?.headers || {}), |
| }, |
| cache: "no-store", |
| }); |
| if (!response.ok) { |
| const payload = await response.json().catch(() => ({})); |
| throw new ApiError( |
| readableDetail(payload.detail, `Request failed (${response.status})`), |
| response.status, |
| ); |
| } |
| if (response.status === 204) return undefined as T; |
| return response.json() as Promise<T>; |
| } |
|
|