feat: strengthen backup and mobile workflows
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
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'
|
||||
Reference in New Issue
Block a user