api-client.ts60 lines · main
| 1 | export interface ApiCallOptions { |
| 2 | apiOrigin: string; |
| 3 | apiKey?: string; |
| 4 | bearer?: string; |
| 5 | method?: string; |
| 6 | body?: unknown; |
| 7 | headers?: Record<string, string>; |
| 8 | } |
| 9 | |
| 10 | export class ApiCallError extends Error { |
| 11 | constructor( |
| 12 | readonly status: number, |
| 13 | readonly code: string, |
| 14 | message: string, |
| 15 | ) { |
| 16 | super(message); |
| 17 | this.name = 'ApiCallError'; |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | export async function apiCall<T>(path: string, options: ApiCallOptions): Promise<T> { |
| 22 | const authHeaders: Record<string, string> = {}; |
| 23 | if (options.bearer && options.bearer.length > 0) { |
| 24 | authHeaders.authorization = `Bearer ${options.bearer}`; |
| 25 | } else if (options.apiKey && options.apiKey.length > 0) { |
| 26 | authHeaders.authorization = `Bearer ${options.apiKey}`; |
| 27 | } else { |
| 28 | throw new Error('apiCall: either apiKey or bearer must be provided'); |
| 29 | } |
| 30 | |
| 31 | const headers: Record<string, string> = { |
| 32 | accept: 'application/json', |
| 33 | ...authHeaders, |
| 34 | ...(options.headers ?? {}), |
| 35 | }; |
| 36 | if (options.body !== undefined) headers['content-type'] = 'application/json'; |
| 37 | |
| 38 | const res = await fetch(`${options.apiOrigin}${path}`, { |
| 39 | method: options.method ?? 'GET', |
| 40 | headers, |
| 41 | body: options.body !== undefined ? JSON.stringify(options.body) : undefined, |
| 42 | }); |
| 43 | |
| 44 | if (!res.ok) { |
| 45 | const text = await res.text().catch(() => ''); |
| 46 | let code = 'http_error'; |
| 47 | let message = text || res.statusText; |
| 48 | try { |
| 49 | const parsed = JSON.parse(text) as { code?: string; message?: string }; |
| 50 | if (parsed.code) code = parsed.code; |
| 51 | if (parsed.message) message = parsed.message; |
| 52 | } catch { |
| 53 | // leave defaults |
| 54 | } |
| 55 | throw new ApiCallError(res.status, code, message); |
| 56 | } |
| 57 | |
| 58 | if (res.status === 204) return undefined as T; |
| 59 | return (await res.json()) as T; |
| 60 | } |