// ==================== API CLIENT ==================== // Handles all communication with the VoiceGuard backend API import { CONFIG } from './config.js'; /** * Custom error class for API errors */ export class APIError extends Error { constructor(message, status) { super(message); this.name = 'APIError'; this.status = status; } } /** * VoiceGuard API Client */ class APIClient { constructor() { this.baseUrl = CONFIG.API_BASE_URL; this.apiKey = CONFIG.API_KEY; } /** * Check API health status * @returns {Promise<{status: string, message?: string}>} */ async checkHealth() { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), CONFIG.TIMEOUTS.HEALTH_CHECK); const response = await fetch(`${this.baseUrl}${CONFIG.ENDPOINTS.HEALTH}`, { method: 'GET', signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { return { status: 'offline', message: `HTTP ${response.status}` }; } const data = await response.json(); return { status: 'online', ...data }; } catch (error) { if (error.name === 'AbortError') { return { status: 'offline', message: 'Request timeout' }; } return { status: 'offline', message: error.message }; } } /** * Detect AI-generated voice in audio file * @param {File} audioFile - MP3 audio file * @param {string} language - Language of the audio * @returns {Promise} - Detection result */ async detectVoice(audioFile, language) { // 1. Convert file to Base64 console.log('📤 Converting file to Base64...'); const audioBase64 = await this._fileToBase64(audioFile); console.log(`📦 Base64 length: ${audioBase64.length} characters`); // 2. Build request body const requestBody = { language: language, audioFormat: 'mp3', audioBase64: audioBase64 }; // 3. Make API request console.log('📡 Sending request to API...'); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), CONFIG.TIMEOUTS.DETECTION); try { const response = await fetch(`${this.baseUrl}${CONFIG.ENDPOINTS.DETECT}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(requestBody), signal: controller.signal }); clearTimeout(timeoutId); // 4. Parse response const data = await response.json(); console.log('📊 Response received:', data); // 5. Handle errors if (!response.ok) { const errorMessage = data.message || data.detail?.message || data.detail || 'Detection failed'; throw new APIError(errorMessage, response.status); } return data; } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new APIError('Request timed out. The server may be downloading the model.', 0); } if (error instanceof APIError) { throw error; } throw new APIError(`Network error: ${error.message}`, 0); } } /** * Convert File to Base64 string * @private * @param {File} file - File to convert * @returns {Promise} - Base64 encoded string */ _fileToBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { // Remove data URL prefix (e.g., "data:audio/mpeg;base64,") const base64 = reader.result.split(',')[1]; resolve(base64); }; reader.onerror = () => { reject(new Error('Failed to read file')); }; reader.readAsDataURL(file); }); } } // Export singleton instance export const api = new APIClient();