@@ -83,202 +83,203 @@ export class TryCatch implements Integration {
8383 const global = getGlobalObject ( ) ;
8484
8585 if ( this . _options . setTimeout ) {
86- fill ( global , 'setTimeout' , this . _wrapTimeFunction . bind ( this ) ) ;
86+ fill ( global , 'setTimeout' , _wrapTimeFunction ) ;
8787 }
8888
8989 if ( this . _options . setInterval ) {
90- fill ( global , 'setInterval' , this . _wrapTimeFunction . bind ( this ) ) ;
90+ fill ( global , 'setInterval' , _wrapTimeFunction ) ;
9191 }
9292
9393 if ( this . _options . requestAnimationFrame ) {
94- fill ( global , 'requestAnimationFrame' , this . _wrapRAF . bind ( this ) ) ;
94+ fill ( global , 'requestAnimationFrame' , _wrapRAF ) ;
9595 }
9696
9797 if ( this . _options . XMLHttpRequest && 'XMLHttpRequest' in global ) {
98- fill ( XMLHttpRequest . prototype , 'send' , this . _wrapXHR . bind ( this ) ) ;
98+ fill ( XMLHttpRequest . prototype , 'send' , _wrapXHR ) ;
9999 }
100100
101- if ( this . _options . eventTarget ) {
102- const eventTarget = Array . isArray ( this . _options . eventTarget ) ? this . _options . eventTarget : DEFAULT_EVENT_TARGET ;
103- eventTarget . forEach ( this . _wrapEventTarget . bind ( this ) ) ;
101+ const eventTargetOption = this . _options . eventTarget ;
102+ if ( eventTargetOption ) {
103+ const eventTarget = Array . isArray ( eventTargetOption ) ? eventTargetOption : DEFAULT_EVENT_TARGET ;
104+ eventTarget . forEach ( _wrapEventTarget ) ;
104105 }
105106 }
107+ }
106108
107- /** JSDoc */
108- private _wrapTimeFunction ( original : ( ) => void ) : ( ) => number {
109- // eslint-disable-next-line @typescript-eslint/no-explicit-any
110- return function ( this : any , ...args : any [ ] ) : number {
111- const originalCallback = args [ 0 ] ;
112- args [ 0 ] = wrap ( originalCallback , {
109+ /** JSDoc */
110+ function _wrapTimeFunction ( original : ( ) => void ) : ( ) => number {
111+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112+ return function ( this : any , ...args : any [ ] ) : number {
113+ const originalCallback = args [ 0 ] ;
114+ args [ 0 ] = wrap ( originalCallback , {
115+ mechanism : {
116+ data : { function : getFunctionName ( original ) } ,
117+ handled : true ,
118+ type : 'instrument' ,
119+ } ,
120+ } ) ;
121+ return original . apply ( this , args ) ;
122+ } ;
123+ }
124+
125+ /** JSDoc */
126+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
127+ function _wrapRAF ( original : any ) : ( callback : ( ) => void ) => any {
128+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
129+ return function ( this : any , callback : ( ) => void ) : ( ) => void {
130+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
131+ return original . call (
132+ this ,
133+ wrap ( callback , {
113134 mechanism : {
114- data : { function : getFunctionName ( original ) } ,
135+ data : {
136+ function : 'requestAnimationFrame' ,
137+ handler : getFunctionName ( original ) ,
138+ } ,
115139 handled : true ,
116140 type : 'instrument' ,
117141 } ,
118- } ) ;
119- return original . apply ( this , args ) ;
120- } ;
121- }
142+ } ) ,
143+ ) ;
144+ } ;
145+ }
122146
123- /** JSDoc */
147+ /** JSDoc */
148+ function _wrapXHR ( originalSend : ( ) => void ) : ( ) => void {
124149 // eslint-disable-next-line @typescript-eslint/no-explicit-any
125- private _wrapRAF ( original : any ) : ( callback : ( ) => void ) => any {
126- // eslint-disable-next-line @typescript-eslint/no-explicit-any
127- return function ( this : any , callback : ( ) => void ) : ( ) => void {
128- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
129- return original . call (
130- this ,
131- wrap ( callback , {
132- mechanism : {
133- data : {
134- function : 'requestAnimationFrame' ,
135- handler : getFunctionName ( original ) ,
136- } ,
137- handled : true ,
138- type : 'instrument' ,
139- } ,
140- } ) ,
141- ) ;
142- } ;
143- }
144-
145- /** JSDoc */
146- private _wrapEventTarget ( target : string ) : void {
147- // eslint-disable-next-line @typescript-eslint/no-explicit-any
148- const global = getGlobalObject ( ) as { [ key : string ] : any } ;
149- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
150- const proto = global [ target ] && global [ target ] . prototype ;
151-
152- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins
153- if ( ! proto || ! proto . hasOwnProperty || ! proto . hasOwnProperty ( 'addEventListener' ) ) {
154- return ;
155- }
150+ return function ( this : XMLHttpRequest , ...args : any [ ] ) : void {
151+ // eslint-disable-next-line @typescript-eslint/no-this-alias
152+ const xhr = this ;
153+ const xmlHttpRequestProps : XMLHttpRequestProp [ ] = [ 'onload' , 'onerror' , 'onprogress' , 'onreadystatechange' ] ;
156154
157- fill ( proto , 'addEventListener' , function (
158- original : ( ) => void ,
159- ) : ( eventName : string , fn : EventListenerObject , options ?: boolean | AddEventListenerOptions ) => void {
160- return function (
155+ xmlHttpRequestProps . forEach ( prop => {
156+ if ( prop in xhr && typeof xhr [ prop ] === 'function' ) {
161157 // eslint-disable-next-line @typescript-eslint/no-explicit-any
162- this : any ,
163- eventName : string ,
164- fn : EventListenerObject ,
165- options ?: boolean | AddEventListenerOptions ,
166- ) : ( eventName : string , fn : EventListenerObject , capture ?: boolean , secure ?: boolean ) => void {
167- try {
168- if ( typeof fn . handleEvent === 'function' ) {
169- fn . handleEvent = wrap ( fn . handleEvent . bind ( fn ) , {
170- mechanism : {
171- data : {
172- function : 'handleEvent' ,
173- handler : getFunctionName ( fn ) ,
174- target,
175- } ,
176- handled : true ,
177- type : 'instrument' ,
178- } ,
179- } ) ;
180- }
181- } catch ( err ) {
182- // can sometimes get 'Permission denied to access property "handle Event'
183- }
184-
185- return original . call (
186- this ,
187- eventName ,
188- // eslint-disable-next-line @typescript-eslint/no-explicit-any
189- wrap ( ( fn as any ) as WrappedFunction , {
158+ fill ( xhr , prop , function ( original : WrappedFunction ) : ( ) => any {
159+ const wrapOptions = {
190160 mechanism : {
191161 data : {
192- function : 'addEventListener' ,
193- handler : getFunctionName ( fn ) ,
194- target,
162+ function : prop ,
163+ handler : getFunctionName ( original ) ,
195164 } ,
196165 handled : true ,
197166 type : 'instrument' ,
198167 } ,
199- } ) ,
200- options ,
201- ) ;
202- } ;
203- } ) ;
168+ } ;
204169
205- fill ( proto , 'removeEventListener' , function (
206- originalRemoveEventListener : ( ) => void ,
207- // eslint-disable-next-line @typescript-eslint/no-explicit-any
208- ) : ( this : any , eventName : string , fn : EventListenerObject , options ?: boolean | EventListenerOptions ) => ( ) => void {
209- return function (
210- // eslint-disable-next-line @typescript-eslint/no-explicit-any
211- this : any ,
212- eventName : string ,
213- fn : EventListenerObject ,
214- options ?: boolean | EventListenerOptions ,
215- ) : ( ) => void {
216- /**
217- * There are 2 possible scenarios here:
218- *
219- * 1. Someone passes a callback, which was attached prior to Sentry initialization, or by using unmodified
220- * method, eg. `document.addEventListener.call(el, name, handler). In this case, we treat this function
221- * as a pass-through, and call original `removeEventListener` with it.
222- *
223- * 2. Someone passes a callback, which was attached after Sentry was initialized, which means that it was using
224- * our wrapped version of `addEventListener`, which internally calls `wrap` helper.
225- * This helper "wraps" whole callback inside a try/catch statement, and attached appropriate metadata to it,
226- * in order for us to make a distinction between wrapped/non-wrapped functions possible.
227- * If a function was wrapped, it has additional property of `__sentry_wrapped__`, holding the handler.
228- *
229- * When someone adds a handler prior to initialization, and then do it again, but after,
230- * then we have to detach both of them. Otherwise, if we'd detach only wrapped one, it'd be impossible
231- * to get rid of the initial handler and it'd stick there forever.
232- */
233- const wrappedEventHandler = ( fn as unknown ) as WrappedFunction ;
234- try {
235- const originalEventHandler = wrappedEventHandler && wrappedEventHandler . __sentry_wrapped__ ;
236- if ( originalEventHandler ) {
237- originalRemoveEventListener . call ( this , eventName , originalEventHandler , options ) ;
170+ // If Instrument integration has been called before TryCatch, get the name of original function
171+ if ( original . __sentry_original__ ) {
172+ wrapOptions . mechanism . data . handler = getFunctionName ( original . __sentry_original__ ) ;
238173 }
239- } catch ( e ) {
240- // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
241- }
242- return originalRemoveEventListener . call ( this , eventName , wrappedEventHandler , options ) ;
243- } ;
174+
175+ // Otherwise wrap directly
176+ return wrap ( original , wrapOptions ) ;
177+ } ) ;
178+ }
244179 } ) ;
245- }
246180
247- /** JSDoc */
248- private _wrapXHR ( originalSend : ( ) => void ) : ( ) => void {
249- // eslint-disable-next-line @typescript-eslint/no-explicit-any
250- return function ( this : XMLHttpRequest , ...args : any [ ] ) : void {
251- // eslint-disable-next-line @typescript-eslint/no-this-alias
252- const xhr = this ;
253- const xmlHttpRequestProps : XMLHttpRequestProp [ ] = [ 'onload' , 'onerror' , 'onprogress' , 'onreadystatechange' ] ;
181+ return originalSend . apply ( this , args ) ;
182+ } ;
183+ }
254184
255- xmlHttpRequestProps . forEach ( prop => {
256- if ( prop in xhr && typeof xhr [ prop ] === 'function' ) {
257- // eslint-disable-next-line @typescript-eslint/no-explicit-any
258- fill ( xhr , prop , function ( original : WrappedFunction ) : ( ) => any {
259- const wrapOptions = {
260- mechanism : {
261- data : {
262- function : prop ,
263- handler : getFunctionName ( original ) ,
264- } ,
265- handled : true ,
266- type : 'instrument' ,
267- } ,
268- } ;
185+ /** JSDoc */
186+ function _wrapEventTarget ( target : string ) : void {
187+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
188+ const global = getGlobalObject ( ) as { [ key : string ] : any } ;
189+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
190+ const proto = global [ target ] && global [ target ] . prototype ;
269191
270- // If Instrument integration has been called before TryCatch, get the name of original function
271- if ( original . __sentry_original__ ) {
272- wrapOptions . mechanism . data . handler = getFunctionName ( original . __sentry_original__ ) ;
273- }
192+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins
193+ if ( ! proto || ! proto . hasOwnProperty || ! proto . hasOwnProperty ( 'addEventListener' ) ) {
194+ return ;
195+ }
274196
275- // Otherwise wrap directly
276- return wrap ( original , wrapOptions ) ;
197+ fill ( proto , 'addEventListener' , function (
198+ original : ( ) => void ,
199+ ) : ( eventName : string , fn : EventListenerObject , options ?: boolean | AddEventListenerOptions ) => void {
200+ return function (
201+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
202+ this : any ,
203+ eventName : string ,
204+ fn : EventListenerObject ,
205+ options ?: boolean | AddEventListenerOptions ,
206+ ) : ( eventName : string , fn : EventListenerObject , capture ?: boolean , secure ?: boolean ) => void {
207+ try {
208+ if ( typeof fn . handleEvent === 'function' ) {
209+ fn . handleEvent = wrap ( fn . handleEvent . bind ( fn ) , {
210+ mechanism : {
211+ data : {
212+ function : 'handleEvent' ,
213+ handler : getFunctionName ( fn ) ,
214+ target,
215+ } ,
216+ handled : true ,
217+ type : 'instrument' ,
218+ } ,
277219 } ) ;
278220 }
279- } ) ;
221+ } catch ( err ) {
222+ // can sometimes get 'Permission denied to access property "handle Event'
223+ }
224+
225+ return original . call (
226+ this ,
227+ eventName ,
228+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
229+ wrap ( ( fn as any ) as WrappedFunction , {
230+ mechanism : {
231+ data : {
232+ function : 'addEventListener' ,
233+ handler : getFunctionName ( fn ) ,
234+ target,
235+ } ,
236+ handled : true ,
237+ type : 'instrument' ,
238+ } ,
239+ } ) ,
240+ options ,
241+ ) ;
242+ } ;
243+ } ) ;
280244
281- return originalSend . apply ( this , args ) ;
245+ fill ( proto , 'removeEventListener' , function (
246+ originalRemoveEventListener : ( ) => void ,
247+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
248+ ) : ( this : any , eventName : string , fn : EventListenerObject , options ?: boolean | EventListenerOptions ) => ( ) => void {
249+ return function (
250+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
251+ this : any ,
252+ eventName : string ,
253+ fn : EventListenerObject ,
254+ options ?: boolean | EventListenerOptions ,
255+ ) : ( ) => void {
256+ /**
257+ * There are 2 possible scenarios here:
258+ *
259+ * 1. Someone passes a callback, which was attached prior to Sentry initialization, or by using unmodified
260+ * method, eg. `document.addEventListener.call(el, name, handler). In this case, we treat this function
261+ * as a pass-through, and call original `removeEventListener` with it.
262+ *
263+ * 2. Someone passes a callback, which was attached after Sentry was initialized, which means that it was using
264+ * our wrapped version of `addEventListener`, which internally calls `wrap` helper.
265+ * This helper "wraps" whole callback inside a try/catch statement, and attached appropriate metadata to it,
266+ * in order for us to make a distinction between wrapped/non-wrapped functions possible.
267+ * If a function was wrapped, it has additional property of `__sentry_wrapped__`, holding the handler.
268+ *
269+ * When someone adds a handler prior to initialization, and then do it again, but after,
270+ * then we have to detach both of them. Otherwise, if we'd detach only wrapped one, it'd be impossible
271+ * to get rid of the initial handler and it'd stick there forever.
272+ */
273+ const wrappedEventHandler = ( fn as unknown ) as WrappedFunction ;
274+ try {
275+ const originalEventHandler = wrappedEventHandler && wrappedEventHandler . __sentry_wrapped__ ;
276+ if ( originalEventHandler ) {
277+ originalRemoveEventListener . call ( this , eventName , originalEventHandler , options ) ;
278+ }
279+ } catch ( e ) {
280+ // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
281+ }
282+ return originalRemoveEventListener . call ( this , eventName , wrappedEventHandler , options ) ;
282283 } ;
283- }
284+ } ) ;
284285}
0 commit comments