您需要先安装一个扩展,例如 篡改猴、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 // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/d3.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js // @run-at document-end // @namespace http://tampermonkey.net/ // @version 1.1 // @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/ Missing feature: DRAGGABLE // https://wiki.greasespot.net/Content_Script_Injection // IIFE (function() { //Init "use strict"; let id = 0; let masterId = 0; let scrolling = false; let stats = []; let timeoutId = null; let canonicalLinkURI, windowURI; let firstRun = true; const constants = { GTM: "GTM", ENV: "ENV" }; const { GTM, ENV } = constants; const cache = []; const isIframe = (window.self === window.top); const $ = window.jQuery; const rxjs = window.rxjs; const axios = window.axios; window.dataLayer = window.dataLayer || []; const bubbleStates = ["primary", "danger", "warning"]; let open = XMLHttpRequest.prototype.open; let send = XMLHttpRequest.prototype.send; console.defaultError = console.error.bind(console); console.errors = []; console.defaultWarn = console.warn.bind(console); console.warns = []; console.defaultInfo = console.info.bind(console); console.infos = []; //IIFE: Add non-rewritable styles to avoid getting affected by target website CSS's (function() { // 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; width: 40px !important; box-shadow: none !important; font-size: 1.5rem !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; } .containerErrors pre { padding: 0 !important; background: no-repeat !important}`; 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 requestsBoxCssWidth = (isIframe) ? "388px" : "351px"; const requestsBoxCss = `.requestsBox { overflow-y: scroll !important; max-height: 88% !important; width: ${requestsBoxCssWidth} !important; position: fixed !important; top: 0% !important; z-index: 99999999 !important; left: 0; } .string {word-wrap: break-word !important;}`; GM_addStyle(requestsBoxCss); const spinnerDivCss = `.spinnerDiv { width: 10% !important; float: left; background-color: #1f1f22 !important }`; GM_addStyle(spinnerDivCss); 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%" : "26%"; const containerErrorsCSSWidth = (isIframe) ? "388px" : "351px"; const containerErrorsCss = `.containerErrors { display: none; max-height: 752px !important; width: ${containerErrorsCSSWidth} !important; top: ${top} !important; position: fixed !important; overflow-y: scroll !important; z-index: 99999999 !important; } .containerErrors div { font-style: normal !important; }`; GM_addStyle(containerErrorsCss); const containerSvgCss = `.svgContainer { cursor: pointer !important; left: 1%; z-index: 99999999 !important; } .svgContainer svg{ float: left }`; GM_addStyle(containerSvgCss); const objectMessagePCss = ".objectMessageP {word-break: break-word;font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji !important; font-size: 13px !important}"; GM_addStyle(objectMessagePCss); const width = (isIframe) ? "90%" : "90%"; const replDivCss = `.replDiv { display: none; height: 50px !important; width: ${width} !important; float: left !important } .replDiv input { background-color: #1f1f22 !important; height: 100% !important; width: 100% !important; font-size: 18px !important; border: 0 !important; }`; GM_addStyle(replDivCss); })(); //Create reference to elements const spinner = $("<div class=\"spinnerDiv\"><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 replDiv = $("<div id=\"replDiv\" class=\"replDiv\"><input type=\"text\" placeholder=\"REPL: alert('Happy Hacking!')\" ></input></div>"); const requestsBox = $("<div id=\"requestsBox\" draggable = \"true\" 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\" style=\"background-image: none !important;\" role=\"button\">href</a>"); const refreshButton = $("<a class=\"btn btn-primary\" style=\"background-image: none !important;\" onclick=\"window.location.reload(true)\" role=\"button\">refresh</a>"); const closeButton = $("<a class=\"btn btn-danger\" style=\"background-image: none !important;\" role=\"button\">X</a>"); const buttonsDiv = $("<div class=\"buttonsDiv\"></div>"); refreshButton.prependTo(buttonsDiv); hrefButton.prependTo(buttonsDiv); closeButton.prependTo(buttonsDiv); spinner.prependTo(containerSvg); replDiv.on("click", repl); replDiv.appendTo(containerSvg); buttonsDiv.appendTo(containerSvg); containerSvg.prependTo(requestsBox); requestsBox.appendTo($("body")); containerErrors.appendTo($("body")); //Make element Draggable W.I.P requestsBox[0].ondragstart = (event) => { event.dataTransfer.setData("text", event.target.id); }; const body = $("body"); if (body) { body.ondrop = (event) => { event.preventDefault(); let data = event.dataTransfer.getData("text"); event.target.appendChild(document.getElementById(data)); }; body.ondragover = (event) => { event.preventDefault(); }; } //Handcraft 'out' animation for requestsBox(This is not easy to modify) 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); setTimeout(() => { if (closeButtons) { $(requestsBox).addClass("customCollapse"); $(".replDiv").css("display", "none"); } $(".containerErrors div").fadeOut("2000"); if (collapseSpinner) { $(buttonsDiv).css("display", "none"); $(containerErrors).css("display", "none"); } }, 1000); } //Spinner Click handler spinner.on("click", function() { //Handcraft 'in' animation for requestsBox if ($(requestsBox).hasClass("customCollapse")) { $(containerErrors).css("display", "block"); $(requestsBox).removeClass("customCollapse"); $(".containerErrors div").fadeIn("2000"); $(".replDiv").css("display", "block"); $(requestsBox).css("left", "0%"); $(buttonsDiv).css("display", "block"); $(containerErrors).css("display", "block"); } else { closeContainerErrorsWithAnimations(true, true); } }); // Add close button animation closeButton.on("click", () => { closeContainerErrorsWithAnimations(); }); // Add HREF button functionality hrefButton.on("click", function() { // 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"); // } // }); try { const anchors = $("a[href^=\"/\"], a[href*=\"" + window.location.host + "\"]") .not(".svgContainer a") .not(".containerErrors a") .not("a[href=\"#\"]"); const corsAnchors = $("a[href^=\"http\"]") .not("a[href*=\"" + window.location.host + "\"]") .not("a[href^=\"/\"]") .not(".svgContainer a") .not(".containerErrors a") .not("a[href=\"#\"]"); // d3.selectAll(anchors).style("background-color", function() { // return "hsl(" + Math.random() * 360 + ",100%,50%)"; // }); if (corsAnchors) { corsAnchors.map((index, corsAnchor) => { d3.select(corsAnchor).transition().duration(750) .style("background-color", "#ffc107") .style("border", "5px") .style("border-color", "#ffc107"); }); } else { console.warn("No CORS anchors found"); } if (anchors) { anchors.map(async (index, anchor) => { try { await axios.get(anchor.href,{ maxRedirects: 0, validateStatus: null}) d3.select(anchor).transition().duration(500) .style("background-color", "green") .style("border", "5px") .style("border-color", "green"); } catch (error) { d3.select(anchor).transition() .style("background-color", "red") .style("border", "5px") .style("border-color", "red"); } }); } else { console.warn("No anchors found"); } // d3.select("body").transition() // .style("background-color", "black"); // d3.selectAll(anchors).transition() // .duration(750) // .delay(function(d, i) { // return i * 10; // }) // .attr("r", function(d) { // return Math.sqrt(d * 1); // }); } catch (e) { console.error(e); } // $(anchors).css("background", "red").css("border-color", "yellow").css("border", "2px"); }); //Hijack error console.error = function() { renderEventInHTML(arguments[0], "danger"); // default & console.error() console.defaultError.apply(console, arguments); // new & array data console.errors.push(Array.from(arguments)); }; //Hijack warn console.warn = function() { renderEventInHTML(arguments[0], "warning"); // default & console.error() console.defaultWarn.apply(console, arguments); // new & array data console.warns.push(Array.from(arguments)); }; //Hijack info console.info = function() { renderEventInHTML(arguments[0], "primary"); // default & console.error() console.defaultInfo.apply(console, arguments); // new & array data console.infos.push(Array.from(arguments)); }; //Handler for window unhandledrejection, rejectionhandled, error function logEvent(errorEvent, type) { 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.${type}: ${error}`); } // this is only to distinguish http errors by status, cannot be used to distinguish every single event we might get function sendToConsole(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); } }); } //TODO, We need to return promises here to be able to syncronize the event ids //Add custom Event listener to window, XMLHttpRequest function addCustomEventListeners() { let timer = null; 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 = [ masterId, " Message: " + eventMessage, "URL: " + url, "Line: " + lineNo, "Column: " + columnNo, "Error object: " + JSON.stringify(error) ].join(" - "); console.error(message); } return false; }; window.onerror = function() { let args = Array.prototype.slice.call(arguments); if (_onerror) { return _onerror.apply(window, args); } return false; }; $.error = function(message) { console.error("jQuery: " + message); }; window.onscroll = function(event) { event.preventDefault(); event.stopPropagation(); if (!scrolling) { scrolling = true; masterId++; } if (timer !== null) { clearTimeout(timer); } timer = setTimeout(function() { scrolling = false; }, 1000); // if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) { // // you're at the bottom of the page // } }; window.addEventListener("unhandledrejection", function(errorEvent) { logEvent(errorEvent, "unhandledrejection"); }); window.addEventListener("rejectionhandled", function(errorEvent) { logEvent(errorEvent, "rejectionhandled"); }); window.addEventListener("error", function(errorEvent) { logEvent(errorEvent, "error"); }); XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { this._url = url; this._method = method; open.call(this, method, url, async, user, pass); }; XMLHttpRequest.prototype.send = function(data) { let self = this; let start; let oldOnReadyStateChange; let url = this._url; let method = this._method; function onReadyStateChange(event) { //Info: Log all you need from event if (self.readyState === 4 /* complete */) { let time = new Date() - start; stats.push({ url: url, id: masterId, 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 : method }); sendToConsole(stats); timeoutId = null; stats = []; } 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); }; } // let source = rxjs.Node.fromEvent(XMLHttpRequest.prototype.send, 'send'); } //Window Load Event Handler window.addEventListener("load", () => { addCustomEventListeners(); }); //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"); //Get Xpath and XY Coordinates of any clicked element document.onclick = (event) => { // event.stopImmediatePropagation(); if (event === undefined) event = window.event; // IE hack let target = "target" in event ? event.target : event.srcElement; // another IE hack 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-") || path.includes("replDiv")) return;//We don't want to acknowledge the click we do in our own terminal // event.preventDefault(); masterId++; let txy = getPageXY(target); console.info(masterId + " - " + path + " <br/>offset x:" + (mxy[0] - txy[0]) + ", y:" + (mxy[1] - txy[1])); addCustomEventListeners(); getDataFromWindow(); }; function getPathTo(element) { if (element.id !== "") return "id(\"" + element.id + "\")"; if (element === document.body) return element.tagName; let ix = 0; if (!element.parentNode) { return element.tagName; } 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]; } // Check if canonical link URI is matching window.location.href function checkCanonicalLink(id) { if (isIframe) {// 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 = `${id} - Canonical: ${canonicalLinkURI}`; (canonicalLinkURI.pathname === windowURI.pathname) ? console.info(message) : console.error(message); } else { console.warn("No canonical link found"); } } } checkCanonicalLink(masterId); // Evaluate the ENV variables function getEnvsFromWindow(id) { const env = window.eval("window.ENV"); if (env) { console.info({ id: id, "ENV": env }); } } // Evaluate the GTM DataLayer function getDataFromWindow() { const dataLayer = unsafeWindow.dataLayer;//This the best and simpler way to do it, eval gives random behaviour and code is non-debuggeable if (dataLayer) { let sameObject = false; if (dataLayer.length > 0) { sameObject = JSON.stringify(dataLayer[dataLayer.length - 1]) === JSON.stringify(cache[cache.length - 1]); } if ((!sameObject && dataLayer) || firstRun || dataLayer.length >= cache.length) {//If it's the first time render the dataLayer, after that, only render if you detect changes in dataLayer firstRun = false; cache.push(dataLayer[dataLayer.length - 1]); // console.info({ "GTM": dataLayer }); //Observe GTM dataLayer const gtmObserver = rxjs.of(dataLayer); gtmObserver .subscribe(changedDataLayerEntry => { if (changedDataLayerEntry) { console.info({ id: masterId, GTM: changedDataLayerEntry }); } else { console.warn("Subscribe null received"); } }, err => { console.error(err); }, () => { console.info("GTM done"); }); } } } //Access window.dataLayer without skipping the Tamper Monkey Sandbox(secure method) setTimeout(() => { getDataFromWindow(masterId); }, 2000); getEnvsFromWindow(masterId); // try { // unsafeWindow.onYouTubeIframeAPIReady = function() { // alert("API loaded"); // }; // } catch (e) { // console.error({response: "unsafeWindowNotFound"}) // } //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 // 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); // } //REPL async function repl() { // let result = "input javascript code"; // const expression = prompt(result); const command = $(".replDiv input").val(); let result = await eval(command); if (result) { console.info({ response: result }); } await sleep(0); } function xmlHttpRequestPromise(url, option) { return new Promise(resolve => { const newOption = { url, onload, ...option }; if (!newOption.method) newOption.method = "GET"; GM.xmlHttpRequest(newOption); function onload(response) { resolve(response); } }); } function sleep(second) { return new Promise(wake => setTimeout(wake, second * 1000)); } repl(); //Get Event Url function getEventUrl(event) { try { return new URL(event.url || event.config.url); } catch (e) {//Sometimes event.url will not have `origin` attribute return event.url || undefined; } } // Get Json Tree Element from Event function getEventJsonTreeElement(type, event) { let jsonTreeElement, parsedResponse; try { if (type === GTM || type === ENV) { jsonTreeElement = renderjson(event[type]); } else { if (event.response) { parsedResponse = JSON.parse(event.response); jsonTreeElement = renderjson(parsedResponse); } } } catch (e) { // console.log(e); //Sometimes we get malformed JSON try { parsedResponse = JSON.parse(JSON.stringify({ response: event.response })); jsonTreeElement = renderjson(parsedResponse); } catch (e) { console.log(e); } } return jsonTreeElement; } // Add Json tree to alert message line function addJsonTreeToRequestLine(url, event, requestLine, type, jsonTreeElement) { if (url) { url = event.url ? event.url.replace(url.origin, "") : url.href; requestLine.append("<p class=\"objectMessageP\">" + event.id + " - " + (event.method ? event.method.toUpperCase() : "") + " " + event.statusCode + " " + event.duration + "<br/>" + "<a href=\"" + url + "\">" + url.substring(1, url.length) + "<a/><p\>"); // adding the error response to the message } else { if (type === GTM || type === ENV) { if (type === GTM && cache.length === 0) { cache.push(event.GTM[event.GTM.length - 1]); } requestLine.append("<p class=\"objectMessageP\">" + event.id + " - " + type + ":<p\>"); // adding the GTM info response to the message } else { requestLine.append("<p class=\"objectMessageP\">" + event.id + " - " + event[0] + "<p\>"); // adding the error response to the message } } if (jsonTreeElement) { requestLine.append(jsonTreeElement); } if (type === GTM) { requestLine.attr("data-object", JSON.stringify(event[type])); // alertLine.data("gtm-object", JSON.stringify(event[type])); } } //This method needs complex logic since every event needs to be rendered differently in the DOM(event, error, info, env_variable, GTM object), TODO NEEDS REFACTOR function appendObjectToRequestLine(event, requestLine, type = "event") { let url, jsonTreeElement; url = getEventUrl(event); jsonTreeElement = getEventJsonTreeElement(type, event); addJsonTreeToRequestLine(url, event, requestLine, type, jsonTreeElement); } //Render events into HTML function renderEventInHTML(event, alertType) { alertType = bubbleStates.includes(alertType) ? alertType : "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-" + alertType); 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 { if (event.GTM || event.ENV) { appendObjectToRequestLine(event, requestLine, event.ENV ? ENV : GTM); } else { appendObjectToRequestLine(event, requestLine); } } requestLine.prependTo(containerErrors).fadeIn(300); //.delay(20000).fadeOut(500); //.delay(5000).fadeOut(500); } } )();