matchday-v3 / lib /api-client.ts
mzidan000's picture
Publish verified MatchDay v3 source
6d528b3 verified
Raw
History Blame Contribute Delete
2.09 kB
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>;
}