Yassine Mhirsi
Add routing and analysis features
b6bed80
raw
history blame
2.8 kB
import type { ApiRequestOptions, ApiError } from '../types/api.types.ts';
import { getUserId } from '../utils/index.ts';
const DEFAULT_HEADERS: HeadersInit = {
'Content-Type': 'application/json',
};
const BASE_URL =
process.env.REACT_APP_API_BASE_URL?.replace(/\/$/, '');
export type { ApiRequestOptions, ApiError };
export async function apiRequest<TResponse = unknown, TBody = unknown>({
path,
method = 'GET',
body,
headers,
signal,
}: ApiRequestOptions<TBody>): Promise<TResponse> {
const url = `${BASE_URL}${path.startsWith('/') ? '' : '/'}${path}`;
// Automatically include user ID header if available
const userId = getUserId();
// Convert headers to plain object if needed
let baseHeaders: Record<string, string> = {};
if (headers instanceof Headers) {
baseHeaders = Object.fromEntries(headers.entries());
} else if (headers) {
baseHeaders = headers as Record<string, string>;
}
const requestHeaders: Record<string, string> = {
...(DEFAULT_HEADERS as Record<string, string>),
...baseHeaders,
};
if (userId) {
requestHeaders['X-User-ID'] = userId;
}
const response = await fetch(url, {
method,
headers: requestHeaders,
body: body ? JSON.stringify(body) : undefined,
signal,
});
const contentType = response.headers.get('content-type');
const isJson = contentType?.includes('application/json');
const payload = isJson ? await response.json().catch(() => undefined) : undefined;
if (!response.ok) {
const error: ApiError = {
status: response.status,
message:
(payload as { message?: string })?.message ||
response.statusText ||
'Request failed',
details: payload,
};
throw error;
}
return payload as TResponse;
}
export const api = {
get: <TResponse>(path: string, init?: Omit<ApiRequestOptions, 'path' | 'method'>) =>
apiRequest<TResponse>({ path, method: 'GET', ...init }),
post: <TResponse, TBody = unknown>(
path: string,
body?: TBody,
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>,
) => apiRequest<TResponse, TBody>({ path, method: 'POST', body, ...init }),
put: <TResponse, TBody = unknown>(
path: string,
body?: TBody,
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>,
) => apiRequest<TResponse, TBody>({ path, method: 'PUT', body, ...init }),
patch: <TResponse, TBody = unknown>(
path: string,
body?: TBody,
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>,
) => apiRequest<TResponse, TBody>({ path, method: 'PATCH', body, ...init }),
delete: <TResponse>(path: string, init?: Omit<ApiRequestOptions, 'path' | 'method'>) =>
apiRequest<TResponse>({ path, method: 'DELETE', ...init }),
};
export default api;