/** * HTTP Utility — Lightweight GET/POST helpers * * Uses Node.js built-in http/https modules. * No external dependencies required. */ import type { IncomingMessage, RequestOptions } from 'node:http'; const DEFAULT_TIMEOUT_MS = 10_000; export interface HttpResponse { statusCode: number; data: T; } /** * Perform an HTTP GET request and parse JSON response. */ export async function httpGet( url: string, options?: { headers?: Record; timeoutMs?: number }, ): Promise { const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS; const isHttps = url.startsWith('https'); const client = await import(isHttps ? 'node:https' : 'node:http'); return new Promise((resolve, reject) => { const timeout = setTimeout(() => reject(new Error(`GET timeout: ${url}`)), timeoutMs); const reqOptions: RequestOptions = {}; if (options?.headers) { reqOptions.headers = options.headers; } client.get(url, reqOptions, (res: IncomingMessage) => { let data = ''; res.on('data', (chunk: string) => (data += chunk)); res.on('end', () => { clearTimeout(timeout); if (res.statusCode && res.statusCode >= 400) { try { const errJson = JSON.parse(data) as { error?: string }; return reject(new Error(errJson.error || `HTTP ${res.statusCode}`)); } catch { return reject(new Error(`HTTP ${res.statusCode}: ${data}`)); } } try { resolve(JSON.parse(data) as T); } catch { reject(new Error('Failed to parse response')); } }); }).on('error', (err: Error) => { clearTimeout(timeout); reject(err); }); }); } /** * Perform an HTTP POST request with JSON body and parse JSON response. */ export async function httpPost( url: string, body: unknown, timeoutMs = DEFAULT_TIMEOUT_MS, ): Promise { const isHttps = url.startsWith('https'); const httpModule = await import(isHttps ? 'node:https' : 'node:http'); return new Promise((resolve, reject) => { const timeout = setTimeout(() => reject(new Error(`POST timeout: ${url}`)), timeoutMs); const bodyStr = typeof body === 'string' ? body : JSON.stringify(body); const options: RequestOptions = { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(bodyStr), }, }; const req = httpModule.request(url, options, (res: IncomingMessage) => { let data = ''; res.on('data', (chunk: string) => (data += chunk)); res.on('end', () => { clearTimeout(timeout); if (res.statusCode && res.statusCode >= 400) { try { const errJson = JSON.parse(data) as { error?: string }; return reject(new Error(errJson.error || `HTTP ${res.statusCode}`)); } catch { return reject(new Error(`HTTP ${res.statusCode}: ${data}`)); } } try { resolve(JSON.parse(data) as T); } catch { reject(new Error('Failed to parse response')); } }); }); req.on('error', (err: Error) => { clearTimeout(timeout); reject(err); }); req.write(bodyStr); req.end(); }); }