Files
edr-platform/apps/edr-freight-web/portal/src/utils/api.ts

131 lines
3.5 KiB
TypeScript

import {
UseQueryOptions,
QueryObserverOptions,
} from "@tanstack/react-query";
import axios, { AxiosError, InternalAxiosRequestConfig } from "axios";
import { URL_CONSTANTS } from "@/constants/URLS";
const client = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
function getCookie(name: string): string | undefined {
return document.cookie
.split("; ")
.find((row) => row.startsWith(`${name}=`))
?.split("=")[1];
}
function setCookie(name: string, value: string, days: number) {
const expires = new Date();
expires.setDate(expires.getDate() + days);
document.cookie = `${name}=${value}; path=/; expires=${expires.toUTCString()}; SameSite=Lax`;
}
function clearAuthCookies() {
["auth-token", "refresh-token", "auth-user", "current-position-id", "selected-position-id"].forEach(
(name) => {
document.cookie = `${name}=; Max-Age=0; path=/`;
},
);
}
// Attach auth token to every request
client.interceptors.request.use((config) => {
const token = getCookie("auth-token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Token refresh state
let isRefreshing = false;
let failedQueue: {
resolve: (token: string) => void;
reject: (error: unknown) => void;
}[] = [];
function processQueue(error: unknown, token?: string) {
failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error);
} else {
resolve(token!);
}
});
failedQueue = [];
}
// Handle auth errors globally with token refresh
client.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig & {
_retry?: boolean;
};
// Don't intercept if:
// - no response (network error)
// - status is not 401
// - already retried
// - it's the refresh endpoint itself
if (
!error.response ||
error.response.status !== 401 ||
originalRequest._retry ||
originalRequest.url === URL_CONSTANTS.AUTH.REFRESH_TOKEN
) {
return Promise.reject(error);
}
if (isRefreshing) {
return new Promise<string>((resolve, reject) => {
failedQueue.push({ resolve, reject });
}).then((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
return client(originalRequest);
});
}
originalRequest._retry = true;
isRefreshing = true;
const refreshToken = getCookie("refresh-token");
if (!refreshToken) {
isRefreshing = false;
clearAuthCookies();
if (window.location.pathname !== "/login") {
window.location.href = "/login";
}
return Promise.reject(error);
}
try {
const { data } = await client.post<{ data: { token: string; refreshToken: string } }>(
URL_CONSTANTS.AUTH.REFRESH_TOKEN,
{ refreshToken },
);
const { token, refreshToken: newRefreshToken } = data.data;
setCookie("auth-token", token, 7);
setCookie("refresh-token", newRefreshToken, 7);
originalRequest.headers.Authorization = `Bearer ${token}`;
processQueue(null, token);
return client(originalRequest);
} catch (refreshError) {
processQueue(refreshError, undefined);
clearAuthCookies();
if (window.location.pathname !== "/login") {
window.location.href = "/login";
}
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
},
);
export { client };
export type { UseQueryOptions, QueryObserverOptions };