Files
ui/src/api/client.ts
T

66 lines
2.1 KiB
TypeScript

let accessToken: string | null = null
export function setToken(token: string | null) {
accessToken = token
}
export function getToken() {
return accessToken
}
async function handleResponse<T>(res: Response): Promise<T> {
if (res.status === 204) return undefined as T
if (!res.ok) {
const text = await res.text().catch(() => '')
throw new Error(`${res.status}: ${text || res.statusText}`)
}
return res.json()
}
async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
const headers = new Headers(options.headers)
if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`)
if (options.body && typeof options.body === 'string') headers.set('Content-Type', 'application/json')
let res = await fetch(url, { ...options, headers })
if (res.status === 401 && accessToken) {
const refreshed = await refreshToken()
if (refreshed) {
headers.set('Authorization', `Bearer ${accessToken}`)
res = await fetch(url, { ...options, headers })
} else {
setToken(null)
window.location.href = '/login'
throw new Error('Session expired')
}
}
return handleResponse<T>(res)
}
async function refreshToken(): Promise<boolean> {
try {
const res = await fetch('/api/admin/auth/refresh', { method: 'POST', credentials: 'include' })
if (!res.ok) return false
const data = await res.json()
setToken(data.token)
return true
} catch {
return false
}
}
export const api = {
get: <T>(url: string) => request<T>(url),
post: <T>(url: string, body?: unknown) =>
request<T>(url, { method: 'POST', body: body ? JSON.stringify(body) : undefined }),
put: <T>(url: string, body?: unknown) =>
request<T>(url, { method: 'PUT', body: body ? JSON.stringify(body) : undefined }),
patch: <T>(url: string, body?: unknown) =>
request<T>(url, { method: 'PATCH', body: body ? JSON.stringify(body) : undefined }),
delete: <T>(url: string) => request<T>(url, { method: 'DELETE' }),
upload: <T>(url: string, formData: FormData) =>
request<T>(url, { method: 'POST', body: formData }),
}