63 lines
1.5 KiB
TypeScript
63 lines
1.5 KiB
TypeScript
import { env } from '../config/env'
|
|
|
|
type RequestJsonOptions = Omit<RequestInit, 'body'> & {
|
|
timeoutMs?: number
|
|
body?: unknown
|
|
}
|
|
|
|
export class HttpError extends Error {
|
|
public readonly status: number
|
|
public readonly payload: string
|
|
|
|
constructor(message: string, status: number, payload: string) {
|
|
super(message)
|
|
this.status = status
|
|
this.payload = payload
|
|
}
|
|
}
|
|
|
|
export function apiUrl(path: string): string {
|
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`
|
|
if (!env.apiBaseUrl) {
|
|
return normalizedPath
|
|
}
|
|
return `${env.apiBaseUrl}${normalizedPath}`
|
|
}
|
|
|
|
export async function requestJson<T>(path: string, options: RequestJsonOptions = {}): Promise<T> {
|
|
const controller = new AbortController()
|
|
const timeoutMs = options.timeoutMs ?? 8000
|
|
|
|
const headers = new Headers(options.headers || {})
|
|
const hasBody = options.body !== undefined
|
|
if (hasBody && !headers.has('Content-Type')) {
|
|
headers.set('Content-Type', 'application/json')
|
|
}
|
|
|
|
const timeout = setTimeout(() => {
|
|
controller.abort()
|
|
}, timeoutMs)
|
|
|
|
try {
|
|
const response = await fetch(apiUrl(path), {
|
|
...options,
|
|
headers,
|
|
body: hasBody ? JSON.stringify(options.body) : undefined,
|
|
signal: controller.signal,
|
|
})
|
|
|
|
const text = await response.text()
|
|
if (!response.ok) {
|
|
throw new HttpError(`Request failed: ${response.status}`, response.status, text)
|
|
}
|
|
|
|
if (!text) {
|
|
return {} as T
|
|
}
|
|
|
|
return JSON.parse(text) as T
|
|
} finally {
|
|
clearTimeout(timeout)
|
|
}
|
|
}
|