Session Inactivity Timeout with Dojo

By Chris L Hardin
Sr. Software Architect

A common problem is Ajax applications is what to do with the user when their session times out. Everyone handles this one differently, so there isn’t really a right or wrong way to do it. It depends on the business cases that you have how you will implement it. I decided to share the way I have done it for an application I am working on.

The first thing you need is a custom object that you will use to create the timeout and publish the idle and active events that we will subscribe to later. This is a copy of a class that I found on the Dojo Website, but it’s been heavily modified to make it work correctly.

dojo.declare("Monitor", null ,{
_events : [[window, 'scroll'], [window, 'resize'], [document, 'mousemove'], [document, 'keydown']],
_idleTime : null,
_timers : null,
idleTime : null,

constructor: function(time,timers){
Monitor.prototype.time=time;
this._timers=timers;
this.initObservers();
this.setTimer();
},
initObservers:function(){
dojo.forEach(this._events, function(e){
dojo.connect(e[0],e[1], function(event){
Monitor.prototype.onInterrupt();
});
})
},
onInterrupt:function(){
this.idleTime = new Date() - this._idleTime;
dojo.publish("state:active", [this.idleTime]);
this.setTimer();
},
setTimer: function(){
var oj = Monitor.prototype;
this.clearTimers();
this._idleTime = new Date();

this._timers.push(setTimeout(function(){
dojo.publish("state:idle", null);
},oj.time));

},
clearTimers: function(){
if(this._timers){
for (var i= 0;i < this._timers.length; i++) { console.debug("Clearing Timer: " + this._timers[i]); clearTimeout(this._timers[i]); } } this._timers = new Array(); } });

Now you need to load the timer up when the page loads. We store the ids for each timer in a global variable. This makes darn sure we wipe out all timers when an event triggers the clear.


var timerArray = new Array();

dojo.addOnLoad(function(){
var timeout = 840000; //Timeout is 15 minutes //900 000 15 minutes
var monitor = new Monitor(timeout,timerArray);

dojo.subscribe("state:active", null, onActive);
dojo.subscribe("state:idle", null, onIdle);

function onActive(args){
// dojo.query("p").style("opacity",1);
//console.debug("active... after: " + args + "ms");
monitor.clearTimers();

};
function onIdle(){
//dojo.query("p").style("opacity",0.2);

console.debug("idle...logging out");
var logoutTimeout = setTimeout(logoutNow, 60000); //Show the logout dialog for 1 minute. Give time to reset.
raiseQueryDialog("Session Timeout", "Your session is about to timeout. Would you like to continue?", function(isContinue){

if (isContinue){
clearTimeout(logoutTimeout);
monitor.clearTimers();

} else {

logoutNow();
}

});

//window.location.href='j_spring_security_logout?inactive=y';

};
});

var logoutNow = function(){

window.location.href='j_spring_security_logout?inactive=y';

}

var randomString = function randomString() {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 8;
var randomstring = '';
for (var i=0; i%lt;string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum,rnum+1);
}
return randomstring;
}

function raiseQueryDialog(title, question, callbackFn, e) {

var randomId = randomString();

var errorDialog = new dj.Dialog({ id: randomId, title: title });
// When either button is pressed, kill the dialog and call the callbackFn.
var commonCallback = function(mouseEvent) {
errorDialog.hide();
errorDialog.destroyRecursive();
// alert(mouseEvent.explicitOriginalTarget.nodeName);

if (window.event) e = window.event;

var srcEl = mouseEvent.srcElement? mouseEvent.srcElement : mouseEvent.target;

if (srcEl.id == 'yesButton' +randomId) {
callbackFn(true, e);
} else {
callbackFn(false, e);
}
};
var questionDiv = dojo.create('div', { innerHTML: question });
var yesButton = new dj.form.Button(
{ label: 'Yes', id: 'yesButton' +randomId, onClick: commonCallback });
var noButton = new dj.form.Button(
{ label: 'No', id: 'noButton' +randomId, onClick: commonCallback });

errorDialog.containerNode.appendChild(questionDiv);
errorDialog.containerNode.appendChild(yesButton.domNode);
errorDialog.containerNode.appendChild(noButton.domNode);
errorDialog.show();
}

What happens in this example is that after 14 minutes of idle, a message will popup asking the user if they would like to continue. If the user chooses to continue, then the timer is reset, if no, then they get logged out. If they aren’t around to confirm anything, the app will automatically logout after 1 minute from displaying the message.

Follow

Get every new post delivered to your Inbox.