web-dev-qa-db-fra.com

Déconnexion automatique avec Angularjs en fonction de l'utilisateur inactif

Est-il possible de déterminer si un utilisateur est inactif et de le déconnecter automatiquement après, disons, 10 minutes d'inactivité à l'aide de angularjs?

J'essayais d'éviter d'utiliser jQuery, mais je ne trouve aucun tutoriel ni article sur la procédure à suivre dans angularjs. Toute aide serait appréciée.

77
user2101411

J'ai écrit un module appelé Ng-Idle cela peut vous être utile dans cette situation. Voici la page qui contient des instructions et une démo.

Fondamentalement, il dispose d’un service qui lance une minuterie pour votre durée d’inactivité qui peut être perturbée par l’activité de l’utilisateur (événements tels que cliquer, faire défiler, taper). Vous pouvez également interrompre manuellement le délai en appelant une méthode sur le service. Si le délai d'attente n'est pas interrompu, un avertissement est décompté et vous pouvez avertir l'utilisateur qu'il va être déconnecté. S'ils ne répondent pas après que le décompte d'avertissement a atteint 0, un événement est diffusé auquel votre application peut répondre. Dans votre cas, il pourrait émettre une demande pour tuer leur session et rediriger vers une page de connexion.

En outre, il dispose d'un service de maintien en activité pouvant envoyer une requête ping à une URL à un intervalle donné. Ceci peut être utilisé par votre application pour maintenir la session d’un utilisateur active pendant qu’elle est active. Le service inactif s'intègre par défaut au service de maintenance, en suspendant le ping s'il devient inactif et en le reprenant à son retour.

Toutes les informations dont vous avez besoin pour commencer se trouvent sur le site avec plus de détails dans le wiki . Cependant, voici un extrait de config montrant comment les déconnecter lorsqu'ils arrivent à expiration.

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
    });
});
110
HackedByChinese

Voir Demo qui utilise angularjs et voir le journal de votre navigateur

<!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>

Le code ci-dessous est une version javascript pure

<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>

MISE À JOUR

J'ai mis à jour ma solution sous forme de suggestion @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>

Cliquez ici pour voir à Plunker pour la version mise à jour

20
Frank Myat Thu

Il devrait y avoir différentes façons de le faire et chaque approche devrait correspondre à une application particulière mieux qu’une autre. Pour la plupart des applications, vous pouvez simplement gérer les événements de clé ou de souris et activer/désactiver un minuteur de déconnexion de manière appropriée. Cela dit, sur le dessus de ma tête, une solution "sophistiquée" d'AngularJS-y surveille la boucle de digestion, si aucune n'a été déclenchée depuis la dernière [durée spécifiée], puis déconnectez-vous. Quelque chose comme ça.

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;
  });
});
19
Buu Nguyen

Joué avec l'approche de Boo, n'apprécie toutefois pas le fait que l'utilisateur n'ait été lancé qu'une fois qu'un autre résumé est exécuté, ce qui signifie que l'utilisateur reste connecté jusqu'à ce qu'il essaie de modifier quelque chose dans la page, puis immédiatement.

J'essaie de forcer la fermeture de session en utilisant un intervalle qui vérifie toutes les minutes si la dernière action date de plus de 30 minutes. Je l'ai accroché à $ routeChangeStart, mais je pourrais aussi l'être à $ rootScope. $ Watch comme dans l'exemple 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();  
    });
});
11
v-tec

Vous pouvez également utiliser angular-activity-monitor de manière plus simple que d’injecter plusieurs fournisseurs et il utilise setInterval() (vs [angulaire de $interval) Pour évitez de déclencher manuellement une boucle de digestion (ce qui est important pour éviter de garder des éléments en vie involontairement).

En fin de compte, vous vous abonnez simplement à quelques événements qui déterminent le moment où un utilisateur est inactif ou en passe de devenir proche. Ainsi, si vous souhaitez déconnecter un utilisateur après 10 minutes d'inactivité, vous pouvez utiliser l'extrait suivant:

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
  });
}
5
Sean3z

J'ai essayé l'approche de Buu et je ne pouvais pas le comprendre tout à fait à cause du nombre d'événements qui déclenchent l'exécution du digesteur, y compris l'exécution de fonctions $ interval et $ timeout. Cela laisse l'application dans un état tel qu'elle ne soit jamais inactive, quelle que soit l'entrée de l'utilisateur.

Si vous devez réellement suivre le temps d'inactivité des utilisateurs, je ne suis pas sûr qu'il existe une bonne approche angular. Je suggérerais qu'une meilleure approche est représentée par Witoldz ici https: // github.com/witoldsz/angular-http-auth . Cette approche invitera l'utilisateur à s'authentifier à nouveau lorsqu'une action nécessitant ses informations d'identification sera exécutée. si rien ne s'est passé.

Cela résout l'inquiétude que vous pourriez avoir de laisser la session de l'utilisateur expirer tant qu'il est actif, car même si leur authentification expire, ils sont toujours en mesure de conserver l'état de l'application et de ne perdre aucun travail.

Si vous avez une session sur votre client (cookies, jetons, etc.), vous pouvez également les regarder et déclencher votre processus de déconnexion si elles expirent.

app.run(['$interval', function($interval) {
  $interval(function() {
    if (/* session still exists */) {
    } else {
      // log out of client
    }
  }, 1000);
}]);

MISE À JOUR: Voici un dossier qui montre l'inquiétude. http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W . Cela prouve que le temps d'exécution du digesteur est mis à jour uniquement lorsque l'intervalle est écoulé. Une fois que l'intervalle atteint son compte maximum, le digesteur ne fonctionnera plus.

3
Seth M.

ng-Idle semble être la voie à suivre, mais je ne pouvais pas comprendre les modifications de Brian F et je voulais également que le temps imparti soit écoulé, mais j'avais aussi un cas d'utilisation assez simple à l'esprit. Je l'ai réduit au code ci-dessous. Il raccroche des événements pour réinitialiser un indicateur de délai d'attente (placé paresseusement dans $ rootScope). Il ne détecte que le dépassement de délai s'est produit lorsque l'utilisateur revient (et déclenche un événement), mais cela me suffit. Je ne pouvais pas obtenir le travail de $ location d'angular ici, mais encore une fois, utiliser document.location.href fait le travail.

Je l'ai collé dans mon application.js après l'exécution du fichier .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
        }
    }
});
3
James Bell

Je pense que la surveillance du cycle de digestion de Buu est géniale. Merci d'avoir partagé. Comme d'autres l'ont noté, $ intervalle provoque également l'exécution du cycle de digestion. Nous pourrions, dans le but de déconnecter automatiquement l'utilisateur, utiliser setInterval qui ne provoquera pas de boucle de digestion.

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();
    });
});
1
Declan McLaughlin

Je voudrais élargir les réponses à quiconque pourrait utiliser cela dans un projet plus important, vous pourriez accidentellement attacher plusieurs gestionnaires d'événements et le programme se comporterait bizarrement.

Pour me débarrasser de cela, j’ai utilisé une fonction singleton exposée par une fabrique, à partir de laquelle vous appeleriez inactivityTimeoutFactory.switchTimeoutOn() et inactivityTimeoutFactory.switchTimeoutOff() dans votre angular à respectivement activer et désactiver la déconnexion en raison de la fonctionnalité d’inactivité.

Ainsi, vous vous assurez que vous n'exécutez qu'une seule instance des gestionnaires d'événements, quel que soit le nombre de tentatives d'activation de la procédure de délai d'attente, ce qui simplifie son utilisation dans les applications où l'utilisateur peut se connecter à partir de différents itinéraires.

Voici mon code:

'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();
    }
  };

}
1
GChamon

J'ai utilisé ng-idle pour cela et ajouté une petite déconnexion et un code de jeton nul; cela fonctionne bien, vous pouvez l'essayer. Merci à @HackedByChinese d'avoir créé un module aussi agréable.

Dans IdleTimeout Je viens de supprimer mes données de session et mon jeton.

Voici mon code

$scope.$on('IdleTimeout', function () {
        closeModals();
        delete $window.sessionStorage.token;
        $state.go("login");
        $scope.timedout = $uibModal.open({
            templateUrl: 'timedout-dialog.html',
            windowClass: 'modal-danger'
        });
    });
1
Sameer Khan