Files
peertube-collector/webrtc-internals-exporter/override.js
Mirko Milovanovic f579000a96
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m16s
docs: simplify Docker commands in README and enhance logging in WebRTC stats exporter
2025-02-13 18:31:09 +01:00

182 lines
4.6 KiB
JavaScript

function log(...args) {
console.log.apply(null, ["[webrtc-internal-exporter:override]", ...args]);
}
log("Override RTCPeerConnection.");
class WebrtcInternalExporter {
peerConnections = new Map();
url = "";
enabled = false;
updateInterval = 2000;
enabledStats = [];
constructor() {
window.addEventListener("message", async (message) => {
const { event, options } = message.data;
if (event === "webrtc-internal-exporter:options") {
log("options updated:", options);
Object.assign(this, options);
}
});
window.postMessage({ event: "webrtc-internal-exporter:ready" });
this.collectAllStats();
}
randomId() {
return (
window.crypto?.randomUUID() || (2 ** 64 * Math.random()).toString(16)
);
}
/**
* @param {RTCPeerConnection} pc
*/
add(pc) {
const id = this.randomId();
pc.iceCandidates = [];
pc.iceCandidateErrors = [];
this.peerConnections.set(id, pc);
pc.addEventListener("connectionstatechange", () => {
log(`connectionStateChange: ${pc.connectionState}`);
this.collectAndPostSingleStat(id);
if (pc.connectionState === "closed") {
this.peerConnections.delete(id);
}
});
/**
* @param {RTCPeerConnectionIceErrorEvent} event
*/
pc.addEventListener("icecandidateerror", (event) => {
this.peerConnections.get(id).iceCandidateErrors.push({
timestamp: Date.now(),
address: event.errorAddress,
errorCode: event.errorCode,
errorText: event.errorText,
port: event.errorPort,
url: event.url,
});
});
/**
* @param {RTCPeerConnectionIceEvent} event
*/
pc.addEventListener("icecandidate", (event) => {
this.peerConnections.get(id).iceCandidates.push({
timestamp: Date.now(),
candidate: event.candidate?.candidate,
component: event.candidate?.component,
foundation: event.candidate?.foundation,
port: event.candidate?.port,
priority: event.candidate?.priority,
protocol: event.candidate?.protocol,
relatedAddress: event.candidate?.relatedAddress,
relatedPort: event.candidate?.relatedPort,
sdpMLineIndex: event.candidate?.sdpMLineIndex,
sdpMid: event.candidate?.sdpMid,
tcpType: event.candidate?.tcpType,
type: event.candidate?.type,
usernameFragment: event.candidate?.usernameFragment,
});
});
}
async collectAndPostSingleStat(id) {
const stats = await this.collectStats(id);
if (Object.keys(stats).length === 0 || !stats) return;
window.postMessage(
{
event: "webrtc-internal-exporter:peer-connection-stats",
stats: [stats]
},
[stats]
);
log(`Single stat collected:`, [stats]);
}
async collectAllStats() {
const stats = [];
for (const [id] of this.peerConnections) {
if (this.url && this.enabled) {
const pcStats = await this.collectStats(id);
if (Object.keys(pcStats).length === 0 || !pcStats) continue;
stats.push(pcStats);
}
}
window.postMessage(
{
event: "webrtc-internal-exporter:peer-connections-stats",
data: stats
},
stats
);
log(`Stats collected:`, stats);
setTimeout(this.collectAllStats.bind(this), this.updateInterval);
return stats;
}
/**
* @param {string} id
*/
async collectStats(id) {
var pc = this.peerConnections.get(id);
if (!pc) return;
var completeStats = {};
if (this.url && this.enabled) {
try {
const stats = await pc.getStats();
const values = [...stats.values()].filter(
(v) =>
["peer-connection", ...this.enabledStats].indexOf(v.type) !== -1
);
completeStats = {
url: window.location.href,
id,
connectionState: pc.connectionState,
iceConnectionState: pc.iceConnectionState,
iceGatheringState: pc.iceGatheringState,
signalingState: pc.signalingState,
iceCandidateErrors: pc.iceCandidateErrors,
iceCandidates: pc.iceCandidates,
values,
};
} catch (error) {
log(`collectStats error: ${error.message}`);
}
}
if (pc.connectionState === "closed") {
this.peerConnections.delete(id);
}
return completeStats;
}
}
const webrtcInternalExporter = new WebrtcInternalExporter();
window.RTCPeerConnection = new Proxy(window.RTCPeerConnection, {
construct(target, argumentsList) {
log(`RTCPeerConnection`, argumentsList);
const pc = new target(...argumentsList);
webrtcInternalExporter.add(pc);
return pc;
},
});