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) <noreply@anthropic.com>
This commit is contained in:
Chatter
2026-07-22 11:48:20 -04:00
parent f64823fb74
commit 74d8efb1a8
2 changed files with 28 additions and 9 deletions

View File

@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name cams // @name cams
// @namespace http://tampermonkey.net/ // @namespace http://tampermonkey.net/
// @version 1.3.0 // @version 1.3.1
// @description Set maxDockedCamsForUser, keep-alive, and multi-poke // @description Set maxDockedCamsForUser, keep-alive, and multi-poke
// @author You // @author You
// @match https://chat.fabswingers.com/* // @match https://chat.fabswingers.com/*
@@ -223,18 +223,37 @@
localStorage.setItem(KEEPALIVE_SETTINGS_KEY, JSON.stringify(keepAliveSettings)); localStorage.setItem(KEEPALIVE_SETTINGS_KEY, JSON.stringify(keepAliveSettings));
} }
// Try to acquire lock // Try to acquire lock.
const lockValue = Date.now().toString(); // 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)) { const existingLock = localStorage.getItem(LOCK_KEY);
console.log('MaxDockedCams: Another instance already running, exiting'); if (existingLock) {
return; 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() { window.addEventListener('beforeunload', function() {
if (localStorage.getItem(LOCK_KEY) === lockValue) { if (localStorage.getItem(LOCK_KEY) === ourLockValue) {
localStorage.removeItem(LOCK_KEY); localStorage.removeItem(LOCK_KEY);
} }
}); });

View File

@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name cams // @name cams
// @namespace http://tampermonkey.net/ // @namespace http://tampermonkey.net/
// @version 1.3.0 // @version 1.3.1
// @description Set maxDockedCamsForUser, keep-alive, and multi-poke // @description Set maxDockedCamsForUser, keep-alive, and multi-poke
// @author You // @author You
// @match https://chat.fabswingers.com/* // @match https://chat.fabswingers.com/*