Skip to content

Commit c2b224a

Browse files
committed
cleanup tracker
1 parent b622fa7 commit c2b224a

File tree

2 files changed

+88
-58
lines changed

2 files changed

+88
-58
lines changed

apps/dashboard/app/(main)/websites/[id]/users/[userId]/_components/session-row.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ import { getDeviceIcon } from "@/lib/utils";
2727
import { generateSessionName } from "./generate-session-name";
2828
import { SessionEventTimeline } from "./session-event-timeline";
2929

30+
function getEventSortPriority(eventName: string): number {
31+
if (eventName === "page_exit") {
32+
return 0;
33+
}
34+
if (eventName === "screen_view") {
35+
return 1;
36+
}
37+
return 2;
38+
}
39+
3040
function transformSessionEvents(
3141
events: RawSessionEventTuple[]
3242
): SessionEvent[] {
@@ -55,7 +65,17 @@ function transformSessionEvents(
5565
properties: propertiesObj,
5666
};
5767
})
58-
.filter((event): event is SessionEvent => event !== null);
68+
.filter((event): event is SessionEvent => event !== null)
69+
.sort((a, b) => {
70+
const timeA = new Date(a.time).getTime();
71+
const timeB = new Date(b.time).getTime();
72+
if (timeA !== timeB) {
73+
return timeA - timeB;
74+
}
75+
return (
76+
getEventSortPriority(a.event_name) - getEventSortPriority(b.event_name)
77+
);
78+
});
5979
}
6080

6181
function getReferrerInfo(session: Session): SessionReferrer {

packages/tracker/src/index.ts

Lines changed: 67 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)