Detail Bug Report
https://app.detail.dev/org_ea7bf3e3-a2f4-4402-9351-baa0e1eaa1f5/bugs/bug_5d8903f7-f47c-4bb0-b0be-e0f0a2911dc5
Summary
- Context: The
EventQueue class is responsible for buffering analytics events and flushing them to the API either when a threshold is reached, a timer fires, or the user leaves the page.
- Bug: The
onPageLeave method incorrectly inverts the isAccessible logic for pagehide and visibilitychange events, and the flush method only processes a limited number of events, leading to significant data loss when users navigate away or close tabs (especially on mobile).
- Actual vs. expected: Currently, when the page is hidden (e.g.,
visibilityState === "hidden"), isAccessible is set to true, which prevents the flush from occurring. Additionally, flush only takes up to flushAt (default 20) items from the queue, even if more are present.
- Impact: Permanent loss of analytics data for events occurring at the end of a session or when switching tabs, particularly affecting mobile users where
beforeunload is unreliable.
Code with Bug
// src/queue/EventQueue.ts
document.addEventListener("pagehide", () => {
isAccessible = document.visibilityState === "hidden"; // <-- BUG 🔴 Inverts meaning: true when page is hidden
handleOnLeave();
});
document.addEventListener("visibilitychange", () => {
isAccessible = true; // <-- BUG 🔴 Always true, preventing flush in consumer check
if (document.visibilityState === "hidden") {
handleOnLeave();
} else {
// ...
}
});
// And the consumer:
this.onPageLeave(async (isAccessible: boolean) => {
if (isAccessible === false) { // <-- BUG 🔴 Flush is skipped when isAccessible incorrectly stays true
await this.flush();
}
});
// Also in flush:
const items = this.queue.splice(0, this.flushAt); // <-- BUG 🔴 Only flushes first N items; remainder can be lost on page leave
Explanation
- For
pagehide/visibilitychange when the document becomes hidden, isAccessible is incorrectly set to true. The page-leave consumer only flushes when isAccessible === false, so the final flush() is skipped precisely when the user is leaving/hiding the page.
- Even when
flush() does run on page leave, it only removes up to flushAt items from the queue. Because the page-leave flow calls flush() once, any queued events beyond the first batch remain in memory and are lost when the page unloads.
Recommended Fix
- Correct the
isAccessible logic for pagehide and visibilitychange so it reflects whether the page is still accessible.
- On page-leave/manual flush, drain the entire queue (or iterate until empty) rather than splicing only
flushAt items.
// src/queue/EventQueue.ts
document.addEventListener("pagehide", () => {
isAccessible = document.visibilityState !== "hidden"; // <-- FIX 🟢
handleOnLeave();
});
async flush(callback?: (...args: any) => void) {
// ...
const items = this.queue.splice(0, this.queue.length); // <-- FIX 🟢 drain all items on flush
// ...
}
History
This bug was introduced in commit 39c461f. This commit implemented the initial version of the EventQueue and its flush logic, which contained several behavioral errors including inverted page-leave detection and capped event flushing.
Detail Bug Report
https://app.detail.dev/org_ea7bf3e3-a2f4-4402-9351-baa0e1eaa1f5/bugs/bug_5d8903f7-f47c-4bb0-b0be-e0f0a2911dc5
Summary
EventQueueclass is responsible for buffering analytics events and flushing them to the API either when a threshold is reached, a timer fires, or the user leaves the page.onPageLeavemethod incorrectly inverts theisAccessiblelogic forpagehideandvisibilitychangeevents, and theflushmethod only processes a limited number of events, leading to significant data loss when users navigate away or close tabs (especially on mobile).visibilityState === "hidden"),isAccessibleis set totrue, which prevents the flush from occurring. Additionally,flushonly takes up toflushAt(default 20) items from the queue, even if more are present.beforeunloadis unreliable.Code with Bug
Explanation
pagehide/visibilitychangewhen the document becomes hidden,isAccessibleis incorrectly set totrue. The page-leave consumer only flushes whenisAccessible === false, so the finalflush()is skipped precisely when the user is leaving/hiding the page.flush()does run on page leave, it only removes up toflushAtitems from the queue. Because the page-leave flow callsflush()once, any queued events beyond the first batch remain in memory and are lost when the page unloads.Recommended Fix
isAccessiblelogic forpagehideandvisibilitychangeso it reflects whether the page is still accessible.flushAtitems.History
This bug was introduced in commit 39c461f. This commit implemented the initial version of the EventQueue and its flush logic, which contained several behavioral errors including inverted page-leave detection and capped event flushing.