UNPKG

inactivity-warning

Version:

Multi-framework inactivity warning component for Vue 3, React, Angular and Vanilla JavaScript

262 lines (229 loc) 7.81 kB
import { useState, useEffect, useCallback, useRef } from 'react' /** * Hook personalizado para manejar el cierre automático de sesión por inactividad en React * @param {Object} options - Opciones de configuración * @param {number} options.timeoutSeconds - Tiempo en segundos para cerrar sesión (default: 60 = 1 minuto) * @param {number} options.warningSeconds - Tiempo en segundos para mostrar advertencia (default: 20 = 20 segundos) * @param {Function} options.onLogout - Callback ejecutado al cerrar sesión * @param {Function} options.onWarning - Callback ejecutado al mostrar advertencia * @param {Function} options.onExtend - Callback ejecutado al extender sesión * @param {Array} options.resetEvents - Eventos que resetean el timer (opcional) * @param {boolean} options.enabled - Si el timer está habilitado (default: true) */ export function useInactivityTimer(options = {}) { const { timeoutSeconds = 60, warningSeconds = 20, onLogout = () => { }, onWarning = () => { }, onExtend = () => { }, resetEvents = [ 'mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click', 'keydown' ], enabled = true } = options // Estado const [isWarningVisible, setIsWarningVisible] = useState(false) const [timeRemaining, setTimeRemaining] = useState(0) const [isActive, setIsActive] = useState(false) // Referencias para los timers const inactivityTimerRef = useRef(null) const warningTimerRef = useRef(null) const countdownTimerRef = useRef(null) /** * Resetea el timer de inactividad */ const resetTimer = useCallback(() => { // Limpiar timers existentes if (inactivityTimerRef.current) { clearTimeout(inactivityTimerRef.current) inactivityTimerRef.current = null } if (warningTimerRef.current) { clearTimeout(warningTimerRef.current) warningTimerRef.current = null } if (countdownTimerRef.current) { clearInterval(countdownTimerRef.current) countdownTimerRef.current = null } // Ocultar advertencia si está visible setIsWarningVisible(false) // Solo iniciar timer si está habilitado if (!enabled || !isActive) { return } // Configurar timer de advertencia const warningTimeMs = (timeoutSeconds - warningSeconds) * 1000 warningTimerRef.current = setTimeout(() => { showWarning() }, warningTimeMs) // Configurar timer de cierre de sesión const timeoutMs = timeoutSeconds * 1000 inactivityTimerRef.current = setTimeout(() => { logout() }, timeoutMs) }, [enabled, isActive, timeoutSeconds, warningSeconds]) /** * Muestra la advertencia de inactividad */ const showWarning = useCallback(() => { setIsWarningVisible(true) setTimeRemaining(warningSeconds) // Ejecutar callback de advertencia onWarning() // Iniciar countdown countdownTimerRef.current = setInterval(() => { setTimeRemaining(prev => { if (prev <= 1) { clearInterval(countdownTimerRef.current) logout() return 0 } return prev - 1 }) }, 1000) }, [warningSeconds, onWarning]) /** * Extiende la sesión cuando el usuario confirma */ const extendSession = useCallback(() => { setIsWarningVisible(false) if (countdownTimerRef.current) { clearInterval(countdownTimerRef.current) countdownTimerRef.current = null } // Ejecutar callback de extensión onExtend() resetTimer() }, [onExtend, resetTimer]) /** * Cierra la sesión por inactividad */ const logout = useCallback(async () => { try { // Limpiar todos los timers if (inactivityTimerRef.current) { clearTimeout(inactivityTimerRef.current) inactivityTimerRef.current = null } if (warningTimerRef.current) { clearTimeout(warningTimerRef.current) warningTimerRef.current = null } if (countdownTimerRef.current) { clearInterval(countdownTimerRef.current) countdownTimerRef.current = null } // Ocultar advertencia setIsWarningVisible(false) // Ejecutar callback de logout await onLogout() console.log('Sesión cerrada por inactividad') } catch (error) { console.error('Error al cerrar sesión por inactividad:', error) } }, [onLogout]) /** * Inicia el monitoring de inactividad */ const startMonitoring = useCallback(() => { setIsActive(true) }, []) /** * Detiene el monitoring de inactividad */ const stopMonitoring = useCallback(() => { setIsActive(false) // Limpiar todos los timers if (inactivityTimerRef.current) { clearTimeout(inactivityTimerRef.current) inactivityTimerRef.current = null } if (warningTimerRef.current) { clearTimeout(warningTimerRef.current) warningTimerRef.current = null } if (countdownTimerRef.current) { clearInterval(countdownTimerRef.current) countdownTimerRef.current = null } // Ocultar advertencia setIsWarningVisible(false) }, []) /** * Pausa el monitoring temporalmente */ const pauseMonitoring = useCallback(() => { if (inactivityTimerRef.current) { clearTimeout(inactivityTimerRef.current) inactivityTimerRef.current = null } if (warningTimerRef.current) { clearTimeout(warningTimerRef.current) warningTimerRef.current = null } if (countdownTimerRef.current) { clearInterval(countdownTimerRef.current) countdownTimerRef.current = null } }, []) /** * Reanuda el monitoring */ const resumeMonitoring = useCallback(() => { if (isActive) { resetTimer() } }, [isActive, resetTimer]) /** * Formatea el tiempo restante en formato MM:SS */ const formatTimeRemaining = useCallback(() => { const minutes = Math.floor(timeRemaining / 60) const seconds = timeRemaining % 60 return `${minutes}:${seconds.toString().padStart(2, '0')}` }, [timeRemaining]) // Efecto para manejar los event listeners useEffect(() => { if (!isActive) return // Agregar event listeners resetEvents.forEach(event => { document.addEventListener(event, resetTimer, true) }) // Iniciar timer resetTimer() // Cleanup return () => { resetEvents.forEach(event => { document.removeEventListener(event, resetTimer, true) }) } }, [isActive, resetEvents, resetTimer]) // Cleanup al desmontar useEffect(() => { return () => { stopMonitoring() } }, [stopMonitoring]) return { // Estado isWarningVisible, timeRemaining, isActive, // Métodos startMonitoring, stopMonitoring, pauseMonitoring, resumeMonitoring, extendSession, resetTimer, formatTimeRemaining } }