import axios, { AxiosRequestConfig } from 'axios'; export * from './auth'; export * from './gacha'; export * from './users'; export * from './mentors'; export * from './upload'; export * from './hackathon'; export * from './admin'; // Common API response wrapper interface export interface ApiResponse { data: T; version: string; } const TOKEN_KEY = 'token'; // Helper functions for session management (avoiding circular dependency) const getSessionTokenFromCookies = () => { if (typeof document === 'undefined') return null; const cookies = document.cookie.split(';'); const tokenCookie = cookies.find((cookie) => cookie.trim().startsWith(`${TOKEN_KEY}=`) ); if (!tokenCookie) return null; try { const tokenValue = tokenCookie.split('=')[1]; return JSON.parse(decodeURIComponent(tokenValue)); } catch { return null; } }; const setSessionTokenToCookies = (tokenData: { token: { access_token: string; refresh_token: string }; }) => { if (typeof document === 'undefined') return; const expires = new Date(); expires.setDate(expires.getDate() + 7); document.cookie = `${TOKEN_KEY}=${encodeURIComponent( JSON.stringify(tokenData) )}; expires=${expires.toUTCString()}; path=/; secure; samesite=strict`; }; const removeSessionTokenFromCookies = () => { if (typeof document === 'undefined') return; document.cookie = `${TOKEN_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; }; const config: AxiosRequestConfig = { baseURL: import.meta.env.VITE_API_URL, }; export const api = axios.create(config); // Add request interceptor to include authentication token api.interceptors.request.use( (config) => { const sessionData = getSessionTokenFromCookies(); const token = sessionData?.token?.access_token; if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(new Error(error.message || 'Request failed')); } ); // Add response interceptor to handle token refresh api.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; return handleTokenRefresh(originalRequest); } // If backend sends a message, use it const backendMsg = error?.response?.data?.message; if (backendMsg && typeof backendMsg === 'string') { return Promise.reject(new Error(backendMsg)); } return Promise.reject(new Error(error.message || 'Request failed')); } ); // Helper function to handle token refresh async function handleTokenRefresh(originalRequest: AxiosRequestConfig) { const sessionData = getSessionTokenFromCookies(); const refreshToken = sessionData?.token?.refresh_token; if (!refreshToken) { clearSessionAndRedirect(); throw new Error('No refresh token available'); } try { const response = await refreshAccessToken(refreshToken); if (response.data?.access_token) { // Update session in cookies setSessionTokenToCookies({ token: { access_token: response.data.access_token, refresh_token: response.data.refresh_token || refreshToken, }, }); // Retry original request with new token originalRequest.headers ??= {}; originalRequest.headers.Authorization = `Bearer ${response.data.access_token}`; return api(originalRequest); } throw new Error('Invalid refresh response'); } catch (refreshError) { console.error('Token refresh failed:', refreshError); clearSessionAndRedirect(); throw new Error('Token refresh failed'); } } // Helper function to refresh access token async function refreshAccessToken(refreshToken: string) { return axios.post(`${import.meta.env.VITE_API_URL}/auth/refresh`, { refresh_token: refreshToken, }); } // Helper function to clear session and redirect function clearSessionAndRedirect() { removeSessionTokenFromCookies(); if (typeof window !== 'undefined') { window.location.href = '/auth/login'; } }