217 lines
5.5 KiB
JavaScript
217 lines
5.5 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
}
|