Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at RIA Unleashed (Nov. 2010) with: Bob Silverberg

Very Simple Pusher And ColdFusion Powered Chat

By Ben Nadel on

When it comes to AJAX and WebSockets, it's hard to think about client-server interaction without eventually thinking about Chat. Chat applications are probably one of the first ways in which we ever experienced remote communication outside of the traditional web-based, request-response life cycle. As such, I think there is a certain romance with the chat paradigm - something about it just feels special in a way that I can't quite articulate. So it should come as no real surprise that I wanted to try using the Pusher WebSocket service to create a super simple chat application.


 
 
 

 
Pusher And ColdFusion Chat Application Proof Of Concept Video Demo.  
 
 
 

Up until now, my previous Pusher experiments have only dealt with one-way communication: one page posted a message to Pusher's RESTful web service and another page received the message pushed down through the HTML5 WebSockets. In a chat application, the communication is much more fluid, flowing in all directions from client to client (by proxy). I am, of course, not building a full-fledged chatting application; but, I did want to capture some of this bidirectional, multi-event goodness.

In my little proof-of-concept, there is only one chat room as defined by the Pusher channel, "chatRoom". Anyone who opens up my chat client is automatically part of that chat room and subscribes to the Pusher server for chat-room-based events. But, unlike my previous two experiments that only dealt with one event per channel, this chatRoom channel has two events: messageEvent and typeEvent.

The messageEvent is triggered whenever a user posts a chat message to the server. The typeEvent is triggered whenever the user either starts or stops typing. I wanted to create this latter event to see if the Pusher service was fast enough to handle realtime activity updates as each user interacted with the chat Interface. As each user starts a message, an "XYX is typing" style note gets displayed on each client's screen alerting them to the pending activity.

The following code for the Chat interface is simple, but a bit rough. Parts of it could certainly be refactored to be a little more elegant. But, as far as a proof-of-concept goes, it works pretty well. You'll see that I am posting two different events to the ColdFusion server (which posts to Pusher), and I am binding to two different events over my HTML5 WebSocket connection.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Pusher And ColdFusion Powered Chat</title>
  • <style type="text/css">
  •  
  • form {
  • width: 500px ;
  • }
  •  
  • #chatLog {
  • background-color: #FAFAFA ;
  • border: 1px solid #D0D0D0 ;
  • height: 200px ;
  • margin-bottom: 10px ;
  • overflow-x: hidden ;
  • overflow-y: scroll ;
  • padding: 10px 10px 10px 10px ;
  • width: 480px ;
  • }
  •  
  • #handle {
  • float: left ;
  • margin-bottom: 5px ;
  • }
  •  
  • #handleLabel {
  • font-weight: bold ;
  • }
  •  
  • #handleTools {
  • font-size: 90% ;
  • font-style: italic ;
  • }
  •  
  • #handleTools a {
  • color: #333333 ;
  • }
  •  
  • #typeNote {
  • color: #999999 ;
  • display: none ;
  • float: right ;
  • font-style: italic ;
  • }
  •  
  • #message {
  • clear: both ;
  • font-size: 16px ;
  • width: 420px ;
  • }
  •  
  • #submit {
  • font-size: 16px ;
  • width: 70px ;
  • }
  •  
  • div.chatItem {
  • border-bottom: 1px solid #F0F0F0 ;
  • margin: 0px 0px 3px 0px ;
  • padding: 0px 0px 3px 0px ;
  • }
  •  
  • div.chatItem span.handle {
  • color: blue ;
  • font-weight: bold ;
  • }
  •  
  • div.myChatItem span.handle {
  • color: red ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="jquery-1.4.2.min.js"></script>
  • <script type="text/javascript" src="http://js.pusherapp.com/1.4/pusher.min.js"></script>
  • <script type="text/javascript">
  •  
  • // This is for compatability with browsers that don't yet
  • // support Web Sockets, but DO support Flash.
  • //
  • // NOTE: This SWF can be downloaded from the PusherApp
  • // website. It is a Flash proxy to the standard Web
  • // Sockets interface.
  • WebSocket.__swfLocation = "./WebSocketMain.swf";
  •  
  •  
  • // When the DOM is ready, init the scripts.
  • $(function(){
  •  
  • // This is the user ID. This allows us to track the user
  • // outside the context of the handle.
  • <cfoutput>
  • var userID = "#createUUID()#";
  • </cfoutput>
  •  
  • // Create a Pusher server object with your app's key and
  • // the channel you want to listen on. Channels are unique
  • // to your application.
  • var server = new Pusher(
  • "ceef7621b815dc2a7c9f",
  • "chatRoom"
  • );
  •  
  • // Get references to our DOM elements.
  • var form = $( "form" );
  • var chatLog = $( "#chatLog" );
  • var handleLabel = $( "#handleLabel" );
  • var handleToggle = $( "#handleTools a" );
  • var typeNote = $( "#typeNote" );
  • var typeLabel = $( "#typeLabel" );
  • var message = $( "#message" );
  •  
  • // Allow the changing of the handle.
  • handleToggle
  • .attr( "href", "javascript:void( 0 )" )
  • .click(
  • function( event ){
  • // Prevent default click.
  • event.preventDefault();
  •  
  • // Prompt user for new name.
  • handleLabel.text(
  • prompt( "New Handle:", handleLabel.text() )
  • );
  •  
  • // Refocus the messsage box so the user can
  • // start typing again.
  • message.focus();
  • }
  • )
  • ;
  •  
  • // Bind to the form submission to send the message to the
  • // ColdFusion server (to be pushed to all clients).
  • form.submit(
  • function( event ){
  •  
  • // Prevent the default events since we don't want
  • // the page to refresh.
  • event.preventDefault();
  •  
  • // Check to see if we have a message. If there is
  • // no message, don't hit the server.
  • if (!message.val().length){
  • return;
  • }
  •  
  • // Send the message to the server.
  • $.get(
  • "./send.cfm",
  • {
  • userID: userID,
  • handle: handleLabel.text(),
  • message: message.val()
  • },
  • function(){
  • // Clear the message and refocus it.
  • message
  • .val( "" )
  • .focus()
  • ;
  • }
  • );
  •  
  • // Clear any "stop" timer for typing. If the
  • // user has submitted the message then we can
  • // assume they are done typing this message.
  • clearTimeout( message.data( "timer" ) );
  •  
  • // Flag that the user is no longer typing a
  • // message.
  • message.data( "isTyping", false );
  •  
  • // Tell the server that this user has stopped
  • // typing.
  • $.get(
  • "./type.cfm",
  • {
  • userID: userID,
  • handle: handleLabel.text(),
  • isTyping: false
  • }
  • );
  •  
  • }
  • );
  •  
  • // Bind the message input so that we can see when the
  • // user starts typing (and we can alert the server).
  • message.keydown(
  • function( event ){
  •  
  • // Clear any "stop" timer for typing. This way,
  • // the previous stop event doesn't get triggered
  • // while the user has continued to type.
  • clearTimeout( message.data( "timer" ) );
  •  
  • // Check to see if the user is currently typing.
  • // If they are, then we don't need to do any of
  • // this stuff until they stop.
  • if (message.data( "isTyping" )){
  • return;
  • }
  •  
  • // At this point, we know the user was not
  • // previously typing so we can send the request
  • // to the server that the user has started.
  • message.data( "isTyping", true );
  •  
  • // Tell the server that this user is typing.
  • $.get(
  • "./type.cfm",
  • {
  • userID: userID,
  • handle: handleLabel.text(),
  • isTyping: true
  • }
  • );
  •  
  • }
  • );
  •  
  • // Bind to the message input so that we can see when the
  • // users stops typing (and we can alert the server).
  • message.keyup(
  • function( event ){
  •  
  • // Clear any "stop" timer for typing. We need to
  • // clear here as well because it looks like the
  • // browser has trouble trapping every single
  • // individual key as a different typing event
  • // (at least, that's what I think is going on).
  • clearTimeout( message.data( "timer" ) );
  •  
  • // The key up event doesn't mean that the user
  • // has stopped typing. But, it does give us a
  • // reason to start paying attention. Let's check
  • // back shortly.
  • message.data(
  • "timer",
  • setTimeout(
  • function(){
  • // Flag that the user is no longer
  • // typing a message.
  • message.data( "isTyping", false );
  •  
  • // Tell the server that this user
  • // has stopped typing.
  • $.get(
  • "./type.cfm",
  • {
  • userID: userID,
  • handle: handleLabel.text(),
  • isTyping: false
  • }
  • );
  • },
  • 750
  • )
  • );
  •  
  • }
  • );
  •  
  • // Now that we have the pusher connection for a given
  • // channel, we want to listen for certain events to come
  • // over that channel (chat messages).
  • server.bind(
  • "messageEvent",
  • function( chatData ) {
  •  
  • // Append the chat item to the chat log. That
  • // chatData is a Javascript object with the
  • // message meta data.
  • var chatItem = $(
  • "<div class='chatItem'>" +
  • "<span class='handle'>" +
  • chatData.handle +
  • "</span>: " +
  • "<span class='message'>" +
  • chatData.message +
  • "</span>" +
  • "</div>"
  • );
  •  
  • // Check to see if the chat item is "mine" or
  • // if it someone else's/
  • if (chatData.userID == userID){
  •  
  • // Add the "mine" item.
  • chatItem.addClass( "myChatItem" );
  •  
  • }
  •  
  • // Append the chat item to the chat log.
  • chatLog.append( chatItem );
  •  
  • // Scroll the chat item to the bottom.
  • chatLog.scrollTop( chatLog.outerHeight() );
  •  
  • }
  • );
  •  
  • // Let's also bind to the pusher connection for the the
  • // type event so that we can see when given people start
  • // and stop typing.
  • server.bind(
  • "typeEvent",
  • function( typeData ){
  •  
  • // First, check to see if this is an event for
  • // THIS user. If so, we can ignore it.
  • if (typeData.userID == userID){
  • return;
  • }
  •  
  • // Now, check to see if the event is a start
  • // event. This will take presendence over the
  • // stop event for visual display.
  • if (typeData.isTyping){
  •  
  • // Set the typing label.
  • typeLabel.text( typeData.handle );
  •  
  • // Set the REL attribute.
  • typeLabel.attr( "rel", typeData.userID );
  •  
  • // Show the label.
  • typeNote.show();
  •  
  • // If it's a stop event, we only care if the stop
  • // event is corresponding to the most current
  • // start event (otherwise, we've already lost the
  • // opportunity for that one).
  • } else if (typeLabel.attr( "rel" ) == typeData.userID){
  •  
  • // Hide the note.
  • typeNote.hide();
  •  
  • }
  •  
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Pusher And ColdFusion Powered Chat
  • </h1>
  •  
  • <form>
  •  
  • <div id="chatLog">
  • <!--- To be populated dynamically. --->
  • </div>
  •  
  • <div id="handle">
  • <span id="handleLabel">RandomDude</span>
  • <span id="handleTools">( <a>Change Handle</a> )</span>
  • </div>
  •  
  • <div id="typeNote">
  • <span id="typeLabel" rel="">Unknown</span> is typing.
  • </div>
  •  
  • <div id="messageTools">
  • <input id="message" type="text" name="message" />
  • <input id="submit" type="submit" value="SEND" />
  • </div>
  •  
  • </form>
  •  
  • </body>
  • </html>

I won't go into much detail about the code itself since there is a good bit of it. What I can say, however, is that not having to constantly poll the server for information is making things so much easier. Being able to just bind to the HTML5 WebSocket and listen for events as if they were truly local is just really awesome. I can't even state enough the kind of emotional enjoyment I am getting out of this simplicity of Pusher-based realtime communication.

The ColdFusion code that acts as a proxy between the chat client and the Pusher web service is almost exactly like it has been in my previous examples. As such, I won't bother explaining it. Here is the ColdFusion code (send.cfm) that relays the chat message to the Pusher web service:

Send - send.cfm

  • <!--- Param the url value. --->
  • <cfparam name="url.userID" type="string" default="" />
  • <cfparam name="url.handle" type="string" default="" />
  • <cfparam name="url.message" type="string" default="" />
  •  
  •  
  • <!---
  • Pusher information used to tie this push notification
  • to a given application and to a given client (listening
  • for a given channel).
  •  
  • NOTE: Your Pusher account can have multiple applications
  • associated with it (each of which can have on-the-fly
  • channel creation).
  • --->
  • <cfset pusherAppID = "1353" />
  • <cfset pusherKey = "ceef7621b815dc2a7c9f" />
  • <cfset pusherSecret = "****************" />
  • <cfset pusherChannel = "chatRoom" />
  •  
  • <!---
  • Event we are triggering (this is what the client will
  • "bind" to on the given channel).
  • --->
  • <cfset pusherEvent = "messageEvent" />
  •  
  • <!---
  • At this point, we have to assemble the data we are going to
  • push using Pusher. We need to create a chat data struct with
  • the passed-in data.
  •  
  • NOTE: We are using the array-notation to ensure the case of
  • the keys is maintained. This is because we have to pass this
  • off to Javascript which is case-sensitive.
  • --->
  • <cfset chatData = {} />
  • <cfset chatData[ "userID" ] = url.userID />
  • <cfset chatData[ "handle" ] = htmlEditFormat( url.handle ) />
  • <cfset chatData[ "message" ] = htmlEditFormat( url.message ) />
  •  
  • <!---
  • Now that we have our chat data, we have to serialize it for
  • our HTTP POST to the Pusher App. All values "pushed" must be
  • in JSON format.
  • --->
  • <cfset pusherData = serializeJSON( chatData ) />
  •  
  • <!--- Authentication information. --->
  • <cfset authVersion = "1.0" />
  • <cfset authMD5Body = lcase( hash( pusherData, "md5" ) ) />
  • <cfset authTimeStamp = fix( getTickCount() / 1000 ) />
  •  
  • <!--- Build the post resource (the RESTfule resource). --->
  • <cfset pusherResource = "/apps/#pusherAppID#/channels/#pusherChannel#/events" />
  •  
  •  
  • <!--- ------------------------------------------------- --->
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <!---
  • The following is the digital signing of the HTTP request.
  • Frankly, this stuff is pretty far above my understanding
  • of cryptology. I have adapted code from the PusherApp
  • ColdFusion component written by Bradley Lambert:
  •  
  • http://github.com/blambert/pusher-cfc
  • --->
  •  
  •  
  • <!---
  • Create the list of query string values (alpha-sorted with no
  • URL encoding).
  • --->
  • <cfsavecontent variable="queryStringData">
  • <cfoutput>
  • auth_key=#pusherKey#
  • auth_timestamp=#authTimeStamp#
  • auth_version=#authVersion#
  • body_md5=#authMD5Body#
  • name=#pusherEvent#
  • </cfoutput>
  • </cfsavecontent>
  •  
  • <!---
  • Create the raw signature data. This is the HTTP
  • method, the resource, and the alpha-ordered query
  • string (non-URL-encoded values).
  • --->
  • <cfset signatureData = (
  • ("POST" & chr( 10 )) &
  • (pusherResource & chr( 10 )) &
  • reReplace(
  • trim( queryStringData ),
  • "\s+",
  • "&",
  • "all"
  • )
  • ) />
  •  
  • <!---
  • Create our secret key generator. This can create a secret
  • key from a given byte array. Initialize it with the byte
  • array version of our PushApp secret key and the algorithm
  • we want to use to generate the secret key.
  • --->
  • <cfset secretKeySpec = createObject(
  • "java",
  • "javax.crypto.spec.SecretKeySpec"
  • ).init(
  • toBinary( toBase64( pusherSecret ) ),
  • "HmacSHA256"
  • )
  • />
  •  
  • <!---
  • Create our MAC (Message Authentication Code) generator
  • to encrypt the message data using the PusherApp shared
  • secret key.
  • --->
  • <cfset mac = createObject( "java", "javax.crypto.Mac" )
  • .getInstance( "HmacSHA256" )
  • />
  •  
  • <!--- Initialize the MAC instance using our secret key. --->
  • <cfset mac.init( secretKeySpec ) />
  •  
  • <!---
  • Complete the mac operation, encrypting the given secret
  • key (that we created above).
  • --->
  • <cfset encryptedBytes = mac.doFinal( signatureData.getBytes() ) />
  •  
  •  
  • <!---
  • Now that we have the encrypted data, we have to convert
  • that data to a HEX-encoded string. We will use the big
  • integer for this.
  • --->
  • <cfset bigInt = createObject( "java", "java.math.BigInteger" )
  • .init( 1, encryptedBytes )
  • />
  •  
  • <!--- Convert the encrypted bytes to the HEX string. --->
  • <cfset secureSignature = bigInt.toString(16) />
  •  
  • <!---
  • Apparently, we need to make sure the signature is at least
  • 32 characters long. As such, let's just left-pad with spaces
  • and then replace with zeroes.
  • --->
  • <cfset secureSignature = replace(
  • lJustify( secureSignature, 32 ),
  • " ",
  • "0",
  • "all"
  • ) />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Now that we have all the values we want to post, including
  • our encrypted signature, we can post to the PusherApp REST
  • web service using CFHTTP.
  • --->
  • <cfhttp
  • result="post"
  • method="post"
  • url="http://api.pusherapp.com#pusherResource#">
  •  
  • <!---
  • Alert the post resource that the value is coming through
  • as JSON data.
  • --->
  • <cfhttpparam
  • type="header"
  • name="content-type"
  • value="application/json"
  • />
  •  
  • <!--- Set the authorization parameters. --->
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_version"
  • value="#authVersion#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_key"
  • value="#pusherKey#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_timestamp"
  • value="#authTimeStamp#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="body_md5"
  • value="#authMD5Body#"
  • />
  •  
  • <!--- Sent the name of the pusher event. --->
  • <cfhttpparam
  • type="url"
  • name="name"
  • value="#pusherEvent#"
  • />
  •  
  • <!--- Send the actual message data (JSON data). --->
  • <cfhttpparam
  • type="body"
  • value="#pusherData#"
  • />
  •  
  • <!--- Digitally sign the HTTP request. --->
  • <cfhttpparam
  • type="url"
  • name="auth_signature"
  • value="#secureSignature#"
  • />
  •  
  • </cfhttp>
  •  
  •  
  • <!--- Push a success! --->
  • <cfcontent
  • type="text/plain"
  • variable="#toBinary( toBase64( 'success' ) )#"
  • />

Here is the ColdFusion code (type.cfm) that relays the start and stop type events to the Pusher web service:

Type - type.cfm

  • <!--- Param the url value. --->
  • <cfparam name="url.userID" type="string" default="" />
  • <cfparam name="url.handle" type="string" default="" />
  • <cfparam name="url.isTyping" type="boolean" default="false" />
  •  
  •  
  • <!---
  • Pusher information used to tie this push notification
  • to a given application and to a given client (listening
  • for a given channel).
  •  
  • NOTE: Your Pusher account can have multiple applications
  • associated with it (each of which can have on-the-fly
  • channel creation).
  • --->
  • <cfset pusherAppID = "1353" />
  • <cfset pusherKey = "ceef7621b815dc2a7c9f" />
  • <cfset pusherSecret = "****************" />
  • <cfset pusherChannel = "chatRoom" />
  •  
  • <!---
  • Event we are triggering (this is what the client will
  • "bind" to on the given channel).
  • --->
  • <cfset pusherEvent = "typeEvent" />
  •  
  • <!---
  • At this point, we have to assemble the data we are going to
  • push using Pusher. We need to create a Typing event to signify
  • that the given handle has started or stopped typing.
  •  
  • NOTE: We are using the array-notation to ensure the case of
  • the keys is maintained. This is because we have to pass this
  • off to Javascript which is case-sensitive.
  • --->
  • <cfset typeData = {} />
  • <cfset typeData[ "userID" ] = url.userID />
  • <cfset typeData[ "handle" ] = htmlEditFormat( url.handle ) />
  • <cfset typeData[ "isTyping" ] = url.isTyping />
  •  
  • <!---
  • Now that we have our typing data, we have to serialize it for
  • our HTTP POST to the Pusher App. All values "pushed" must be
  • in JSON format.
  • --->
  • <cfset pusherData = serializeJSON( typeData ) />
  •  
  • <!--- Authentication information. --->
  • <cfset authVersion = "1.0" />
  • <cfset authMD5Body = lcase( hash( pusherData, "md5" ) ) />
  • <cfset authTimeStamp = fix( getTickCount() / 1000 ) />
  •  
  • <!--- Build the post resource (the RESTfule resource). --->
  • <cfset pusherResource = "/apps/#pusherAppID#/channels/#pusherChannel#/events" />
  •  
  •  
  • <!--- ------------------------------------------------- --->
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <!---
  • The following is the digital signing of the HTTP request.
  • Frankly, this stuff is pretty far above my understanding
  • of cryptology. I have adapted code from the PusherApp
  • ColdFusion component written by Bradley Lambert:
  •  
  • http://github.com/blambert/pusher-cfc
  • --->
  •  
  •  
  • <!---
  • Create the list of query string values (alpha-sorted with no
  • URL encoding).
  • --->
  • <cfsavecontent variable="queryStringData">
  • <cfoutput>
  • auth_key=#pusherKey#
  • auth_timestamp=#authTimeStamp#
  • auth_version=#authVersion#
  • body_md5=#authMD5Body#
  • name=#pusherEvent#
  • </cfoutput>
  • </cfsavecontent>
  •  
  • <!---
  • Create the raw signature data. This is the HTTP
  • method, the resource, and the alpha-ordered query
  • string (non-URL-encoded values).
  • --->
  • <cfset signatureData = (
  • ("POST" & chr( 10 )) &
  • (pusherResource & chr( 10 )) &
  • reReplace(
  • trim( queryStringData ),
  • "\s+",
  • "&",
  • "all"
  • )
  • ) />
  •  
  • <!---
  • Create our secret key generator. This can create a secret
  • key from a given byte array. Initialize it with the byte
  • array version of our PushApp secret key and the algorithm
  • we want to use to generate the secret key.
  • --->
  • <cfset secretKeySpec = createObject(
  • "java",
  • "javax.crypto.spec.SecretKeySpec"
  • ).init(
  • toBinary( toBase64( pusherSecret ) ),
  • "HmacSHA256"
  • )
  • />
  •  
  • <!---
  • Create our MAC (Message Authentication Code) generator
  • to encrypt the message data using the PusherApp shared
  • secret key.
  • --->
  • <cfset mac = createObject( "java", "javax.crypto.Mac" )
  • .getInstance( "HmacSHA256" )
  • />
  •  
  • <!--- Initialize the MAC instance using our secret key. --->
  • <cfset mac.init( secretKeySpec ) />
  •  
  • <!---
  • Complete the mac operation, encrypting the given secret
  • key (that we created above).
  • --->
  • <cfset encryptedBytes = mac.doFinal( signatureData.getBytes() ) />
  •  
  •  
  • <!---
  • Now that we have the encrypted data, we have to convert
  • that data to a HEX-encoded string. We will use the big
  • integer for this.
  • --->
  • <cfset bigInt = createObject( "java", "java.math.BigInteger" )
  • .init( 1, encryptedBytes )
  • />
  •  
  • <!--- Convert the encrypted bytes to the HEX string. --->
  • <cfset secureSignature = bigInt.toString(16) />
  •  
  • <!---
  • Apparently, we need to make sure the signature is at least
  • 32 characters long. As such, let's just left-pad with spaces
  • and then replace with zeroes.
  • --->
  • <cfset secureSignature = replace(
  • lJustify( secureSignature, 32 ),
  • " ",
  • "0",
  • "all"
  • ) />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Now that we have all the values we want to post, including
  • our encrypted signature, we can post to the PusherApp REST
  • web service using CFHTTP.
  • --->
  • <cfhttp
  • result="post"
  • method="post"
  • url="http://api.pusherapp.com#pusherResource#">
  •  
  • <!---
  • Alert the post resource that the value is coming through
  • as JSON data.
  • --->
  • <cfhttpparam
  • type="header"
  • name="content-type"
  • value="application/json"
  • />
  •  
  • <!--- Set the authorization parameters. --->
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_version"
  • value="#authVersion#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_key"
  • value="#pusherKey#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_timestamp"
  • value="#authTimeStamp#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="body_md5"
  • value="#authMD5Body#"
  • />
  •  
  • <!--- Sent the name of the pusher event. --->
  • <cfhttpparam
  • type="url"
  • name="name"
  • value="#pusherEvent#"
  • />
  •  
  • <!--- Send the actual message data (JSON data). --->
  • <cfhttpparam
  • type="body"
  • value="#pusherData#"
  • />
  •  
  • <!--- Digitally sign the HTTP request. --->
  • <cfhttpparam
  • type="url"
  • name="auth_signature"
  • value="#secureSignature#"
  • />
  •  
  • </cfhttp>
  •  
  •  
  • <!--- Push a success! --->
  • <cfcontent
  • type="text/plain"
  • variable="#toBinary( toBase64( 'success' ) )#"
  • />

Both of these ColdFusion pages post to the same Pusher application and Pusher channel; but, each of them defines a different event (messageEvent and typeEvent respectively). Although the application has to be pre-defined in the Pusher dashboard, both channels and events can be defined on the fly, in your code, as needed.

I've really enjoyed playing around with the Pusher web service. Its speed is quite impressive (especially on Chrome) and the functionality that it provides makes development of client-server interaction markedly easier. Chat is probably the most complicated thing I would ever build with this kind of a service and creating this proof of concept was relatively painless.




Reader Comments

So, you defined the server.bind events, "messageEvent" and typeEvent in the CFC. However, you can set these up in the Pusher app dashboard as well?

This was a really informative post that brought together a lot of good stuff. It's great to see ColdFusion really taking up its role as the middleman and, save for one UUID, completely free from the view.

Reply to this Comment

@Justin,

The events are created in the CFC only (and then bound to in the Javascript). The only thing you need to do in the PusherApp dashboard is create the app (which gives you the app ID and the secret key). Basically, the eventType is just meta data to the RESTful web service post - you can come up with any validly named event you want.

You don't need the UUID, per say. At first, I was going based solely on the Handle. The problem was, the client always starts out with the same handle, which made debugging a real pain in the butt :)

Glad you're liking these posts.

Reply to this Comment

Right, so you could build a user event that would just increase the anonymous user name by an integer as is the traditional basic chat logic. Then it would be CF free.

It's just amazing how much has changed in the fast five years.

Reply to this Comment

@Justin,

Yeah, I think I see what you're saying; although, I think you'd start to run into a headache making sure the right user got the right ID. At the end of the day, you *still* need a server-side language to act as a proxy to the Pusher service since you can't post from the web client directly to Pusher. As such, you might not want to make great the enemy of good.

I know what you mean - this stuff is all so cool and exciting these days!

Reply to this Comment

just not able to get to the pusher debug console. it keep says connecting.
the code runs fine but don't see the chart going from one browser to another, it maybe pusherapp is not working.
Not sure, will debug more and will post the results.

Reply to this Comment

Good collection of articles for Pusher, Ben. I have been contemplating installing BlazeDS for a project I have been playing around with, but I will give this a shot. Definitely less painful and I like being able to natively use sockets when possible over using a flash bridge.

Reply to this Comment

@Robert,

I don't know too much about BlazeDZ. I've seen a few presentations on all the conflict management stuff (which I think is powered by Blaze or LifeCycle), but the presentations usually go way over my head. Pusher just made things very easy.

Right now, I'm working on a slightly more complex chat experiment, just to get a feeling for how higher-volume push notification stuff might be organized... kind of stumbling though. I'll post when I have some more stuff to share on the matter.

@asim,

I had a few times where the console was not connecting. After a few refreshes, it typically worked. You might also try using a different browser when accessing the console?

Reply to this Comment

Hi,

I wanted to try this, but no luck so far. This is what I did:
-downloaded the package
-renamed the files (to .html and .cfm)
-changed all the pusher ID's etc
-uploaded all to my website: http://www.mauricederegt.nl/chatbox

But no luck. What am I missing? Are there more requirements needed? Like cfm support on my website? Hot to check/install this? Do I need to install some extra stuff on my server?

Kind regards,

Maurice

ps: also my ( Change Handle ) is no link...

Reply to this Comment

@Maurice,

It looks like you got caught by the one piece of ColdFusion code on this page:

<cfoutput>
var userID = "#createUUID()#";
</cfoutput>

CFOutput tags are ColdFusion-specific, so if you're not running ColdFusion, that will break. For your purposes, you can probably replace the above code with something like:

var userID = ("user" + (new Date()).getTime());

The idea here is just to create a unique ID for each user (which the getTime() can approximate for testing).

I hope that helps.

Reply to this Comment

@Ben,

Instead of Pusher, have you ever tried to use the CF Event Gateway for sockets? It's right there in CF Admin > Event Gateways > Gateway Types > second type from the bottom (in CF 9.01).

CF Admin tells us that the Java Class of the socket gateway is examples.socket.SocketGateway. (I guess we're just supposed to guess where that might reside.) In hopes that it didn't reside deep within a JAR file somewhere, I did hard disk searches for examples.socket.SocketGateway, just SocketGateway and SocketGateway.java. I eventually found it with the SocketGateway.java search at this location (of a multiserver Unix installation, obviously):

/opt/jrun4/servers/((instancename))/cfusion.ear/cfusion.war/WEB-INF/cfusion/gateway/src/examples/socket/SocketGateway.java

The default timeout for Event Gateway socket listeners is 30 seconds. That seems a little brief to me, considering that you're liable to keep the socket open for a little back-and-forth.

I'm considering exploring this further if one of my bosses wants to do google-like search box autofill and menus of suggested search terms. I figure the lesser overhead and latency of sockets (as opposed to AJAX) would make for a pretty peppy interaction with the server.

It would APPEAR that you can just grab the source, compile it to a JAR file and put it into

/opt/jrun4/servers/((instancename))/cfusion.ear/cfusion.war/WEB-INF/cfusion/gateway/lib/myjarname.jar

But maybe I should actually read the Event Gateway documentation in the CFML Reference first. :-)

Reply to this Comment

@WebManWalking,

I've heard someone refer to the Gateway functionality in ColdFusion as "both the most powerful and most underused feature" of the language. I wish I know *something* about it; but, I've just never looked into it. Perhaps I'll bump it up on my list of things to look into.

Reply to this Comment

@Ben,

You'll like it. Event Gateways are a different way of coding, but fun.

Here's a good, practical use for Event Gateways: Define a "directory watcher" that looks for new files in an FTP upload directory. If it sees any, it cfexecutes a virus scanner on the file. If the file's clean, it does a cffile action="move" to a different directory for further processing. If the file's infected, it moves the file to a quarantine directory instead. That's a "drop box" usage.

Reply to this Comment

@asim,

There are actually 2 message gateway types already defined in CF Admin's Gateway Types page: SMS (phone text messaging) and XMPP instant messaging.

In other words, you don't need to write a messaging app. CF ships with 2 of them. You just need to hook them up.

You could actually create a responder that would accept a stock abbreviation and return a stock quote via SMS or XMPP. Or accept a Zip code and return a weather report.

Look, Ma, no Web!

Reply to this Comment

@WebManWalking,
thanks for your reply.
thats what I am trying to do.. I need to create a webpage popup, which can provide chat interface to the user and on the other side the responder. obviously not bot, but a human. I think I need to play with sockets for that. not sure how

I don't wanna play old school application scope to pass msgs etc. I tried earlier with the gtalk xmpp but again, thats talking to bot.

I need something like LivePerson app

Reply to this Comment

@asim: I did something similar with Pusher not too long ago. I will see if I can dig up the source code. Between work and clients I am spread pretty thin these days..

Reply to this Comment

I worked with pusher too.
But as CF supports sockets (and xmpp) is there any way just to build something like purely with CF not putting anything in the middle?\

Is there any sample available anywhere how to use the sockets with CF? (8 or 9)

Reply to this Comment

@asim,

That's exactly what I was saying. There is a way "just to build something like purely with CF not putting anything in the middle", ... just use one of the other gateways specially built for that purpose.

This thread is on chat sessions, so I didn't mention it earlier, but there are actually eleven predefined gateway types. Some of them are in actual production use all over the place by developers who might not even realize that they're using event gateways: FlexDataManagementGateway, FlexMessagingGateway and FMSGateway (Flash Media Server) in particular.

So you set up a nifty Flex page that does callbacks to the server to get streaming videos, datasets from the database, etc. Guess what? You're using event gateways.

Have you heard about this cool new feature in CF 9, the ability to some CFML commands as a service (usually consumed by Flex/Flash)? There's a gateway for that: CfmlGateway.

Of course, you have to enable these service in ColdFusion Administrator, but still: Nothing in between. Just Flex/Flash to CF. The socket gateway is just an example app, but don't let that first impression fool you. Adobe's doing real stuff with the interface.

Reply to this Comment

Why do I always only see my typos after hitting Post Comment? Is it the different font family? Sheesh:

"some CFML commands" = "execute some CFML commands"

"these service" = "these services"

Reply to this Comment

@WebManWalking,

I actually think the DirectoryWatcher is one of the cooler use cases. I think it's the only one that Adobe actually packages with the install, as an example, but it's definitely a feature that has very exciting potential.

So often, I have to worry about a directory-watcher-like functionality with a scheduled task. Always felt a bit junky to have to do it that way.

I really want to look into the socket support for ColdFusion. I know people have already played with it and there's that RIAForge project.

Can I please just clone myself a few times :)

Reply to this Comment

this is great! I need to bookmark this so I can come back and ponder it later when I have more time. :-)

Reply to this Comment

After I post a message I get undefined: undefined in the message box.

I did change all the pages to .cfm files and also edited:

pusherAppID
pusherKey
pusherSecret
pusherChannel

Reply to this Comment

Brilliant!!! I'm going to test this and let hyou know how I get. And by the way, if only you had married me Ben....*sigh* Ah well!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.