Pairing-Logs in ?debug: _log() in p2p.js, Panel links unten
All checks were successful
Pin to IPFS / pin (push) Successful in 11s

This commit is contained in:
Ole 2026-06-14 18:33:36 +02:00
parent f3f43ae68e
commit becd1593b6
4 changed files with 95 additions and 5 deletions

15
app.js
View file

@ -281,6 +281,7 @@
mesh.playerName = name;
mesh.names.set(mesh.myPeerId, name || mesh.myPeerId.slice(0, 8));
mesh.onchat = (peerId, text) => addChatMessage(mesh.getPeerName(peerId), text);
mesh.onlog = addPairingLog;
mesh.onpeerconnect = pid => {
renderPlayers('host-player-list');
@ -348,7 +349,7 @@
mesh.playerName = name;
mesh.names.set(mesh.myPeerId, name || mesh.myPeerId.slice(0, 8));
mesh.onchat = (peerId, text) => addChatMessage(mesh.getPeerName(peerId), text);
show('join');
mesh.onlog = addPairingLog;
$('join-answer-section').hidden = true;
$('join-scan-status').textContent = 'QR-Code scannen...';
@ -438,6 +439,18 @@
show('start');
});
/* ─── Debug pairing log ─── */
const pairingMessages = $('debug-pairing-messages');
function addPairingLog(msg) {
if (!pairingMessages) return;
const div = document.createElement('div');
const t = new Date().toLocaleTimeString('de-DE', { hour12: false });
div.textContent = `[${t}] ${msg}`;
pairingMessages.appendChild(div);
pairingMessages.scrollTop = pairingMessages.scrollHeight;
}
/* ─── Debug chat ─── */
const chatInput = $('debug-chat-text');
const chatMessages = $('debug-chat-messages');

View file

@ -128,6 +128,10 @@
<button id="debug-chat-send">Senden</button>
</div>
</div>
<div id="debug-pairing" class="debug-only">
<div id="debug-pairing-header">Pairing-Logs</div>
<div id="debug-pairing-messages"></div>
</div>
<footer>
<p>&copy; by neunzig.wtf &middot; commit <a id="commit-hash" href="https://git.ziemlichoptimal.de/90wtf/randomp2p/src/commit/HASH">HASH</a></p>
</footer>

43
p2p.js
View file

@ -22,8 +22,11 @@ class MeshNet {
this.onanswerready = null;
this.onreadyupdate = null;
this.onchat = null;
this.onlog = null;
}
_log(msg) { if (this.onlog) this.onlog(msg); }
/* ─── Host: room ─── */
createRoom() {
@ -42,6 +45,7 @@ class MeshNet {
peer.on('signal', async signal => {
if (signalSent) return;
signalSent = true;
this._log('SDP-Signal (Offer) empfangen');
const minSignal = signal.sdp ? { ...signal, sdp: stripSDP(signal.sdp) } : signal;
const packed = await pack({ v: 1, id: this.myPeerId, s: minSignal });
@ -49,10 +53,12 @@ class MeshNet {
this._fullSDPData = this._currentQRData;
if (this.onqrupdate) this.onqrupdate(this._currentQRData);
this._log('Offer QR erstellt');
});
peer.on('connect', () => {
peer.send(JSON.stringify({ type: 'identity', peerId: this.myPeerId, name: this.playerName }));
this._log('DataChannel offen, sende Identity');
});
peer.on('data', data => {
@ -71,14 +77,19 @@ class MeshNet {
}
async feedAnswer(answerStr) {
if (!this._pendingPeer || this._pendingPeer.signalReceived) return;
if (!this._pendingPeer || this._pendingPeer.signalReceived) {
this._log('feedAnswer ignoriert (bereits verbunden)');
return;
}
try {
const data = await unpack(decode45(answerStr));
this._pendingPeer.peerId = data.id;
this._pendingPeer.peer.signal(data.s);
this._pendingPeer.signalReceived = true;
this._log(`Answer verarbeitet von ${data.id.slice(0, 8)}`);
} catch (e) {
console.error('feedAnswer error:', e);
this._log(`feedAnswer Fehler: ${e.message}`);
}
}
@ -89,6 +100,7 @@ class MeshNet {
const remoteId = data.id;
this.hostPeerId = remoteId;
const offer = data.s;
this._log(`QR gelesen, signalisiere Host ${remoteId.slice(0, 8)}`);
const peer = new SimplePeer({ initiator: false, trickle: false, config: { iceServers: [] } });
let answerSent = false;
@ -100,11 +112,13 @@ class MeshNet {
const packed = await pack({ v: 1, id: this.myPeerId, s: minSignal, to: remoteId });
const answerData = encode45(packed);
if (this.onanswerready) this.onanswerready(answerData);
this._log('Answer QR erstellt');
});
peer.on('connect', () => {
this.connections.set(remoteId, peer);
peer.send(JSON.stringify({ type: 'identity', peerId: this.myPeerId, name: this.playerName }));
this._log(`DataChannel zu Host ${remoteId.slice(0, 8)} offen`);
if (this.onpeerconnect) this.onpeerconnect(remoteId);
});
@ -116,7 +130,10 @@ class MeshNet {
});
peer.on('close', () => this._cleanupPeer(peer));
peer.on('error', err => console.error('p2p error:', err));
peer.on('error', err => {
console.error('p2p error:', err);
this._log(`Peer-Fehler: ${err.message}`);
});
peer.signal(offer);
this.connections.set(remoteId, peer);
@ -134,7 +151,7 @@ class MeshNet {
peer.on('connect', () => {
if (onConnect) onConnect();
// No identity exchange peerId known from context
this._log('DataChannel (Mesh) offen');
});
peer.on('data', data => {
@ -145,7 +162,10 @@ class MeshNet {
});
peer.on('close', () => this._cleanupPeer(peer));
peer.on('error', e => console.error('mesh error:', e));
peer.on('error', e => {
console.error('mesh error:', e);
this._log(`Mesh-Fehler: ${e.message}`);
});
return peer;
}
@ -154,6 +174,7 @@ class MeshNet {
if (this.connections.has(targetPeerId)) return;
if (this._pendingMeshPeers.has(targetPeerId)) return;
if (this.myPeerId >= targetPeerId) return;
this._log(`Mesh zu ${targetPeerId.slice(0, 8)} initiiert`);
const peer = this._createSimplePeer(true,
signal => {
@ -171,6 +192,7 @@ class MeshNet {
}
_handleRelayedSignal(fromPeerId, signal) {
this._log(`Relayed-Signal von ${fromPeerId.slice(0, 8)}`);
let peer = this.connections.get(fromPeerId) || this._pendingMeshPeers.get(fromPeerId);
if (!peer) {
@ -192,6 +214,8 @@ class MeshNet {
}
_checkMeshConnections() {
const missing = this.roster.filter(pid => pid !== this.myPeerId && !this.connections.has(pid) && this.myPeerId < pid);
if (missing.length) this._log(`Prüfe Mesh: ${missing.length} fehlende Verbindungen`);
for (const pid of this.roster) {
if (pid === this.myPeerId) continue;
if (this.connections.has(pid)) continue;
@ -214,6 +238,7 @@ class MeshNet {
if (allConnected && this.roster.length > 0) {
this._meshReadySent = true;
this.sendTo(this.hostPeerId, { type: 'mesh_ready' });
this._log('Mesh ready gesendet');
}
}
@ -221,6 +246,8 @@ class MeshNet {
this.roster = this.getPeerIds();
this.readyPeers.clear();
this.broadcast({ type: 'roster', peers: this.roster.map(id => ({ id, name: this.names.get(id) || '' })) });
this.readyPeers.clear();
this._log(`Roster broadcast an ${this.roster.length} Peers`);
if (this.onreadyupdate) this.onreadyupdate(0, this.roster.length);
}
@ -236,23 +263,27 @@ class MeshNet {
this.connections.delete(existing);
}
this.connections.set(msg.peerId, peer);
this._log(`Identity: ${msg.peerId.slice(0, 8)}`);
if (this._pendingPeer && this._pendingPeer.peer === peer) {
this._pendingPeer.peerId = msg.peerId;
this._pendingPeer.connected = true;
this._pendingPeer = null;
if (this.onpeerconnect) this.onpeerconnect(msg.peerId);
this._log(`Host: Peer ${msg.peerId.slice(0, 8)} promoted`);
if (this.isHost) {
setTimeout(() => this._createPendingPeer(), 300);
this._broadcastRoster();
}
} else {
if (this.onpeerconnect) this.onpeerconnect(msg.peerId);
this._log(`Mesh: Peer ${msg.peerId.slice(0, 8)} verbunden`);
}
return;
}
if (msg.type === 'signal_relay') {
this._log(`Signal-Relay weitergeleitet an ${msg.to.slice(0, 8)}`);
const target = this.connections.get(msg.to) || this._pendingMeshPeers.get(msg.to);
if (target) {
target.send(JSON.stringify({
@ -274,11 +305,13 @@ class MeshNet {
if (msg.peers) msg.peers.forEach(p => { if (p.name) this.names.set(p.id, p.name); });
this._meshReadySent = false;
this._checkMeshConnections();
this._log(`Roster empfangen: ${this.roster.length} Peers`);
return;
}
if (msg.type === 'mesh_ready' && this.isHost) {
this.readyPeers.add(peerId);
this._log(`Mesh ready von ${peerId.slice(0, 8)}`);
if (this.onreadyupdate) {
this.onreadyupdate(this.readyPeers.size, this.roster.length);
}
@ -311,6 +344,7 @@ class MeshNet {
const peerId = this._findPeerId(peer);
if (peerId) {
this.connections.delete(peerId);
this._log(`Peer getrennt: ${peerId.slice(0, 8)}`);
if (this.onpeerdisconnect) this.onpeerdisconnect(peerId);
}
@ -366,6 +400,7 @@ class MeshNet {
}
destroy() {
this._log('MeshNet wird zerstört');
for (const peer of this.connections.values()) {
try { peer.destroy(); } catch (e) {}
}

View file

@ -476,5 +476,43 @@ footer {
background: var(--accent-hover);
}
/* Debug pairing log */
#debug-pairing {
position: fixed; bottom: 0; left: 8px;
width: 320px; max-height: 260px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius) var(--radius) 0 0;
font-size: 0.75rem;
font-family: monospace;
z-index: 100;
display: none;
}
.debug-mode #debug-pairing { display: flex; flex-direction: column; }
#debug-pairing-header {
padding: 4px 8px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
cursor: default;
}
#debug-pairing-messages {
flex: 1; overflow-y: auto; padding: 6px 8px;
max-height: 260px;
display: flex; flex-direction: column; gap: 2px;
}
#debug-pairing-messages div {
padding: 2px 4px;
border-radius: 3px;
line-height: 1.3;
word-break: break-all;
}
#debug-pairing-messages div:nth-child(even) {
background: rgba(255,255,255,0.03);
}
/* Utility */
.hidden { display: none !important; }