aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/idle.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-15 02:16:39 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-15 02:21:01 +0200
commit73ad8e4eb566fe682107fa7e50ef624591199e99 (patch)
treeff0017d014d46d8835487c080dca55c6def7fd6b /packages/meshbay-hub/src/meshbay_hub/static/idle.js
parentbdefcd025604f2c3009fe5e0cc01213c2ba62a6a (diff)
downloadmeshbay-73ad8e4eb566fe682107fa7e50ef624591199e99.tar.gz
feat(hub): session lifetime is an admin setting, and a browser signs out when idle
Browser idle sign-out (media playback counts as activity; not the desktop app), refresh idle window and maximum session length, in hours. Sign-out now revokes on the hub, and the profile has "sign out everywhere". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XuNrwLf5EFWCMHzfoEvnpm
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/idle.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/idle.js77
1 files changed, 77 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/idle.js b/packages/meshbay-hub/src/meshbay_hub/static/idle.js
new file mode 100644
index 0000000..e24f758
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/idle.js
@@ -0,0 +1,77 @@
+// Signing a browser out after a stretch with nobody at it.
+//
+// The hub cannot measure this. It hears a token renewal every few hours from
+// any open tab, attended or not, and nothing at all while a film plays over
+// WebRTC. So the page decides, and the hub only says how long
+// (`browser_idle_hours` in /v1/hub/info).
+//
+// Activity is input, or any <video>/<audio> on the page that is playing: a film
+// nobody touches for two hours is somebody watching it. The last-active time
+// lives in localStorage so every tab of this browser shares it — one idle tab
+// must not sign out the tab in use — and a browser closed without signing out
+// is caught the moment it is opened again.
+//
+// Never started in the desktop application (app.js): that is its owner's own
+// machine, and it signs back in with its device key.
+
+const LAST_ACTIVE_KEY = 'mb_last_active';
+const CHECK_MS = 60000;
+// Input fires constantly; the timestamp only has to be minutes-accurate.
+const WRITE_EVERY_MS = 30000;
+const INPUT_EVENTS = ['pointerdown', 'keydown', 'wheel', 'touchstart'];
+
+let lastWrite = 0;
+
+function readLastActive() {
+ try {
+ const v = Number(localStorage.getItem(LAST_ACTIVE_KEY));
+ return Number.isFinite(v) && v > 0 ? v : null;
+ } catch {
+ return null;
+ }
+}
+
+/** Record activity now. `force` skips the write throttle — a sign-in uses it. */
+function markActive(force = false) {
+ const now = Date.now();
+ if (!force && now - lastWrite < WRITE_EVERY_MS) return;
+ lastWrite = now;
+ try { localStorage.setItem(LAST_ACTIVE_KEY, String(now)); } catch { /* private mode */ }
+}
+
+function mediaPlaying() {
+ return [...document.querySelectorAll('video, audio')]
+ .some((m) => !m.paused && !m.ended);
+}
+
+/**
+ * Watch for `idleMs` without activity, then call `onIdle` once.
+ * Returns the function that stops watching.
+ */
+function startIdleWatch(idleMs, onIdle) {
+ let fired = false;
+ const onInput = () => markActive();
+ const check = () => {
+ if (fired) return;
+ if (mediaPlaying()) { markActive(); return; }
+ const last = readLastActive();
+ // No record at all is a browser that predates this, not an idle one.
+ if (last === null) { markActive(true); return; }
+ if (Date.now() - last > idleMs) { fired = true; onIdle(); }
+ };
+
+ INPUT_EVENTS.forEach((e) =>
+ window.addEventListener(e, onInput, { capture: true, passive: true }));
+ document.addEventListener('visibilitychange', check);
+ const timer = setInterval(check, CHECK_MS);
+ check();
+
+ return () => {
+ INPUT_EVENTS.forEach((e) =>
+ window.removeEventListener(e, onInput, { capture: true }));
+ document.removeEventListener('visibilitychange', check);
+ clearInterval(timer);
+ };
+}
+
+export { startIdleWatch, markActive, LAST_ACTIVE_KEY };