Recently, I was tasked with implementing a client side javascript based timeout routine.  By the time I was done, it worked beautifully until we started up multiple browsers on the same site.  If one browser was in the background, it was oblivious to the activity in the other browser window.  This can be accomplished by the famed Ctrl-N.  Browser copy 1 would count down, display the warning message and then log everyone out which would be quite a shock to the user who was in the other browser at the time.  This is because the timeout hinged on a javascript variable, which is not shared between browser sessions.  So, in order to fix this, I went the cookie route.  This post is to detail how I solved this problem.  The only code presented here is the code to implement and check the cookie and some basic UI.

First, I will deal with the cookies.  I created (ok, borrowed) some code for the cookies.

        // Generic function to set a cookie with an expiration date.
        function setCookieWithExpire(cname, cvalue, exdays)
        {
            var d = new Date();
            d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
            var expires = "expires=" + d.toUTCString();
            document.cookie = cname + "=" + cvalue + "; " + expires + "; path=/";
        }

        // Generic function to set a session cookie
        function setCookie(cname, cvalue)
        {
            document.cookie = cname + "=" + cvalue;
        }

        // Generic funtion to get a cookie with a particular name.
        function getCookie(cname)
        {
            var name = cname + "=";
            var ca = document.cookie.split(';');
            for (var i = 0; i < ca.length; i++)
            {
                var c = ca[i];
                while (c.charAt(0) == ' ') c = c.substring(1);
                if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
            }
            return "";
        }

This code helps me work with the cookies.  For the sake of the article, I use a JQuery UI Dialog to popup my timeout warning message.  Here is the basics of the UI.


<input id="txtSecondsLeft" type="text" value="Here Here" style="width: 300px;" />
    <br />
    <input id="reset" type="button" value="Press to reset timer" />

    <div id="dialog-confirm" title="About to time out?">
        <p>
            <span class='ui-icon ui-icon-alert' 
style='float: left; margin: 0 7px 20px 0;'></span> <span id='TimeoutMessage' style="font-size: small;">
Press OK to not be logged out.</span> </p> </div>

The text box "secondsLeft" is used for debugging to see the status of our timer at any given point.  The button reset again, helps us test our code.  The idea behind the reset is that we want to reset the timer any time the user interacts with the page.  Again, I am using JQuery here so let's look at how the page is setup.

        // When the page loads
        $(document).ready(function ()
        {
            // Here are all the activities that can reset the timer.
            $("input").keydown(RenewSession);
            $("textarea").keydown(RenewSession);
            $("a").mousemove(RenewSession);
            $("input").mousemove(RenewSession);
            $("textarea").mousemove(RenewSession);

            // Create the confirmation dialog window.
            renewalVars.TimeoutDialog =
            $("#dialog-confirm").dialog({
                autoOpen: false,
                resizable: false,
                height: 250,
                width: 350,
                modal: true,
                buttons: {
                    "Ok": function ()
                    {
                        RenewSession();
                        $(this).dialog("close");
                    }
                }
            });
            // Setup the timeout session to begin with.
            RenewSession();
            // Set the timer in motion to do the checking.
            renewalVars.intervalID = setInterval('CheckSession()', 1000);
        });

When the document loads, we will setup each of the input devices to renew the session whenever they receive input or even a mouse move event.  This let's our routine know that the user is alive and well.  You can tailor this to suit your needs.  The next thing we do is create the JQuery UI dialog.  Note the close button on the dialog also Renews the session.  Finally we call renew session to set the point from which the ticker can operate and then start up the ticker to 'CheckSession' every second or so.  Again, you can tailor this for your needs.  Now, let's look at the CheckSession and RenewSession functions.

// This function sets the renewal cookie to the current time, 
//as a result, any window basing their
// timeout on this cookie should also get reset.
function RenewSession()
{
    // This will record the getTime value of the date when the 
    // function is invoked.  Draws a line in the sand.
    setCookieWithExpire(renewalVars.secondsLeftCookieName,
        (new Date()).getTime() / 1000, 1);
}

// This function is what fires every so often to check the status of the timeout,
// display the window if necessary and if no action is taken, transfer the user to
// the logout screen.
function CheckSession()
{
    // This will get the elapsed seconds since we "drew the line in the sand" 
    // and then subtract it from the timeout value to get the seconds remaining.
    var savedSeconds = getCookie(renewalVars.secondsLeftCookieName);
    var elapsedSeconds = ((new Date()).getTime() / 1000) - savedSeconds;
    var secondsLeft = renewalVars.timeoutValue - elapsedSeconds;

    $("#TimeoutMessage").html('You Currently Have ' + secondsLeft.toFixed(0)
        + ' second(s) left.<br/>Press OK to not be logged out.');
    $("#txtSecondsLeft").val(secondsLeft.toFixed(0));

    // This is the case where we are less than the warning but haven't shown the box.
    if (secondsLeft <= renewalVars.warningValue && !renewalVars.confirmDialogShowing)
        ConfirmSessionTimeout();

    // This is the case where we are showing the box, but our timeout 
    // is reset, perhaps by another browser instance. (edge case)
    if (secondsLeft > renewalVars.warningValue && renewalVars.confirmDialogShowing)
    {
        RenewSession();
        renewalVars.confirmDialogShowing = false;
        renewalVars.TimeoutDialog.dialog("close");
    }

    // Now, if nothing was done and the seconds left go to 0, then we 
    // log the user out (which effectively logs all sessions out)
    if (secondsLeft <= 0)
    {
        // This is to prevent the countdown from continuing if the 
        // browser is a bit slow to respond.
        clearInterval(renewalVars.intervalID); 
        window.location.href = "http://www.google.com";
    }
}

The RenewSession simply takes the current date and stores it in the cookie.  Whenever the timer fires, the CheckSession will do the following.

  1. go get the last cookie saved.  This can be by this browser or another browser session.
  2. Determine how many seconds have elapsed.
  3. Determine how many seconds we have left.
  4. Update message
  5. Next bit will check to see if we are below our threshold and the dialog has not been shown, then show the dialog.
  6. If we are above our threshold and the dialog is shown, hide the dialog.  This is the case when another browser session renews.
  7. Finally, if our timer has fallen to zero or below, we can stop the timer and then invoke whatever function you wish.  In this case, I have simply transferred to Google.  You can invoke your logout controller/action or whatever you wish to do.

We only have a little bit more to wrap up so let's show that code.

// This structure will hold some of the data needed to manage the timeout.
var renewalVars =
{
    // This is the cookie that will store the exact time a refresh happened.
    secondsLeftCookieName: "Timeout.ResetSnapshot",

    // Amount of time in seconds till the timeout happens.
    timeoutValue: 12,

    // Amount in seconds when the dialog will appear.  This is 
    //   X seconds before the timer runs out.
    warningValue: 10,

    // Flag that indicates whether or not the Confirmation Dialog is showing.
    confirmDialogShowing: false,

    // Stores the interval ID so the timer can be shut off before exiting.
    intervalID: null,

    // Dialog Variable itself.
    TimeoutDialog: null
}
// This function shows a dialog that will display to the user that their 
// session is about to time out.
function ConfirmSessionTimeout()
{
    // Show the window.
    renewalVars.TimeoutDialog.dialog("open");

    // Indicate to the rest of the program that the dialog is shown.
    renewalVars.confirmDialogShowing = true;
}

The renewalVars structure is just a handy place to keep the variables that I need.  Confirm session timeout is responsible for invoking and showing the dialog and setting the confirmDialogShowing variable to true. 

If you would like to see the entire page working, you can go get it here.

Click here for working example.

In finishing, if you try and get this to work on your local development machine, it will likely not work.  The reason is, in order to share cookies, there must be at least one "." in the domain name.  For example, www.IgnorantTillProvenGuilty.com has dots in the name, localhost does not.  In order to get around this, add the following to your hosts file.

127.0.0.1 dev.localhost.com

Once you do that, then you can refer to your site with this alias and it should work.  Of course, once deployed to real domain, no worries.  This is a good Stack Overflow article with more details on this.