Автоматическое выключение с помощью Angularjs на основе пользователя бездействия

Можно ли определить, является ли пользователь неактивным и автоматически вывести их из системы, скажем, через 10 минут бездействия с помощью angularjs?

Я пытался избежать использования jQuery, но я не могу найти никаких учебников или статей о том, как это сделать в angularjs. Любая помощь будет оценена.

Ответ 1

Я написал модуль под названием Ng-Idle, который может быть вам полезен в этой ситуации. Вот страница, содержащая инструкции и демонстрацию.

В принципе, у него есть служба, которая запускает таймер для вашей продолжительности простоя, который может быть нарушен действием пользователя (события, такие как щелчок, прокрутка, ввод текста). Вы также можете вручную прервать таймаут, вызвав метод в службе. Если тайм-аут не прерывается, он подсчитывает предупреждение, в котором вы можете предупредить пользователя, что они будут выходить из системы. Если они не отвечают после того, как обратный отсчет предупреждения достигнет 0, будет передано событие, на которое может ответить ваше приложение. В вашем случае он может выдать запрос на его сеанс и перенаправить на страницу входа.

Кроме того, у него есть служба keep-alive, которая может пересылать некоторый URL с интервалом. Это может использоваться вашим приложением, чтобы сохранить сеанс пользователя в активном состоянии. Простой сервис по умолчанию интегрируется с сервисом keep-alive, приостанавливая pinging, если он переходит в режим ожидания, и возобновляет его при возврате.

Вся информация, которую вам нужно начать, находится на сайте с более подробной информацией в wiki. Однако здесь приведен фрагмент конфигурации, показывающий, как их подписывать, когда они теряют время.

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

Ответ 2

Должны быть разные способы сделать это, и каждый подход должен соответствовать конкретному приложению лучше другого. Для большинства приложений вы можете просто просто управлять событиями клавиатуры или мыши и соответственно включать/отключать таймер выхода. Тем не менее, на вершине моей головы, "причудливое" решение AngularJS-y контролирует цикл дайджеста, если ни один из них не был запущен для последней [указанной продолжительности], а затем выйти из системы. Что-то вроде этого.

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

Ответ 3

Просмотрите Демо, которая использует angularjs и увидит ваш журнал браузера

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

Ниже код является чистой версией 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>

UPDATE

Я обновил свое решение как предложение @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>

Нажмите здесь, чтобы увидеть в Plunker обновленную версию

Ответ 4

Играется с подходом Boo, однако не нравится тот факт, что пользователь был запущен только один раз, когда запускается еще один дайджест, а это означает, что пользователь остается в системе, пока он не попытается что-то сделать на странице, а затем сразу же отпустил.

Я пытаюсь заставить выход из системы использовать интервал, который проверяется каждую минуту, если последнее действие было более 30 минут назад. Я подключил его на $routeChangeStart, но мог также быть подключен к $rootScope. $Watch, как в примере 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();  
    });
});

Ответ 5

Вы также можете выполнить angular-activity-monitor более прямолинейным способом, чем использовать несколько поставщиков, и использует setInterval() (против angular $interval), чтобы избежать ручного запуска цикла дайджеста (что важно для предотвращения непреднамеренного хранения предметов).

В конечном счете вы просто подписываетесь на несколько событий, которые определяют, когда пользователь неактивен или становится закрытым. Поэтому, если вы хотите выйти из системы после 10 минут бездействия, вы можете использовать следующий фрагмент:

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

Ответ 6

Я опробовал подход Buu и не мог понять его из-за большого количества событий, которые запускают работу варочного котла, включая выполнение функций интервала и $timeout. Это оставляет приложение в состоянии, когда он никогда не будет бездействовать, независимо от ввода пользователя.

Если вам действительно нужно отслеживать время простоя пользователя, я не уверен, что существует хороший подход angular. Я бы предположил, что лучший подход представлен Witoldz здесь https://github.com/witoldsz/angular-http-auth. Такой подход заставит пользователя повторно аутентифицировать, когда будет предпринято действие, требующее их учетных данных. После того, как пользователь выполнил аутентификацию, предыдущий неудавшийся запрос будет обработан, и приложение продолжит работу, как будто ничего не произошло.

Это устраняет проблему, связанную с тем, что срок действия пользовательского сеанса истекает, пока они активны, поскольку, даже если срок их аутентификации истекает, они все еще могут сохранить состояние приложения и не потерять работу.

Если у вас есть какой-то сеанс на вашем клиенте (файлы cookie, токены и т.д.), вы также можете посмотреть их и запустить процесс выхода из системы, если они истекут.

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

ОБНОВЛЕНИЕ: вот плунж, который демонстрирует беспокойство. http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W. Это демонстрирует, что время работы варочного котла обновляется только тогда, когда интервал тикает. Как только интервал достигнет максимума, тогда варочный котел больше не будет работать.

Ответ 7

ng-Idle выглядит как способ, но я не мог понять модификации Brian F и хотел тайм-аут для спящего сеанса тоже, также у меня был довольно простой пример использования. Я позаботился об этом ниже. Он перехватывает события до reset флага тайм-аута (лениво помещается в $rootScope). Он обнаруживает, что тайм-аут произошел, когда пользователь возвращает (и вызывает событие), но это достаточно хорошо для меня. Я не мог получить angular $location, чтобы работать здесь, но снова, используя document.location.href выполняет задание.

Я застрял в моем app.js после запуска .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
        }
    }
});

Ответ 8

Я думаю, что часы цикла Buu digest являются гениальными. Спасибо, что поделился. Как отмечали другие, $interval также приводит к запуску цикла дайджеста. Мы могли бы с целью автоматического регистрации пользователя использовать setInterval, который не вызовет цикл дайджеста.

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

Ответ 9

Я использовал ng-idle для этого и добавил небольшой logout и токен нулевой код, и он работает нормально, вы можете попробовать это. Спасибо @HackedByChinese за создание такого приятного модуля.

В IdleTimeout я только что удалил данные сеанса и токен.

Вот мой код

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

Ответ 10

Я хотел бы расширить ответы на тех, кто может использовать это в более крупном проекте, вы можете случайно связать несколько обработчиков событий, и программа будет вести себя странно.

Чтобы избавиться от этого, я использовал одиночную функцию, открытую factory, из которой вы бы вызвали inactivityTimeoutFactory.switchTimeoutOn() и inactivityTimeoutFactory.switchTimeoutOff() в своем приложении angular, чтобы соответственно активировать и дезактивировать выход из-за неактивности.

Таким образом вы убедитесь, что вы используете только один экземпляр обработчиков событий, независимо от того, сколько раз вы пытаетесь активировать процедуру тайм-аута, что упрощает ее использование в приложениях, где пользователь может подключаться с разных маршрутов.

Вот мой код:

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

}