261 lines
9.2 KiB
TypeScript
261 lines
9.2 KiB
TypeScript
import axios from 'axios';
|
||
|
||
// Получаем URL API из переменных окружения или используем значение по умолчанию
|
||
// Для клиентских запросов (из браузера)
|
||
export const PUBLIC_API_URL = process.env.NEXT_PUBLIC_API_URL || '/api';
|
||
// Для серверных запросов (SSR)
|
||
export const SERVER_API_URL = process.env.NEXT_SERVER_API_URL || 'http://localhost/api';
|
||
// Базовый URL для статических ресурсов
|
||
export const PUBLIC_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost';
|
||
|
||
// Определяем, выполняется ли код на сервере или в браузере
|
||
export const isServer = typeof window === 'undefined';
|
||
|
||
// Константа для ключа токена в localStorage
|
||
const TOKEN_KEY = 'token';
|
||
|
||
// Отладочный режим для API
|
||
export const apiStatus = {
|
||
// Используем переменную окружения для включения/выключения отладки
|
||
debugMode: process.env.NEXT_PUBLIC_DEBUG === 'true',
|
||
connectionError: false,
|
||
lastConnectionError: null as Error | null,
|
||
isAuthenticated: false as boolean, // Флаг аутентификации пользователя (не const)
|
||
SUCCESS: 'success',
|
||
ERROR: 'error'
|
||
};
|
||
|
||
// Для отладки выводим URL API в консоль
|
||
console.log('🔍 Режим отладки API включен');
|
||
console.error('🌐 Клиентский API URL:', PUBLIC_API_URL);
|
||
console.log('🖥️ Серверный API URL:', SERVER_API_URL);
|
||
console.log('📁 BASE URL:', PUBLIC_BASE_URL);
|
||
console.log('🔄 Среда выполнения:', isServer ? 'Сервер' : 'Браузер');
|
||
console.log('📋 ENV переменные:');
|
||
console.log(' - NEXT_PUBLIC_API_URL:', process.env.NEXT_PUBLIC_API_URL);
|
||
console.log(' - NEXT_SERVER_API_URL:', process.env.NEXT_SERVER_API_URL);
|
||
console.log(' - NEXT_PUBLIC_BASE_URL:', process.env.NEXT_PUBLIC_BASE_URL);
|
||
console.log(' - NEXT_PUBLIC_DEBUG:', process.env.NEXT_PUBLIC_DEBUG);
|
||
|
||
// Проверяем, не содержит ли URL порт 8000
|
||
if (PUBLIC_API_URL.includes(':8000')) {
|
||
console.warn('⚠️ Клиентский API URL содержит порт 8000, что может вызывать проблемы при работе через Nginx');
|
||
}
|
||
if (apiStatus.debugMode) {
|
||
}
|
||
|
||
// Получение токена из localStorage
|
||
const getToken = (): string | null => {
|
||
try {
|
||
return localStorage.getItem(TOKEN_KEY);
|
||
} catch (error) {
|
||
console.error('Ошибка при получении токена (api.ts):', error);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
// Создаем экземпляр клиента Axios с базовыми настройками
|
||
// Используем разные URL для клиента и сервера
|
||
const instance = axios.create({
|
||
baseURL: isServer ? SERVER_API_URL : PUBLIC_API_URL,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
timeout: 30000, // 30 секунд таймаут для запросов
|
||
});
|
||
|
||
// Логируем, какой URL используется
|
||
if (apiStatus.debugMode) {
|
||
console.log('🔌 Axios использует baseURL:', instance.defaults.baseURL);
|
||
}
|
||
|
||
// Добавляем перехватчик для исходящих запросов
|
||
instance.interceptors.request.use(
|
||
(config) => {
|
||
// Получаем токен из localStorage (если он есть)
|
||
const token = typeof window !== 'undefined' ? getToken() : null;
|
||
|
||
// Если токен есть, добавляем его в заголовки запроса
|
||
if (token) {
|
||
config.headers = config.headers || {};
|
||
config.headers.Authorization = `Bearer ${token}`;
|
||
apiStatus.isAuthenticated = true;
|
||
} else {
|
||
apiStatus.isAuthenticated = false;
|
||
}
|
||
|
||
// Логируем URL запроса в режиме отладки
|
||
if (apiStatus.debugMode) {
|
||
console.log(`API Request: ${config.method?.toUpperCase()} ${config.url}`);
|
||
}
|
||
|
||
return config;
|
||
},
|
||
(error) => {
|
||
// Обработка ошибок при подготовке запроса
|
||
console.error('API Request Error:', error);
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
// Добавляем перехватчик для входящих ответов
|
||
instance.interceptors.response.use(
|
||
function(response: any) {
|
||
// Обработка успешного ответа
|
||
if (apiStatus.debugMode) {
|
||
console.log(`API Response ${response.status} for ${response.config.url}`);
|
||
}
|
||
return response.data;
|
||
},
|
||
function(error: any) {
|
||
// Проверяем, является ли ошибка 401 (неавторизован) для запроса корзины
|
||
// и пользователь не аутентифицирован - это ожидаемое поведение
|
||
if (error.response && error.response.status === 401) {
|
||
// Для запросов к корзине, когда пользователь не аутентифицирован,
|
||
// просто возвращаем пустой объект без логирования ошибки
|
||
if (!apiStatus.isAuthenticated &&
|
||
error.config.url &&
|
||
(error.config.url.includes('/cart') || error.config.url.includes('/wishlist'))) {
|
||
return Promise.resolve({});
|
||
}
|
||
|
||
// Логируем 401 ошибку, но НЕ удаляем токен, чтобы избежать проблем при перезагрузке
|
||
console.log('Получен 401 ответ от API. Возможно, токен истек или недействителен.');
|
||
|
||
// Больше не перенаправляем на страницу входа автоматически
|
||
// это должно делаться на уровне компонентов проверки авторизации
|
||
}
|
||
|
||
// Логируем ошибки в режиме отладки
|
||
if (apiStatus.debugMode) {
|
||
if (error.response) {
|
||
console.error(`API Error ${error.response.status}: ${error.response.statusText}`);
|
||
console.error('API Error Response Data:', error.response.data);
|
||
} else if (error.request) {
|
||
console.error('API No Response Received:', error.request);
|
||
} else {
|
||
console.error('API Request Setup Error:', error.message);
|
||
}
|
||
}
|
||
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
// Общий интерфейс для ответа API
|
||
export interface ApiResponse<T> {
|
||
success: boolean;
|
||
data: T;
|
||
}
|
||
|
||
export interface ApiErrorResponse {
|
||
success: false;
|
||
error: string;
|
||
details?: any;
|
||
}
|
||
|
||
// API клиент
|
||
const api = {
|
||
// GET запрос
|
||
get: async <T>(url: string, params = {}): Promise<T> => {
|
||
try {
|
||
return await instance.get(url, { params }) as unknown as T;
|
||
} catch (error) {
|
||
if (apiStatus.debugMode) {
|
||
console.error(`GET Error for ${url}:`, error);
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// POST запрос
|
||
post: async <T>(url: string, data = {}, config = {}): Promise<T> => {
|
||
try {
|
||
return await instance.post(url, data, config) as unknown as T;
|
||
} catch (error) {
|
||
if (apiStatus.debugMode) {
|
||
console.error(`POST Error for ${url}:`, error);
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// PUT запрос
|
||
put: async <T>(url: string, data = {}, config = {}): Promise<T> => {
|
||
try {
|
||
return await instance.put(url, data, config) as unknown as T;
|
||
} catch (error) {
|
||
if (apiStatus.debugMode) {
|
||
console.error(`PUT Error for ${url}:`, error);
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// DELETE запрос
|
||
delete: async <T>(url: string, config = {}): Promise<T> => {
|
||
try {
|
||
return await instance.delete(url, config) as unknown as T;
|
||
} catch (error) {
|
||
if (apiStatus.debugMode) {
|
||
console.error(`DELETE Error for ${url}:`, error);
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
};
|
||
|
||
// Функция для выполнения запросов к API с унифицированным обработчиком ошибок
|
||
export async function fetchApi<T>(
|
||
url: string,
|
||
options: RequestInit = {}
|
||
): Promise<ApiResponse<T>> {
|
||
try {
|
||
const token = typeof window !== 'undefined' ? getToken() : null;
|
||
|
||
const headers: Record<string, string> = {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
...(options.headers as Record<string, string> || {})
|
||
};
|
||
|
||
if (token) {
|
||
headers['Authorization'] = `Bearer ${token}`;
|
||
}
|
||
|
||
// Выбираем правильный базовый URL в зависимости от среды выполнения
|
||
const baseUrl = isServer ? SERVER_API_URL : PUBLIC_API_URL;
|
||
|
||
// Логируем запрос в режиме отладки
|
||
if (apiStatus.debugMode) {
|
||
console.log(`🔄 fetchApi запрос: ${baseUrl}${url}`);
|
||
}
|
||
|
||
const response = await fetch(`${baseUrl}${url}`, {
|
||
...options,
|
||
headers,
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (!response.ok) {
|
||
return {
|
||
success: false,
|
||
data: data as T,
|
||
};
|
||
}
|
||
|
||
return {
|
||
success: true,
|
||
data: data as T,
|
||
};
|
||
} catch (error) {
|
||
return {
|
||
success: false,
|
||
data: {} as T,
|
||
};
|
||
}
|
||
}
|
||
|
||
export default api; |