From 0b02b1b15cfcb1269e3f6d840a1b90cda1067f0d Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Thu, 8 Oct 2026 11:46:36 +0000 Subject: [PATCH] GlitchTip: Fehlerprotokoll fuer Server, Fehlersignale und Browser - sentry/sentry-laravel 4.28; config/sentry.php gehaertet (keine PII, keine Request-Bodys, before_send ErrorReportScrubber) - Handler: Integration::captureUnhandledException, CommandNotFoundException nicht melden - ReportErrorSignals (global): toter interner Link (nach Pfad wegen Catch-all-Route), 403/429 fuer Angemeldete, 419; gedrosselt - Browser: selbst gehostetes SDK (esbuild-Bundle 11.5.0), Loader ohne Build-Schritt und ohne Regex-Lookbehind, in 7 Layouts mit area-Tag - phpunit.xml: DSNs und ntfy-URL leer, CACHE_DRIVER=array (Tests schrieben bisher in den lokalen Datei-Cache) - dev/glitch-tip/README.md mit Instanz, Org, Projekt-IDs, API-Auswertung Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 6 + app/Exceptions/Handler.php | 7 + app/Http/Kernel.php | 2 + app/Http/Middleware/ReportErrorSignals.php | 96 +++++ .../Monitoring/ErrorReportScrubber.php | 159 ++++++++ app/Support/Monitoring/ErrorSignal.php | 68 ++++ composer.json | 1 + composer.lock | 382 +++++++++++++++--- config/monitoring.php | 18 + config/sentry.php | 180 +++++++++ dev/betrieb/README.md | 5 + dev/glitch-tip/README.md | 90 +++++ phpunit.xml | 7 + public/js/error-reporting/error-reporting.js | 155 +++++++ .../sentry-browser-11.5.0.min.js | 15 + .../monitoring/browser-errors.blade.php | 15 + resources/views/layouts/application.blade.php | 1 + resources/views/layouts/auth.blade.php | 1 + .../portal/layouts/application.blade.php | 1 + resources/views/portal/layouts/auth.blade.php | 1 + .../views/portal/layouts/public.blade.php | 1 + .../views/web/layouts/application.blade.php | 1 + .../web/user/layouts/application.blade.php | 1 + .../Monitoring/BrowserErrorReportingTest.php | 60 +++ .../Feature/Monitoring/ErrorReportingTest.php | 104 +++++ tests/Feature/Monitoring/ErrorSignalsTest.php | 73 ++++ .../Monitoring/SystemStatusPageTest.php | 1 + tests/Support/FakeGlitchTip.php | 63 +++ 28 files changed, 1453 insertions(+), 61 deletions(-) create mode 100644 app/Http/Middleware/ReportErrorSignals.php create mode 100644 app/Support/Monitoring/ErrorReportScrubber.php create mode 100644 app/Support/Monitoring/ErrorSignal.php create mode 100644 config/sentry.php create mode 100644 dev/glitch-tip/README.md create mode 100644 public/js/error-reporting/error-reporting.js create mode 100644 public/js/error-reporting/sentry-browser-11.5.0.min.js create mode 100644 resources/views/components/monitoring/browser-errors.blade.php create mode 100644 tests/Feature/Monitoring/BrowserErrorReportingTest.php create mode 100644 tests/Feature/Monitoring/ErrorReportingTest.php create mode 100644 tests/Feature/Monitoring/ErrorSignalsTest.php create mode 100644 tests/Support/FakeGlitchTip.php diff --git a/CLAUDE.md b/CLAUDE.md index 2dac1032..68e66779 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -167,6 +167,12 @@ Live wird per **Git-Tag** deployt (`bash dev/betrieb/deploy.sh vX.Y.Z [--migrati - Assets (`public/js`, `public/css`, `public/vendor`, `public/assets`) sind versioniert; auf dem Server wird nichts gebaut. - Version: Datei `RELEASE` → `config('app.version')`, `php artisan about --only=mivita`. +## Überwachung / Fehlerprotokoll + +- Fehler laufen nach **GlitchTip** (`https://errors.adametz.media`, Org `adametz-media`, Projekte `server-mivitacare` = 4, `browser-mivitacare` = 3). Auswerten per API mit `GLITCHTIP_API_TOKEN` aus der `.env`: `dev/glitch-tip/README.md`. +- Systemstatus: Sysadmin → Systemstatus bzw. `/health` (Token); Alarme bei fehlgeschlagenen geplanten Aufgaben/Jobs per Mail und ntfy (`App\Support\OperationalAlert`). Details: `dev/betrieb/README.md`. +- **Tests melden nie nach außen** (`phpunit.xml` leert DSNs und ntfy-URL) — beim Anlegen neuer Tests nichts daran ändern. + ## Scheduled Tasks (Cron) | Zeit | Command | Beschreibung | diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index d5c716f5..12e36959 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -8,6 +8,8 @@ use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; use Laravel\Passport\Exceptions\OAuthServerException; +use Sentry\Laravel\Integration; +use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Response; @@ -25,6 +27,8 @@ class Handler extends ExceptionHandler // PAYONE-Transportfehler werden bereits via MyLog protokolliert (inkl. Mail); // kein zusätzlicher Exception-Report nötig (entspricht dem alten abort(403)-Verhalten). PayoneTransportException::class, + // Vertippte artisan-Befehle sind kein Anwendungsfehler + CommandNotFoundException::class, ]; /** @@ -63,6 +67,9 @@ class Handler extends ExceptionHandler if ($this->shouldReport($exception)) { $this->sendEmail($exception); + + // An GlitchTip (ohne SENTRY_LARAVEL_DSN ein No-op) + Integration::captureUnhandledException($exception); } parent::report($exception); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 792edad3..38714bfb 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -19,6 +19,8 @@ class Kernel extends HttpKernel \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \App\Http\Middleware\TrustProxies::class, + // Fehler ohne Exception (toter interner Link, 403/429 für Angemeldete, 419) an GlitchTip + \App\Http\Middleware\ReportErrorSignals::class, # \App\Http\Middleware\RemoveExcessWhitespaceMiddleware::class, ]; diff --git a/app/Http/Middleware/ReportErrorSignals.php b/app/Http/Middleware/ReportErrorSignals.php new file mode 100644 index 00000000..0c2104b7 --- /dev/null +++ b/app/Http/Middleware/ReportErrorSignals.php @@ -0,0 +1,96 @@ +signalFor($request, $response->getStatusCode()); + + if ($signal !== null) { + $location = $this->locationOf($request, $signal); + $context = [ + 'status' => $response->getStatusCode(), + 'method' => $request->method(), + 'path' => '/'.ltrim($request->path(), '/'), + 'referer' => $this->refererPath($request), + ]; + + defer(fn () => $this->errorSignal->report($signal, $location, $context)); + } + + return $response; + } + + private function signalFor(Request $request, int $status): ?string + { + return match (true) { + $status === 404 && $this->hasInternalReferer($request) => ErrorSignal::DeadInternalLink, + $status === 403 && $request->user() !== null => ErrorSignal::AccessDenied, + $status === 419 => ErrorSignal::PageExpired, + $status === 429 && $request->user() !== null => ErrorSignal::Throttled, + default => null, + }; + } + + /** + * Tote Links nach Pfad: auf der Haupt-Domain fängt die Catch-all-Route + * `/{site}/{subsite?}/{product_slug?}` fast jeden Pfad — mit dem + * Routennamen landeten alle toten Links in einem Eintrag. + */ + private function locationOf(Request $request, string $signal): string + { + if ($signal === ErrorSignal::DeadInternalLink) { + return '/'.ltrim($request->path(), '/'); + } + + $routeName = $request->route()?->getName(); + + return $routeName !== null + ? $routeName + : '/'.ltrim($request->path(), '/'); + } + + private function hasInternalReferer(Request $request): bool + { + $referer = $request->headers->get('referer'); + + return is_string($referer) && parse_url($referer, PHP_URL_HOST) === $request->getHost(); + } + + private function refererPath(Request $request): ?string + { + $referer = $request->headers->get('referer'); + + if (! is_string($referer) || $referer === '') { + return null; + } + + $path = parse_url($referer, PHP_URL_PATH); + + return is_string($path) ? ErrorReportScrubber::scrubPath($path) : null; + } +} diff --git a/app/Support/Monitoring/ErrorReportScrubber.php b/app/Support/Monitoring/ErrorReportScrubber.php new file mode 100644 index 00000000..55cd666d --- /dev/null +++ b/app/Support/Monitoring/ErrorReportScrubber.php @@ -0,0 +1,159 @@ + + */ + public const AllowedQueryParameters = ['page', 'q', 'tab', 'sort', 'lang', 'month', 'year']; + + private const EmailPattern = '/[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}/i'; + + /** Pfadsegmente, die wie ein Token aussehen (Magic Links, Reset-Links). */ + private const TokenSegmentPattern = '#(?<=/)[A-Za-z0-9_\-]{32,}(?=/|$)#'; + + public static function beforeSend(Event $event, ?EventHint $hint = null): ?Event + { + $userId = self::authenticatedUserId(); + + $event->setUser($userId !== null ? UserDataBag::createFromUserIdentifier($userId) : null); + $event->setRequest(self::scrubRequest($event->getRequest())); + + foreach ($event->getExceptions() as $exception) { + $exception->setValue(self::scrubText($exception->getValue())); + } + + if ($event->getMessage() !== null) { + $formatted = $event->getMessageFormatted(); + + $event->setMessage( + self::scrubText($event->getMessage()), + $event->getMessageParams(), + $formatted !== null ? self::scrubText($formatted) : null, + ); + } + + return $event; + } + + /** + * @param array $request + * @return array + */ + public static function scrubRequest(array $request): array + { + if (isset($request['url']) && is_string($request['url'])) { + $request['url'] = self::scrubUrl($request['url']); + } + + if (isset($request['query_string']) && is_string($request['query_string'])) { + $request['query_string'] = self::scrubQuery($request['query_string']); + } + + unset($request['data'], $request['cookies']); + + foreach ($request['headers'] ?? [] as $name => $values) { + if (strtolower((string) $name) === 'referer') { + $request['headers'][$name] = array_map( + static fn (mixed $value): string => self::scrubUrl((string) $value), + (array) $values, + ); + } + } + + return $request; + } + + public static function scrubUrl(string $url): string + { + $query = parse_url($url, PHP_URL_QUERY); + $withoutQuery = $query !== null && $query !== false ? Str::before($url, '?') : $url; + $scrubbedPath = self::scrubPath(self::scrubPathTokens($withoutQuery)); + + return $query !== null && $query !== false && $query !== '' + ? $scrubbedPath.'?'.self::scrubQuery($query) + : $scrubbedPath; + } + + public static function scrubQuery(string $query): string + { + parse_str($query, $parameters); + + $scrubbed = []; + foreach ($parameters as $name => $value) { + $isAllowed = in_array((string) $name, self::AllowedQueryParameters, true) + || str_starts_with((string) $name, 'utm_'); + + $scrubbed[$name] = $isAllowed && is_string($value) ? $value : self::Filtered; + } + + return http_build_query($scrubbed, '', '&', PHP_QUERY_RFC3986); + } + + public static function scrubPath(string $path): string + { + return self::scrubText((string) preg_replace(self::TokenSegmentPattern, self::Filtered, $path)); + } + + public static function scrubText(string $text): string + { + return (string) preg_replace(self::EmailPattern, '[email]', $text); + } + + /** + * Ersetzt Routenparameter, deren Name auf ein Geheimnis hindeutet + * (`token`, `hash`, `signature`), im Pfad der aktuellen Route. + */ + private static function scrubPathTokens(string $url): string + { + try { + $route = request()->route(); + } catch (Throwable) { + return $url; + } + + if ($route === null) { + return $url; + } + + foreach ($route->parameters() as $name => $value) { + if (is_string($value) && $value !== '' && Str::contains(strtolower((string) $name), ['token', 'hash', 'signature'])) { + $url = str_replace(rawurlencode($value), self::Filtered, str_replace($value, self::Filtered, $url)); + } + } + + return $url; + } + + private static function authenticatedUserId(): int|string|null + { + try { + return Auth::id(); + } catch (Throwable) { + return null; + } + } +} diff --git a/app/Support/Monitoring/ErrorSignal.php b/app/Support/Monitoring/ErrorSignal.php new file mode 100644 index 00000000..db66cbc5 --- /dev/null +++ b/app/Support/Monitoring/ErrorSignal.php @@ -0,0 +1,68 @@ + 'Toter interner Link', + self::AccessDenied => 'Zugriff verweigert (angemeldet)', + self::PageExpired => 'Seite abgelaufen (419)', + self::Throttled => 'Drosselung trifft angemeldeten Nutzer', + ]; + + private const MaxReportsPerWindow = 5; + + private const WindowSeconds = 600; + + /** + * @param array $context + */ + public function report(string $signal, string $location, array $context = []): bool + { + $location = Str::limit($location, 200, ''); + + $reported = RateLimiter::attempt( + 'error-signal:'.sha1($signal.'|'.$location), + self::MaxReportsPerWindow, + static fn (): bool => true, + self::WindowSeconds, + ); + + if ($reported === false) { + return false; + } + + withScope(function (Scope $scope) use ($signal, $location, $context): void { + $scope->setFingerprint(['error-signal', $signal, $location]); + $scope->setTag('signal', $signal); + $scope->setContext('signal', $context); + + captureMessage((self::Labels[$signal] ?? $signal).': '.$location, Severity::warning()); + }); + + return true; + } +} diff --git a/composer.json b/composer.json index bec853c8..e0c683ef 100755 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "laravel/tinker": "^2.9", "laravel/ui": "^4.2", "maatwebsite/excel": "^3.1", + "sentry/sentry-laravel": "^4.28", "setasign/fpdf": "^1.8.6", "setasign/fpdi": "^2.6", "spatie/laravel-html": "^3.7", diff --git a/composer.lock b/composer.lock index d988faef..94341388 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2df0d325d151049579625a307ff864af", + "content-hash": "e6e519d35f9743e47b8c08379da6f25e", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -1990,6 +1990,66 @@ ], "time": "2026-05-01T08:20:10+00:00" }, + { + "name": "jean85/pretty-package-versions", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + }, + "time": "2025-03-19T14:43:43+00:00" + }, { "name": "jenssegers/date", "version": "v4.0.0", @@ -5989,6 +6049,195 @@ }, "time": "2025-07-11T13:20:48+00:00" }, + { + "name": "sentry/sentry", + "version": "4.34.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php.git", + "reference": "3d8f4e56becb10ede64e2ac6f8077f6434715461" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/3d8f4e56becb10ede64e2ac6f8077f6434715461", + "reference": "3d8f4e56becb10ede64e2ac6f8077f6434715461", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1|^3.0.0", + "jean85/pretty-package-versions": "^1.5|^2.0.4", + "php": "^7.2|^8.0", + "psr/log": "^1.0|^2.0|^3.0", + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0|^8.0" + }, + "conflict": { + "raven/raven": "*" + }, + "require-dev": { + "carthage-software/mago": "1.30.0", + "friendsofphp/php-cs-fixer": "^3.4", + "guzzlehttp/guzzle": "^7.0|^8.0", + "guzzlehttp/promises": "^2.0.3|^3.0.0", + "monolog/monolog": "^1.6|^2.0|^3.0", + "nyholm/psr7": "^1.8", + "open-telemetry/api": "^1.1", + "open-telemetry/context": "^1.1", + "open-telemetry/exporter-otlp": "^1.1", + "open-telemetry/gen-otlp-protobuf": "^1.2.1", + "open-telemetry/sdk": "^1.1", + "open-telemetry/sem-conv": "^1.27", + "phpstan/phpstan": "^2.2", + "phpunit/phpunit": "^8.5.52|^9.6.34", + "spiral/roadrunner-http": "^3.6", + "spiral/roadrunner-worker": "^3.6" + }, + "suggest": { + "ext-excimer": "Enable Sentry profiling with the Excimer PHP extension.", + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Sentry\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "PHP SDK for Sentry (http://sentry.io)", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "profiling", + "sentry", + "tracing" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-php/issues", + "source": "https://github.com/getsentry/sentry-php/tree/4.34.0" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2026-10-08T11:25:58+00:00" + }, + { + "name": "sentry/sentry-laravel", + "version": "4.28.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-laravel.git", + "reference": "34bf18fbf99b884a9bfbb46957d0178d2d0b988e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/34bf18fbf99b884a9bfbb46957d0178d2d0b988e", + "reference": "34bf18fbf99b884a9bfbb46957d0178d2d0b988e", + "shasum": "" + }, + "require": { + "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0", + "nyholm/psr7": "^1.0", + "php": "^7.2 | ^8.0", + "sentry/sentry": "^4.31.0", + "symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0 | ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.11", + "guzzlehttp/guzzle": "^7.2", + "laravel/ai": "^0.1.5", + "laravel/folio": "^1.1", + "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0", + "laravel/octane": "^2.15", + "laravel/pennant": "^1.0", + "livewire/livewire": "^2.0 | ^3.0 | ^4.0", + "mockery/mockery": "^1.3", + "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5 | ^9.6 | ^10.4 | ^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Sentry": "Sentry\\Laravel\\Facade" + }, + "providers": [ + "Sentry\\Laravel\\ServiceProvider", + "Sentry\\Laravel\\Tracing\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-0": { + "Sentry\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "Laravel SDK for Sentry (https://sentry.io)", + "homepage": "https://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "laravel", + "log", + "logging", + "profiling", + "sentry", + "tracing" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-laravel/issues", + "source": "https://github.com/getsentry/sentry-laravel/tree/4.28.0" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2026-09-23T14:29:19+00:00" + }, { "name": "setasign/fpdf", "version": "1.9.0", @@ -7243,6 +7492,77 @@ ], "time": "2026-05-23T16:22:37+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.37.0", @@ -9790,66 +10110,6 @@ }, "time": "2026-06-16T14:57:46+00:00" }, - { - "name": "jean85/pretty-package-versions", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", - "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.1.0", - "php": "^7.4|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^7.5|^8.5|^9.6", - "rector/rector": "^2.0", - "vimeo/psalm": "^4.3 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" - }, - "time": "2025-03-19T14:43:43+00:00" - }, { "name": "laravel/boost", "version": "v1.8.13", diff --git a/config/monitoring.php b/config/monitoring.php index 7a8e8943..8b7dd777 100644 --- a/config/monitoring.php +++ b/config/monitoring.php @@ -45,6 +45,24 @@ return [ ], ], + /* + |-------------------------------------------------------------------------- + | Browserfehler + |-------------------------------------------------------------------------- + | + | Eigenes GlitchTip-Projekt für JavaScript-Fehler. Die DSN ist öffentlich + | (steht im HTML), sie erlaubt nur das Einliefern von Ereignissen. Ohne + | DSN wird kein Skript eingebunden. Das SDK liegt selbst gehostet unter + | public/js/error-reporting/sentry-browser-.min.js. + | + */ + + 'browser' => [ + 'dsn' => env('SENTRY_BROWSER_DSN'), + 'sample_rate' => (float) env('SENTRY_BROWSER_SAMPLE_RATE', 1.0), + 'sdk_version' => '11.5.0', + ], + 'scheduler' => [ 'heartbeat_warn_minutes' => 3, 'heartbeat_critical_minutes' => 10, diff --git a/config/sentry.php b/config/sentry.php new file mode 100644 index 00000000..a3a71cc8 --- /dev/null +++ b/config/sentry.php @@ -0,0 +1,180 @@ + env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')), + + // @see https://spotlightjs.com/ + // 'spotlight' => env('SENTRY_SPOTLIGHT', false), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#logger + // 'logger' => Sentry\Logger\DebugFileLogger::class, // By default this will log to `storage_path('logs/sentry.log')` + + // The release version of your application + // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) + // Gleiche Kennung wie config('app.version'): SENTRY_RELEASE bzw. + // APP_VERSION, sonst die beim Deploy geschriebene Datei RELEASE. + 'release' => Release::current(env('SENTRY_RELEASE', env('APP_VERSION'))), + + // When left empty or `null` the Laravel environment will be used (usually discovered from `APP_ENV` in your `.env`) + 'environment' => env('SENTRY_ENVIRONMENT'), + + // Override the organization ID used for trace continuation checks. + 'org_id' => env('SENTRY_ORG_ID') === null ? null : (int) env('SENTRY_ORG_ID'), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#sample_rate + 'sample_rate' => env('SENTRY_SAMPLE_RATE') === null ? 1.0 : (float) env('SENTRY_SAMPLE_RATE'), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces_sample_rate + 'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_TRACES_SAMPLE_RATE'), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#profiles_sample_rate + 'profiles_sample_rate' => env('SENTRY_PROFILES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_PROFILES_SAMPLE_RATE'), + + // Only continue incoming traces when the organization IDs are compatible with this SDK instance. + 'strict_trace_continuation' => env('SENTRY_STRICT_TRACE_CONTINUATION', false), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#enable_logs + 'enable_logs' => env('SENTRY_ENABLE_LOGS', false), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#enable_metrics + 'enable_metrics' => env('SENTRY_ENABLE_METRICS', false), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#log_flush_threshold + 'log_flush_threshold' => env('SENTRY_LOG_FLUSH_THRESHOLD') === null ? null : (int) env('SENTRY_LOG_FLUSH_THRESHOLD'), + + // The minimum log level that will be sent to Sentry as logs using the `sentry_logs` logging channel + 'logs_channel_level' => env('SENTRY_LOG_LEVEL', env('SENTRY_LOGS_LEVEL', env('LOG_LEVEL', 'debug'))), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#send_default_pii + 'send_default_pii' => false, + + // Formular- und JSON-Inhalte verlassen den Server nie. + 'max_request_body_size' => 'never', + + // User-ID statt Person, Tokens aus URL/Query/Referer, E-Mails aus Meldungen. + 'before_send' => [ErrorReportScrubber::class, 'beforeSend'], + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#ignore_exceptions + // 'ignore_exceptions' => [], + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#ignore_transactions + 'ignore_transactions' => [ + // Ignore Laravel's default health URL + '/up', + '/health', + ], + + // Breadcrumb specific configuration + 'breadcrumbs' => [ + // Capture Laravel logs as breadcrumbs + 'logs' => env('SENTRY_BREADCRUMBS_LOGS_ENABLED', false), + + // Capture Laravel cache events (hits, writes etc.) as breadcrumbs + 'cache' => env('SENTRY_BREADCRUMBS_CACHE_ENABLED', false), + + // Capture Livewire components like routes as breadcrumbs + 'livewire' => env('SENTRY_BREADCRUMBS_LIVEWIRE_ENABLED', true), + + // Capture SQL queries as breadcrumbs + 'sql_queries' => env('SENTRY_BREADCRUMBS_SQL_QUERIES_ENABLED', true), + + // Capture SQL query bindings (parameters) in SQL query breadcrumbs + 'sql_bindings' => env('SENTRY_BREADCRUMBS_SQL_BINDINGS_ENABLED', false), + + // Capture queue job information as breadcrumbs + 'queue_info' => env('SENTRY_BREADCRUMBS_QUEUE_INFO_ENABLED', true), + + // Capture command information as breadcrumbs + 'command_info' => env('SENTRY_BREADCRUMBS_COMMAND_JOBS_ENABLED', true), + + // Capture HTTP client request information as breadcrumbs + 'http_client_requests' => env('SENTRY_BREADCRUMBS_HTTP_CLIENT_REQUESTS_ENABLED', true), + + // Capture send notifications as breadcrumbs + 'notifications' => env('SENTRY_BREADCRUMBS_NOTIFICATIONS_ENABLED', true), + ], + + // Performance monitoring specific configuration + 'tracing' => [ + // Trace queue jobs as their own transactions (this enables tracing for queue jobs) + 'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', true), + + // Capture queue jobs as spans when executed on the sync driver + 'queue_jobs' => env('SENTRY_TRACE_QUEUE_JOBS_ENABLED', true), + + // Capture SQL queries as spans + 'sql_queries' => env('SENTRY_TRACE_SQL_QUERIES_ENABLED', true), + + // Capture SQL query bindings (parameters) in SQL query spans + 'sql_bindings' => env('SENTRY_TRACE_SQL_BINDINGS_ENABLED', false), + + // Capture where the SQL query originated from on the SQL query spans + 'sql_origin' => env('SENTRY_TRACE_SQL_ORIGIN_ENABLED', true), + + // Define a threshold in milliseconds for SQL queries to resolve their origin + 'sql_origin_threshold_ms' => env('SENTRY_TRACE_SQL_ORIGIN_THRESHOLD_MS', 100), + + // Capture views rendered as spans + 'views' => env('SENTRY_TRACE_VIEWS_ENABLED', true), + + // Capture Livewire components as spans + 'livewire' => env('SENTRY_TRACE_LIVEWIRE_ENABLED', true), + + // Capture HTTP client requests as spans + 'http_client_requests' => env('SENTRY_TRACE_HTTP_CLIENT_REQUESTS_ENABLED', true), + + // Capture Laravel cache events (hits, writes etc.) as spans + 'cache' => env('SENTRY_TRACE_CACHE_ENABLED', true), + + // Capture Redis operations as spans (this enables Redis events in Laravel) + 'redis_commands' => env('SENTRY_TRACE_REDIS_COMMANDS', false), + + // Capture where the Redis command originated from on the Redis command spans + 'redis_origin' => env('SENTRY_TRACE_REDIS_ORIGIN_ENABLED', true), + + // Capture send notifications as spans + 'notifications' => env('SENTRY_TRACE_NOTIFICATIONS_ENABLED', true), + + // Enable tracing for requests without a matching route (404's) + 'missing_routes' => env('SENTRY_TRACE_MISSING_ROUTES_ENABLED', false), + + // Configures if the performance trace should continue after the response has been sent to the user until the application terminates + // This is required to capture any spans that are created after the response has been sent like queue jobs dispatched using `dispatch(...)->afterResponse()` for example + 'continue_after_response' => env('SENTRY_TRACE_CONTINUE_AFTER_RESPONSE', true), + + // Capture AI agent interactions as spans (requires laravel/ai) + 'gen_ai' => env('SENTRY_TRACE_GEN_AI_ENABLED', true), + + // Capture AI invoke_agent spans + 'gen_ai_invoke_agent' => env('SENTRY_TRACE_GEN_AI_INVOKE_AGENT_ENABLED', true), + + // Capture AI chat spans + 'gen_ai_chat' => env('SENTRY_TRACE_GEN_AI_CHAT_ENABLED', true), + + // Capture AI execute_tool spans + 'gen_ai_execute_tool' => env('SENTRY_TRACE_GEN_AI_EXECUTE_TOOL_ENABLED', true), + + // Capture AI embeddings spans + 'gen_ai_embeddings' => env('SENTRY_TRACE_GEN_AI_EMBEDDINGS_ENABLED', true), + + // Enable the tracing integrations supplied by Sentry (recommended) + 'default_integrations' => env('SENTRY_TRACE_DEFAULT_INTEGRATIONS_ENABLED', true), + ], + +]; diff --git a/dev/betrieb/README.md b/dev/betrieb/README.md index ac691745..eed196df 100644 --- a/dev/betrieb/README.md +++ b/dev/betrieb/README.md @@ -245,6 +245,11 @@ wirklich läuft. Steht er nach dem Deploy auf „Kritisch“, ist in Ploi kein S Schwellen in `.env` (optional): `MONITORING_QUEUE_BACKLOG_SECONDS=300`, `MONITORING_DISK_MIN_FREE_PERCENT=10`. +**Fehlerprotokoll GlitchTip:** Exceptions, Fehlersignale (toter interner Link, 403/429, 419) und +Browserfehler — Einrichtung, Werte und Auswertung in [`dev/glitch-tip/README.md`](../glitch-tip/README.md). +Live-`.env`: `SENTRY_LARAVEL_DSN`, `SENTRY_BROWSER_DSN`, `SENTRY_ENVIRONMENT=production`, +`MONITORING_NTFY_URL`, `MONITORING_NTFY_TOPIC`, `MONITORING_NTFY_TOKEN`. + **Alarm-Mails** (`App\Support\OperationalAlert`): Schlägt eine geplante Aufgabe fehl (Exit-Code ≠ 0, z. B. `business:store-optimized`, `user:make_abo_order`) oder ein Queue-Job endgültig, geht eine Mail an `MONITORING_ALERT_EMAIL` (Standard: `EXCEPTION_MAIL`). Gleiche Meldungen höchstens einmal pro Stunde diff --git a/dev/glitch-tip/README.md b/dev/glitch-tip/README.md new file mode 100644 index 00000000..ffd1c61d --- /dev/null +++ b/dev/glitch-tip/README.md @@ -0,0 +1,90 @@ +# GlitchTip — Fehlerprotokoll mivita.care + +**Stand:** 08.10.2026 · Hintergrund und Regeln: [`dev/git-deploy-monitoring/02-fehlerprotokoll-glitchtip.md`](../git-deploy-monitoring/02-fehlerprotokoll-glitchtip.md) + +| | Wert | +|---|---| +| Instanz | `https://errors.adametz.media` (selbst gehostet, gemeinsam mit anderen Projekten) | +| Organisation | `adametz-media` | +| Projekt Server (PHP/Laravel) | `server-mivitacare`, ID **4** → `SENTRY_LARAVEL_DSN` | +| Projekt Browser (JavaScript) | `browser-mivitacare`, ID **3** → `SENTRY_BROWSER_DSN` | +| Umgebungen | `production` (Live), `local` (Entwicklung, nur zum Testen) | +| Push (ntfy) | `https://push.adametz.media`, Topic in `MONITORING_NTFY_TOPIC` | + +## Was gemeldet wird + +| Quelle | Wie | +|---|---| +| Exceptions (Request, Job, Command) | `App\Exceptions\Handler::report()` → `Integration::captureUnhandledException()`; Laravels `dontReport` gilt (404, Validierung, Auth), zusätzlich `PayoneTransportException`, `CommandNotFoundException` | +| Fehler ohne Exception | `App\Http\Middleware\ReportErrorSignals` (global): 404 mit eigenem Referrer (nach **Pfad**, nicht Route — wegen der Catch-all-Route), 403/429 für Angemeldete, 419; höchstens 5 je 10 Min. und Ort | +| Browserfehler | `public/js/error-reporting/error-reporting.js` über `` in 7 Layouts (`crm`, `crm-auth`, `portal`, `portal-auth`, `portal-public`, `web`, `shop`) | +| Betriebs-Alarme | `App\Support\OperationalAlert` → Log, Mail, ntfy (siehe `dev/betrieb/README.md`, Abschnitt G) | + +Datenschutz: `App\Support\Monitoring\ErrorReportScrubber` (Server) und dieselben Regeln in +`error-reporting.js` (Browser): nur User-ID, keine Request-Bodys/Cookies, Tokens und nicht erlaubte +Query-Parameter als `[Filtered]`, E-Mail-Adressen als `[email]`. Erlaubte Query-Parameter: +`page, q, tab, sort, lang, month, year` (+ `utm_*`) — Liste an beiden Stellen gleich halten. + +Tests melden nie an das echte GlitchTip/ntfy (`phpunit.xml`, Wächtertest in `ErrorReportingTest`). + +## Browser-SDK (selbst gehostet) + +Kein Drittanbieter-CDN (sonst ginge die IP jedes Besuchers an Sentry). `public/js/error-reporting/`: + +- `error-reporting.js` — Listener, lädt das SDK erst beim ersten Fehler; ES2018, **ohne Regex-Lookbehind** + (ältere Safari-Versionen brechen sonst beim Parsen ab — Test prüft das) +- `sentry-browser-.min.js` — Version in `config('monitoring.browser.sdk_version')` + +Neu bauen (z. B. Update auf eine neue SDK-Version), außerhalb des Projekts: + +```bash +mkdir /tmp/sentry-build && cd /tmp/sentry-build && npm init -y >/dev/null +npm install --ignore-scripts --save-exact @sentry/browser@ esbuild +echo "export { captureException, init, setTag, setUser } from '@sentry/browser';" > entry.js +npx esbuild entry.js --bundle --minify --format=iife --global-name=MivitaSentry --target=es2018 \ + --legal-comments=none --outfile=sentry-browser-.min.js +``` + +Datei nach `public/js/error-reporting/` kopieren, `sdk_version` in `config/monitoring.php` anpassen, +alte Datei löschen. + +## Auswerten per API + +Lese-Token (`project:read`, `event:read`) liegt lokal als `GLITCHTIP_API_TOKEN` in der `.env` — nie +ausgeben, nie committen. + +```bash +T=$(grep -E "^GLITCHTIP_API_TOKEN=" .env | cut -d= -f2- | tr -d "'\"") +B=https://errors.adametz.media/api/0 + +# Offene Einträge, nur Live (project=4 Server, project=3 Browser) +curl -s -H "Authorization: Bearer $T" \ + "$B/organizations/adametz-media/issues/?project=4&query=is:unresolved&environment=production&limit=100" \ +| python3 -c " +import json,sys +for i in json.load(sys.stdin): + print(i['id'], i['count'], i['firstSeen'][:16], i['lastSeen'][:16], '|', (i['title'] or '')[:100])" + +# Letztes Ereignis eines Eintrags (Umgebung, Release, Bereich, Stacktrace) +curl -s -H "Authorization: Bearer $T" "$B/issues//events/latest/" +``` + +Einordnen und Vorgehen: Abschnitt 7 in `02-fehlerprotokoll-glitchtip.md`. Auflösen in GlitchTip von +Hand (Token ist nur lesend). + +## Testen + +```bash +# Server (Umgebung laut SENTRY_ENVIRONMENT) +php artisan tinker --execute 'report(new RuntimeException("GlitchTip-Test"));' +# Alarm (Mail + ntfy) +php artisan tinker --execute 'App\Support\OperationalAlert::send("Test-Alarm");' +``` + +Browser (Konsole auf einer Seite der App, z. B. CRM): + +```js +setTimeout(() => { throw new Error('Browser-Test test@example.com') }); +``` + +→ erscheint im Projekt `browser-mivitacare`, E-Mail als `[email]`. diff --git a/phpunit.xml b/phpunit.xml index 352d8c85..5e7e70f9 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -27,6 +27,8 @@ + + @@ -38,6 +40,11 @@ + + + + + diff --git a/public/js/error-reporting/error-reporting.js b/public/js/error-reporting/error-reporting.js new file mode 100644 index 00000000..32e24ad3 --- /dev/null +++ b/public/js/error-reporting/error-reporting.js @@ -0,0 +1,155 @@ +/** + * 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)); + }); +})(); diff --git a/public/js/error-reporting/sentry-browser-11.5.0.min.js b/public/js/error-reporting/sentry-browser-11.5.0.min.js new file mode 100644 index 00000000..bdc015ec --- /dev/null +++ b/public/js/error-reporting/sentry-browser-11.5.0.min.js @@ -0,0 +1,15 @@ +var MivitaSentry=(()=>{var jt=Object.defineProperty;var ca=Object.getOwnPropertyDescriptor;var ua=Object.getOwnPropertyNames;var da=Object.prototype.hasOwnProperty;var ma=(e,t)=>{for(var i in t)jt(e,i,{get:t[i],enumerable:!0})},ga=(e,t,i,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ua(t))!da.call(e,n)&&n!==i&&jt(e,n,{get:()=>t[n],enumerable:!(r=ca(t,n))||r.enumerable});return e};var ba=e=>ga(jt({},"__esModule",{value:!0}),e);var sc={};ma(sc,{captureException:()=>De,init:()=>br,setTag:()=>kt,setUser:()=>Tt});var m=typeof __SENTRY_DEBUG__=="undefined"||__SENTRY_DEBUG__;var y=globalThis;var W="11.5.0";function j(){return ee(y),y}function ee(e){let t=e.__SENTRY__=e.__SENTRY__||{};return t.version=t.version||W,t[W]=t[W]||{}}function z(e,t,i=y){let r=i.__SENTRY__=i.__SENTRY__||{},n=r[W]=r[W]||{};return n[e]||(n[e]=t())}var qe=["debug","info","warn","error","log","assert","trace"],ya="Sentry Logger ",Ie={};function x(e){if(!("console"in y))return e();let t=y.console,i={},r=Object.keys(Ie);r.forEach(n=>{let s=Ie[n];i[n]=t[n],t[n]=s});try{return e()}finally{r.forEach(n=>{t[n]=i[n]})}}function _a(){$t().enabled=!0}function fa(){$t().enabled=!1}function fr(){return $t().enabled}function ha(...e){Kt("log",...e)}function va(...e){Kt("warn",...e)}function Sa(...e){Kt("error",...e)}function Kt(e,...t){m&&fr()&&x(()=>{y.console[e](`${ya}[${e}]:`,...t)})}function $t(){return m?z("loggerSettings",()=>({enabled:!1})):{enabled:!1}}var d={enable:_a,disable:fa,isEnabled:fr,log:ha,warn:va,error:Sa};var hr=/\(error: (.*)\)/,vr=/captureMessage|captureException/;function st(...e){let t=e.sort((i,r)=>i[0]-r[0]).map(i=>i[1]);return(i,r=0,n=0)=>{let s=[],a=i.split(` +`);for(let o=r;o1024&&(l=l.slice(0,1024));let p=hr.test(l)?l.replace(hr,"$1"):l;if(!p.includes("Error: ")){for(let c of t){let u=c(p);if(u){s.push(u);break}}if(s.length>=50+n)break}}return Sr(s.slice(n))}}function Xt(e){return Array.isArray(e)?st(...e):e}function Sr(e){if(!e.length)return[];let t=Array.from(e);return/sentryWrapped/.test(nt(t).function||"")&&t.pop(),t.reverse(),vr.test(nt(t).function||"")&&(t.pop(),vr.test(nt(t).function||"")&&t.pop()),t.slice(0,50).map(i=>({...i,filename:i.filename||nt(t).filename,function:i.function||"?"}))}function nt(e){return e[e.length-1]||{}}var Qt="";function q(e){try{return!e||typeof e!="function"?Qt:e.name||Qt}catch(t){return Qt}}function at(e){let t=e.exception;if(t){let i=[];try{return t.values.forEach(r=>{r.stacktrace.frames&&i.push(...r.stacktrace.frames)}),i}catch(r){return}}}var Be={},Er={};function w(e,t){return Be[e]=Be[e]||[],Be[e].push(t),()=>{let i=Be[e];if(i){let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}function R(e,t){if(!Er[e]){Er[e]=!0;try{t()}catch(i){m&&d.error(`Error while instrumenting ${e}`,i)}}}function k(e,t){let i=e&&Be[e];if(i)for(let r of i)try{r(t)}catch(n){m&&d.error(`Error while triggering instrumentation handler. +Type: ${e} +Name: ${q(r)} +Error:`,n)}}var Jt=null;function Zt(e){let t="error";w(t,e),R(t,Ea)}function Ea(){Jt=y.onerror,y.onerror=function(e,t,i,r,n){return k("error",{column:r,error:n,line:i,msg:e,url:t}),Jt?Jt.apply(this,arguments):!1},y.onerror.__SENTRY_INSTRUMENTED__=!0}var ei=null;function ti(e){let t="unhandledrejection";w(t,e),R(t,ka)}function ka(){ei=y.onunhandledrejection,y.onunhandledrejection=function(e){return k("unhandledrejection",e),ei?ei.apply(this,arguments):!0},y.onunhandledrejection.__SENTRY_INSTRUMENTED__=!0}var kr=Object.prototype.toString;function N(e){switch(kr.call(e)){case"[object Error]":case"[object Exception]":case"[object DOMException]":case"[object WebAssembly.Exception]":return!0;default:return lt(e,Error)}}function Ae(e,t){return kr.call(e)===`[object ${t}]`}function Fe(e){return Ae(e,"ErrorEvent")}function ot(e){return Ae(e,"DOMError")}function ii(e){return Ae(e,"DOMException")}function C(e){return Ae(e,"String")}function ge(e){return typeof e=="object"&&e!==null&&"__sentry_template_string__"in e&&"__sentry_template_values__"in e}function B(e){return e===null||ge(e)||typeof e!="object"&&typeof e!="function"}function te(e){return Ae(e,"Object")}function ae(e){return typeof e=="object"&&e!==null}function be(e){return typeof Event!="undefined"&<(e,Event)}function ri(e){return Ae(e,"RegExp")}function F(e){return!!(e!=null&&e.then&&typeof e.then=="function")}function lt(e,t){try{return e instanceof t}catch(i){return!1}}function ni(e){return typeof Request!="undefined"&<(e,Request)}function T(e,t,i){if(!(t in e))return;let r=e[t];if(typeof r!="function")return;let n=i(r);typeof n=="function"&&pt(n,r);try{e[t]=n}catch(s){m&&d.log(`Failed to replace method "${t}" in object`,e)}}function P(e,t,i){try{Object.defineProperty(e,t,{value:i,writable:!0,configurable:!0})}catch(r){m&&d.log(`Failed to add non-enumerable property "${String(t)}" to object`,e)}}function pt(e,t){try{let i=t.prototype||{};e.prototype=t.prototype=i,P(e,"__sentry_original__",t)}catch(i){}}function ye(e){return e.__sentry_original__}function ct(e){if(N(e))return{message:e.message,name:e.name,stack:e.stack,...Tr(e)};if(be(e)){let{type:t,target:i,currentTarget:r,detail:n}=e;return{type:t,target:i,currentTarget:r,...n?{detail:n}:{},...Tr(e)}}return e}function Tr(e){return ae(e)?Object.fromEntries(Object.entries(e)):{}}function si(e){let t=Object.keys(ct(e));return t.sort(),t[0]?t.join(", "):"[object has no keys]"}var Oe;function xe(e){if(Oe!==void 0)return Oe?Oe(e):e();let t=Symbol.for("__SENTRY_SAFE_RANDOM_ID_WRAPPER__"),i=y;return t in i&&typeof i[t]=="function"?(Oe=i[t],Oe(e)):(Oe=null,e())}function we(){return xe(()=>Math.random())}function K(){return xe(()=>Date.now())}var Ta=Symbol.for("sentry.skipNormalization"),Ia=Symbol.for("sentry.overrideNormalizationDepth");function Ir(e){return!!e[Ta]}function Ar(e){let t=e[Ia];return typeof t=="number"?t:void 0}var ai;function li(e){ai=e}function G(e,t=100,i=1/0){try{return oi("",e,t,i)}catch(r){return{ERROR:`**non-serializable** (${r})`}}}function ut(e,t=3,i=100*1024){let r=G(e,t);return xa(r)>i?ut(e,t-1,i):r}function oi(e,t,i=1/0,r=1/0,n=wa()){let[s,a]=n;if(t==null||["boolean","string"].includes(typeof t)||typeof t=="number"&&Number.isFinite(t))return t;let o=pi(e,t);if(!o.startsWith("[object "))return o;if(Ir(t))return t;let l=Ar(t),p=l!==void 0?l:i;if(p===0)return o.replace("object ","");if(s(t))return"[Circular ~]";let c=t;if(c&&typeof c.toJSON=="function")try{let b=c.toJSON();return oi("",b,p-1,r,n)}catch(b){}let u=Array.isArray(t)?[]:{},g=0,_=ct(t);for(let b in _){if(!Object.prototype.hasOwnProperty.call(_,b))continue;if(g>=r){u[b]="[MaxProperties ~]";break}let E=_[b];u[b]=oi(b,E,p-1,r,n),g++}return a(t),u}function pi(e,t){try{if(ai){let r=ai(t);if(r)return r}return typeof global!="undefined"&&t===global?"[Global]":typeof t=="number"&&!Number.isFinite(t)?`[${t}]`:typeof t=="function"?`[Function: ${q(t)}]`:typeof t=="symbol"?`[${String(t)}]`:typeof t=="bigint"?`[BigInt: ${String(t)}]`:`[object ${Aa(t)}]`}catch(i){return`**non-serializable** (${i})`}}function Aa(e){let t=Object.getPrototypeOf(e);return t!=null&&t.constructor?t.constructor.name:"null prototype"}function Oa(e){return~-encodeURI(e).split(/%..|./).length}function xa(e){return Oa(JSON.stringify(e))}function wa(){let e=new WeakSet;function t(r){return e.has(r)?!0:(e.add(r),!1)}function i(r){e.delete(r)}return[t,i]}function Ge(e,t=0){return typeof e!="string"||t===0||e.length<=t?e:`${e.slice(0,t)}...`}function Or(e,t){if(!Array.isArray(e))return"";let i=[];for(let r=0;re.randomUUID()).replace(/-/g,"")}catch(t){}return ci||(ci="10000000100040008000"+1e11),ci.replace(/[018]/g,t=>(t^(Na()&15)>>t/4).toString(16))}function xr(e){var t,i;return(i=(t=e.exception)==null?void 0:t.values)==null?void 0:i[0]}function $(e){let{message:t,event_id:i}=e;if(t)return t;let r=xr(e);return r?r.type&&r.value?`${r.type}: ${r.value}`:r.type||r.value||i||"":i||""}function Ne(e,t,i){let r=e.exception=e.exception||{},n=r.values=r.values||[],s=n[0]=n[0]||{};s.value||(s.value=t||""),s.type||(s.type=i||"Error")}function ie(e,t){let i=xr(e);i&&Rr(i,t)}function wr(e,t){var n,s;let i=(n=e.exception)==null?void 0:n.values,r=(s=i==null?void 0:i.find(a=>{var o;return((o=a.mechanism)==null?void 0:o.exception_id)===0}))!=null?s:i==null?void 0:i[0];r&&Rr(r,t)}function Rr(e,t){let i={type:"generic",handled:!0},r=e.mechanism;e.mechanism={...i,...r,...t},t&&"data"in t&&(e.mechanism.data={...r==null?void 0:r.data,...t.data})}function dt(e){if(Nr(e))return!0;try{P(e,"__sentry_captured__",!0)}catch(t){}return!1}function Nr(e){try{return e.__sentry_captured__}catch(t){}}var Cr=1e3;function Q(){return K()/Cr}function Ca(){let{performance:e}=y;if(!(e!=null&&e.now)||!e.timeOrigin)return Q;let t=e.timeOrigin;return()=>(t+xe(()=>e.now()))/Cr}var mt;function D(){return(mt!=null?mt:mt=Ca())()}function Lr(e){let t=D(),i={sid:I(),init:!0,timestamp:t,started:t,duration:0,status:"ok",errors:0,ignoreDuration:!1,toJSON:()=>La(i)};return e&&re(i,e),i}function re(e,t={}){if(t.user&&(!e.ipAddress&&t.user.ip_address&&(e.ipAddress=t.user.ip_address),!e.did&&!t.did&&(e.did=t.user.id||t.user.email||t.user.username)),e.timestamp=t.timestamp||D(),t.abnormal_mechanism&&(e.abnormal_mechanism=t.abnormal_mechanism),t.ignoreDuration&&(e.ignoreDuration=t.ignoreDuration),t.sid&&(e.sid=t.sid.length===32?t.sid:I()),t.init!==void 0&&(e.init=t.init),!e.did&&t.did&&(e.did=`${t.did}`),typeof t.started=="number"&&(e.started=t.started),e.ignoreDuration)e.duration=void 0;else if(typeof t.duration=="number")e.duration=t.duration;else{let i=e.timestamp-e.started;e.duration=i>=0?i:0}t.release&&(e.release=t.release),t.environment&&(e.environment=t.environment),!e.ipAddress&&t.ipAddress&&(e.ipAddress=t.ipAddress),!e.userAgent&&t.userAgent&&(e.userAgent=t.userAgent),typeof t.errors=="number"&&(e.errors=t.errors),t.status&&(e.status=t.status)}function Pr(e,t){let i={};t?i={status:t}:e.status==="ok"&&(i={status:"exited"}),re(e,i)}function La(e){return{sid:`${e.sid}`,init:e.init,started:new Date(e.started*1e3).toISOString(),timestamp:new Date(e.timestamp*1e3).toISOString(),status:e.status,errors:e.errors,did:typeof e.did=="number"||typeof e.did=="string"?`${e.did}`:void 0,duration:e.duration,abnormal_mechanism:e.abnormal_mechanism,attrs:{release:e.release,environment:e.environment,ip_address:e.ipAddress,user_agent:e.userAgent}}}function oe(e,t,i=2){if(!t||typeof t!="object"||i<=0)return t;if(e&&Object.keys(t).length===0)return e;let r={...e};for(let n in t)Object.prototype.hasOwnProperty.call(t,n)&&(r[n]=oe(r[n],t[n],i-1));return r}function Dr(){return I()}function gt(){return I().substring(16)}var Pa=100,M=class e{constructor(){this._notifyingListeners=!1,this._scopeListeners=[],this._eventProcessors=[],this._breadcrumbs=[],this._attachments=[],this._user={},this._tags={},this._attributes={},this._extra={},this._contexts={},this._sdkProcessingMetadata={},P(this,"refs",{}),this._propagationContext={traceId:Dr(),sampleRand:we()}}clone(){let t=new e;return t._breadcrumbs=[...this._breadcrumbs],t._tags={...this._tags},t._attributes={...this._attributes},t._extra={...this._extra},t._contexts={...this._contexts},this._contexts.flags&&(t._contexts.flags={values:[...this._contexts.flags.values]}),t._user=this._user,t._level=this._level,t._session=this._session,t._transactionName=this._transactionName,t._fingerprint=this._fingerprint,t._eventProcessors=[...this._eventProcessors],t._attachments=[...this._attachments],t._sdkProcessingMetadata={...this._sdkProcessingMetadata},t._propagationContext={...this._propagationContext},t._client=this._client,t._lastEventId=this._lastEventId,t._conversationId=this._conversationId,t.refs={...this.refs},t}setClient(t){this._client=t}setLastEventId(t){this._lastEventId=t}getClient(){return this._client}lastEventId(){return this._lastEventId}addScopeListener(t){this._scopeListeners.push(t)}addEventProcessor(t){return this._eventProcessors.push(t),this}setUser(t){return this._user=t||{email:void 0,id:void 0,ip_address:void 0,username:void 0},this._session&&re(this._session,{user:t}),this._notifyScopeListeners(),this}getUser(){return this._user}setConversationId(t){return this._conversationId=t||void 0,this._notifyScopeListeners(),this}setTags(t){return this._tags={...this._tags,...t},this._notifyScopeListeners(),this}setTag(t,i){return this.setTags({[t]:i})}setAttributes(t){return this._attributes={...this._attributes,...t},this._notifyScopeListeners(),this}setAttribute(t,i){return this.setAttributes({[t]:i})}removeAttribute(t){return t in this._attributes&&(delete this._attributes[t],this._notifyScopeListeners()),this}setExtras(t){return this._extra={...this._extra,...t},this._notifyScopeListeners(),this}setExtra(t,i){return this._extra={...this._extra,[t]:i},this._notifyScopeListeners(),this}setFingerprint(t){return this._fingerprint=t,this._notifyScopeListeners(),this}setLevel(t){return this._level=t,this._notifyScopeListeners(),this}setTransactionName(t){return this._transactionName=t,this._notifyScopeListeners(),this}setContext(t,i){return i===null?delete this._contexts[t]:this._contexts[t]=i,this._notifyScopeListeners(),this}setSession(t){return t?this._session=t:delete this._session,this._notifyScopeListeners(),this}getSession(){return this._session}update(t){if(!t)return this;let i=typeof t=="function"?t(this):t,r=i instanceof e?i.getScopeData():te(i)?t:void 0,{tags:n,attributes:s,extra:a,user:o,contexts:l,level:p,fingerprint:c=[],propagationContext:u,conversationId:g}=r||{};return this._tags={...this._tags,...n},this._attributes={...this._attributes,...s},this._extra={...this._extra,...a},this._contexts={...this._contexts,...l},o&&Object.keys(o).length&&(this._user=o),p&&(this._level=p),c.length&&(this._fingerprint=c),u&&(this._propagationContext=u),g&&(this._conversationId=g),this}addBreadcrumb(t,i){let r=typeof i=="number"?i:Pa;if(r<=0)return this;let n={timestamp:Q(),...t,message:t.message?Ge(t.message,2048):t.message};return this._breadcrumbs.push(n),this._breadcrumbs.length>r&&(this._breadcrumbs=this._breadcrumbs.slice(-r)),this._notifyScopeListeners(),this}getLastBreadcrumb(){return this._breadcrumbs[this._breadcrumbs.length-1]}clearBreadcrumbs(){return this._breadcrumbs=[],this._notifyScopeListeners(),this}addAttachment(t){return this._attachments.push(t),this}clearAttachments(){return this._attachments=[],this}getScopeData(){return{breadcrumbs:this._breadcrumbs,attachments:this._attachments,contexts:this._contexts,tags:this._tags,attributes:this._attributes,extra:this._extra,user:this._user,level:this._level,fingerprint:this._fingerprint||[],eventProcessors:this._eventProcessors,propagationContext:this._propagationContext,sdkProcessingMetadata:this._sdkProcessingMetadata,transactionName:this._transactionName,conversationId:this._conversationId}}setSDKProcessingMetadata(t){return this._sdkProcessingMetadata=oe(this._sdkProcessingMetadata,t,2),this}setPropagationContext(t){return this._propagationContext=t,this}getPropagationContext(){return this._propagationContext}captureException(t,i){let r=(i==null?void 0:i.event_id)||I();if(!this._client)return m&&d.warn("No client configured on scope - will not capture exception!"),r;let n=new Error("Sentry syntheticException");return this._client.captureException(t,{originalException:t,syntheticException:n,...i,event_id:r},this),r}captureMessage(t,i,r){var a;let n=(r==null?void 0:r.event_id)||I();if(!this._client)return m&&d.warn("No client configured on scope - will not capture message!"),n;let s=(a=r==null?void 0:r.syntheticException)!=null?a:new Error(t);return this._client.captureMessage(t,i,{originalException:t,syntheticException:s,...r,event_id:n},this),n}captureEvent(t,i){let r=t.event_id||(i==null?void 0:i.event_id)||I();return this._client?(this._client.captureEvent(t,{...i,event_id:r},this),r):(m&&d.warn("No client configured on scope - will not capture event!"),r)}_notifyScopeListeners(){this._notifyingListeners||(this._notifyingListeners=!0,this._scopeListeners.forEach(t=>{t(this)}),this._notifyingListeners=!1)}};function Mr(){return z("defaultCurrentScope",()=>new M)}function Ur(){return z("defaultIsolationScope",()=>new M)}var ui="__SENTRY_SUPPRESS_TRACING__";var qr=e=>e instanceof Promise&&!e[Br],Br=Symbol("chained PromiseLike"),Fr=(e,t,i)=>{let r=e.then(n=>(t(n),n),n=>{throw i(n),n});return qr(r)&&qr(e)?r:Da(e,r)},Da=(e,t)=>{if(!t)return e;let i=!1;for(let r in e){if(r in t)continue;i=!0;let n=e[r];typeof n=="function"?Object.defineProperty(t,r,{value:(...s)=>n.apply(e,s),enumerable:!0,configurable:!0,writable:!0}):t[r]=n}return i&&Object.assign(t,{[Br]:!0}),t};var di=class{constructor(t,i){let r;t?r=t:r=new M;let n;i?n=i:n=new M,this._stack=[{scope:r}],this._isolationScope=n}withScope(t){let i=this._pushScope(),r;try{r=t(i)}catch(n){throw this._popScope(),n}return F(r)?Fr(r,()=>this._popScope(),()=>this._popScope()):(this._popScope(),r)}getClient(){return this.getStackTop().client}getScope(){return this.getStackTop().scope}getIsolationScope(){return this._isolationScope}getStackTop(){return this._stack[this._stack.length-1]}_pushScope(){let t=this.getScope().clone();return this._stack.push({client:this.getClient(),scope:t}),t}_popScope(){return this._stack.length<=1?!1:!!this._stack.pop()}};function Ce(){let e=j(),t=ee(e);return t.stack=t.stack||new di(Mr(),Ur())}function Hr(e){return Ce().withScope(e)}function Ma(e,t){let i=Ce();return i.withScope(()=>(i.getStackTop().scope=e,t(e)))}function Gr(e){return Ce().withScope(()=>e(Ce().getIsolationScope()))}function zr(){return{suppressTracing:Ua,withIsolationScope:Gr,withScope:Hr,withSetScope:Ma,withSetIsolationScope:(e,t)=>Gr(t),getCurrentScope:()=>Ce().getScope(),getIsolationScope:()=>Ce().getIsolationScope()}}function Ua(e){return Hr(t=>{t.setSDKProcessingMetadata({[ui]:!0});let i=e();return t.setSDKProcessingMetadata({[ui]:void 0}),i})}function Le(e){let t=ee(e);return t.acs?t.acs:zr()}function qa(e){return typeof e=="object"&&e!=null&&!Array.isArray(e)&&Object.keys(e).includes("value")}function Ba(e,t){var o;let{value:i,unit:r}=qa(e)?e:{value:e,unit:void 0},n=Fa(i),s=r&&typeof r=="string"?{unit:r}:{};if(n)return{...n,...s};if(!t||t==="skip-undefined"&&i===void 0)return;let a="";try{a=(o=JSON.stringify(i))!=null?o:""}catch(l){}return{value:a,type:"string",...s}}function mi(e,t=!1){let i={};for(let[r,n]of Object.entries(e!=null?e:{})){let s=Ba(n,t);s&&(i[r]=s)}return i}function Fa(e){if(Array.isArray(e))return{value:e,type:"array"};let t=typeof e=="string"?"string":typeof e=="boolean"?"boolean":typeof e=="number"&&!Number.isNaN(e)?Number.isInteger(e)?"integer":"double":null;if(t)return{value:e,type:t}}function bt(){var e,t;return(t=(e=ee(j())).externalPropagationContextProvider)==null?void 0:t.call(e)}function A(){let e=j();return Le(e).getCurrentScope()}function O(){let e=j();return Le(e).getIsolationScope()}function yt(){return z("globalScope",()=>new M)}function _t(...e){let t=j(),i=Le(t);if(e.length===2){let[r,n]=e;return r?i.withSetScope(r,n):i.withScope(n)}return i.withScope(e[0])}function f(){return A().getClient()}function He(e){let t=bt();if(t)return{trace_id:t.traceId,span_id:t.spanId};let i=e.getPropagationContext(),{traceId:r,parentSpanId:n,propagationSpanId:s}=i,a={trace_id:r,span_id:s||gt()};return n&&(a.parent_span_id=n),a}var Yr="gen_ai.conversation.id";var Vr="http.request.header";var gi="sentry.exclusive_time";var Wr="sentry.is_localhost";var ft="sentry.op",jr="sentry.origin";var bi="sentry.profile_id";var yi="sentry.segment.name.source";var Kr="url.full";var $r="user_agent.original";var _i="sentry.sample_rate",Qr="sentry.previous_trace_sample_rate";var Xr=["ok","deadline_exceeded","unauthenticated","permission_denied","not_found","resource_exhausted","invalid_argument","unimplemented","unavailable","internal_error","unknown_error","cancelled","already_exists","failed_precondition","aborted","out_of_range","data_loss"];var Jr=0,Zr=1;function en(e){return e!=="ok"&&Xr.includes(e)}function ht(e){if(e){if(typeof e=="object"&&"deref"in e&&typeof e.deref=="function")try{return e.deref()}catch(t){return}return e}}var Ha="_sentryScope",za="_sentryIsolationScope";function ze(e){let t=e;return{scope:t[Ha],isolationScope:ht(t[za])}}var tn="sentry-";function nn(e){let t=Ya(e);if(!t)return;let i=Object.entries(t).reduce((r,[n,s])=>{if(n.startsWith(tn)){let a=n.slice(tn.length);r[a]=s}return r},{});if(Object.keys(i).length>0)return i}function Ya(e){if(!(!e||!C(e)&&!Array.isArray(e)))return Array.isArray(e)?e.reduce((t,i)=>{let r=rn(i);return Object.entries(r).forEach(([n,s])=>{t[n]=s}),t},{}):rn(e)}function rn(e){return e.split(",").map(t=>{let i=t.indexOf("=");if(i===-1)return[];let r=t.slice(0,i),n=t.slice(i+1);return[r,n].map(s=>{try{return decodeURIComponent(s.trim())}catch(a){return}})}).reduce((t,[i,r])=>(i&&r&&(t[i]=r),t),{})}var Va=/^o(\d+)\./,Wa=/^(?:(\w+):)\/\/(?:(\w+)(?::(\w+)?)?@)((?:\[[:.%\w]+\]|[\w.-]+))(?::(\d+))?\/(.+)/;function ja(e){return e==="http"||e==="https"}function X(e,t=!1){let{host:i,path:r,pass:n,port:s,projectId:a,protocol:o,publicKey:l}=e;return`${o}://${l}${t&&n?`:${n}`:""}@${i}${s?`:${s}`:""}/${r&&`${r}/`}${a}`}function Ka(e){let t=Wa.exec(e);if(!t){x(()=>{console.error(`Invalid Sentry Dsn: ${e}`)});return}let[i,r,n="",s="",a="",o=""]=t.slice(1),l="",p=o,c=p.split("/");if(c.length>1&&(l=c.slice(0,-1).join("/"),p=c.pop()),p){let u=p.match(/^\d+/);u&&(p=u[0])}return sn({host:s,pass:n,path:l,projectId:p,port:a,protocol:i,publicKey:r})}function sn(e){return{protocol:e.protocol,publicKey:e.publicKey||"",pass:e.pass||"",host:e.host,port:e.port||"",path:e.path||"",projectId:e.projectId}}function $a(e){if(!m)return!0;let{port:t,projectId:i,protocol:r}=e;return["protocol","publicKey","host","projectId"].find(a=>e[a]?!1:(d.error(`Invalid Sentry Dsn: ${a} missing`),!0))?!1:i.match(/^\d+$/)?ja(r)?t&&isNaN(parseInt(t,10))?(d.error(`Invalid Sentry Dsn: Invalid port ${t}`),!1):!0:(d.error(`Invalid Sentry Dsn: Invalid protocol ${r}`),!1):(d.error(`Invalid Sentry Dsn: Invalid projectId ${i}`),!1)}function Qa(e){let t=e.match(Va);return t==null?void 0:t[1]}function an(e){let t=e.getOptions(),{host:i}=e.getDsn()||{},r;return t.orgId?r=String(t.orgId):i&&(r=Qa(i)),r}function on(e){let t=typeof e=="string"?Ka(e):sn(e);if(!(!t||!$a(t)))return t}function ln(e){if(typeof e=="boolean")return Number(e);let t=typeof e=="string"?parseFloat(e):e;if(!(typeof t!="number"||isNaN(t)||t<0||t>1))return t}var Xa="span";function pn(e){return ht(e.refs[Xa])}var dn=1;function mn(e){let{spanId:t,traceId:i,isRemote:r}=e.spanContext(),n=r?t:le(e).parent_span_id,s=ze(e).scope,a=r?(s==null?void 0:s.getPropagationContext().propagationSpanId)||gt():t;return{parent_span_id:n,span_id:a,trace_id:i}}function Ja(e){if(e&&e.length>0)return e.map(({context:{spanId:t,traceId:i,traceFlags:r,...n},attributes:s})=>({span_id:t,trace_id:i,sampled:r===dn,attributes:s,...n}))}function cn(e){return typeof e=="number"?un(e):Array.isArray(e)?e[0]+e[1]/1e9:e instanceof Date?un(e.getTime()):D()}function un(e){return e>9999999999?e/1e3:e}function le(e){if(to(e))return e.getStaticSpanJSON();let{spanId:t,traceId:i}=e.spanContext();if(eo(e)){let{attributes:r,startTime:n,name:s,endTime:a,status:o,links:l}=e;return{span_id:t,trace_id:i,data:r,description:s,parent_span_id:Za(e),start_timestamp:cn(n),timestamp:cn(a)||void 0,status:io(o),op:r[ft],origin:r[jr],links:Ja(l)}}return{span_id:t,trace_id:i,start_timestamp:0,status:"ok",data:{}}}function Za(e){var t;return"parentSpanId"in e?e.parentSpanId:"parentSpanContext"in e?(t=e.parentSpanContext)==null?void 0:t.spanId:void 0}function gn(e){var t,i;return{...e,end_timestamp:(t=e.end_timestamp)!=null?t:e.start_timestamp,attributes:mi(e.attributes),links:(i=e.links)==null?void 0:i.map(r=>({...r,attributes:mi(r.attributes)}))}}function eo(e){let t=e;return!!t.attributes&&!!t.startTime&&!!t.name&&!!t.endTime&&!!t.status}function to(e){return typeof e.getSpanJSON=="function"}function bn(e){let{traceFlags:t}=e.spanContext();return t===dn}function io(e){return!e||e.code===Jr||e.code===Zr?"ok":e.message&&en(e.message)?e.message:"internal_error"}var ro="_sentryRootSpan";var Ye=no;function no(e){return e[ro]||e}function fi(e){let t=j(),i=Le(t);return i.getActiveSpan?i.getActiveSpan(e):pn(e||A())}function hi(e){var i;if(typeof __SENTRY_TRACING__=="boolean"&&!__SENTRY_TRACING__)return!1;let t=e||((i=f())==null?void 0:i.getOptions());return!!t&&(t.tracesSampleRate!=null||!!t.tracesSampler)}function yn(e){d.log(`Ignoring span ${e.op} - ${e.description} because it matches \`ignoreSpans\`.`)}function vi(e,t){if(!(t!=null&&t.length))return!1;for(let i of t){if(ao(i)){if(e.description&&Re(e.description,i))return m&&yn(e),!0;continue}let r=!!i.attributes&&Object.keys(i.attributes).length>0;if(!i.name&&!i.op&&!r)continue;let n=i.name?e.description&&Re(e.description,i.name):!0,s=i.op?e.op&&Re(e.op,i.op):!0,a=i.attributes?Object.entries(i.attributes).every(([o,l])=>{var p;return so((p=e.attributes)==null?void 0:p[o],l)}):!0;if(n&&s&&a)return m&&yn(e),!0}return!1}function so(e,t){return typeof e=="string"&&(typeof t=="string"||t instanceof RegExp)?Re(e,t):Array.isArray(e)&&Array.isArray(t)?e.length===t.length&&e.every((i,r)=>i===t[r]):e===t}function _n(e,t){let i=t.parent_span_id,r=t.span_id;if(i)for(let n of e)n.parent_span_id===r&&(n.parent_span_id=i)}function ao(e){return typeof e=="string"||e instanceof RegExp}var Si=[];function oo(e){let t={};return e.forEach(i=>{let{name:r}=i,n=t[r];n&&!n.isDefaultInstance&&i.isDefaultInstance||(t[r]=i)}),Object.values(t)}function Ei(e){let t=e.defaultIntegrations||[],i=e.integrations;t.forEach(n=>{n.isDefaultInstance=!0});let r;if(Array.isArray(i))r=[...t,...i];else if(typeof i=="function"){let n=i(t);r=Array.isArray(n)?n:[n]}else r=t;return oo(r)}function fn(e,t){let i={};return t.forEach(r=>{r!=null&&r.beforeSetup&&r.beforeSetup(e)}),t.forEach(r=>{r&&Ti(e,r,i)}),i}function ki(e,t){for(let i of t)i!=null&&i.afterAllSetup&&i.afterAllSetup(e)}function Ti(e,t,i){if(i[t.name]){m&&d.log(`Integration skipped because it was already installed: ${t.name}`);return}if(i[t.name]=t,!Si.includes(t.name)&&typeof t.setupOnce=="function"&&(t.setupOnce(),Si.push(t.name)),t.setup&&typeof t.setup=="function"&&t.setup(e),typeof t.preprocessEvent=="function"){let r=t.preprocessEvent.bind(t);e.on("preprocessEvent",(n,s)=>r(n,s,e))}if(typeof t.processEvent=="function"){let r=t.processEvent.bind(t),n=Object.assign((s,a)=>r(s,a,e),{id:t.name});e.addEventProcessor(n)}["processSpan","processSegmentSpan"].forEach(r=>{let n=t[r];typeof n=="function"&&e.on(r,s=>n.call(t,s,e))}),m&&d.log(`Integration installed: ${t.name}`)}var Pe="production";var lo=Symbol.for("sentry.nonRecordingSpan");function hn(e){return!!e&&e[lo]===!0}var po="_frozenDsc";function vn(e,t){let i=t.getOptions(),{publicKey:r}=t.getDsn()||{},n={environment:i.environment||Pe,release:i.release,public_key:r,trace_id:e,org_id:an(t)};return t.emit("createDsc",n),n}function Ii(e,t){if(bt())return;let i=t.getPropagationContext();return i.dsc||vn(i.traceId,e)}function Sn(e){var H,U,S,Te;let t=f();if(!t)return{};let i=Ye(e),r=le(i),n=r.data,s=i.spanContext().traceState,a=(U=(H=s==null?void 0:s.get("sentry.sample_rate"))!=null?H:n[_i])!=null?U:n[Qr];function o(se){return(typeof a=="number"||typeof a=="string")&&(se.sample_rate=`${a}`),se}let l=i[po];if(l)return o(l);let p=hn(i),c=p&&i.dropReason==="ignored";if(p&&(!hi(t.getOptions())||c)){let se=ze(i).scope,yr=se&&Ii(t,se);if(yr){let _r={...yr};return c&&(_r.sampled="false"),o(_r)}}let u=s==null?void 0:s.get("sentry.dsc"),g=u&&nn(u);if(g)return o(g);let _=vn(e.spanContext().traceId,t),b=n[yi],E=r.description;return b!=="url"&&E&&(_.transaction=E),hi()&&(_.sampled=String(bn(i)),_.sample_rand=(Te=s==null?void 0:s.get("sentry.sample_rand"))!=null?Te:(S=ze(i).scope)==null?void 0:S.getPropagationContext().sampleRand.toString()),o(_),t.emit("createDsc",_,i),_}function kn(e,t){let{fingerprint:i,breadcrumbs:r,sdkProcessingMetadata:n}=t;co(e,t),go(e,i),uo(e,r),mo(e,n)}function En(e,t){let{extra:i,tags:r,attributes:n,user:s,contexts:a,level:o,sdkProcessingMetadata:l,breadcrumbs:p,fingerprint:c,eventProcessors:u,attachments:g,propagationContext:_,transactionName:b}=t;Ve(e,"extra",i),Ve(e,"tags",r),Ve(e,"attributes",n),Ve(e,"user",s),Ve(e,"contexts",a),e.sdkProcessingMetadata=oe(e.sdkProcessingMetadata,l,2),o&&(e.level=o),b&&(e.transactionName=b),p.length&&(e.breadcrumbs=[...e.breadcrumbs,...p]),c.length&&(e.fingerprint=[...e.fingerprint,...c]),u.length&&(e.eventProcessors=[...e.eventProcessors,...u]),g.length&&(e.attachments=[...e.attachments,...g]),e.propagationContext={...e.propagationContext,..._}}function Ve(e,t,i){e[t]=oe(e[t],i,1)}function vt(e,t){let i=yt().getScopeData();return e&&En(i,e.getScopeData()),t&&En(i,t.getScopeData()),i}function co(e,t){let{extra:i,tags:r,user:n,contexts:s,level:a,transactionName:o}=t;Object.keys(i).length&&(e.extra={...i,...e.extra}),Object.keys(r).length&&(e.tags={...r,...e.tags}),Object.keys(n).length&&(e.user={...n,...e.user}),Object.keys(s).length&&(e.contexts={...s,...e.contexts}),a&&(e.level=a),o&&e.type!=="transaction"&&(e.transaction=o)}function uo(e,t){let i=[...e.breadcrumbs||[],...t];e.breadcrumbs=i.length?i:void 0}function mo(e,t){e.sdkProcessingMetadata={...e.sdkProcessingMetadata,...t}}function Tn(e,t){e.contexts={trace:mn(t),...e.contexts},e.sdkProcessingMetadata={dynamicSamplingContext:Sn(t),...e.sdkProcessingMetadata};let i=Ye(t),r=le(i).description;r&&!e.transaction&&e.type==="transaction"&&(e.transaction=r)}function go(e,t){e.fingerprint=e.fingerprint?Array.isArray(e.fingerprint)?e.fingerprint:[e.fingerprint]:[],t&&(e.fingerprint=e.fingerprint.concat(t)),e.fingerprint.length||delete e.fingerprint}function J(e,t,i){let r;try{r=t()}catch(n){return In(e,n,i)}return F(r)?r.then(void 0,n=>In(e,n,i)):r}function In(e,t,i){return m&&d.error(e,t),i(t)}function Ai(e){return!!e&&typeof e=="function"&&"_static"in e&&!!e._static}function Oi(e,t){return J(m?"The `beforeSendSpan` callback threw an error, sending the span unmodified:":"",()=>t(e),()=>e)||e}function An(e){let t={trace_id:e.trace_id,span_id:e.span_id,parent_span_id:e.parent_span_id,name:e.description||"",start_timestamp:e.start_timestamp,end_timestamp:e.timestamp,status:!e.status||e.status==="ok"||e.status==="cancelled"?"ok":"error",is_segment:!1,attributes:{...e.data},links:e.links};return gn(t)}function We(e,t){var r;let i=(r=e.attributes)!=null?r:e.attributes={};Object.entries(t).forEach(([n,s])=>{s!=null&&!(n in i)&&(i[n]=s)})}function On(e){return e.getOptions().traceLifecycle==="stream"}function St(e){return typeof e=="object"&&typeof e.unref=="function"&&e.unref(),e}function Y(e,t=[]){return[e,t]}function wi(e,t){let[i,r]=e;return[i,[...r,t]]}function Et(e,t){let i=e[1];for(let r of i){let n=r[0].type;if(t(r,n))return!0}return!1}function xn(e,t){return Et(e,(i,r)=>t.includes(r))}function xi(e){let t=ee(y);return t.encodePolyfill?t.encodePolyfill(e):new TextEncoder().encode(e)}function wn(e){let[t,i]=e,r=JSON.stringify(t);function n(s){typeof r=="string"?r=typeof s=="string"?r+s:[xi(r),s]:r.push(typeof s=="string"?xi(s):s)}for(let s of i){let[a,o]=s;if(n(` +${JSON.stringify(a)} +`),typeof o=="string"||o instanceof Uint8Array)n(o);else{let l;try{l=JSON.stringify(o)}catch(p){l=JSON.stringify(G(o))}n(l)}}return typeof r=="string"?r:bo(r)}function bo(e){let t=e.reduce((n,s)=>n+s.length,0),i=new Uint8Array(t),r=0;for(let n of e)i.set(n,r),r+=n.length;return i}function Rn(e){let t=typeof e.data=="string"?xi(e.data):e.data;return[{type:"attachment",length:t.length,filename:e.filename,content_type:e.contentType,attachment_type:e.attachmentType},t]}var Nn={sessions:"session",event:"error",client_report:"internal",user_report:"default",profile_chunk:"profile",replay_event:"replay",replay_recording:"replay",check_in:"monitor",raw_security:"security",log:"log_item",trace_metric:"metric"};function yo(e){return e in Nn}function Ri(e){return yo(e)?Nn[e]:e}function Ni(e){if(!(e!=null&&e.sdk))return;let{name:t,version:i}=e.sdk;return{name:t,version:i}}function Cn(e,t,i,r){var s;let n=(s=e.sdkProcessingMetadata)==null?void 0:s.dynamicSamplingContext;return{event_id:e.event_id,sent_at:new Date(K()).toISOString(),...t&&{sdk:t},...!!i&&r&&{dsn:X(r)},...n&&{trace:n}}}function je(e){return e==="replay_event"?"replay":e||"error"}function Ci(){return typeof __SENTRY_BROWSER_BUNDLE__!="undefined"&&!!__SENTRY_BROWSER_BUNDLE__}function Li(){return"npm"}function Ln(){return!Ci()&&Object.prototype.toString.call(typeof process!="undefined"?process:0)==="[object process]"}function pe(){return typeof window!="undefined"&&(!Ln()||_o())}function _o(){let e=y.process;return(e==null?void 0:e.type)==="renderer"}function Pn(e,t){let i=t.getDataCollectionOptions().userInfo?"auto":"never";return[{type:"span",item_count:e.length,content_type:"application/vnd.sentry.items.span.v2+json"},{version:2,ingest_settings:pe()?{infer_ip:i,infer_user_agent:i}:void 0,items:e}]}var fo=new WeakMap;function ho(e){return B(e)?void 0:e}function Dn(e,t,i){var o,l;let r=ho(t.originalException),n=r&&fo.get(r);if(!n)return;let s=(o=e.contexts)==null?void 0:o.trace;((l=s==null?void 0:s.trace_id)!=null?l:i&&He(i).trace_id)===n.trace_id&&(e.contexts={...e.contexts,trace:{...s,...n}})}function vo(e,t){var r,n,s,a;if(!t)return e;let i=e.sdk||{};return e.sdk={...i,name:i.name||t.name,version:i.version||t.version,integrations:[...((r=e.sdk)==null?void 0:r.integrations)||[],...t.integrations||[]],packages:[...((n=e.sdk)==null?void 0:n.packages)||[],...t.packages||[]],settings:(s=e.sdk)!=null&&s.settings||t.settings?{...(a=e.sdk)==null?void 0:a.settings,...t.settings}:void 0},e}function Mn(e,t,i,r){let n=Ni(i),s={sent_at:new Date(K()).toISOString(),...n&&{sdk:n},...!!r&&t&&{dsn:X(t)}},a="aggregates"in e?[{type:"sessions"},e]:[{type:"session"},e.toJSON()];return Y(s,[a])}function Un(e,t,i,r){let n=Ni(i),s=e.type&&e.type!=="replay_event"?e.type:"event";vo(e,i==null?void 0:i.sdk);let a=Cn(e,n,r,t);return delete e.sdkProcessingMetadata,Y(a,[[{type:s},e]])}var Pi=0,qn=1,Bn=2;function Z(e){return new Ke(t=>{t(e)})}function $e(e){return new Ke((t,i)=>{i(e)})}var Ke=class e{constructor(t){this._state=Pi,this._handlers=[],this._runExecutor(t)}then(t,i){return new e((r,n)=>{this._handlers.push([!1,s=>{if(!t)r(s);else try{r(t(s))}catch(a){n(a)}},s=>{if(!i)n(s);else try{r(i(s))}catch(a){n(a)}}]),this._executeHandlers()})}catch(t){return this.then(i=>i,t)}finally(t){return new e((i,r)=>{let n,s;return this.then(a=>{s=!1,n=a,t&&t()},a=>{s=!0,n=a,t&&t()}).then(()=>{if(s){r(n);return}i(n)})})}_executeHandlers(){if(this._state===Pi)return;let t=this._handlers.slice();this._handlers=[],t.forEach(i=>{i[0]||(this._state===qn&&i[1](this._value),this._state===Bn&&i[2](this._value),i[0]=!0)})}_runExecutor(t){let i=(s,a)=>{if(this._state===Pi){if(F(a)){a.then(r,n);return}this._state=s,this._value=a,this._executeHandlers()}},r=s=>{i(qn,s)},n=s=>{i(Bn,s)};try{t(r,n)}catch(s){n(s)}}};function Fn(e,t,i,r=0,n){try{let s=Di(t,i,e,r,n);return F(s)?s:Z(s)}catch(s){return $e(s)}}function Di(e,t,i,r,n){let s=i[r];if(!e||!s)return e;let a=`Event processor "${s.id||"?"}"`,o=!1,l=J(m?`${a} threw an error, dropping event:`:"",()=>s({...e},t),()=>(o=!0,null));return m&&l===null&&d.log(`${a} dropped event`),F(l)?l.then(p=>p?Di(p,t,i,r+1,n):(n==null||n(o?"callback_error":"event_processor"),null)):l?Di(l,t,i,r+1,n):(n==null||n(o?"callback_error":"event_processor"),null)}var ne,Gn,Hn,ce;function zn(e){let t=y._sentryDebugIds,i=y._debugIds;if(!t&&!i)return{};let r=t?Object.keys(t):[],n=i?Object.keys(i):[];if(ce&&r.length===Gn&&n.length===Hn)return ce;Gn=r.length,Hn=n.length,ce={},ne||(ne={});let s=(a,o)=>{for(let l of a){let p=o[l],c=ne==null?void 0:ne[l];if(c&&ce&&p)ce[c[0]]=p,ne&&(ne[l]=[c[0],p]);else if(p){let u=e(l);for(let g=u.length-1;g>=0;g--){let _=u[g],b=_==null?void 0:_.filename;if(b&&ce&&ne){ce[b]=p,ne[l]=[b,p];break}}}}};return t&&s(r,t),i&&s(n,i),ce}function Yn(e,t,i,r,n,s){let{normalizeDepth:a=3,normalizeMaxBreadth:o=1e3}=e,l={...t,event_id:t.event_id||i.event_id||I(),timestamp:t.timestamp||Q()},p=i.integrations||e.integrations.map(S=>S.name);So(l,e),To(l,p),n&&n.emit("applyFrameMetadata",t),t.type===void 0&&Eo(l,e.stackParser);let c=Ao(r,i.captureContext);i.mechanism&&wr(l,i.mechanism);let u=n?n.getEventProcessors():[],g=vt(s,c),_=[...i.attachments||[],...g.attachments];_.length&&(i.attachments=_),kn(l,g);let b=fi(c);b&&Tn(l,b),Dn(l,i,c);let E=[...u,...g.eventProcessors];return(i.data&&i.data.__sentry__===!0?Z(l):Fn(E,l,i,0,S=>{n&&(n.recordDroppedEvent(S,je(t.type)),t.type==="transaction"&&n.recordDroppedEvent(S,"span",1+(t.spans||[]).length))})).then(S=>S?(ko(S),typeof a=="number"&&a>0?Io(S,a,o):S):null)}function So(e,t){var o,l;let{environment:i,release:r,dist:n,maxValueLength:s}=t;e.environment=e.environment||i||Pe,!e.release&&r&&(e.release=r),!e.dist&&n&&(e.dist=n);let a=e.request;a!=null&&a.url&&s&&(a.url=Ge(a.url,s)),s&&((l=(o=e.exception)==null?void 0:o.values)==null||l.forEach(p=>{p.value&&(p.value=Ge(p.value,s))}))}function Eo(e,t){var r,n;let i=zn(t);(n=(r=e.exception)==null?void 0:r.values)==null||n.forEach(s=>{var a,o;(o=(a=s.stacktrace)==null?void 0:a.frames)==null||o.forEach(l=>{l.filename&&(l.debug_id=i[l.filename])})})}function ko(e){var r,n;let t={};if((n=(r=e.exception)==null?void 0:r.values)==null||n.forEach(s=>{var a,o;(o=(a=s.stacktrace)==null?void 0:a.frames)==null||o.forEach(l=>{l.debug_id&&(l.abs_path?t[l.abs_path]=l.debug_id:l.filename&&(t[l.filename]=l.debug_id),delete l.debug_id)})}),Object.keys(t).length===0)return;e.debug_meta=e.debug_meta||{},e.debug_meta.images=e.debug_meta.images||[];let i=e.debug_meta.images;Object.entries(t).forEach(([s,a])=>{i.push({type:"sourcemap",code_file:s,debug_id:a})})}function To(e,t){t.length>0&&(e.sdk=e.sdk||{},e.sdk.integrations=[...e.sdk.integrations||[],...t])}function Io(e,t,i){var n,s;if(!e)return null;let r={...e,...e.breadcrumbs&&{breadcrumbs:e.breadcrumbs.map(a=>({...a,...a.data&&{data:G(a.data,t,i)}}))},...e.user&&{user:G(e.user,t,i)},...e.contexts&&{contexts:G(e.contexts,t,i)},...e.extra&&{extra:G(e.extra,t,i)}};return(n=e.contexts)!=null&&n.trace&&r.contexts&&(r.contexts.trace=e.contexts.trace,e.contexts.trace.data&&(r.contexts.trace.data=G(e.contexts.trace.data,t,i))),e.spans&&(r.spans=e.spans.map(a=>({...a,...a.data&&{data:G(a.data,t,i)}}))),(s=e.contexts)!=null&&s.flags&&r.contexts&&(r.contexts.flags=G(e.contexts.flags,3,i)),r}function Ao(e,t){if(!t)return e;let i=e?e.clone():new M;return i.update(t),i}function Vn(e){if(e)return Oo(e)?{captureContext:e}:wo(e)?{captureContext:e}:e}function Oo(e){return e instanceof M||typeof e=="function"}var xo=["user","level","extra","contexts","tags","fingerprint","propagationContext"];function wo(e){return Object.keys(e).some(t=>xo.includes(t))}function De(e,t){return A().captureException(e,Vn(t))}function Qe(e,t){return A().captureEvent(e,t)}function kt(e,t){O().setTag(e,t)}function Tt(e){O().setUser(e)}function Xe(e){let t=O(),{user:i}=vt(t,A()),{userAgent:r}=y.navigator||{},n=Lr({user:i,...r&&{userAgent:r},...e}),s=t.getSession();return(s==null?void 0:s.status)==="ok"&&re(s,{status:"exited"}),It(),t.setSession(n),n}function It(){let e=O(),i=A().getSession()||e.getSession();i&&Pr(i),Wn(),e.setSession()}function Wn(){let e=O(),t=f(),i=e.getSession();i&&t&&t.captureSession(i)}function Me(e=!1){if(e){It();return}Wn()}var Ro="7";function No(e){let t=e.protocol?`${e.protocol}:`:"",i=e.port?`:${e.port}`:"";return`${t}//${e.host}${i}${e.path?`/${e.path}`:""}/api/`}function Co(e){return`${No(e)}${e.projectId}/envelope/`}function Lo(e,t){let i={sentry_version:Ro};return e.publicKey&&(i.sentry_key=e.publicKey),t&&(i.sentry_client=`${t.name}/${t.version}`),new URLSearchParams(i).toString()}function jn(e,t,i){return t||`${Co(e)}?${Lo(e,i)}`}function Po(e,t){let i=t?"auto":"never";return[{type:"log",item_count:e.length,content_type:"application/vnd.sentry.items.log+json"},{version:2,...pe()&&{ingest_settings:{infer_ip:i,infer_user_agent:i}},items:e}]}function Kn(e,t,i,r,n){let s={};return t!=null&&t.sdk&&(s.sdk={name:t.sdk.name,version:t.sdk.version}),i&&r&&(s.dsn=X(r)),Y(s,[Po(e,n)])}function $n(e,t){var s;let i=(s=t!=null?t:Do(e))!=null?s:[];if(i.length===0)return;let r=e.getOptions(),n=Kn(i,r._metadata,r.tunnel,e.getDsn(),e.getDataCollectionOptions().userInfo);Qn().set(e,[]),e.emit("flushLogs"),e.sendEnvelope(n)}function Do(e){return Qn().get(e)}function Qn(){return z("clientToLogBufferMap",()=>new WeakMap)}function Mo(e,t){let i=t?"auto":"never";return[{type:"trace_metric",item_count:e.length,content_type:"application/vnd.sentry.items.trace-metric+json"},{version:2,...pe()&&{ingest_settings:{infer_ip:i,infer_user_agent:i}},items:e}]}function Xn(e,t,i,r,n){let s={};return t!=null&&t.sdk&&(s.sdk={name:t.sdk.name,version:t.sdk.version}),i&&r&&(s.dsn=X(r)),Y(s,[Mo(e,n)])}function Jn(e,t){var s;let i=(s=t!=null?t:Uo(e))!=null?s:[];if(i.length===0)return;let r=e.getOptions(),n=Xn(i,r._metadata,r.tunnel,e.getDsn(),e.getDataCollectionOptions().userInfo);Zn().set(e,[]),e.emit("flushMetrics"),e.sendEnvelope(n)}function Uo(e){return Zn().get(e)}function Zn(){return z("clientToMetricBufferMap",()=>new WeakMap)}function es(e,t){var n,s,a;if(e.type!=="transaction"||!((n=e.spans)!=null&&n.length)||!((s=e.sdkProcessingMetadata)!=null&&s.hasGenAiSpans)||On(t))return;let i=[],r=[];for(let o of e.spans)(a=o.op)!=null&&a.startsWith("gen_ai.")?i.push(An(o)):r.push(o);if(i.length!==0)return e.spans=r,Pn(i,t)}var Ue=Symbol.for("SentryBufferFullError");function fe(e=100){let t=new Set;function i(){return t.sizer(o),()=>r(o)),o}function s(a){if(!t.size)return Z(!0);let o=Promise.allSettled(Array.from(t)).then(()=>!0);if(!a)return o;let l=[o,new Promise(p=>St(setTimeout(()=>p(!1),a)))];return Promise.race(l)}return{get $(){return Array.from(t)},add:n,drain:s}}var qo=60*1e3;function Bo(e,t=K()){let i=parseInt(`${e}`,10);if(!isNaN(i))return i*1e3;let r=Date.parse(`${e}`);return isNaN(r)?qo:r-t}function Fo(e,t){return e[t]||e.all||0}function ts(e,t,i=K()){return Fo(e,t)>i}function is(e,{statusCode:t,headers:i},r=K()){let n={...e},s=i==null?void 0:i["x-sentry-rate-limits"],a=i==null?void 0:i["retry-after"];if(s)for(let o of s.trim().split(",")){let[l,p,,,c]=o.split(":",5),u=parseInt(l,10),g=(isNaN(u)?60:u)*1e3;if(!p)n.all=r+g;else for(let _ of p.split(";"))_==="metric_bucket"?(!c||c.split(";").includes("custom"))&&(n[_]=r+g):n[_]=r+g}else a?n.all=r+Bo(a,r):t===429&&(n.all=r+60*1e3);return n}var Mi=64;function At(e,t,i=fe(e.bufferSize||Mi)){let r={},n=a=>i.drain(a);function s(a){let o=[];if(Et(a,(u,g)=>{let _=Ri(g);ts(r,_)?e.recordDroppedEvent("ratelimit_backoff",_):o.push(u)}),o.length===0)return Promise.resolve({});let l=Y(a[0],o),p=u=>{if(xn(l,["client_report"])){m&&d.warn(`Dropping client report. Will not send outcomes (reason: ${u}).`);return}Et(l,(g,_)=>{e.recordDroppedEvent(u,Ri(_))})},c=()=>t({body:wn(l)}).then(u=>u.statusCode===413?(m&&d.error("Sentry responded with status code 413. Envelope was discarded due to exceeding size limits."),p("send_error"),u):(m&&u.statusCode!==void 0&&(u.statusCode<200||u.statusCode>=300)&&d.warn(`Sentry responded with status code ${u.statusCode} to sent event.`),r=is(r,u),u),u=>{throw p("network_error"),m&&d.error("Encountered error running transport request:",u),u});return i.add(c).then(u=>u,u=>{if(u===Ue)return m&&d.error("Skipped sending event because buffer is full."),p("queue_overflow"),Promise.resolve({});throw u})}return{send:s,flush:n}}function rs(e,t,i){let r=[{type:"client_report"},{timestamp:i||Q(),discarded_events:e}];return Y(t?{dsn:t}:{},[r])}function Ot(e){let t=[];e.message&&t.push(e.message);try{let i=e.exception.values[e.exception.values.length-1];i!=null&&i.value&&(t.push(i.value),i.type&&t.push(`${i.type}: ${i.value}`))}catch(i){}return t}function ns(e){var l,p,c;let{trace_id:t,parent_span_id:i,span_id:r,status:n,origin:s,data:a,op:o}=(p=(l=e.contexts)==null?void 0:l.trace)!=null?p:{};return{data:a!=null?a:{},description:e.transaction,op:o,parent_span_id:i,span_id:r!=null?r:"",start_timestamp:(c=e.start_timestamp)!=null?c:0,status:n!=null?n:"ok",timestamp:e.timestamp,trace_id:t!=null?t:"",origin:s,profile_id:a==null?void 0:a[bi],exclusive_time:a==null?void 0:a[gi],measurements:e.measurements,is_segment:!0}}function ss(e){return{type:"transaction",timestamp:e.timestamp,start_timestamp:e.start_timestamp,transaction:e.description,contexts:{trace:{trace_id:e.trace_id,span_id:e.span_id,parent_span_id:e.parent_span_id,op:e.op,status:e.status,origin:e.origin,data:{...e.data,...e.profile_id&&{[bi]:e.profile_id},...e.exclusive_time&&{[gi]:e.exclusive_time}}}},measurements:e.measurements}}function as(e){var t;e.traceLifecycle!=="stream"||!(e.beforeSendTransaction||(t=e.ignoreTransactions)!=null&&t.length)||x(()=>{console.warn("[Sentry] `beforeSendTransaction` and `ignoreTransactions` are ignored with `traceLifecycle: 'stream'` (enabled by default). Use `beforeSendSpan` and `ignoreSpans` instead, or set `traceLifecycle: 'static'`.")})}var L={userInfo:!0,cookies:!0,httpHeaders:{request:!0,response:!0},httpBodies:["incomingRequest","outgoingRequest","incomingResponse","outgoingResponse"],urlQueryParams:!0,graphQL:{document:!0,variables:!0},genAI:{inputs:!0,outputs:!0},databaseQueryData:!0,queues:!0,stackFrameVariables:!0,frameContextLines:5};function Go(e){return typeof e=="boolean"||"allow"in e||"deny"in e}function Ho(e){var t,i;return e===void 0?{...L.httpHeaders}:Go(e)?{request:e,response:e}:{request:(t=e.request)!=null?t:L.httpHeaders.request,response:(i=e.response)!=null?i:L.httpHeaders.response}}function os(e){var i,r,n,s,a,o,l,p,c,u,g,_,b,E,H,U,S;let t=(i=e.dataCollection)!=null?i:{};return{userInfo:(r=t.userInfo)!=null?r:L.userInfo,cookies:(n=t.cookies)!=null?n:L.cookies,httpHeaders:Ho(t.httpHeaders),httpBodies:(s=t.httpBodies)!=null?s:L.httpBodies,urlQueryParams:(a=t.urlQueryParams)!=null?a:L.urlQueryParams,graphQL:{document:(l=(o=t.graphQL)==null?void 0:o.document)!=null?l:L.graphQL.document,variables:(c=(p=t.graphQL)==null?void 0:p.variables)!=null?c:L.graphQL.variables},genAI:{inputs:(g=(u=t.genAI)==null?void 0:u.inputs)!=null?g:L.genAI.inputs,outputs:(b=(_=t.genAI)==null?void 0:_.outputs)!=null?b:L.genAI.outputs},databaseQueryData:(E=t.databaseQueryData)!=null?E:L.databaseQueryData,queues:(H=t.queues)!=null?H:L.queues,stackFrameVariables:(U=t.stackFrameVariables)!=null?U:L.stackFrameVariables,frameContextLines:(S=t.frameContextLines)!=null?S:L.frameContextLines}}var ls="Not capturing exception because it's already been captured.",ps="Discarded session because of missing or non-string release",gs=Symbol.for("SentryInternalError"),bs=Symbol.for("SentryDoNotSendEventError"),zo=5e3,ys=Symbol.for("SentryClosedClient");function Bi(e){return ys in e}function xt(e){return{message:e,[gs]:!0}}function Ui(e){return{message:e,[bs]:!0}}function cs(e){return ae(e)&&gs in e}function us(e){return ae(e)&&bs in e}function ds(e,t,i,r,n){let s=0,a,o=!1;e.on(i,()=>{s=0,clearTimeout(a),o=!1}),e.on(t,l=>{var p;if(s+=r(l),s>=8e5)n(e);else if(!o){let c=(p=e.getOptions()._flushInterval)!=null?p:zo;c>0&&(o=!0,a=St(setTimeout(()=>{n(e)},c)))}}),e.on("flush",()=>{n(e)})}var Je=class{constructor(t){var n,s;this._options={attachStacktrace:!0,...t,traceLifecycle:t.traceLifecycle==="static"?"static":"stream"},this._integrations={},this._numProcessing=0,this._outcomes={},this._hooks={},this._eventProcessors=[],this._promiseBuffer=fe((s=(n=t.transportOptions)==null?void 0:n.bufferSize)!=null?s:Mi),this._dataCollection=os(t),this._unhandledSessionStatus="crashed",t.dsn?this._dsn=on(t.dsn):m&&d.warn("No DSN provided, client will not send events.");let{beforeSendSpan:i,traceLifecycle:r}=this._options;if(m&&i&&Ai(i)!==(r==="static")&&x(()=>{console.warn(`Ignoring \`beforeSendSpan\`: ${r==="static"?"wrap it with":"remove"} \`Sentry.withStaticSpan\` to use it with \`traceLifecycle: "${r}"\`.`)}),this._dsn){let a=jn(this._dsn,t.tunnel,t._metadata?t._metadata.sdk:void 0);this._transport=t.transport({tunnel:this._options.tunnel,recordDroppedEvent:this.recordDroppedEvent.bind(this),...t.transportOptions,url:a})}ds(this,"afterCaptureLog","flushLogs",jo,$n),ds(this,"afterCaptureMetric","flushMetrics",Wo,Jn)}captureException(t,i,r){let n=I();if(dt(t))return m&&d.log(ls),n;let s={event_id:n,...i};return this._process(()=>this.eventFromException(t,s).then(a=>this._captureEvent(a,s,r)).then(a=>a),"error"),s.event_id}captureMessage(t,i,r,n){let s={event_id:I(),...r},a=ge(t)?t:String(t),o=B(t),l=o?this.eventFromMessage(a,i,s):this.eventFromException(t,s);return this._process(()=>l.then(p=>this._captureEvent(p,s,n)),o?"unknown":"error"),s.event_id}captureEvent(t,i,r){let n=I();if(i!=null&&i.originalException&&dt(i.originalException))return m&&d.log(ls),n;let s={event_id:n,...i},a=t.sdkProcessingMetadata||{},o=a.capturedSpanScope,l=a.capturedSpanIsolationScope,p=je(t.type);return this._process(()=>this._captureEvent(t,s,o||r,l),p),s.event_id}captureSession(t){this.sendSession(t),re(t,{init:!1})}getDsn(){return this._dsn}getOptions(){return this._options}getDataCollectionOptions(){return this._dataCollection}getSdkMetadata(){return this._options._metadata}getTransport(){return this._transport}async flush(t){let i=this._transport;if(this.emit("flush"),!i)return!0;let r=await this._isClientDoneProcessing(t),n=await i.flush(t);return r&&n}async close(t){Object.defineProperty(this,ys,{value:!0});let i=await this.flush(t);return this.getOptions().enabled=!1,this.emit("close"),i}getEventProcessors(){return this._eventProcessors}addEventProcessor(t){this._eventProcessors.push(t)}init(){(this._isEnabled()||this._options.integrations.some(({name:t})=>t.startsWith("Spotlight")))&&(this._setupIntegrations(),as(this._options))}getIntegrationByName(t){return this._integrations[t]}getIntegrationNames(){return Object.keys(this._integrations)}addIntegration(t){let i=this._integrations[t.name];!i&&t.beforeSetup&&t.beforeSetup(this),Ti(this,t,this._integrations),i||ki(this,[t])}sendEvent(t,i={}){this.emit("beforeSendEvent",t,i);let r=es(t,this),n=Un(t,this._dsn,this._options._metadata,this._options.tunnel);for(let s of i.attachments||[])n=wi(n,Rn(s));r&&(n=wi(n,r)),this.sendEnvelope(n).then(s=>this.emit("afterSendEvent",t,s))}sendSession(t){let{release:i,environment:r=Pe}=this._options;if("aggregates"in t){let s=t.attrs||{};if(!s.release&&!i){m&&d.warn(ps);return}s.release=s.release||i,s.environment=s.environment||r,t.attrs=s}else{if(!t.release&&!i){m&&d.warn(ps);return}t.release=t.release||i,t.environment=t.environment||r}this.emit("beforeSendSession",t);let n=Mn(t,this._dsn,this._options._metadata,this._options.tunnel);this.sendEnvelope(n)}recordDroppedEvent(t,i,r=1){if(this._options.sendClientReports){let n=`${t}:${i}`;m&&d.log(`Recording outcome: "${n}"${r>1?` (${r} times)`:""}`),this._outcomes[n]=(this._outcomes[n]||0)+r}}on(t,i){let r=this._hooks[t]=this._hooks[t]||new Set,n=(...s)=>i(...s);return r.add(n),()=>{r.delete(n)}}emit(t,...i){let r=this._hooks[t];r&&r.forEach(n=>n(...i))}async sendEnvelope(t){if(this.emit("beforeEnvelope",t),this._isEnabled()&&this._transport)try{let i=await this._transport.send(t);return this.emit("afterEnvelope",t),i}catch(i){return m&&d.error("Error while sending envelope:",i),{}}return m&&d.error("Transport disabled"),{}}registerCleanup(t){}dispose(){}_setupIntegrations(){let{integrations:t}=this._options;this._integrations=fn(this,t),ki(this,t)}_updateSessionFromEvent(t,i){var l,p;let r=i.level==="fatal",n=!1,s=(l=i.exception)==null?void 0:l.values;if(s){n=!0,r=!1;for(let c of s)if(((p=c.mechanism)==null?void 0:p.handled)===!1){r=!0;break}}let a=t.status==="ok";(a&&t.errors===0||a&&r)&&(re(t,{...r&&{status:this._unhandledSessionStatus},errors:t.errors||Number(n||r)}),this.captureSession(t))}async _isClientDoneProcessing(t){let i=0;for(;!t||isetTimeout(r,1)),!this._numProcessing)return!0;i++}return!1}_isEnabled(){return this.getOptions().enabled!==!1&&this._transport!==void 0}_prepareEvent(t,i,r,n){let s=this.getOptions(),a=this.getIntegrationNames();return!i.integrations&&a.length&&(i.integrations=a),this.emit("preprocessEvent",t,i),t.type||n.setLastEventId(t.event_id||i.event_id),Yn(s,t,i,r,this,n).then(o=>{var p;if(o===null)return o;this.emit("postprocessEvent",o,i),o.contexts={trace:{...(p=o.contexts)==null?void 0:p.trace,...He(r)},...o.contexts};let l=Ii(this,r);return o.sdkProcessingMetadata={dynamicSamplingContext:l,...o.sdkProcessingMetadata},o})}_captureEvent(t,i={},r=A(),n=O()){return m&&qi(t)&&d.log(`Captured error event \`${Ot(t)[0]||""}\``),this._processEvent(t,i,r,n).then(s=>s.event_id,s=>{m&&(us(s)?d.log(s.message):cs(s)?d.warn(s.message):d.warn(s))})}_processEvent(t,i,r,n){let s=this.getOptions(),{sampleRate:a}=s,o=_s(t),l=qi(t),c=`before send for type \`${t.type||"error"}\``,u="before_send",g=typeof a=="undefined"?void 0:ln(a),_=je(t.type);return this._prepareEvent(t,i,r,n).then(b=>{var U;if(b===null)throw Ui("An event processor returned `null`, will not send event.");if(((U=i.data)==null?void 0:U.__sentry__)===!0)return b;let H=Vo(this,s,b,i,()=>{u="callback_error"});return Yo(H,c)}).then(b=>{var U;if(b===null){if(this.recordDroppedEvent(u,_),o){let Te=t.spans||[];this.recordDroppedEvent(u,"span",1+Te.length)}throw Ui(`${c} ${u==="callback_error"?"threw an error":"returned `null`"}, will not send event.`)}let E=r.getSession()||n.getSession();if(l&&E&&this._updateSessionFromEvent(E,b),l&&typeof g=="number"&&we()>g)throw this.recordDroppedEvent("sample_rate","error"),Ui(`Discarding event because it's not included in the random sample (sampling rate = ${a})`);if(o){let S=((U=b.sdkProcessingMetadata)==null?void 0:U.spanCountBeforeProcessing)||0,Te=b.spans?b.spans.length:0,se=S-Te;se>0&&this.recordDroppedEvent("before_send","span",se)}let H=b.transaction_info;if(o&&H&&b.transaction!==t.transaction){let S="custom";b.transaction_info={...H,source:S}}return this.sendEvent(b,i),b}).then(null,b=>{throw us(b)||cs(b)?b:(this.captureException(b,{mechanism:{handled:!1,type:"internal"},data:{__sentry__:!0},originalException:b}),xt(`Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event. +Reason: ${b}`))})}_process(t,i){this._numProcessing++,this._promiseBuffer.add(t).then(r=>(this._numProcessing--,r),r=>(this._numProcessing--,r===Ue&&this.recordDroppedEvent("queue_overflow",i),r))}_clearOutcomes(){let t=this._outcomes;return this._outcomes={},Object.entries(t).map(([i,r])=>{let[n,s]=i.split(":");return{reason:n,category:s,quantity:r}})}_flushOutcomes(){m&&d.log("Flushing outcomes...");let t=this._clearOutcomes();if(t.length===0){m&&d.log("No outcomes to send");return}if(!this._dsn){m&&d.log("No dsn provided, will not send outcomes");return}m&&d.log("Sending outcomes:",t);let i=rs(t,this._options.tunnel&&X(this._dsn));this.sendEnvelope(i)}};function Yo(e,t){let i=`${t} must return \`null\` or a valid event.`;if(F(e))return e.then(r=>{if(!te(r)&&r!==null)throw xt(i);return r},r=>{throw xt(`${t} rejected with ${r}`)});if(!te(e)&&e!==null)throw xt(i);return e}function Vo(e,t,i,r,n){let{beforeSend:s,ignoreSpans:a,beforeSendTransaction:o}=t,l=Ai(t.beforeSendSpan)&&t.beforeSendSpan,p=i;if(qi(p)&&s){let c=p;return J(m?"The `beforeSend` callback threw an error, dropping the event:":"",()=>s(c,r),()=>(n(),null))}if(_s(p)){if(l||a){let c=ns(p);if(a!=null&&a.length&&vi({description:c.description,op:c.op,attributes:c.data},a))return null;if(l){let u=Oi(c,l);p=oe(i,ss(u))}if(p.spans){let u=[],g=p.spans;for(let b of g){if(a!=null&&a.length&&vi({description:b.description,op:b.op,attributes:b.data},a)){_n(g,b);continue}l?u.push(Oi(b,l)):u.push(b)}let _=p.spans.length-u.length;_&&e.recordDroppedEvent("before_send","span",_),p.spans=u}}if(o){if(p.spans){let c=p.spans.length;p.sdkProcessingMetadata={...i.sdkProcessingMetadata,spanCountBeforeProcessing:c}}return J(m?"The `beforeSendTransaction` callback threw an error, dropping the event:":"",()=>o(p,r),()=>(n(),null))}}return p}function qi(e){return e.type===void 0}function _s(e){return e.type==="transaction"}function Wo(e){let t=0;return e.name&&(t+=e.name.length*2),t+=8,t+fs(e.attributes)}function jo(e){let t=0;return e.message&&(t+=e.message.length*2),t+fs(e.attributes)}function fs(e){if(!e)return 0;let t=0;return Object.values(e).forEach(i=>{Array.isArray(i)?t+=i.length*ms(i[0]):B(i)?t+=ms(i):t+=100}),t}function ms(e){return typeof e=="string"?e.length*2:typeof e=="number"?8:typeof e=="boolean"?4:0}function Fi(e,t){t.debug===!0&&(m?d.enable():x(()=>{console.warn("[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.")})),vs(),A().update(t.initialScope);let r=new e(t);return Gi(r),r.init(),r}function hs(){let e=f();return e&&!Bi(e)?e:void 0}function vs(){hs()&&x(()=>{console.warn("[Sentry] `Sentry.init()` was called more than once. The new client replaces the active client, but the active client is not closed, so state from both can mix. Call `Sentry.init()` only once, or call `await Sentry.close()` before you call it again.")})}function Gi(e){A().setClient(e)}var wt="[Filtered]",Hi=["auth","token","secret","session","password","passwd","pwd","key","jwt","bearer","sso","saml","csrf","xsrf","credentials","sid","identity","set-cookie","cookie"];function Ko(e,t){return t.some(i=>e.includes(i))}function zi(e,t,i){if(t===!1)return!0;let r=e.toLowerCase(),n=i!=null?[...Hi,...i]:Hi;if(Ko(r,n))return!0;if(t===!0)return!1;let a=("deny"in t?t.deny:t.allow).some(o=>r.includes(o.toLowerCase()));return"deny"in t?a:!a}function Rt(e,t,i){if(t===!1)return{};let r={};for(let n of Object.keys(e))r[n]=zi(n,t,i)?wt:e[n];return r}function Ss(e,t){if(!(!e||t===!1))return e.split("&").map(i=>{let r=i.indexOf("="),n=r===-1?i:i.slice(0,r),s=new URLSearchParams(`${n}=`).keys().next().value;return s!==void 0&&zi(s,t)?`${n}=${wt}`:i}).join("&")}function Es(e,t){let i=e.indexOf("#"),r=i===-1?e.length:i,n=e.indexOf("?");if(n===-1||n>r)return e;let s=e.slice(0,n),a=e.slice(n+1,r),o=e.slice(r),l=Ss(a,t);return l?`${s}?${l}${o}`:`${s}${o}`}function $o(e){var t,i;return(i=(t=e!=null?e:f())==null?void 0:t.getDataCollectionOptions().urlQueryParams)!=null?i:!0}function Yi(e,t){return e===void 0?void 0:Es(e,$o(t))}function Ze(e){if(!e)return{};let t=e.match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);if(!t)return{};let i=t[6]||"",r=t[8]||"";return{host:t[4],path:t[5],protocol:t[2],search:i,hash:r,relative:t[5]+i+r}}function Vi(e,t=!0){if(e.startsWith("data:")){let i=e.match(/^data:([^;,]+)/),r=i?i[1]:"text/plain",n=e.includes(";base64,"),s=e.indexOf(","),a="";if(t&&s!==-1){let o=e.slice(s+1);a=o.length>10?`${o.slice(0,10)}... [truncated]`:o}return`data:${r}${n?",base64":""}${a?`,${a}`:""}`}return e}function Wi(e){var t;"aggregates"in e?((t=e.attrs)==null?void 0:t.ip_address)===void 0&&(e.attrs={...e.attrs,ip_address:"{{auto}}"}):e.ipAddress===void 0&&(e.ipAddress="{{auto}}")}function ji(e,t,i=[t],r="npm"){let n=(e._metadata=e._metadata||{}).sdk=e._metadata.sdk||{};n.name||(n.name=`sentry.javascript.${t}`,n.packages=i.map(s=>({name:`${r}:@sentry/${s}`,version:W})),n.version=W)}var Qo=100;function V(e,t){let i=f(),r=O();if(!i)return;let{beforeBreadcrumb:n=null,maxBreadcrumbs:s=Qo}=i.getOptions();if(s<=0)return;let o={timestamp:Q(),...e},l=n?J(m?"The `beforeBreadcrumb` callback threw an error, dropping the breadcrumb:":"",()=>x(()=>n(o,t)),()=>null):o;l!==null&&(i.emit&&i.emit("beforeAddBreadcrumb",l,t),r.addBreadcrumb(l,s))}var Xo="FunctionToString",ks=new WeakMap,Jo=(()=>({name:Xo,setupOnce(){let e=Function.prototype.toString;try{Function.prototype.toString=function(...t){let i=ye(this),r;try{ks.has(f())&&i!==void 0&&(r=i)}catch(n){}return e.apply(r!=null?r:this,t)}}catch(t){}},setup(e){ks.set(e,!0)}})),Nt=Jo;var Zo=[/^Script error\.?$/,/^Javascript error: Script error\.? on line 0$/,/^ResizeObserver loop completed with undelivered notifications.$/,/^Cannot redefine property: googletag$/,/^Can't find variable: gmo$/,/^undefined is not an object \(evaluating 'a\.[A-Z]'\)$/,/can't redefine non-configurable property "solana"/,/vv\(\)\.getRestrictions is not a function/,/Can't find variable: _AutofillCallbackHandler/,/Object Not Found Matching Id:\d+, MethodName:simulateEvent/,/Java exception was raised during method invocation$/,/Java object is gone$/],el="EventFilters",Lt=(e={})=>{let t;return{name:el,setup(i){let r=i.getOptions();t=Ts(e,r)},processEvent(i,r,n){if(!t){let s=n.getOptions();t=Ts(e,s)}return tl(i,t)?null:i}}};function Ts(e={},t={}){return{allowUrls:[...e.allowUrls||[],...t.allowUrls||[]],denyUrls:[...e.denyUrls||[],...t.denyUrls||[]],ignoreErrors:[...e.ignoreErrors||[],...t.ignoreErrors||[],...e.disableErrorDefaults?[]:Zo],ignoreTransactions:[...e.ignoreTransactions||[],...t.ignoreTransactions||[]]}}function tl(e,t){if(e.type){if(e.type==="transaction"&&rl(e,t.ignoreTransactions))return m&&d.warn(`Event dropped due to being matched by \`ignoreTransactions\` option. +Event: ${$(e)}`),!0}else{if(il(e,t.ignoreErrors))return m&&d.warn(`Event dropped due to being matched by \`ignoreErrors\` option. +Event: ${$(e)}`),!0;if(ol(e))return m&&d.warn(`Event dropped due to not having an error message, error type or stacktrace. +Event: ${$(e)}`),!0;if(nl(e,t.denyUrls))return m&&d.warn(`Event dropped due to being matched by \`denyUrls\` option. +Event: ${$(e)}. +Url: ${Ct(e)}`),!0;if(!sl(e,t.allowUrls))return m&&d.warn(`Event dropped due to not being matched by \`allowUrls\` option. +Event: ${$(e)}. +Url: ${Ct(e)}`),!0}return!1}function il(e,t){return t!=null&&t.length?Ot(e).some(i=>_e(i,t)):!1}function rl(e,t){if(!(t!=null&&t.length))return!1;let i=e.transaction;return i?_e(i,t):!1}function nl(e,t){if(!(t!=null&&t.length))return!1;let i=Ct(e);return i?_e(i,t):!1}function sl(e,t){if(!(t!=null&&t.length))return!0;let i=Ct(e);return i?_e(i,t):!0}function al(e=[]){for(let t=e.length-1;t>=0;t--){let i=e[t];if(i&&i.filename!==""&&i.filename!=="[native code]")return i.filename||null}return null}function Ct(e){var t,i,r;try{let n=[...(i=(t=e.exception)==null?void 0:t.values)!=null?i:[]].reverse().find(a=>{var o,l,p;return((o=a.mechanism)==null?void 0:o.parent_id)===void 0&&((p=(l=a.stacktrace)==null?void 0:l.frames)==null?void 0:p.length)}),s=(r=n==null?void 0:n.stacktrace)==null?void 0:r.frames;return s?al(s):null}catch(n){return m&&d.error(`Cannot extract url for event ${$(e)}`),null}}function ol(e){var t,i;return(i=(t=e.exception)==null?void 0:t.values)!=null&&i.length?!e.message&&!e.exception.values.some(r=>r.stacktrace||r.type&&r.type!=="Error"||r.value):!1}function $i(e,t,i,r,n,s){var o;if(!((o=n.exception)!=null&&o.values)||!s||!N(s.originalException))return;let a=n.exception.values.length>0?n.exception.values[n.exception.values.length-1]:void 0;a&&(n.exception.values=Ki(e,t,r,s.originalException,i,n.exception.values,a,0))}function Ki(e,t,i,r,n,s,a,o){if(s.length>=i+1)return s;let l=[...s];if(N(r[n])){Is(a,o,r);let p=e(t,r[n]),c=l.length;As(p,n,c,o),l=Ki(e,t,i,r[n],n,[p,...l],p,c)}return Os(r)&&r.errors.forEach((p,c)=>{if(N(p)){Is(a,o,r);let u=e(t,p),g=l.length;As(u,`errors[${c}]`,g,o),l=Ki(e,t,i,p,n,[u,...l],u,g)}}),l}function Os(e){return Array.isArray(e.errors)}function Is(e,t,i){e.mechanism={handled:!0,type:"auto.core.linked_errors",...Os(i)&&{is_exception_group:!0},...e.mechanism,exception_id:t}}function As(e,t,i,r){e.mechanism={handled:!0,...e.mechanism,type:"chained",source:t,exception_id:i,parent_id:r}}function ll(e){return N(e)&&"__sentry_fetch_url_host__"in e&&typeof e.__sentry_fetch_url_host__=="string"}function Pt(e){return ll(e)?`${e.message} (${e.__sentry_fetch_url_host__})`:e.message}var Dt=new Set([]);function ws(e){let t="console",i=w(t,e);return R(t,pl),i}function Rs(e){for(let t of e)Dt.add(t);return()=>{for(let t of e)Dt.delete(t)}}var xs=new Set;function pl(){"console"in y&&qe.forEach(function(e){xs.has(e)||!(e in y.console)||(xs.add(e),T(y.console,e,function(t){return Ie[e]=t,function(...i){let r=i[0],n=Ie[e],s=Dt.size&&typeof r=="string"&&_e(r,Dt);s||k("console",{args:i,level:e}),(!s||m&&d.isEnabled())&&(n==null||n.apply(y.console,i))}}))})}function Ns(e){return e==="warn"?"warning":["fatal","error","warning","log","info","debug"].includes(e)?e:"log"}var cl="Dedupe",ul=(()=>{let e;return{name:cl,processEvent(t){if(t.type)return t;try{if(dl(t,e))return m&&d.warn("Event dropped due to being a duplicate of previously captured event."),null}catch(i){}return e=t}}}),Mt=ul;function dl(e,t){return t?!!(ml(e,t)||gl(e,t)):!1}function ml(e,t){let i=e.message,r=t.message;return!(!i&&!r||i&&!r||!i&&r||i!==r||!Ps(e,t)||!Ls(e,t))}function gl(e,t){let i=Cs(t),r=Cs(e);return!(!i||!r||i.type!==r.type||i.value!==r.value||!Ps(e,t)||!Ls(e,t))}function Ls(e,t){let i=at(e),r=at(t);if(!i||!r)return!i&&!r;if(r.length!==i.length)return!1;for(let n=0;n{let t=new Set(e.levels||qe);return{name:bl,setup(i){let r=ws(({args:n,level:s})=>{f()!==i||!t.has(s)||yl(s,n)});if(i.registerCleanup(r),e.filter){let n=Rs(e.filter);i.registerCleanup(n)}}}};function yl(e,t){let i={category:"console",data:{arguments:t,logger:"console"},level:Ns(e),message:Ds(t)};if(e==="assert")if(t[0]===!1){let r=t.slice(1);i.message=r.length>0?`Assertion failed: ${Ds(r)}`:"Assertion failed",i.data.arguments=r}else return;V(i,{input:t,level:e})}function Ds(e){return"util"in y&&typeof y.util.format=="function"?y.util.format(...e):Or(e," ")}var _l="ConversationId",fl=(()=>({name:_l,setup(e){e.on("spanStart",t=>{let i=A().getScopeData(),r=O().getScopeData(),n=i.conversationId||r.conversationId;if(n){let{op:s,data:a,description:o}=le(t);if(!(s!=null&&s.startsWith("gen_ai."))&&!a["ai.operationId"]&&!(o!=null&&o.startsWith("ai.")))return;t.setAttribute(Yr,n)}})}})),Qi=fl;var Xi=y;function hl(){if(!("fetch"in Xi))return!1;try{return new Headers,new Request("data:,"),new Response,!0}catch(e){return!1}}function et(e){return e&&/^function\s+\w+\(\)\s+\{\s+\[native code\]\s+\}$/.test(e.toString())}function Ms(){var i;if(typeof EdgeRuntime=="string")return!0;if(!hl())return!1;if(et(Xi.fetch))return!0;let e=!1,t=Xi.document;if(t&&typeof t.createElement=="function")try{let r=t.createElement("iframe");r.hidden=!0,t.head.appendChild(r),(i=r.contentWindow)!=null&&i.fetch&&(e=et(r.contentWindow.fetch)),t.head.removeChild(r)}catch(r){m&&d.warn("Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ",r)}return e}function Ji(e){let t="fetch",i=w(t,e);return R(t,()=>vl()),i}function vl(e){pe()&&!Ms()||T(y,"fetch",function(t){return new Proxy(t,{apply(i,r,n){let s=new Error,{method:a,url:o}=Sl(n),l={args:n,fetchData:{method:a,url:o},startTimestamp:D()*1e3,virtualError:s,headers:El(n)};return e||k("fetch",{...l}),Reflect.apply(i,y,n).then(async p=>(e?e(p):k("fetch",{...l,endTimestamp:D()*1e3,response:p}),p),p=>{var _;k("fetch",{...l,endTimestamp:D()*1e3,error:p}),N(p)&&p.stack===void 0&&(p.stack=s.stack,P(p,"framesToPop",1));let c=f(),u=(_=c==null?void 0:c.getOptions().enhanceFetchErrorMessages)!=null?_:"always";if(u!==!1&&N(p)&&p.name==="TypeError"&&(p.message==="Failed to fetch"||p.message==="Load failed"||p.message==="NetworkError when attempting to fetch resource."))try{let E=new URL(l.fetchData.url).host;u==="always"?p.message=`${p.message} (${E})`:P(p,"__sentry_fetch_url_host__",E)}catch(b){}throw p})}})})}function qt(e,t){return ae(e)&&!!e[t]}function Us(e){return typeof e=="string"?e:e?qt(e,"url")?e.url:e.toString?e.toString():"":""}function Sl(e){if(e.length===0)return{method:"GET",url:""};if(e.length===2){let[i,r]=e;return{url:Us(i),method:qt(r,"method")?String(r.method).toUpperCase():ni(i)&&qt(i,"method")?String(i.method).toUpperCase():"GET"}}let t=e[0];return{url:Us(t),method:qt(t,"method")?String(t.method).toUpperCase():"GET"}}function El(e){let[t,i]=e;try{if(typeof i=="object"&&i!==null&&"headers"in i&&i.headers)return new Headers(i.headers);if(ni(t))return new Headers(t.headers)}catch(r){}}function Bt(e){if(e!==void 0)return e>=400&&e<500?"warning":e>=500?"error":void 0}var tt=typeof __SENTRY_DEBUG__=="undefined"||__SENTRY_DEBUG__;var v=y;var Fl=80,ve={};try{typeof Node!="undefined"&&(ve.parentNode=Object.getOwnPropertyDescriptor(Node.prototype,"parentNode").get),typeof Element!="undefined"&&(ve.tagName=Object.getOwnPropertyDescriptor(Element.prototype,"tagName").get,ve.id=Object.getOwnPropertyDescriptor(Element.prototype,"id").get,ve.className=Object.getOwnPropertyDescriptor(Element.prototype,"className").get,ve.getAttribute=Element.prototype.getAttribute),typeof HTMLElement!="undefined"&&(ve.dataset=Object.getOwnPropertyDescriptor(HTMLElement.prototype,"dataset").get)}catch(e){}function de(e,t,i){let r=ve[t];if(r)try{return r.call(e,i)}catch(s){}let n=e[t];return typeof n=="function"?n.call(e,i):n}function it(e,t={}){if(!e)return"";try{let i=e,r=5,n=[],s=0,a=0,o=" > ",l=o.length,p,c=Array.isArray(t)?t:t.keyAttrs,u=!Array.isArray(t)&&t.maxStringLength||Fl;for(;i&&s++1&&a+n.length*l+p.length>=u));)n.push(p),a+=p.length,i=de(i,"parentNode");return n.reverse().join(o)}catch(i){return""}}function Gl(e,t){let i=[],r=de(e,"tagName");if(!r)return"";if(typeof HTMLElement!="undefined"&&e instanceof HTMLElement){let s=de(e,"dataset");if(s){if(s.sentryComponent)return s.sentryComponent;if(s.sentryElement)return s.sentryElement}}i.push(r.toLowerCase());let n=t!=null&&t.length?t.filter(s=>de(e,"getAttribute",s)).map(s=>[s,de(e,"getAttribute",s)]):null;if(n!=null&&n.length)n.forEach(s=>{i.push(`[${s[0]}="${s[1]}"]`)});else{let s=de(e,"id");s&&i.push(`#${s}`);let a=de(e,"className");if(a&&C(a)){let o=a.split(/\s+/);for(let l of o)i.push(`.${l}`)}}for(let s of["aria-label","type","name","title","alt"]){let a=de(e,"getAttribute",s);a&&i.push(`[${s}="${a}"]`)}return i.join("")}function Hl(e,t,i){v.document&&v.addEventListener(e,t,i)}function zl(e,t,i){v.document&&v.removeEventListener(e,t,i)}var Zi=e=>{var i;let t=v.requestIdleCallback||v.setTimeout;if(((i=v.document)==null?void 0:i.visibilityState)==="hidden")e();else{let r=!1,n=()=>{r||(e(),r=!0)};Hl("visibilitychange",n,{once:!0,capture:!0}),t(()=>{n(),zl("visibilitychange",n,{capture:!0})})}};function er(e,t=5){if(!y.HTMLElement)return null;let i=e;for(let r=0;r{var s,a;let n=(s=v[i])==null?void 0:s.prototype;(a=n==null?void 0:n.hasOwnProperty)!=null&&a.call(n,"addEventListener")&&(T(n,"addEventListener",function(o){return function(l,p,c){if((l==="click"||l=="keypress")&&p)try{let u=this.__sentry_instrumentation_handlers__=this.__sentry_instrumentation_handlers__||{},g=u[l]=u[l]||{captureListeners:new WeakSet,bubbleListeners:new WeakSet,listenerCount:0},_=Gs(c);if(!g.handler){let E=Hs(e);g.handler=E,g.capture=_,o.call(this,l,E,g.capture)}let b=g[_?"captureListeners":"bubbleListeners"];b.has(p)||(typeof c=="object"&&(c!=null&&c.once||c!=null&&c.signal)?g.sticky=!0:(b.add(p),g.listenerCount++))}catch(u){}return o.call(this,l,p,c)}}),T(n,"removeEventListener",function(o){return function(l,p,c){if((l==="click"||l=="keypress")&&p)try{let u=this.__sentry_instrumentation_handlers__||{},g=u[l];g!=null&&g[Gs(c)?"captureListeners":"bubbleListeners"].delete(p)&&(!--g.listenerCount&&!g.sticky&&(o.call(this,l,g.handler,g.capture),g.handler=void 0,delete u[l]),Object.keys(u).length===0&&delete this.__sentry_instrumentation_handlers__)}catch(u){}return o.call(this,l,p,c)}}))})}function Wl(e){if(e.type!==ir)return!1;try{if(!e.target||e.target._sentryId!==rr)return!1}catch(t){}return!0}function jl(e,t){return e!=="keypress"?!1:t!=null&&t.tagName?!(t.tagName==="INPUT"||t.tagName==="TEXTAREA"||t.isContentEditable):!0}function Hs(e,t=!1){return i=>{if(!i||i._sentryCaptured)return;let r=Kl(i);if(jl(i.type,r))return;P(i,"_sentryCaptured",!0),r&&!r._sentryId&&P(r,"_sentryId",I());let n=i.type==="keypress"?"input":i.type;Wl(i)||(e({event:i,name:n,global:t}),ir=i.type,rr=r?r._sentryId:void 0),clearTimeout(Fs),Fs=v.setTimeout(()=>{rr=void 0,ir=void 0},Yl)}}function Kl(e){try{return e.target}catch(t){return null}}var Ft;function $l(){return"history"in v&&!!v.history}function rt(e){let t="history";w(t,e),R(t,Ql)}function Ql(){if(v.addEventListener("popstate",()=>{let t=v.location.href,i=Ft;if(Ft=t,i===t)return;k("history",{from:i,to:t})}),!$l())return;function e(t){return function(...i){let r=i.length>2?i[2]:void 0;if(r){let n=Ft,s=Xl(String(r));if(Ft=s,n===s)return t.apply(this,i);k("history",{from:n,to:s})}return t.apply(this,i)}}T(v.history,"pushState",e),T(v.history,"replaceState",e)}function Xl(e){try{return new URL(e,v.location.origin).toString()}catch(t){return e}}var Gt={};function sr(e){let t=Gt[e];if(t)return t;let i=v[e];if(et(i))return Gt[e]=i.bind(v);let r=v.document;if(r&&typeof r.createElement=="function")try{let n=r.createElement("iframe");n.hidden=!0,r.head.appendChild(n);let s=n.contentWindow;s!=null&&s[e]&&(i=s[e]),r.head.removeChild(n)}catch(n){tt&&d.warn(`Could not create sandbox iframe for ${e} check, bailing to window.${e}: `,n)}return i&&(Gt[e]=i.bind(v))}function ar(e){Gt[e]=void 0}var Ee="__sentry_xhr_v3__";function or(e){w("xhr",e),R("xhr",Jl)}function Jl(){if(!v.XMLHttpRequest)return;let e=XMLHttpRequest.prototype;e.open=new Proxy(e.open,{apply(t,i,r){let n=new Error,s=D()*1e3,a=C(r[0])?r[0].toUpperCase():void 0,o=Zl(r[1]);if(!a||!o)return t.apply(i,r);i[Ee]={method:a,url:o,request_headers:{}},a==="POST"&&o.match(/sentry_key/)&&(i.__sentry_own_request__=!0);let l=()=>{let p=i[Ee];if(p&&i.readyState===4){try{p.status_code=i.status}catch(u){}let c={endTimestamp:D()*1e3,startTimestamp:s,xhr:i,virtualError:n};k("xhr",c),n=void 0,i.removeEventListener("readystatechange",l)}};return"onreadystatechange"in i&&typeof i.onreadystatechange=="function"?i.onreadystatechange=new Proxy(i.onreadystatechange,{apply(p,c,u){return l(),p.apply(c,u)}}):i.addEventListener("readystatechange",l),i.setRequestHeader=new Proxy(i.setRequestHeader,{apply(p,c,u){let[g,_]=u,b=c[Ee];return b&&C(g)&&C(_)&&(b.request_headers[g.toLowerCase()]=_),p.apply(c,u)}}),t.apply(i,r)}}),e.send=new Proxy(e.send,{apply(t,i,r){let n=i[Ee];if(!n)return t.apply(i,r);r[0]!==void 0&&(n.body=r[0]);let s={startTimestamp:D()*1e3,xhr:i};return k("xhr",s),t.apply(i,r)}})}function Zl(e){if(C(e))return e;try{return e.toString()}catch(t){}}function lr(e){if(typeof Element=="undefined")return!1;try{return e instanceof Element}catch(t){return!1}}var h=y,pr=0,zs=[];function cr(e){if(pr>0)return!0;let t=e?zs.findIndex(i=>ep(i,e)):-1;return t===-1?!1:(zs.splice(t,1),!0)}function ep(e,t){return e.msg===t.msg&&e.url===t.url&&e.line===t.line&&e.column===t.column}function tp(){pr++,setTimeout(()=>{pr--})}function ke(e,t={}){function i(n){return typeof n=="function"}if(!i(e))return e;try{if(Object.prototype.hasOwnProperty.call(e,"__sentry_wrapped__")){let s=e.__sentry_wrapped__;return typeof s=="function"?s:e}if(ye(e))return e}catch(n){return e}let r=function(...n){y._sentryWrappedDepth=(y._sentryWrappedDepth||0)+1;try{let s=n.map(a=>ke(a,t));return e.apply(this,s)}catch(s){throw tp(),_t(a=>{a.addEventProcessor(o=>(t.mechanism&&(Ne(o,void 0,void 0),ie(o,t.mechanism)),o.extra={...o.extra,arguments:n},o)),De(s)}),s}finally{y._sentryWrappedDepth=(y._sentryWrappedDepth||0)-1}};try{for(let n in e)Object.prototype.hasOwnProperty.call(e,n)&&(r[n]=e[n])}catch(n){}pt(r,e),P(e,"__sentry_wrapped__",r);try{Object.getOwnPropertyDescriptor(r,"name").configurable&&Object.defineProperty(r,"name",{get(){return e.name}})}catch(n){}return r}function ur(){let e=Se(),{referrer:t}=h.document||{},{userAgent:i}=h.navigator||{},r={...t&&{Referer:t},...i&&{"User-Agent":i}};return{url:e,headers:r}}function Ht(e,t){let i=zt(e,t),r={type:ap(t),value:op(t)};return i.length&&(r.stacktrace={frames:i}),r.type===void 0&&r.value===""&&(r.value="Unrecoverable error caught"),r}function ip(e,t,i,r){let n=f(),s=n==null?void 0:n.getOptions().normalizeDepth,a=cp(t),o={__serialized__:ut(t,s)};if(a)return{exception:{values:[Ht(e,a)]},extra:o};let p={exception:{values:[{type:be(t)?t.constructor.name:r?"UnhandledRejection":"Error",value:lp(t,{isUnhandledRejection:r})}]},extra:o};if(i){let c=zt(e,i);c.length&&(p.exception.values[0].stacktrace={frames:c})}return p}function dr(e,t){return{exception:{values:[Ht(e,t)]}}}function zt(e,t){let i=t.stacktrace||t.stack||"",r=np(t),n=sp(t);try{return e(i,r,n)}catch(s){}return[]}var rp=/Minified React error #\d+;/i;function np(e){return e&&rp.test(e.message)?1:0}function sp(e){return typeof e.framesToPop=="number"?e.framesToPop:0}function Ys(e){return typeof WebAssembly!="undefined"&&typeof WebAssembly.Exception!="undefined"?e instanceof WebAssembly.Exception:!1}function ap(e){let t=e==null?void 0:e.name;return!t&&Ys(e)?e.message&&Array.isArray(e.message)&&e.message.length==2?e.message[0]:"WebAssembly.Exception":t}function op(e){let t=e==null?void 0:e.message;return Ys(e)?Array.isArray(e.message)&&e.message.length==2?e.message[1]:"wasm exception":t?t.error&&typeof t.error.message=="string"?Pt(t.error):Pt(e):"No error message"}function Vs(e,t,i,r){let n=(i==null?void 0:i.syntheticException)||void 0,s=Yt(e,t,n,r);return ie(s),s.level="error",i!=null&&i.event_id&&(s.event_id=i.event_id),Z(s)}function Ws(e,t,i="info",r,n){let s=(r==null?void 0:r.syntheticException)||void 0,a=mr(e,t,s,n);return a.level=i,r!=null&&r.event_id&&(a.event_id=r.event_id),Z(a)}function Yt(e,t,i,r,n){var o,l;let s;if(Fe(t)&&t.error)return dr(e,t.error);if(ot(t)||ii(t)){let p=t;if("stack"in t){s=dr(e,t);let c=(l=(o=s.exception)==null?void 0:o.values)==null?void 0:l[0];if(r&&i&&c&&!c.stacktrace){let u=zt(e,i);u.length&&(c.stacktrace={frames:u},ie(s,{synthetic:!0}))}}else{let c=p.name||(ot(p)?"DOMError":"DOMException"),u=p.message?`${c}: ${p.message}`:c;s=mr(e,u,i,r),Ne(s,u)}return s}if(N(t))return dr(e,t);if(te(t)||be(t))return s=ip(e,t,i,n),ie(s,{synthetic:!0}),s;let a=String(t);return s=mr(e,a,i,r),Ne(s,a,void 0),ie(s,{synthetic:!0}),s}function mr(e,t,i,r){let n={};if(r&&i){let s=zt(e,i);s.length&&(n.exception={values:[{value:t,stacktrace:{frames:s}}]}),ie(n,{synthetic:!0})}if(ge(t)){let{__sentry_template_string__:s,__sentry_template_values__:a}=t;return n.logentry={message:s,params:a},n}return n.message=t,n}function lp(e,{isUnhandledRejection:t}){let i=si(e),r=t?"promise rejection":"exception";return Fe(e)?`Event \`ErrorEvent\` captured as ${r} with message \`${e.message}\``:be(e)?`Event \`${pp(e)}\` (type=${e.type}) captured as ${r}`:`Object captured as ${r} with keys: ${i}`}function pp(e){try{let t=Object.getPrototypeOf(e);return t?t.constructor.name:void 0}catch(t){}}function cp(e){return Object.values(e).find(N)}var Vt=class extends Je{constructor(t){var a;let i=up(t),r=h.SENTRY_SDK_SOURCE||Li();ji(i,"browser",["browser"],r),super(i),i.routeProvider&&tr(i.routeProvider,this),this._unhandledSessionStatus="unhandled";let{userInfo:n}=this.getDataCollectionOptions();(a=i._metadata)!=null&&a.sdk&&(i._metadata.sdk.settings={infer_ip:n?"auto":"never",...i._metadata.sdk.settings});let{sendClientReports:s}=this._options;h.document&&h.document.addEventListener("visibilitychange",()=>{h.document.visibilityState==="hidden"&&(s&&this._flushOutcomes(),queueMicrotask(()=>{this.flush()}))}),n&&this.on("beforeSendSession",Wi)}eventFromException(t,i){return Vs(this._options.stackParser,t,i,this._options.attachStacktrace)}eventFromMessage(t,i="info",r){return Ws(this._options.stackParser,t,i,r,this._options.attachStacktrace)}_prepareEvent(t,i,r,n){return t.platform=t.platform||"javascript",super._prepareEvent(t,i,r,n)}};function up(e){var t;return{release:typeof __SENTRY_RELEASE__=="string"?__SENTRY_RELEASE__:(t=h.SENTRY_RELEASE)==null?void 0:t.id,sendClientReports:!0,parentSpanIsAlwaysRootSpan:!0,...e}}var dp=40;function js(e,t=sr("fetch")){let i=0,r=0;async function n(s){let a=s.body.length;i+=a,r++;let o={body:s.body,method:"POST",referrerPolicy:"strict-origin",headers:e.headers,keepalive:i<=6e4&&r<15,...e.fetchOptions};try{let l=await t(e.url,o);return{statusCode:l.status,headers:{"x-sentry-rate-limits":l.headers.get("X-Sentry-Rate-Limits"),"retry-after":l.headers.get("Retry-After")}}}catch(l){throw ar("fetch"),l}finally{i-=a,r--}}return At(e,n,fe(e.bufferSize||dp))}var me=typeof __SENTRY_DEBUG__=="undefined"||__SENTRY_DEBUG__;var mp=30;var gp=50;function gr(e,t,i,r){let n={filename:e,function:t===""?"?":t,in_app:!0};return i!==void 0&&(n.lineno=i),r!==void 0&&(n.colno=r),n}var bp=/^\s*at (\S+?)(?::(\d+))(?::(\d+))\s*$/i,yp=/^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,_p=/\((\S*)(?::(\d+))(?::(\d+))\)/,fp=/at (.+?) ?\(data:(.+?),/,hp=e=>{var n;let t=e.match(fp);if(t)return{filename:``,function:t[1]};let i=bp.exec(e);if(i){let[,s,a,o]=i;return gr(s,"?",+a,+o)}let r=yp.exec(e);if(r){if(((n=r[2])==null?void 0:n.indexOf("eval"))===0){let l=_p.exec(r[2]);l&&(r[2]=l[1],r[3]=l[2],r[4]=l[3])}let[a,o]=$s(r[1]||"?",r[2]);return gr(o,a,r[3]?+r[3]:void 0,r[4]?+r[4]:void 0)}},vp=[mp,hp],Sp=/^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:[-a-z]+)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i,Ep=/(\S+) line (\d+)(?: > eval line \d+)* > eval/i,kp=e=>{let t=Sp.exec(e);if(t){if(t[3]&&t[3].indexOf(" > eval")>-1){let s=Ep.exec(t[3]);s&&(t[1]=t[1]||"eval",t[3]=s[1],t[4]=s[2],t[5]="")}let r=t[3],n=t[1]||"?";return[n,r]=$s(n,r),gr(r,n,t[4]?+t[4]:void 0,t[5]?+t[5]:void 0)}},Tp=[gp,kp];var Ip=[vp,Tp],Ks=st(...Ip),$s=(e,t)=>{let i=e.indexOf("safari-extension")!==-1,r=e.indexOf("safari-web-extension")!==-1;return i||r?[e.indexOf("@")!==-1?e.split("@")[0]:"?",i?`safari-extension:${t}`:`safari-web-extension:${t}`]:[e,t]};var Wt=1024,Ap="Breadcrumbs",Op=((e={})=>{let t={dom:!0,fetch:!0,history:!0,sentry:!0,xhr:!0,...e};return{name:Ap,setup(i){t.dom&&nr(wp(i,t.dom)),t.xhr&&or(Rp(i)),t.fetch&&Ji(Np(i)),t.history&&rt(Cp(i)),t.sentry&&i.on("beforeSendEvent",xp(i))}}}),Qs=Op;function xp(e){return function(i){f()===e&&V({category:`sentry.${i.type==="transaction"?"transaction":"event"}`,event_id:i.event_id,level:i.level,message:$(i)},{event:i})}}function wp(e,t){return function(r){if(f()!==e)return;let n,s,a=typeof t=="object"?t.serializeAttribute:void 0,o=typeof t=="object"&&typeof t.maxStringLength=="number"?t.maxStringLength:void 0;o&&o>Wt&&(me&&d.warn(`\`dom.maxStringLength\` cannot exceed ${Wt}, but a value of ${o} was configured. Sentry will use ${Wt} instead.`),o=Wt),typeof a=="string"&&(a=[a]);try{let p=r.event,c=Lp(p)?p.target:p;n=it(c,{keyAttrs:a,maxStringLength:o}),s=er(c)}catch(p){n=""}if(n.length===0)return;let l={category:`ui.${r.name}`,message:n};s&&(l.data={"ui.component_name":s}),V(l,{event:r.event,name:r.name,global:r.global})}}function Rp(e){return function(i){if(f()!==e)return;let{startTimestamp:r,endTimestamp:n}=i,s=i.xhr[Ee];if(!r||!n||!s)return;let{method:a,url:o,status_code:l,body:p}=s,c={method:a,url:o,status_code:l},u={xhr:i.xhr,input:p,startTimestamp:r,endTimestamp:n},g={category:"xhr",data:c,type:"http",level:Bt(l)};e.emit("beforeOutgoingRequestBreadcrumb",g,u),V(g,u)}}function Np(e){return function(i){if(f()!==e)return;let{startTimestamp:r,endTimestamp:n}=i;if(n&&!(i.fetchData.url.match(/sentry_key/)&&i.fetchData.method==="POST"))if(i.error){let s={data:i.error,input:i.args,startTimestamp:r,endTimestamp:n},a={category:"fetch",data:i.fetchData,level:"error",type:"http"};e.emit("beforeOutgoingRequestBreadcrumb",a,s),V(a,s)}else{let s=i.response,a={...i.fetchData,status_code:s==null?void 0:s.status},o={input:i.args,response:s,startTimestamp:r,endTimestamp:n},l={category:"fetch",data:a,type:"http",level:Bt(a.status_code)};e.emit("beforeOutgoingRequestBreadcrumb",l,o),V(l,o)}}}function Cp(e){return function(i){if(f()!==e)return;let r=i.from,n=i.to,s=Ze(h.location.href),a=r?Ze(r):void 0,o=Ze(n);a!=null&&a.path||(a=s),s.protocol===o.protocol&&s.host===o.host&&(n=o.relative),s.protocol===a.protocol&&s.host===a.host&&(r=a.relative),V({category:"navigation",data:{from:r,to:n}})}}function Lp(e){return!!e&&!!e.target}var Pp="EventTarget,Window,Node,ApplicationCache,AudioTrackList,BroadcastChannel,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,SharedWorker,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload".split(","),Dp="BrowserApiErrors",Mp=((e={})=>{let t={XMLHttpRequest:!0,eventTarget:!0,requestAnimationFrame:!0,setInterval:!0,setTimeout:!0,unregisterOriginalCallbacks:!1,...e};return{name:Dp,setupOnce(){t.setTimeout&&T(h,"setTimeout",Xs),t.setInterval&&T(h,"setInterval",Xs),t.requestAnimationFrame&&T(h,"requestAnimationFrame",Up),t.XMLHttpRequest&&"XMLHttpRequest"in h&&T(XMLHttpRequest.prototype,"send",qp);let i=t.eventTarget;i&&(Array.isArray(i)?i:Pp).forEach(n=>Bp(n,t))}}}),Js=Mp;function Xs(e){return function(...t){let i=t[0];return t[0]=ke(i,{mechanism:{handled:!1,type:`auto.browser.browserapierrors.${q(e)}`}}),e.apply(this,t)}}function Up(e){return function(t){return e.apply(this,[ke(t,{mechanism:{data:{handler:q(e)},handled:!1,type:"auto.browser.browserapierrors.requestAnimationFrame"}})])}}function qp(e){return function(...t){let i=this;return["onload","onerror","onprogress","onreadystatechange"].forEach(n=>{n in i&&typeof i[n]=="function"&&T(i,n,function(s){let a={mechanism:{data:{handler:q(s)},handled:!1,type:`auto.browser.browserapierrors.xhr.${n}`}},o=ye(s);return o&&(a.mechanism.data.handler=q(o)),ke(s,a)})}),e.apply(this,t)}}function Bp(e,t){var n,s;let r=(n=h[e])==null?void 0:n.prototype;(s=r==null?void 0:r.hasOwnProperty)!=null&&s.call(r,"addEventListener")&&(T(r,"addEventListener",function(a){return function(o,l,p){try{Fp(l)&&(l.handleEvent=ke(l.handleEvent,{mechanism:{data:{handler:q(l),target:e},handled:!1,type:"auto.browser.browserapierrors.handleEvent"}}))}catch(c){}return t.unregisterOriginalCallbacks&&Gp(this,o,l),a.apply(this,[o,ke(l,{mechanism:{data:{handler:q(l),target:e},handled:!1,type:"auto.browser.browserapierrors.addEventListener"}}),p])}}),T(r,"removeEventListener",function(a){return function(o,l,p){try{if(Object.prototype.hasOwnProperty.call(l,"__sentry_wrapped__")){let c=l.__sentry_wrapped__;c&&a.call(this,o,c,p)}}catch(c){}return a.call(this,o,l,p)}}))}function Fp(e){return typeof e.handleEvent=="function"}function Gp(e,t,i){e&&typeof e=="object"&&"removeEventListener"in e&&typeof e.removeEventListener=="function"&&e.removeEventListener(t,i)}var Zs=(e={})=>{var i;let t=(i=e.lifecycle)!=null?i:"page";return{name:"BrowserSession",setupOnce(){if(typeof h.document=="undefined"){me&&d.warn("Using the `browserSessionIntegration` in non-browser environments is not supported.");return}Xe({ignoreDuration:!0});let r=!1;Zi(()=>{r||(Me(),r=!0)});let n=O(),s=n.getUser();n.addScopeListener(a=>{let o=a.getUser();((s==null?void 0:s.id)!==(o==null?void 0:o.id)||(s==null?void 0:s.ip_address)!==(o==null?void 0:o.ip_address))&&(s=o,r&&Me())}),t==="route"&&rt(({from:a,to:o})=>{a!==o&&(Xe({ignoreDuration:!0}),Me(),r=!0)})}}};var Hp="CultureContext",zp=(()=>({name:Hp,preprocessEvent(e){var i;let t=ea();t&&(e.contexts={...e.contexts,culture:{...t,...(i=e.contexts)==null?void 0:i.culture}})},processSegmentSpan(e){let t=ea();t&&We(e,{"culture.locale":t.locale,"culture.timezone":t.timezone,"culture.calendar":t.calendar})}})),ta=zp;function ea(){try{let e=h.Intl;if(!e)return;let t=e.DateTimeFormat().resolvedOptions();return{locale:t.locale,timezone:t.timeZone,calendar:t.calendar}}catch(e){return}}var Yp="GlobalHandlers",Vp=((e={})=>{let t={onerror:!0,onunhandledrejection:!0,...e};return{name:Yp,setupOnce(){Error.stackTraceLimit=50},setup(i){t.onerror&&(Wp(i),ia("onerror")),t.onunhandledrejection&&(jp(i),ia("onunhandledrejection"))}}}),ra=Vp;function Wp(e){Zt(t=>{let{stackParser:i,attachStacktrace:r}=na();if(f()!==e||cr(t))return;let{msg:n,url:s,line:a,column:o,error:l}=t,p=Qp(Yt(i,l||n,void 0,r,!1),s,a,o);p.level="error",Qe(p,{originalException:l,mechanism:{handled:!1,type:"auto.browser.global_handlers.onerror"}})})}function jp(e){ti(t=>{let{stackParser:i,attachStacktrace:r}=na();if(f()!==e||cr())return;let n=Kp(t),s=B(n)?$p(n):Yt(i,n,void 0,r,!0);s.level="error",Qe(s,{originalException:n,mechanism:{handled:!1,type:"auto.browser.global_handlers.onunhandledrejection"}})})}function Kp(e){if(B(e))return e;try{if("reason"in e)return e.reason;if("detail"in e&&"reason"in e.detail)return e.detail.reason}catch(t){}return e}function $p(e){return{exception:{values:[{type:"UnhandledRejection",value:`Non-Error promise rejection captured with value: ${String(e)}`}]}}}function Qp(e,t,i,r){var p;let n=e.exception=e.exception||{},s=n.values=n.values||[],a=s[0]=s[0]||{},o=a.stacktrace=a.stacktrace||{},l=o.frames=o.frames||[];return l.length===0&&l.push({colno:r,lineno:i,filename:(p=Xp(t))!=null?p:Se(),function:"?",in_app:!0}),e}function ia(e){me&&d.log(`Global Handler attached: ${e}`)}function na(){let e=f();return(e==null?void 0:e.getOptions())||{stackParser:()=>[],attachStacktrace:!1}}function Xp(e){if(!(!C(e)||e.length===0))return e.startsWith("data:")?`<${Vi(e,!1)}>`:e}var sa=()=>({name:"HttpContext",preprocessEvent(e,t,i){var l,p;if(!h.navigator&&!h.location&&!h.document)return;let{url:r,headers:n}=ur(),s=i.getDataCollectionOptions().httpHeaders.request,a=Object.entries((p=(l=e.request)==null?void 0:l.headers)!=null?p:{}).filter(([c])=>!(c in n)),o={...Rt(n,s),...Object.fromEntries(a)};e.request={url:r,...e.request,...Object.keys(o).length>0?{headers:o}:{headers:void 0}}},processSpan(e,t){var o;if(!h.navigator&&!h.location&&!h.document)return;let i=ur(),r=Rt(i.headers,t.getDataCollectionOptions().httpHeaders.request),n=r.Referer,{hostname:s,protocol:a}=h.location||{};We(e,{[$r]:r["User-Agent"],[Wr]:a==="file:"||s==="localhost"||s==="127.0.0.1"||s==="[::1]"||!!(s!=null&&s.endsWith(".localhost")),...e.is_segment&&{[Kr]:((o=e.attributes)==null?void 0:o[ft])!=="http.client"?Yi(i.url):void 0,[`${Vr}.referer`]:n?[n]:void 0}})}});var Jp="cause",Zp=5,ec="LinkedErrors",tc=((e={})=>{let t=e.limit||Zp,i=e.key||Jp;return{name:ec,preprocessEvent(r,n,s){let a=s.getOptions();$i(Ht,a.stackParser,i,t,r,n)}}}),aa=tc;var ic=/^HTML(\w*)Element$/;function oa(e){if(typeof window!="undefined"&&e===window)return"[Window]";if(typeof document!="undefined"&&e===document)return"[Document]";if(lr(e)){let t=rc(e);if(ic.test(t))return`[HTMLElement: ${it(e)}]`}}function rc(e){let t=Object.getPrototypeOf(e);return t!=null&&t.constructor?t.constructor.name:"null prototype"}function la(){return nc()?(me&&x(()=>{console.error("[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/")}),!0):!1}function nc(){var n;if(typeof h.window=="undefined")return!1;let e=h;if(e.nw)return!1;let t=e.chrome||e.browser;if(!((n=t==null?void 0:t.runtime)!=null&&n.id))return!1;let i=Se();return!(h===h.top&&/^(?:chrome-extension|moz-extension|ms-browser-extension|safari-web-extension):\/\//.test(i))}function pa(e){return[Lt(),Nt(),Qi(),Js(),Qs(),Ut(),ra(),aa(),Mt(),sa(),ta(),Zs()]}function br(e={}){let t=!e.skipBrowserExtensionCheck&&la(),i=e.defaultIntegrations==null?pa():e.defaultIntegrations,r=Ei({integrations:e.integrations,defaultIntegrations:i}),n={...e,enabled:t?!1:e.enabled,stackParser:Xt(e.stackParser||Ks),integrations:r,transport:e.transport||js};return li(oa),Fi(Vt,n)}return ba(sc);})(); diff --git a/resources/views/components/monitoring/browser-errors.blade.php b/resources/views/components/monitoring/browser-errors.blade.php new file mode 100644 index 00000000..89345c56 --- /dev/null +++ b/resources/views/components/monitoring/browser-errors.blade.php @@ -0,0 +1,15 @@ +@props(['area' => 'web']) +{{-- Browserfehler an GlitchTip. Ohne SENTRY_BROWSER_DSN wird nichts eingebunden; + das SDK selbst lädt erst beim ersten Fehler (public/js/error-reporting/). --}} +@if (filled(config('monitoring.browser.dsn'))) + + +@endif diff --git a/resources/views/layouts/application.blade.php b/resources/views/layouts/application.blade.php index 45b40fda..ee171027 100755 --- a/resources/views/layouts/application.blade.php +++ b/resources/views/layouts/application.blade.php @@ -54,6 +54,7 @@ + diff --git a/resources/views/layouts/auth.blade.php b/resources/views/layouts/auth.blade.php index 15e1a287..203199c9 100755 --- a/resources/views/layouts/auth.blade.php +++ b/resources/views/layouts/auth.blade.php @@ -67,6 +67,7 @@ })}); + diff --git a/resources/views/portal/layouts/application.blade.php b/resources/views/portal/layouts/application.blade.php index 61d841f0..9232fbd0 100644 --- a/resources/views/portal/layouts/application.blade.php +++ b/resources/views/portal/layouts/application.blade.php @@ -79,6 +79,7 @@ + diff --git a/resources/views/portal/layouts/auth.blade.php b/resources/views/portal/layouts/auth.blade.php index 24e67774..1334d0cc 100755 --- a/resources/views/portal/layouts/auth.blade.php +++ b/resources/views/portal/layouts/auth.blade.php @@ -67,6 +67,7 @@ })}); + diff --git a/resources/views/portal/layouts/public.blade.php b/resources/views/portal/layouts/public.blade.php index 9173e60f..c05caf96 100644 --- a/resources/views/portal/layouts/public.blade.php +++ b/resources/views/portal/layouts/public.blade.php @@ -28,6 +28,7 @@ + diff --git a/resources/views/web/layouts/application.blade.php b/resources/views/web/layouts/application.blade.php index 80432cb5..57fa29a4 100644 --- a/resources/views/web/layouts/application.blade.php +++ b/resources/views/web/layouts/application.blade.php @@ -88,6 +88,7 @@ color: #fff !important; } + diff --git a/resources/views/web/user/layouts/application.blade.php b/resources/views/web/user/layouts/application.blade.php index 6c0ef9af..f58cfa7b 100644 --- a/resources/views/web/user/layouts/application.blade.php +++ b/resources/views/web/user/layouts/application.blade.php @@ -119,6 +119,7 @@ border-color: #989a9c; } + diff --git a/tests/Feature/Monitoring/BrowserErrorReportingTest.php b/tests/Feature/Monitoring/BrowserErrorReportingTest.php new file mode 100644 index 00000000..9a245bdc --- /dev/null +++ b/tests/Feature/Monitoring/BrowserErrorReportingTest.php @@ -0,0 +1,60 @@ +set('monitoring.browser.dsn', null); + + expect(trim(Blade::render('')))->toBe(''); +}); + +test('the configuration carries dsn, release, sdk and the user id only', function () { + config()->set('monitoring.browser.dsn', 'https://public@glitchtip.test/2'); + config()->set('sentry.release', 'v1.3.0'); + $user = User::forceCreate(['email' => 'kunde@example.com', 'password' => bcrypt('secret'), 'lang' => 'de']); + $this->actingAs($user); + + $html = Blade::render(''); + + preg_match('/toMatchArray([ + 'dsn' => 'https://public@glitchtip.test/2', + 'release' => 'v1.3.0', + 'userId' => $user->id, + 'area' => 'portal', + ]) + ->and($config['sdk'])->toEndWith('/js/error-reporting/sentry-browser-'.config('monitoring.browser.sdk_version').'.min.js') + ->and($html)->toContain('js/error-reporting/error-reporting.js') + ->and($html)->not->toContain('kunde@example.com'); +}); + +test('the self-hosted sdk and the loader exist and the loader avoids regex lookbehind', function () { + $loader = file_get_contents(public_path('js/error-reporting/error-reporting.js')); + + expect(public_path('js/error-reporting/sentry-browser-'.config('monitoring.browser.sdk_version').'.min.js'))->toBeFile() + ->and($loader)->not->toContain('(?<=') + ->and($loader)->not->toContain('(?toContain('with([ + 'layouts/application', + 'layouts/auth', + 'portal/layouts/application', + 'portal/layouts/auth', + 'portal/layouts/public', + 'web/layouts/application', + 'web/user/layouts/application', +]); diff --git a/tests/Feature/Monitoring/ErrorReportingTest.php b/tests/Feature/Monitoring/ErrorReportingTest.php new file mode 100644 index 00000000..25064107 --- /dev/null +++ b/tests/Feature/Monitoring/ErrorReportingTest.php @@ -0,0 +1,104 @@ +set('app.exception_mail', null); +}); + +test('reported exceptions reach glitchtip with the user id only', function () { + $glitchTip = FakeGlitchTip::install(); + $user = User::forceCreate(['email' => 'kunde@example.com', 'password' => bcrypt('secret'), 'lang' => 'de']); + $this->actingAs($user); + + report(new RuntimeException('Import für kunde@example.com fehlgeschlagen')); + + expect($glitchTip->events)->toHaveCount(1); + + $event = $glitchTip->events[0]; + expect($event->getUser()?->getId())->toBe($user->id) + ->and($event->getUser()?->getEmail())->toBeNull() + ->and($event->getExceptions()[0]->getValue())->toBe('Import für [email] fehlgeschlagen'); +}); + +test('exceptions laravel does not report stay out of glitchtip', function () { + $glitchTip = FakeGlitchTip::install(); + + $this->get('/diese/seite/gibt-es/nicht')->assertNotFound(); + + expect($glitchTip->events)->toBe([]); +}); + +test('a mistyped artisan command is not reported', function () { + $glitchTip = FakeGlitchTip::install(); + + report(new CommandNotFoundException('Command "config:cachels" is not defined.')); + + expect($glitchTip->events)->toBe([]); +}); + +test('request data loses tokens, signatures and bodies', function () { + $scrubbed = ErrorReportScrubber::scrubRequest([ + 'url' => 'https://my.mivita.care/einladung/5?signature=abc123&expires=1700000000&page=2', + 'query_string' => 'signature=abc123&expires=1700000000&page=2&utm_source=nl', + 'data' => ['password' => 'geheim'], + 'cookies' => ['mivita_session' => 'x'], + 'headers' => ['referer' => ['https://my.mivita.care/password/reset/'.str_repeat('a', 64).'?email=kunde@example.com']], + ]); + + expect($scrubbed['url'])->toBe('https://my.mivita.care/einladung/5?signature=%5BFiltered%5D&expires=%5BFiltered%5D&page=2') + ->and($scrubbed['query_string'])->toBe('signature=%5BFiltered%5D&expires=%5BFiltered%5D&page=2&utm_source=nl') + ->and($scrubbed)->not->toHaveKeys(['data', 'cookies']) + ->and($scrubbed['headers']['referer'][0])->toBe('https://my.mivita.care/password/reset/[Filtered]?email=%5BFiltered%5D'); +}); + +test('route parameters named like secrets are removed from the url', function () { + Route::middleware('web')->get('/_test/monitoring/magic/{token}', function () { + $event = Event::createEvent(); + $event->setRequest(['url' => request()->fullUrl()]); + + return ErrorReportScrubber::beforeSend($event)?->getRequest()['url']; + }); + + $this->get('/_test/monitoring/magic/kurz123') + ->assertSuccessful() + ->assertSee('/_test/monitoring/magic/[Filtered]', false) + ->assertDontSee('kurz123'); +}); + +test('messages and exception values lose e-mail addresses', function () { + $event = Event::createEvent(); + $event->setMessage('Bounce von info@firma.de', [], 'Bounce von info@firma.de'); + $event->setExceptions([new ExceptionDataBag(new RuntimeException('Duplicate entry \'a.b@c.org\''))]); + + $scrubbed = ErrorReportScrubber::beforeSend($event); + + expect($scrubbed?->getMessage())->toBe('Bounce von [email]') + ->and($scrubbed?->getMessageFormatted())->toBe('Bounce von [email]') + ->and($scrubbed?->getExceptions()[0]->getValue())->toBe('Duplicate entry \'[email]\'') + ->and($scrubbed?->getUser())->toBeNull(); +}); + +test('the test suite never reports to the real glitchtip or ntfy', function () { + // Ohne diese Absicherung landeten absichtlich provozierte Fehler + // („Queue weg“, „Datenbank nicht erreichbar“) in GlitchTip bzw. als Push. + expect(config('sentry.dsn'))->toBeEmpty() + ->and(config('monitoring.browser.dsn'))->toBeEmpty() + ->and(config('monitoring.alerts.ntfy.url'))->toBeEmpty() + ->and(SentrySdk::getCurrentHub()->getClient()?->getOptions()->getDsn())->toBeNull(); +}); diff --git a/tests/Feature/Monitoring/ErrorSignalsTest.php b/tests/Feature/Monitoring/ErrorSignalsTest.php new file mode 100644 index 00000000..f90c9804 --- /dev/null +++ b/tests/Feature/Monitoring/ErrorSignalsTest.php @@ -0,0 +1,73 @@ +set('cache.default', 'array'); + $this->withoutDefer(); + $this->glitchTip = FakeGlitchTip::install(); + + Route::middleware('web')->get('/_test/monitoring/signal/forbidden', fn () => abort(403))->name('test.forbidden'); + Route::middleware('web')->get('/_test/monitoring/signal/expired', fn () => abort(419))->name('test.expired'); + Route::middleware('web')->get('/_test/monitoring/signal/throttled', fn () => abort(429))->name('test.throttled'); +}); + +test('a 404 reached from one of our own pages is reported as a dead internal link', function () { + $this->get('/gibt/es/nicht/mehr', ['referer' => url('/produkte/aloe')])->assertNotFound(); + + expect($this->glitchTip->messages())->toBe(['Toter interner Link: /gibt/es/nicht/mehr']); + + $event = $this->glitchTip->events[0]; + expect($event->getTags())->toMatchArray(['signal' => ErrorSignal::DeadInternalLink]) + ->and($event->getFingerprint())->toBe(['error-signal', ErrorSignal::DeadInternalLink, '/gibt/es/nicht/mehr']) + ->and($event->getContexts()['signal']['referer'])->toBe('/produkte/aloe'); +}); + +test('a 404 without referer or from another site is not reported', function () { + $this->get('/gibt/es/nicht/mehr')->assertNotFound(); + $this->get('/gibt/es/nicht/mehr', ['referer' => 'https://google.com/search'])->assertNotFound(); + + expect($this->glitchTip->events)->toBe([]); +}); + +test('403 and 429 are reported only for signed-in users', function () { + $this->get('/_test/monitoring/signal/forbidden')->assertForbidden(); + $this->get('/_test/monitoring/signal/throttled')->assertTooManyRequests(); + + expect($this->glitchTip->events)->toBe([]); + + $this->actingAs(User::forceCreate(['email' => 'berater@example.com', 'password' => bcrypt('secret'), 'lang' => 'de'])); + $this->get('/_test/monitoring/signal/forbidden')->assertForbidden(); + $this->get('/_test/monitoring/signal/throttled')->assertTooManyRequests(); + + expect($this->glitchTip->messages())->toBe([ + 'Zugriff verweigert (angemeldet): test.forbidden', + 'Drosselung trifft angemeldeten Nutzer: test.throttled', + ]); +}); + +test('an expired page is always reported', function () { + $this->get('/_test/monitoring/signal/expired')->assertStatus(419); + + expect($this->glitchTip->messages())->toBe(['Seite abgelaufen (419): test.expired']); +}); + +test('each signal group is capped at five reports per window', function () { + foreach (range(1, 7) as $attempt) { + $this->get('/_test/monitoring/signal/expired'); + } + + expect($this->glitchTip->events)->toHaveCount(5); +}); diff --git a/tests/Feature/Monitoring/SystemStatusPageTest.php b/tests/Feature/Monitoring/SystemStatusPageTest.php index 1db0ed1c..e1f5789c 100644 --- a/tests/Feature/Monitoring/SystemStatusPageTest.php +++ b/tests/Feature/Monitoring/SystemStatusPageTest.php @@ -57,6 +57,7 @@ function statusPageUser(int $admin): User test('system admins see the checks, the version and the menu entry', function () { config()->set('app.version', 'v1.0.2'); + config()->set('monitoring.health.token', null); app(HealthProbe::class)->recordHeartbeat(); $this->actingAs(statusPageUser(4)) diff --git a/tests/Support/FakeGlitchTip.php b/tests/Support/FakeGlitchTip.php new file mode 100644 index 00000000..f3c38b1a --- /dev/null +++ b/tests/Support/FakeGlitchTip.php @@ -0,0 +1,63 @@ +events + */ +class FakeGlitchTip implements TransportInterface +{ + /** @var list */ + public array $events = []; + + public static function install(): self + { + $transport = new self; + + $client = ClientBuilder::create([ + 'dsn' => 'https://public@glitchtip.test/1', + 'default_integrations' => false, + 'send_default_pii' => config('sentry.send_default_pii'), + 'max_request_body_size' => config('sentry.max_request_body_size'), + 'before_send' => config('sentry.before_send'), + ])->setTransport($transport)->getClient(); + + SentrySdk::getCurrentHub()->bindClient($client); + + return $transport; + } + + public function send(Event $event): Result + { + $this->events[] = $event; + + return new Result(ResultStatus::success(), $event); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); + } + + /** + * @return list + */ + public function messages(): array + { + return array_values(array_map( + static fn (Event $event): string => (string) ($event->getMessage() ?? $event->getExceptions()[0]?->getValue()), + $this->events, + )); + } +}