Use the filepond-image field, parse the API envelope, and load private files through the authenticated content endpoint so uploads persist and images display.
135 lines
4.1 KiB
JavaScript
135 lines
4.1 KiB
JavaScript
// /src/api/axiosInstance.js
|
|
import axios from 'axios';
|
|
import Cookies from 'js-cookie';
|
|
|
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api';
|
|
|
|
const axiosInstance = axios.create({
|
|
baseURL: API_BASE_URL,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
timeout: 15000
|
|
});
|
|
|
|
// Request interceptor: attach token
|
|
axiosInstance.interceptors.request.use(
|
|
(config) => {
|
|
const token = localStorage.getItem('accessToken') || Cookies.get('accessToken');
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
|
|
// Ask backend for Persian (or stored) localized error messages
|
|
const lang = localStorage.getItem('lang') || 'fa';
|
|
config.headers['Accept-Language'] = lang;
|
|
if (!config.params) config.params = {};
|
|
if (config.params.lang == null) {
|
|
config.params.lang = lang;
|
|
}
|
|
|
|
const isFormData = typeof FormData !== 'undefined' && config.data instanceof FormData;
|
|
if (isFormData) {
|
|
// Let the browser set multipart boundary — a bare multipart/form-data header breaks Multer
|
|
if (typeof config.headers?.delete === 'function') {
|
|
config.headers.delete('Content-Type');
|
|
config.headers.delete('content-type');
|
|
} else {
|
|
delete config.headers['Content-Type'];
|
|
delete config.headers['content-type'];
|
|
}
|
|
config.timeout = Math.max(config.timeout || 0, 120000);
|
|
}
|
|
|
|
return config;
|
|
},
|
|
(error) => Promise.reject(error)
|
|
);
|
|
|
|
// Response interceptor: auto-refresh token on 401
|
|
let isRefreshing = false;
|
|
let failedQueue = [];
|
|
|
|
const processQueue = (error, token = null) => {
|
|
failedQueue.forEach(prom => {
|
|
if (error) {
|
|
prom.reject(error);
|
|
} else {
|
|
prom.resolve(token);
|
|
}
|
|
});
|
|
failedQueue = [];
|
|
};
|
|
|
|
axiosInstance.interceptors.response.use(
|
|
(response) => response.data,
|
|
async (error) => {
|
|
const originalRequest = error.config;
|
|
|
|
if (error.response?.status === 401 && !originalRequest._retry) {
|
|
if (originalRequest.url.includes('/auth/login') || originalRequest.url.includes('/auth/refresh')) {
|
|
return Promise.reject(error);
|
|
}
|
|
|
|
if (isRefreshing) {
|
|
return new Promise((resolve, reject) => {
|
|
failedQueue.push({ resolve, reject });
|
|
})
|
|
.then((token) => {
|
|
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
return axiosInstance(originalRequest);
|
|
})
|
|
.catch((err) => Promise.reject(err));
|
|
}
|
|
|
|
originalRequest._retry = true;
|
|
isRefreshing = true;
|
|
|
|
const refreshToken = localStorage.getItem('refreshToken') || Cookies.get('refreshToken');
|
|
|
|
if (!refreshToken) {
|
|
isRefreshing = false;
|
|
localStorage.removeItem('accessToken');
|
|
localStorage.removeItem('refreshToken');
|
|
window.location.href = '/login';
|
|
return Promise.reject(error);
|
|
}
|
|
|
|
try {
|
|
const refreshResponse = await axios.post(`${API_BASE_URL}/auth/refresh`, { refreshToken });
|
|
const data = refreshResponse.data?.data || refreshResponse.data;
|
|
const newAccessToken = data.accessToken;
|
|
const newRefreshToken = data.refreshToken;
|
|
|
|
localStorage.setItem('accessToken', newAccessToken);
|
|
Cookies.set('accessToken', newAccessToken);
|
|
if (newRefreshToken) {
|
|
localStorage.setItem('refreshToken', newRefreshToken);
|
|
Cookies.set('refreshToken', newRefreshToken);
|
|
}
|
|
|
|
axiosInstance.defaults.headers.common.Authorization = `Bearer ${newAccessToken}`;
|
|
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
|
|
|
|
processQueue(null, newAccessToken);
|
|
return axiosInstance(originalRequest);
|
|
} catch (refreshErr) {
|
|
processQueue(refreshErr, null);
|
|
localStorage.removeItem('accessToken');
|
|
localStorage.removeItem('refreshToken');
|
|
Cookies.remove('accessToken');
|
|
Cookies.remove('refreshToken');
|
|
window.location.href = '/login';
|
|
return Promise.reject(refreshErr);
|
|
} finally {
|
|
isRefreshing = false;
|
|
}
|
|
}
|
|
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
export default axiosInstance;
|