import { IHttpClient } from '@/common/http'; import { xUserAgent } from '../../common/http/xua'; import { context } from '@/common/context'; import { getH5ApiHost, platform } from '@jolibox/common'; declare global { interface Window { JoliboxHttpClient: typeof JoliboxHttpClient; } } interface IHttpClientInitParams { baseUrl?: string; } const timeoutFn = (ms: number) => { const ctrl = new AbortController(); setTimeout(() => ctrl.abort(), ms); return ctrl.signal; }; (AbortSignal as any).timeout ??= timeoutFn; export class HttpClientManager { private static _instance: HttpClientManager; private httpClients = new WeakSet(); private networkRequests: boolean[] = []; private maxRequestsToTrack = 20; static getInstance(): HttpClientManager { if (!HttpClientManager._instance) { HttpClientManager._instance = new HttpClientManager(); } return HttpClientManager._instance; } create(config?: IHttpClientInitParams): JoliboxHttpClient { const client = new JoliboxHttpClient(config); HttpClientManager.getInstance().httpClients.add(client); return client; } recordNetworkRequest(success: boolean): void { this.networkRequests.push(success); if (this.networkRequests.length > this.maxRequestsToTrack) { this.networkRequests.shift(); } } getNetworkStatus = (): boolean => { if (this.networkRequests.length === 0) { return true; } const successCount = this.networkRequests.filter((success) => success).length; const successRate = successCount / this.networkRequests.length; return successRate >= 0.6; }; } export class JoliboxHttpClient implements IHttpClient { private baseUrl: string; protected xua = xUserAgent(); private getJoliSource = () => { return context.joliSource; }; constructor(config?: IHttpClientInitParams) { const defaultUrl = getH5ApiHost(context.testMode); this.baseUrl = config?.baseUrl ?? defaultUrl; } async get( path: string, configs?: { query?: Record; headers?: Record; timeout?: number; } ) { try { const { query, timeout } = configs ?? {}; let { headers } = configs ?? {}; const searchParams = query ? new URLSearchParams(query) : null; const search = searchParams?.toString(); const url = `${this.baseUrl}${path}${search ? `?${search}` : ''}`; const xua = this.xua; const joliSource = this.getJoliSource(); headers = Object.assign( {}, headers ?? {}, xua ? { 'x-user-agent': xua } : {}, joliSource ? { 'x-joli-source': joliSource } : {} ); const response = await fetch(url, { method: 'GET', headers, credentials: 'include', signal: timeoutFn(timeout ?? 30000) }); HttpClientManager.getInstance().recordNetworkRequest(true); return (await response.json()) as T; } catch (error) { HttpClientManager.getInstance().recordNetworkRequest(false); throw error; } } async post( path: string, configs?: { data?: any; query?: Record; headers?: Record; timeout?: number; } ) { try { const { data, query, timeout } = configs ?? {}; let { headers } = configs ?? {}; const searchParams = query ? new URLSearchParams(query) : null; const search = searchParams?.toString(); const url = `${this.baseUrl}${path}${search ? `?${search}` : ''}`; const xua = this.xua; const joliSource = this.getJoliSource(); headers = Object.assign( {}, headers ?? {}, { 'Content-Type': 'application/json' }, xua ? { 'x-user-agent': xua } : {}, joliSource ? { 'x-joli-source': joliSource } : {}, { 'x-os-type': platform.isAndroid ? 'ANDROID' : platform.isiOS ? 'IOS' : 'PC' }, { 'x-runtime-type': 'WEB' } ); const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(data), signal: timeoutFn(timeout ?? 30000), credentials: 'include' }); HttpClientManager.getInstance().recordNetworkRequest(true); const responseContentType = response.headers.get('content-type'); if (responseContentType?.includes('application/octet-stream')) { try { return response.blob() as unknown as T; } catch (e) { return (await response.arrayBuffer()) as unknown as T; } } if ( responseContentType?.includes('multipart/form-data') || responseContentType?.includes('application/x-www-form-urlencoded') ) { try { return response.formData() as unknown as T; } catch (e) { return (await response.text()) as unknown as T; } } if (responseContentType?.includes('application/json')) { try { return (await response.json()) as T; } catch (e) { return (await response.text()) as unknown as T; } } return response as unknown as T; } catch (error) { HttpClientManager.getInstance().recordNetworkRequest(false); throw error; } } async put( path: string, configs?: { data?: any; query?: Record; headers?: Record; timeout?: number; } ) { try { const { data, query, timeout } = configs ?? {}; let { headers } = configs ?? {}; const searchParams = query ? new URLSearchParams(query) : null; const search = searchParams?.toString(); const url = `${this.baseUrl}${path}${search ? `?${search}` : ''}`; const xua = this.xua; const joliSource = this.getJoliSource(); headers = Object.assign( {}, headers ?? {}, { 'Content-Type': 'application/json' }, xua ? { 'x-user-agent': xua } : {}, joliSource ? { 'x-joli-source': joliSource } : {} ); const response = await fetch(url, { method: 'PUT', headers, credentials: 'include', body: JSON.stringify(data ?? {}) }); if (!response.ok) { HttpClientManager.getInstance().recordNetworkRequest(false); throw new Error(`HTTP error! status: ${response.status}`); } HttpClientManager.getInstance().recordNetworkRequest(true); const result = (await response.json()) as T; return result; } catch (error) { console.error('Error:', error); throw error; } } } window.JoliboxHttpClient = JoliboxHttpClient; export const httpClientManager = HttpClientManager.getInstance();