Files
elmprodvpn/selective-vpn-web/src/shared/api/http.ts

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)
}
}