Автоматичний вихід із Angularjs на основі простою користувача


78

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

Я намагався уникати використання jQuery, але я не можу знайти підручників чи статей про те, як це зробити в angularjs. Будь-яка допомога буде вдячна.



6
@Stewie він писав, що намагається уникати jQuery ...
Себастьян

Ви отримали якісь варіанти цієї Функціональності?
HP 411,

Відповіді:


112

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

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

Крім того, він має службу підтримання життєдіяльності, яка може пінгувати деякі URL-адреси з інтервалом. Це може використовуватися вашим додатком, щоб підтримувати живий сеанс користувача, поки він активний. Служба простою за замовчуванням інтегрується із службою підтримання життя, призупиняючи пінг, якщо вони простоюють, і відновлюючи його, коли вони повертаються.

Вся інформація, яка вам потрібна для початку, знаходиться на веб-сайті, а детальніше - у вікі . Однак, ось фрагмент конфігурації, який показує, як вийти з них, коли вони закінчуються.

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
Гей, твій підхід для мене працював ідеально, поки у мене не виникли проблеми з мобільним сафарі, де тайм-аути призупиняються, коли сторінка переходить на другий план. Мені довелося його змінити, щоб встановити непрацюючу позначку часу на кожному годиннику, яка потім оновлюється при перериванні. Перед кожним оновленням переривання, хоча я перевіряю, чи не закінчився термін idleTimeout, який вирішує проблему Safari (не виконує автоматичний вихід, а виходить із першого дотику / миші / клацання).
Brian F

@BrianF Цікаво. Якщо ви бажаєте та можете, я був би зацікавлений у запиті на вилучення ваших змін або, принаймні, проблемі, відкритій із прикладом коду із вашими змінами.
moribvndvs

подивіться зразок, який я кинув на github - github.com/brianfoody/Angular/blob/master/src/idle.js . Я не використовую функцію зворотного відліку або keepAlive, тож це була просто розібрана версія, але ви мали змогу побачити моє виправлення за допомогою idleCutOffMoment
Брайан Ф

3
@BrianF Дякую за це. Я розумію, що ви використовуєте індивідуальну версію, тому це може не мати значення, але я додаю це до офіційного випуску.
moribvndvs

1
@HackedByChinese Дякую :) у вашому фрагменті - це все, що вам потрібно зробити, щоб він працював? Це не працювало для мене, поки я не додав Idle.watch()у .run()функцію, IdleTimeoutподія взагалі не запускалася, поки я цього не зробив. Я бачив заклик до Idle.watch()вашої демонстрації github, хоча отже, звідки я його взяв.
Борис

24

Перегляньте демонстрацію, яка використовується, 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>

ОНОВЛЕННЯ

Я оновив своє рішення як пропозицію @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


2
Класне рішення, може бути коротшим, якщо виконати циклічний перегляд назв подій: var eventNames = ['keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart' , 'touchmove', 'scroll', 'focus']; for (var i = 0; i <імена подій.довжина; i ++) {bodyElement.bind (імена подій [i], TimeOut_Resetter); }
Том,

@Tom дякую за вашу пропозицію, я оновив свою відповідь як вашу пропозицію.
Frank Myat, четвер,

Я знаю, що це стара відповідь, але всім, хто читає це, слід вимкнути події в розділі виходу. На даний момент (грудень-2017) прив'язка застаріла, тому вам слід виконати bodyElement.on(...)та LogoutByTimerвиконати всерединіbodyElement.off(...)
GChamon

Це досить круто. Я додав $ uib.modal, щоб дати попередження користувачеві про онлік.
Фергюс

трохи змінив своє рішення до особливостей мого проекту, але воно працює! Дякую!
mp_loki

19

Це повинні бути різні способи, і кожен підхід повинен відповідати певному застосуванню краще, ніж інший. Для більшості програм ви можете просто обробляти події клавіш або миші та належним чином увімкнути / вимкнути таймер виходу. Тим не менш, на моїй голові, «вигадливе» рішення 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;
  });
});

4
Я думаю, що це досить нове рішення, і я трохи погрався з ним. Головна проблема у мене полягає в тому, що це буде виконуватися на багатьох інших подіях, які, можливо, не керуються користувачем ($ інтервали, $ таймаути тощо), і ці події скинуть ваш lastDigestRun.
Сет М.

1
Ви можете використовувати цей метод і замість кожного дайджесту просто перевіряйте наявність певних подій, як це робить модуль ngIdle нижче. тобто $ document.find ('тіло'). on ('переміщення клавіші миші DOMMouseScroll коліщатко миші мишею touchstart', checkIdleTimeout);
Brian F

Мені подобається ідея дайджест-годинника, але якщо у вас є веб-розетки чи щось інше, ви завжди можете бути активними з цим; натяки на перемогу над суттю.
amcdnl

@BrianF Mmh буде webSockets запускати такі події, як "переміщення миші", "клавіатура", "DOM-xxx", як ті, що каже Брайан? Думаю, зазвичай це не так, правда?
фрімен

3
"if (зараз - lastDigestRun> 10 * 60 * 60) {" це означає, що я повинен почекати 1 хвилину?
Томаш Ващик

11

Граючи з підходом 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();  
    });
});

1
Гарний приклад. У рядку 3 "var lastRun = Date.now ();" Я вважаю, що ви мали на увазі, що змінною є "lastDigestRun"
Мартін,

Для експерименту я змінив його на тайм-аут після однієї хвилини бездіяльності. І він продовжує тайм-аут, поки користувач знаходиться в середині активності. що дає?
BHR

Використання $rootScope.$watchв цьому випадку вимагатиме переключення на setInterval, оскільки $intervalбуде запускати дайджест при кожному виклику функції, ефективно скидаючи ваш lastDigestRun.
alalonde

@ v-tec Я використовую ваш підхід у моїй програмі, але як я міг очистити інтервал, який я не можу зупинити, використовуючи clearInterval, Це демонстрація: github.com/MohamedSahir/UserSession
Мохамед Сахір

6

Ви також можете виконати використання angular-activity-monitorбільш прямолінійним способом, ніж вводити кілька провайдерів, і він використовує setInterval()(порівняно з кутовим $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
  });
}

Будь ласка, не публікуйте однакові відповіді на кілька запитань. Опублікуйте одну хорошу відповідь, а потім проголосуйте / позначте, щоб закрити інші запитання як дублікати. Якщо питання не є дублікатом, адаптуйте відповіді на запитання .
Мартін Пітерс

Я впроваджую це рішення, досить круто, але я сумніваюся, чи відстежує воно і інші дії браузера? Я просто хочу обмежити це, щоб моя програма означала, що користувач простоює у моїй програмі, тоді лише
автовихід

@ user1532976: скрипти у веб-застосунку не вибиватимуться з вікна (вкладки), в якому він знаходиться. Тож ні, він не буде відстежувати інші дії
DdW

3

Я випробував підхід Буу і не зміг правильно його зрозуміти через величезну кількість подій, які викликають виконання датчика, включаючи виконувані функції $ interval та $ timeout. Це залишає програму в стані, коли вона ніколи не буде простоювати незалежно від вводу користувача.

Якщо вам насправді потрібно відстежувати час простою користувача, я не впевнений, що існує хороший кутовий підхід. Я б припустив, що кращий підхід представлений 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 . Це демонструє те, що час роботи варильного апарата оновлюється лише тоді, коли відстає інтервал. Як тільки інтервал досягне максимального значення, то варочний апарат більше не працюватиме.


3

ng-Idle виглядає як шлях, але я не зміг зрозуміти модифікації Брайана Ф і хотів також зробити тайм-аут для сеансу сну, також я мав на увазі досить простий варіант використання. Я порівняв його до коду нижче. Він підключає події, щоб скинути прапор тайм-ауту (ліниво розміщений у $ rootScope). Він виявляє лише час очікування, коли користувач повертається (і запускає подію), але це досить добре для мене. Я не зміг отримати тут розташування $ angular, щоб працювати тут, але знову ж таки, використовуючи 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
        }
    }
});

1

Я думаю, що дайджест цикловий годинник Буу є геніальним. Дякую, що поділились. Як зазначають інші, інтервал $ також призводить до запуску циклу перетравлення. Для автоматичного виходу користувача з системи ми можемо використовувати 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();
    });
});

"10 * 60 * 1000" це кількість мілісекунд?
Томаш Ващик,

У поданні продуктивності який метод буде працювати краще ?? Перегляд останнього дайджесту чи перегляд подій ???
Раві Шенкер Редді

1

Я використав для цього ng-idle і додав невеликий вихідний код та нульовий код маркера, і він працює нормально, ви можете спробувати це. Дякую @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'
        });
    });

1

Я хотів би розширити відповіді на тих, хто міг би використовувати це у великому проекті, ви могли випадково приєднати кілька обробників подій, і програма поводилась би дивно.

Щоб позбутися цього, я використав функцію singleton, виставлену на заводі, з якої ви зателефонуєте inactivityTimeoutFactory.switchTimeoutOn()та inactivityTimeoutFactory.switchTimeoutOff()у вашому кутовому додатку, щоб відповідно активувати та деактивувати вихід із системи через неактивність.

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

Ось мій код:

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

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.