// Gateway Client - Communicates with spore-gateway for node discovery const http = require('http'); class GatewayClient { constructor(options = {}) { this.gatewayUrl = options.gatewayUrl || 'http://localhost:3001'; this.pollInterval = options.pollInterval || 2000; // Poll every 2 seconds this.filterAppLabel = options.filterAppLabel || 'pixelstream'; // Filter nodes by app label, set to null to disable this.nodes = new Map(); // ip -> { lastSeen, status, hostname, port } this.isRunning = false; this.pollTimer = null; } start() { if (this.isRunning) { return; } this.isRunning = true; console.log(`Starting Gateway client, connecting to ${this.gatewayUrl}`); // Initial fetch this.fetchNodes(); // Start polling this.pollTimer = setInterval(() => { this.fetchNodes(); }, this.pollInterval); } stop() { if (!this.isRunning) { return; } this.isRunning = false; if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } this.nodes.clear(); console.log('Gateway client stopped'); } async fetchNodes() { try { const response = await this.httpGet(`${this.gatewayUrl}/api/discovery/nodes`); const data = JSON.parse(response); // Update nodes from gateway response const newNodes = new Map(); let totalNodes = 0; let filteredNodes = 0; if (data.nodes && Array.isArray(data.nodes)) { totalNodes = data.nodes.length; data.nodes.forEach(node => { // Filter for nodes with specified app label (if filtering is enabled) if (this.filterAppLabel && !this.hasAppLabel(node, this.filterAppLabel)) { filteredNodes++; return; } const nodeIp = node.ip; newNodes.set(nodeIp, { lastSeen: Date.now(), status: node.status || 'active', hostname: node.hostname || nodeIp, port: node.port || 4210, isPrimary: node.isPrimary || false }); }); //if (totalNodes > 0 && filteredNodes > 0 && this.filterAppLabel) { // console.loh(`Filtered ${filteredNodes} nodes without app: ${this.filterAppLabel} label (${newNodes.size} ${this.filterAppLabel} nodes active)`); //} } // Check for newly discovered nodes for (const [ip, nodeInfo] of newNodes.entries()) { const existingNode = this.nodes.get(ip); if (!existingNode) { console.log(`Node discovered via gateway: ${ip} (${nodeInfo.hostname})`); this.nodes.set(ip, nodeInfo); // Could emit an event here if needed: this.emit('nodeDiscovered', nodeInfo); } else if (existingNode.hostname !== nodeInfo.hostname) { console.log(`Node hostname updated: ${ip} -> ${nodeInfo.hostname}`); this.nodes.set(ip, nodeInfo); } } // Check for lost nodes for (const ip of this.nodes.keys()) { if (!newNodes.has(ip)) { console.log(`Node lost via gateway: ${ip}`); this.nodes.delete(ip); // Could emit an event here if needed: this.emit('nodeLost', { ip }); } } } catch (error) { console.error('Error fetching nodes from gateway:', error.message); } } httpGet(url) { return new Promise((resolve, reject) => { http.get(url, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode === 200) { resolve(data); } else { reject(new Error(`HTTP ${res.statusCode}: ${data}`)); } }); res.on('error', (err) => { reject(err); }); }).on('error', (err) => { reject(err); }); }); } getNodes() { return Array.from(this.nodes.entries()).map(([ip, node]) => ({ ip, hostname: node.hostname || ip, port: node.port, status: node.status, ...node })); } getNodeCount() { return this.nodes.size; } hasAppLabel(node, appLabel) { // Check if node has the app: label if (!node.labels || typeof node.labels !== 'object') { return false; } return node.labels.app === appLabel; } } module.exports = GatewayClient;