import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from "axios" /** * Token management utilities */ const getTokenFromCookie = (name: string): string | null => { if (typeof document === "undefined") return null const value = `; ${document.cookie}` const parts = value.split(`; ${name}=`) if (parts.length === 2) return parts.pop()?.split(";").shift() || null return null } const setTokenInCookie = (name: string, token: string): void => { if (typeof document === "undefined") return document.cookie = `${name}=${token}; path=/; secure; SameSite=Strict; max-age=2592000` } const removeTokenFromCookie = (name: string): void => { if (typeof document === "undefined") return document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;` } const getToken = (): string | null => { // Check cookies first (more secure) const authToken = getTokenFromCookie("authToken") if (authToken) return authToken const guestToken = getTokenFromCookie("guestToken") if (guestToken) return guestToken return null } /** * Centralized API client with interceptors */ class APIClient { private client: AxiosInstance private baseUrl: string private isRefreshing = false private failedQueue: Array<{ resolve: (value?: unknown) => void reject: (reason?: unknown) => void }> = [] constructor() { this.baseUrl = process.env.NEXT_PUBLIC_API_URL || "https://api.example.com" this.client = axios.create({ baseURL: `${this.baseUrl}/api/v1`, timeout: 15000, headers: { "Content-Type": "application/json", "Api-Token": process.env.NEXT_PUBLIC_API_TOKEN || "123", }, }) this.setupInterceptors() } private setupInterceptors(): void { // Request interceptor this.client.interceptors.request.use( (config) => { const token = getToken() if (token) { config.headers.Authorization = `Bearer ${token}` } // Add language parameter if i18n is available if (typeof window !== "undefined" && (window as any).i18n) { const lang = (window as any).i18n.language || "tm" const url = config.url || "" config.url = `${url}${url.includes("?") ? "&" : "?"}lang=${lang}` } return config }, (error) => Promise.reject(error) ) // Response interceptor this.client.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config // Handle 401 errors if (error.response?.status === 401 && !originalRequest._retry) { if (this.isRefreshing) { // Queue requests while refreshing return new Promise((resolve, reject) => { this.failedQueue.push({ resolve, reject }) }) .then(() => this.client(originalRequest)) .catch((err) => Promise.reject(err)) } originalRequest._retry = true this.isRefreshing = true try { // Attempt to get guest token const guestTokenResponse = await axios.post( `${this.baseUrl}/api/v1/auth/guest-token`, {}, { headers: { "Content-Type": "application/json", "Api-Token": process.env.NEXT_PUBLIC_API_TOKEN || "123", }, } ) const newToken = guestTokenResponse.data?.token || guestTokenResponse.data?.data if (newToken) { setTokenInCookie("guestToken", newToken) this.processQueue(null) return this.client(originalRequest) } } catch (refreshError) { this.processQueue(refreshError) this.clearAuthToken() if (typeof window !== "undefined") { window.location.href = "/login" } return Promise.reject(refreshError) } finally { this.isRefreshing = false } } // Handle HTML error responses if ( error.response?.data && typeof error.response.data === "string" && error.response.data.includes("") ) { return Promise.reject({ ...error, response: { ...error.response, data: { message: "Server returned HTML instead of JSON" }, }, }) } return Promise.reject(error) } ) } private processQueue(error: any): void { this.failedQueue.forEach((promise) => { if (error) { promise.reject(error) } else { promise.resolve() } }) this.failedQueue = [] } get(url: string, config?: AxiosRequestConfig): Promise> { return this.client.get(url, config) } post(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.client.post(url, data, config) } put(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.client.put(url, data, config) } patch(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.client.patch(url, data, config) } delete(url: string, config?: AxiosRequestConfig): Promise> { return this.client.delete(url, config) } setAuthToken(token: string): void { removeTokenFromCookie("guestToken") setTokenInCookie("authToken", token) this.client.defaults.headers.common["Authorization"] = `Bearer ${token}` } setGuestToken(token: string): void { setTokenInCookie("guestToken", token) this.client.defaults.headers.common["Authorization"] = `Bearer ${token}` } clearAuthToken(): void { removeTokenFromCookie("authToken") removeTokenFromCookie("guestToken") delete this.client.defaults.headers.common["Authorization"] } } // Export singleton instance export const apiClient = new APIClient() // Export helper functions export const setAuthToken = (token: string) => apiClient.setAuthToken(token) export const setGuestToken = (token: string) => apiClient.setGuestToken(token) export const clearAuthToken = () => apiClient.clearAuthToken()