Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created June 17, 2022 12:54
Ask Ben: Extending A ColdFusion Session On A Long-Lived Page
component
output = false
hint = "I define the application settings and event handlers."
{
// Configure application management.
this.name = "SessionHeartBeatDemo";
this.applicationTimeout = createTimeSpan( 1, 0, 0, 0 );
// Configure session management.
this.sessionManagement = true;
this.sessionTimeout = createTimeSpan( 0, 0, 30, 0 );
this.setClientCookies = true;
// ---
// PUBLIC METHODS.
// ---
/**
* I get called once at the start of each session to initialize the session.
*/
public void function onSessionStart() {
session.id = createUuid();
}
}
<cfscript>
// This pages doesn't actually have to do anything - just making the AJAX request to
// this page should extend the user's current session timeout (assuming that the
// session is still active).
// Logging for the demo.
writeDump(
var = "Session heartbeat for: #session.id#",
output = "console"
);
cfcontent(
type = "application/json; charset=utf-8",
variable = charsetDecode( serializeJson({ "sessionID": session.id }), "utf-8" )
);
</cfscript>
<cfoutput>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>
Ask Ben: Extending A ColdFusion Session On A Long-Lived Page
</title>
</head>
<body style="min-height: 300vh ;">
<h1>
Welcome to Our Portal
</h1>
<p>
Please enjoy all of the wondrous things we have to offer here. Put your feet
up, make yourself comfortable. And, maybe drop us a long note that will take
a really long time to type:
</p>
<textarea placeholder="Leave us a message..." cols="50" rows="5"></textarea>
<script type="text/javascript">
// The events that will be used to drive the session heartbeat. These are the
// user-interaction events that indicate that the user is still here.
var heartbeatEvents = [ "scroll", "touchstart", "mousemove", "mousedown", "keydown" ];
// How long should the page wait until it starts monitoring user-interactions.
// This delay acts both as a throttle to the network requests; but, also as a
// way for us to reduce the event-handler activity on the page as a whole.
// --
// NOTE: For the demo, I'm keeping this rather short. In production, you might
// want to set it to something like half your session timeout.
var heartbeatDelayInMilliseconds = 10000;
startHeartbeatTimer();
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Our heartbeat is going to be triggered by user-interaction events. However,
// there's no need to constantly be listening for events - we just want to
// start listening at some point in the future, before the session times-out,
// but with enough time for the user to interact with the page.
function startHeartbeatTimer() {
setTimeout( setupHeartbeatEvents, heartbeatDelayInMilliseconds );
}
// I bind the heartbeat events to the page such that the next meaningful user-
// interaction triggers a single ping to the server in order to extend the
// life of the user's session.
function setupHeartbeatEvents() {
console.info( "Setting up heartbeat event-bindings." );
// When binding events, we can tell the browser that our binding will
// be "passive". This means that we'll never invoke the preventDefault()
// method on the event object. This allows the browser to enable some
// performance enhancements that will reduce jank. This is especially true
// for "scroll" events.
var options = {
passive: true,
once: true
};
for ( var eventType of heartbeatEvents ) {
window.addEventListener( eventType, handleHeartbeatTriggerEvent, options );
}
}
// I unbind the heartbeat events from the page. We only need them in place
// when we start to listen for interaction events - there's no need to have
// them in place all the time.
function teardownHeartbeatEvents() {
for ( var eventType of heartbeatEvents ) {
window.removeEventListener( eventType, handleHeartbeatTriggerEvent );
}
}
// I handle one of the user-interaction events relating to our heartbeat.
function handleHeartbeatTriggerEvent( event ) {
console.warn( "User-interaction detected [%s], triggering heartbeat.", event.type );
// Now that we're about to ping the heartbeat end-point, let's reset the
// event-bindings and setup the timer that adds event-handlers back to the
// DOM in the future when we need to trigger another heartbeat.
teardownHeartbeatEvents();
startHeartbeatTimer();
// Hit the server. This should extend the user's current session.
fetch( "./heartbeat.cfm" ).then(
() => {
console.info( "Heartbeat pinged successfully." );
}
// How you handle errors is going to depend on what kind of AJAX
// client you are using. For example, a retry option might be built
// into the client. Or, you might have to implement retry yourself.
// Or you could just ignore errors and set the timer-delay to be
// small, thereby allowing errors to be "absorbed" naturally.
);
}
</script>
</body>
</html>
</cfoutput>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment