// API Client for communicating with the backend class ApiClient { constructor() { // Auto-detect server URL based on current location const currentHost = window.location.hostname; const currentPort = window.location.port; // If accessing from localhost, use localhost:3001 // If accessing from another device, use the same hostname but port 3001 if (currentHost === 'localhost' || currentHost === '127.0.0.1') { this.baseUrl = 'http://localhost:3001'; } else { // Use the same hostname but port 3001 this.baseUrl = `http://${currentHost}:3001`; } console.log('API Client initialized with base URL:', this.baseUrl); } 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() { return this.request('/api/cluster/members', { method: 'GET' }); } async getClusterMembersFromNode(ip) { return this.request(`/api/cluster/members`, { method: 'GET', query: { ip: ip } }); } async getDiscoveryInfo() { return this.request('/api/discovery/nodes', { method: 'GET' }); } async selectRandomPrimaryNode() { return this.request('/api/discovery/random-primary', { method: 'POST', body: { timestamp: new Date().toISOString() } }); } async getNodeStatus(ip) { return this.request(`/api/node/status/${encodeURIComponent(ip)}`, { method: 'GET' }); } async getTasksStatus(ip) { return this.request('/api/tasks/status', { method: 'GET', query: ip ? { ip } : undefined }); } async getCapabilities(ip) { return this.request('/api/capabilities', { method: 'GET', query: ip ? { ip } : undefined }); } async callCapability({ ip, method, uri, params }) { return this.request('/api/proxy-call', { method: 'POST', body: { ip, method, uri, params } }); } async uploadFirmware(file, nodeIp) { const formData = new FormData(); formData.append('file', file); return this.request(`/api/node/update`, { method: 'POST', query: { ip: nodeIp }, body: formData, isForm: true, headers: {}, }); } } // Global API client instance window.apiClient = new ApiClient();