Files
imphnen-frontend-service/libs/service/src/api/index.ts
T

151 lines
4.0 KiB
TypeScript

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<T> {
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';
}
}