您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Logs fetch, xhr, ws requests in console
当前为
// ==UserScript== // @name Site Measurements // @namespace http://tampermonkey.net/ // @version 2024-08-07.4 // @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 let networkLogsEnabled = localStorage.getItem("networkMeasuermentConsoleLogs") == "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",\{(?!.*enableTrace)/, 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_MouseUp|NETWORK_User_TouchStart)/, filter: null, filterArgIndex: null, func: (args) => "USER_START" }, { callType: /^NETWORK_Ws_Send/, filter: /"join"/, filterArgIndex: 1, func: (args) => "OC_JOIN" //measurements = measurements.concat(JSON.parse(args[0].substr(3)).map( e => }, { callType: /^NETWORK_Ws_Msg/, filter: /"getEdge"/, filterArgIndex: 1, func: (args) => "OC_JOINED" //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) => { return "STREAM_ON_STREAM_STATUS " + (args[1].includes('isColdStarted":true') ? "☁" : ( args[1].includes('isColdStarted":false') ? "🔥" : "?")) } }, { 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: /.{2,}/, filterArgIndex: 0, func: (args) => { return args[0].length > 0 ? "STREAM_MEDIA_LOADED_DATA" : null } // empty video id filtered out because it's some preroll stuff }, { callType: /^NETWORK_Video_Playing/, filter: /.{2,}/, filterArgIndex: 0, func: (args) => { return args[0].length > 0 ? "STREAM_MEDIA_PLAYING" : null } // empty video id filtered out because it's some preroll stuff } ]; // these steps will be measured and shown when reaches the last step // SCRIPT console.log("MEASUREMENT"); // UI const createFloating = ( id, style, addClose ) => { const div = document.createElement('div'); div.id = id; Object.assign(div.style, {width: '210px', height: '100px', color: "white", background: '#700000', position: 'absolute', top: '50px', left: '50px', cursor: 'move', zIndex: 100000 }); Object.assign(div.style, style); const content = document.createElement("div"); div.appendChild( content ); if (addClose) { const close = document.createElement("div"); close.textContent = "x"; close.className = "measurement-close"; Object.assign(close.style, { position: "absolute", padding: "5px", textAlign: "center", lineHeight: "12px", top: "0px", right: "5px", color: "#999999", cursor: "pointer" } ); close.onclick = () => { div.style.display = "none" } div.appendChild(close); } let isDown = false, offsetX, offsetY; div.addEventListener('mousedown', e => { isDown = true; offsetX = e.offsetX; offsetY = e.offsetY; }); document.addEventListener('mousemove', e => { if (isDown) Object.assign(div.style, {left: e.pageX - offsetX + 'px', top: e.pageY - offsetY + 'px'}); }); document.addEventListener('mouseup', () => { isDown = false }); return div; } const measurementIcon = createFloating( "measurementFloatingIcon", {left: 0, top: 0, cursor: "poiner", borderRadius: "50%", width: "35px", height: "35px", border: "1px solid #500000", background: "#700000", color: "white", textAlign: "center", lineHeight: "35px", userSelect: "none" } ); measurementIcon.firstElementChild.textContent = "M"; const display = localStorage.getItem("networkMeasuermentDisplay") == "true"; const floatingBox = createFloating( "measurementFloatingBox", {display: display ? "box" : "none", whiteSpace: "pre", fontFamily: "monospace", padding: "5px", fontSize: "11px", height: "auto", minHeight: "100px", borderRadius: "2px"}, true ); measurementIcon.addEventListener("click", () => { floatingBox.style.display == "none" ? floatingBox.style.display = "block" : floatingBox.style.display = "none"; localStorage.setItem("networkMeasuermentDisplay", floatingBox.style.display != "none"); }); floatingBox.querySelector(".measurement-close").addEventListener("click", () => { localStorage.setItem("networkMeasuermentDisplay", floatingBox.style.display != "none"); } ); const enableLogsCheckbox = document.createElement('input'); enableLogsCheckbox.type = "checkbox"; enableLogsCheckbox.title = "Enable console logs"; enableLogsCheckbox.checked = networkLogsEnabled; enableLogsCheckbox.onchange = () => { networkLogsEnabled = !networkLogsEnabled; localStorage.setItem("networkMeasuermentConsoleLogs", networkLogsEnabled); }; Object.assign(enableLogsCheckbox.style, { position: "absolute", top: "5px", right: "20px", width: "12px", height: "12px" }); floatingBox.appendChild(enableLogsCheckbox); // Measurements const printMeasurements = () => { if (measurements.length < 3 ) { return; } const pad = 27; 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(pad) + " " + (diffTime).toString().padStart(4) + "\n"; } const measurementText = 'MEASUREMENTS'.padEnd(pad) + '\n' + '____________'.padEnd(pad) + '\n' + result + '\n' + 'SUM'.padEnd(pad) + ' ' + sumTime.toString().padStart(4); console.log('%c' + measurementText, 'background: red; color: #bada55'); floatingBox.firstElementChild.textContent = measurementText; } const addMeasurement = (callType, ...args) => { let highlighted = false; 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 (pushed) { highlighted = true; networkLogsEnabled && console.log('%c' + callType + " " + JSON.stringify(args), 'background: darkorange; color: white'); } if ( i == measurementConfig.length - 1 && pushed ) { printMeasurements(); // print if it was the last measurements = []; } break; } } if ( networkLogsEnabled && !highlighted ) { console.log( callType, ...args ); } } // Image listeners const OrigImage = window.Image; window.Image = function(...args) { const img = new OrigImage(...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.style.opacity = 0.1; 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); } } }); } }); }); window.addEventListener("load", () => { setTimeout( () => { document.body.appendChild(floatingBox); document.body.appendChild(measurementIcon); }, 1000); 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("mousedown", (event) => { addMeasurement("NETWORK_User_MouseDown", event.target); }); document.addEventListener("mouseup", (event) => { addMeasurement("NETWORK_User_MouseUp", 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; } })();