Files
router-lists-ui/frontend/src/lib/api.js
T

68 lines
2.3 KiB
JavaScript

import axios from 'axios';
// Базовый axios-клиент для всего приложения
const api = axios.create({
baseURL: '/api',
timeout: 10000,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
});
// Авто-ретрай для идемпотентных GET: до 2 попыток с экспоненциальной задержкой
api.interceptors.response.use(
(response) => response,
async (error) => {
const config = error?.config || {};
const isGet = String(config.method || 'get').toLowerCase() === 'get';
const status = error?.response?.status;
const retriable = !error.response || (status >= 500 && status !== 501);
config.__retryCount = config.__retryCount || 0;
if (isGet && retriable && config.__retryCount < 2) {
config.__retryCount += 1;
const delay = 300 * Math.pow(2, config.__retryCount - 1);
await new Promise((r) => setTimeout(r, delay));
return api(config);
}
return Promise.reject(error);
}
);
// Нормализация ошибок и уведомления по умолчанию
api.interceptors.response.use(
(res) => res,
(err) => {
try {
const status = err?.response?.status;
const message = err?.response?.data?.message || err?.message || 'Ошибка запроса';
if (status >= 400 && typeof window !== 'undefined' && window.notify?.error) {
window.notify.error(`${message} (${status ?? '—'})`);
}
} catch {}
return Promise.reject(err);
}
);
// Request интерсептор: If-Match из etag (если не задан явный заголовок)
api.interceptors.request.use((config) => {
try {
// Если заголовок не указан, но в теле есть etag — пробуем проставить If-Match
if (!config.headers?.['If-Match'] && config.data && typeof config.data === 'object' && config.data.etag) {
config.headers = config.headers || {};
config.headers['If-Match'] = String(config.data.etag);
}
} catch {}
return config;
});
// Утилита: опциональная распаковка стандартного формата
export function unwrapStd(res) {
const data = res?.data;
if (data && typeof data === 'object' && Array.isArray(data.items)) return data.items;
return data;
}
export default api;