@@ -8,6 +8,12 @@ import { initScrollDepthTracking } from "./plugins/scroll-depth";
88import { initWebVitalsTracking } from "./plugins/vitals" ;
99
1010export class Databuddy extends BaseTracker {
11+ // Store references for cleanup
12+ private cleanupFns : Array < ( ) => void > = [ ] ;
13+ private originalPushState : typeof history . pushState | null = null ;
14+ private originalReplaceState : typeof history . replaceState | null = null ;
15+ private globalProperties : Record < string , unknown > = { } ;
16+
1117 constructor ( options : TrackerOptions ) {
1218 super ( options ) ;
1319
@@ -43,15 +49,12 @@ export class Databuddy extends BaseTracker {
4349
4450 if ( typeof window !== "undefined" ) {
4551 window . databuddy = {
46- track : ( name : string , props ?: any ) => this . track ( name , props ) ,
47- screenView : ( props ?: any ) => this . screenView ( props ) ,
48- identify : ( ) => { } ,
49- clear : ( ) => { } , // Placeholder
50- flush : ( ) => {
51- this . flushBatch ( ) ;
52- } ,
53- setGlobalProperties : ( ) => { } , // Placeholder
54- trackCustomEvent : ( ) => { } , // Placeholder
52+ track : ( name : string , props ?: Record < string , unknown > ) => this . track ( name , props ) ,
53+ screenView : ( props ?: Record < string , unknown > ) => this . screenView ( props ) ,
54+ flush : ( ) => this . flushBatch ( ) ,
55+ clear : ( ) => this . clear ( ) ,
56+ setGlobalProperties : ( props : Record < string , unknown > ) => this . setGlobalProperties ( props ) ,
57+ trackCustomEvent : ( name : string , props ?: Record < string , unknown > ) => this . trackCustomEvent ( name , props ) ,
5558 options : this . options ,
5659 } ;
5760 window . db = window . databuddy ;
@@ -63,25 +66,31 @@ export class Databuddy extends BaseTracker {
6366 return ;
6467 }
6568
66- const pushState = history . pushState ;
69+ // Store original methods for cleanup
70+ this . originalPushState = history . pushState ;
71+ this . originalReplaceState = history . replaceState ;
72+
73+ const originalPushState = this . originalPushState ;
6774 history . pushState = ( ...args ) => {
68- const ret = pushState . apply ( history , args ) ;
75+ const ret = originalPushState . apply ( history , args ) ;
6976 window . dispatchEvent ( new Event ( "pushstate" ) ) ;
7077 window . dispatchEvent ( new Event ( "locationchange" ) ) ;
7178 return ret ;
7279 } ;
7380
74- const replaceState = history . replaceState ;
81+ const originalReplaceState = this . originalReplaceState ;
7582 history . replaceState = ( ...args ) => {
76- const ret = replaceState . apply ( history , args ) ;
83+ const ret = originalReplaceState . apply ( history , args ) ;
7784 window . dispatchEvent ( new Event ( "replacestate" ) ) ;
7885 window . dispatchEvent ( new Event ( "locationchange" ) ) ;
7986 return ret ;
8087 } ;
8188
82- window . addEventListener ( "popstate" , ( ) => {
89+ const popstateHandler = ( ) => {
8390 window . dispatchEvent ( new Event ( "locationchange" ) ) ;
84- } ) ;
91+ } ;
92+ window . addEventListener ( "popstate" , popstateHandler ) ;
93+ this . cleanupFns . push ( ( ) => window . removeEventListener ( "popstate" , popstateHandler ) ) ;
8594
8695 let debounceTimer : ReturnType < typeof setTimeout > ;
8796 const debouncedScreenView = ( ) => {
@@ -92,17 +101,32 @@ export class Databuddy extends BaseTracker {
92101 } ;
93102
94103 window . addEventListener ( "locationchange" , debouncedScreenView ) ;
104+ this . cleanupFns . push ( ( ) => window . removeEventListener ( "locationchange" , debouncedScreenView ) ) ;
105+
95106 if ( this . options . trackHashChanges ) {
96107 window . addEventListener ( "hashchange" , debouncedScreenView ) ;
108+ this . cleanupFns . push ( ( ) => window . removeEventListener ( "hashchange" , debouncedScreenView ) ) ;
97109 }
98110 }
99111
100- screenView ( props ?: any ) {
112+ screenView ( props ?: Record < string , unknown > ) {
101113 if ( this . isServer ( ) ) {
102114 return ;
103115 }
104116 const url = window . location . href ;
105117 if ( this . lastPath !== url ) {
118+ if ( ! this . options . trackHashChanges && this . lastPath ) {
119+ const lastUrl = new URL ( this . lastPath ) ;
120+ const currentUrl = new URL ( url ) ;
121+ const isHashOnlyChange =
122+ lastUrl . origin === currentUrl . origin &&
123+ lastUrl . pathname === currentUrl . pathname &&
124+ lastUrl . search === currentUrl . search &&
125+ lastUrl . hash !== currentUrl . hash ;
126+ if ( isHashOnlyChange ) {
127+ return ;
128+ }
129+ }
106130 this . lastPath = url ;
107131 this . pageCount += 1 ;
108132 this . track ( "screen_view" , {
@@ -113,7 +137,7 @@ export class Databuddy extends BaseTracker {
113137 }
114138
115139 trackOutgoingLinks ( ) {
116- document . addEventListener ( "click" , ( e ) => {
140+ const handler = ( e : MouseEvent ) => {
117141 const target = e . target as HTMLElement ;
118142 const link = target . closest ( "a" ) ;
119143 if ( link && link . hostname !== window . location . hostname ) {
@@ -132,17 +156,19 @@ export class Databuddy extends BaseTracker {
132156 { keepalive : true }
133157 ) ;
134158 }
135- } ) ;
159+ } ;
160+ document . addEventListener ( "click" , handler ) ;
161+ this . cleanupFns . push ( ( ) => document . removeEventListener ( "click" , handler ) ) ;
136162 }
137163
138164 trackAttributes ( ) {
139- document . addEventListener ( "click" , ( e ) => {
165+ const handler = ( e : MouseEvent ) => {
140166 const target = e . target as HTMLElement ;
141167 const trackable = target . closest ( "[data-track]" ) ;
142168 if ( trackable ) {
143169 const eventName = trackable . getAttribute ( "data-track" ) ;
144170 if ( eventName ) {
145- const properties : Record < string , any > = { } ;
171+ const properties : Record < string , string > = { } ;
146172 for ( const attr of trackable . attributes ) {
147173 if ( attr . name . startsWith ( "data-" ) && attr . name !== "data-track" ) {
148174 const key = attr . name
@@ -154,21 +180,88 @@ export class Databuddy extends BaseTracker {
154180 this . track ( eventName , properties ) ;
155181 }
156182 }
157- } ) ;
183+ } ;
184+ document . addEventListener ( "click" , handler ) ;
185+ this . cleanupFns . push ( ( ) => document . removeEventListener ( "click" , handler ) ) ;
158186 }
159187
160- track ( name : string , props : any ) {
188+ track ( name : string , props ?: Record < string , unknown > ) {
161189 const payload = {
162190 eventId : generateUUIDv4 ( ) ,
163191 name,
164192 anonymousId : this . anonymousId ,
165193 sessionId : this . sessionId ,
166194 timestamp : Date . now ( ) ,
167195 ...this . getBaseContext ( ) ,
196+ ...this . globalProperties ,
168197 ...props ,
169198 } ;
170199 this . send ( payload ) ;
171200 }
201+
202+ trackCustomEvent ( name : string , props ?: Record < string , unknown > ) {
203+ this . track ( name , {
204+ event_type : "custom" ,
205+ ...props ,
206+ } ) ;
207+ }
208+
209+ setGlobalProperties ( props : Record < string , unknown > ) {
210+ this . globalProperties = { ...this . globalProperties , ...props } ;
211+ }
212+
213+ clear ( ) {
214+ this . globalProperties = { } ;
215+
216+ if ( ! this . isServer ( ) ) {
217+ try {
218+ localStorage . removeItem ( "did" ) ;
219+ sessionStorage . removeItem ( "did_session" ) ;
220+ sessionStorage . removeItem ( "did_session_timestamp" ) ;
221+ sessionStorage . removeItem ( "did_session_start" ) ;
222+ } catch {
223+ }
224+ }
225+
226+ this . anonymousId = this . generateAnonymousId ( ) ;
227+ this . sessionId = this . generateSessionId ( ) ;
228+ this . sessionStartTime = Date . now ( ) ;
229+ this . pageCount = 0 ;
230+ this . lastPath = "" ;
231+ this . interactionCount = 0 ;
232+ this . maxScrollDepth = 0 ;
233+ }
234+
235+ destroy ( ) {
236+ // Run all cleanup functions
237+ for ( const cleanup of this . cleanupFns ) {
238+ cleanup ( ) ;
239+ }
240+ this . cleanupFns = [ ] ;
241+
242+ // Restore original history methods
243+ if ( this . originalPushState ) {
244+ history . pushState = this . originalPushState ;
245+ this . originalPushState = null ;
246+ }
247+ if ( this . originalReplaceState ) {
248+ history . replaceState = this . originalReplaceState ;
249+ this . originalReplaceState = null ;
250+ }
251+
252+ // Clear batch queue and timer
253+ if ( this . batchTimer ) {
254+ clearTimeout ( this . batchTimer ) ;
255+ this . batchTimer = null ;
256+ }
257+ this . batchQueue = [ ] ;
258+
259+ // Remove global references
260+ if ( typeof window !== "undefined" ) {
261+ window . databuddy = undefined ;
262+ window . db = undefined ;
263+ }
264+ }
172265}
173266
174267function initializeDatabuddy ( ) {
@@ -183,7 +276,6 @@ function initializeDatabuddy() {
183276 window . databuddy = {
184277 track : ( ) => { } ,
185278 screenView : ( ) => { } ,
186- identify : ( ) => { } ,
187279 clear : ( ) => { } ,
188280 flush : ( ) => { } ,
189281 setGlobalProperties : ( ) => { } ,
0 commit comments