fix: improve visit duration reliability with pagehide fallback and dedup guard
This commit is contained in:
@@ -8,7 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
|
||||
### Improved
|
||||
|
||||
- **Deeper journey exploration.** The depth slider on the Journeys page now goes up to 10 steps (previously capped at 5), so you can follow longer visitor paths through your site and see exactly where people go after many clicks.
|
||||
- **Redesigned Journeys page.** The depth slider now matches the rest of the UI and goes up to 10 steps. Controls are integrated into the chart card for a cleaner layout, and Top Paths are shown as visual breadcrumb cards instead of a cramped table.
|
||||
- **More reliable visit duration tracking.** Visit duration was silently dropping to 0s for visitors who only viewed one page — especially on mobile or when closing a tab quickly. The tracking script now captures time-on-page more reliably across all browsers, and sessions where duration couldn't be measured are excluded from the average instead of counting as 0s.
|
||||
- **More accurate rage click detection.** Rage clicks no longer fire when you triple-click to select text on a page. Previously, selecting a paragraph (a normal 3-click action) was being counted as a rage click, which inflated frustration metrics. Only genuinely frustrated rapid clicking is tracked now.
|
||||
- **Fresher CDN data.** BunnyCDN statistics now refresh every 3 hours instead of once a day, so your CDN tab shows much more current bandwidth, request, and cache data.
|
||||
- **More accurate dead click detection.** Dead clicks were being reported on elements that actually worked — like close buttons on cart drawers, modal dismiss buttons, and page content areas. Three fixes make dead clicks much more reliable:
|
||||
|
||||
@@ -37,21 +37,20 @@
|
||||
// * Time-on-page tracking: records when the current pageview started
|
||||
var pageStartTime = 0;
|
||||
|
||||
var metricsSent = false;
|
||||
|
||||
function sendMetrics() {
|
||||
if (!currentEventId) return;
|
||||
if (!currentEventId || metricsSent) return;
|
||||
|
||||
// * Calculate time-on-page in seconds
|
||||
var durationSec = pageStartTime > 0 ? Math.round((Date.now() - pageStartTime) / 1000) : 0;
|
||||
|
||||
var payload = { event_id: currentEventId };
|
||||
|
||||
// * Always include duration if we have a valid measurement
|
||||
if (durationSec > 0) payload.duration = durationSec;
|
||||
|
||||
// * Skip if nothing to send (no duration)
|
||||
if (!payload.duration) return;
|
||||
if (durationSec <= 0) return;
|
||||
|
||||
var data = JSON.stringify(payload);
|
||||
metricsSent = true;
|
||||
|
||||
var data = JSON.stringify({ event_id: currentEventId, duration: durationSec });
|
||||
|
||||
if (navigator.sendBeacon) {
|
||||
navigator.sendBeacon(apiUrl + '/api/v1/metrics', new Blob([data], {type: 'application/json'}));
|
||||
@@ -66,12 +65,12 @@
|
||||
}
|
||||
|
||||
// * Send metrics when user leaves or hides the page
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
// * Delay slightly so duration measurement captures final moment
|
||||
setTimeout(sendMetrics, 150);
|
||||
}
|
||||
// * visibilitychange is the primary signal, pagehide is the fallback
|
||||
// * for browsers/scenarios where visibilitychange doesn't fire (tab close, mobile app kill)
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (document.visibilityState === 'hidden') sendMetrics();
|
||||
});
|
||||
window.addEventListener('pagehide', sendMetrics);
|
||||
|
||||
// * Memory cache for session ID (fallback if storage is unavailable)
|
||||
let cachedSessionId = null;
|
||||
@@ -312,6 +311,7 @@
|
||||
if (data && data.id) {
|
||||
currentEventId = data.id;
|
||||
pageStartTime = Date.now();
|
||||
metricsSent = false;
|
||||
}
|
||||
}).catch(() => {
|
||||
// * Silently fail - don't interrupt user experience
|
||||
|
||||
Reference in New Issue
Block a user