1(function () { 2 'use strict'; 3 4 var ENDPOINT = '/api/error_report.php'; 5 var MAX_REPORTS = 5; 6 var COOLDOWN_MS = 600000; 7 8 var reportCount = 0; 9 var firstReportTime = 0; 10 11 function canReport() { 12 var now = Date.now(); 13 if (now - firstReportTime > COOLDOWN_MS) { 14 reportCount = 0; 15 firstReportTime = now; 16 } 17 if (reportCount >= MAX_REPORTS) return false; 18 reportCount++; 19 if (reportCount === 1) firstReportTime = now; 20 return true; 21 } 22 23 // First-party origins we own and can actually fix. A report is only 24 // forwarded when it originates here. location.origin covers the current 25 // host (incl. local/staging); the explicit hosts cover apex and www. 26 var FIRST_PARTY = [ 27 location.origin, 28 'https://souppe.ai', 29 'https://www.souppe.ai' 30 ]; 31 32 // Third-party / browser noise we embed but do not control and cannot 33 // patch. Matched against message + source + stack, so a report drops if 34 // any field names a known third-party origin. This is what closes the 35 // Cloudflare Turnstile gap: the widget surfaces a real message naming 36 // challenges.cloudflare.com even when window.onerror attributes the 37 // source to the page itself (cross-origin script without CORS). 38 var THIRD_PARTY_PATTERNS = [ 39 /challenges\.cloudflare\.com/, // Cloudflare Turnstile widget 40 /TurnstileError/, 41 /Cloudflare Turnstile/, 42 /accounts\.google\.com/, // Google sign-in 43 /\.firebaseapp\.com/, // Firebase auth 44 /\.gstatic\.com/, // Google static assets 45 /chrome-extension:\/\//, // browser extensions 46 /moz-extension:\/\//, 47 /safari-extension:\/\//, 48 /ResizeObserver loop/, // benign browser layout warning 49 /unsafe-eval/, // CSP eval violation from injected scripts 50 /executeScript/ // extension content-script injection 51 ]; 52 53 function isFirstPartySource(src) { 54 for (var i = 0; i < FIRST_PARTY.length; i++) { 55 if (src.indexOf(FIRST_PARTY[i]) === 0) return true; 56 } 57 return false; 58 } 59 60 function shouldReport(data) { 61 var msg = String(data.message || ''); 62 var src = String(data.source || ''); 63 var stack = String(data.stack || ''); 64 65 // Opaque cross-origin errors (no CORS) carry no usable detail. 66 if ((msg === 'Script error.' || msg === 'Script error') && !src) return false; 67 68 // Known third-party / browser noise we can never fix. 69 var combined = msg + ' ' + src + ' ' + stack; 70 for (var i = 0; i < THIRD_PARTY_PATTERNS.length; i++) { 71 if (THIRD_PARTY_PATTERNS[i].test(combined)) return false; 72 } 73 74 // With a concrete source file, require it to be first-party. An empty 75 // source (e.g. an unhandled promise rejection) is allowed through; 76 // the patterns above already vetted its message and stack. 77 if (src && !isFirstPartySource(src)) return false; 78 79 return true; 80 } 81 82 function sendReport(data) { 83 if (!shouldReport(data)) return; 84 if (!canReport()) return; 85 try { 86 var payload = JSON.stringify({ 87 message: String(data.message || '').substring(0, 2000), 88 source: String(data.source || '').substring(0, 500), 89 lineno: data.lineno || 0, 90 colno: data.colno || 0, 91 stack: String(data.stack || '').substring(0, 5000), 92 page: location.href, 93 user_agent: navigator.userAgent 94 }); 95 96 if (navigator.sendBeacon) { 97 navigator.sendBeacon(ENDPOINT, new Blob([payload], { type: 'application/json' })); 98 } else { 99 var xhr = new XMLHttpRequest(); 100 xhr.open('POST', ENDPOINT, true); 101 xhr.setRequestHeader('Content-Type', 'application/json'); 102 xhr.send(payload); 103 } 104 } catch (_) {} 105 } 106 107 window.onerror = function (message, source, lineno, colno, error) { 108 sendReport({ 109 message: message, 110 source: source, 111 lineno: lineno, 112 colno: colno, 113 stack: error && error.stack ? error.stack : '' 114 }); 115 }; 116 117 window.__souppe_reportError = function (message, err) { 118 sendReport({ 119 message: String(message || ''), 120 source: (err && err.fileName) || '', 121 lineno: (err && err.lineNumber) || 0, 122 colno: (err && err.columnNumber) || 0, 123 stack: (err && err.stack) ? err.stack : '' 124 }); 125 }; 126 127 window.addEventListener('unhandledrejection', function (event) { 128 var reason = event.reason;
129 var message = 'Unhandled Promise Rejection'; 130 var stack = ''; 131 132 if (reason instanceof Error) { 133 message = reason.message || message; 134 stack = reason.stack || ''; 135 } else if (typeof reason === 'string') { 136 message = reason; 137 } else if (reason && typeof reason === 'object') { 138 message = reason.message || JSON.stringify(reason).substring(0, 500); 139 stack = reason.stack || ''; 140 } 141 142 sendReport({ 143 message: message, 144 source: '', 145 lineno: 0, 146 colno: 0, 147 stack: stack 148 }); 149 }); 150})();
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.