Don't accrue reading time across sleep
The inactivity cutoff timer cannot fire while the computer is asleep, and performance.now() keeps running through sleep on some platforms, so after a wake the whole slept span could be credited as reading time (inflating min/visit stats). A gap longer than the inactivity timeout now closes the span at its intended cutoff, and the cutoff timer clamps its accumulation to its scheduled fire time — covering both event-first and timer-first wake orderings.
This commit is contained in:
1 parent
d4add54f10
commit
fb2914d853
2 files changed
+26
-4
No files matched your search
+5
-1
@@ -119,7 +119,11 @@ transition applies the new `<html lang>` only after the ping goes out) and
|
||||
follows the session, so the activity of a visit stays tied together, and
|
||||
while the user is active the accumulated reading time is flushed every few
|
||||
seconds: the times are incremental, so a disconnection simply leaves the
|
||||
last reported time in place (no close beacon). After 5 minutes without
|
||||
last reported time in place (no close beacon). A sleeping computer accrues
|
||||
no reading time: the inactivity-cutoff timer cannot fire while asleep (and
|
||||
`performance.now()` keeps running through sleep on some platforms), so a
|
||||
gap longer than the timeout closes the span at its intended cutoff, and a
|
||||
late-firing cutoff timer clamps its accumulation the same way. After 5 minutes without
|
||||
any activity the client closes the socket itself — a sleeping browser tab
|
||||
would lose it anyway — and the next activity reconnects; reconnects are
|
||||
attempted only on user activity, with an exponential backoff between
|
||||
|
||||
@@ -684,16 +684,34 @@ import { reconnectPolicy, socketSlot, watchConnecting } from "./reconnect";
|
||||
let reading = true;
|
||||
let readInactivityTimer = null;
|
||||
let idleTimer = null;
|
||||
let lastActivityAt = performance.now();
|
||||
|
||||
function markReadActivity() {
|
||||
if (!reading) {
|
||||
const t = performance.now();
|
||||
if (reading) {
|
||||
// A gap longer than the inactivity timeout means the cutoff timer
|
||||
// could not run on time (the computer slept, or the tab was throttled
|
||||
// hard): close the span at its intended cutoff — the gap itself was
|
||||
// not reading time, and performance.now() keeps running through sleep
|
||||
// on some platforms.
|
||||
const cutoff = lastActivityAt + INACTIVE_MS;
|
||||
if (t > cutoff) {
|
||||
readElapsed += Math.max(0, cutoff - readStart);
|
||||
readStart = t;
|
||||
}
|
||||
} else {
|
||||
reading = true;
|
||||
readStart = performance.now();
|
||||
readStart = t;
|
||||
}
|
||||
lastActivityAt = t;
|
||||
clearTimeout(readInactivityTimer);
|
||||
// Same sleep guard, other ordering: when this timer itself fires late
|
||||
// (waking from sleep before any user event), clamp the accumulation to
|
||||
// its intended fire time.
|
||||
const stopAt = t + INACTIVE_MS;
|
||||
readInactivityTimer = setTimeout(() => {
|
||||
if (reading) {
|
||||
readElapsed += performance.now() - readStart;
|
||||
readElapsed += Math.max(0, Math.min(performance.now(), stopAt) - readStart);
|
||||
reading = false;
|
||||
}
|
||||
}, INACTIVE_MS);
|
||||
|
||||
Reference in new issue
Block a user