161 lines
4.4 KiB
JavaScript
161 lines
4.4 KiB
JavaScript
// 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: <appLabel> label
|
|
if (!node.labels || typeof node.labels !== 'object') {
|
|
return false;
|
|
}
|
|
|
|
return node.labels.app === appLabel;
|
|
}
|
|
}
|
|
|
|
module.exports = GatewayClient;
|
|
|