PageSourceSearch

https://souppe.ai/js/error-reporter.js?v=3

js souppe.ai collected 2026-10-05 12:39:14 UTC 5,488 bytes, 150 lines download raw bytes

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.