Files
forge/client/src/services/api.service.ts
T

43 lines
1.5 KiB
TypeScript
Raw Normal View History

2026-08-29 11:59:28 +03:00
// Fired when any API call returns 401 (session expired/absent). App registers it
// to drop to the login screen.
let onUnauthorized: (() => void) | null = null;
export function setUnauthorizedHandler(fn: (() => void) | null): void {
onUnauthorized = fn;
}
interface ApiOptions extends RequestInit {
// When true, a 401 body is returned to the caller instead of throwing +
// firing the logout hook. Used by /login to surface { success, error }.
allowAuthErrorBody?: boolean;
}
export async function apiFetch<T>(url: string, options?: ApiOptions): Promise<T> {
const { allowAuthErrorBody, ...init } = options ?? {};
const res = await fetch(url, init);
if (!res.ok) {
if (res.status === 401) {
if (!allowAuthErrorBody) { onUnauthorized?.(); throw new Error('unauthenticated'); }
// else fall through and return the { success, error } body
} else if (res.status === 400 || res.status === 409) {
// Validation / conflict responses carry a { error, message } body the caller reads.
} else {
const text = await res.text().catch(() => res.statusText);
throw new Error(text || `HTTP ${res.status}`);
}
}
return res.json() as Promise<T>;
}
export function apiGet<T>(url: string): Promise<T> {
return apiFetch<T>(url);
}
export function apiPost<T>(url: string, body: unknown, options?: ApiOptions): Promise<T> {
return apiFetch<T>(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
...options,
});
}