class MeshNet { constructor() { this.myPeerId = generatePeerId(); this.connections = new Map(); this.isHost = false; this._pendingPeer = null; this._currentQRData = null; this.onpeerconnect = null; this.onpeerdisconnect = null; this.ondata = null; this.onqrupdate = null; this.onanswerready = null; } createRoom() { this.isHost = true; this._createPendingPeer(); } _createPendingPeer() { if (this._pendingPeer) { try { this._pendingPeer.peer.destroy(); } catch (e) {} } const peer = new SimplePeer({ initiator: true, trickle: false }); let signalSent = false; let connected = false; peer.on('signal', signal => { if (signalSent) return; signalSent = true; this._currentQRData = JSON.stringify({ v: 1, id: this.myPeerId, s: signal }); if (this.onqrupdate) this.onqrupdate(this._currentQRData); }); peer.on('connect', () => { connected = true; peer.send(JSON.stringify({ type: 'identity', peerId: this.myPeerId })); }); peer.on('data', data => { try { const msg = JSON.parse(data.toString()); this._handleMessage(msg, peer); } catch (e) { console.error('p2p data parse error:', e); } }); peer.on('close', () => { this._cleanupPeer(peer); }); peer.on('error', err => { console.error('p2p error:', err); }); this._pendingPeer = { peer, signalSent, connected, peerId: null }; } feedAnswer(answerStr) { if (!this._pendingPeer || this._pendingPeer.signalReceived) return; try { const data = JSON.parse(answerStr); this._pendingPeer.peerId = data.id; this._pendingPeer.peer.signal(data.s); this._pendingPeer.signalReceived = true; } catch (e) { console.error('feedAnswer error:', e); } } joinFromQR(qrContent) { const data = JSON.parse(qrContent); const remoteId = data.id; const offer = data.s; const peer = new SimplePeer({ initiator: false, trickle: false }); let answerSent = false; let connected = false; peer.on('signal', signal => { if (answerSent) return; answerSent = true; const answerData = JSON.stringify({ v: 1, id: this.myPeerId, s: signal, to: remoteId }); if (this.onanswerready) this.onanswerready(answerData); }); peer.on('connect', () => { connected = true; this.connections.set(remoteId, peer); peer.send(JSON.stringify({ type: 'identity', peerId: this.myPeerId })); if (this.onpeerconnect) this.onpeerconnect(remoteId); }); peer.on('data', data => { try { const msg = JSON.parse(data.toString()); this._handleMessage(msg, peer); } catch (e) { console.error('p2p data parse error:', e); } }); peer.on('close', () => { this._cleanupPeer(peer); }); peer.on('error', err => { console.error('p2p error:', err); }); peer.signal(offer); this.connections.set(remoteId, peer); this._pendingPeer = { peer, peerId: remoteId, connected }; } _handleMessage(msg, peer) { if (msg.type === 'identity') { const existing = this._findPeerId(peer); if (existing && existing !== msg.peerId) { this.connections.delete(existing); } this.connections.set(msg.peerId, peer); if (this._pendingPeer && this._pendingPeer.peer === peer) { this._pendingPeer.peerId = msg.peerId; this._pendingPeer.connected = true; if (this.onpeerconnect) this.onpeerconnect(msg.peerId); if (this.isHost) { setTimeout(() => this._createPendingPeer(), 300); } } else { if (this.onpeerconnect) this.onpeerconnect(msg.peerId); } return; } if (msg.type === 'signal_relay') { const target = this.connections.get(msg.to); if (target) { target.send(JSON.stringify({ type: 'signal_relayed', from: msg.from, signal: msg.signal })); } return; } if (this.ondata) { const peerId = this._findPeerId(peer) || 'unknown'; this.ondata(peerId, msg); } } _findPeerId(peer) { for (const [id, p] of this.connections) { if (p === peer) return id; } return null; } _cleanupPeer(peer) { const peerId = this._findPeerId(peer); if (peerId) { this.connections.delete(peerId); if (this.onpeerdisconnect) this.onpeerdisconnect(peerId); } if (this._pendingPeer && this._pendingPeer.peer === peer) { this._pendingPeer = null; } } broadcast(msg) { const str = JSON.stringify(msg); for (const [id, peer] of this.connections) { try { peer.send(str); } catch (e) { console.error('broadcast to', id, 'failed:', e); } } } sendTo(peerId, msg) { const peer = this.connections.get(peerId); if (peer) { try { peer.send(JSON.stringify(msg)); } catch (e) { console.error('sendTo', peerId, 'failed:', e); } } } relaySignal(from, to, signal) { const target = this.connections.get(to); if (target) { target.send(JSON.stringify({ type: 'signal_relay', from, to, signal })); } } getPeerIds() { return Array.from(this.connections.keys()); } getCount() { return this.connections.size; } destroy() { for (const peer of this.connections.values()) { try { peer.destroy(); } catch (e) {} } this.connections.clear(); if (this._pendingPeer) { try { this._pendingPeer.peer.destroy(); } catch (e) {} this._pendingPeer = null; } } }