interface ApiConfig { baseUrl: string; defaultHeaders?: Record>; maxRetries?: number; initialRetryDelay?: number; } interface FetchOptions extends RequestInit { retries?: number; } export class Api { private baseUrl: string; private headers: Record>; private maxRetries: number; private initialRetryDelay: number; constructor(config: ApiConfig) { this.baseUrl = config.baseUrl; this.headers = { 'Content-Type': 'application/json', ...config.defaultHeaders, }; this.maxRetries = config.maxRetries ?? 3; this.initialRetryDelay = config.initialRetryDelay ?? 500; } private async resolveHeaders(): Promise> { const resolvedHeaders: Record = {}; for (const [key, value] of Object.entries(this.headers)) { const resolvedValue = await value; if (resolvedValue !== null) { resolvedHeaders[key] = resolvedValue; } } return resolvedHeaders; } public addHeader(key: string, value: string | Promise) { this.headers[key] = value; } private async post( url: string, data: ReqBody, options: FetchOptions, attempt: number, ): Promise { try { const response = await fetch(url, { method: 'POST', headers: await this.resolveHeaders(), body: JSON.stringify(data ?? {}), keepalive: true, ...options, }); if (response.status === 401) return null; if (response.status !== 200 && response.status !== 202) { throw new Error(`HTTP error! status: ${response.status}`); } const responseText = await response.text(); return responseText ? JSON.parse(responseText) : null; } catch (error) { if (attempt < this.maxRetries) { const delay = this.initialRetryDelay * 2 ** attempt; await new Promise((resolve) => setTimeout(resolve, delay)); return this.post(url, data, options, attempt + 1); } console.error('Max retries reached:', error); return null; } } async fetch( path: string, data: ReqBody, options: FetchOptions = {}, ): Promise { const url = `${this.baseUrl}${path}`; return this.post(url, data, options, 0); } }