The Art of Gracefully Handling Session Timeouts with ExtJS
Join the DZone community and get the full member experience.
Join For FreeA very common solution to gracefully handling session timeouts consists of notifying your user that her session is about to time out, and asking her to take an action in order to prolong the session. In this article I will walk you through one implementation of this important usability and stability pattern utilizing the ExtJS JavaScript library.
The key to this approach is to divide the session duration into two distinct intervals, let’s call them t1 and t2, where t1 is the time the user has been inactive, until we alert her that her session is about to terminate, and t2 is a short grace period we will give the user to perform an action that will extend her session.
If we define Session Duration = t1 + t2, we’re looking at a logical sequence like so:
- Start a “session timeout” timer
- When the timer reaches t1, we alert the user and ask her if she wants to extend the session
- If the user does not extend her session and t2 is reached, we terminate the session
- If the user extends her session, we reset the session timer and jump back to step 2
As this approach relies on calculating the time the user has been inactive, the manner we define user inactivity in very important. In order to keep the sample code of this article simple, we will assume that the user is inactive if her interaction with the application is not generating requests to the server. For a more solid implementation you might want to consider additional events such as using the mouse, scrolling, etc. But this, of course, depends on the type of system you’re building.
Let’s now look at what it takes to implement this approach.
Starting a session timeout timer upon completing an AJAX request
As we’re assuming that the user is inactive if she is not generating requests to the server, the most important part of our implementaiton is a piece of code we will run upon completing every AJAX request:
Ext.Ajax.on('requestcomplete', function (conn, response, options) {
if (options.url !== App.SESSION_KILL_URL) {
// Reset the client-side session timeout timers.
// Note that you must not reset if the request was to kill the server-side session.
App.sessionAboutToTimeoutPromptTask.delay(App.toMilliseconds(App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN));
App.killSessionTask.cancel();
} else {
// Notify user her session timed out.
Ext.Msg.alert(
'Session Expired',
'Your session expired. Please login to start a new session.',
function (btn, text) {
// TODO: Here you need to make sure you kill the server-side session state.
if (btn == App.BTN_OK) {
// TODO: Show logon form here.
}
}
);
}
});
This requestcomplete event handler consists of two branches. The first branch will run after we request any resource, except the page that will terminate the server-side session. Inside this branch we utilize a delayed task – App.sessionAboutToTimeoutPromptTask – to keep track of the duration of the session, and a delayed task – App.killSessionTask – that is responsible for triggering the request to terminate the server-side session. I will explain how these delayed tasks work in a minute.
The second branch of the event handler will run after we request the page that will take care of terminating the server-side session. What we need to do in this case, is notify the user that the session was terminated:
Ext.Msg.alert('Session Expired','Your session expired. Please login to start a new session.',function (btn, text) {
// TODO: Here you need to make sure you kill the server-side session state.
if (btn == App.BTN_OK) {
// TODO: Show logon form here.
}
});
I use an Ext.Msg.alert for this example, but you can modify this area to fit the needs of your application.
Let’s now look at the delayed tasks.
Notifying the user that her session is about to time out
In order to let the user know that her session is about to time out, we will use a delayed task like so:
App.sessionAboutToTimeoutPromptTask = new Ext.util.DelayedTask(function () {
Ext.Msg.confirm(
'Your Session is About to Expire',
String.format('Your session will expire in {0} minute(s). Would you like to continue your session?',
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN),
function (btn, text) {
if (btn == App.BTN_YES) {
// Simulate resetting the server-side session timeout timer
// by sending an AJAX request.
App.simulateAjaxRequest();
} else {
// Send request to kill server-side session.
App.simulateAjaxRequestToKillServerSession();
}
}
);
App.killSessionTask.delay(App.toMilliseconds(
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN));
});
In this task we utilize an Ext.Msg.confirm to notify the user about the timeout and ask her to push the Yes button in order to extend her session. Upon clicking the Yes button, our code will execute an AJAX request that will restart the server-side session. Clicking the No button will execute an AJAX request that will terminate the server-side session immediately.
Notice how while we wait for the user’s response, we also run the App.killSessionTask delayed task, which will terminate the server-side session after the grace period if the user did not respond to our prompt.
It is important that we restart or terminate the session on the server side. As session state includes both client and server variables, we need to make sure that when the session terminates or is extended, these variables are correctly handled on both sides of our system.
The last piece of our timeout engine is App.killSessionTask , the delayed task that will trigger a request to terminate the server-side session after the grace period:
App.killSessionTask = new Ext.util.DelayedTask(function () {
App.simulateAjaxRequestToKillServerSession();
});
Now that we have the main pieces in place, let’s take a look at the complete solution:
Ext.onReady(function () {
Ext.ns('App');
App.BTN_OK = 'ok';
App.BTN_YES = 'yes';
// 1 min. before notifying the user her session will expire. Change this to a reasonable interval.
App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN = .25;
// 1 min. to kill the session after the user is notified.
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN = 1;
// The page that kills the server-side session variables.
App.SESSION_KILL_URL = 'kill-session.html';
// Helper that converts minutes to milliseconds.
App.toMilliseconds = function (minutes) {
return minutes * 60 * 1000;
}
// Helper that simulates AJAX request.
App.simulateAjaxRequest = function () {
Ext.Ajax.request({
url: 'foo.html',
success: Ext.emptyFn,
failure: Ext.emptyFn
});
}
// Helper that simulates request to kill server-side session variables.
App.simulateAjaxRequestToKillServerSession = function () {
Ext.Ajax.request({
url: App.SESSION_KILL_URL,
success: Ext.emptyFn,
failure: Ext.emptyFn
});
}
// Notifies user that her session is about to time out.
App.sessionAboutToTimeoutPromptTask = new Ext.util.DelayedTask(function () {
console.log('sessionAboutToTimeoutPromptTask');
Ext.Msg.confirm(
'Your Session is About to Expire',
String.format('Your session will expire in {0} minute(s). Would you like to continue your session?',
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN),
function (btn, text) {
if (btn == App.BTN_YES) {
// Simulate resetting the server-side session timeout timer
// by sending an AJAX request.
App.simulateAjaxRequest();
} else {
// Send request to kill server-side session.
App.simulateAjaxRequestToKillServerSession();
}
}
);
App.killSessionTask.delay(App.toMilliseconds(
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN));
});
// Schedules a request to kill server-side session.
App.killSessionTask = new Ext.util.DelayedTask(function () {
console.log('killSessionTask');
App.simulateAjaxRequestToKillServerSession();
});
// Starts the session timeout workflow after an AJAX request completes.
Ext.Ajax.on('requestcomplete', function (conn, response, options) {
if (options.url !== App.SESSION_KILL_URL) {
// Reset the client-side session timeout timers.
// Note that you must not reset if the request was to kill the server-side session.
App.sessionAboutToTimeoutPromptTask.delay(App.toMilliseconds(App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN));
App.killSessionTask.cancel();
} else {
// Notify user her session timed out.
Ext.Msg.alert(
'Session Expired',
'Your session expired. Please login to start a new session.',
function (btn, text) {
if (btn == App.BTN_OK) {
// TODO: Show logon form here.
}
}
);
}
});
// The rest of your app's code would go here. I will just simulate
// an AJAX request so the session timeout workflow gets started.
App.simulateAjaxRequest();
});
What do you think? Want to give it a try?
From http://miamicoder.com/2011/the-art-of-gracefully-handling-session-timeouts-with-extjs
Opinions expressed by DZone contributors are their own.
Comments