/** Kleine Hilfen für Aufrufe der eigenen API aus Client-Komponenten. */ async function readError(response: Response): Promise { let body: unknown = null; try { body = await response.json(); } catch { // Antwort ohne JSON-Körper — unten kommt die allgemeine Meldung. } const message = body !== null && typeof body === "object" && "error" in body && typeof (body as { error: unknown }).error === "string" ? (body as { error: string }).error : `Die Anfrage ist fehlgeschlagen (HTTP ${response.status}).`; return new ApiError(message, response.status, body); } /** * Fehler einer eigenen API-Antwort. * * Trägt neben der Meldung auch den Status und den Antwortkörper: Manche * Fehler sind keine Sackgasse, sondern eine Rückfrage — etwa die gemeldete * Dublette beim Ablegen, zu der die Oberfläche den vorhandenen Eintrag * anzeigen will. */ export class ApiError extends Error { readonly status: number; readonly details: unknown; constructor(message: string, status = 0, details: unknown = null) { super(message); this.status = status; this.details = details; } } export async function apiJson( url: string, init?: RequestInit & { json?: unknown }, ): Promise { const { json, ...rest } = init ?? {}; const response = await fetch(url, { ...rest, headers: json === undefined ? rest.headers : { "Content-Type": "application/json", ...rest.headers }, body: json === undefined ? rest.body : JSON.stringify(json), }); if (!response.ok) { throw await readError(response); } if (response.status === 204) { return undefined as T; } return (await response.json()) as T; } /** Holt eine Datei von der API und stößt den Browser-Download an. */ export async function downloadFile( url: string, json: unknown, fallbackName: string, ): Promise { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(json), }); if (!response.ok) { throw await readError(response); } const disposition = response.headers.get("Content-Disposition") ?? ""; const match = /filename="([^"]+)"/.exec(disposition); const blob = await response.blob(); const objectUrl = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = objectUrl; anchor.download = match?.[1] ?? fallbackName; document.body.append(anchor); anchor.click(); anchor.remove(); // Der Browser braucht den Blob noch einen Moment für den Download. setTimeout(() => URL.revokeObjectURL(objectUrl), 10_000); }