Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| // Native fetch-based API client - No axios dependency! | |
| // Handles relative URLs correctly without HTTP/HTTPS conversion issues | |
| // Environment-aware API base URL with NUCLEAR OPTION for production | |
| let API_BASE_URL: string | |
| if (import.meta.env.PROD) { | |
| // π¨ NUCLEAR OPTION: HARDCODE /api in production - IGNORE ALL ENVIRONMENT VARIABLES | |
| // This prevents HuggingFace build secrets from injecting http:// URLs | |
| API_BASE_URL = '/api' | |
| console.log('π [API] Production mode: HARDCODED relative path:', API_BASE_URL) | |
| console.log('π¨ [API] Ignoring all environment variables (nuclear option enabled)') | |
| // SAFETY CHECK: If somehow an http:// URL got through, log a warning | |
| if (typeof import.meta.env.VITE_API_URL === 'string' && import.meta.env.VITE_API_URL.startsWith('http://')) { | |
| console.warn('β οΈ [API] BLOCKED http:// URL from environment:', import.meta.env.VITE_API_URL) | |
| console.warn('β οΈ [API] Using hardcoded /api instead') | |
| } | |
| } else { | |
| // Development: Use environment variable or default to localhost | |
| API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000/api' | |
| console.log('π§ [API] Development mode:', API_BASE_URL) | |
| } | |
| console.log('π‘ [API] Final base URL:', API_BASE_URL) | |
| console.log('π [API] Page protocol:', typeof window !== 'undefined' ? window.location.protocol : 'N/A') | |
| // Response type that matches axios structure | |
| interface APIResponse<T> { | |
| data: T | |
| status: number | |
| statusText: string | |
| } | |
| // Fetch wrapper that mimics axios interface | |
| class APIClient { | |
| private baseURL: string | |
| constructor(baseURL: string) { | |
| this.baseURL = baseURL | |
| } | |
| private async request<T>( | |
| url: string, | |
| options: RequestInit = {} | |
| ): Promise<APIResponse<T>> { | |
| // Build full URL | |
| const fullUrl = url.startsWith('http') ? url : `${this.baseURL}${url}` | |
| // π¨ PRODUCTION SAFETY CHECK: Block any http:// URLs | |
| if (import.meta.env.PROD && fullUrl.startsWith('http://')) { | |
| const httpsUrl = fullUrl.replace('http://', 'https://') | |
| console.error('β [API] BLOCKED insecure HTTP request in production:', fullUrl) | |
| console.error('β [API] This would cause Mixed Content errors') | |
| console.error('β [API] Upgrading to HTTPS:', httpsUrl) | |
| throw new Error(`BLOCKED: Attempted to make insecure HTTP request in production: ${fullUrl}`) | |
| } | |
| console.log('π [FETCH] Request URL:', fullUrl) | |
| console.log('π [FETCH] Method:', options.method || 'GET') | |
| // Add auth token if available | |
| const token = localStorage.getItem('auth_token') | |
| const headers: Record<string, string> = { | |
| 'Content-Type': 'application/json', | |
| ...(options.headers as Record<string, string>), | |
| } | |
| if (token) { | |
| headers['Authorization'] = `Bearer ${token}` | |
| } | |
| try { | |
| const response = await fetch(fullUrl, { | |
| ...options, | |
| headers, | |
| }) | |
| // Handle 401 unauthorized | |
| if (response.status === 401) { | |
| localStorage.removeItem('auth_token') | |
| } | |
| // Parse response | |
| let data: T | |
| const contentType = response.headers.get('content-type') | |
| if (contentType && contentType.includes('application/json')) { | |
| data = await response.json() | |
| } else { | |
| data = (await response.text()) as unknown as T | |
| } | |
| if (!response.ok) { | |
| throw { | |
| response: { | |
| data, | |
| status: response.status, | |
| statusText: response.statusText, | |
| }, | |
| message: `HTTP ${response.status}: ${response.statusText}`, | |
| } | |
| } | |
| console.log('β [FETCH] Success:', response.status) | |
| return { | |
| data, | |
| status: response.status, | |
| statusText: response.statusText, | |
| } | |
| } catch (error) { | |
| console.error('β [FETCH] Error:', error) | |
| throw error | |
| } | |
| } | |
| async get<T = any>(url: string, config?: { params?: Record<string, any> }): Promise<APIResponse<T>> { | |
| // Build query string | |
| let fullUrl = url | |
| if (config?.params) { | |
| const params = new URLSearchParams() | |
| Object.entries(config.params).forEach(([key, value]) => { | |
| if (value !== undefined && value !== null) { | |
| params.append(key, String(value)) | |
| } | |
| }) | |
| const queryString = params.toString() | |
| if (queryString) { | |
| fullUrl = `${url}?${queryString}` | |
| } | |
| } | |
| return this.request<T>(fullUrl, { method: 'GET' }) | |
| } | |
| async post<T = any>(url: string, data?: any): Promise<APIResponse<T>> { | |
| return this.request<T>(url, { | |
| method: 'POST', | |
| body: data ? JSON.stringify(data) : undefined, | |
| }) | |
| } | |
| async put<T = any>(url: string, data?: any): Promise<APIResponse<T>> { | |
| return this.request<T>(url, { | |
| method: 'PUT', | |
| body: data ? JSON.stringify(data) : undefined, | |
| }) | |
| } | |
| async delete<T = any>(url: string): Promise<APIResponse<T>> { | |
| return this.request<T>(url, { method: 'DELETE' }) | |
| } | |
| async patch<T = any>(url: string, data?: any): Promise<APIResponse<T>> { | |
| return this.request<T>(url, { | |
| method: 'PATCH', | |
| body: data ? JSON.stringify(data) : undefined, | |
| }) | |
| } | |
| } | |
| // Create and export the API client instance | |
| const api = new APIClient(API_BASE_URL) | |
| export default api | |