feat: sync session warning across tabs
This commit is contained in:
@@ -11,6 +11,10 @@
|
||||
const extendButton = document.getElementById("app-session-warning-extend");
|
||||
if (!modal || !countdown || !extendButton || !status || !orb || !orbValue) return;
|
||||
|
||||
const storageKey = "workdock.session.lastConfirmedAt";
|
||||
const syncChannel = typeof BroadcastChannel !== "undefined"
|
||||
? new BroadcastChannel("workdock-session-warning")
|
||||
: null;
|
||||
let lastConfirmedAt = Date.now();
|
||||
let warningVisible = false;
|
||||
let keepaliveInFlight = false;
|
||||
@@ -40,6 +44,32 @@
|
||||
status.textContent = "";
|
||||
}
|
||||
|
||||
function readStoredConfirmedAt() {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(storageKey);
|
||||
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function syncConfirmedAt(timestamp, source) {
|
||||
lastConfirmedAt = timestamp;
|
||||
try {
|
||||
window.localStorage.setItem(storageKey, String(timestamp));
|
||||
} catch (_error) {
|
||||
// Ignore storage write failures.
|
||||
}
|
||||
if (syncChannel && source !== "broadcast") {
|
||||
syncChannel.postMessage({ type: "confirmed-at", value: timestamp });
|
||||
}
|
||||
if (source !== "self") {
|
||||
hideWarning();
|
||||
hideStatus();
|
||||
}
|
||||
}
|
||||
|
||||
function showWarning(secondsLeft) {
|
||||
countdown.textContent = `Noch etwa ${secondsLeft} Sekunden bis zur automatischen Abmeldung.`;
|
||||
orb.style.setProperty("--session-warning-progress", String(Math.max(0, Math.min(1, secondsLeft / warningLeadSeconds))));
|
||||
@@ -69,7 +99,7 @@
|
||||
window.location.href = config.loginUrl;
|
||||
return;
|
||||
}
|
||||
lastConfirmedAt = Date.now();
|
||||
syncConfirmedAt(Date.now(), "self");
|
||||
showStatus("Sitzung erfolgreich verlängert.");
|
||||
orb.style.setProperty("--session-warning-progress", "1");
|
||||
orbValue.textContent = "OK";
|
||||
@@ -84,6 +114,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
const storedConfirmedAt = readStoredConfirmedAt();
|
||||
if (storedConfirmedAt) {
|
||||
lastConfirmedAt = storedConfirmedAt;
|
||||
} else {
|
||||
syncConfirmedAt(lastConfirmedAt, "self");
|
||||
}
|
||||
|
||||
if (syncChannel) {
|
||||
syncChannel.addEventListener("message", function (event) {
|
||||
if (event.data && event.data.type === "confirmed-at" && Number.isFinite(event.data.value)) {
|
||||
syncConfirmedAt(event.data.value, "broadcast");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("storage", function (event) {
|
||||
if (event.key !== storageKey || !event.newValue) return;
|
||||
const parsed = Number.parseInt(event.newValue, 10);
|
||||
if (Number.isFinite(parsed)) {
|
||||
syncConfirmedAt(parsed, "storage");
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("visibilitychange", function () {
|
||||
if (document.visibilityState !== "visible") return;
|
||||
const latest = readStoredConfirmedAt();
|
||||
if (latest && latest > lastConfirmedAt) {
|
||||
syncConfirmedAt(latest, "storage");
|
||||
}
|
||||
});
|
||||
|
||||
function tick() {
|
||||
const elapsedSeconds = Math.floor((Date.now() - lastConfirmedAt) / 1000);
|
||||
const secondsLeft = config.idleTimeoutSeconds - elapsedSeconds;
|
||||
|
||||
@@ -846,6 +846,7 @@ make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS</code></pre>
|
||||
<li><code>SESSION_IDLE_TIMEOUT_SECONDS</code> controls the idle session window.</li>
|
||||
<li><code>SENSITIVE_ACTION_REAUTH_SECONDS</code> controls when sensitive POST actions require fresh authentication.</li>
|
||||
<li><code>/session/keepalive/</code> refreshes both session timestamps when the user chooses <code>Angemeldet bleiben</code>.</li>
|
||||
<li>Open tabs now sync the confirmed session timestamp through browser storage and <code>BroadcastChannel</code>, so extending the session in one tab updates the warning state in the others.</li>
|
||||
</ul>
|
||||
<p>This warning is meant to protect work in progress without silently relaxing the security middleware.</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user