import { ApiException, type ApiError } from './types' const BASE = '' // 同源;dev 走 vite proxy const DEFAULT_TIMEOUT_MS = 30_000 interface RequestOptions { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' body?: unknown signal?: AbortSignal } let authTokenProvider: () => string | null = () => null let onUnauthorized: () => void = () => {} let onForbidden: () => void = () => {} export function configure(opts: { tokenProvider: () => string | null onUnauthorized: () => void onForbidden?: () => void }) { authTokenProvider = opts.tokenProvider onUnauthorized = opts.onUnauthorized if (opts.onForbidden) onForbidden = opts.onForbidden } export async function request(path: string, opts: RequestOptions = {}): Promise { const isForm = opts.body instanceof FormData const headers: Record = { 'X-Client-Request-ID': crypto.randomUUID(), } if (!isForm) headers['Content-Type'] = 'application/json' const token = authTokenProvider() if (token) headers['Authorization'] = `Bearer ${token}` const controller = new AbortController() const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS) if (opts.signal) { if (opts.signal.aborted) { controller.abort() } else { opts.signal.addEventListener('abort', () => controller.abort(), { once: true }) } } try { const body = opts.body === undefined ? undefined : isForm ? (opts.body as FormData) : JSON.stringify(opts.body) const resp = await fetch(BASE + path, { method: opts.method || 'GET', headers, body, signal: controller.signal, }) if (resp.status === 401) { onUnauthorized() } else if (resp.status === 403) { onForbidden() } if (resp.status === 204) { return undefined as T } const text = await resp.text() const data = text ? JSON.parse(text) : null if (!resp.ok) { throw new ApiException( resp.status, (data as ApiError) || { code: 'unknown', message: resp.statusText }, ) } return data as T } finally { clearTimeout(timer) } } // buildQuery 把任意对象拼成 "?k=v&..." 形式;undefined / null / '' 跳过; // 数组与对象用 String() 退化(业务侧目前没有数组 query 需求)。 export function buildQuery(params: object): string { const q = new URLSearchParams() for (const [k, v] of Object.entries(params)) { if (v === undefined || v === null || v === '') continue q.set(k, String(v)) } const s = q.toString() return s ? '?' + s : '' }