class PeerManager { constructor() { this.peer = null; this.connections = new Map(); this.roomId = null; this.isHost = false; this.onPeerConnected = null; this.onPeerDisconnected = null; this.onMessageReceived = null; this.onConnectionStatusChange = null; } async initialize() { try { this.peer = new Peer({ debug: 2, config: { iceServers: [ { urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' } ] } }); this.peer.on('open', (id) => { console.log('My peer ID is:', id); this.updateConnectionStatus('connected'); this.updatePeersList(); if (this.onConnectionStatusChange) { this.onConnectionStatusChange('connected', id); } }); this.peer.on('connection', (conn) => { this.handleIncomingConnection(conn); }); this.peer.on('disconnected', () => { this.updateConnectionStatus('disconnected'); this.updatePeersList(); }); this.peer.on('error', (err) => { console.error('Peer error:', err); this.updateConnectionStatus('error'); }); } catch (error) { console.error('Failed to initialize peer:', error); throw error; } } async createRoom() { if (!this.peer) { throw new Error('Peer not initialized'); } this.roomId = this.generateRoomId(); this.isHost = true; console.log('Created room:', this.roomId); return this.roomId; } async joinRoom(roomId) { if (!this.peer) { throw new Error('Peer not initialized'); } this.roomId = roomId; this.isHost = false; console.log('Attempting to join room:', roomId); try { const conn = this.peer.connect(roomId); await this.setupConnection(conn); console.log('Successfully joined room'); return true; } catch (error) { console.error('Failed to join room:', error); throw new Error('Could not connect to host. Make sure the host is online and you have the correct peer ID.'); } } handleIncomingConnection(conn) { this.setupConnection(conn); } async setupConnection(conn) { return new Promise((resolve, reject) => { conn.on('open', () => { console.log('Connected to:', conn.peer); this.connections.set(conn.peer, conn); this.updatePeersList(); if (this.isHost) { if (this.onPeerConnected) { this.onPeerConnected(conn.peer); } } else { if (this.onPeerConnected) { this.onPeerConnected(conn.peer); } } resolve(conn); }); conn.on('data', (data) => { this.handleMessage(data, conn.peer); }); conn.on('close', () => { console.log('Connection closed:', conn.peer); this.connections.delete(conn.peer); this.updatePeersList(); if (this.onPeerDisconnected) { this.onPeerDisconnected(conn.peer); } }); conn.on('error', (err) => { console.error('Connection error:', err); reject(err); }); }); } handleMessage(data, senderId) { console.log('Received message:', data, 'from:', senderId); if (this.onMessageReceived) { this.onMessageReceived(data, senderId); } } sendMessage(peerId, message) { const conn = this.connections.get(peerId); if (conn && conn.open) { conn.send(message); return true; } return false; } broadcastMessage(message) { let sentCount = 0; this.connections.forEach((conn, peerId) => { if (conn.open) { conn.send(message); sentCount++; } }); return sentCount; } getPeerId() { return this.peer ? this.peer.id : null; } getRoomId() { return this.roomId; } getConnectedPeers() { return Array.from(this.connections.keys()); } getPeerCount() { return this.connections.size; } isConnected() { return this.peer && this.peer.disconnected !== true; } generateRoomId() { return Math.random().toString(36).substr(2, 9).toUpperCase(); } updateConnectionStatus(status) { const indicator = document.getElementById('connection-indicator'); const statusText = document.getElementById('connection-status-text'); if (indicator) { indicator.className = `status-${status}`; } if (statusText) { statusText.textContent = status.charAt(0).toUpperCase() + status.slice(1); statusText.className = `status-text ${status}`; } } updatePeersList() { const peersList = document.getElementById('peers'); const peerCount = document.getElementById('peer-count'); if (peersList && peerCount) { peerCount.textContent = this.getPeerCount(); peersList.innerHTML = ''; if (this.getPeerCount() === 0) { const li = document.createElement('li'); li.textContent = 'No connected peers'; li.style.fontStyle = 'italic'; li.style.color = '#9ca3af'; peersList.appendChild(li); } else { this.getConnectedPeers().forEach(peerId => { const li = document.createElement('li'); li.textContent = `${peerId} ${peerId === this.peer.id ? '(You)' : ''}`; li.style.fontWeight = peerId === this.peer.id ? 'bold' : 'normal'; li.style.color = peerId === this.peer.id ? '#667eea' : '#6b7280'; peersList.appendChild(li); }); } } } destroy() { if (this.peer) { this.connections.forEach(conn => conn.close()); this.peer.destroy(); this.peer = null; this.connections.clear(); } } }