wrongmove/frontend/src/services/apiClient.ts

84 lines
1.9 KiB
TypeScript
Raw Normal View History

// Generic API client with authentication
import type { AuthUser } from '@/auth/types';
import { ApiError } from '@/types';
let onUnauthorized: (() => void) | null = null;
export function setOnUnauthorized(handler: () => void): void {
onUnauthorized = handler;
}
export function fireUnauthorized(): void {
if (onUnauthorized) {
onUnauthorized();
}
}
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
params?: Record<string, string | number | boolean | Date | undefined>;
body?: unknown;
}
/**
* Build query string from parameters object
*/
function buildQueryString(params: Record<string, string | number | boolean | Date | undefined>): string {
const queryString = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null && value !== '') {
if (value instanceof Date) {
queryString.append(key, value.toISOString());
} else {
queryString.append(key, String(value));
}
}
}
return queryString.toString();
}
/**
* Generic authenticated API request
*/
export async function apiRequest<T>(
user: AuthUser,
endpoint: string,
options: RequestOptions = {}
): Promise<T> {
const { method = 'GET', params, body } = options;
let url = endpoint;
if (params) {
const queryString = buildQueryString(params);
if (queryString) {
url = `${endpoint}?${queryString}`;
}
}
const fetchOptions: RequestInit = {
method,
headers: {
Authorization: `Bearer ${user.accessToken}`,
'Content-Type': 'application/json',
},
};
if (body !== undefined) {
fetchOptions.body = JSON.stringify(body);
}
const response = await fetch(url, fetchOptions);
if (!response.ok) {
if (response.status === 401) {
fireUnauthorized();
}
throw new ApiError(`Error: ${response.status}`, response.status);
}
return response.json() as Promise<T>;
}