// Diagnostic harness for real-browser testing; excluded from production bundles. (() => { const NativePeer = window.RTCPeerConnection, NativeSocket = window.WebSocket; const trace = [], channels = new Set(); let sockets = 0, nextPeer = 0, dropPackets = false; document.addEventListener('DOMContentLoaded', () => { const controls = document.createElement('aside'); controls.style.cssText = 'position:fixed;right:0;top:0;z-index:100;background:white;color:black;padding:4px'; controls.innerHTML = ''; document.body.append(controls); for (const [label, action] of [ ['채널 종료', () => { for (const channel of channels) if (channel.readyState === 'open') channel.close(); }], ['패킷 차단', () => { dropPackets = true; report('packets-blocked'); }], ['패킷 복구', () => { dropPackets = false; report('packets-restored'); }], ]) { const button = document.createElement('button'); button.textContent = label; button.onclick = action; controls.append(button); } setInterval(() => { const mode = controls.querySelector('select').value; if (mode === 'none') return; const blast = document.querySelector('button[data-action="blast"]'); const action = mode === 'blast' ? blast && !blast.disabled ? 'blast' : 'charge' : mode; const button = document.querySelector('button[data-action="' + action + '"]'); if (button && !button.disabled && button.getClientRects().length) button.click(); }, 100); }); function report(event, details = {}) { trace.push({ event, ...details }); if (trace.length > 80) trace.shift(); console.info('RTC_TEST ' + JSON.stringify({ sockets, ...trace.at(-1) })); let output = document.getElementById('rtc-test'); if (!output && document.body) { output = document.createElement('pre'); output.id = 'rtc-test'; output.style.cssText = 'position:fixed;right:0;bottom:0;max-height:110px;max-width:460px;overflow:auto;background:white;color:black;font:10px monospace;z-index:100;opacity:.85'; document.body.append(output); } if (output) output.textContent = JSON.stringify({ sockets, trace: trace.slice(-12) }, null, 1); } window.WebSocket = class extends NativeSocket { constructor(...args) { super(...args); this.addEventListener('open', () => { sockets++; report('socket-open'); }); this.addEventListener('close', () => { sockets--; report('socket-close'); }); } }; window.RTCPeerConnection = class extends NativePeer { constructor(...args) { super(...args); this.testId = ++nextPeer; report('pc-created', { id: this.testId }); for (const event of ['connectionstatechange', 'iceconnectionstatechange', 'signalingstatechange']) this.addEventListener(event, () => report(event, { id: this.testId, connection: this.connectionState, ice: this.iceConnectionState, signaling: this.signalingState })); this.addEventListener('datachannel', e => this.monitor(e.channel)); } createDataChannel(...args) { const channel = super.createDataChannel(...args); this.monitor(channel); return channel; } setConfiguration(value) { try { super.setConfiguration(value); report('configuration-updated', { id: this.testId }); } catch (error) { report('configuration-error', { message: error.message, state: this.connectionState, before: this.getConfiguration().bundlePolicy, after: value.bundlePolicy }); throw error; } } restartIce() { try { super.restartIce(); report('ice-restart', { id: this.testId }); } catch (error) { report('restart-error', { message: error.message, state: this.connectionState }); throw error; } } monitor(channel) { channels.add(channel); channel.addEventListener('close', () => channels.delete(channel)); const send = channel.send.bind(channel); channel.send = data => { if (!dropPackets) send(data); }; channel.addEventListener('message', event => { if (dropPackets) event.stopImmediatePropagation(); }, { capture: true }); for (const event of ['open', 'close', 'error']) channel.addEventListener(event, () => report('channel-' + event, { id: this.testId })); channel.addEventListener('message', event => { try { const m = JSON.parse(event.data); report('message', { id: this.testId, type: m.type, epoch: m.connectionEpoch ?? m.epoch, turn: m.turnId }); } catch {} }); channel.addEventListener('open', () => { void this.getStats().then(stats => { for (const item of stats.values()) if (item.type === 'candidate-pair' && item.state === 'succeeded') report('candidate', { id: this.testId, local: stats.get(item.localCandidateId)?.candidateType, remote: stats.get(item.remoteCandidateId)?.candidateType, rtt: item.currentRoundTripTime }); }); }); } }; })();