Is it possible to determine if a user is inactive and automatically log them out after say 10 minutes of inactivity using angularjs?
I was trying to avoid using jQue
I would like to expand the answers to whoever might be using this in a bigger project, you could accidentally attach multiple event handlers and the program would behave weirdly.
To get rid of that, I used a singleton function exposed by a factory, from which you would call inactivityTimeoutFactory.switchTimeoutOn()
and inactivityTimeoutFactory.switchTimeoutOff()
in your angular application to respectively activate and deactivate the logout due to inactivity functionality.
This way you make sure you are only running a single instance of the event handlers, no matter how many times you try to activate the timeout procedure, making it easier to use in applications where the user might login from different routes.
Here is my 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();
}
};
}
I tried out Buu's approach and couldn't get it quite right due to the sheer number of events that trigger the digester to execute, including $interval and $timeout functions executing. This leaves the application in a state where it never be idle regardless of user input.
If you actually need to track user idle time I am not sure that there is a good angular approach. I would suggest that a better approach is represented by Witoldz here https://github.com/witoldsz/angular-http-auth. This approach will prompt the user to reauthenticate when an action is taken that requires their credentials. After the user has authenticated the previous failed request is reprocessed and the application continues on as if nothing happened.
This handles the concern that you might have of letting the user's session expire while they are active since even if their authentication expires they are still able to retain the application state and not lose any work.
If you have some kind of session on your client (cookies, tokens, etc) you could watch them as well and trigger your logout process if they expire.
app.run(['$interval', function($interval) {
$interval(function() {
if (/* session still exists */) {
} else {
// log out of client
}
}, 1000);
}]);
UPDATE: Here is a plunk that demonstrates the concern. http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W. What this demonstates is that the digester run time is updated only when the interval ticks. Once the interval reaches it max count then the digester will no longer run.
There should be different ways to do it and each approach should fit a particular application better than another. For most apps, you can simply just handle key or mouse events and enable/disable a logout timer appropriately. That said, on the top of my head, a "fancy" AngularJS-y solution is monitoring the digest loop, if none has been triggered for the last [specified duration] then logout. Something like this.
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;
});
});
I think Buu's digest cycle watch is genius. Thanks for sharing. As others have noted $interval also causes the digest cycle to run. We could for the purpose of auto logging the user out use setInterval which will not cause a digest loop.
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();
});
});