/** * fetch wrapper ของ web portal — แนบ Bearer ทุก request, * โดน 401 → force refresh token แล้ว retry ครั้งเดียว (ยัง 401/refresh พัง → throw ให้ UI กลับไปหน้า login) */ import { AuthError } from './useAuth' export class ApiError extends Error { constructor( readonly status: number, message: string, ) { super(message) } } /** ดึงข้อความ error จาก response ของ backend (string ตรงๆ หรือ ProblemDetails) */ async function toApiError(res: Response): Promise { const fallback = `เรียก API ไม่สำเร็จ (HTTP ${res.status})` try { const text = (await res.text()).trim() if (!text) return new ApiError(res.status, fallback) try { const json = JSON.parse(text) as { title?: string; detail?: string } const msg = json.detail ?? json.title return new ApiError(res.status, msg ?? text) } catch { return new ApiError(res.status, text) // backend คืน message เป็น string เปล่าๆ (เช่น BadRequest) } } catch { return new ApiError(res.status, fallback) } } export function useApi() { const config = useRuntimeConfig() const base = (config.public.apiBaseUrl as string).replace(/\/+$/, '') const { getAccessToken } = useAuth() async function request(path: string, init: RequestInit = {}): Promise { const doFetch = async (forceRefresh: boolean): Promise => { let token: string try { token = await getAccessToken(forceRefresh) } catch (err) { // refresh token ตาย — AuthError พร้อมข้อความชี้นำให้ login ใหม่ (สถานะ auth ถูกเคลียร์ไปแล้ว) throw err instanceof AuthError ? new ApiError(401, err.message) : err } return fetch(base + path, { ...init, headers: { ...(init.headers as Record | undefined), Authorization: `Bearer ${token}` }, }) } let res = await doFetch(false) if (res.status === 401) res = await doFetch(true) // retry ครั้งเดียว return res } /** เรียก API ที่คืน JSON (หรือ 204 → undefined) — !ok จะ throw ApiError */ async function apiJson(path: string, init: RequestInit = {}): Promise { const res = await request(path, init) if (!res.ok) throw await toApiError(res) if (res.status === 204) return undefined return (await res.json()) as T } /** upload multipart — ห้าม set Content-Type เอง (browser ต้องใส่ boundary ให้) */ async function apiUpload(path: string, form: FormData, method: 'POST' | 'PUT' = 'POST'): Promise { return (await apiJson(path, { method, body: form })) as T } return { request, apiJson, apiUpload } }