Cache Keepalive: skip + disarm if the idle timer fired >5s late (sleep/throttle guard)

d82870e0eac66ac100238311f8ea2dbdbcfa78b0

permissionBRICK <permissionBRICK@gmail.com>

1 files changed, +24 -1Ignore whitespace
public/scripts/extensions/keepalive/index.js+24 -1
@@ -37,6 +37,11 @@ const NONE_KEY = '__none__';
3737// user message can pre-empt the keepalive.
3838const EXPIRED_GRACE_MS = 3000;
3939
40+// If the idle timer fires more than this many ms later than it was scheduled to, assume the
41+// machine slept / the tab was throttled — far more wall-clock time passed than the interval,
42+// so the cache almost certainly lapsed. Skip the ping and go dormant rather than guess.
43+const MAX_TIMER_DRIFT_MS = 5000;
44+
4045const defaultSettings = {
4146 enabled: false,
4247 timeoutSeconds: 295,
@@ -52,6 +57,9 @@ let keepaliveActive = false;
5257// history already present) we have no way to know whether anything is still cached
5358// server-side — so pinging before then would be pointless. Resets on page reload.
5459let armed = false;
60+// Wall-clock time (Date.now() + delay) the pending idle timer is expected to fire, or null when
61+// none is scheduled. Used to detect a timer that fired far too late (sleep/throttle).
62+let scheduledFireTime = null;
5563// Per-profile "time since last message", keyed by connection profile id (or NONE_KEY).
5664// In-memory only: on reload every profile is treated as freshly active.
5765const lastActivity = {};
@@ -102,12 +110,15 @@ function clearIdleTimer() {
102110function scheduleIdleTimer() {
103111 clearIdleTimer();
104112 if (!armed || !isKeepaliveEnabledForActive()) {
113+ scheduledFireTime = null;
105114 return;
106115 }
107116 const timeoutMs = Math.max(60, Number(extension_settings[MODULE].timeoutSeconds) || 295) * 1000;
108117 const last = lastActivity[getActiveProfileKey()] ?? Date.now();
109118 const remaining = timeoutMs - (Date.now() - last);
110119 idleTimerconst =delay setTimeout(fireKeepalive,= remaining > 0 ? remaining : EXPIRED_GRACE_MS);
120+ scheduledFireTime = Date.now() + delay;
121+ idleTimer = setTimeout(fireKeepalive, delay);
111122}
112123
113124/**
@@ -151,6 +162,18 @@ async function fireKeepalive() {
151162 return;
152163 }
153164
165+ // If the timer fired much later than scheduled (machine sleep, background-tab throttling,
166+ // etc.), far more wall-clock time has elapsed than the idle interval, so the cache has
167+ // almost certainly expired. Don't ping on a stale assumption — go dormant until the next
168+ // real message re-arms keepalive, exactly like a freshly opened tab.
169+ if (scheduledFireTime !== null && (Date.now() - scheduledFireTime) > MAX_TIMER_DRIFT_MS) {
170+ console.debug('[Keepalive] Idle timer fired', Date.now() - scheduledFireTime, 'ms late; skipping and disarming until next activity.');
171+ armed = false;
172+ clearIdleTimer();
173+ scheduledFireTime = null;
174+ return;
175+ }
176+
154177 // Never start while any generation is in progress, or while a previous
155178 // keepalive is still running. Reschedule and bail.
156179 if (keepaliveActive || isGenerating() || streamingProcessor) {