// ==UserScript==
// @name Site Measurements
// @namespace http://tampermonkey.net/
// @version 2024-08-06.3
// @license MIT
// @description Logs fetch, xhr, ws requests in console
// @author Grosmar
// @match https://*.livejasmin.com/*
// @match https://livejasmin.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=livejasmin.com
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// Measurement prep
let measurements = [];
const pushMeasurement = (msg) => { if (msg) { measurements.push( {time: Date.now(), msg: msg } ) } return msg; };
// CONFIG
const enableLogs = true;
const filteredWs = /jaws\./; // these websockets will be filtered out from logging
const binaryWsPackageCountLimit = 3; //show only this amount of binary packages
const wsEventFilter = { r: /onRandomAccessPoint|onMetaData/, limit: 2} // messages with this content will be shown only `limit` amount of time
const injectWsSend = { pattern: /"join",\{/, replacement: '"join",{"enableTrace":true,' }; // if message pattern matches, injects this into websocket command
let wsBinaryCounter = 0; // Used to determine second binary package for keyframe
const measurementConfig =
[
{
callType: /^(?:NETWORK_User_Click|NETWORK_User_TouchStart)/,
filter: null,
filterArgIndex: null,
func: (args) => "USER_CLICK"
},
{
callType: /^NETWORK_Ws_Msg/,
filter: /"getEdge"/,
filterArgIndex: 1,
func: (args) => "OC_JOIN" //measurements = measurements.concat(JSON.parse(args[0].substr(3)).map( e =>
},
{
callType: /^NETWORK_Ws_Msg/,
filter: /setVideoData/,
filterArgIndex: 1,
func: (args) => "OC_SET_VIDEO_DATA" //measurements = measurements.concat(JSON.parse(args[0].substr(3)).map( e =>
},
{
callType: /^NETWORK_Ws_Conn/,
filter: /ngs-edge/,
filterArgIndex: 0,
func: (args) => { wsBinaryCounter = 0; return "STREAM_ON_CONNECT" }
},
{
callType: /^NETWORK_Ws_Open/,
filter: /ngs-edge/,
filterArgIndex: 0,
func: (args) => "STREAM_ON_CONNECTED"
},
{
callType: /^NETWORK_Ws_Msg/,
filter: /"eventType":"onServerInfo"/,
filterArgIndex: 1,
func: (args) => "STREAM_ON_SERVER_INFO"
},
{
callType: /^NETWORK_Ws_Msg/,
filter: /"onStreamStatus":/,
filterArgIndex: 1,
func: (args) => "STREAM_ON_STREAM_STATUS"
},
{
callType: /^NETWORK_Ws_Msg/,
filter: /"eventType":"onStreamInfo"/,
filterArgIndex: 1,
func: (args) => "STREAM_ON_STREAM_INFO"
},
{
callType: /^NETWORK_Ws_Msg/,
filter: /ngs-edge/,
filterArgIndex: 0,
func: (args) => args[2] && ++wsBinaryCounter == 2 ? "STREAM_RECEIVE_KEYFRAME" : null
},
{
callType: /^NETWORK_Video_LoadedData/,
filter: null,
filterArgIndex: null,
func: (args) => "STREAM_MEDIA_LOADED_DATA"
},
{
callType: /^NETWORK_Video_Playing/,
filter: null,
filterArgIndex: null,
func: (args) => "STREAM_MEDIA_PLAYING"
}
]; // these steps will be measured and shown when reaches the last step
// SCRIPT
console.log("MEASUREMENT");
// Measurements
const printMeasurements = () =>
{
if (measurements.length < 2 )
{
return;
}
let result = "";
let sumTime = 0;
for ( let i = 1; i < measurements.length; i++ )
{
let diffTime = measurements[i].time - measurements[i-1].time;
sumTime += diffTime;
result += measurements[i].msg.padEnd(25) + " " + (diffTime).toString().padStart(4) + "\n";
}
console.log('%cMEASUREMENTS\n____________\n' + result + '\n' + 'SUM'.padEnd(25) + ' ' + sumTime.toString().padStart(4), 'background: red; color: #bada55');
}
const addMeasurement = (callType, ...args) =>
{
if (enableLogs)
{
console.log( callType, ...args );
}
for ( let i = 0; i < measurementConfig.length; i++ )
{
if (measurementConfig[i].callType.test(callType) && (!measurementConfig[i].filter || !measurementConfig[i].filterArgIndex || measurementConfig[i].filter.test(args[measurementConfig[i].filterArgIndex])))
{
let pushed = false;
if ( (pushed = pushMeasurement( measurementConfig[i].func(args) )) && i == 0 )
{
measurements = measurements.slice(-1); // reset if it was the first real action
}
if ( i == measurementConfig.length - 1 && pushed )
{
printMeasurements(); // print if it was the last
measurements = [];
}
}
}
}
// Image listeners
const OriglImage = window.Image;
window.Image = function(...args) { const img = new OriginalImage(...args); img.addEventListener('load', function() {addMeasurement('NETWORK_Img2', img.src);}); return img; };
function handleNewImages(images) {
images.forEach(img => {
addMeasurement('NETWORK_Img', img.src);
});
}
const loadedListener = (e) => { addMeasurement('NETWORK_Video_LoadedData', e.target.id); }
const playingListener = (e) => { addMeasurement('NETWORK_Video_Playing', e.target.id); }
function handleNewVideos(videos) {
videos.forEach(video => {
video.addEventListener("loadeddata", loadedListener);
video.addEventListener("playing", playingListener );
});
}
const observer = new MutationObserver(mutations => { // Create a MutationObserver to watch for added nodes
mutations.forEach(mutation => {
if (mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(node => {
if (node.tagName === 'IMG') {
handleNewImages([node]);
} else if (node.querySelectorAll) {
// Check for images within added containers
const imgs = node.querySelectorAll('img');
if (imgs.length > 0) {
handleNewImages(imgs);
}
}
if (node.tagName === 'VIDEO') {
handleNewVideos([node]);
} else if (node.querySelectorAll) {
// Check for images within added containers
const videos = node.querySelectorAll('video');
if (videos.length > 0) {
handleNewVideos(videos);
}
}
});
}
});
});
observer.observe(document.body, { // Start observing the document body for changes
childList: true,
subtree: true
});
// User listeners
document.addEventListener("click", (event) => { addMeasurement("NETWORK_User_Click", event.target); });
document.addEventListener("touchstart", (event) => { addMeasurement("NETWORK_User_TouchStart", event.target); });
document.addEventListener("touchend", (event) => { addMeasurement("NETWORK_User_TouchEnd", event.target); });
// Request listeners
const origFetch = fetch;
window.fetch = (...args) => { addMeasurement("NETWORK_Fetch", ...args); return origFetch(...args); }
const origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (...args) { addMeasurement("NETWORK_Xhr", ...args); return origOpen.apply(this, args); }
// Websocket listeners
const origWsSend = WebSocket.prototype.send;
WebSocket.prototype.send = function(data)
{
if (data != 3 && !filteredWs.test(this.url))
{
addMeasurement("NETWORK_Ws_Send", this.url, data);
}
if ( !(data instanceof ArrayBuffer) && injectWsSend.pattern.test(data))
{
data = data.replace(injectWsSend.pattern, injectWsSend.replacement);
}
return origWsSend.call(this, data);
}
const OrigWs = window.WebSocket;
window.WebSocket = function (url, protocols)
{
let ws = new OrigWs(url, protocols);
if (!filteredWs.test(url)) {
let binaryCount = 0;
let filteredMsgCount = 0;
addMeasurement("NETWORK_Ws_Conn", url, protocols);
ws.addEventListener("open", (event) => { addMeasurement("NETWORK_Ws_Open", url); });
ws.addEventListener("close", (event) => { addMeasurement("NETWORK_Ws_Close", url); });
ws.addEventListener("message", (event) =>
{
const isBinary = event.data instanceof ArrayBuffer;
const eventFilterTest = wsEventFilter.r.test(event.data);
if (event.data != 2 && ((isBinary && binaryCount++ < binaryWsPackageCountLimit) || (!isBinary && (!eventFilterTest || filteredMsgCount++ < wsEventFilter.limit))))
{
addMeasurement("NETWORK_Ws_Msg", url, event.data, isBinary);
}
}
);
}
return ws;
}
})();