Spaces:
Running
Running
| /* TuringDNA dwell-time heartbeat. | |
| * | |
| * Measures how long a user actually spends ON the engine (foreground time), | |
| * which the server-side run/event timestamps can't capture β they only mark | |
| * discrete actions, not the reading/thinking gaps between them. | |
| * | |
| * Posts { session_id, visible_ms } to /api/ping: | |
| * - an initial beat ~3s after load (captures the user_id early, so even a | |
| * short visit gets attributed before they leave), | |
| * - then every ~30s WHILE the tab is visible (background tabs are skipped β | |
| * we only count foreground time), and | |
| * - once more on tab-hide / pagehide via navigator.sendBeacon (survives the | |
| * tab going away, where a normal fetch would be cancelled). | |
| * | |
| * visible_ms is CUMULATIVE foreground time for the session; the server keeps the | |
| * max, so duplicate or out-of-order beats are harmless. auth.js has already | |
| * wrapped fetch to attach the JWT on /api/* calls, so periodic beats are | |
| * attributed to the signed-in user. The sendBeacon fallback is unauthenticated | |
| * (anonymous), but the server preserves an already-known user_id on that row. | |
| * | |
| * Privacy: sends only an opaque random session id + a millisecond duration. | |
| * No sequence content, no page paths, no identity β matches PRIVACY.md. | |
| */ | |
| (function () { | |
| 'use strict'; | |
| var PING_MS = 30000; // periodic cadence while visible | |
| var INITIAL_MS = 3000; // first beat β early user_id capture for short visits | |
| function uuid() { | |
| try { if (window.crypto && crypto.randomUUID) return crypto.randomUUID(); } catch (e) {} | |
| return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | |
| var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); | |
| return v.toString(16); | |
| }); | |
| } | |
| var sessionId = uuid(); | |
| var visibleMs = 0; // accumulated foreground time from FINISHED segments | |
| var segStart = (document.visibilityState === 'visible') ? Date.now() : 0; | |
| function currentVisibleMs() { | |
| var ongoing = segStart ? (Date.now() - segStart) : 0; | |
| return Math.round(visibleMs + ongoing); | |
| } | |
| function send(useBeacon) { | |
| var body = JSON.stringify({ session_id: sessionId, visible_ms: currentVisibleMs() }); | |
| if (useBeacon && navigator.sendBeacon) { | |
| try { | |
| navigator.sendBeacon('/api/ping', new Blob([body], { type: 'application/json' })); | |
| return; | |
| } catch (e) { /* fall through to fetch */ } | |
| } | |
| try { | |
| fetch('/api/ping', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: body, | |
| keepalive: true, | |
| }).catch(function () {}); | |
| } catch (e) { /* ignore β telemetry is best-effort */ } | |
| } | |
| // Close the foreground segment on hide (and flush via beacon); reopen on show. | |
| document.addEventListener('visibilitychange', function () { | |
| if (document.visibilityState === 'hidden') { | |
| if (segStart) { visibleMs += Date.now() - segStart; segStart = 0; } | |
| send(true); | |
| } else if (document.visibilityState === 'visible') { | |
| if (!segStart) segStart = Date.now(); | |
| } | |
| }); | |
| // pagehide β last reliable chance to record the tail of the session | |
| // (beforeunload is unreliable on mobile; pagehide fires on bfcache too). | |
| window.addEventListener('pagehide', function () { | |
| if (segStart) { visibleMs += Date.now() - segStart; segStart = 0; } | |
| send(true); | |
| }); | |
| // Initial beat, then steady cadence. setInterval is throttled in background | |
| // tabs β fine, since we gate on visibility and only want foreground time. | |
| setTimeout(function () { if (document.visibilityState === 'visible') send(false); }, INITIAL_MS); | |
| setInterval(function () { if (document.visibilityState === 'visible') send(false); }, PING_MS); | |
| })(); | |