182 lines
5.2 KiB
JavaScript
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";
|
|
}
|