2026-04-16 16:03:10 +00:00
|
|
|
import { auth } from './auth.js';
|
2026-05-28 17:29:27 +00:00
|
|
|
import { initVeriFactuStatus, resetVeriFactuStatus } from './verifactu.js';
|
2026-04-16 16:03:10 +00:00
|
|
|
|
|
|
|
|
const SESSION_TIMEOUT_MS = Number(import.meta.env.VITE_SESSION_TIMEOUT_MS || 15 * 60 * 1000);
|
|
|
|
|
const SESSION_WARNING_MS = Number(import.meta.env.VITE_SESSION_WARNING_MS || 60 * 1000);
|
|
|
|
|
|
|
|
|
|
let inactivityTimer = null;
|
|
|
|
|
let warningTimer = null;
|
|
|
|
|
let warningCountdownTimer = null;
|
|
|
|
|
let warningEndsAt = null;
|
|
|
|
|
let warningOverlay = null;
|
|
|
|
|
let hasInitialized = false;
|
|
|
|
|
let fetchIsPatched = false;
|
|
|
|
|
|
|
|
|
|
function isAuthenticated() {
|
|
|
|
|
return Boolean(localStorage.getItem('token'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearTimers() {
|
|
|
|
|
if (inactivityTimer) {
|
|
|
|
|
clearTimeout(inactivityTimer);
|
|
|
|
|
inactivityTimer = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (warningTimer) {
|
|
|
|
|
clearTimeout(warningTimer);
|
|
|
|
|
warningTimer = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (warningCountdownTimer) {
|
|
|
|
|
clearInterval(warningCountdownTimer);
|
|
|
|
|
warningCountdownTimer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function removeWarningOverlay() {
|
|
|
|
|
if (warningOverlay) {
|
|
|
|
|
warningOverlay.remove();
|
|
|
|
|
warningOverlay = null;
|
|
|
|
|
}
|
|
|
|
|
warningEndsAt = null;
|
|
|
|
|
if (warningCountdownTimer) {
|
|
|
|
|
clearInterval(warningCountdownTimer);
|
|
|
|
|
warningCountdownTimer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getRemainingSeconds() {
|
|
|
|
|
if (!warningEndsAt) return 0;
|
|
|
|
|
return Math.max(0, Math.ceil((warningEndsAt - Date.now()) / 1000));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function forceSessionExpiration(message = 'Tu sesion ha expirado. Inicia sesion de nuevo.') {
|
|
|
|
|
removeWarningOverlay();
|
|
|
|
|
clearTimers();
|
|
|
|
|
auth.logout();
|
|
|
|
|
sessionStorage.setItem('session-expired-message', message);
|
|
|
|
|
window.location.hash = '#login';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function showWarningOverlay() {
|
|
|
|
|
if (!isAuthenticated()) return;
|
|
|
|
|
|
|
|
|
|
removeWarningOverlay();
|
|
|
|
|
|
|
|
|
|
const overlay = document.createElement('div');
|
|
|
|
|
overlay.className = 'session-warning-overlay';
|
|
|
|
|
overlay.innerHTML = `
|
|
|
|
|
<div class="session-warning-modal" role="dialog" aria-modal="true" aria-labelledby="session-warning-title">
|
|
|
|
|
<h3 id="session-warning-title">Tu sesion va a expirar</h3>
|
|
|
|
|
<p>Por inactividad, se cerrara tu sesion automaticamente en <strong id="session-warning-countdown"></strong>.</p>
|
|
|
|
|
<div class="session-warning-actions">
|
|
|
|
|
<button type="button" class="session-warning-logout">Cerrar sesion ahora</button>
|
|
|
|
|
<button type="button" class="session-warning-continue">Seguir conectado</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const continueBtn = overlay.querySelector('.session-warning-continue');
|
|
|
|
|
const logoutBtn = overlay.querySelector('.session-warning-logout');
|
|
|
|
|
const countdownEl = overlay.querySelector('#session-warning-countdown');
|
|
|
|
|
|
|
|
|
|
continueBtn?.addEventListener('click', () => {
|
|
|
|
|
removeWarningOverlay();
|
|
|
|
|
resetSessionTimers();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logoutBtn?.addEventListener('click', () => {
|
|
|
|
|
forceSessionExpiration('Sesion finalizada por inactividad.');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
warningEndsAt = Date.now() + SESSION_WARNING_MS;
|
|
|
|
|
|
|
|
|
|
const updateCountdown = () => {
|
|
|
|
|
const seconds = getRemainingSeconds();
|
|
|
|
|
const mins = String(Math.floor(seconds / 60)).padStart(2, '0');
|
|
|
|
|
const secs = String(seconds % 60).padStart(2, '0');
|
|
|
|
|
if (countdownEl) {
|
|
|
|
|
countdownEl.textContent = `${mins}:${secs}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (seconds <= 0) {
|
|
|
|
|
forceSessionExpiration();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
updateCountdown();
|
|
|
|
|
warningCountdownTimer = setInterval(updateCountdown, 1000);
|
|
|
|
|
|
|
|
|
|
warningOverlay = overlay;
|
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetSessionTimers() {
|
|
|
|
|
clearTimers();
|
|
|
|
|
|
|
|
|
|
if (!isAuthenticated()) {
|
|
|
|
|
removeWarningOverlay();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const warningDelay = Math.max(SESSION_TIMEOUT_MS - SESSION_WARNING_MS, 1000);
|
|
|
|
|
warningTimer = setTimeout(showWarningOverlay, warningDelay);
|
|
|
|
|
inactivityTimer = setTimeout(() => forceSessionExpiration(), SESSION_TIMEOUT_MS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function patchFetchFor401AndActivity() {
|
|
|
|
|
if (fetchIsPatched) return;
|
|
|
|
|
|
|
|
|
|
const originalFetch = window.fetch.bind(window);
|
|
|
|
|
|
|
|
|
|
window.fetch = async (...args) => {
|
|
|
|
|
const requestUrl = typeof args[0] === 'string' ? args[0] : (args[0]?.url || '');
|
|
|
|
|
const isLoginEndpoint = requestUrl.includes('/api/Auth/login');
|
|
|
|
|
|
|
|
|
|
if (!isLoginEndpoint && isAuthenticated()) {
|
|
|
|
|
resetSessionTimers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await originalFetch(...args);
|
|
|
|
|
|
|
|
|
|
if (response.status === 401 && !isLoginEndpoint && isAuthenticated()) {
|
|
|
|
|
forceSessionExpiration('Tu sesion ha expirado o ya no es valida. Inicia sesion de nuevo.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fetchIsPatched = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function bindActivityListeners() {
|
|
|
|
|
const events = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll'];
|
|
|
|
|
|
|
|
|
|
const onActivity = () => {
|
|
|
|
|
if (isAuthenticated()) {
|
|
|
|
|
resetSessionTimers();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
events.forEach(eventName => {
|
|
|
|
|
window.addEventListener(eventName, onActivity, { passive: true });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function bindAuthListeners() {
|
|
|
|
|
window.addEventListener('auth:login', () => {
|
|
|
|
|
removeWarningOverlay();
|
|
|
|
|
resetSessionTimers();
|
2026-05-28 17:29:27 +00:00
|
|
|
initVeriFactuStatus();
|
2026-04-16 16:03:10 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.addEventListener('auth:logout', () => {
|
|
|
|
|
removeWarningOverlay();
|
|
|
|
|
clearTimers();
|
2026-05-28 17:29:27 +00:00
|
|
|
resetVeriFactuStatus();
|
2026-04-16 16:03:10 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function initSessionManager() {
|
|
|
|
|
if (hasInitialized) return;
|
|
|
|
|
|
|
|
|
|
patchFetchFor401AndActivity();
|
|
|
|
|
bindActivityListeners();
|
|
|
|
|
bindAuthListeners();
|
|
|
|
|
resetSessionTimers();
|
|
|
|
|
|
|
|
|
|
hasInitialized = true;
|
|
|
|
|
}
|