Add opt-in error reporting via GlitchTip (Sentry-compatible)#109
Conversation
Port the legacy-godot client's error reporting stack to Flutter: - GlitchTipClient: pure Dart Sentry store-API client (DSN parsing, tags, breadcrumb ring buffer, message/error capture), like the reference's glitchtip_client.gd — no native SDK dependency. - ErrorReportingController: consent-gated Riverpod service that wires PII scrubbing (tokens, token= params, URLs with ports), global FlutterError/PlatformDispatcher capture hooks, context tags (server count, truncated space/channel ids), and breadcrumbs for navigation, space/channel switches, and message sends. - First-launch consent dialog (asked once, stamped in settings), settings toggle, and a "Report a problem" feedback dialog that sends a type=user-feedback tagged event. - Settings model/controller: errorReportingEnabled + errorReportingConsentShown, off by default; nothing is sent without consent. - release.yml injects the SENTRY_DSN secret at build time; the dev DSN stays in-repo for local testing, as before. - Tests: DSN parsing, event payloads, breadcrumb cap, PII scrubbing (ported from test_error_reporting.gd), consent gating. https://claude.ai/code/session_01BWiFwfBbVEzQr9Tdgq33F6
- reportProblem permanently stamped type=user-feedback on the client;
all subsequent auto-captured errors inherited it. Fixed with a
try/finally that calls client.removeTag('type') after each send.
- GlitchTipClient._http was never closed; toggling consent on/off
leaked a connection pool each time. Added close() and call it from
ref.onDispose alongside the _active nulling.
- Random() replaced with Random.secure() for UUID v4 event IDs.
- while loop for breadcrumb capping replaced with if (one added at a
time, so while never loops more than once).
- Added test for removeTag to prevent regression.
https://claude.ai/code/session_011DSV18kWfoLYNyUgStp9JP
Code Review — PR #109 (opt-in error reporting)Fixes pushed in commit 🔴 HIGH — Tag pollution bug in
|
Summary
Implements anonymous, opt-in error reporting to a self-hosted GlitchTip instance, mirroring the reference Godot client's error reporting feature. Nothing is sent until the user explicitly consents via a first-launch dialog or the Settings toggle. All personally identifiable information (tokens, URLs, message content) is scrubbed before transmission.
Key Changes
GlitchTipClient (
lib/features/error_reporting/repositories/glitchtip_client.dart): Pure Dart HTTP client for the Sentry-compatible store API. Handles DSN parsing, event building, breadcrumb management, and stack frame parsing. Deliberately avoids native SDKs for cross-platform consistency (web, mobile, desktop).ErrorReportingController (
lib/features/error_reporting/controllers/error_reporting.dart): Riverpod-based state management for error reporting. Wraps globalFlutterError.onErrorandPlatformDispatcher.onErrorhooks once, capturing unhandled exceptions and async errors. Provides breadcrumb recording for navigation, messaging, and voice events. All text is scrubbed viascrubPiiText()before leaving the device.PII Scrubbing (
scrubPiiText): Redacts Bearer tokens,token=query parameters,dk_hex tokens, bare 64-char hex strings, and URLs with explicit ports — matching the reference client's patterns.Settings Integration: Added
errorReportingEnabledanderrorReportingConsentShownflags toAccordSettings. First-launch consent dialog shown inmain.dart(never reshown once answered). Settings screen includes toggle and "Report a Problem" dialog for user-initiated feedback.Release Build Integration (
.github/workflows/release.yml): InjectsSENTRY_DSNsecret at build time via--dart-define. Falls back to in-repo dev DSN when unset, mirroring the reference client'sproject.godotsetup.Comprehensive Tests (
test/features/error_reporting/): Unit tests for DSN parsing, PII scrubbing, event sending, breadcrumb capping, and settings round-trip serialization.Implementation Details
No-op until consent: All guarded methods (
addBreadcrumb,captureMessage,captureError,reportProblem) return immediately if reporting is disabled, so toggling consent requires no re-initialization.Global hooks installed once:
FlutterError.onErrorandPlatformDispatcher.onErrorare wrapped exactly once and chain to any previously installed handlers (e.g., LiveKit noise filters inmain.dart).Breadcrumb ring buffer: Capped at 100 entries; oldest are dropped when exceeded. Attached to every subsequent event.
Stack frame parsing: Lenient regex-based parser (matching the reference client) handles both well-formed Dart frames and anomalies like
<asynchronous suspension>.Event ID tracking: UUID v4 (32 hex chars, no dashes) generated per event;
lastEventIdexposed for correlation in the UI.Transport resilience: HTTP errors never throw back into the app; all exceptions are silently caught.
https://claude.ai/code/session_01BWiFwfBbVEzQr9Tdgq33F6