Files
dumpsterChat/web/src/lib/api.ts
T

62 lines
1.8 KiB
TypeScript

const API_BASE = '/api/v1';
export interface ApiError extends Error {
status?: number;
retryAfter?: number;
}
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
const headers: Record<string, string> = {};
if (body !== undefined) {
headers['Content-Type'] = 'application/json';
}
const response = await fetch(API_BASE + path, {
method,
headers,
credentials: 'include',
body: body !== undefined ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
let errorMessage = 'Request failed: ' + response.status;
let retryAfter: number | undefined;
try {
const errorData = await response.json();
if (typeof errorData?.message === 'string') {
errorMessage = errorData.message;
} else if (typeof errorData?.error === 'string') {
errorMessage = errorData.error;
}
if (response.status === 429 && typeof errorData?.retry_after === 'number') {
retryAfter = errorData.retry_after;
}
} catch {
// ignore parse error
}
const err = new Error(errorMessage) as ApiError;
err.status = response.status;
if (response.status === 429) {
const ra = response.headers.get('Retry-After');
err.retryAfter = retryAfter ?? (ra ? parseInt(ra, 10) : undefined);
}
throw err;
}
if (response.status === 204) {
return undefined as T;
}
return response.json() as Promise<T>;
}
export const api = {
get: <T>(path: string) => request<T>('GET', path),
post: <T>(path: string, body?: unknown) => request<T>('POST', path, body),
put: <T>(path: string, body?: unknown) => request<T>('PUT', path, body),
patch: <T>(path: string, body?: unknown) => request<T>('PATCH', path, body),
delete: <T>(path: string) => request<T>('DELETE', path),
};
export default api;