import { deleteAccount, updateAccountTokens } from "@/repository/account"; import { useDbStore } from "@/store/db"; import { useAuth } from "@/store/auth"; import Axios, { type AxiosResponse } from "axios"; import { refreshTokenApi } from "./refresh"; import { isExpired } from "react-jwt"; export const axios = Axios.create({ headers: { "Content-Type": "application/json", }, }); let refreshQueue: ((token: string | null) => void)[] = []; const waitForTokenRefresh = () => { return new Promise((resolve) => { refreshQueue.push((token: string | null) => resolve(token)); }); }; const processRefreshQueue = async (token: string | null) => { refreshQueue.forEach((resolve) => resolve(token)); refreshQueue = []; }; const refreshToken = async ( accountId: string, refreshToken: string, ): Promise<{ access: string; refresh: string }> => { const db = useDbStore.getState().db; const loadAccounts = useAuth.getState().loadAccounts; const requireSignIn = useAuth.getState().requireSignIn; if (!db) { console.log("No database connection available."); return Promise.reject("No database connection available."); } try { const response = await refreshTokenApi(refreshToken); await updateAccountTokens(db, { accountId: accountId, access: response.access, refresh: response.refresh, }); processRefreshQueue(response.access); return { access: response.access, refresh: response.refresh }; } catch (err) { console.error("Token refresh failed:", err); await deleteAccount(db, accountId); requireSignIn?.(); processRefreshQueue(null); throw err; } finally { localStorage.removeItem("refreshing"); loadAccounts?.(); window.guard.refreshing = false; } }; axios.interceptors.request.use( async (request) => { const account = useAuth.getState().activeAccount; let token: string | null = account?.access ?? null; if (!token || !isExpired(token)) { request.headers["Authorization"] = `Bearer ${token}`; return request; } if (!window.guard.refreshing) { console.log(`request to ${request.url} is refreshing token`); window.guard.refreshing = true; try { const { access } = await refreshToken( account!.accountId, account!.refresh, ); token = access; } catch (err) { console.error("Token refresh failed:", err); throw err; } } else { console.log(`request to ${request.url} is waiting for token`); token = await waitForTokenRefresh(); console.log(`request to ${request.url} waited for token:`, token); } if (!token) { throw new Error("No token available"); } request.headers["Authorization"] = `Bearer ${token}`; return request; }, (error) => Promise.reject(error), ); export const handleApiError = async (response: AxiosResponse) => { const text = response.data?.error || response.data?.toString?.() || "unexpected error happened"; return new Error(text[0].toUpperCase() + text.slice(1)); };