1- /* eslint-disable @typescript-eslint/no-unnecessary-condition,@typescript-eslint/no-unsafe-function-type,@typescript-eslint/no-empty-function */
1+ /* eslint-disable @typescript-eslint/no-unnecessary-condition,@typescript-eslint/no-unsafe-function-type */
22"use strict"
33
44// turns on override logs
@@ -322,14 +322,15 @@ document.addEventListener("readystatechange", () => {
322322} )
323323
324324// #region GTM override
325- function wrapDataLayerPush ( push : DataLayer [ "push" ] , dataLayer : DataLayer ) {
325+ function wrapDataLayerPush ( push : DataLayer [ "push" ] ) {
326326 // Must be a (non-async) function, not an arrow function, because we need to bind the original `this` context
327327 // the GTM dataLayer push function returns `true` if the push was successful, we just assume it is.
328328 // The `...args` spread is needed so the call results in the exactly same result as the original.
329- return function yieldingPush ( ...args : object [ ] ) {
330- void yieldUnlessUrgent ( true ) . then ( function innerPush ( ) {
331- // In case we override the native Array#push here, we need to set the original array as `this` to not cause runtime errors
332- push . apply ( dataLayer , args )
329+ // The function syntax is important here so we keep the correct `this`.
330+ return function yieldingPush ( this : DataLayer , ...args : object [ ] ) {
331+ void yieldUnlessUrgent ( true ) . then ( ( ) => {
332+ // The arrow FN is important here so we keep the correct `this`.
333+ push . apply ( this , args )
333334 } )
334335 return true
335336 }
@@ -353,7 +354,7 @@ function defineCustomDataLayerPush(dataLayer: DataLayer) {
353354 if ( DEBUG ) console . log ( "set dataLayer.push" , value )
354355
355356 if ( value === mostRecentPushWrapper ) return // skip for `window.dataLayer = window.dataLayer||[]`
356- mostRecentPushWrapper = value ? wrapDataLayerPush ( value as DataLayer [ "push" ] , dataLayer ) : undefined
357+ mostRecentPushWrapper = value ? wrapDataLayerPush ( value as DataLayer [ "push" ] ) : undefined
357358 } ,
358359 } )
359360 Object . defineProperty ( dataLayer , "__f" , {
@@ -403,40 +404,60 @@ const gtmObserver = new MutationObserver(() => {
403404gtmObserver . observe ( document . documentElement , { childList : true , subtree : true } )
404405
405406// #region History/submit wrapper override
406- function wrapListener < T extends object > ( target : T , method : keyof T , value : Function ) {
407- // @ts -expect-error TS(2339): Prototype chain call. We try __proto__ first, as this will usually be the original method.
408- const originalMethod : Function = ( target . __proto__ as unknown as T ) [ method ] ?? ( target [ method ] as Function )
407+ function callOriginalMethod (
408+ this : unknown ,
409+ originalMethod : Function ,
410+ args : unknown [ ] ,
411+ callIfFirstArgIsntObject = false
412+ ) {
413+ const firstArg = args [ 0 ] ?? this
414+
415+ const argIsObject = firstArg != null && typeof firstArg === "object"
416+
417+ if ( argIsObject && ! ( "__f" in firstArg ) ) {
418+ originalMethod . apply ( this , args )
419+
420+ // @ts -expect-error TS(2339): Flag to indicate that the native method was called
421+ firstArg . __f = true
422+ } else if ( ! argIsObject && callIfFirstArgIsntObject ) {
423+ // If for some reason, we haven't called the original method yet, we call it here.
424+ originalMethod . apply ( this , args )
425+ }
426+ }
409427
410- // eslint-disable-next-line @typescript-eslint/no-explicit-any
411- return ( ...args : [ data : object , ...args : any [ ] ] ) => {
428+ function wrapListener ( originalMethod : Function , value : Function ) {
429+ // the function syntax is important here so we keep the correct `this`.
430+ return function yieldingListener ( this : unknown , ...args : [ data : object , ...args : unknown [ ] ] ) {
412431 if ( DEBUG ) {
413- console . log ( "Yielding for" , method )
414- console . timeStamp ( method as string )
432+ console . log ( "Yielding for" , originalMethod )
433+ console . timeStamp ( originalMethod as unknown as string )
415434 }
416435
417436 // We first call the original: This optimizes for UX & correctness of React components.
418437 // e.g., for pushState, when a component renders on a new route, it might set state and/or read from the URL. If the URL isn't
419438 // accurate, it might lead to wrong behavior.
420439 // We don't want to call the underlying native method twice (or multiple times), so we add a
421- // flag to the data object.
422- if ( typeof args [ 0 ] === "object" && ! ( "__f" in args [ 0 ] ) ) {
423- originalMethod . apply ( target , args )
424-
425- // @ts -expect-error TS(2339): Flag to indicate that the native method was called
426- args [ 0 ] . __f = true
427- }
440+ // flag to the data object or `this`.
441+ callOriginalMethod . call ( this , originalMethod , args )
428442
429443 // If `method` is overriden N times, it creates N yield points (as overrides might be chained)
430444 void yieldUnlessUrgent ( ) . then ( ( ) => {
431- value . apply ( target , args )
445+ // The arrow FN is important here so we keep the correct `this`.
446+ value . apply ( this , args )
432447 } )
433448 }
434449}
435450function overrideListener < T extends object > ( target : T , method : keyof T ) {
436- // The initial value is a noop, so that the first override doesn't call the native method.
437- // We still need to set it to a function, so that the `get` function returns a function that is
438- // used if nothing ever overrides it.
439- let mostRecentWrapper : Function | undefined = wrapListener ( target , method , ( ) => { } )
451+ // @ts -expect-error TS(2339): Prototype chain call. We try __proto__ first, as this will usually be the original method.
452+ const originalMethod : Function = ( target . __proto__ as unknown as T ) [ method ] ?? ( target [ method ] as Function )
453+
454+ let mostRecentWrapper : Function | undefined = wrapListener (
455+ originalMethod ,
456+ // The function syntax is important here so we keep the correct `this`.
457+ function firstOverride ( this : unknown , ...args : [ data : object , ...args : unknown [ ] ] ) {
458+ callOriginalMethod . call ( this , originalMethod , args , true )
459+ }
460+ )
440461
441462 Object . defineProperty ( target , method , {
442463 enumerable : true ,
@@ -447,7 +468,7 @@ function overrideListener<T extends object>(target: T, method: keyof T) {
447468 if ( DEBUG ) console . log ( `set ${ String ( method ) } ` , target , value )
448469
449470 if ( value === mostRecentWrapper ) return
450- mostRecentWrapper = value ? wrapListener ( target , method , value as Function ) : undefined
471+ mostRecentWrapper = value ? wrapListener ( originalMethod , value as Function ) : undefined
451472 } ,
452473 } )
453474}
0 commit comments