@@ -51,7 +51,12 @@ final class SentryReportingService implements ReportingService {
5151 ..debug = config.debug
5252 ..diagnosticLevel = SentryLevel .fromName (config.diagnosticLevel)
5353 ..release = config.release
54- ..dist = config.dist;
54+ ..dist = config.dist
55+ ..beforeSend = filterThirdPartyErrors
56+ ..addInAppInclude ('catalyst' )
57+ ..addInAppExclude ('chrome-extension://' )
58+ ..addInAppExclude ('moz-extension://' )
59+ ..addInAppExclude ('safari-web-extension://' );
5560
5661 if (config.enableLogs) {
5762 options.addIntegration (LoggingIntegration ());
@@ -97,4 +102,50 @@ final class SentryReportingService implements ReportingService {
97102
98103 @override
99104 Widget wrapApp (Widget app) => SentryWidget (child: app);
105+
106+ /// Filters out errors originating from third-party browser extensions
107+ /// that don't involve our application code.
108+ ///
109+ /// This prevents noise from wallet extensions (e.g., Talisman, MetaMask)
110+ /// reporting their own internal errors that are unrelated to Catalyst Voices.
111+ ///
112+ /// Errors are kept if:
113+ /// - They originate from our app code (catalyst_* packages)
114+ /// - They occur during wallet interactions initiated by our app
115+ ///
116+ /// Errors are filtered if:
117+ /// - They only have browser extension frames (no app involvement)
118+ /// - They are pure third-party extension errors
119+ @visibleForTesting
120+ static FutureOr <SentryEvent ?> filterThirdPartyErrors (
121+ SentryEvent event,
122+ Hint hint,
123+ ) {
124+ final exceptions = event.exceptions;
125+ if (exceptions == null || exceptions.isEmpty) {
126+ return event;
127+ }
128+
129+ final containsAppFrames = exceptions.any ((exception) {
130+ final stackTrace = exception.stackTrace;
131+ // If no stack trace, assume it's from our app to be safe
132+ if (stackTrace == null ) return true ;
133+
134+ final frames = stackTrace.frames;
135+ if (frames.isEmpty) return false ;
136+
137+ // Check if ALL frames in this exception are from browser extensions
138+ final allFramesAreExtensions = frames.every ((frame) {
139+ final absPath = (frame.absPath ?? '' ).toLowerCase ();
140+ return absPath.contains ('chrome-extension://' ) ||
141+ absPath.contains ('moz-extension://' ) ||
142+ absPath.contains ('safari-web-extension://' );
143+ });
144+
145+ // If all frames are from extensions, filter out this exception
146+ return ! allFramesAreExtensions;
147+ });
148+
149+ return containsAppFrames ? event : null ;
150+ }
100151}
0 commit comments