Files
ssessesse-ki-battle/tests/peer-browser-monitor.js
T

60 lines
4.9 KiB
JavaScript

// 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 = '<label>검증 입력 <select id="test-input"><option value="none">수동</option><option value="charge">충전 반복</option><option value="blast">충전 후 파</option><option value="guard">막기 반복</option></select></label>';
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 });
}); });
}
};
})();