import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import { API_CONFIG } from '../config/api'; import { keycloak } from '../config/keycloak'; import { KEYCLOAK_TIMEOUTS } from '../config/keycloak'; // Créer une instance axios avec configuration par défaut depuis API_CONFIG const axiosInstance: AxiosInstance = axios.create({ baseURL: API_CONFIG.baseURL, timeout: API_CONFIG.timeout, headers: API_CONFIG.headers, }); // Intercepteur pour ajouter le token d'authentification axiosInstance.interceptors.request.use( async (config) => { // Vérifier si nous avons un token d'accès stocké if (typeof window !== 'undefined') { const accessToken = localStorage.getItem('accessToken'); if (accessToken) { // Ajouter le token Bearer à l'en-tête Authorization config.headers.Authorization = `Bearer ${accessToken}`; console.log('🔐 API Request avec token:', config.method?.toUpperCase(), config.url); } else { console.log('⚠️ API Request sans token:', config.method?.toUpperCase(), config.url); } } else { // Fallback vers l'ancien système pour la rétrocompatibilité if (typeof window !== 'undefined') { const token = localStorage.getItem('accessToken') || localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } } } return config; }, (error) => { return Promise.reject(error); } ); // Intercepteur pour gérer les réponses et erreurs axiosInstance.interceptors.response.use( (response: AxiosResponse) => { return response; }, (error) => { // Gérer les erreurs d'authentification et d'autorisation if (error.response?.status === 401 || error.response?.status === 403) { // Ne pas rediriger si on est en train de traiter un code d'autorisation if (typeof window !== 'undefined') { const currentUrl = window.location.href; const hasAuthCode = currentUrl.includes('code=') && currentUrl.includes('/dashboard'); console.log(`🔍 API Client ${error.response?.status}:`, { currentUrl, hasAuthCode }); if (!hasAuthCode) { // Token expiré, invalide ou permissions insuffisantes - sauvegarder la page actuelle et rediriger console.log(`🔄 API Client: Erreur ${error.response?.status}, redirection vers Keycloak...`); // Sauvegarder la page actuelle pour y revenir après reconnexion const currentPath = window.location.pathname + window.location.search; localStorage.setItem('returnUrl', currentPath); // Nettoyer les anciens tokens localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); localStorage.removeItem('idToken'); // Rediriger vers Keycloak pour reconnexion window.location.href = '/api/auth/login'; } else { console.log(`🔄 API Client: Erreur ${error.response?.status} ignorée car authentification en cours...`); } } } // Gérer les erreurs serveur if (error.response?.status >= 500) { console.error('Erreur serveur:', error.response?.data || error.message); } return Promise.reject(error); } ); // Interface pour les réponses API standardisées export interface ApiResponse { data: T; message?: string; success?: boolean; } // Interface pour les erreurs API export interface ApiError { message: string; code?: string; details?: any; } // Client API avec méthodes utilitaires export class ApiClient { private instance: AxiosInstance; constructor(baseURL?: string) { this.instance = baseURL ? axios.create({ baseURL }) : axiosInstance; } // GET request async get(url: string, config?: AxiosRequestConfig): Promise> { return this.instance.get(url, config); } // POST request async post(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.instance.post(url, data, config); } // PUT request async put(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.instance.put(url, data, config); } // DELETE request async delete(url: string, config?: AxiosRequestConfig): Promise> { return this.instance.delete(url, config); } // PATCH request async patch(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.instance.patch(url, data, config); } // Upload file async upload(url: string, file: File, config?: AxiosRequestConfig): Promise> { const formData = new FormData(); formData.append('file', file); return this.instance.post(url, formData, { ...config, headers: { ...config?.headers, 'Content-Type': 'multipart/form-data', }, }); } // Upload multiple files async uploadMultiple(url: string, files: File[], config?: AxiosRequestConfig): Promise> { const formData = new FormData(); files.forEach((file, index) => { formData.append(`files[${index}]`, file); }); return this.instance.post(url, formData, { ...config, headers: { ...config?.headers, 'Content-Type': 'multipart/form-data', }, }); } // Download file async download(url: string, filename?: string, config?: AxiosRequestConfig): Promise { const response = await this.instance.get(url, { ...config, responseType: 'blob', }); // Créer un lien de téléchargement const blob = new Blob([response.data]); const downloadUrl = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = downloadUrl; link.download = filename || 'download'; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(downloadUrl); } // Set auth token setAuthToken(token: string): void { this.instance.defaults.headers.common['Authorization'] = `Bearer ${token}`; } // Remove auth token removeAuthToken(): void { delete this.instance.defaults.headers.common['Authorization']; } } // Instance par défaut exportée export const apiClient = new ApiClient(); // Export de l'instance axios brute pour les cas particuliers export { axiosInstance }; // Utilitaires pour gérer les erreurs export const handleApiError = (error: any): ApiError => { if (error.response) { // Erreur de réponse du serveur return { message: error.response.data?.message || error.response.statusText || 'Erreur serveur', code: error.response.status.toString(), details: error.response.data, }; } else if (error.request) { // Erreur de réseau return { message: 'Impossible de se connecter au serveur', code: 'NETWORK_ERROR', details: error.request, }; } else { // Autre erreur return { message: error.message || 'Une erreur inattendue est survenue', code: 'UNKNOWN_ERROR', details: error, }; } }; // Type guards pour vérifier les réponses API export const isApiResponse = (response: any): response is ApiResponse => { return response && typeof response === 'object' && 'data' in response; }; export const isApiError = (error: any): error is ApiError => { return error && typeof error === 'object' && 'message' in error; };