238 líneas
8.2 KiB
JavaScript
238 líneas
8.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { RingNode } from './src/ring-node.js';
|
|
import chalk from 'chalk';
|
|
import { readFileSync } from 'fs';
|
|
|
|
const args = process.argv.slice(2);
|
|
const options = {};
|
|
|
|
// Parse command line arguments
|
|
for (let i = 0; i < args.length; i++) {
|
|
switch (args[i]) {
|
|
case '--port':
|
|
options.port = parseInt(args[++i]);
|
|
break;
|
|
case '--id':
|
|
options.id = args[++i];
|
|
break;
|
|
case '--bootstrap':
|
|
options.bootstrap = args[++i];
|
|
break;
|
|
case '--dashboard':
|
|
options.dashboardUrl = args[++i];
|
|
break;
|
|
case '--ice-servers':
|
|
try {
|
|
options.iceServers = JSON.parse(args[++i]);
|
|
} catch (error) {
|
|
console.error(chalk.red('Error parsing ICE servers JSON:'), error.message);
|
|
process.exit(1);
|
|
}
|
|
break;
|
|
case '--config':
|
|
try {
|
|
const configPath = args[++i];
|
|
const configData = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
if (configData.iceServers) {
|
|
options.iceServers = configData.iceServers;
|
|
}
|
|
} catch (error) {
|
|
console.error(chalk.red('Error loading config file:'), error.message);
|
|
process.exit(1);
|
|
}
|
|
break;
|
|
case '--help':
|
|
console.log(`
|
|
${chalk.blue('Ring Network Node')}
|
|
|
|
Usage: node node.js [options]
|
|
|
|
Options:
|
|
--port <port> Port to listen on (default: random)
|
|
--id <id> Node ID (default: auto-generated)
|
|
--bootstrap <addr> Bootstrap node address (host:port)
|
|
--dashboard <url> Dashboard server URL (default: http://localhost:3000)
|
|
--ice-servers <json> ICE servers configuration (JSON array)
|
|
--config <file> Load configuration from JSON file
|
|
--help Show this help message
|
|
|
|
Examples:
|
|
node node.js --port 8080
|
|
node node.js --port 8081 --bootstrap localhost:8080
|
|
node node.js --id mynode --port 8082 --bootstrap localhost:8080
|
|
node node.js --config config/ice-servers-with-turn.json --port 8080
|
|
|
|
Note: Ring positions are now assigned automatically for optimal network topology.
|
|
|
|
ICE Servers Example:
|
|
--ice-servers '[{"urls":"stun:stun.l.google.com:19302"},{"urls":"turn:turn.example.com:3478","username":"user","credential":"pass"}]'
|
|
|
|
Config File Example:
|
|
config/ice-servers-default.json - Default STUN servers
|
|
config/ice-servers-with-turn.json - With TURN server
|
|
config/ice-servers-public-turn.json - Public TURN servers
|
|
`);
|
|
process.exit(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const node = new RingNode(options);
|
|
|
|
// Handle graceful shutdown
|
|
process.on('SIGINT', async () => {
|
|
console.log(chalk.yellow('\n🛑 Received shutdown signal...'));
|
|
await node.destroy();
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGTERM', async () => {
|
|
console.log(chalk.yellow('\n🛑 Received termination signal...'));
|
|
await node.destroy();
|
|
process.exit(0);
|
|
});
|
|
|
|
// Connect to bootstrap node if specified
|
|
if (options.bootstrap) {
|
|
setTimeout(async () => {
|
|
try {
|
|
await node.joinRing(options.bootstrap);
|
|
} catch (error) {
|
|
// Connection failed
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
// Set up event handlers
|
|
node.on('peerConnected', (peerId) => {
|
|
// Peer connected
|
|
});
|
|
|
|
node.on('peerDisconnected', (peerId) => {
|
|
// Peer disconnected
|
|
});
|
|
|
|
node.on('ringMessage', ({ from, payload }) => {
|
|
// Ring message received
|
|
});
|
|
|
|
node.on('networkUpdate', ({ from, payload }) => {
|
|
// Network update received
|
|
});
|
|
|
|
// Interactive commands
|
|
process.stdin.setEncoding('utf8');
|
|
process.stdin.on('readable', () => {
|
|
const chunk = process.stdin.read();
|
|
if (chunk !== null) {
|
|
const command = chunk.trim();
|
|
handleCommand(command);
|
|
}
|
|
});
|
|
|
|
function handleCommand(command) {
|
|
const [cmd, ...args] = command.split(' ');
|
|
|
|
switch (cmd) {
|
|
case 'send':
|
|
if (args.length > 0) {
|
|
const message = args.join(' ');
|
|
node.sendRingMessage({ type: 'chat', content: message });
|
|
}
|
|
break;
|
|
|
|
case 'info':
|
|
const info = node.getNetworkInfo();
|
|
console.log(chalk.blue('\n📊 Network Info:'));
|
|
console.log(JSON.stringify(info, null, 2));
|
|
break;
|
|
|
|
case 'peers':
|
|
const peers = node.webrtc.getConnectedPeers();
|
|
console.log(chalk.blue(`\n👥 Connected Peers (${peers.length}):`));
|
|
peers.forEach(peer => {
|
|
console.log(` - ${peer.substring(0, 8)}...`);
|
|
});
|
|
break;
|
|
|
|
case 'connections':
|
|
const status = node.getConnectionStatus();
|
|
const persistentConns = node.getPersistentConnections();
|
|
|
|
console.log(chalk.blue('\n🔗 Connection Status:'));
|
|
console.log(` Total Connections: ${status.totalConnections}`);
|
|
console.log(` Persistent Connections: ${status.persistentConnections}`);
|
|
console.log(` Active Connections: ${status.activeConnections}`);
|
|
console.log(` Known Nodes: ${status.knownNodes}`);
|
|
console.log(` Oracle Nodes: ${status.oracleNodes}`);
|
|
|
|
if (persistentConns.length > 0) {
|
|
console.log(chalk.blue('\n🔄 Persistent Connections:'));
|
|
persistentConns.forEach(conn => {
|
|
const timeSince = conn.lastSeen ? `${Math.floor((Date.now() - conn.lastSeen) / 1000)}s ago` : 'never';
|
|
const statusColor = conn.connectionState === 'connected' ? chalk.green : chalk.red;
|
|
console.log(` - ${conn.nodeId.substring(0, 8)}... [${statusColor(conn.connectionState)}] ${conn.isOracle ? '🔮' : '💾'} (${timeSince})`);
|
|
});
|
|
}
|
|
break;
|
|
|
|
case 'topology':
|
|
const topology = node.getRingTopology();
|
|
console.log(chalk.blue('\n🔄 Ring Topology:'));
|
|
console.log(` Ring Size: ${topology.ringSize}`);
|
|
console.log(` Total Nodes: ${topology.totalNodes}`);
|
|
console.log(` My Position: ${node.ringPosition}`);
|
|
|
|
if (topology.nodes.length > 0) {
|
|
console.log(chalk.yellow('\n📍 Node Positions:'));
|
|
topology.nodes.forEach(nodeInfo => {
|
|
const indicators = [];
|
|
if (nodeInfo.isThisNode) indicators.push(chalk.green('ME'));
|
|
if (nodeInfo.isOracle) indicators.push(chalk.yellow('🔮'));
|
|
if (nodeInfo.isConnected) indicators.push(chalk.green('✓'));
|
|
|
|
const indicatorStr = indicators.length > 0 ? ` [${indicators.join(' ')}]` : '';
|
|
console.log(` ${nodeInfo.position.toString().padStart(4)}: ${nodeInfo.nodeId}...${indicatorStr}`);
|
|
});
|
|
|
|
console.log(chalk.cyan('\n🔄 Ring Connections:'));
|
|
console.log(` Inner Ring: ${topology.innerRing.left}... ← ME → ${topology.innerRing.right}...`);
|
|
console.log(` Outer Ring: ${topology.outerRing.left}... ← ME → ${topology.outerRing.right}...`);
|
|
}
|
|
break;
|
|
|
|
case 'help':
|
|
console.log(chalk.blue(`
|
|
📚 Available Commands:
|
|
send <message> - Send a message through the ring
|
|
info - Show network information
|
|
peers - List connected peers
|
|
connections - Show persistent connection status
|
|
topology - Show ring topology and positions
|
|
help - Show this help
|
|
quit - Exit the node
|
|
`));
|
|
break;
|
|
|
|
case 'quit':
|
|
case 'exit':
|
|
console.log(chalk.yellow('👋 Goodbye!'));
|
|
process.exit(0);
|
|
break;
|
|
|
|
default:
|
|
if (command.length > 0) {
|
|
console.log(chalk.red(`❓ Unknown command: ${cmd}. Type 'help' for available commands.`));
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(chalk.blue(`
|
|
🔗 Ring Network Node Started!
|
|
Node ID: ${node.id.substring(0, 8)}...
|
|
Port: ${node.port}
|
|
|
|
Type 'help' for available commands.
|
|
`));
|