49 lines
2.3 KiB
TypeScript
49 lines
2.3 KiB
TypeScript
import { csrfHeader } from '../lib/csrf'
|
|
import { ApiError, formatApiErrorDetail } from './errors'
|
|
|
|
export type JsonRequestOptions = Omit<RequestInit, 'body'> & { body?: unknown }
|
|
|
|
function apiUrl(path: string) {
|
|
return path.startsWith('/api/') ? path : `/api/v1${path.startsWith('/') ? path : `/${path}`}`
|
|
}
|
|
|
|
async function apiFetch(path: string, options: RequestInit = {}) {
|
|
const headers = new Headers(options.headers)
|
|
const csrf = csrfHeader(options.method)
|
|
if (csrf['x-csrf-token']) headers.set('x-csrf-token', csrf['x-csrf-token'])
|
|
const response = await fetch(apiUrl(path), { credentials: 'include', ...options, headers })
|
|
if (!response.ok) {
|
|
const contentType = response.headers.get('content-type') || ''
|
|
const body = contentType.includes('json') ? await response.json().catch(() => ({})) : await response.text().catch(() => '')
|
|
const detail = body && typeof body === 'object' && 'detail' in body ? body.detail : body
|
|
const code = body && typeof body === 'object' && typeof body.code === 'string'
|
|
? body.code
|
|
: detail && typeof detail === 'object' && !Array.isArray(detail) && typeof detail.code === 'string' ? detail.code : undefined
|
|
throw new ApiError(formatApiErrorDetail(detail), response.status, code, detail)
|
|
}
|
|
return response
|
|
}
|
|
|
|
export async function requestJson<T>(path: string, options: JsonRequestOptions = {}): Promise<T> {
|
|
const headers = new Headers(options.headers)
|
|
const { body: ignoredBody, ...requestOptions } = options
|
|
void ignoredBody
|
|
const body = options.body === undefined ? undefined : JSON.stringify(options.body)
|
|
if (body !== undefined) headers.set('content-type', 'application/json')
|
|
const response = await apiFetch(path, { ...requestOptions, headers, body })
|
|
return response.status === 204 ? undefined as T : await response.json() as T
|
|
}
|
|
|
|
export async function requestBlob(path: string, options: RequestInit = {}): Promise<Blob> {
|
|
return (await apiFetch(path, options)).blob()
|
|
}
|
|
|
|
export async function uploadJson<T>(path: string, file: File, options: Omit<RequestInit, 'body'> = {}): Promise<T> {
|
|
const form = new FormData()
|
|
form.append('file', file)
|
|
const response = await apiFetch(path, { ...options, method: options.method ?? 'POST', body: form })
|
|
return response.status === 204 ? undefined as T : await response.json() as T
|
|
}
|
|
|
|
export { ApiError } from './errors'
|