@@ -2,6 +2,7 @@ import { defineIntegration } from '../integration';
22import { addMetadataToStackFrames , stripMetadataFromStackFrames } from '../metadata' ;
33import type { EventItem } from '../types-hoist/envelope' ;
44import type { Event } from '../types-hoist/event' ;
5+ import type { StackFrame } from '../types-hoist/stackframe' ;
56import { forEachEnvelopeItem } from '../utils/envelope' ;
67import { getFramesFromEvent } from '../utils/stacktrace' ;
78
@@ -32,6 +33,13 @@ interface Options {
3233 | 'drop-error-if-exclusively-contains-third-party-frames'
3334 | 'apply-tag-if-contains-third-party-frames'
3435 | 'apply-tag-if-exclusively-contains-third-party-frames' ;
36+
37+ /**
38+ * @experimental
39+ * If set to true, the integration will exclude frames that are internal to the Sentry SDK from the third-party frame detection.
40+ * Note that enabling this option might lead to errors being misclassified as third-party errors.
41+ */
42+ experimentalExcludeSentryInternalFrames ?: boolean ;
3543}
3644
3745/**
@@ -67,7 +75,7 @@ export const thirdPartyErrorFilterIntegration = defineIntegration((options: Opti
6775 } ,
6876
6977 processEvent ( event ) {
70- const frameKeys = getBundleKeysForAllFramesWithFilenames ( event ) ;
78+ const frameKeys = getBundleKeysForAllFramesWithFilenames ( event , options . experimentalExcludeSentryInternalFrames ) ;
7179
7280 if ( frameKeys ) {
7381 const arrayMethod =
@@ -98,27 +106,73 @@ export const thirdPartyErrorFilterIntegration = defineIntegration((options: Opti
98106 } ;
99107} ) ;
100108
101- function getBundleKeysForAllFramesWithFilenames ( event : Event ) : string [ ] [ ] | undefined {
109+ /**
110+ * Checks if a stack frame is a Sentry internal frame by strictly matching:
111+ * 1. The frame must be the last frame in the stack
112+ * 2. The filename must indicate the internal helpers file
113+ * 3. The context_line must contain the exact pattern "fn.apply(this, wrappedArguments)"
114+ * 4. The comment pattern "Attempt to invoke user-land function" must be present in pre_context
115+ *
116+ */
117+ function isSentryInternalFrame ( frame : StackFrame , frameIndex : number ) : boolean {
118+ // Only match the last frame (index 0 in reversed stack)
119+ if ( frameIndex !== 0 || ! frame . context_line || ! frame . filename ) {
120+ return false ;
121+ }
122+
123+ // Filename would look something like this: 'node_modules/@sentry/browser/build/npm/esm/helpers.js'
124+ if ( ! frame . filename . includes ( 'sentry' ) || ! frame . filename . includes ( 'helpers' ) ) {
125+ return false ;
126+ }
127+
128+ // Must have context_line with the exact fn.apply pattern (case-sensitive)
129+ if ( ! frame . context_line . includes ( SENTRY_INTERNAL_FN_APPLY ) ) {
130+ return false ;
131+ }
132+
133+ // Check pre_context array for comment pattern
134+ if ( frame . pre_context ) {
135+ const len = frame . pre_context . length ;
136+ for ( let i = 0 ; i < len ; i ++ ) {
137+ if ( frame . pre_context [ i ] ?. includes ( SENTRY_INTERNAL_COMMENT ) ) {
138+ return true ;
139+ }
140+ }
141+ }
142+
143+ return false ;
144+ }
145+
146+ function getBundleKeysForAllFramesWithFilenames (
147+ event : Event ,
148+ excludeSentryInternalFrames ?: boolean ,
149+ ) : string [ ] [ ] | undefined {
102150 const frames = getFramesFromEvent ( event ) ;
103151
104152 if ( ! frames ) {
105153 return undefined ;
106154 }
107155
108- return (
109- frames
156+ return frames
157+ . filter ( ( frame , index ) => {
110158 // Exclude frames without a filename or without lineno and colno,
111159 // since these are likely native code or built-ins
112- . filter ( frame => ! ! frame . filename && ( frame . lineno ?? frame . colno ) != null )
113- . map ( frame => {
114- if ( frame . module_metadata ) {
115- return Object . keys ( frame . module_metadata )
116- . filter ( key => key . startsWith ( BUNDLER_PLUGIN_APP_KEY_PREFIX ) )
117- . map ( key => key . slice ( BUNDLER_PLUGIN_APP_KEY_PREFIX . length ) ) ;
118- }
160+ if ( ! frame . filename || ( frame . lineno == null && frame . colno == null ) ) {
161+ return false ;
162+ }
163+ // Optionally exclude Sentry internal frames
164+ return ! excludeSentryInternalFrames || ! isSentryInternalFrame ( frame , index ) ;
165+ } )
166+ . map ( frame => {
167+ if ( ! frame . module_metadata ) {
119168 return [ ] ;
120- } )
121- ) ;
169+ }
170+ return Object . keys ( frame . module_metadata )
171+ . filter ( key => key . startsWith ( BUNDLER_PLUGIN_APP_KEY_PREFIX ) )
172+ . map ( key => key . slice ( BUNDLER_PLUGIN_APP_KEY_PREFIX . length ) ) ;
173+ } ) ;
122174}
123175
124176const BUNDLER_PLUGIN_APP_KEY_PREFIX = '_sentryBundlerPluginAppKey:' ;
177+ const SENTRY_INTERNAL_COMMENT = 'Attempt to invoke user-land function' ;
178+ const SENTRY_INTERNAL_FN_APPLY = 'fn.apply(this, wrappedArguments)' ;
0 commit comments