/** * Browserfehler an GlitchTip (ohne Build-Schritt, ES2018). * * Fehlerfreie Seiten laden nur diese Datei. Das Sentry-SDK (selbst gehostet, * sentry-browser-.min.js) wird erst beim ersten Fehler nachgeladen * und meldet dann genau diesen Fehler. Konfiguration kommt aus * , das die Blade-Komponente * x-monitoring.browser-errors setzt. * * Bewusst ohne Regex-Lookbehind: ältere Safari-Versionen brechen sonst beim * Parsen ab. */ (function () { 'use strict'; var meta = document.querySelector('meta[name="mivita-error-reporting"]'); var config = null; try { config = meta ? JSON.parse(meta.content) : null; } catch (e) { config = null; } if (!config || !config.dsn || !config.sdk) { return; } // wie App\Support\Monitoring\ErrorReportScrubber::AllowedQueryParameters var ALLOWED_QUERY = ['page', 'q', 'tab', 'sort', 'lang', 'month', 'year']; var TOKEN_SEGMENT = /^[A-Za-z0-9_-]{32,}$/; var EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi; /** Gleiche Regeln wie ErrorReportScrubber auf dem Server. */ function scrubUrl(value) { try { var url = new URL(value, window.location.origin); url.pathname = url.pathname .split('/') .map(function (segment) { return TOKEN_SEGMENT.test(segment) ? '[Filtered]' : segment; }) .join('/'); Array.from(url.searchParams.keys()).forEach(function (key) { if (ALLOWED_QUERY.indexOf(key) === -1 && key.indexOf('utm_') !== 0) { url.searchParams.set(key, '[Filtered]'); } }); url.hash = ''; return url.toString().replace(EMAIL, '[email]'); } catch (e) { return '[Filtered]'; } } var sdk = null; function loadSdk() { if (sdk) { return sdk; } sdk = new Promise(function (resolve, reject) { var script = document.createElement('script'); script.src = config.sdk; script.async = true; script.onload = function () { resolve(window.MivitaSentry); }; script.onerror = reject; document.head.appendChild(script); }).then(function (Sentry) { Sentry.init({ dsn: config.dsn, release: config.release || undefined, environment: config.environment, sampleRate: typeof config.sampleRate === 'number' ? config.sampleRate : 1, sendDefaultPii: false, // Eigene Listener unten statt GlobalHandlers (sonst doppelt); // keine Breadcrumbs (Klicktexte, URLs) und keine Sessions. integrations: function (defaults) { return defaults.filter(function (integration) { return ['GlobalHandlers', 'Breadcrumbs', 'BrowserSession', 'BrowserApiErrors'].indexOf(integration.name) === -1; }); }, ignoreErrors: ['ResizeObserver loop', 'Script error.', 'AbortError'], denyUrls: [/^chrome-extension:\/\//, /^moz-extension:\/\//, /^safari-(web-)?extension:\/\//], beforeSend: function (event) { if (event.request && event.request.url) { event.request.url = scrubUrl(event.request.url); } if (event.request && event.request.headers && event.request.headers.Referer) { event.request.headers.Referer = scrubUrl(event.request.headers.Referer); } ((event.exception && event.exception.values) || []).forEach(function (exception) { if (exception.value) { exception.value = exception.value.replace(EMAIL, '[email]'); } }); return event; }, }); if (config.userId) { Sentry.setUser({ id: String(config.userId) }); } Sentry.setTag('area', config.area); return Sentry; }); return sdk; } /** * Promise-Ablehnungen mit Objekt statt Error kämen als „Error: [object Object]“ * an — Typ und Inhalt mitschicken, damit die Quelle erkennbar ist. */ function rejectionToError(reason) { if (reason instanceof Error) { return reason; } var kind = (reason && reason.constructor && reason.constructor.name) || typeof reason; var text; try { text = typeof reason === 'string' ? reason : JSON.stringify(reason); } catch (e) { text = String(reason); } return new Error('Unbehandelte Ablehnung (' + kind + '): ' + String(text == null ? '' : text).slice(0, 300)); } function capture(error) { loadSdk() .then(function (Sentry) { Sentry.captureException(error); }) .catch(function () {}); } window.addEventListener('error', function (event) { // Ressourcenfehler (Bild, Skript) haben kein `error` — nur echte Skriptfehler. if (event.error) { capture(event.error); } }); window.addEventListener('unhandledrejection', function (event) { capture(rejectionToError(event.reason)); }); })();