104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
import axios, { type AxiosRequestConfig } from "axios";
|
|
import { AUTH_FLAG } from "@/hooks/useAuth";
|
|
|
|
export const privateApi = axios.create({
|
|
withCredentials: true,
|
|
baseURL: '',
|
|
});
|
|
|
|
let isRefreshing = false;
|
|
let failedQueue: Array<{
|
|
resolve: (value?: unknown) => void;
|
|
reject: (reason?: any) => void;
|
|
}> = [];
|
|
|
|
const processQueue = (error: any = null) => {
|
|
failedQueue.forEach((promise) => {
|
|
if (error) {
|
|
promise.reject(error);
|
|
} else {
|
|
promise.resolve();
|
|
}
|
|
});
|
|
failedQueue = [];
|
|
};
|
|
|
|
privateApi.interceptors.response.use(
|
|
(res) => res,
|
|
async (error) => {
|
|
const original = error.config;
|
|
|
|
if (error.response?.status === 400 && error.response?.data) {
|
|
const data = error.response.data;
|
|
if (typeof data === 'object' && !Array.isArray(data)) {
|
|
const firstKey = Object.keys(data)[0];
|
|
if (firstKey && Array.isArray(data[firstKey]) && data[firstKey].length > 0) {
|
|
error.message = data[firstKey][0];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((error.response?.status === 401 || error.response?.status === 403) && !original._retry) {
|
|
if (original.url?.includes("/api/account/logout/")) {
|
|
return Promise.reject(error);
|
|
}
|
|
|
|
if (error.response?.data?.code === "user_not_found") {
|
|
processQueue(error);
|
|
isRefreshing = false;
|
|
localStorage.removeItem(AUTH_FLAG);
|
|
window.location.href = "/social/login";
|
|
return Promise.reject(error);
|
|
}
|
|
|
|
if (original.url?.includes("/api/account/token/refresh/")) {
|
|
processQueue(error);
|
|
isRefreshing = false;
|
|
localStorage.removeItem(AUTH_FLAG);
|
|
try {
|
|
await privateApi.post("/api/account/logout/");
|
|
} catch {
|
|
// cookies may already be expired
|
|
}
|
|
window.location.href = "/social/login";
|
|
return Promise.reject(error);
|
|
}
|
|
|
|
if (isRefreshing) {
|
|
return new Promise((resolve, reject) => {
|
|
failedQueue.push({ resolve, reject });
|
|
})
|
|
.then(() => privateApi(original))
|
|
.catch((err) => Promise.reject(err));
|
|
}
|
|
|
|
original._retry = true;
|
|
isRefreshing = true;
|
|
|
|
try {
|
|
await privateApi.post("/api/account/token/refresh/");
|
|
processQueue();
|
|
return privateApi(original);
|
|
} catch (refreshError: any) {
|
|
processQueue(refreshError);
|
|
// Refresh failed for any reason (400 missing cookie, 401 expired) — clear auth and redirect
|
|
localStorage.removeItem(AUTH_FLAG);
|
|
window.location.href = "/social/login";
|
|
return Promise.reject(refreshError);
|
|
} finally {
|
|
isRefreshing = false;
|
|
}
|
|
}
|
|
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
export const privateMutator = async <T>(config: AxiosRequestConfig): Promise<T> => {
|
|
if (config.data instanceof FormData) {
|
|
delete config.headers?.['Content-Type'];
|
|
}
|
|
const response = await privateApi.request<T>(config);
|
|
return response.data;
|
|
};
|