// http service hub // handles interceptors and such import axios from "axios"; const baseUrl: string = import.meta.env.DEV ? import.meta.env.VITE_DEV_API_URL : "https://app.vxbard.net/api" export const api = axios.create({ baseURL: baseUrl }); type FailedRequest = { resolve: (token: string) => void, reject: (error: unknown) => void} let isRefreshing: boolean = false; let failedQueue: FailedRequest[] = []; export const authStorage = { getAccessToken: () => localStorage.getItem("accessToken"), getRefreshToken: () => localStorage.getItem("refreshToken"), setTokens: ({ accessToken, refreshToken } : { accessToken: string, refreshToken: string }) => { localStorage.setItem("accessToken", accessToken) localStorage.setItem("refreshToken", refreshToken) }, clear: () => { localStorage.removeItem("accessToken") localStorage.removeItem("refreshToken") } } const processQueue = (error: unknown, token: string | null = null): void => { failedQueue.forEach(prom => { if (error) prom.reject(error); else prom.resolve(token as string); }) failedQueue = []; } // intercept on each request api.interceptors.request.use(config => { // add access token to request headers const token = authStorage.getAccessToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // intercept on each response api.interceptors.response.use(response => response, async error => { // mainly for authentication refreshTokens const originalRequest = error.config; // if un authorized then refresh the token if(error.response?.status === 401 && !originalRequest._retry) { if(isRefreshing) { return new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }) }).then(token => { originalRequest.headers.Authorization = `Bearer ${token}`; return api(originalRequest); }).catch(err => Promise.reject(err)); } originalRequest._retry = true; isRefreshing = true; const refreshToken = authStorage.getRefreshToken(); try { // request refresh endpoint get back a new accessToken const res = await axios.post(`${baseUrl}/auth/refresh`, { refreshToken }); const { accessToken, refreshToken: newRefresh } = res.data; authStorage.setTokens({ accessToken, refreshToken: newRefresh }); processQueue(null, accessToken); originalRequest.headers.Authorization = `Bearer ${accessToken}`; return api(originalRequest); } catch (err) { processQueue(err, null); authStorage.clear() window.location.href = "/login"; return Promise.reject(err); } finally { isRefreshing = false; } } return Promise.reject(error); }) // TODO: if you get a 403 while navigating then redirect to the last authenticated page // if you gert a 403 on a form submissio nthen do like an unauthorized popup (message: stale session ) (or redirect to login like i said elsewhere) export default api;