peerpigeon
Version:
WebRTC-based peer-to-peer mesh networking library with intelligent routing and signaling server
183 lines (155 loc) • 8.1 kB
JavaScript
import { EventEmitter } from './EventEmitter.js';
import DebugLogger from './DebugLogger.js';
/**
* Manages mesh optimization, connection strategies, and peer discovery optimization
*/
export class MeshOptimizer extends EventEmitter {
constructor(mesh, connectionManager, evictionManager) {
super();
this.debug = DebugLogger.create('MeshOptimizer');
this.mesh = mesh;
this.connectionManager = connectionManager;
this.evictionManager = evictionManager;
}
handleOptimizeConnections(unconnectedPeers) {
if (!this.mesh.autoDiscovery) return;
// For small mesh sizes, optimize if under capacity. For larger meshes, only optimize if significantly under capacity.
const currentConnected = this.connectionManager.getConnectedPeerCount();
// CRITICAL FIX: If we have no connections and there are available peers, we should connect
// This handles the case where a peer with a small ID needs to connect to maintain mesh connectivity
const hasNoConnections = currentConnected === 0;
const belowMinimum = currentConnected < this.mesh.minPeers;
if (hasNoConnections && unconnectedPeers.length > 0) {
this.debug.log(`Peer has no connections but found ${unconnectedPeers.length} unconnected peers - forcing connection attempt`);
// Force connection to the closest peer by XOR distance
const sortedByDistance = unconnectedPeers.sort((a, b) => {
const distA = this.calculateXorDistance(this.mesh.peerId, a);
const distB = this.calculateXorDistance(this.mesh.peerId, b);
return distA < distB ? -1 : 1;
});
const targetPeer = sortedByDistance[0];
this.debug.log(`Forcing connection to closest peer: ${targetPeer.substring(0, 8)}... (no connections, mesh connectivity required)`);
this.connectionManager.connectToPeer(targetPeer);
return;
}
if (belowMinimum && unconnectedPeers.length > 0) {
this.debug.log(`Below minimum peers (${currentConnected}/${this.mesh.minPeers}) - connecting to additional peers`);
const needed = Math.min(this.mesh.minPeers - currentConnected, unconnectedPeers.length);
// Sort by XOR distance and connect to closest peers
const sortedByDistance = unconnectedPeers.sort((a, b) => {
const distA = this.calculateXorDistance(this.mesh.peerId, a);
const distB = this.calculateXorDistance(this.mesh.peerId, b);
return distA < distB ? -1 : 1;
});
for (let i = 0; i < needed; i++) {
const targetPeer = sortedByDistance[i];
this.debug.log(`Connecting to reach minimum: ${targetPeer.substring(0, 8)}...`);
this.connectionManager.connectToPeer(targetPeer);
}
return;
}
// Normal optimization logic
if (this.mesh.maxPeers <= 3) {
// For small meshes, always try to reach max capacity
if (currentConnected >= this.mesh.maxPeers) {
this.debug.log(`Skipping optimization - already at max capacity ${currentConnected}/${this.mesh.maxPeers}`);
return;
}
} else {
// For larger meshes, only optimize if below 70% capacity
const targetThreshold = Math.floor(this.mesh.maxPeers * 0.7);
if (currentConnected >= targetThreshold) {
this.debug.log(`Skipping optimization - ${currentConnected}/${this.mesh.maxPeers} peers connected (threshold: ${targetThreshold})`);
return;
}
}
const availableSlots = this.mesh.maxPeers - currentConnected;
const peersToConnect = unconnectedPeers.slice(0, Math.min(availableSlots, 1)); // Only connect to 1 peer at a time
this.debug.log(`Optimizing connections carefully: ${availableSlots} slots available, connecting to ${peersToConnect.length} peer(s)`);
peersToConnect.forEach((peerId, _index) => {
if (this.mesh.peerDiscovery.shouldInitiateConnection(peerId)) {
this.debug.log(`Initiating immediate connection to ${peerId.substring(0, 8)}... in optimization`);
// Double-check conditions before connecting
if (this.connectionManager.canAcceptMorePeers() &&
!this.connectionManager.hasPeer(peerId) &&
!this.mesh.peerDiscovery.isAttemptingConnection(peerId)) {
this.connectionManager.connectToPeer(peerId);
} else {
this.debug.log(`Skipping connection to ${peerId.substring(0, 8)}... - conditions changed`);
}
} else {
this.debug.log(`Not initiating connection to ${peerId.substring(0, 8)}... (should not initiate)`);
}
});
}
calculateXorDistance(peerId1, peerId2) {
let distance = 0n;
for (let i = 0; i < Math.min(peerId1.length, peerId2.length); i += 2) {
const byte1 = parseInt(peerId1.substr(i, 2), 16);
const byte2 = parseInt(peerId2.substr(i, 2), 16);
const xor = byte1 ^ byte2;
distance = (distance << 8n) | BigInt(xor);
}
return distance;
}
// Method to force connection attempts to all discovered peers
forceConnectToAllPeers() {
const discoveredPeers = this.mesh.getDiscoveredPeers();
let connectionAttempts = 0;
this.debug.log(`Forcing connections to ${discoveredPeers.length} discovered peers...`);
discoveredPeers.forEach(peer => {
if (!this.connectionManager.hasPeer(peer.peerId) && this.connectionManager.canAcceptMorePeers()) {
this.debug.log(`Force connecting to ${peer.peerId.substring(0, 8)}...`);
this.connectionManager.connectToPeer(peer.peerId);
connectionAttempts++;
}
});
this.debug.log(`Initiated ${connectionAttempts} forced connection attempts`);
return connectionAttempts;
}
// Debug method to help diagnose connectivity issues
debugConnectivity() {
const connectedPeers = this.connectionManager.getConnectedPeerCount();
const discoveredPeers = this.mesh.getDiscoveredPeers();
const totalPeers = this.connectionManager.peers.size;
this.debug.log('=== CONNECTIVITY DEBUG ===');
this.debug.log(`My Peer ID: ${this.mesh.peerId}`);
this.debug.log(`Connected Peers: ${connectedPeers}/${this.mesh.maxPeers}`);
this.debug.log(`Total Peer Objects: ${totalPeers}`);
this.debug.log(`Discovered Peers: ${discoveredPeers.length}`);
this.debug.log('\nPeer Details:');
this.connectionManager.peers.forEach((peerConnection, peerId) => {
const status = peerConnection.getStatus();
const dataChannelReady = peerConnection.dataChannelReady;
const connectionState = peerConnection.connection?.connectionState || 'unknown';
const dataChannelState = peerConnection.dataChannel?.readyState || 'unknown';
this.debug.log(` ${peerId.substring(0, 8)}... - Status: ${status}, WebRTC: ${connectionState}, DataChannel: ${dataChannelState}, Ready: ${dataChannelReady}`);
});
this.debug.log('\nDiscovered Peers:');
discoveredPeers.forEach(peer => {
const shouldInitiate = this.mesh.peerId < peer.peerId;
const isConnected = this.connectionManager.hasPeer(peer.peerId);
this.debug.log(` ${peer.peerId.substring(0, 8)}... - ShouldInitiate: ${shouldInitiate}, IsConnected: ${isConnected}`);
});
this.debug.log('\nConnection Attempts:');
this.connectionManager.connectionAttempts.forEach((attempts, peerId) => {
this.debug.log(` ${peerId.substring(0, 8)}... - Attempts: ${attempts}/${this.connectionManager.maxConnectionAttempts}`);
});
this.debug.log('\nEviction Status:');
this.debug.log(` Eviction Strategy: ${this.mesh.evictionStrategy ? 'enabled' : 'disabled'}`);
this.debug.log(` XOR Routing: ${this.mesh.xorRouting ? 'enabled' : 'disabled'}`);
this.debug.log('=== END DEBUG ===\n');
return {
connectedPeers,
totalPeers,
discoveredPeers: discoveredPeers.length,
evictionEnabled: this.mesh.evictionStrategy,
xorRouting: this.mesh.xorRouting,
peerStatuses: Array.from(this.connectionManager.peers.entries()).map(([peerId, conn]) => ({
peerId: peerId.substring(0, 8),
status: conn.getStatus(),
dataChannelReady: conn.dataChannelReady
}))
};
}
}