forked from sagnik/Velocity-OS
53 lines
1.6 KiB
TypeScript
53 lines
1.6 KiB
TypeScript
/**
|
|
* Velocity-OS API Client
|
|
* Thin wrapper around fetch. Injects JWT auth header automatically.
|
|
* All requests go to /api (proxied by Traefik to core-api:8443).
|
|
*/
|
|
import { useAuthStore } from '@/store/authStore';
|
|
|
|
const BASE_URL = '/api';
|
|
|
|
export class ApiError extends Error {
|
|
constructor(public status: number, message: string) {
|
|
super(message);
|
|
this.name = 'ApiError';
|
|
}
|
|
}
|
|
|
|
async function apiFetch<T>(
|
|
path: string,
|
|
options: RequestInit = {}
|
|
): Promise<T> {
|
|
const token = useAuthStore.getState().token;
|
|
|
|
const response = await fetch(`${BASE_URL}${path}`, {
|
|
...options,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
...(options.headers ?? {}),
|
|
},
|
|
});
|
|
|
|
if (response.status === 401) {
|
|
// Token expired — clear session and let AuthGuard redirect
|
|
useAuthStore.getState().clearSession();
|
|
throw new ApiError(401, 'Session expired');
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const body = await response.text().catch(() => '');
|
|
throw new ApiError(response.status, body || `HTTP ${response.status}`);
|
|
}
|
|
|
|
if (response.status === 204) return undefined as T;
|
|
return response.json();
|
|
}
|
|
|
|
export const api = {
|
|
get: <T>(path: string) => apiFetch<T>(path),
|
|
post: <T>(path: string, body: unknown) => apiFetch<T>(path, { method: 'POST', body: JSON.stringify(body) }),
|
|
patch: <T>(path: string, body: unknown) => apiFetch<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
|
|
delete: <T>(path: string) => apiFetch<T>(path, { method: 'DELETE' }),
|
|
};
|