buster/apps/web/src/api/createAxiosInstance.ts

96 lines
3.1 KiB
TypeScript

import { isServer } from '@tanstack/react-query';
import type { AxiosRequestHeaders } from 'axios';
import axios, { type AxiosError, type InternalAxiosRequestConfig } from 'axios';
import type { SupabaseContextReturnType } from '@/context/Supabase/SupabaseContextProvider';
import { BusterRoutes, createBusterRoute } from '@/routes';
import { rustErrorHandler } from './buster_rest/errors';
import { getSupabaseTokenFromCookies } from './createServerInstance';
const AXIOS_TIMEOUT = 120000; // 2 minutes
export const createAxiosInstance = (baseURL: string) => {
const apiInstance = axios.create({
baseURL,
timeout: AXIOS_TIMEOUT,
headers: {
'Content-Type': 'application/json'
}
});
// Response interceptor with retry logic for auth errors
apiInstance.interceptors.response.use(
(resp) => {
return resp;
},
async (error: AxiosError) => {
const errorCode = error.response?.status;
//402 is the payment required error code
if (errorCode === 402) {
window.location.href = createBusterRoute({
route: BusterRoutes.INFO_GETTING_STARTED
});
return Promise.reject(rustErrorHandler(error));
}
// Handle 401 Unauthorized - token might be expired
if (errorCode === 401 && !isServer) {
// Only retry once to avoid infinite loops
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
if (originalRequest && !originalRequest._retry) {
originalRequest._retry = true;
try {
// Force token refresh and retry the request
console.info('401 error detected, attempting to refresh token and retry request');
// The request interceptor will handle getting the new token
return apiInstance(originalRequest);
} catch (refreshError) {
console.error('Failed to refresh token and retry request:', refreshError);
// If refresh fails, redirect to login or show error
window.location.href = createBusterRoute({
route: BusterRoutes.AUTH_LOGIN
});
}
}
}
return Promise.reject(rustErrorHandler(error));
}
);
apiInstance.interceptors.request.use(defaultAxiosRequestHandler);
return apiInstance;
};
export const defaultAxiosRequestHandler = async (
config: InternalAxiosRequestConfig<unknown>,
options?: {
checkTokenValidity: SupabaseContextReturnType['checkTokenValidity'];
}
) => {
let token = '';
try {
if (isServer) {
token = await getSupabaseTokenFromCookies();
} else {
// Always check token validity before making requests
const tokenResult = await options?.checkTokenValidity?.();
token = tokenResult?.access_token || '';
}
if (!token && options?.checkTokenValidity) {
throw new Error('User authentication error - no token found');
}
(config.headers as AxiosRequestHeaders).Authorization = `Bearer ${token}`;
return config;
} catch (error) {
console.error('Error getting auth token for request:', error);
throw new Error('User authentication error - failed to get valid token');
}
};