¿Es posible determinar si un usuario está inactivo y cerrar sesión automáticamente después de, digamos, 10 minutos de inactividad usando angularjs?
Estaba tratando de evitar el uso de jQuery, pero no puedo encontrar tutoriales o artículos sobre cómo hacer esto en angularjs. Cualquier ayuda sería apreciada.
Respuestas:
Escribí un módulo llamado
Ng-Idle
que puede serle útil en esta situación. Aquí está la página que contiene instrucciones y una demostración.Básicamente, tiene un servicio que inicia un temporizador durante el tiempo de inactividad que puede verse interrumpido por la actividad del usuario (eventos, como hacer clic, desplazarse, escribir). También puede interrumpir manualmente el tiempo de espera llamando a un método en el servicio. Si el tiempo de espera no se interrumpe, se muestra una advertencia en la que puede alertar al usuario de que se cerrará la sesión. Si no responden después de que la cuenta regresiva de advertencia llega a 0, se transmite un evento al que su aplicación puede responder. En su caso, podría emitir una solicitud para cerrar su sesión y redirigirla a una página de inicio de sesión.
Además, tiene un servicio de mantenimiento que puede hacer ping a alguna URL en un intervalo. Esto puede ser utilizado por su aplicación para mantener viva la sesión de un usuario mientras están activos. El servicio inactivo se integra de forma predeterminada con el servicio Keep-Alive, suspendiendo el ping si quedan inactivos y reanudándolo cuando regresan.
Toda la información que necesita para comenzar está en el sitio con más detalles en la wiki . Sin embargo, aquí hay un fragmento de configuración que muestra cómo cerrar la sesión cuando se agote el tiempo de espera.
angular.module('demo', ['ngIdle']) // omitted for brevity .config(function(IdleProvider, KeepaliveProvider) { IdleProvider.idle(10*60); // 10 minutes idle IdleProvider.timeout(30); // after 30 seconds idle, time the user out KeepaliveProvider.interval(5*60); // 5 minute keep-alive ping }) .run(function($rootScope) { $rootScope.$on('IdleTimeout', function() { // end their session and redirect to login }); });
fuente
Idle.watch()
la.run()
función, elIdleTimeout
evento no se activó en absoluto hasta que hice eso.Idle.watch()
Sin embargo, vi la llamada a en tu demo de github, así que de ahí la saqué.Vea la demostración que está usando
angularjs
y vea el registro de su navegador<!DOCTYPE html> <html ng-app="Application_TimeOut"> <head> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script> </head> <body> </body> <script> var app = angular.module('Application_TimeOut', []); app.run(function($rootScope, $timeout, $document) { console.log('starting run'); // Timeout timer value var TimeOutTimerValue = 5000; // Start a timeout var TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue); var bodyElement = angular.element($document); /// Keyboard Events bodyElement.bind('keydown', function (e) { TimeOut_Resetter(e) }); bodyElement.bind('keyup', function (e) { TimeOut_Resetter(e) }); /// Mouse Events bodyElement.bind('click', function (e) { TimeOut_Resetter(e) }); bodyElement.bind('mousemove', function (e) { TimeOut_Resetter(e) }); bodyElement.bind('DOMMouseScroll', function (e) { TimeOut_Resetter(e) }); bodyElement.bind('mousewheel', function (e) { TimeOut_Resetter(e) }); bodyElement.bind('mousedown', function (e) { TimeOut_Resetter(e) }); /// Touch Events bodyElement.bind('touchstart', function (e) { TimeOut_Resetter(e) }); bodyElement.bind('touchmove', function (e) { TimeOut_Resetter(e) }); /// Common Events bodyElement.bind('scroll', function (e) { TimeOut_Resetter(e) }); bodyElement.bind('focus', function (e) { TimeOut_Resetter(e) }); function LogoutByTimer() { console.log('Logout'); /////////////////////////////////////////////////// /// redirect to another page(eg. Login.html) here /////////////////////////////////////////////////// } function TimeOut_Resetter(e) { console.log('' + e); /// Stop the pending timeout $timeout.cancel(TimeOut_Thread); /// Reset the timeout TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue); } }) </script> </html>
El siguiente código es la versión pura de JavaScript
<html> <head> <script type="text/javascript"> function logout(){ console.log('Logout'); } function onInactive(millisecond, callback){ var wait = setTimeout(callback, millisecond); document.onmousemove = document.mousedown = document.mouseup = document.onkeydown = document.onkeyup = document.focus = function(){ clearTimeout(wait); wait = setTimeout(callback, millisecond); }; } </script> </head> <body onload="onInactive(5000, logout);"></body> </html>
ACTUALIZAR
Actualicé mi solución como sugerencia de @Tom.
<!DOCTYPE html> <html ng-app="Application_TimeOut"> <head> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script> </head> <body> </body> <script> var app = angular.module('Application_TimeOut', []); app.run(function($rootScope, $timeout, $document) { console.log('starting run'); // Timeout timer value var TimeOutTimerValue = 5000; // Start a timeout var TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue); var bodyElement = angular.element($document); angular.forEach(['keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart', 'touchmove', 'scroll', 'focus'], function(EventName) { bodyElement.bind(EventName, function (e) { TimeOut_Resetter(e) }); }); function LogoutByTimer(){ console.log('Logout'); /////////////////////////////////////////////////// /// redirect to another page(eg. Login.html) here /////////////////////////////////////////////////// } function TimeOut_Resetter(e){ console.log(' ' + e); /// Stop the pending timeout $timeout.cancel(TimeOut_Thread); /// Reset the timeout TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue); } }) </script> </html>
Haga clic aquí para ver en Plunker para obtener una versión actualizada
fuente
bodyElement.on(...)
yLogoutByTimer
ejecutarlo desde adentrobodyElement.off(...)
Debe haber diferentes formas de hacerlo y cada enfoque debe adaptarse mejor a una aplicación particular que a otra. Para la mayoría de las aplicaciones, simplemente puede manejar eventos de teclas o mouse y habilitar / deshabilitar un temporizador de cierre de sesión de manera apropiada. Dicho esto, en la parte superior de mi cabeza, una solución "elegante" de AngularJS-y está monitoreando el bucle de resumen, si no se ha activado ninguno durante la última [duración especificada], cierre la sesión. Algo como esto.
app.run(function($rootScope) { var lastDigestRun = new Date(); $rootScope.$watch(function detectIdle() { var now = new Date(); if (now - lastDigestRun > 10*60*60) { // logout here, like delete cookie, navigate to login ... } lastDigestRun = now; }); });
fuente
Jugado con el enfoque de Boo, sin embargo, no me gusta el hecho de que el usuario fue expulsado solo una vez que se ejecuta otro resumen, lo que significa que el usuario permanece conectado hasta que intenta hacer algo dentro de la página y luego se inicia inmediatamente.
Estoy tratando de forzar el cierre de sesión usando un intervalo que verifica cada minuto si la última acción fue hace más de 30 minutos. Lo enganché en $ routeChangeStart, pero también podría engancharlo en $ rootScope. $ Watch como en el ejemplo de Boo.
app.run(function($rootScope, $location, $interval) { var lastDigestRun = Date.now(); var idleCheck = $interval(function() { var now = Date.now(); if (now - lastDigestRun > 30*60*1000) { // logout } }, 60*1000); $rootScope.$on('$routeChangeStart', function(evt) { lastDigestRun = Date.now(); }); });
fuente
$rootScope.$watch
en este caso requeriría cambiar asetInterval
, ya$interval
que activará un resumen en cada llamada de función, restableciendo efectivamente su archivolastDigestRun
.También puede lograr el uso
angular-activity-monitor
de una manera más sencilla que inyectar múltiples proveedores y usasetInterval()
(en lugar de angular$interval
) para evitar activar manualmente un bucle de resumen (que es importante para evitar mantener los elementos vivos de forma involuntaria).En última instancia, solo se suscribe a algunos eventos que determinan cuándo un usuario está inactivo o se acerca. Entonces, si desea cerrar la sesión de un usuario después de 10 minutos de inactividad, puede usar el siguiente fragmento:
angular.module('myModule', ['ActivityMonitor']); MyController.$inject = ['ActivityMonitor']; function MyController(ActivityMonitor) { // how long (in seconds) until user is considered inactive ActivityMonitor.options.inactive = 600; ActivityMonitor.on('inactive', function() { // user is considered inactive, logout etc. }); ActivityMonitor.on('keepAlive', function() { // items to keep alive in the background while user is active }); ActivityMonitor.on('warning', function() { // alert user when they're nearing inactivity }); }
fuente
Probé el enfoque de Buu y no pude hacerlo bien debido a la gran cantidad de eventos que activan la ejecución del digestor, incluida la ejecución de las funciones $ interval y $ timeout. Esto deja la aplicación en un estado en el que nunca estará inactiva, independientemente de la entrada del usuario.
Si realmente necesita realizar un seguimiento del tiempo de inactividad del usuario, no estoy seguro de que haya un buen enfoque angular. Sugeriría que Witoldz represente un mejor enfoque aquí https://github.com/witoldsz/angular-http-auth . Este enfoque solicitará al usuario que se vuelva a autenticar cuando se realice una acción que requiera sus credenciales. Una vez que el usuario se ha autenticado, la solicitud fallida anterior se vuelve a procesar y la aplicación continúa como si nada.
Esto maneja la preocupación que podría tener de dejar que la sesión del usuario caduque mientras está activa, ya que incluso si su autenticación caduca, aún pueden retener el estado de la aplicación y no perder ningún trabajo.
Si tiene algún tipo de sesión en su cliente (cookies, tokens, etc.) también puede verlos y activar su proceso de cierre de sesión si caducan.
app.run(['$interval', function($interval) { $interval(function() { if (/* session still exists */) { } else { // log out of client } }, 1000); }]);
ACTUALIZACIÓN: Aquí hay un plunk que demuestra la preocupación. http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W . Lo que esto demuestra es que el tiempo de ejecución del digestor se actualiza solo cuando el intervalo marca. Una vez que el intervalo alcanza el recuento máximo, el digestor dejará de funcionar.
fuente
ng-Idle parece el camino a seguir, pero no pude entender las modificaciones de Brian F y también quería tiempo de espera para una sesión de suspensión, también tenía un caso de uso bastante simple en mente. Lo reduje al código de abajo. Engancha eventos para restablecer un indicador de tiempo de espera (colocado perezosamente en $ rootScope). Solo detecta que se ha agotado el tiempo de espera cuando el usuario regresa (y activa un evento), pero eso es lo suficientemente bueno para mí. No pude hacer que la ubicación $ de angular funcione aquí, pero nuevamente, usando document.location.href hace el trabajo.
Pegué esto en mi app.js después de que se haya ejecutado .config.
app.run(function($rootScope,$document) { var d = new Date(); var n = d.getTime(); //n in ms $rootScope.idleEndTime = n+(20*60*1000); //set end time to 20 min from now $document.find('body').on('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart', checkAndResetIdle); //monitor events function checkAndResetIdle() //user did something { var d = new Date(); var n = d.getTime(); //n in ms if (n>$rootScope.idleEndTime) { $document.find('body').off('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart'); //un-monitor events //$location.search('IntendedURL',$location.absUrl()).path('/login'); //terminate by sending to login page document.location.href = 'https://whatever.com/myapp/#/login'; alert('Session ended due to inactivity'); } else { $rootScope.idleEndTime = n+(20*60*1000); //reset end time } } });
fuente
Creo que el ciclo de digestión de Buu es genial. Gracias por compartir. Como otros han señalado, $ interval también hace que se ejecute el ciclo de resumen. Podríamos, con el propósito de cerrar automáticamente la sesión del usuario, usar setInterval que no causará un bucle de resumen.
app.run(function($rootScope) { var lastDigestRun = new Date(); setInterval(function () { var now = Date.now(); if (now - lastDigestRun > 10 * 60 * 1000) { //logout } }, 60 * 1000); $rootScope.$watch(function() { lastDigestRun = new Date(); }); });
fuente
He usado ng-idle para esto y agregué un pequeño código de cierre de sesión y token nulo y está funcionando bien, puedes probar esto. Gracias @HackedByChinese por hacer un módulo tan agradable.
En IdleTimeout, acabo de eliminar los datos de mi sesión y el token.
$scope.$on('IdleTimeout', function () { closeModals(); delete $window.sessionStorage.token; $state.go("login"); $scope.timedout = $uibModal.open({ templateUrl: 'timedout-dialog.html', windowClass: 'modal-danger' }); });
fuente
Me gustaría expandir las respuestas a quien pueda estar usando esto en un proyecto más grande, podría adjuntar accidentalmente múltiples controladores de eventos y el programa se comportaría de manera extraña.
Para deshacerme de eso, utilicé una función singleton expuesta por una fábrica, desde la cual llamarías
inactivityTimeoutFactory.switchTimeoutOn()
yinactivityTimeoutFactory.switchTimeoutOff()
en tu aplicación angular para activar y desactivar respectivamente el cierre de sesión debido a la funcionalidad de inactividad.De esta manera, se asegura de que solo está ejecutando una única instancia de los controladores de eventos, sin importar cuántas veces intente activar el procedimiento de tiempo de espera, lo que facilita su uso en aplicaciones donde el usuario puede iniciar sesión desde diferentes rutas.
Aquí está mi código:
'use strict'; angular.module('YOURMODULENAME') .factory('inactivityTimeoutFactory', inactivityTimeoutFactory); inactivityTimeoutFactory.$inject = ['$document', '$timeout', '$state']; function inactivityTimeoutFactory($document, $timeout, $state) { function InactivityTimeout () { // singleton if (InactivityTimeout.prototype._singletonInstance) { return InactivityTimeout.prototype._singletonInstance; } InactivityTimeout.prototype._singletonInstance = this; // Timeout timer value const timeToLogoutMs = 15*1000*60; //15 minutes const timeToWarnMs = 13*1000*60; //13 minutes // variables let warningTimer; let timeoutTimer; let isRunning; function switchOn () { if (!isRunning) { switchEventHandlers("on"); startTimeout(); isRunning = true; } } function switchOff() { switchEventHandlers("off"); cancelTimersAndCloseMessages(); isRunning = false; } function resetTimeout() { cancelTimersAndCloseMessages(); // reset timeout threads startTimeout(); } function cancelTimersAndCloseMessages () { // stop any pending timeout $timeout.cancel(timeoutTimer); $timeout.cancel(warningTimer); // remember to close any messages } function startTimeout () { warningTimer = $timeout(processWarning, timeToWarnMs); timeoutTimer = $timeout(processLogout, timeToLogoutMs); } function processWarning() { // show warning using popup modules, toasters etc... } function processLogout() { // go to logout page. The state might differ from project to project $state.go('authentication.logout'); } function switchEventHandlers(toNewStatus) { const body = angular.element($document); const trackedEventsList = [ 'keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart', 'touchmove', 'scroll', 'focus' ]; trackedEventsList.forEach((eventName) => { if (toNewStatus === 'off') { body.off(eventName, resetTimeout); } else if (toNewStatus === 'on') { body.on(eventName, resetTimeout); } }); } // expose switch methods this.switchOff = switchOff; this.switchOn = switchOn; } return { switchTimeoutOn () { (new InactivityTimeout()).switchOn(); }, switchTimeoutOff () { (new InactivityTimeout()).switchOff(); } }; }
fuente