/** * session_timeout.js - scollega l'utente dopo un periodo di INATTIVITÀ vera (mouse, tastiera, tocco, scorrimento), * con un avviso e il conto alla rovescia prima della scadenza. * * - L'ultima attività è condivisa tra le schede dello stesso browser (localStorage): se lavori in una scheda * le altre non ti scollegano. * - Finché l'utente è attivo, al massimo una volta al minuto la sessione viene timbrata sul server (session_ping.php). * - Le chiamate automatiche delle pagine (aggiornamenti periodici) NON contano come attività. * * Configurazione da window.SESSION_TIMEOUT (scritta da include/session_timeout.php). */ (function () { "use strict"; const CFG = Object.assign( { lifetimeMin: 120, warnSec: 120, // avviso N secondi prima marginSec: 90, // scollego un po' prima della scadenza vera del server pingEverySec: 60, logoutUrl: "/logout", loginUrl: "/login", pingUrl: "session_ping.php", }, window.SESSION_TIMEOUT || {}, ); const KEY = "zibo_last_activity"; const LIMIT = Math.max(120, CFG.lifetimeMin * 60 - CFG.marginSec) * 1000; // inattività massima (ms) const WARN = Math.min(CFG.warnSec * 1000, LIMIT - 30000); let lastPing = 0; let box = null; let leaving = false; /* ---------------- ultima attività, condivisa tra schede ---------------- */ let memLast = Date.now(); function getLast() { try { const v = parseInt(localStorage.getItem(KEY), 10); if (v && v > memLast) memLast = v; } catch (e) {} return memLast; } function setLast(t) { memLast = t; try { localStorage.setItem(KEY, String(t)); } catch (e) {} } /* ---------------- ping al server ---------------- */ function ping(force) { const now = Date.now(); if (!force && now - lastPing < CFG.pingEverySec * 1000) return Promise.resolve(true); lastPing = now; return fetch(CFG.pingUrl + "?t=" + now, { credentials: "same-origin", cache: "no-store", headers: { "X-Requested-With": "XMLHttpRequest" }, }) .then(function (r) { const ct = r.headers.get("content-type") || ""; if (!r.ok || ct.indexOf("application/json") === -1) throw new Error("sessione scaduta"); return r.json(); }) .then(function (d) { if (!d || !d.ok) throw new Error("sessione scaduta"); return true; }) .catch(function () { // il server non ci riconosce più: al login goLogin(); return false; }); } function goLogin() { if (leaving) return; leaving = true; location.href = CFG.loginUrl; } function logout() { if (leaving) return; leaving = true; try { localStorage.removeItem(KEY); } catch (e) {} location.href = CFG.logoutUrl; } /* ---------------- attività dell'utente ---------------- */ let throttle = 0; function activity() { if (leaving) return; const now = Date.now(); if (now - throttle < 2000) return; // basta una registrazione ogni 2 secondi throttle = now; if (box) return; // con l'avviso aperto conta solo il pulsante setLast(now); ping(false); } [ "mousedown", "mousemove", "keydown", "touchstart", "scroll", "wheel", ].forEach(function (ev) { window.addEventListener(ev, activity, { passive: true, capture: true }); }); document.addEventListener("visibilitychange", function () { if (!document.hidden) check(); }); window.addEventListener("storage", function (e) { if (e.key === KEY && box) { // l'utente è tornato attivo in un'altra scheda const idle = Date.now() - getLast(); if (idle < LIMIT - WARN) hideBox(); } }); /* ---------------- avviso ---------------- */ function css() { if (document.getElementById("st-css")) return; const s = document.createElement("style"); s.id = "st-css"; s.textContent = ".st-ov{position:fixed;inset:0;z-index:20000;background:rgba(15,23,42,.55);display:flex;align-items:center;justify-content:center;padding:16px}" + ".st-box{background:#fff;border-radius:18px;max-width:440px;width:100%;padding:26px 24px 22px;text-align:center;box-shadow:0 24px 60px rgba(0,0,0,.3);font-family:inherit;color:#1f2d3d}" + ".st-ico{width:64px;height:64px;border-radius:50%;background:#fef3c7;color:#b45309;display:flex;align-items:center;justify-content:center;font-size:30px;margin:0 auto 12px}" + ".st-t{font-size:1.25rem;font-weight:800;margin-bottom:6px}" + ".st-p{color:#475569;margin-bottom:10px}" + ".st-c{font-size:2.6rem;font-weight:900;color:#b45309;font-variant-numeric:tabular-nums;margin:6px 0 16px}" + ".st-b{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}" + ".st-b button{border:0;border-radius:12px;padding:13px 22px;font-weight:800;font-size:1rem;min-height:50px;cursor:pointer}" + ".st-stay{background:#198754;color:#fff;flex:1 1 200px}" + ".st-out{background:#f1f5f9;color:#334155}"; document.head.appendChild(s); } function showBox() { if (box || leaving) return; css(); box = document.createElement("div"); box.className = "st-ov"; box.setAttribute("role", "alertdialog"); box.setAttribute("aria-modal", "true"); box.innerHTML = '