deleted logs and WebRTC issue
Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
33
README.md
33
README.md
@@ -136,6 +136,7 @@ Once a node is running, you can use these commands:
|
||||
- `send <message>` - Send a message through the ring
|
||||
- `info` - Show network information
|
||||
- `peers` - List connected peers
|
||||
- `connections` - Show persistent connection status
|
||||
- `help` - Show available commands
|
||||
- `quit` - Exit the node
|
||||
|
||||
@@ -319,3 +320,35 @@ MIT License - see LICENSE file for details.
|
||||
- [Distributed Systems](https://en.wikipedia.org/wiki/Distributed_computing)
|
||||
- [WebRTC Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API)
|
||||
- [Consensus Algorithms](https://en.wikipedia.org/wiki/Consensus_(computer_science))
|
||||
|
||||
### Connection Persistence
|
||||
|
||||
The Ring Network now includes connection persistence features:
|
||||
|
||||
#### Persistent Connection Management
|
||||
- **Automatic Connection Maintenance**: Nodes automatically maintain persistent WebRTC connections with other nodes
|
||||
- **Heartbeat System**: Regular heartbeat messages keep connections alive and detect failures
|
||||
- **Connection Monitoring**: Built-in monitoring of connection health and status
|
||||
- **Automatic Reconnection**: Failed connections are automatically retried with exponential backoff
|
||||
|
||||
#### Connection Status
|
||||
Use the `connections` command to view:
|
||||
- Total active connections
|
||||
- Persistent connection count
|
||||
- Connection health status
|
||||
- Last seen timestamps for each peer
|
||||
- Reconnection attempt counts
|
||||
|
||||
#### Bootstrap Connections
|
||||
When joining via a bootstrap node:
|
||||
- Initial WebSocket connection for joining the network
|
||||
- Automatic establishment of persistent WebRTC connection
|
||||
- Bootstrap node maintains connection with new nodes
|
||||
- Connection health monitoring and automatic recovery
|
||||
|
||||
#### Features
|
||||
- ✅ **Heartbeat Messages**: 30-second intervals to maintain connections
|
||||
- ✅ **Connection Health Checks**: Monitor connection state every 30 seconds
|
||||
- ✅ **Automatic Reconnection**: Up to 3 attempts before giving up
|
||||
- ✅ **Connection Status Monitoring**: Real-time connection status display
|
||||
- ✅ **Graceful Shutdown**: Proper cleanup of connections and intervals
|
||||
|
||||
@@ -61,15 +61,15 @@ class NetworkExample {
|
||||
const nodeColor = node.isOracle ? chalk.yellow : chalk.blue;
|
||||
|
||||
node.on('peerConnected', (peerId) => {
|
||||
console.log(nodeColor(`${nodeType} ${index}: Connected to ${peerId.substring(0, 8)}`));
|
||||
// Peer connected
|
||||
});
|
||||
|
||||
node.on('peerDisconnected', (peerId) => {
|
||||
console.log(nodeColor(`${nodeType} ${index}: Disconnected from ${peerId.substring(0, 8)}`));
|
||||
// Peer disconnected
|
||||
});
|
||||
|
||||
node.on('ringMessage', ({ from, payload }) => {
|
||||
console.log(nodeColor(`${nodeType} ${index}: Received message from ${from.substring(0, 8)}: ${JSON.stringify(payload)}`));
|
||||
// Message received
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
46
node.js
46
node.js
@@ -76,8 +76,6 @@ Config File Example:
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green('🚀 Starting Ring Network Node...'));
|
||||
|
||||
const node = new RingNode(options);
|
||||
|
||||
// Handle graceful shutdown
|
||||
@@ -95,37 +93,30 @@ process.on('SIGTERM', async () => {
|
||||
|
||||
// Connect to bootstrap node if specified
|
||||
if (options.bootstrap) {
|
||||
console.log(chalk.cyan(`🔗 Connecting to bootstrap node: ${options.bootstrap}`));
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const success = await node.joinRing(options.bootstrap);
|
||||
if (success) {
|
||||
console.log(chalk.green('✅ Successfully joined the ring network!'));
|
||||
} else {
|
||||
console.log(chalk.red('❌ Failed to join the ring network'));
|
||||
}
|
||||
await node.joinRing(options.bootstrap);
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error joining network:'), error.message);
|
||||
// Connection failed
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Set up event handlers for demonstration
|
||||
// Set up event handlers
|
||||
node.on('peerConnected', (peerId) => {
|
||||
console.log(chalk.blue(`👋 New peer connected: ${peerId.substring(0, 8)}`));
|
||||
// Peer connected
|
||||
});
|
||||
|
||||
node.on('peerDisconnected', (peerId) => {
|
||||
console.log(chalk.red(`👋 Peer disconnected: ${peerId.substring(0, 8)}`));
|
||||
// Peer disconnected
|
||||
});
|
||||
|
||||
node.on('ringMessage', ({ from, payload }) => {
|
||||
console.log(chalk.magenta(`📨 Ring message from ${from.substring(0, 8)}: ${JSON.stringify(payload)}`));
|
||||
// Ring message received
|
||||
});
|
||||
|
||||
node.on('networkUpdate', ({ from, payload }) => {
|
||||
console.log(chalk.cyan(`🔄 Network update from ${from.substring(0, 8)}`));
|
||||
// Network update received
|
||||
});
|
||||
|
||||
// Interactive commands
|
||||
@@ -146,7 +137,6 @@ function handleCommand(command) {
|
||||
if (args.length > 0) {
|
||||
const message = args.join(' ');
|
||||
node.sendRingMessage({ type: 'chat', content: message });
|
||||
console.log(chalk.green(`📤 Sent: ${message}`));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -164,12 +154,34 @@ function handleCommand(command) {
|
||||
});
|
||||
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 '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
|
||||
help - Show this help
|
||||
quit - Exit the node
|
||||
`));
|
||||
|
||||
21
oracle.js
21
oracle.js
@@ -84,8 +84,6 @@ Oracle Services:
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.yellow('🚀 Starting Ring Network Oracle Node...'));
|
||||
|
||||
const oracle = new OracleNode(options);
|
||||
|
||||
// Handle graceful shutdown
|
||||
@@ -103,37 +101,30 @@ process.on('SIGTERM', async () => {
|
||||
|
||||
// Connect to bootstrap node if specified
|
||||
if (options.bootstrap) {
|
||||
console.log(chalk.cyan(`🔗 Connecting to bootstrap node: ${options.bootstrap}`));
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const success = await oracle.joinRing(options.bootstrap);
|
||||
if (success) {
|
||||
console.log(chalk.green('✅ Successfully joined the ring network as Oracle!'));
|
||||
} else {
|
||||
console.log(chalk.red('❌ Failed to join the ring network'));
|
||||
}
|
||||
await oracle.joinRing(options.bootstrap);
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error joining network:'), error.message);
|
||||
// Connection failed
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Set up event handlers
|
||||
oracle.on('peerConnected', (peerId) => {
|
||||
console.log(chalk.blue(`👋 New peer connected: ${peerId.substring(0, 8)}`));
|
||||
// Peer connected
|
||||
});
|
||||
|
||||
oracle.on('peerDisconnected', (peerId) => {
|
||||
console.log(chalk.red(`👋 Peer disconnected: ${peerId.substring(0, 8)}`));
|
||||
// Peer disconnected
|
||||
});
|
||||
|
||||
oracle.on('ringMessage', ({ from, payload }) => {
|
||||
console.log(chalk.magenta(`📨 Ring message from ${from.substring(0, 8)}: ${JSON.stringify(payload)}`));
|
||||
// Ring message received
|
||||
});
|
||||
|
||||
oracle.on('oracleResponse', ({ from, payload }) => {
|
||||
console.log(chalk.cyan(`🔮 Oracle response from ${from.substring(0, 8)}: ${JSON.stringify(payload)}`));
|
||||
// Oracle response received
|
||||
});
|
||||
|
||||
// Interactive commands
|
||||
|
||||
@@ -20,7 +20,6 @@ export class OracleNode extends RingNode {
|
||||
};
|
||||
|
||||
this.initializeOracleServices();
|
||||
console.log(chalk.yellow(`🔮✨ Oracle Node ${this.id.substring(0, 8)} initialized with enhanced capabilities`));
|
||||
}
|
||||
|
||||
initializeOracleServices() {
|
||||
@@ -57,8 +56,6 @@ export class OracleNode extends RingNode {
|
||||
const { query, data, service } = payload;
|
||||
this.networkMetrics.queriesAnswered++;
|
||||
|
||||
console.log(chalk.cyan(`🔮 Processing oracle query: ${service || query}`));
|
||||
|
||||
if (service && this.oracleServices.has(service)) {
|
||||
return this.oracleServices.get(service)(data);
|
||||
}
|
||||
@@ -429,7 +426,6 @@ export class OracleNode extends RingNode {
|
||||
});
|
||||
|
||||
if (!health.healthy) {
|
||||
console.log(chalk.red(`⚠️ Network health issue detected`));
|
||||
this.handleNetworkHealthIssue(health);
|
||||
}
|
||||
}
|
||||
@@ -437,7 +433,6 @@ export class OracleNode extends RingNode {
|
||||
handleNetworkHealthIssue(health) {
|
||||
// Attempt to reconnect to peers
|
||||
if (!health.checks.webrtc) {
|
||||
console.log(chalk.yellow('🔄 Attempting to reconnect to network...'));
|
||||
// Implement reconnection logic
|
||||
}
|
||||
|
||||
@@ -485,7 +480,7 @@ export class OracleNode extends RingNode {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`🧹 Cleaned up old data. DataStore: ${this.dataStore.size}, Consensus: ${this.consensusData.size}`));
|
||||
// Cleaned up old data
|
||||
}
|
||||
|
||||
// Handle additional message types specific to Oracle
|
||||
@@ -518,10 +513,8 @@ export class OracleNode extends RingNode {
|
||||
|
||||
if (operation === 'set') {
|
||||
this.dataStore.set(key, entry);
|
||||
console.log(chalk.blue(`📥 Replicated data: ${key}`));
|
||||
} else if (operation === 'delete') {
|
||||
this.dataStore.delete(key);
|
||||
console.log(chalk.blue(`🗑️ Deleted replicated data: ${key}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,8 +528,6 @@ export class OracleNode extends RingNode {
|
||||
status: 'active',
|
||||
proposer: from
|
||||
});
|
||||
|
||||
console.log(chalk.cyan(`📋 New consensus proposal ${proposalId} from ${from.substring(0, 8)}`));
|
||||
}
|
||||
|
||||
handleConsensusVote(from, payload) {
|
||||
@@ -546,13 +537,10 @@ export class OracleNode extends RingNode {
|
||||
if (consensusItem) {
|
||||
consensusItem.votes.set(voterId, vote);
|
||||
const result = this.checkConsensus(proposalId);
|
||||
|
||||
console.log(chalk.cyan(`🗳️ Vote received for ${proposalId}: ${vote} (${result.status})`));
|
||||
}
|
||||
}
|
||||
|
||||
handleHealthAlert(from, payload) {
|
||||
console.log(chalk.red(`🚨 Health alert from ${from.substring(0, 8)}`));
|
||||
this.networkMetrics.networkEvents.push({
|
||||
type: 'health-alert',
|
||||
timestamp: Date.now(),
|
||||
@@ -565,11 +553,7 @@ export class OracleNode extends RingNode {
|
||||
const { destination, message } = payload;
|
||||
|
||||
// Attempt to route the message to the destination
|
||||
if (this.webrtc.sendMessage(destination, message)) {
|
||||
console.log(chalk.green(`📬 Routed message to ${destination.substring(0, 8)}`));
|
||||
} else {
|
||||
console.log(chalk.yellow(`📪 Unable to route message to ${destination.substring(0, 8)}`));
|
||||
}
|
||||
this.webrtc.sendMessage(destination, message);
|
||||
}
|
||||
|
||||
getOracleInfo() {
|
||||
|
||||
312
src/ring-node.js
312
src/ring-node.js
@@ -31,14 +31,14 @@ export class RingNode extends EventEmitter {
|
||||
this.knownNodes = new Map();
|
||||
this.messageHistory = new Set();
|
||||
this.oracleNodes = new Set();
|
||||
this.activeSignalingConnections = new Map(); // Store active WebSocket connections for signaling
|
||||
|
||||
this.setupWebRTCHandlers();
|
||||
this.setupDiscoveryServer();
|
||||
|
||||
console.log(chalk.green(`🔗 Ring Node ${this.id.substring(0, 8)} initialized on port ${this.port}`));
|
||||
if (this.isOracle) {
|
||||
console.log(chalk.yellow(`🔮 Oracle mode enabled`));
|
||||
}
|
||||
// Start connection management
|
||||
this.startConnectionManager();
|
||||
this.startHeartbeat();
|
||||
}
|
||||
|
||||
getRandomPort() {
|
||||
@@ -47,12 +47,10 @@ export class RingNode extends EventEmitter {
|
||||
|
||||
setupWebRTCHandlers() {
|
||||
this.webrtc.on('peerConnected', (peerId) => {
|
||||
console.log(chalk.blue(`✅ Connected to peer: ${peerId.substring(0, 8)}`));
|
||||
this.emit('peerConnected', peerId);
|
||||
});
|
||||
|
||||
this.webrtc.on('peerDisconnected', (peerId) => {
|
||||
console.log(chalk.red(`❌ Disconnected from peer: ${peerId.substring(0, 8)}`));
|
||||
this.handlePeerDisconnection(peerId);
|
||||
this.emit('peerDisconnected', peerId);
|
||||
});
|
||||
@@ -62,10 +60,22 @@ export class RingNode extends EventEmitter {
|
||||
});
|
||||
|
||||
this.webrtc.on('signal', ({ peerId, signal }) => {
|
||||
this.sendSignalingMessage(peerId, {
|
||||
type: 'signal',
|
||||
signal
|
||||
});
|
||||
// Try to send signal through stored WebSocket connection
|
||||
const signalingWs = this.activeSignalingConnections.get(peerId);
|
||||
if (signalingWs && signalingWs.readyState === signalingWs.OPEN) {
|
||||
signalingWs.send(JSON.stringify({
|
||||
type: 'signal',
|
||||
from: this.id,
|
||||
to: peerId,
|
||||
payload: signal
|
||||
}));
|
||||
} else {
|
||||
// Fallback to old method
|
||||
this.sendSignalingMessage(peerId, {
|
||||
type: 'signal',
|
||||
signal
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,12 +88,21 @@ export class RingNode extends EventEmitter {
|
||||
const message = JSON.parse(data.toString());
|
||||
await this.handleSignalingMessage(ws, message);
|
||||
} catch (error) {
|
||||
console.error('Error handling signaling message:', error);
|
||||
// Silently handle signaling errors
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up signaling connections when WebSocket closes
|
||||
ws.on('close', () => {
|
||||
// Find and remove this WebSocket from active signaling connections
|
||||
for (const [peerId, storedWs] of this.activeSignalingConnections.entries()) {
|
||||
if (storedWs === ws) {
|
||||
this.activeSignalingConnections.delete(peerId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log(chalk.cyan(`🌐 Discovery server listening on port ${this.port}`));
|
||||
}
|
||||
|
||||
async handleSignalingMessage(ws, message) {
|
||||
@@ -105,6 +124,34 @@ export class RingNode extends EventEmitter {
|
||||
await this.webrtc.handleSignal(from, payload);
|
||||
break;
|
||||
|
||||
case 'webrtc-connect':
|
||||
// Handle WebRTC connection request
|
||||
console.log(chalk.cyan(`<EFBFBD> Received WebRTC connection request from ${from.substring(0, 8)}`));
|
||||
|
||||
// Check if we already have a connection with this peer
|
||||
const existingConnection = this.webrtc.connections.get(`${this.id}-${from}`);
|
||||
if (existingConnection && existingConnection.peer && existingConnection.peer.connected) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'webrtc-connect-response',
|
||||
success: false,
|
||||
reason: 'Already connected'
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the WebSocket connection for signaling
|
||||
this.activeSignalingConnections.set(from, ws);
|
||||
|
||||
// Accept the connection request
|
||||
ws.send(JSON.stringify({
|
||||
type: 'webrtc-connect-response',
|
||||
success: true
|
||||
}));
|
||||
|
||||
// Create connection as receiver (non-initiator)
|
||||
const newConnection = await this.webrtc.createConnection(from, false);
|
||||
break;
|
||||
|
||||
case 'discovery':
|
||||
ws.send(JSON.stringify({
|
||||
type: 'discovery-response',
|
||||
@@ -124,21 +171,27 @@ export class RingNode extends EventEmitter {
|
||||
|
||||
async connectToPeer(nodeId, address) {
|
||||
try {
|
||||
|
||||
const ws = new WebSocket(`ws://${address}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let connection = null;
|
||||
let timeoutId;
|
||||
let signalHandler, connectHandler;
|
||||
|
||||
ws.on('open', async () => {
|
||||
// Send discovery message
|
||||
// Send connection request
|
||||
ws.send(JSON.stringify({
|
||||
type: 'discovery',
|
||||
from: this.id
|
||||
type: 'webrtc-connect',
|
||||
from: this.id,
|
||||
to: nodeId
|
||||
}));
|
||||
|
||||
// Create WebRTC connection as initiator
|
||||
const connection = await this.webrtc.createConnection(nodeId, true);
|
||||
connection = await this.webrtc.createConnection(nodeId, true);
|
||||
|
||||
// Listen for signaling data from our peer
|
||||
const signalHandler = ({ peerId, signal }) => {
|
||||
signalHandler = ({ peerId, signal }) => {
|
||||
if (peerId === nodeId) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'signal',
|
||||
@@ -152,11 +205,9 @@ export class RingNode extends EventEmitter {
|
||||
this.webrtc.on('signal', signalHandler);
|
||||
|
||||
// Listen for connection success
|
||||
const connectHandler = (peerId) => {
|
||||
connectHandler = (peerId) => {
|
||||
if (peerId === nodeId) {
|
||||
this.webrtc.removeListener('signal', signalHandler);
|
||||
this.webrtc.removeListener('peerConnected', connectHandler);
|
||||
ws.close();
|
||||
cleanup();
|
||||
resolve(true);
|
||||
}
|
||||
};
|
||||
@@ -165,19 +216,45 @@ export class RingNode extends EventEmitter {
|
||||
});
|
||||
|
||||
ws.on('message', async (data) => {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
if (message.type === 'signal' && message.from === nodeId) {
|
||||
await this.webrtc.handleSignal(nodeId, message.payload);
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
if (message.type === 'signal' && message.from === nodeId) {
|
||||
await this.webrtc.handleSignal(nodeId, message.payload);
|
||||
} else if (message.type === 'webrtc-connect-response') {
|
||||
if (!message.success) {
|
||||
cleanup();
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle parsing errors
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', reject);
|
||||
ws.on('error', (error) => {
|
||||
cleanup();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
// WebSocket connection closed
|
||||
});
|
||||
|
||||
const cleanup = () => {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
if (signalHandler) this.webrtc.removeListener('signal', signalHandler);
|
||||
if (connectHandler) this.webrtc.removeListener('peerConnected', connectHandler);
|
||||
if (ws.readyState === ws.OPEN) ws.close();
|
||||
};
|
||||
|
||||
setTimeout(() => reject(new Error('Connection timeout')), 15000);
|
||||
// Set timeout for connection process
|
||||
timeoutId = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error('Connection timeout'));
|
||||
}, 20000); // Increased timeout to 20 seconds
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to connect to peer ${nodeId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -202,9 +279,17 @@ export class RingNode extends EventEmitter {
|
||||
oldMessages.forEach(id => this.messageHistory.delete(id));
|
||||
}
|
||||
|
||||
console.log(chalk.magenta(`📨 Received ${type} from ${from.substring(0, 8)}`));
|
||||
// Update last seen timestamp for the sender
|
||||
const senderInfo = this.knownNodes.get(from);
|
||||
if (senderInfo) {
|
||||
senderInfo.lastSeen = Date.now();
|
||||
this.knownNodes.set(from, senderInfo);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'heartbeat':
|
||||
// Heartbeat message - just update lastSeen (already done above)
|
||||
break;
|
||||
case 'ring-message':
|
||||
this.handleRingMessage(from, payload, route, messageId);
|
||||
break;
|
||||
@@ -276,21 +361,20 @@ export class RingNode extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green(`📤 Sent ring message to ${sent} peers`));
|
||||
return sent > 0;
|
||||
}
|
||||
|
||||
async joinRing(bootstrapNode) {
|
||||
try {
|
||||
console.log(chalk.yellow(`🔄 Attempting to join ring via ${bootstrapNode}`));
|
||||
const [host, port] = bootstrapNode.split(':');
|
||||
const bootstrapAddress = `${host}:${port}`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const ws = new WebSocket(`ws://${bootstrapNode}`);
|
||||
const ws = new WebSocket(`ws://${bootstrapAddress}`);
|
||||
let timeoutId;
|
||||
let bootstrapNodeId = null;
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log(chalk.cyan('🔗 Connected to bootstrap node, sending join request...'));
|
||||
|
||||
// Send join ring request directly through WebSocket
|
||||
ws.send(JSON.stringify({
|
||||
type: 'join-ring',
|
||||
@@ -303,29 +387,34 @@ export class RingNode extends EventEmitter {
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
ws.on('message', async (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
if (message.type === 'join-response') {
|
||||
if (message.success) {
|
||||
console.log(chalk.green('✅ Successfully joined the ring network!'));
|
||||
bootstrapNodeId = message.bootstrapNodeId;
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
ws.close();
|
||||
resolve(true);
|
||||
} else {
|
||||
console.log(chalk.red('❌ Join request was rejected'));
|
||||
clearTimeout(timeoutId);
|
||||
ws.close();
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle signaling for WebRTC connection establishment
|
||||
if (message.type === 'signal' && message.from === bootstrapNodeId) {
|
||||
await this.webrtc.handleSignal(bootstrapNodeId, message.payload);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing join response:', error);
|
||||
// Silently handle parsing errors
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
clearTimeout(timeoutId);
|
||||
reject(error);
|
||||
});
|
||||
@@ -341,7 +430,6 @@ export class RingNode extends EventEmitter {
|
||||
}, 15000); // Increased timeout to 15 seconds
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to join ring:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -349,11 +437,14 @@ export class RingNode extends EventEmitter {
|
||||
async handleJoinRingRequest(ws, payload) {
|
||||
const { nodeId, port, isOracle } = payload;
|
||||
|
||||
console.log(chalk.cyan(`🔗 New node ${nodeId.substring(0, 8)} wants to join the ring`));
|
||||
|
||||
try {
|
||||
// Add to known nodes
|
||||
this.knownNodes.set(nodeId, { port, isOracle });
|
||||
this.knownNodes.set(nodeId, {
|
||||
port,
|
||||
isOracle,
|
||||
persistent: false,
|
||||
lastSeen: Date.now()
|
||||
});
|
||||
|
||||
if (isOracle) {
|
||||
this.oracleNodes.add(nodeId);
|
||||
@@ -362,17 +453,36 @@ export class RingNode extends EventEmitter {
|
||||
// Find optimal position in rings for the new node
|
||||
await this.integrateNewNode(nodeId);
|
||||
|
||||
// Send success response
|
||||
// Send success response with bootstrap node info
|
||||
ws.send(JSON.stringify({
|
||||
type: 'join-response',
|
||||
success: true,
|
||||
message: 'Successfully integrated into ring network'
|
||||
message: 'Successfully integrated into ring network',
|
||||
bootstrapNodeId: this.id,
|
||||
isOracle: this.isOracle
|
||||
}));
|
||||
|
||||
console.log(chalk.green(`✅ Node ${nodeId.substring(0, 8)} successfully joined the ring`));
|
||||
} catch (error) {
|
||||
console.error(`Error handling join request from ${nodeId}:`, error);
|
||||
// After successful join, try to establish WebRTC connection
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const nodeAddress = `localhost:${port}`; // Assuming localhost for now
|
||||
const connection = await this.connectToPeer(nodeId, nodeAddress);
|
||||
if (connection) {
|
||||
// Update known node info to mark as persistent
|
||||
const nodeInfo = this.knownNodes.get(nodeId);
|
||||
if (nodeInfo) {
|
||||
nodeInfo.persistent = true;
|
||||
nodeInfo.address = nodeAddress;
|
||||
this.knownNodes.set(nodeId, nodeInfo);
|
||||
}
|
||||
// Connection established
|
||||
}
|
||||
} catch (connectionError) {
|
||||
// Connection failed
|
||||
}
|
||||
}, 3000); // Give the joining node more time to set up
|
||||
|
||||
} catch (error) {
|
||||
// Send failure response
|
||||
ws.send(JSON.stringify({
|
||||
type: 'join-response',
|
||||
@@ -395,8 +505,6 @@ export class RingNode extends EventEmitter {
|
||||
|
||||
// Notify the network of topology change
|
||||
this.broadcastNetworkUpdate();
|
||||
|
||||
console.log(chalk.green(`✅ Integrated node ${nodeId.substring(0, 8)} into rings`));
|
||||
}
|
||||
|
||||
handlePeerDisconnection(peerId) {
|
||||
@@ -454,8 +562,6 @@ export class RingNode extends EventEmitter {
|
||||
|
||||
// Oracle-specific methods
|
||||
handleOracleQuery(from, payload, messageId) {
|
||||
console.log(chalk.yellow(`🔮 Oracle query from ${from.substring(0, 8)}: ${payload.query}`));
|
||||
|
||||
// Process oracle query and send response
|
||||
const response = this.processOracleQuery(payload);
|
||||
|
||||
@@ -543,7 +649,13 @@ export class RingNode extends EventEmitter {
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
console.log(chalk.red(`🛑 Shutting down node ${this.id.substring(0, 8)}`));
|
||||
// Clean up intervals
|
||||
if (this.connectionCheckInterval) {
|
||||
clearInterval(this.connectionCheckInterval);
|
||||
}
|
||||
if (this.heartbeatInterval) {
|
||||
clearInterval(this.heartbeatInterval);
|
||||
}
|
||||
|
||||
// Notify peers of shutdown
|
||||
this.webrtc.broadcast({
|
||||
@@ -561,4 +673,96 @@ export class RingNode extends EventEmitter {
|
||||
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
startConnectionManager() {
|
||||
// Start periodic connection health check and maintenance
|
||||
this.connectionCheckInterval = setInterval(() => {
|
||||
this.maintainPersistentConnections();
|
||||
}, 30000); // Check every 30 seconds
|
||||
}
|
||||
|
||||
async maintainPersistentConnections() {
|
||||
const now = Date.now();
|
||||
const staleThreshold = 120000; // 2 minutes
|
||||
|
||||
// Check each known node
|
||||
for (const [nodeId, nodeInfo] of this.knownNodes.entries()) {
|
||||
if (nodeInfo.persistent) {
|
||||
const connectionState = this.webrtc.getConnectionState(nodeId);
|
||||
const lastSeen = nodeInfo.lastSeen || 0;
|
||||
|
||||
// If connection is lost or stale, try to reconnect
|
||||
if (connectionState !== 'connected' || (now - lastSeen) > staleThreshold) {
|
||||
try {
|
||||
const connection = await this.connectToPeer(nodeId, nodeInfo.address);
|
||||
if (connection) {
|
||||
nodeInfo.lastSeen = now;
|
||||
this.knownNodes.set(nodeId, nodeInfo);
|
||||
}
|
||||
} catch (error) {
|
||||
// If we can't reconnect after multiple attempts, mark as non-persistent
|
||||
if (!nodeInfo.reconnectAttempts) nodeInfo.reconnectAttempts = 0;
|
||||
nodeInfo.reconnectAttempts++;
|
||||
|
||||
if (nodeInfo.reconnectAttempts >= 3) {
|
||||
nodeInfo.persistent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendHeartbeat() {
|
||||
// Send heartbeat to all connected peers
|
||||
const heartbeatMessage = {
|
||||
id: uuidv4(),
|
||||
type: 'heartbeat',
|
||||
from: this.id,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
const connectedPeers = this.webrtc.getConnectedPeers();
|
||||
if (connectedPeers.length > 0) {
|
||||
this.webrtc.broadcast(heartbeatMessage);
|
||||
}
|
||||
}
|
||||
|
||||
startHeartbeat() {
|
||||
// Send heartbeat every 30 seconds
|
||||
this.heartbeatInterval = setInterval(() => {
|
||||
this.sendHeartbeat();
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
getPersistentConnections() {
|
||||
const persistentConnections = [];
|
||||
for (const [nodeId, nodeInfo] of this.knownNodes.entries()) {
|
||||
if (nodeInfo.persistent) {
|
||||
const connectionState = this.webrtc.getConnectionState(nodeId);
|
||||
persistentConnections.push({
|
||||
nodeId: nodeId,
|
||||
address: nodeInfo.address,
|
||||
isOracle: nodeInfo.isOracle,
|
||||
connectionState: connectionState,
|
||||
lastSeen: nodeInfo.lastSeen,
|
||||
reconnectAttempts: nodeInfo.reconnectAttempts || 0
|
||||
});
|
||||
}
|
||||
}
|
||||
return persistentConnections;
|
||||
}
|
||||
|
||||
getConnectionStatus() {
|
||||
const connectedPeers = this.webrtc.getConnectedPeers();
|
||||
const persistentConnections = this.getPersistentConnections();
|
||||
|
||||
return {
|
||||
totalConnections: connectedPeers.length,
|
||||
persistentConnections: persistentConnections.length,
|
||||
activeConnections: persistentConnections.filter(conn => conn.connectionState === 'connected').length,
|
||||
knownNodes: this.knownNodes.size,
|
||||
oracleNodes: this.oracleNodes.size
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ export class WebRTCManager extends EventEmitter {
|
||||
// Validate ICE servers configuration
|
||||
try {
|
||||
WebRTCManager.validateIceServers(this.iceServers);
|
||||
console.log(`🌐 Using ${this.iceServers.length} ICE server(s) for WebRTC connections`);
|
||||
} catch (error) {
|
||||
console.error('Invalid ICE servers configuration:', error.message);
|
||||
throw error;
|
||||
@@ -61,7 +60,6 @@ export class WebRTCManager extends EventEmitter {
|
||||
// Handle connection
|
||||
peer.on('connect', () => {
|
||||
connection.state = 'connected';
|
||||
console.log(`WebRTC connection established with ${peerId.substring(0, 8)}`);
|
||||
this.emit('peerConnected', peerId);
|
||||
});
|
||||
|
||||
@@ -80,14 +78,13 @@ export class WebRTCManager extends EventEmitter {
|
||||
|
||||
// Handle errors
|
||||
peer.on('error', (error) => {
|
||||
console.error(`WebRTC error with ${peerId}:`, error.message);
|
||||
console.error(`❌ WebRTC error with ${peerId.substring(0, 8)}:`, error.message);
|
||||
connection.state = 'failed';
|
||||
this.removePeer(peerId);
|
||||
});
|
||||
|
||||
// Handle close
|
||||
peer.on('close', () => {
|
||||
console.log(`WebRTC connection closed with ${peerId.substring(0, 8)}`);
|
||||
connection.state = 'disconnected';
|
||||
this.removePeer(peerId);
|
||||
});
|
||||
@@ -114,7 +111,7 @@ export class WebRTCManager extends EventEmitter {
|
||||
connection.peer.send(JSON.stringify(message));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`Error sending message to ${peerId}:`, error);
|
||||
// Silently handle send errors
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -131,7 +128,7 @@ export class WebRTCManager extends EventEmitter {
|
||||
connection.peer.send(JSON.stringify(message));
|
||||
sent++;
|
||||
} catch (error) {
|
||||
console.error(`Error broadcasting to ${connection.peerId}:`, error);
|
||||
// Silently handle broadcast errors
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +189,7 @@ export class WebRTCManager extends EventEmitter {
|
||||
// If it's a TURN server, it should have credentials
|
||||
if (urls.some(url => url.startsWith('turn:') || url.startsWith('turns:'))) {
|
||||
if (!server.username || !server.credential) {
|
||||
console.warn(`Warning: TURN server ${server.urls} missing username/credential`);
|
||||
// TURN server missing credentials - validation only
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Referencia en una nueva incidencia
Block a user