Files
gridpilot.gg/apps/website/lib/api/base/BaseApiClient.ts
2025-12-28 21:02:32 +01:00

101 lines
2.7 KiB
TypeScript

/**
* Base API Client for HTTP operations
*
* Provides generic HTTP methods with common request/response handling,
* error handling, and authentication.
*/
import { Logger } from '../../interfaces/Logger';
import { ErrorReporter } from '../../interfaces/ErrorReporter';
export class BaseApiClient {
protected baseUrl: string;
private errorReporter: ErrorReporter;
private logger: Logger;
constructor(baseUrl: string, errorReporter: ErrorReporter, logger: Logger) {
this.baseUrl = baseUrl;
this.errorReporter = errorReporter;
this.logger = logger;
}
protected async request<T>(
method: string,
path: string,
data?: object | FormData,
options?: { allowUnauthenticated?: boolean },
): Promise<T> {
this.logger.info(`${method} ${path}`);
const isFormData = typeof FormData !== 'undefined' && data instanceof FormData;
const headers: HeadersInit = isFormData
? {}
: {
'Content-Type': 'application/json',
};
const config: RequestInit = {
method,
headers,
credentials: 'include', // Include cookies for auth
};
if (data) {
config.body = isFormData ? data : JSON.stringify(data);
}
const response = await fetch(`${this.baseUrl}${path}`, config);
if (!response.ok) {
if (
options?.allowUnauthenticated &&
(response.status === 401 || response.status === 403)
) {
// For "auth probe" endpoints (e.g. session/policy checks), 401/403 is an expected state
// in public context and should not be logged as an application error.
return null as T;
}
let errorData: { message?: string } = { message: response.statusText };
try {
errorData = await response.json();
} catch {
// Keep default error message
}
const error = new Error(
errorData.message || `API request failed with status ${response.status}`,
) as Error & { status?: number };
error.status = response.status;
this.errorReporter.report(error);
throw error;
}
const text = await response.text();
if (!text) {
return null as T;
}
return JSON.parse(text) as T;
}
protected get<T>(path: string): Promise<T> {
return this.request<T>('GET', path);
}
protected post<T>(path: string, data: object): Promise<T> {
return this.request<T>('POST', path, data);
}
protected put<T>(path: string, data: object): Promise<T> {
return this.request<T>('PUT', path, data);
}
protected delete<T>(path: string): Promise<T> {
return this.request<T>('DELETE', path);
}
protected patch<T>(path: string, data: object): Promise<T> {
return this.request<T>('PATCH', path, data);
}
}