export const USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0 Safari/537.36"; export const DEFAULT_TIMEOUT = 15.0; export interface HttpResponseLike { text: string; status: number; headers: Record; raiseForStatus(): void; } export class HttpError extends Error { constructor(message: string) { super(message); this.name = "HttpError"; } } export class HttpStatusError extends HttpError { response: Pick; constructor(response: Pick) { super(`HTTP ${response.status}`); this.name = "HttpStatusError"; this.response = response; } } class HttpResponse implements HttpResponseLike { constructor( public readonly text: string, public readonly status: number, public readonly headers: Record, ) {} raiseForStatus(): void { if (this.status >= 400) { throw new HttpStatusError({ status: this.status, text: this.text, headers: this.headers, }); } } } function appendParams(url: URL, params?: Record): void { if (!params) return; for (const [key, value] of Object.entries(params)) { if (value === undefined || value === null) continue; if (Array.isArray(value)) { for (const item of value) { if (item === undefined || item === null) continue; url.searchParams.append(key, String(item)); } continue; } url.searchParams.append(key, String(value)); } } export class HttpClient { constructor( private readonly defaultHeaders: Record = { "User-Agent": USER_AGENT, Accept: "*/*", }, private readonly timeoutSeconds: number = DEFAULT_TIMEOUT, ) {} async get( url: string, opts?: { headers?: Record; params?: Record; }, ): Promise { const parsed = new URL(url); appendParams(parsed, opts?.params); const controller = new AbortController(); const timeout = globalThis.setTimeout(() => controller.abort(), this.timeoutSeconds * 1000); try { const response = await fetch(parsed.toString(), { method: "GET", headers: { ...this.defaultHeaders, ...(opts?.headers ?? {}), }, signal: controller.signal, }); const text = await response.text(); const headers = Object.fromEntries(response.headers.entries()); return new HttpResponse(text, response.status, headers); } catch (error) { if (error instanceof HttpError) { throw error; } throw new HttpError(`Request failed: ${error instanceof Error ? error.message : String(error)}`); } finally { clearTimeout(timeout); } } } export async function createHttpClient(): Promise { return new HttpClient(); } export type HttpxLikeClient = HttpClient;