(path: string, init?: ApiFetchInit)
| 46 | } |
| 47 | |
| 48 | async function apiResponse(path: string, init?: ApiFetchInit): Promise<Response> { |
| 49 | const url = new URL(`${API_BASE}${path}`, window.location.origin) |
| 50 | const { search, headers, ...fetchInit } = init ?? {} |
| 51 | if (search) { |
| 52 | for (const [key, value] of Object.entries(search)) { |
| 53 | if (value !== undefined && value !== '') { |
| 54 | url.searchParams.set(key, String(value)) |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | const makeHeaders = (overrideToken?: string) => { |
| 60 | const h = new Headers(headers) |
| 61 | const isFormDataBody = typeof FormData !== 'undefined' && fetchInit.body instanceof FormData |
| 62 | if (!h.has('content-type') && !isFormDataBody) { |
| 63 | h.set('content-type', 'application/json') |
| 64 | } |
| 65 | const token = overrideToken ?? useAuthStore.getState().accessToken |
| 66 | if (token && !h.has('authorization')) { |
| 67 | h.set('authorization', `Bearer ${token}`) |
| 68 | } |
| 69 | return h |
| 70 | } |
| 71 | |
| 72 | let response = await fetch(url, { |
| 73 | ...fetchInit, |
| 74 | headers: makeHeaders(), |
| 75 | }) |
| 76 | |
| 77 | if (response.status === 401) { |
| 78 | let newToken: string |
| 79 | try { |
| 80 | newToken = await refreshAccess() |
| 81 | } catch { |
| 82 | useAuthStore.getState().clearAuth() |
| 83 | window.location.href = '/login' |
| 84 | throw new ApiError('Unauthorized', 401) |
| 85 | } |
| 86 | response = await fetch(url, { |
| 87 | ...fetchInit, |
| 88 | headers: makeHeaders(newToken), |
| 89 | }) |
| 90 | } |
| 91 | |
| 92 | if (!response.ok) { |
| 93 | const text = await response.text() |
| 94 | throw new ApiError( |
| 95 | text || response.statusText, |
| 96 | response.status, |
| 97 | response.headers.get('x-request-id') ?? undefined, |
| 98 | ) |
| 99 | } |
| 100 | |
| 101 | return response |
| 102 | } |
| 103 | |
| 104 | export async function apiFetch<T>(path: string, init?: ApiFetchInit): Promise<T> { |
| 105 | const response = await apiResponse(path, init) |
no test coverage detected