62 lines
1.8 KiB
TypeScript
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;
|