/** * HTTP Client implementation for React Native * * This implementation properly implements the core SDK's HttpClient interface * and uses fetch API which is available in React Native. */ /** * Request options for HTTP client operations * * @interface RequestOptions * @property {Record} [headers] - HTTP headers to include * @property {Record} [params] - URL parameters * @property {number} [timeout] - Request timeout in milliseconds * @property {'json' | 'blob' | 'text' | 'arraybuffer'} [responseType] - Expected response type */ export interface RequestOptions { headers?: Record; params?: Record; timeout?: number; responseType?: 'json' | 'blob' | 'text' | 'arraybuffer'; } /** * HTTP Client interface for React Native * * @interface HttpClient * @template T - Response type */ export interface HttpClient { get(url: string, options?: RequestOptions): Promise; post(url: string, body: any, options?: RequestOptions): Promise; put(url: string, body: any, options?: RequestOptions): Promise; delete(url: string, options?: RequestOptions): Promise; } export class ReactNativeHttpClient implements HttpClient { private baseURL?: string; constructor(baseURL?: string) { this.baseURL = baseURL; } async get(url: string, options?: RequestOptions): Promise { return this.request('GET', url, undefined, options); } async post(url: string, body?: any, options?: RequestOptions): Promise { return this.request('POST', url, body, options); } async put(url: string, body?: any, options?: RequestOptions): Promise { return this.request('PUT', url, body, options); } async delete(url: string, options?: RequestOptions): Promise { return this.request('DELETE', url, undefined, options); } private async request( method: string, url: string, body?: any, options?: RequestOptions ): Promise { const fullUrl = this.buildUrl(url, options?.params); const config: RequestInit = { method, headers: { 'Content-Type': 'application/json', ...options?.headers, }, }; // Add timeout if specified if (options?.timeout) { const controller = new AbortController(); config.signal = controller.signal; setTimeout(() => controller.abort(), options.timeout); } // Add body for POST/PUT requests if (body && (method === 'POST' || method === 'PUT')) { config.body = JSON.stringify(body); } try { const response = await fetch(fullUrl, config); if (!response.ok) { // Get raw error response for ErrorUtils to handle let errorText = ''; try { errorText = await response.text(); } catch (e) { // Ignore errors when reading error body } // Create simple error with HTTP status and raw response const httpError = new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`); (httpError as any).status = response.status; throw httpError; } // Handle different response types switch (options?.responseType) { case 'text': return response.text() as Promise; case 'blob': return response.blob() as Promise; case 'arraybuffer': return response.arrayBuffer() as Promise; case 'json': default: // Check if response has content const contentLength = response.headers.get('content-length'); if (contentLength === '0' || response.status === 204) { return undefined as T; } return response.json(); } } catch (error) { if (error instanceof Error && error.name === 'AbortError') { throw new Error(`Request timeout after ${options?.timeout}ms`); } throw error; } } private buildUrl(url: string, params?: Record): string { const fullUrl = this.baseURL ? `${this.baseURL}${url}` : url; if (!params || Object.keys(params).length === 0) { return fullUrl; } const urlObj = new URL(fullUrl); Object.entries(params).forEach(([key, value]) => { urlObj.searchParams.append(key, value); }); return urlObj.toString(); } }