AJAX session keep-alive with expiration detection

This AJAX keepalive script can be used to keep sessions alive on a web server by polling it every minute (using POST requests to avoid caching). The page itself is not reloaded, the requests to the server are happening in the background. A hashed ID based on the server-side session ID is returned and the client-side script checks if it matches the last received session ID.

Embedded in a page, it looks and works like this:

Session status

The system consists of four elements: The client-side HTML that loads the Javascript (example.html), the Javascript itself (keepalive.js), the server-side code for returning the hashed session ID (session.asp) and the hashing algoritm (sha256.asp).

Note that although classic ASP is used in this example for returning a hashed server-side session ID, the server-side part can be implemented on any modern session-aware platform.

example.html (lines needed to be included in the HTML content highlighted):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252" />
<title>Ajax session keep-alive with expiration detection</title>
</head>
<body>

<span id="sessionstatus">Session status</span>
<script type="text/javascript" src="keepalive.js"></script>

</body>
</html>

...Or like this, if the default options need to be tweaked:

<span id="sessionstatus">Session status</span>
<script type="text/javascript">
  kaDebug=false; // Set to true to show the full server response
  kaServerPage='session.asp'; // a page returning the server-side session ID
  kaInterval=60; // keep-alive interval in seconds
  kaOkMessage='session alive';
  kaExpiredMessage='session expired';
  kaErrorMessage='session check error';
  kaStatusElementID='sessionstatus'; // DOM element ID to update with status
</script>
<script type="text/javascript" src="keepalive.js"></script>

keepalive.js: (based on the generic AJAX example)

var kaHttpRequest = false;
var kaOldSessionId = '';
if (typeof kaDebug == 'undefined')
{
  var kaDebug = false;
}
if (typeof kaServerPage == 'undefined')
{
  var kaServerPage = 'session.asp';
}
if (typeof kaInterval == 'undefined')
{
  var kaInterval = 60;
}
if (typeof kaOkMessage == 'undefined')
{
  var kaOkMessage = '<span style="color: #41930a;">Session alive</span>';
}
if (typeof kaExpiredMessage == 'undefined')
{
  var kaExpiredMessage = '<span style="color: #b82c06;">Session expired</span>';
}
if (typeof kaErrorMessage == 'undefined')
{
  var kaErrorMessage = '<span style="color: #b82c06;">Session check error</span>';
}
if (typeof kaStatusElementID == 'undefined')
{
  var kaStatusElementID = 'sessionstatus';
}

kaAjax('POST', kaServerPage, '', kaStatusElementID);
setInterval("kaAjax('POST', kaServerPage, '', kaStatusElementID)", kaInterval * 1000);

function kaAjax(httpRequestMethod, url, parameters, target)
{
  kaHttpRequest = false;
  document.getElementById(target).innerHTML = 'Wait...'
  if (window.XMLHttpRequest)
  { // For Mozilla, Safari, Opera, IE7+
    kaHttpRequest = new XMLHttpRequest();
    if (kaHttpRequest.overrideMimeType)
    {
      kaHttpRequest.overrideMimeType('text/plain');
      //Change MimeType to match the data type of the server response.
      //Examples: "text/xml", "text/html", "text/plain"
    }
  }
  else if (window.ActiveXObject)
  { // For IE6
    try
    {
      kaHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e)
    {
      try
      {
        kaHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch (e)
      {}
    }
  }
  if (!kaHttpRequest)
  {
    alert('Giving up :( Cannot create an XMLHTTP instance');
    return false;
  }
  kaHttpRequest.onreadystatechange = function() {updateElement(target);};
  if (httpRequestMethod == 'GET')
  {
    var ser = Math.round(Math.random()*1000000); // Anti-caching random number
    kaHttpRequest.open('GET', url + '?' + parameters + '&random=' + ser, true);
    kaHttpRequest.send(null);
  }
  else if (httpRequestMethod == 'POST')
  {
    kaHttpRequest.open('POST', url, true);
    kaHttpRequest.setRequestHeader('Content-Type',
      'application/x-www-form-urlencoded');
    kaHttpRequest.send(parameters);
  }
  else
  {
    alert('Sorry, unsupported HTTP method');
  }
}

function updateElement(target)
{
  if (kaHttpRequest.readyState == 4)
  {
    if (kaDebug == true)
    {
      alert(kaHttpRequest.responseText);
    }
    if (kaHttpRequest.status == 200)
    {
      if (kaOldSessionId == '')
      {
        kaOldSessionId = kaHttpRequest.responseText;
      }
      if (kaHttpRequest.responseText == kaOldSessionId)
      {
        document.getElementById(target).innerHTML = kaOkMessage;
      }
      else
      {
        document.getElementById(target).innerHTML = kaExpiredMessage;
      }
    }
    else
    {
      document.getElementById(target).innerHTML = kaErrorMessage;
    }
  }
}

session.asp:

<%
Option Explicit
%>
<!-- #include file="sha256.asp" -->
<%
' For security, the server-side session ID is salted and
' hashed before being sent to the client
Dim strSalt
strSalt = "rsq8SwDAmEMDkRv5sMvDe3C" ' Just some random data
' The hash is only calculated once and stored in a session variable:
If Session("SaltedSessionID") = "" Then
  Session("SaltedSessionID") = sha256(CStr(Session.SessionID) & strSalt)
End If
Response.Write(Session("SaltedSessionID"))
%>

Note: If you don't care about hashing the session ID, session.asp can be as simple as this one-liner:

<% Response.Write(Session.SessionID) %>

sha256.asp (only needed if the session ID should be hashed):

The SHA256 ASP implementation (sha256.asp) used in this example can be downloaded at http://www.frez.co.uk/vb6.aspx.

Page last updated 2009-08-29 21:19. Some rights reserved (CC by 3.0)