@@ -13,6 +13,7 @@ export class Databuddy extends BaseTracker {
1313 private originalReplaceState : typeof history . replaceState | null = null ;
1414 private globalProperties : Record < string , unknown > = { } ;
1515 private hasInitialized = false ;
16+ private hasSentExitBeacon = false ;
1617
1718 constructor ( options : TrackerOptions ) {
1819 super ( options ) ;
@@ -62,9 +63,7 @@ export class Databuddy extends BaseTracker {
6263 }
6364
6465 this . trackScreenViews ( ) ;
65- this . setupPageExitTracking ( ) ;
66- this . setupVisibilityTracking ( ) ;
67- this . setupBfCacheHandling ( ) ;
66+ this . setupPageLifecycle ( ) ;
6867
6968 if ( document . visibilityState === "visible" ) {
7069 this . startEngagement ( ) ;
@@ -160,10 +159,11 @@ export class Databuddy extends BaseTracker {
160159 }
161160
162161 if ( this . lastPath ) {
163- this . trackPageExit ( ) ;
162+ this . trackPageExit ( this . lastPath ) ;
164163 this . notifyRouteChange ( window . location . pathname ) ;
165164 }
166165
166+ this . hasSentExitBeacon = false ;
167167 this . lastPath = url ;
168168 this . pageCount += 1 ;
169169 this . resetPageEngagement ( ) ;
@@ -173,86 +173,96 @@ export class Databuddy extends BaseTracker {
173173 } ) ;
174174 }
175175
176- private setupPageExitTracking ( ) {
177- const handleExit = ( ) => this . sendPageExitBeacon ( ) ;
178- window . addEventListener ( "beforeunload" , handleExit ) ;
179- window . addEventListener ( "pagehide" , handleExit ) ;
180- this . cleanupFns . push ( ( ) => {
181- window . removeEventListener ( "beforeunload" , handleExit ) ;
182- window . removeEventListener ( "pagehide" , handleExit ) ;
183- } ) ;
184- }
176+ private setupPageLifecycle ( ) {
177+ const handleHide = ( ) => this . handlePageHide ( ) ;
178+ const handleResume = ( ) => this . handlePageResume ( ) ;
185179
186- private setupVisibilityTracking ( ) {
187- const handler = ( ) => {
180+ window . addEventListener ( "beforeunload" , handleHide ) ;
181+ window . addEventListener ( "pagehide" , handleHide ) ;
182+
183+ const visibilityHandler = ( ) => {
188184 if ( document . visibilityState === "hidden" ) {
189- this . pauseEngagement ( ) ;
190- this . sendPageExitBeacon ( ) ;
185+ handleHide ( ) ;
191186 } else {
192- this . startEngagement ( ) ;
187+ handleResume ( ) ;
193188 }
194189 } ;
195- document . addEventListener ( "visibilitychange" , handler ) ;
196- this . cleanupFns . push ( ( ) =>
197- document . removeEventListener ( "visibilitychange" , handler )
198- ) ;
199- }
190+ document . addEventListener ( "visibilitychange" , visibilityHandler ) ;
200191
201- private setupBfCacheHandling ( ) {
202- const handler = ( event : PageTransitionEvent ) => {
192+ const pageshowHandler = ( event : PageTransitionEvent ) => {
203193 if ( ! event . persisted ) { return ; }
204-
205- this . resetEngagement ( ) ;
206- this . startEngagement ( ) ;
207-
208- const sessionTimestamp = sessionStorage . getItem ( "did_session_timestamp" ) ;
209- if ( sessionTimestamp ) {
210- const sessionAge = Date . now ( ) - Number . parseInt ( sessionTimestamp , 10 ) ;
211- if ( sessionAge >= 30 * 60 * 1000 ) {
212- this . sessionId = this . generateSessionId ( ) ;
213- sessionStorage . setItem ( "did_session" , this . sessionId ) ;
214- sessionStorage . setItem (
215- "did_session_timestamp" ,
216- Date . now ( ) . toString ( )
217- ) ;
218- }
219- }
220-
221- this . notifyRouteChange ( window . location . pathname ) ;
222- this . lastPath = "" ;
223- this . screenView ( { navigation_type : "back_forward_cache" } ) ;
194+ this . handleBfCacheRestore ( ) ;
224195 } ;
225- window . addEventListener ( "pageshow" , handler ) ;
226- this . cleanupFns . push ( ( ) => window . removeEventListener ( "pageshow" , handler ) ) ;
227- }
196+ window . addEventListener ( "pageshow" , pageshowHandler ) ;
228197
229- private trackPageExit ( ) {
230- this . _trackInternal ( "page_exit" , {
231- time_on_page : Math . round ( ( Date . now ( ) - this . pageStartTime ) / 1000 ) ,
232- scroll_depth : this . maxScrollDepth ,
233- interaction_count : this . interactionCount ,
234- page_count : this . pageCount ,
198+ this . cleanupFns . push ( ( ) => {
199+ window . removeEventListener ( "beforeunload" , handleHide ) ;
200+ window . removeEventListener ( "pagehide" , handleHide ) ;
201+ document . removeEventListener ( "visibilitychange" , visibilityHandler ) ;
202+ window . removeEventListener ( "pageshow" , pageshowHandler ) ;
235203 } ) ;
236204 }
237205
238- private sendPageExitBeacon ( ) {
206+ private handlePageHide ( ) {
207+ this . pauseEngagement ( ) ;
208+ if ( this . hasSentExitBeacon ) { return ; }
209+ this . hasSentExitBeacon = true ;
210+
211+ const now = Date . now ( ) ;
239212 this . sendBatchBeacon ( [
240213 {
241214 eventId : generateUUIDv4 ( ) ,
242215 name : "page_exit" ,
243216 anonymousId : this . anonymousId ,
244217 sessionId : this . sessionId ,
245- timestamp : Date . now ( ) ,
218+ timestamp : now ,
246219 ...this . getBaseContext ( ) ,
247220 ...this . globalProperties ,
248- time_on_page : Math . round ( ( Date . now ( ) - this . pageStartTime ) / 1000 ) ,
221+ time_on_page : Math . round ( ( now - this . pageStartTime ) / 1000 ) ,
249222 scroll_depth : this . maxScrollDepth ,
250223 interaction_count : this . interactionCount ,
251224 page_count : this . pageCount ,
252225 } ,
253226 ] ) ;
254227 }
255228
229+ private handlePageResume ( ) {
230+ this . hasSentExitBeacon = false ;
231+ this . startEngagement ( ) ;
232+ }
233+
234+ private handleBfCacheRestore ( ) {
235+ this . hasSentExitBeacon = false ;
236+ this . resetEngagement ( ) ;
237+ this . startEngagement ( ) ;
238+
239+ const sessionTimestamp = sessionStorage . getItem ( "did_session_timestamp" ) ;
240+ if ( sessionTimestamp ) {
241+ const sessionAge = Date . now ( ) - Number . parseInt ( sessionTimestamp , 10 ) ;
242+ if ( sessionAge >= 30 * 60 * 1000 ) {
243+ this . sessionId = this . generateSessionId ( ) ;
244+ sessionStorage . setItem ( "did_session" , this . sessionId ) ;
245+ sessionStorage . setItem ( "did_session_timestamp" , Date . now ( ) . toString ( ) ) ;
246+ }
247+ }
248+
249+ this . notifyRouteChange ( window . location . pathname ) ;
250+ this . lastPath = "" ;
251+ this . screenView ( { navigation_type : "back_forward_cache" } ) ;
252+ }
253+
254+ private trackPageExit ( exitPath ?: string ) {
255+ const now = Date . now ( ) ;
256+ this . _trackInternal ( "page_exit" , {
257+ path : exitPath ,
258+ timestamp : now ,
259+ time_on_page : Math . round ( ( now - this . pageStartTime ) / 1000 ) ,
260+ scroll_depth : this . maxScrollDepth ,
261+ interaction_count : this . interactionCount ,
262+ page_count : this . pageCount ,
263+ } ) ;
264+ }
265+
256266 private resetPageEngagement ( ) {
257267 this . pageStartTime = Date . now ( ) ;
258268 this . interactionCount = 0 ;
0 commit comments