All checks were successful
Build and Push Docker Image / build (push) Successful in 1m5s
181 lines
4.6 KiB
JavaScript
181 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" });
|
|
setInterval(() => this.collectAndPostAllStats(), this.updateInterval);
|
|
}
|
|
|
|
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.collectAndPostAllStats();
|
|
|
|
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 collectAndPostAllStats() {
|
|
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);
|
|
|
|
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;
|
|
},
|
|
});
|