import axios from 'axios'; import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios'; import { APIException } from '$lib/types'; /** * API Client Configuration */ const getAPIBaseURL = (): string => { const apiUrl = import.meta.env?.VITE_API_BASE_URL || 'https://api.csgow.tf'; // Check if we're running on the server (SSR) or in production // On the server, we must use the actual API URL, not the proxy if (import.meta.env.SSR || import.meta.env.PROD) { return apiUrl; } // In development mode on the client, use the Vite proxy to avoid CORS issues // The proxy will forward /api requests to VITE_API_BASE_URL return '/api'; }; const API_BASE_URL = getAPIBaseURL(); const API_TIMEOUT = Number(import.meta.env?.VITE_API_TIMEOUT) || 10000; // Log the API configuration if (import.meta.env.DEV) { if (import.meta.env.SSR) { console.log('[API Client] SSR mode - using direct API URL:', API_BASE_URL); } else { console.log('[API Client] Browser mode - using Vite proxy'); console.log('[API Client] Frontend requests: /api/*'); console.log( '[API Client] Proxy target:', import.meta.env?.VITE_API_BASE_URL || 'https://api.csgow.tf' ); } } /** * Base API Client * Provides centralized HTTP communication with error handling */ class APIClient { private client: AxiosInstance; private abortControllers: Map; constructor() { this.client = axios.create({ baseURL: API_BASE_URL, timeout: API_TIMEOUT, headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }); this.abortControllers = new Map(); // Request interceptor this.client.interceptors.request.use( (config) => { // Add request ID for tracking const requestId = `${config.method}_${config.url}_${Date.now()}`; config.headers['X-Request-ID'] = requestId; return config; }, (error) => Promise.reject(error) ); // Response interceptor for error handling this.client.interceptors.response.use( (response) => response, (error: AxiosError) => { const apiError = this.handleError(error); return Promise.reject(apiError); } ); } /** * Handle API errors and convert to APIException */ private handleError(error: AxiosError): APIException { // Network error (no response from server) if (!error.response) { if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { return APIException.timeout('Request timed out. Please try again.'); } return APIException.networkError( 'Unable to connect to the server. Please check your internet connection.' ); } // Server responded with error status const { status, data } = error.response; return APIException.fromResponse(status, data); } /** * GET request */ async get(url: string, config?: AxiosRequestConfig): Promise { const response = await this.client.get(url, config); return response.data; } /** * POST request */ async post(url: string, data?: unknown, config?: AxiosRequestConfig): Promise { const response = await this.client.post(url, data, config); return response.data; } /** * PUT request */ async put(url: string, data?: unknown, config?: AxiosRequestConfig): Promise { const response = await this.client.put(url, data, config); return response.data; } /** * DELETE request */ async delete(url: string, config?: AxiosRequestConfig): Promise { const response = await this.client.delete(url, config); return response.data; } /** * Cancelable GET request * Automatically cancels previous request with same key */ async getCancelable(url: string, key: string, config?: AxiosRequestConfig): Promise { // Cancel previous request with same key if (this.abortControllers.has(key)) { this.abortControllers.get(key)?.abort(); } // Create new abort controller const controller = new AbortController(); this.abortControllers.set(key, controller); try { const response = await this.client.get(url, { ...config, signal: controller.signal }); this.abortControllers.delete(key); return response.data; } catch (error) { this.abortControllers.delete(key); throw error; } } /** * Cancel a specific request by key */ cancelRequest(key: string): void { const controller = this.abortControllers.get(key); if (controller) { controller.abort(); this.abortControllers.delete(key); } } /** * Cancel all pending requests */ cancelAllRequests(): void { this.abortControllers.forEach((controller) => controller.abort()); this.abortControllers.clear(); } /** * Get base URL for constructing full URLs */ getBaseURL(): string { return API_BASE_URL; } } /** * Singleton API client instance */ export const apiClient = new APIClient(); /** * Export for testing/mocking */ export { APIClient };