const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '/api' export function apiUrl(path: string) { return `${API_BASE_URL}${path}` } let csrfToken: string | null = null export class ApiError extends Error { status: number payload: unknown constructor(status: number, message: string, payload: unknown) { super(message) this.name = 'ApiError' this.status = status this.payload = payload } } async function readError(response: Response) { let payload: unknown try { payload = await response.json() } catch { payload = null } const detail = payload && typeof payload === 'object' && 'detail' in payload ? String((payload as { detail: unknown }).detail) : `API 请求失败(${response.status})` return new ApiError(response.status, detail, payload) } async function getCsrfToken() { if (!csrfToken) { const response = await fetch(apiUrl('/accounts/auth/csrf'), { credentials: 'include' }) if (!response.ok) { throw await readError(response) } const data = (await response.json()) as { csrfToken: string } csrfToken = data.csrfToken } return csrfToken } export async function apiRequest(path: string, init?: RequestInit): Promise { const headers = new Headers(init?.headers) if (init?.body && !(init.body instanceof FormData) && !headers.has('content-type')) { headers.set('content-type', 'application/json') } const method = init?.method?.toUpperCase() ?? 'GET' if (!['GET', 'HEAD', 'OPTIONS'].includes(method) && !headers.has('x-csrftoken')) { headers.set('x-csrftoken', await getCsrfToken()) } const response = await fetch(apiUrl(path), { ...init, credentials: 'include', headers }) if (!response.ok) { throw await readError(response) } if (response.status === 204) { return undefined as T } return response.json() as Promise }