| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- export const apiBaseUrl = import.meta.env.VITE_API_URL ?? "http://localhost:3001";
- export function extractErrorMessage(payload, status) {
- if (payload?.msg) return payload.msg;
- if (payload?.error) return payload.error;
- if (payload?.message) return payload.message;
- return `http_error:${status}`;
- }
- export class ApiError extends Error {
- constructor(category, message, extra = {}) {
- super(message || category);
- this.category = category;
- Object.assign(this, extra);
- }
- }
- export function categorizeHttpStatus(status) {
- if (status === 401) return "invalid_credentials";
- if (status === 429) return "locked";
- return "server";
- }
- let _accessToken = null;
- let _refreshFn = null;
- let _authErrorFn = null;
- let _refreshPromise = null;
- export function setAccessToken(token) {
- _accessToken = token ?? null;
- }
- export function setRefreshCallback(fn) {
- _refreshFn = fn ?? null;
- }
- export function setAuthErrorCallback(fn) {
- _authErrorFn = fn ?? null;
- }
- export function buildAuthHeaders(extra = {}) {
- const headers = { "content-type": "application/json", ...extra };
- if (_accessToken) headers["Authorization"] = `Bearer ${_accessToken}`;
- return headers;
- }
- export async function refreshAccessToken() {
- if (!_refreshFn) return null;
- if (!_refreshPromise) {
- _refreshPromise = _refreshFn().finally(() => { _refreshPromise = null; });
- }
- return _refreshPromise;
- }
- export function notifyAuthError() {
- _authErrorFn?.();
- }
- export async function apiFetch(path, { method = "GET", body } = {}) {
- const headers = { "content-type": "application/json" };
- if (_accessToken) headers["Authorization"] = `Bearer ${_accessToken}`;
- let res;
- try {
- res = await fetch(`${apiBaseUrl}${path}`, {
- method,
- headers,
- body: body ? JSON.stringify(body) : undefined
- });
- } catch {
- throw new ApiError("network", "network_error");
- }
- if (res.status === 401 && _refreshFn) {
- let newToken;
- try {
- newToken = await refreshAccessToken();
- } catch {
- _authErrorFn?.();
- throw new Error("session_expired");
- }
- if (newToken) {
- headers["Authorization"] = `Bearer ${newToken}`;
- try {
- res = await fetch(`${apiBaseUrl}${path}`, {
- method,
- headers,
- body: body ? JSON.stringify(body) : undefined
- });
- } catch {
- throw new ApiError("network", "network_error");
- }
- }
- }
- if (res.status === 401) {
- _authErrorFn?.();
- throw new Error("session_expired");
- }
- if (!res.ok) {
- const text = await res.text().catch(() => "");
- let payload = null;
- try { payload = JSON.parse(text); } catch {}
- throw new Error(extractErrorMessage(payload, res.status) || text || `http_error:${res.status}`);
- }
- const contentType = res.headers.get("content-type") ?? "";
- if (contentType.includes("application/json")) return res.json();
- return res.text();
- }
|