marco.pms.web/src/utils/axiosClient.jsx

182 lines
5.2 KiB
JavaScript

import axios from "axios";
import { useNavigate } from "react-router-dom";
import axiosRetry from "axios-retry";
import showToast from "../services/toastService";
import { startSignalR, stopSignalR } from "../services/signalRService";
import { BASE_URL } from "./constants";
import { removeSession } from "./authUtils";
const base_Url = BASE_URL;
export const axiosClient = axios.create({
baseURL: base_Url,
withCredentials: false,
headers: {
"Content-Type": "application/json",
},
});
// Auto retry failed requests (e.g., network issues)
axiosRetry(axiosClient, { retries: 3 });
// Request Interceptor — Add Bearer token if required
axiosClient.interceptors.request.use(
async (config) => {
const requiresAuth = config.authRequired !== false; // default to true
if (requiresAuth) {
const token =
localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken");
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
config._retry = true;
} else {
config._retry = false;
}
}
return config;
},
(error) => Promise.reject(error)
);
// 🔄 Response Interceptor — Handle 401, refresh token, etc.
axiosClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Skip retry for public requests or already retried ones
if (
(!originalRequest && originalRequest._retry) ||
originalRequest.authRequired === false
) {
return Promise.reject(error);
}
// Avoid showing multiple toasts
if (!originalRequest._toastShown) {
originalRequest._toastShown = true;
if (error.code === "ERR_CONNECTION_REFUSED") {
showToast(
"Unable to connect to the server. Please try again later.",
"error"
);
} else if (error.code === "ERR_NETWORK") {
showToast("Network error. Please check your connection.", "error");
redirectToLogin();
} else if (error.code === "ECONNABORTED") {
showToast("Request timed out. Please try again.", "error");
} else if (error.response) {
const status = error.response.status;
const isRefreshRequest = error.config.url.includes("refresh-token");
if (status === 401 && !isRefreshRequest) {
originalRequest._retry = true;
const refreshToken = localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken");
if (
!refreshToken ||
error.response.data?.errors === "Invalid or expired refresh token."
) {
redirectToLogin();
removeSession()
return Promise.reject(error);
}
stopSignalR();
try {
// Refresh token call
const res = await axiosClient.post("/api/Auth/refresh-token", {
token: localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"),
refreshToken,
});
const { token, refreshToken: newRefreshToken } = res.data.data;
// Save updated tokens
if (localStorage.getItem("jwtToken")) {
localStorage.setItem("jwtToken", token);
localStorage.setItem("refreshToken", newRefreshToken);
} else {
sessionStorage.setItem("jwtToken", token);
sessionStorage.setItem("refreshToken", newRefreshToken);
}
startSignalR();
// Set Authorization header
originalRequest.headers["Authorization"] = `Bearer ${token}`;
return axiosClient(originalRequest);
} catch (refreshError) {
removeSession()
redirectToLogin();
return Promise.reject(refreshError);
}
}
} else {
showToast("An unknown error occurred.", "error");
}
}
return Promise.reject(error);
}
);
// Generic API function
const apiRequest = async (method, url, data = {}, config = {}) => {
try {
const response = await axiosClient({
method,
url,
data: method !== "get" ? data : undefined,
params: method === "get" ? data : undefined,
...config,
});
return response.data;
} catch (error) {
throw error;
}
};
// Exported API wrapper
export const api = {
// Public routes (no token required)
postPublic: (url, data = {}, customHeaders = {}) =>
apiRequest("post", url, data, {
headers: { ...customHeaders },
authRequired: false,
}),
// Authenticated routes
get: (url, params = {}, customHeaders = {}) =>
apiRequest("get", url, params, {
headers: { ...customHeaders },
authRequired: true,
}),
post: (url, data = {}, customHeaders = {}) =>
apiRequest("post", url, data, {
headers: { ...customHeaders },
authRequired: true,
}),
put: (url, data = {}, customHeaders = {}) =>
apiRequest("put", url, data, {
headers: { ...customHeaders },
authRequired: true,
}),
delete: (url, data = {}, customHeaders = {}) =>
apiRequest("delete", url, data, {
headers: { ...customHeaders },
authRequired: true,
}),
};
// Redirect helper
function redirectToLogin() {
window.location.href = "/auth/login";
}