(function (endpoint, optout, globalsData) { "use strict"; if (optout) { console.warn("Analytics opted out: " + optout); if (optout != "dev") return; } var lastHref = null; function genToken() { var token = ''; for (var i = 0; i < 4; i++) { // Generates 32 random bytes as hex token += Math.floor(Math.random() * 4294967296 + 4294967296).toString(16).substring(1); } return token; } var token = genToken(); var webdriver = [navigator.webdriver + ""]; try { webdriver.push(Navigator.prototype.webdriver + ""); } catch (e) { webdriver.push("error"); } try { var webdriverDesc = Object.getOwnPropertyDescriptor(Navigator.prototype, "webdriver"); if (webdriverDesc) { webdriver.push(webdriverDesc.get.apply(navigator) + ""); } else { webdriver.push("missing"); } } catch (e) { webdriver.push("error"); } var browserStats = { brands: { // Officially "await navigator.brave.isBrave()", but I don't want to bother with promises brave: navigator.brave && true, ddg: navigator.duckduckgo && navigator.duckduckgo.platform && navigator.duckduckgo.platform != "extension", webdriver: webdriver, phantom: (window.phantom || window._phantom) && true, nightmare: window.__nightmare && true, cypress: window.Cypress && true, playwright: (window.__playwright__binding__ || window.__pwInitScripts) && true, rhino: window.JavaException && true, thug: window.__init_window_personality && true, // https://github.com/buffer/thug wpt: window.WptAgentFlatten && true, // https://github.com/WPO-Foundation/wptagent/blob/master/internal/support/chrome/inject.js (usually covered by user agent) } }; if (optout == "dev") { browserStats.dev = true; } function hash(s, key) { let hash = 0x811c9dc5; for (let i = 0; i < s.length; i++) { hash ^= (s.charCodeAt(i) + key); hash *= 0x01000193; hash &= 0xFFFFFFFF; } if (hash < 0) { hash += Math.pow(2, 32); } return hash; } let filter = atob(globalsData.filter); function testBloom(value) { let size = filter.length * 8; for (let i = 0; i < globalsData.hashKeys.length; i++) { let index = hash(value, globalsData.hashKeys[i]) % size; if (!(filter.charCodeAt(index >> 3) & (1 << (index & 7)))) { return false; } } return true; } function scanGlobals() { function dumpProperties(filterPrefix, obj) { var globals = []; if (Object.getOwnPropertyNames) { var curr = obj; while (curr) { var props = Object.getOwnPropertyNames(curr); for (var i = 0; i < props.length; i++) { if (!testBloom(filterPrefix + props[i])) { globals.push(props[i]); } } if (Object.getOwnPropertySymbols) { props = Object.getOwnPropertySymbols(curr); for (var i = 0; i < props.length; i++) { if (!testBloom(filterPrefix + props[i].toString())) { globals.push(props[i].toString()); } } } if (Object.getPrototypeOf) { curr = Object.getPrototypeOf(curr); } else { break; } } } else { globals.push("jby_no_object_getOwnPropertyNames"); } return globals; } var windowGlobals = dumpProperties("window.", window); var navigatorGlobals = dumpProperties("navigator.", navigator); return { window: windowGlobals, navigator: navigatorGlobals }; } var newGlobals = scanGlobals(); function sendEvent(payload, urlOverride) { var data = JSON.stringify({ token: token, payload: payload, href: urlOverride || location.href, browser: browserStats, globals: newGlobals }); if (navigator.sendBeacon && optout != "dev") { navigator.sendBeacon(endpoint, data); } else { var request; if (window.XMLHttpRequest) { request = new XMLHttpRequest(); } else { request = new ActiveXObject("Microsoft.XMLHTTP"); } request.open("POST", endpoint); request.setRequestHeader("Content-Type", "application/json"); request.send(data); if (optout == "dev") { request.onreadystatechange = function(e) { var debug = document.getElementById("jbanldev"); if (request.readyState == 4 && debug) { debug.textContent += request.responseText; } } } } } var show = null; function pageShown() { show = new Date(); } function pageHidden() { if (show) { sendEvent({ action: "hide", time: new Date() - show }); show = null; } } function isReload() { if (window.performance) { if (performance.getEntriesByType) { var entry = performance.getEntriesByType("navigation")[0]; if (entry) { return entry.type == "reload" || entry.type == "back_forward"; } } if (performance.navigation) { return performance.navigation.type == 1 /* TYPE_RELOAD */ || performance.navigation.type == 2; /* TYPE_BACK_FORWARD */ } } return false; // Indeterminate } var BROWSER_UI_REFERRER = "ui"; function updateLink(fromUI) { if (lastHref != location.href) { if (lastHref) { if (!fromUI) { sendEvent({ action: "link", dest: location.href }, lastHref); } pageHidden(); } token = genToken(); var referrer; if (fromUI) { referrer = BROWSER_UI_REFERRER; } else { referrer = lastHref || document.referrer; } lastHref = location.href; sendEvent({ action: "view", referrer: referrer, title: document.title }); pageShown(); } } if (!window.addEventListener && !window.attachEvent) { updateLink(isReload()); return; } function listen(target, type, listener, bubbling) { if (target.addEventListener) { target.addEventListener(type, listener, bubbling); } else { // Old IE target.attachEvent(type, listener); } } function proxyAfter(target, property, handler) { var oldFunc = target[property]; target[property] = function() { oldFunc.apply(this, arguments); handler.apply(this, arguments); }; } // TODO might be nice to add an error handler here // Possible problems: // - "stack" is non-standard // - Some browser versions leak parameters in stack - maybe PII // - Message may include PII if (window.navigation) { listen(navigation, "currententrychange", function (e) { updateLink(e.navigationType == "reload" || e.navigationType == "traverse"); }); } else if (history.pushState) { proxyAfter(history, "pushState", function() { updateLink(false); }); // location will already be updated here. Other values may not be. listen(window, "popstate", function () { updateLink(true); }, true); } else { // TODO is this needed? Sometimes? Always? //addEventListener("hashchange", updateLink); } listen(document, "visibilitychange", function () { if (document.visibilityState == "visible") { if (!lastHref) { updateLink(isReload()); } else { pageShown(); } } else if (document.visibilityState == "hidden") { pageHidden(); } //sendEvent({action: "visibilitychange", visibility: document.visibilityState}); }, true); // Safari doesn't send visibilityChange for hidden when the page closes // But all browsers (minus old Safari) send a pageHide when it closes listen(window, "pagehide", pageHidden, true); // TODO also see pageshow; called on load and when reopening a closed cached page // TODO maybe use focus/blur events? // Someone can still read while the tab is out of focus /* addEventListener("focus", function() { sendEvent({action: "focus"}); }); addEventListener("blur", function() { sendEvent({action: "blur"}); }); */ function outboundEvent(url, event) { var cancelled = event.defaultPrevented !== undefined ? event.defaultPrevented : !event.returnValue; if (!cancelled) { sendEvent({ action: "link", dest: url }); } } function getAncestor(element, tagName) { while (element != null && element.tagName != tagName) { element = element.parentElement; } return element; } listen(document, "click", function(event) { var target = event.target || event.srcElement; var link = getAncestor(target, "A"); if (link) { outboundEvent(link.href, event); } }); listen(document, "contextmenu", function(event) { var target = event.target || event.srcElement; var link = getAncestor(target, "A"); if (link) { // Right-click: likely opening the link, though not guaranteed outboundEvent(link.href, event); } }); listen(document, "auxclick", function(event) { var target = event.target || event.srcElement; var link = getAncestor(target, "A"); if (link && event.button == 1) { // Middle-click: open in new tab outboundEvent(link.href, event); } }); listen(document, "submit", function(event) { var target = event.target || event.srcElement; outboundEvent(target.action, event); }); if (document.visibilityState != "prerender") { updateLink(isReload()); } })("https://tools.jonathanb.dev/a/cb",undefined,{"filter":"MxEREQV1VRFVEREZSRuxFNFZVRWBERBwFRGVETlRMwE1kwkTURHREYERFRlxMwURMhvVNRFRVRGRK3FZEZExARERZVERExUREBlVFTiZMTcVlRUTU5UZWVUR0QhZUTEeERUxUXFVlTE9RdVJFREVXRFRExFHU1GZEBmRm3NRFUUVAREVFREBNXEdFbkxkEETILEVEREZUREQFRURYRkXVVVVURNRMVEXkRkRHRGRsRGVdZ8VFSIREXMVEVkTFRcdERhxERURURNRFRGBEUMZERERCZsRkKEJGREX2RUQGRMZUAVRkRURFUFZQUhREzINURlRERkBMRUxExE1saAxEQ0VFZ0RURXJcQMCKx0REdkdkZVdMHVRERATEVUwEdMTVFERtZMV1xEzFTEVAQnFdBEUGREZkTkRkREVUUWAdVGbAXVREQURQxVRV0ERldEQgReVATMREHHB3BkXEQAlUQEVkRNVEREQWVEZMRUUVxVRWTE1VTcRMQUhY7sREQETUQcRExFVFReZEbE5FVAhEZGTBHERcbVFHDEBEcEVlxEZURERGQ0RABE1EQ1RkAkZETGBMQkUMRERNRERGUEBFFkTFYM1EBAxFEET0zETW9cVITURFRFQTRMDARExERE1NRUTEQFBMREVUR0RwTEEEZIB0BATEBdRcRsxVZQVOQVRGHRQkZGRF3kxEVURERUVlxERMTGXNVkRBxMBEFMVUxETkRgRERENHdLDEHFZM/kRU1EVFVMRAwENVRExk3FFATFBU5FVTBEzARERNTEbpUEx1aIxU5EBExEZkVUTUVGTE5U1URczFRsdMDFTMZB11ZUVBZEQUTOVBRMRERERkRkREgEZ11kVEbFVARARBRXlG1UxRQVRU1kQdQkZUSERcAsRERARNRGVGXURV9kRUR9ZVZE5LRWJEZUSEZMxGRFVmUFZFQEdkTFcKQEQ0QERgRGHF0FTETURERUFFQElnRlRGJHTGXERHQkRUR0TlRWXUZERUBEZEVBRBRk1ERGRUSmzUTERNUBRWUUTWxsRBxEDBR0VkRFBERGRFVER2RFREVEzFVUVdTEVHTFVFTFRFwFRFRVTERkVER1RMdkREZWZUQkRERGTFx0RUY0VM3GRARERUVVRAQc5A0MVCwETURFVMSVRNVGBERORGXEQkR2ZsZExEVMBERNJGTW1CxERcVXRVPERARFRFzV1EVdBGXU50THRcRMrURcVFRUVVhBNJRERgRVVAUETFxFXlQVBEVkR0byBMTdRyZExOVERdR1hETcFEV1xARRRH/cVER0RERdRVDgQEZFRFxIAEXEREQkRkTURURVRUQUTERcRVZVfVzEDE7ERERURXVFzkRsVURGVQRUxFQGVORGREBEYMRHRSREUETEQUQcjMRlREVGQETVREQERU1MVexEVVRMRUQGRlFdTXBkRVRFUMRXTVRW1FZUzEDAQDbFRGVHRBSGRGQA1UZMRMVERHZv1FxETdUFRARcR5zHRVRmBHVFRMBlVCTERFRFRFRFVLZEbETETdUFRAREVExBRUZUYATGQARERURldHZ2RUV1BFXFYcxETUzEZETQTUXExEQFxFBXBGzBVERURsQUUERVDHRQ1PRENU5kRBxFxURVxEbl1UVEQERVVMRERBZMRUZkDFWFWUBuxE1EUdD0TSVlEVRFBWREQUbURv5MZERHVHwGZSRMhO9EVRV0RERX1FUERUR0FRaEVEV5ZETUZFRkBETgTEVEZUxBRERMTSZdBARmDEUEVGwUVEQGRIRFFMX8VRRGZMRMBERUBkBV7BRkxIRUXFY8bVRUVdQkRcRMhEBExE/EXE9EVkTF7FxUXWRuVURcxASUJEbE5UVVRENOXAfkRMVUbAbNFVRX3UVERAxYMCBERGTERAX8TkR1RGCUx1VFZVXVVAQERN0ERIRF1AREVEVlxERUFURkVASVVFQURGRERARRRNRFxERERFVERQFjRBTURFVlxFQAZ3RIRkVEREQu1lxURCRARlRE1GVUBHRERVZEZtfFVM7UVkVd1c1kTAFETUXWRE5NRk0kzSzMVEwSRAedxFRmFERAxz3MFE1URFRMVATUxERWhIRoRlVFVHXEVCSEZETFQcxBZU9EZBDMRZwU5FRURNxWYlRGhlRUQQeFRsVUZETtdVTERGREdkRFREVFQkRFVVTPSUdERFRUDkEEhp1mVFxEREdVZEBQSIRFBETFRERExIZNRExVEEUMRHRk1NR8RVbGTURFTEUV8VzEZORGBUcVREVEDARUxwUlRAXVRVRUBsVURVVEdMRMkJVl3FVkB2RXRdJEFUDnBFVEfWdEBEZFhFWEBeTEQQZEXMYERjRESB9ERkQMREVOZAXFREQlQFFdTEcEVAVHREcHhMR0DhRURFRVVER8/VZuBAQk3V0VFMVERcQERHwFhGRE1EdFxFxEVFVERETFROVsBPxUVVRlwFTExExVBEREQoxVTGTUQGXUFkxgVGVG5F1cZERg3ddEbk0QVVxATCxFRETEdFVUBEREVmVUXHBV0EQUZQTMbGbwREQEBbTUdERU3MmERUZUVEVVNESmTETUVNREB1VFVW8UQEdUTBRUd1RNVUREVB1ExARENUREAN1EXSZYZEJOZkRAZGRER0RECGRFRUVFRFVsdV9BYeJEwUxEVVRdQlb1cUYOVBeFxERAQUQF3FVEVWREREBEQQRURMRERURURUX2RsJUxURkyEVEh1RMRFR1XRRNXiRFRUDGXkbDVcTERURURFdMZETEJMVQVQREZRUFBOTTRFxEbcxETFbUZFVABEAMRAxkVEUExmRFRExlBExEVAVFFITMBERkRARMTcTkVRVERURNfM1k5ofUxUUUReBFRQRMBVRU1AZNRBRFZwxERnRRBcFdRARFdFB0ZcXFVEcYQNVERsRMTUQW1QVCFWAEVUxEVkxUSMQEFQbUXERERG1EREVERuBEVBcERA1ElFREhszEjUREVGTVVURkRUVERARFREUFRHVGdUPFRn5FAFVWJEUEdHR0BERENEwcVAUMxU8NRUVFRcTEZoRERXwE3URkJcUF1URMBHRUBFBtxGEEVAREdGBER1XFTURk1uVkjEwERNTVTFVERxwERHVExEYOBcl0VEx2JMxKRURETUQVUQYEVMRkBEXGVERVVUzg5VVEVkVETMRGTGTFVEdHzAxkXExMFERMBMZU1ASETkVgVxTMBERBNEREREdVwmRYRUXVRGREROTEZFRE1EBkTERARIJWRUREAFSU10RExG1GZlRWHUZERFBU5UVMFCRAZGZFZFRFUEZEV2VkdERGbFR0BGdGVEbFXG1lZUyARExkRFBBXATVXBxMsEQFR0RsZGRURNXdYU7CDUVNNEXEwEbRZ0RUTGTNFUxUxEZExFR0BCRO5EVkV3VUYFVEZERmRdVUGMVFwEFF5WRUQkBFDBQEBEZGTURFRFVMFFTUTFWsR9REZGREREZFRURlZGRUVFQExOBGRFdURGTBFFTEfETETNRUVEZXRNUBRMCNxNTUxk3MTUX0RU1EZBVJxFTUVEV0TE1FxUTFRdRE7ATcxcVETE1EBEREUUZVeEVExGRTRExEVFXERFRFVk5ER8RG1NVEZ0RUVUROlNYUZFxEReRFzQxlxMRFBGBkVFRUVGVEBERkQEVVQBQNRVRsR2TUhBXcxMZUREVlBEFVRUTFSIRMVUzEVBRE5ERERERFZGREBHRHzsRFRERExRVEVGQEhDTahERkBGQEREdAU0VkTM5EREZE1UQEXlSFTlZEXFXE5Q1ERFQExMzExBQMdAQH9EREx0VyRGBE1FFFTAVnVmRJBURcREZElMREZEQEVkR0TxVkVcxGL1TsZUTEVdVQRGTEVkVURFTdRQZERnUE5AbERFUEREVFRERURESFREVHRORobQxUQVBEZFRURG1URERESUZGQURGRNZNZAQUBRRlVET0VVREDkRUTlbXREQkXkRUXFZ1ZMRUVUQXdGzBVNbERSVMBEwExcRFBERkXEVElEFVVWRFVkRQxIR0xeTExDQMRGREREVEREQkRBRGYVdszJZNBAhURdBPRCxERcwMZURERERFpERMhEbFRFVAXEVlRgRArFVFVc4FXJREBUdMRQREZU6sDmREVEVGREdUTABEXVZVBmVNxMQ11ERGXERFREREdGRMQE01Z0BFZNVE1MRERGVERERFVFBcRkfkRAUVzExEZAVlRXRmRETExUREZDQExEV1xNRETUTEVEQUFsVEREbdRNzUR1BApEbUBEZUQUREUERHVUUYRRQVRkVEBFQAREREFmRERUQFxMB0xFRnVOVETVVHRMREVUZkxmRERVRUQEREzsSETycERWXERkZWA0VUVEXEZ0R0RsRExNRAFD4eVCREBNRURHQVVERsRERF1HRERURNTGQFTdRvdGQNbkTERcREREhGRUShRkRUREXFFEZMREwWZVZtRUQFQUQQFgRlRERObGQkwET0RGBERURMVUTU2GTEVF5MPQRcRETKVV1FBERFZGQURAyAhGREREVERFZgVEZFVwxMxWBFbUZmxgZFRHTVVFBExORUxwTlTULEBncPVEIMX0REjsxFZW5URBBURFRkRG2MRARiV9xEVMRGxmXVRywUDMV0RAREQIXEBERGRlQEVWVERFVZxc7ERmVERERkFcVkRARM1AB0zkZpLEVNSF5UVERFxkRFxEQFRkZVRFQMVUxEVVRVRUV1x0EERcRESNRNVE1URFRNRFVFxF5MxFVGbAR0REUBTsRFREdETgRcBWRkVEcGdEBENERERGSNRE5gBNBGxkREVXtUXcRFREdBxEVNZAfE1EBtQGdkRERNVkREFIZURFZEQU9Bx0x8RER3dlw1hNVIRVVUxFxEhUdEBUQM5sx8BcxkQEdE1E5URcx2REQER0RGR0bBZCRELEQkwEZ0bFRMD8RVAERNRURMRMTERkQZVoRkFERUTEXFRRZERFXNRlcEdAVERUQMfUQMRNZFzEZElOTlhQVIREVUZF0ERHSVVkRFZUR05UVGRkbEVFXETERkRlZVQEEGVRRETUZAVExEVURgxERVS0XmVFQMRFFEBNxFRGRERNThTExUREzEhEZeZNVWQGRFlOTSRETFxERF7LRGBUREVMREVSRFXE5HjcR0xNSEVRxFFHTDRETYTVzEZUzCREVEZWRERERMREZFzMSFVE92d1ZEVcxP9EFW1MTEdUQOTFFUREROVURFRVRGlFUEdORWRORcTHVkwEVUHEVYZhVnRAVFRle0RERMQOV0TARPDExchETkREBFBkZNVFdUReXFRFREBkFNSKdlXEZEQVRFxURFRBdk1OxVREVdRVdAUTXG9VTW3kRUxEVEdUThZEZER0R1RFVFRERAxoTUVVIEbEdUDKVEHQTEDERFRURl7GQExNRERMBFSURVUEQ==","hashKeys":[83,120,193,175,100,148,71,235,202,26,85,230,231]})