From cf224480cca4bb7f6e663467d6a7d25438d54848 Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Wed, 29 Jul 2026 16:07:57 +0200 Subject: [PATCH] [FEAT] Sessione + release: refresh token silenzioso + avviso "nuova versione" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Evita la "sessione scaduta" e i deploy che restano invisibili all'utente. - api.js: refresh PROATTIVO dell'access token ~2 min prima della scadenza (scheduleTokenRefresh su exp del JWT, riprogrammato ad ogni setTokens) + ensureFreshToken() per rinnovo immediato al ritorno sulla scheda. Con refresh a 7gg l'utente resta loggato senza mai vedere il login. - common.js: watch versione (poll version.json ogni 5 min + su visibilitychange) → banner "È disponibile una nuova versione — Aggiorna" (reload) quando cambia; nudge service worker (registration.update); su visibilitychange chiama anche api.ensureFreshToken(). Nessun hard-refresh manuale. - Cache-buster api/common ?v=20260729b. version 1.25.12. Solo frontend. Co-Authored-By: Claude Opus 4.8 (1M context) --- public/_app-bi-demo.html | 4 +-- public/admin/index.html | 4 +-- public/admin/organizations.html | 4 +-- public/admin/users.html | 4 +-- public/architecture.html | 4 +-- public/assessment.html | 4 +-- public/assets.html | 4 +-- public/calendario.html | 4 +-- public/companies.html | 4 +-- public/competenze.html | 4 +-- public/connettori-discovery.html | 4 +-- public/controlli-periodici.html | 4 +-- public/cross-analysis.html | 4 +-- public/dashboard.html | 4 +-- public/forgot-password.html | 2 +- public/guida.html | 4 +-- public/incidents.html | 4 +-- public/integrazioniext.html | 4 +-- public/internal-audits.html | 4 +-- public/isms.html | 4 +-- public/js/api.js | 37 +++++++++++++++++++++ public/js/common.js | 52 ++++++++++++++++++++++++++++++ public/kb.html | 4 +-- public/licenseExt.html | 2 +- public/login.html | 4 +-- public/management-review.html | 4 +-- public/misure-requisiti.html | 4 +-- public/mktg-api-doc.html | 2 +- public/normative.html | 4 +-- public/onboarding.html | 4 +-- public/organigramma.html | 4 +-- public/policies.html | 4 +-- public/raci.html | 4 +-- public/register.html | 4 +-- public/reports.html | 4 +-- public/review-schedule.html | 4 +-- public/risks.html | 4 +-- public/settings.html | 4 +-- public/stakeholder-activities.html | 4 +-- public/stakeholders.html | 4 +-- public/supply-chain.html | 4 +-- public/training.html | 4 +-- public/version.json | 2 +- public/whistleblowing.html | 4 +-- public/workflow.html | 4 +-- 45 files changed, 171 insertions(+), 82 deletions(-) diff --git a/public/_app-bi-demo.html b/public/_app-bi-demo.html index b494276..ab338c7 100644 --- a/public/_app-bi-demo.html +++ b/public/_app-bi-demo.html @@ -70,8 +70,8 @@ - - + + - + + diff --git a/public/admin/organizations.html b/public/admin/organizations.html index 3c45598..9e40bf0 100644 --- a/public/admin/organizations.html +++ b/public/admin/organizations.html @@ -85,8 +85,8 @@ - - + + diff --git a/public/admin/users.html b/public/admin/users.html index d37627b..32d8740 100644 --- a/public/admin/users.html +++ b/public/admin/users.html @@ -92,8 +92,8 @@ - - + + diff --git a/public/architecture.html b/public/architecture.html index 79a27b6..fe8d1e3 100644 --- a/public/architecture.html +++ b/public/architecture.html @@ -1122,8 +1122,8 @@ - - + + diff --git a/public/assessment.html b/public/assessment.html index f38d073..16ba36f 100644 --- a/public/assessment.html +++ b/public/assessment.html @@ -270,8 +270,8 @@ - - + + diff --git a/public/assets.html b/public/assets.html index ae853aa..eb389a3 100644 --- a/public/assets.html +++ b/public/assets.html @@ -377,8 +377,8 @@ - - + + diff --git a/public/calendario.html b/public/calendario.html index 2dc4e96..8640cd7 100644 --- a/public/calendario.html +++ b/public/calendario.html @@ -190,8 +190,8 @@ - - + + diff --git a/public/companies.html b/public/companies.html index c5f6033..be103b6 100644 --- a/public/companies.html +++ b/public/companies.html @@ -197,8 +197,8 @@ - - + + diff --git a/public/competenze.html b/public/competenze.html index 3932fcb..668550c 100644 --- a/public/competenze.html +++ b/public/competenze.html @@ -192,8 +192,8 @@ - - + + diff --git a/public/connettori-discovery.html b/public/connettori-discovery.html index bf40e13..a3dbb27 100644 --- a/public/connettori-discovery.html +++ b/public/connettori-discovery.html @@ -98,8 +98,8 @@ - - + + diff --git a/public/controlli-periodici.html b/public/controlli-periodici.html index f3db7bb..a247c7b 100644 --- a/public/controlli-periodici.html +++ b/public/controlli-periodici.html @@ -155,8 +155,8 @@ - - + + diff --git a/public/cross-analysis.html b/public/cross-analysis.html index cdc1a5f..482b12f 100644 --- a/public/cross-analysis.html +++ b/public/cross-analysis.html @@ -222,8 +222,8 @@ - - + + diff --git a/public/dashboard.html b/public/dashboard.html index 6bbd068..60ae281 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -112,8 +112,8 @@ - - + + diff --git a/public/forgot-password.html b/public/forgot-password.html index 1a4dfd8..852272e 100644 --- a/public/forgot-password.html +++ b/public/forgot-password.html @@ -73,7 +73,7 @@ - + - + + diff --git a/public/incidents.html b/public/incidents.html index 9b652c7..1a27a2f 100644 --- a/public/incidents.html +++ b/public/incidents.html @@ -359,8 +359,8 @@ - - + + diff --git a/public/integrazioniext.html b/public/integrazioniext.html index ec261b7..f9769f3 100644 --- a/public/integrazioniext.html +++ b/public/integrazioniext.html @@ -931,8 +931,8 @@ curl -H "X-API-Key: nis2_TUA_CHIAVE" \ - - + + diff --git a/public/internal-audits.html b/public/internal-audits.html index 7f52298..2a71d25 100644 --- a/public/internal-audits.html +++ b/public/internal-audits.html @@ -160,8 +160,8 @@ - - + + diff --git a/public/isms.html b/public/isms.html index 6401e7f..8f55de1 100644 --- a/public/isms.html +++ b/public/isms.html @@ -199,8 +199,8 @@ - - + + diff --git a/public/js/api.js b/public/js/api.js index 8d472bb..a2635e1 100644 --- a/public/js/api.js +++ b/public/js/api.js @@ -10,6 +10,42 @@ class NIS2API { this.token = localStorage.getItem('nis2_access_token'); this.refreshToken = localStorage.getItem('nis2_refresh_token'); this.orgId = localStorage.getItem('nis2_org_id'); + this._refreshTimer = null; + if (this.token) this.scheduleTokenRefresh(); + } + + // ── Refresh proattivo/silenzioso ──────────────────────────────────── + // Rinnova l'access token PRIMA che scada, così l'utente non incontra 401 né deve + // rifare il login (il refresh token dura 7 giorni). Evita la "sessione scaduta". + _tokenExp(token) { + try { + const raw = (token || this.token || '').split('.')[1]; + const p = JSON.parse(atob(raw.replace(/-/g, '+').replace(/_/g, '/'))); + return (typeof p.exp === 'number') ? p.exp : 0; + } catch (e) { return 0; } + } + + scheduleTokenRefresh() { + if (this._refreshTimer) { clearTimeout(this._refreshTimer); this._refreshTimer = null; } + if (!this.token || !this.refreshToken) return; + const exp = this._tokenExp(this.token); + if (!exp) return; + let delay = exp * 1000 - Date.now() - 120000; // rinnova ~2 min prima della scadenza + if (delay < 1000) delay = 1000; + if (delay > 2000000000) delay = 2000000000; // cap (limite setTimeout a 32 bit) + this._refreshTimer = setTimeout(() => { + if (this.isAuthenticated() && this.refreshToken) this.doRefreshToken().catch(() => {}); + }, delay); + } + + // Rinnova SUBITO se l'access è scaduto o entro 5 min (usato al ritorno sulla scheda + // dopo sospensione/inattività, quando i timer possono non essere scattati). + async ensureFreshToken() { + if (!this.token || !this.refreshToken) return; + const exp = this._tokenExp(this.token); + if (exp && exp * 1000 - Date.now() < 300000) { + await this.doRefreshToken().catch(() => {}); + } } // ═══════════════════════════════════════════════════════════════════ @@ -133,6 +169,7 @@ class NIS2API { this.refreshToken = refresh; localStorage.setItem('nis2_access_token', access); localStorage.setItem('nis2_refresh_token', refresh); + this.scheduleTokenRefresh(); // riprogramma il refresh silenzioso sul nuovo exp } clearTokens() { diff --git a/public/js/common.js b/public/js/common.js index 86df077..552d4b8 100644 --- a/public/js/common.js +++ b/public/js/common.js @@ -507,11 +507,17 @@ async function _loadFirmBranding() { // ── Versioning live (Fase 4 / G13) ────────────────────────────────────── let _versionInfo = null; +let _loadedVersion = null; // versione con cui l'app è stata caricata (per il check aggiornamenti) +let _updateWatchStarted = false; async function _loadVersionFooter() { try { const r = await fetch('/version.json?_=' + Date.now()); if (!r.ok) return; _versionInfo = await r.json(); + if (_loadedVersion === null && _versionInfo && _versionInfo.version) { + _loadedVersion = _versionInfo.version; + _startUpdateWatch(); + } const el = document.getElementById('sidebar-version'); if (el && _versionInfo.version) { el.textContent = 'v' + _versionInfo.version + (_versionInfo.build ? ' · ' + _versionInfo.build : ''); @@ -524,6 +530,52 @@ function _showVersionChangelog() { alert('NIS2 Agile v' + v.version + '\nBuild: ' + (v.build || '—') + '\nData: ' + (v.date || '—') + '\n\n' + (v.changelog || 'Nessun changelog disponibile')); } +// ── Gestione release: avvisa (senza forzare) quando è online una nuova versione ── +// Poll di version.json ogni 5 min + al ritorno sulla scheda; se la versione è cambiata +// mostra un banner "Aggiorna" che ricarica (asset freschi via cache-buster + service worker). +// Al ritorno sulla scheda rinnova anche il token (evita la sessione scaduta dopo sospensione). +function _startUpdateWatch() { + if (_updateWatchStarted) return; + _updateWatchStarted = true; + setInterval(_checkForUpdate, 5 * 60 * 1000); + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + _checkForUpdate(); + if (typeof api !== 'undefined' && api.ensureFreshToken) api.ensureFreshToken(); + } + }); + _swUpdate(); +} +function _swUpdate() { + try { if (navigator.serviceWorker) navigator.serviceWorker.getRegistration().then(r => { if (r) r.update(); }).catch(() => {}); } catch (e) { /* noop */ } +} +async function _checkForUpdate() { + try { + const r = await fetch('/version.json?_=' + Date.now(), { cache: 'no-store' }); + if (!r.ok) return; + const info = await r.json(); + if (info && info.version && _loadedVersion && info.version !== _loadedVersion) { + _showUpdateBanner(info.version); + } + _swUpdate(); + } catch (e) { /* silenzioso */ } +} +function _showUpdateBanner(newVersion) { + if (document.getElementById('nis2-update-banner')) return; + const b = document.createElement('div'); + b.id = 'nis2-update-banner'; + b.style.cssText = 'position:fixed;left:50%;bottom:20px;transform:translateX(-50%);z-index:9998;' + + 'background:#0A2E54;color:#fff;border-radius:12px;padding:12px 16px;display:flex;align-items:center;gap:14px;' + + 'box-shadow:0 10px 30px rgba(11,40,80,.28);font:500 14px/1.4 Inter,system-ui,Arial,sans-serif;max-width:92vw'; + b.innerHTML = + 'È disponibile una nuova versione' + (newVersion ? ' (v' + escapeHtml(String(newVersion)) + ')' : '') + '. Aggiorna per averla.' + + '' + + ''; + document.body.appendChild(b); + document.getElementById('nis2-update-btn').addEventListener('click', () => location.reload()); + document.getElementById('nis2-update-x').addEventListener('click', () => b.remove()); +} + function _setupMobileToggle() { // Crea pulsante toggle se non esiste if (!document.querySelector('.sidebar-toggle')) { diff --git a/public/kb.html b/public/kb.html index 24e4cc9..cdd8bf9 100644 --- a/public/kb.html +++ b/public/kb.html @@ -175,8 +175,8 @@ - - + + diff --git a/public/licenseExt.html b/public/licenseExt.html index 84edb3e..a6fd36f 100644 --- a/public/licenseExt.html +++ b/public/licenseExt.html @@ -515,7 +515,7 @@ body { background: var(--bg-main); }
- + - - + + - + + diff --git a/public/misure-requisiti.html b/public/misure-requisiti.html index 2859c53..c5d4b82 100644 --- a/public/misure-requisiti.html +++ b/public/misure-requisiti.html @@ -175,8 +175,8 @@ - - + + diff --git a/public/mktg-api-doc.html b/public/mktg-api-doc.html index ddf581b..160c0b5 100644 --- a/public/mktg-api-doc.html +++ b/public/mktg-api-doc.html @@ -426,6 +426,6 @@ curl https://nis2.agile.software/api/services/status - + diff --git a/public/normative.html b/public/normative.html index a900095..79d5743 100644 --- a/public/normative.html +++ b/public/normative.html @@ -137,8 +137,8 @@ - - + + diff --git a/public/onboarding.html b/public/onboarding.html index 34c1c8f..e10ae9f 100644 --- a/public/onboarding.html +++ b/public/onboarding.html @@ -494,8 +494,8 @@ - - + + - + + diff --git a/public/policies.html b/public/policies.html index e95fa94..c9989f6 100644 --- a/public/policies.html +++ b/public/policies.html @@ -340,8 +340,8 @@ - - + + diff --git a/public/raci.html b/public/raci.html index 5ed7c7b..1e544b7 100644 --- a/public/raci.html +++ b/public/raci.html @@ -141,8 +141,8 @@ - - + + diff --git a/public/register.html b/public/register.html index fc5f367..f5140fe 100644 --- a/public/register.html +++ b/public/register.html @@ -268,8 +268,8 @@ - - + + - + + diff --git a/public/review-schedule.html b/public/review-schedule.html index 89131a4..5bc5fac 100644 --- a/public/review-schedule.html +++ b/public/review-schedule.html @@ -157,8 +157,8 @@ - - + + diff --git a/public/risks.html b/public/risks.html index 1659ce1..48610ae 100644 --- a/public/risks.html +++ b/public/risks.html @@ -499,8 +499,8 @@ - - + + diff --git a/public/settings.html b/public/settings.html index 551c37f..2308c79 100644 --- a/public/settings.html +++ b/public/settings.html @@ -735,8 +735,8 @@ - - + + diff --git a/public/stakeholder-activities.html b/public/stakeholder-activities.html index 1654cec..6ee4615 100644 --- a/public/stakeholder-activities.html +++ b/public/stakeholder-activities.html @@ -215,8 +215,8 @@ - - + + diff --git a/public/stakeholders.html b/public/stakeholders.html index d1bf1e4..a643a14 100644 --- a/public/stakeholders.html +++ b/public/stakeholders.html @@ -210,8 +210,8 @@ - - + + diff --git a/public/supply-chain.html b/public/supply-chain.html index 1a27213..721a983 100644 --- a/public/supply-chain.html +++ b/public/supply-chain.html @@ -542,8 +542,8 @@ - - + + diff --git a/public/training.html b/public/training.html index be1bd2c..9a1ce8a 100644 --- a/public/training.html +++ b/public/training.html @@ -190,8 +190,8 @@ - - + + diff --git a/public/version.json b/public/version.json index ef287fc..4380918 100644 --- a/public/version.json +++ b/public/version.json @@ -1 +1 @@ -{"version": "1.25.11", "build": "2026-07-29-v1.25.11", "date": "2026-07-29", "changelog": "Cruscotto Studio: ora e' previsto anche il ruolo CONSULENTE. Un utente con ruolo 'consulente' vede nel selettore azienda e nel Cruscotto Studio SOLO le aziende del proprio studio (i clienti collegati al proprio studio) piu' quelle di cui e' membro diretto, non l'intero elenco (che resta agli amministratori)."} +{"version": "1.25.12", "build": "2026-07-29-v1.25.12", "date": "2026-07-29", "changelog": "Sessione e aggiornamenti piu' fluidi. (1) Il collegamento ora si RINNOVA DA SOLO in background poco prima di scadere (e al ritorno sulla scheda dopo una pausa il rinnovo e' immediato): non serve piu' rifare il login dopo un po', e le pagine non restano piu' 'appese'. (2) Quando esce una NUOVA VERSIONE dell'app, compare in basso un avviso 'E' disponibile una nuova versione - Aggiorna': un clic ricarica con la versione aggiornata, senza dover svuotare la cache o ricaricare a mano."} diff --git a/public/whistleblowing.html b/public/whistleblowing.html index 5ab3851..817a000 100644 --- a/public/whistleblowing.html +++ b/public/whistleblowing.html @@ -258,8 +258,8 @@ - - + + diff --git a/public/workflow.html b/public/workflow.html index 42fd064..137f3a3 100644 --- a/public/workflow.html +++ b/public/workflow.html @@ -143,8 +143,8 @@ - - + +