From 7312dc0514fbd87f608574ee5726985ec0e48244 Mon Sep 17 00:00:00 2001 From: Md Bayazid Bostame Date: Wed, 1 Apr 2026 22:24:18 +0200 Subject: [PATCH] feat: sync session warning across tabs --- .../static/workflows/js/session_warning.js | 63 ++++++++++++++++++- .../workflows/developer_handbook.html | 1 + 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/backend/workflows/static/workflows/js/session_warning.js b/backend/workflows/static/workflows/js/session_warning.js index ae7eff5..6ea9ca5 100644 --- a/backend/workflows/static/workflows/js/session_warning.js +++ b/backend/workflows/static/workflows/js/session_warning.js @@ -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; diff --git a/backend/workflows/templates/workflows/developer_handbook.html b/backend/workflows/templates/workflows/developer_handbook.html index 04b1318..afc41b1 100644 --- a/backend/workflows/templates/workflows/developer_handbook.html +++ b/backend/workflows/templates/workflows/developer_handbook.html @@ -846,6 +846,7 @@ make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS
  • SESSION_IDLE_TIMEOUT_SECONDS controls the idle session window.
  • SENSITIVE_ACTION_REAUTH_SECONDS controls when sensitive POST actions require fresh authentication.
  • /session/keepalive/ refreshes both session timestamps when the user chooses Angemeldet bleiben.
  • +
  • Open tabs now sync the confirmed session timestamp through browser storage and BroadcastChannel, so extending the session in one tab updates the warning state in the others.
  • This warning is meant to protect work in progress without silently relaxing the security middleware.