// API Client for communicating with the backend class ApiClient { constructor() { this.baseUrl = (typeof window !== 'undefined' && window.API_BASE_URL) || 'http://localhost:3001'; // Backend server URL } async request(path, { method = 'GET', headers = {}, body = undefined, query = undefined, isForm = false } = {}) { const url = new URL(`${this.baseUrl}${path}`); if (query && typeof query === 'object') { Object.entries(query).forEach(([k, v]) => { if (v !== undefined && v !== null) url.searchParams.set(k, String(v)); }); } const finalHeaders = { 'Accept': 'application/json', ...headers }; const options = { method, headers: finalHeaders }; if (body !== undefined) { if (isForm) { options.body = body; } else { options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/json'; options.body = typeof body === 'string' ? body : JSON.stringify(body); } } const response = await fetch(url.toString(), options); let data; const text = await response.text(); try { data = text ? JSON.parse(text) : null; } catch (_) { data = text; // Non-JSON payload } if (!response.ok) { const message = (data && data.message) || `HTTP ${response.status}: ${response.statusText}`; throw new Error(message); } return data; } async getClusterMembers() { try { return await this.request('/api/cluster/members', { method: 'GET' }); } catch (error) { throw new Error(`Request failed: ${error.message}`); } } async getClusterMembersFromNode(ip) { try { return await this.request(`/api/cluster/members`, { method: 'GET', query: { ip: ip } }); } catch (error) { throw new Error(`Request failed: ${error.message}`); } } async getDiscoveryInfo() { try { return await this.request('/api/discovery/nodes', { method: 'GET' }); } catch (error) { throw new Error(`Request failed: ${error.message}`); } } async selectRandomPrimaryNode() { try { return await this.request('/api/discovery/random-primary', { method: 'POST', body: { timestamp: new Date().toISOString() } }); } catch (error) { throw new Error(`Request failed: ${error.message}`); } } async getNodeStatus(ip) { try { return await this.request(`/api/node/status/${encodeURIComponent(ip)}`, { method: 'GET' }); } catch (error) { throw new Error(`Request failed: ${error.message}`); } } async getTasksStatus(ip) { try { return await this.request('/api/tasks/status', { method: 'GET', query: ip ? { ip } : undefined }); } catch (error) { throw new Error(`Request failed: ${error.message}`); } } async getCapabilities(ip) { try { return await this.request('/api/capabilities', { method: 'GET', query: ip ? { ip } : undefined }); } catch (error) { throw new Error(`Request failed: ${error.message}`); } } async callCapability({ ip, method, uri, params }) { try { return await this.request('/api/proxy-call', { method: 'POST', body: { ip, method, uri, params } }); } catch (error) { throw new Error(`Request failed: ${error.message}`); } } async uploadFirmware(file, nodeIp) { try { const formData = new FormData(); formData.append('file', file); return await this.request(`/api/node/update`, { method: 'POST', query: { ip: nodeIp }, body: formData, isForm: true, headers: {}, }); } catch (error) { throw new Error(`Upload failed: ${error.message}`); } } } // Global API client instance window.apiClient = new ApiClient();