From 74d8efb1a83143c102084f0a12db9e4e62fb42e4 Mon Sep 17 00:00:00 2001 From: Chatter Date: Wed, 22 Jul 2026 11:48:20 -0400 Subject: [PATCH] Reclaim stale script lock after crash (v1.3.1) The single-tab lock was only released in beforeunload, so a browser crash or power-cut left the localStorage key behind and the script exited on every subsequent load until site data was cleared. Treat a lock not refreshed within 90s as dead and take it over, and add a 30s heartbeat so a live instance keeps its lock fresh. Co-Authored-By: Claude Opus 4.8 (1M context) --- cams.user.js | 35 +++++++++++++++++++++++++++-------- meta.js | 2 +- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/cams.user.js b/cams.user.js index a57c150..d5e5f40 100644 --- a/cams.user.js +++ b/cams.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name cams // @namespace http://tampermonkey.net/ -// @version 1.3.0 +// @version 1.3.1 // @description Set maxDockedCamsForUser, keep-alive, and multi-poke // @author You // @match https://chat.fabswingers.com/* @@ -223,18 +223,37 @@ localStorage.setItem(KEEPALIVE_SETTINGS_KEY, JSON.stringify(keepAliveSettings)); } - // Try to acquire lock - const lockValue = Date.now().toString(); + // Try to acquire lock. + // The lock value is a timestamp that a live instance refreshes on a heartbeat. + // If a crash / power-cut kills the tab, beforeunload never fires and the key is + // left behind; a new load treats a lock older than LOCK_STALE_MS as dead and + // takes over, instead of exiting forever (which required clearing site data). + const LOCK_STALE_MS = 90 * 1000; // reclaim a lock not refreshed within 90s + const LOCK_HEARTBEAT_MS = 30 * 1000; // refresh our lock every 30s while alive + let ourLockValue = Date.now().toString(); - if (localStorage.getItem(LOCK_KEY)) { - console.log('MaxDockedCams: Another instance already running, exiting'); - return; + const existingLock = localStorage.getItem(LOCK_KEY); + if (existingLock) { + const lockAge = Date.now() - parseInt(existingLock, 10); + if (isNaN(lockAge) || lockAge < LOCK_STALE_MS) { + console.log('MaxDockedCams: Another instance already running, exiting'); + return; + } + console.log(`MaxDockedCams: Reclaiming stale lock (${Math.round(lockAge / 1000)}s old)`); } - localStorage.setItem(LOCK_KEY, lockValue); + localStorage.setItem(LOCK_KEY, ourLockValue); + + // Keep our lock fresh so a live instance is never mistaken for a crashed one. + setInterval(function() { + if (localStorage.getItem(LOCK_KEY) === ourLockValue) { + ourLockValue = Date.now().toString(); + localStorage.setItem(LOCK_KEY, ourLockValue); + } + }, LOCK_HEARTBEAT_MS); window.addEventListener('beforeunload', function() { - if (localStorage.getItem(LOCK_KEY) === lockValue) { + if (localStorage.getItem(LOCK_KEY) === ourLockValue) { localStorage.removeItem(LOCK_KEY); } }); diff --git a/meta.js b/meta.js index 8e09cf1..7b39008 100644 --- a/meta.js +++ b/meta.js @@ -1,7 +1,7 @@ // ==UserScript== // @name cams // @namespace http://tampermonkey.net/ -// @version 1.3.0 +// @version 1.3.1 // @description Set maxDockedCamsForUser, keep-alive, and multi-poke // @author You // @match https://chat.fabswingers.com/*