您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
log uncaught window (XHR.send, XHR.onerror) exceptions and write them in the document as bootstrap alert html elements
当前为
// ==UserScript== // @name Bubble Logger // @require https://code.jquery.com/jquery-3.4.1.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/renderjson.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/rxjs/8.0.0-alpha.2/rxjs.umd.min.js // @namespace http://tampermonkey.net/ // @version 1.0 // @license MIT // @description log uncaught window (XHR.send, XHR.onerror) exceptions and write them in the document as bootstrap alert html elements // @author Sloppy Lo // @match http*://*/* // @icon https://store-images.s-microsoft.com/image/apps.32031.13510798887630003.b4c5c861-c9de-4301-99ce-5af68bf21fd1.ba559483-bc2c-4eb9-a17e-c302009b2690?w=180&h=180&q=60 // @resource REMOTE_BOOTSTRAP_CSS https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css // @resource ANIMATE_CSS_MIN https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @grant GM_addStyle // @grant unsafeWindow // ==/UserScript== //Info: https://sourceforge.net/p/greasemonkey/wiki/unsafeWindow/ // https://benjamine.github.io/jsondiffpatch/demo/index.html // https://abodelot.github.io/jquery.json-viewer/ // https://programming.mediatagtw.com/article/tampermonkey+unsafewindow // https://stackoverflow.com/questions/2631820/how-do-i-ensure-saved-click-coordinates-can-be-reload-to-the-same-place-even-if/2631931#2631931 // http://jsfiddle.net/luisperezphd/L8pXL/ // /nl/wlan-access-points/mikrotik/omnitik-5-poe-ac-rbomnitikpg-5hacd-art-rbomnitikpg-5hacd-num-6166159/ // https://theonlytutorials.com/how-to-make-a-div-movable-draggable/ //IIFE (function() { "use strict"; let id = 0; const isIFrame = (window.self === window.top); const $ = window.jQuery; const rxjs = window.rxjs; window.dataLayer = window.dataLayer || []; const spinner = $("<div class=\"svgDiv\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" style=\"margin: auto; background: none; display: block; shape-rendering: crispedges; animation-play-state: running; animation-delay: 0s;\" width=\"50px\" height=\"50px\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid\"> <g style=\"animation-play-state: running; animation-delay: 0s;\"> <circle cx=\"60\" cy=\"50\" r=\"4\" fill=\"#ffffff\" style=\"animation-play-state: running; animation-delay: 0s;\"> <animate attributeName=\"cx\" repeatCount=\"indefinite\" dur=\"1s\" values=\"95;35\" keyTimes=\"0;1\" begin=\"-0.67s\" style=\"animation-play-state: running; animation-delay: 0s;\"></animate> <animate attributeName=\"fill-opacity\" repeatCount=\"indefinite\" dur=\"1s\" values=\"0;1;1\" keyTimes=\"0;0.2;1\" begin=\"-0.67s\" style=\"animation-play-state: running; animation-delay: 0s;\"></animate> </circle> <circle cx=\"60\" cy=\"50\" r=\"4\" fill=\"#ffffff\" style=\"animation-play-state: running; animation-delay: 0s;\"> <animate attributeName=\"cx\" repeatCount=\"indefinite\" dur=\"1s\" values=\"95;35\" keyTimes=\"0;1\" begin=\"-0.33s\" style=\"animation-play-state: running; animation-delay: 0s;\"></animate> <animate attributeName=\"fill-opacity\" repeatCount=\"indefinite\" dur=\"1s\" values=\"0;1;1\" keyTimes=\"0;0.2;1\" begin=\"-0.33s\" style=\"animation-play-state: running; animation-delay: 0s;\"></animate> </circle> <circle cx=\"60\" cy=\"50\" r=\"4\" fill=\"#ffffff\" style=\"animation-play-state: running; animation-delay: 0s;\"> <animate attributeName=\"cx\" repeatCount=\"indefinite\" dur=\"1s\" values=\"95;35\" keyTimes=\"0;1\" begin=\"0s\" style=\"animation-play-state: running; animation-delay: 0s;\"></animate> <animate attributeName=\"fill-opacity\" repeatCount=\"indefinite\" dur=\"1s\" values=\"0;1;1\" keyTimes=\"0;0.2;1\" begin=\"0s\" style=\"animation-play-state: running; animation-delay: 0s;\"></animate> </circle> </g><g transform=\"translate(-15 0)\" style=\"animation-play-state: running; animation-delay: 0s;\"> <path d=\"M50 50L20 50A30 30 0 0 0 80 50Z\" fill=\"#005bbf\" transform=\"rotate(90 50 50)\" style=\"animation-play-state: running; animation-delay: 0s;\"></path> <path d=\"M50 50L20 50A30 30 0 0 0 80 50Z\" fill=\"#005bbf\" style=\"animation-play-state: running; animation-delay: 0s;\"> <animateTransform attributeName=\"transform\" type=\"rotate\" repeatCount=\"indefinite\" dur=\"1s\" values=\"0 50 50;45 50 50;0 50 50\" keyTimes=\"0;0.5;1\" style=\"animation-play-state: running; animation-delay: 0s;\"></animateTransform> </path> <path d=\"M50 50L20 50A30 30 0 0 1 80 50Z\" fill=\"#005bbf\" style=\"animation-play-state: running; animation-delay: 0s;\"> <animateTransform attributeName=\"transform\" type=\"rotate\" repeatCount=\"indefinite\" dur=\"1s\" values=\"0 50 50;-45 50 50;0 50 50\" keyTimes=\"0;0.5;1\" style=\"animation-play-state: running; animation-delay: 0s;\"></animateTransform> </path></g> <!-- [ldio] generated by https://loading.io/ --></svg></div>"); const bubbleStates = ["primary", "danger", "warning"]; console.defaultError = console.error.bind(console); console.errors = []; console.defaultWarn = console.warn.bind(console); console.warns = []; console.defaultInfo = console.info.bind(console); console.infos = []; importCSS(); const requestsBox = $("<div id=\"requestsBox\" class=\"requestsBox animate__animated animate__rollIn customCollapse\">"); // const responsesBox = $("<div class=\"responsesBox\">"); const containerSvg = $("<div id=\"svgContainer\" class=\"svgContainer\">"); const containerErrors = $("<div id=\"containerErrors\" class=\"containerErrors animate__animated\">"); const hrefButton = $("<a class=\"btn btn-primary\" role=\"button\">href</a>"); const refreshButton = $("<a class=\"btn btn-primary\" onclick=\"window.location.reload(true)\" role=\"button\">refresh</a>"); const closeButton = $("<a class=\"btn btn-danger\" role=\"button\">X</a>"); const buttonsDiv = $("<div class=\"buttonsDiv\"></div>"); refreshButton.prependTo(buttonsDiv); hrefButton.prependTo(buttonsDiv); closeButton.prependTo(buttonsDiv); spinner.prependTo(containerSvg); buttonsDiv.appendTo(containerSvg); containerSvg.prependTo(requestsBox); requestsBox.appendTo($("body")); containerErrors.appendTo($("body")); function closeContainerErrorsWithAnimations(closeButtons = false, collapseSpinner = false) { $(containerErrors).addClass("animate__hinge"); if (closeButtons) { $(buttonsDiv).css("height", "500px"); $(buttonsDiv).addClass("animate__hinge"); } setTimeout(() => { $(containerErrors).removeClass("animate__hinge"); $(buttonsDiv).removeClass("animate__hinge"); $(buttonsDiv).css("height", ""); }, 2000); // $(containerErrors).css("display", "none"); setTimeout(() => { if (closeButtons) { $(requestsBox).addClass("customCollapse"); $(".svgContainer svg").css("float", "right"); } $(".containerErrors div").fadeOut("2000"); if (collapseSpinner) { $(buttonsDiv).css("display", "none"); $(containerErrors).css("display", "none"); $(containerSvg).css("left", "1%"); if (isIFrame) { $(requestsBox).css("left", "-37%"); } else { $(requestsBox).css("left", "-30%"); } } }, 1000); } spinner.on("click", function() { if ($(requestsBox).hasClass("customCollapse")) { $(containerErrors).css("display", "block"); $(requestsBox).removeClass("customCollapse"); $(".containerErrors div").fadeIn("2000"); $(".svgContainer svg").css("float", "left"); $(requestsBox).css("left", "0%"); $(buttonsDiv).css("display", "block"); $(containerErrors).css("display", "block"); } else { closeContainerErrorsWithAnimations(true, true); } // $(containerErrors).siblings().hide("slow"); }); hrefButton.on("click", function() { const anchors = $("a[href]").not(".svgContainer a").not(".containerErrors a"); const buttons = $("button"); buttons.map((index, button) => { // if($(button).data("events").click){ // $(button).css("background", "red"); // $(button).css("border-color", "yellow"); // $(button).css("border", "2px"); // } }); $(anchors).css("background", "red").css("border-color", "yellow").css("border", "2px"); }); closeButton.on("click", function() { // $(".containerErrors div").fadeOut("2000"); closeContainerErrorsWithAnimations(); }); // responsesBox.appendTo($("body")); function importCSS() { // Load remote CSS // @see https://github.com/Tampermonkey/tampermonkey/issues/835 const overwriteBootrapDismissableButtonCSS = `.alert-dismissible .close { position: absolute !important; top: 0 !important; right: 0 !important; padding: 0.75rem 1.25rem !important; color: inherit !important; }`; GM_addStyle(overwriteBootrapDismissableButtonCSS); const bootstrapCss = GM_getResourceText("REMOTE_BOOTSTRAP_CSS"); GM_addStyle(bootstrapCss); const animateCssMin = GM_getResourceText("ANIMATE_CSS_MIN"); GM_addStyle(animateCssMin); const jsonViewerCss = `.renderjson a { text-decoration: none; } .renderjson .disclosure { color: green; font-size: 150%; } .renderjson .syntax { color: grey; } .renderjson .string { color: darkred; } .renderjson .number { color: darkcyan; } .renderjson .boolean { color: blueviolet; } .renderjson .key { color: darkblue; } .renderjson .keyword { color: blue; } .renderjson .object.syntax { color: lightseagreen; } .renderjson .array.syntax { color: orange; }`; GM_addStyle(jsonViewerCss); const customCollapse = ".customCollapse {float: right !important}"; GM_addStyle(customCollapse); const spinnerCss = ".loadingio-spinner-bean-eater-m1d52hd0p4d {top:50% !important; left:50% !important} a {display: inline !important}"; GM_addStyle(spinnerCss); const errorMessageCss = ".alert {word-break: break-word !important; opacity: 0.95 !important; margin: 0px !important; font-size:13px !important}"; GM_addStyle(errorMessageCss); const requestsBoxCssTop = (isIFrame) ? "-37%" : "-30%"; const requestsBoxCss = `.requestsBox { overflow-y: scroll !important; max-height: 88% !important; opacity: 0.95 !important; position: fixed !important; top: 0% !important; width: 40% !important; z-index: 99999999 !important; left: ${requestsBoxCssTop}; } .string {word-wrap: break-word !important;}`; GM_addStyle(requestsBoxCss); const svgDivCss = ".svgDiv {width: 100% !important; float: left; background-color: black !important} .svgContainer svg {float: right}"; GM_addStyle(svgDivCss); const buttonsDivCss = ".buttonsDiv {overflow-y: scroll !important;display: none; width: 100% !important; position: relative !important; float: left !important;} .buttonsDiv a {width: 33.3% !important; height: 31px !important; float: left !important; text-align: center !important; font-size: 12px !important; border-color: black !important; padding-top: 5px !important}"; GM_addStyle(buttonsDivCss); // const responsesBoxCss = ".responsesBox {position: fixed !important; right: 0% !important; top: 70% !important; width: 50% !important; z-index: 99999999 !important; overflow-y: scroll !important;}"; // GM_addStyle(responsesBoxCss); const top = (isIFrame) ? "7.5% !important" : "25.5% !important"; const containerErrorsCss = `.containerErrors { display: none; max-height: 752px !important; width: 100% !important; top: ${top}; width: 40% !important; position: fixed !important; overflow-y: scroll !important; z-index: 99999999 !important; }`; GM_addStyle(containerErrorsCss); const containerSvgCss = ".svgContainer {cursor: pointer !important;left: 1%; z-index: 99999999 !important;} .svgContainer svg{float: right}"; GM_addStyle(containerSvgCss); } function appendObjectToHTML(event, requestLine) { let url; try { url = new URL(event.url); } catch (e) {//Sometimes event.url will not have origin url = event.url; } let jsonHTML; let response; try { response = JSON.parse(event.response); } catch (e) { console.log(e);//Sometimes we get malformed jsons response = JSON.parse(JSON.stringify({ response: event.response })); } jsonHTML = renderjson(response); if (event.url) { url = event.url.replace(url.origin, ""); requestLine.append("<p style=\"word-break: break-word;\">" + " <a href=\"" + event.url + "\" class=\"alert-link\">" + url.substring(1, url.length) + "<a/> <br/>" + (event.method ? event.method.toUpperCase() : "") + " " + event.statusCode + " " + event.duration + "<p\>"); // adding the error response to the message } else { requestLine.append("<p style=\"word-break: break-word;\">" + event[0] + "<p\>"); // adding the error response to the message } // if($(requestsBox).hasClass("customCollapse")){ // requestLine.css("display", "none"); // } requestLine.append(jsonHTML); } function bubbleErrorInHtml(event, bubbleType) { bubbleType = bubbleStates.includes(bubbleType) ? bubbleType : "primary"; const newId = id++; const requestLine = $("<div id=\"requestId-" + newId + "\" class=\"alert alert-dismissible fade show\" style=\"display: none;\">"); // const responseLine = $("<div id=\"responseId-" + newId + "\" style=\"display: block;\">"); requestLine.addClass("alert-" + bubbleType); const close = $("<button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\">×</button>"); requestLine.append(close); if (typeof event === "string") { requestLine.append(event); } else { appendObjectToHTML(event, requestLine); } requestLine.prependTo(containerErrors).fadeIn(300); //.delay(20000).fadeOut(500); //.delay(5000).fadeOut(500); } console.error = function() { bubbleErrorInHtml(arguments[0], "danger"); // default & console.error() console.defaultError.apply(console, arguments); // new & array data console.errors.push(Array.from(arguments)); }; console.warn = function() { bubbleErrorInHtml(arguments[0], "warning"); // default & console.error() console.defaultWarn.apply(console, arguments); // new & array data console.warns.push(Array.from(arguments)); }; console.info = function() { bubbleErrorInHtml(arguments[0], "primary"); // default & console.error() console.defaultInfo.apply(console, arguments); // new & array data console.infos.push(Array.from(arguments)); }; window.addEventListener("load", function() { if (window) { let _onerror = function(event, url, lineNo, columnNo, error) { let message = []; let eventMessage = event.message ? event.message.toLowerCase() : event.error ? event.error : null; if (!eventMessage && event.path[0].tagName === "IMG") {//This is hacky, change me eventMessage = event.type + " at <a href=\"#\" style=\"word-break: break-word;\">" + new Option(event.path[0].outerHTML).innerHTML + "<a\>"; } if (!eventMessage) { eventMessage = event.target.id || event.target.src; } let substring = "script error"; if (eventMessage.indexOf(substring) > -1) { alert("Script Error: See Browser Console for Detail"); } else { message = [ "Message: " + eventMessage, "URL: " + url, "Line: " + lineNo, "Column: " + columnNo, "Error object: " + JSON.stringify(error) ].join(" - "); console.error(message); } //GM_notification('text', 'title', 'https://store-images.s-microsoft.com/image/apps.32031.13510798887630003.b4c5c861-c9de-4301-99ce-5af68bf21fd1.ba559483-bc2c-4eb9-a17e-c302009b2690?w=180&h=180&q=60', ()=>{console.log('click');}) return false; }; // Handle Uncaught Errors window.onerror = function() { let args = Array.prototype.slice.call(arguments); if (_onerror) { return _onerror.apply(window, args); } return false; }; } window.addEventListener("unhandledrejection", function(errorEvent) { let error = undefined; try { if (errorEvent.reason) { error = errorEvent.reason.stack || errorEvent.reason.message; } else { error = errorEvent.error || errorEvent.message || JSON.stringify(errorEvent); } } catch (e) { console.log(e); } console.error("window.rejectionhandled: " + error); }); window.addEventListener("rejectionhandled", function(errorEvent) { let error = undefined; try { if (errorEvent.reason) { error = errorEvent.reason.stack || errorEvent.reason.message; } else { error = errorEvent.error || errorEvent.message || JSON.stringify(errorEvent); } } catch (e) { console.log(e); } console.error("window.rejectionhandled: " + error); }); window.addEventListener("error", function(errorEvent) { let error = undefined; try { if (errorEvent.reason) { error = errorEvent.reason.stack || errorEvent.reason.message; } else { error = errorEvent.error || errorEvent.message || JSON.stringify(errorEvent); } } catch (e) { console.log(e); } console.error("window.error: " + error); }); window.addEventListener("fetch", function(event) { let error = undefined; try { if (event.reason) { error = event.reason.stack || event.reason.message; } else { error = event.error || JSON.stringify(event); } } catch (e) { console.log(e); } console.warn("window.fetch: " + error); event.respondWith( fetch(event.request) ); }); $.error = function(message) { console.error("jQuery: " + message); }; let stats = []; let timeoutId = null; let open = XMLHttpRequest.prototype.open; let send = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { this._url = url; open.call(this, method, url, async, user, pass); }; function sendToConsole(event, stats) { stats.forEach((stat) => { if (stat.statusCode >= 200 && stat.statusCode < 400) { console.info(stat); } else if (stat.statusCode === 404) { console.warn(stat); } else { console.error(stat); } }); } XMLHttpRequest.prototype.send = function(data) { let self = this; let start; let oldOnReadyStateChange; let url = this._url; function onReadyStateChange(event) { //Info: Log all you need from event if (self.readyState === 4 /* complete */) { let time = new Date() - start; stats.push({ url: url, duration: time + "ms", statusCode: ["undefined", "null", "", null, undefined].includes(event.currentTarget.status) ? "unknown" : event.currentTarget.status, response: ["undefined", "null", "", null, undefined].includes(event.currentTarget.response) ? "unknown" : event.currentTarget.response, method: event.currentTarget["nr@context"] && event.currentTarget["nr@context"].params ? event.currentTarget["nr@context"].params.method : "unknown" }); if (!timeoutId) { timeoutId = window.setTimeout(function() { sendToConsole(event, stats); timeoutId = null; stats = []; }, 2000); } } if (oldOnReadyStateChange) { oldOnReadyStateChange(); } } if (!this.noIntercept) { start = new Date(); if (this.addEventListener) { this.addEventListener("readystatechange", onReadyStateChange, false); } else { oldOnReadyStateChange = this.onreadystatechange; this.onreadystatechange = onReadyStateChange; } } send.call(this, data); }; }); //Only way to access window.dataLayer is by creating a script in the DOM as seeing in https://programming.mediatagtw.com/article/tampermonkey+unsafewindow AND use // @grant unsafeWindow function observeDataLayer() { for (let i = 0; i < 10; i++) { console.log(window.dataLayer); console.log(window.jQuery); } rxjs.of(window.dataLayer) .subscribe(x => { try { const element = document.createElement("div"); element.innerText = "Data: " + JSON.stringify(x); document.body.appendChild(element); document.body.appendChild(JSON.stringify(window.dataLayer)); } catch (e) { console.log(e); } }, err => { console.log(err); }, () => { const element = document.createElement("div"); element.innerText = "All done"; document.body.appendChild(element); }); } let script = document.createElement("script"); script.textContent = "(" + observeDataLayer.toString() + ")();"; try { document.head.appendChild(script); } catch (e) {// This is for: Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self' 'sha256-nnRzvGsB15enSSxWufoVP+C4WOA6Spq28ybk2OobhJo=' https://static.observablehq.com https://www.google-analytics.com https://www.googleapis.com https://apis.google.com https://js.stripe.com". Either the 'unsafe-inline' keyword, a hash ('sha256-NW48ymmYcooO0dbY1vr0sH+pipddsZUK7g5L8N3COw8='), or a nonce ('nonce-...') is required to enable inline execution. console.error(e); } //Get info of clicked elements document.onclick = (event) => { if (event === undefined) event = window.event; // IE hack let target = "target" in event ? event.target : event.srcElement; // another IE hack // if (window.event.path[0].attributes[0].nodeValue ==='requestsBox' || window.event.path[2].attributes[0].nodeValue) return;//Don't let root = document.compatMode === "CSS1Compat" ? document.documentElement : document.body; let mxy = [event.clientX + root.scrollLeft, event.clientY + root.scrollTop]; let path = getPathTo(target); if (path.includes("requestsBox") || path.includes("svgContainer") || path.includes("containerErrors") || path.includes("requestId-")) return; let txy = getPageXY(target); // path = path.replaceAll("/", "\n"); //// let treePath = {response: {}}; // const array = [1, 2, 3, 4]; // const reducer = (accumulator, currentValue, index ) => { // return accumulator + '<pre>' + currentValue; // } // 1 + 2 + 3 + 4 // console.log(array.reduce(reducer)); //10 // event.response = path.split("/").reduce(reducer) ////path.split("/").forEach(function(part, index) { // const last = index - 1; ////treePath.response.add(index, part) ////}); ////treePath["offset"] = { ////x: (mxy[0] - txy[0]), ////y: (mxy[1] - txy[1]) ////}; ////console.info(treePath); console.info(path + " \noffset x:" + (mxy[0] - txy[0]) + ", y:" + (mxy[1] - txy[1])); }; function getPathTo(element) { if (element.id !== "") return "id(\"" + element.id + "\")"; if (element === document.body) return element.tagName; let ix = 0; let siblings = element.parentNode.childNodes; for (let i = 0; i < siblings.length; i++) { let sibling = siblings[i]; if (sibling === element) return getPathTo(element.parentNode) + "/" + element.tagName + "[" + (ix + 1) + "]"; if (sibling.nodeType === 1 && sibling.tagName === element.tagName) ix++; } } function getPageXY(element) { let x = 0, y = 0; while (element) { x += element.offsetLeft; y += element.offsetTop; element = element.offsetParent; } return [x, y]; } //Count number of elements // let paragraphCount = document.evaluate("count(//p)", document, null, XPathResult.ANY_TYPE, null); // console.info("This document contains " + paragraphCount.numberValue + " paragraph elements"); // Check if canonical link URI is matching window.location.href let canonicalLinkURI, windowURI; try { setTimeout(() => {// This needs to be async and timedout to aovid false positives when website has a redirection if (window.self === window.top) {// We don't want to check this if we are in an Iframe to avoid false positive canonicalLinkURI = $("link[rel='canonical']"); if (canonicalLinkURI[0] && canonicalLinkURI[0].href) { canonicalLinkURI = new URL(canonicalLinkURI[0].href); windowURI = new URL(window.location.href); const message = `Canonical url: ${canonicalLinkURI}`; if (canonicalLinkURI.pathname === windowURI.pathname) { console.info(message); } else { console.error(message); } } else { console.warn("No canonical link found"); } } }, 2000); } catch (e) { console.log(e); return; } } )();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址