import { FailureResponse } from './errors'; import type { BaseResponse, IStorage } from './types/common'; type HttpRequestOptions = { method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; body?: any; contentType?: 'json' | 'form'; authenticated?: boolean; queryParams?: Record; }; function createFormData(body: any): string { return Object.keys(body) .map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(body[key])) .join('&'); } function buildUrl( base: string, path: string, queryParams?: Record ): string { const url = `${base}${path}`; if (!queryParams) return url; const params = Object.entries(queryParams) .filter(([, v]) => v !== undefined && v !== null) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`) .join('&'); return params ? `${url}?${params}` : url; } class HttpClient { private apiUrl: string; private storage: IStorage; private apiKey?: string; constructor(apiUrl: string, storage: IStorage, apiKey?: string) { this.apiUrl = apiUrl; this.storage = storage; this.apiKey = apiKey; } getApiUrl(): string { return this.apiUrl; } getStorage(): IStorage { return this.storage; } async getToken(): Promise { return await this.storage.getItem('token'); } /** * Raw HTTP request. Returns parsed JSON. Throws FailureResponse on HTTP errors. */ async request(path: string, options: HttpRequestOptions = {}): Promise { const { method = 'GET', body, contentType = 'json', authenticated = true, queryParams } = options; const headers: Record = { Accept: 'application/json' }; if (contentType === 'json') { headers['Content-Type'] = 'application/json'; } else if (contentType === 'form') { headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; } // API key takes priority if set if (this.apiKey) { headers['x-api-key'] = this.apiKey; } if (authenticated) { const token = await this.getToken(); if (token && token.length > 0) { headers['Authorization'] = `Bearer ${token}`; } } let requestBody: string | undefined; if (body !== undefined) { if (contentType === 'form') { requestBody = typeof body === 'string' ? body : createFormData(body); } else { requestBody = typeof body === 'string' ? body : JSON.stringify(body); } } const url = buildUrl(this.apiUrl, path, queryParams); const request = new Request(url, { method, headers, body: requestBody }); const response = await fetch(request); if (response.status === 401) { throw FailureResponse.handled('iam.error.unauthorized'); } if (response.status === 403) { throw FailureResponse.handled('iam.error.not-authorized'); } if (!response.ok) { throw FailureResponse.unhandled(response.statusText); } return response.json() as Promise; } /** * Like request() but also verifies responseCode === 'ok'. * Throws FailureResponse if responseCode is not 'ok'. */ async requestAndValidate( path: string, options: HttpRequestOptions = {} ): Promise { const data = await this.request(path, options); if (data.responseCode !== 'ok') { throw FailureResponse.handled(data.responseCode); } return data; } } export { HttpClient, createFormData }; export type { HttpRequestOptions };