Files
vontor-cz/frontend/src/api/privateClient.ts
2026-05-19 16:52:55 +02:00

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;
};