1(function() { 2 // Guard against the script being initialized multiple times. 3 if (window.isErrorTrackingInitialized) { 4 return; 5 } 6 window.isErrorTrackingInitialized = true; 7 8 // Use a Set for more efficient and idiomatic storage of hashes. 9 const trackedErrorHashes = new Set(); 10 async function getHash(str) { 11 // The Web Crypto API is only available in secure contexts (HTTPS). 12 if (!window.crypto || !window.crypto.subtle) { 13 // Fallback for insecure contexts. Returning the string itself is a simple 14 // way to still allow session-based deduplication without a cryptographic hash. 15 console.warn('Web Crypto API not available. Using simple string for hashing.'); 16 return str; 17 } 18 const strBuf = new TextEncoder().encode(str); 19 const hashBuffer = await crypto.subtle.digest("SHA-256", strBuf); 20 const hashArray = Array.from(new Uint8Array(hashBuffer)); 21 return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); 22 } 23 24 function getBrowserName() { 25 const userAgent = navigator.userAgent || ''; 26 // The order of checks is important, as many browser user agent strings contain 27 // identifiers from other browsers. More specific identifiers must be checked first. 28 if (userAgent.includes("Edg/")) return "Microsoft Edge"; 29 if (userAgent.includes("Opera") || userAgent.includes("OPR/")) return "Opera"; 30 if (userAgent.includes("MSIE") || userAgent.includes("Trident/")) return "Internet Explorer"; 31 // Chrome's UA string contains "Safari", so "Chrome" must be checked for first. 32 if (userAgent.includes("Chrome")) return "Chrome"; 33 if (userAgent.includes("Safari")) return "Safari"; 34 if (userAgent.includes("Firefox")) return "Firefox"; 35 return "Unknown"; 36 } 37 38 function getOSName() { 39 // navigator.userAgentData.platform is the modern, more reliable API. 40 // Fallback to navigator.platform for older browsers. 41 const platform = window.navigator?.userAgentData?.platform || window.navigator.platform; 42 const userAgent = window.navigator.userAgent; 43 44 const macosPlatforms = ['macOS', 'Macintosh', 'MacIntel', 'MacPPC', 'Mac68K', 'darwin']; 45 const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']; 46 const iosPlatforms = ['iPhone', 'iPad', 'iPod']; 47 48 if (macosPlatforms.includes(platform)) { 49 return 'Mac OS'; 50 } 51 if (iosPlatforms.includes(platform)) { 52 return 'iOS'; 53 } 54 if (windowsPlatforms.includes(platform)) { 55 return 'Windows'; 56 } 57 // For Android, userAgent is more reliable than platform, which can just be "Linux". 58 if (/Android/.test(userAgent)) { 59 return 'Android'; 60 } 61 // Check for Linux last, as Android may also report a Linux platform. 62 if (/Linux/.test(platform)) { 63 return 'Linux'; 64 } 65 66 return 'Unknown'; 67 } 68 69 // https://developer.mozilla.org/en-US/docs/Web/API/Event 70 function getErrorInfos(event) { 71 let filename = ''; 72 let line = 0; 73 let column = 0; 74 let message = ''; 75 let eventname = ''; 76 let stack = ''; 77 78 // Handle ErrorEvent from window.onerror 79 if (event instanceof ErrorEvent) { 80 filename = event.filename || ''; 81 line = event.lineno || 0; 82 column = event.colno || 0; 83 message = event.message || ''; 84 // Prefer the specific error name (e.g., 'TypeError') over the generic event type ('error'). 85 // The actual Error object is often nested inside the event. 86 eventname = event.error?.name || event.name || event.type || 'Error'; 87 stack = event.error?.stack || ''; 88 } else if (event instanceof Error) { 89 // Handle manually passed Error objects 90 message = event.message; 91 eventname = event.name; 92 stack = event.stack || ''; 93 } else if (event !== null && typeof event !== 'object') { 94 // Fallback for primitive types (e.g., strings) 95 message = String(event); 96 eventname = 'Error'; 97 } 98 99 // Final fallback for filename if it's still empty 100 if (!filename) { 101 filename = window.location.href 102 } 103 return { 104 filename, 105 line, 106 column, 107 message, 108 eventname, 109 stack 110 } 111 } 112 113 /** 114 * Processes and sends an error report to the backend. 115 * @param {any} errorSource The error event, error object, or promise rejection reason. 116 * @param {object|null} extras Additional custom data to include with the report. 117 */ 118 async function processAndSendError(errorSource, extras = null) { 119 120 const errorInfos = getErrorInfos(errorSource); 121 122 const errorSignature = `${errorInfos.eventname}:${errorInfos.message}:${errorInfos.filename}:${errorInfos.line}`; 123 const errorHash = await getHash(errorSignature); 124 125 if (trackedErrorHashes.has(errorHash)) { 126 return; // Avoid sending duplicate errors from the same session. 127 } 128 trackedErrorHashes.add(errorHash); 129 130 // Prepare the extras object, starting with any provided custom data. 131 const extrasPayload = (extras !== null && typeof extras === 'object') ? { ...extras } : {}; 132 // Add the stack trace to the extras payload if it exists. 133 if (errorInfos.stack) { 134 extrasPayload.stack = errorInfos.stack; 135 } 136 137 const payload = { 138 browser: getBrowserName(), 139 os: getOSName(), 140 eventname: errorInfos.eventname, 141 filename: errorInfos.filename, 142 line: errorInfos.line, 143 column: errorInfos.column, 144 message: errorInfos.message, 145 extras: JSON.stringify(extrasPayload) 146 } 147 148 // Prevent from tracking extensions errors 149 if (/^[a-z]+\-extension:/.test(payload.filename)) { 150 return 151 } 152 153 try { 154 await fetch('/record/errors/javascript', { 155 method: 'POST', 156 body: new URLSearchParams(payload), 157 }) 158 } catch (apiError) { 159 console.error('Failed to send error report:', apiError); 160 } 161 162 } 163 164 window.addEventListener('error', (event) => processAndSendError(event)); 165 window.addEventListener('unhandledrejection', (event) => { 166 processAndSendError(event.reason || event); 167 }); 168 169 // Expose a manual reporting function on the window object for custom error handling. 170 window.sendJSErrors = processAndSendError; 171})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.