A Coldfusion Who’s Online Widget

Last week I set up Ray Camden’s Lighthouse Pro , a open source issue/bug tracking Coldfusion application. We are going to use this internally for project and bug tracking. One bit of functionality I wanted that wasn’t included was a Who’s Online function so everyone could see who was using the site at any given time. This gives me to opportunity to “gently” prod folks who are resistant to using the new program that “it’s the way we now do things”.  Change is hard you know.

This code is not specifically tied to any app and can be dropped in any system using application.cfc.  It would be good for forums, intranets or any other “social web” app where knowing who’s online may be a benefit.

The first bit is to define the struct we are going to use to store the information and to populate it with the newly logged in user.

On thing to note here is that I’m using a combination of CFID and Username to give me my user_cfid which is the key we are looking for. The reason for this is that we want to be able to allow multiple users with the same name to use the application. If we just keyed on username, the system couldn’t tell there were more than one and we could see name overlap problems. Because CFID is set with the session, it’s “reasonably” unique, especially when combined with username. For greater overlap prevention or for systems which only use jsessionid’s, you could use createuuid() intead of CFID.

Application.cfc onRequest in your successful login section

<!— begin Who’s Online section —>
<cfparam name=”session.whosOnFirst” default=””>
<!— Test for existence of UserInfo and create if necessary  —>
<cflock timeout=”15″ scope=”APPLICATION” type=”EXCLUSIVE”>
<cfif NOT isDefined(“Application.UsersInfo”)>
<cfset Application.UsersInfo = StructNew()>
<cfif isDefined(‘form.userName’)>
<!— create new user info for the struct —>
<cflock name=”#CreateUUID()#” timeout=”15″ type=”EXCLUSIVE”>
<cfset user_cfid = Evaluate(CFID) & “,” & form.userName>
<cfset user_time = Now()>
<!— set a session id so we can use it to verify that this session is still active in application.cfm —>
<cfset session.whosOnFirst = user_cfid>
<cflock scope=”APPLICATION” type=”EXCLUSIVE” timeout=”15″>
<!— If the user does not exist in the struct, insert it —>
<cfif NOT StructKeyExists(Application.UsersInfo, user_cfid)>
<cfset temp = StructInsert(Application.UsersInfo, user_cfid, user_time)>
<!— end Who’s Online section —>
the second bit does the updating and checking of user activity and either updates or deletes the user from the tracking struct

Application.cfc onRequest
<!— Begin Who’s Online section —>
<!— determines timeout to heck if user still is active.
set to 0 will check on each page load and will cause list to change a lot as people “timeout”
while viewing or working on pages
set to 10 minutes will give a more general idea of activity.
remember that activity timeout and session timeout may be different.

<!— handle application timeout —>
<cflock timeout=”15″ scope=”APPLICATION” type=”EXCLUSIVE”>
<cfif NOT isDefined(“Application.UsersInfo”)>
<cfset Application.UsersInfo = StructNew()>

<cfset ActivityTimeout = 0>
<!— if we still have a session id but have been cleared from the struct reset the struct
this would be due to having an activity online timeout which is shorter than
your session timeout. —>

<!– check if session exists –>
<cfif structKeyExists(session,”whosOnFirst”)>
<!– check if key in who’s online exists –>
<cfif NOT StructKeyExists(Application.UsersInfo, session.whosOnFirst)>
<cfset temp = StructInsert(Application.UsersInfo, session.whosOnFirst, now())>

<cfloop collection=”#Application.UsersInfo#” item=”uName”>
<!— if the struct matches the session id then it’s us so update our activity time —>
<cfif structKeyExists(session,”whosOnFirst”)>
<cfif uName eq session.whosOnFirst>
<cfset user_cfid = uName>
<cfset user_time = Now()>
<cfset temp = StructUpdate(Application.UsersInfo, user_cfid, user_time)>
<!— look for values in the struct which are larger than ActivityTimeout
and delete them as their timeout period will have expired.   —>
<cfif Evaluate(DateDiff(“n”, StructFind(Application.UsersInfo, uName), Now())) GT ActivityTimeout>
<cfset StructDelete(Application.UsersInfo, uName)>
<!— end Who’s Online section —>

This last bit is the display section which you can place on any page you want the widget to appear.

<div style=”padding:5px;”>
<cflock scope=”APPLICATION” type=”EXCLUSIVE” timeout=”10″>
Users Online : #StructCount(Application.UsersInfo)#<br>
<cfloop collection=”#Application.UsersInfo#” item=”uName”>
<cfif Uname eq session.whosOnFirst>
<div style=”font-weight:bold;border-top:1px dashed;border-bottom:1px dashed;”>#listlast(UCASE(Uname))#</div><!— this is me —>
<div>#listlast(UCASE(Uname))# <br>
<span style=”font-size:.7em;”>
Last Activity : #timeformat(structfind(Application.UsersInfo,uname), “hh:mm:ss”)#

For apps that allow both registered users and guests, you could add an additional logic section to onRequestStart which determines if a user is browser as a guest and then set user_cfid as

<cfset user_cfid = Evaluate(CFID) & “,” & “Guest”>

Since CFID is “unique-ish” we can combine it with multiple Guests and not have overlap. For larger volume sites which could have many guests, I’d recommend using the createUUID() as recommend above.

Word of warning: I’m not sure what performance implications there would be on a heavily visited forum site.  You would also want to either limit the number of users displayed by the collection cfloop, or add some css to allow for scrolling of the div so you didn’t have a list that had 300-400 visible users.

Have Fun.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: