Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Rolando Lopez and Ryan Jeffords
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Rolando Lopez Ryan Jeffords

Cell Phones, SMS, Twilio, Pusher, ColdFusion, And Google Maps == Fun

By
Published in , Comments (7)

As of late, I've been playing around some very cool technologies like Pusher's HTML5 WebSocket server and Twilio's mobile SMS text messaging web services. Each of these technologies is exciting on its own; but, I wanted to see if I could create a fairly simple but fun way to start integrating these technologies into some sort of ColdFusion work flow. What I came up with was the idea of allowing people to post SMS text messages to a Google map. While this idea isn't hugely useful, it does create a small system that touches Twilio, ColdFusion, Pusher, and the Google Maps API.

The idea here is straight forward: the user opens up the following Google Map:

Twilio, Pusher, ColdFusion, And Google Maps Powered Work Flow.

Then, the user sends an SMS text message to the given phone number. This phone number is a rented number in the Twilio service. The SMS text message gets delivered to Twilio. Twilio then takes the SMS text message and posts it via HTTP to the ColdFusion-powered SMS end point. The end point then looks at the form data to see if it can determine the user's address. If it cannot, it prompts the user (via an SMS response) for their information. Once the address is determined, the SMS end point posts the address information to Pusher's REST API. Pusher then "pushes" that information onto our HTML5 web client over the native WebSockets or a SWF-based fallback. The client then uses the Google Maps API to geocode and display the text message on the open map.

Twilio, Pusher, ColdFusion, And Google Maps Powered Work Flow.

Once the phone / Twilio / ColdFusion / Pusher / Google Maps work flow has completed, the SMS text message appears in realtime in front of the user's eyes:

Twilio, Pusher, ColdFusion, And Google Maps Powered Work Flow With SMS Text Message Realtime Display.

This might not be the most useful demo; but, I think it nicely illustrates how easily some of these 3rd party services can be integrated with ColdFusion. Of course, seeing the code will demonstrate this more than any video. The core of the demo lies in the Twilio SMS end point and the google maps page. Let's take at a look at the SMS end point first:

Twilio SMS End Point (Public ColdFusion URL)

<!--- Param the form variables. --->
<cfparam name="form.fromCity" type="string" default="" />
<cfparam name="form.fromState" type="string" default="" />
<cfparam name="form.fromZip" type="string" default="" />
<cfparam name="form.fromCountry" type="string" default="" />
<cfparam name="form.body" type="string" default="" />


<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->


<!---
	If Twilio was not able to give us any location information,
	we'll have asked for clarification from the user. Let's see
	if we have an outstanding request for the address.
--->
<cfif session.addressRequested>


	<!---
		Since we already asked the user for their location, we
		know (hope) that this message is the user's manually
		entered location. Store the current text message as
		the address.

		NOTE: We are adding a comma to the end here only to make
		sure that the serializeJSON() call on a zip code doesn't
		create a decimal number. Adding the comma does not adversely
		affect the geocoding of the address.
	--->
	<cfset session.address = "#form.body#," />


<!---
	Check to see if Twilio was able to determine the address of
	the user based on their phone number.
--->
<cfelseif (
	len( form.fromCity ) ||
	len( form.fromState ) ||
	len( form.fromZip ) ||
	len( form.fromCountry )
	)>


	<!---
		Twilio was able to determine the user's location based
		on their phone number. As such, we wouldn't need to take
		any more steps with this user.
	--->

	<!--- Store the text message. --->
	<cfset session.message = form.body />

	<!---
		Build the location based on the message meta data. Even
		if Twilio didn't fill out all of these values, it doesn't
		seem to affect the geocoding very much and it allows us to
		err on the side of more data.
	--->
	<cfset session.address = "#form.fromCity#, #form.fromState# #form.fromZip#, #form.fromCountry#" />


<!---
	At this point, Twilio has not been able to determine the
	location of the user from their phone number and we have
	not asked for it manually yet.
--->
<cfelse>


	<!---
		Store the user's current message in their session for
		the next request.
	--->
	<cfset session.message = form.body />

	<!---
		Flag that we are asking the user for their location
		on the next request.
	--->
	<cfset session.addressRequested = true />

	<!--- Set the response, asking the user for location. --->
	<cfset response = "Sorry, your location could not be determined :( What is your zip code?" />


</cfif>


<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->


<!---
	At this point, we may or may not have both the message and
	the address from the user. We only want to act IF we have both.
	Otherwise, we'll assume we are waiting on an action from the
	user.
--->
<cfif (
	len( session.message ) &&
	len( session.address )
	)>


	<!---
		Now that we have both the message and the address, let's
		post the message data to the Pusher app so that it can be
		pushed to the client and the map.

		NOTE: We are using array notation when creating the struct
		so that key-case will be retained when ColdFusion serializes
		the struct into JSON.
	--->
	<cfset message = {} />
	<cfset message[ "address" ] = session.address />
	<cfset message[ "message" ] = session.message />

	<!---
		Now, we need to push the message. For some reason, I am
		getting random 401 Unauthorized problems with the service.
		As such, I'm going to try a few times if it fails.
	--->
	<cfloop
		index="tryIndex"
		from="1"
		to="3"
		step="1">

		<!--- Push the message. --->
		<cfset pushResponse = application.pusher.pushMessage(
			"sms",
			"textMessage",
			message
			) />

		<!---
		<cfdump
			var="#pushResponse#"
			output="#expandPath( './log.htm' )#"
			format="html" />
		--->

		<!--- Check to see if we should break out. --->
		<cfif reFind( "20\d", pushResponse.statusCode )>

			<!--- This request worked, break out of try loop. --->
			<cfbreak />

		</cfif>

		<!---
			Sleep very briefly to allow the time to change in case
			there is something time-specific breaking this approach.
		--->
		<cfthread
			action="sleep"
			duration="500"
			/>

	</cfloop>

	<!---
		Now that we have tried the Pusher app submission up to a
		certain number of times, let's check to see if the Pusher
		HTTP request failed.
	--->
	<cfif reFind( "20\d", pushResponse.statusCode )>

		<!---
			The pusher request was successful. This means the
			message arrive at the client successfully.
		--->
		<cfset response = "Thanks! Check the map for your text message!" />

	<cfelse>

		<!---
			The pusher request failed for some reason; let the user
			know about the failure.
		--->
		<cfset response = "Sorry! I could not communicate with Pusher's HTML5 WebSockets. Please try again :)" />

	</cfif>


	<!---
		Regardless of what the pusher app has told us, we are going
		to consider this pass as "successful" as possible. As such,
		let's reset the session information.

		To keep this code easier to maintain, just use the
		onSessionStart() event handler to manage the session reset.
	--->
	<cfset createObject( "component", "Application" )
		.onSessionStart()
		/>


</cfif>


<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->


<!--- Convert the message into Twilio XML response. --->
<cfsavecontent variable="responseXml">
	<cfoutput>

		<?xml version="1.0" encoding="UTF-8"?>
		<Response>
			<Sms>#xmlFormat( response )#</Sms>
		</Response>

	</cfoutput>
</cfsavecontent>


<!---
	Stream XML response to Twilio client. Make sure to TRIM
	the XML response such that it is valid XML.
--->
<cfcontent
	type="text/xml"
	variable="#toBinary( toBase64( trim( responseXml ) ) )#"
	/>

At first, this code was actually much shorter. But then during testing, I discovered that not all mobile carriers report the addresses of their mobile customers. As such, I had to expand the code to prompt the user for their location if it could not be determined from Twilio's meta data. To do this, I had to keep track of the user's information across multiple SMS text messages. Fortunately, the Twilio Proxy service acts like a true web browser which allows us to use ColdFusion's native session management (the Application.cfc is shown a bit farther down).

Once both the SMS text message and the user's location have been established, the Twilio SMS end point posts the message information to Pusher's HTML5 WebSocket server. Unfortunately, I was having some trouble getting consistent authorization when posting to Pusher's REST web service. This is why, in the code, I am allowing the SMS text message to be posted and re-posted to Pusher's web service up to 3 times. I have submitted a help question on Pusher's support forum and will be working on debugging this issue with the Pusher team.

Once the message is successfully posted to the Pusher web service, Pusher "pushes" it over the "sms" WebSocket channel and triggers a "textMessage" event on the client (web browser). The client then geocodes the address and posts both a marker and an infoWindow on the visible map:

Google Map Page

<!--- Reset the output buffer. --->
<cfcontent type="text/html" />

<!DOCTYPE HTML>
<html>
<head>
	<title>Twilio + Pusher + ColdFusion + Google Maps</title>
	<link rel="stylesheet" type="text/css" href="./map.css"></link>
	<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" src="http://maps.google.com/maps/api/js?sensor=false"></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 has loaded, initialize the map and scripts.
		$(function(){

			// Get a reference to the header.
			var siteHeader = $( "#siteHeader" );

			// Get a reference to the site map.
			var siteMap = $( "#siteMap" );

			// Get a reference to the intro message.
			var introMessage = $( "#introMessage" );

			// The google map object - this will be initialized once
			// the page has loaded fully.
			var map = null;

			// Get an instance of the Google map geocoder object so
			// that we can get the lat/long for addresses.
			var geocoder = new google.maps.Geocoder();

			// This is the visible window that displays the text
			// message above a given marker.
			var infoWindow = new google.maps.InfoWindow();

			// This is a collection of markers, indexed by address.
			// We are using this so we don't add mulitple markers for
			// repeat text messages.
			var markers = {};

			// Create a Pusher server object with your app's key and
			// the SMS channel we want to listen on.
			var server = new Pusher(
				"52f3e571a0c9b08ee647",
				"sms"
			);


			// ---------------------------------------------- //
			// ---------------------------------------------- //


			// Bind to the window resize event so we can re-size the
			// map to fill any available space.
			$( window ).resize(
				function(){
					// Resize the map.
					siteMap.height(
						$( window ).height() - siteHeader.height()
					);
				}
			);

			// Trigger the resize to get the right map dimensions
			// once the window has loaded.
			$( window ).resize();


			// ---------------------------------------------- //
			// ---------------------------------------------- //


			// Bind to the server to listen for SMS text messages.
			server.bind(
				"textMessage",
				function( data ){
					// Check to make sure we have a map object. If
					// we don't then we can't react yet.
					if (!map){
						return;
					}

					// I prepare the text message for display on the
					// map using a marker and info window.
					addTextMessageToMap( data.address, data.message );
				}
			);


			// ---------------------------------------------- //
			// ---------------------------------------------- //


			// I take the given address and message and try to
			// prepare them for the map interface by turning the
			// address into a latitude / longitude point (geocoding).
			var addTextMessageToMap = function( address, message ){

				// Check to make sure that this marker has not
				// already been found.
				if (address in markers){

					// Store the new message with the existing
					// marker.
					markers[ address ].smsData = message;

					// Simply show the window above the current text
					// message.
					openInfoWindow( markers[ address ] );

					// Return out since we don't need to decode
					// anything else at this point.
					return;

				}

				// If we made it here, then this is a new address.
				// Geocode the address code into a lat/long object.
				geocoder.geocode(
					{
						"address": address
					},
					function( results, status ){
						// Check to see make sure the geocoding was
						// successful and that we got a result based
						// on the zip code.
						if (
							(status == google.maps.GeocoderStatus.OK) &&
							results.length
							){

							// Create the marker at the given
							// location with the given message.
							var marker = addMarkerToMap(
								results[ 0 ].geometry.location,
								message
							);

							// Store the marker based on the address
							// so that we can reference it later if
							// we need to.
							markers[ address ] = marker;

							// Open this new info window.
							openInfoWindow( marker );

						} else {

							alert( "Geocoding failed!" );

						}
					}
				);

			};


			// I add maker to the map and initialize it such that it
			// will respond to click events.
			var addMarkerToMap = function( latLong, message ){
				// Create new marker from the location.
				var marker = new google.maps.Marker({
					map: map,
					position: latLong,
					title: "SMS Text Message From Twilio"
				});

				// Store the SMS data with the marker itself. This
				// is a corruption of the Marker object, but I am
				// not sure how else to keep this data with the
				// marker accross clicks (outside of a closure).
				marker.smsData = message;

				// Add a click-event handler fo the marker as well to
				// allow the info window to be shown on demand.
				google.maps.event.addListener(
					marker,
					"click",
					function(){
						openInfoWindow( marker );
					}
				);

				// Return the newly created marker.
				return( marker );
			};


			// I open the info window above the given marker using
			// the stored SMS text data for the info winod content.
			var openInfoWindow = function( marker ){
				// Set the info window contents.
				infoWindow.setContent(
					"<div class='messageBody'>" +
						marker.smsData +
					"</div>"
				);

				// Open the info window above the given marker.
				infoWindow.open( map, marker );

				// Pan to the position of the new marker.
				map.panTo( marker.getPosition() );
			};


			// ---------------------------------------------- //
			// ---------------------------------------------- //


			// When the window has fully loaded, we need to set up
			// the Google map.
			$( window ).load(
				function(){

					// Empty the map container.
					siteMap.empty();

					// Create the new Goole map controller using our
					// site map (pass in the actual DOM object).
					// Center it above the United States (lat/long)
					// with a reasonable zoom (5).
					map = new google.maps.Map(
						siteMap[ 0 ],
						{
							zoom: 5,
							center: new google.maps.LatLng(
								38.925229,
								-96.943359
							),
							mapTypeId: google.maps.MapTypeId.ROADMAP
						}
					);

				}
			);


			// ---------------------------------------------- //
			// ---------------------------------------------- //


			// I hide the intro message.
			var hideIntroMessage = function(){
				introMessage.animate(
					{
						opacity: 0,
						marginTop: -300
					},
					1500,
					function(){
						introMessage.remove();
					}
				);
			};


			// Bind to the document click to hide the intro message
			// when someone clicks the document.
			$( document ).bind(
				"click.intro",
				function(){
					// Unbind this click.
					$( document ).unbind( "click.intro" );

					// Hide the intro message.
					hideIntroMessage()
				}
			);

		});

	</script>
</head>
<body>

	<div id="siteHeader">

		<h1>
			Send an SMS text message to <strong>(917) 791-2120</strong>
		</h1>

		<div id="logos">
			Twilio + Pusher + ColdFusion + Google Maps
		</div>

	</div>

	<div id="siteMap">
		Loading Map....
	</div>

	<div id="introMessage">
		<span class="instructions">
			Send a text message to the following number and it will
			show up on the map.
		</span>
		<span class="number">
			(917) 791-2120
		</span>
		<span class="close">
			Click anywhere in this window to hide these instructions.
		</span>
	</div>

</body>
</html>

And that's pretty much all there is to this. It's not a tiny amount of code; but, considering how many services this work flow touches, the amount of code is rather small! While the code posted above constitutes the bulk of the work flow, I'll post the Application.cfc and the Pusher.cfc below:

Application.cfc

<cfcomponent
	output="false"
	hint="I define the application settings and event handlers.">

	<!--- Define the application. --->
	<cfset this.name = hash( getCurrentTemplatePath() ) />
	<cfset this.applicationTimeout = createTimeSpan( 0, 1, 0, 0 ) />
	<cfset this.sessionManagement = true />
	<cfset this.sessionTimeout = createTimeSpan( 0, 0, 7, 0 ) />


	<!--- Define the request settings. --->
	<cfsetting
		requesttimeout="15"
		showdebugoutput="false"
		/>


	<cffunction
		name="onApplicationStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I initialize the application.">

		<!--- Cache an instance of our Pusher utility. --->
		<cfset application.pusher = createObject( "component", "Pusher" ).init(
			"1527",
			"52f3e571a0c9b08ee647",
			"********************"
			) />

		<!--- Return true so the page can process. --->
		<cfreturn true />
	</cffunction>


	<cffunction
		name="onSessionStart"
		access="public"
		returntype="void"
		output="false"
		hint="I initialize the session.">

		<!---
			Set up the session variables. Remember, since
			Twilio supports cookies, we can turn on stanard
			session management.
		--->
		<cfset session.message = "" />
		<cfset session.address = "" />
		<cfset session.addressRequested = false />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="onRequestStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I initialize the request.">

		<!--- Check for manually application reset. --->
		<cfif structKeyExists( url, "init" )>

			<!--- Reset application. --->
			<cfset this.onApplicationStart() />

		</cfif>

		<!--- Return true so the page can process. --->
		<cfreturn true />
	</cffunction>


	<cffunction
		name="onError"
		access="public"
		returntype="void"
		output="true"
		hint="I handle any uncaught application errors.">

		Error!

		<cfreturn />
	</cffunction>

</cfcomponent>

Remember, since Twilio supports cookies, we can turn on and leverage ColdFusion session management as we would in any standard web application. Here, I am keeping the session timeout low - 7 minutes - since we need to keep track of the user across, at most, two different SMS text messages.

The Pusher.cfc is nothing more than encapsulated version of the Pusher integration code that I have posted before:

Pusher.cfc

<cfcomponent
	output="false"
	hint="I provide access to the Pusher App's RESTful web service API.">


	<cffunction
		name="init"
		access="public"
		returntype="any"
		output="false"
		hint="I return the initialized component.">

		<!--- Define arguments. --->
		<cfargument
			name="appID"
			type="string"
			required="true"
			hint="I am the Pusher application ID defined by Pusher."
			/>

		<cfargument
			name="appKey"
			type="string"
			required="true"
			hint="I am the Pusher application Key defined by Pusher."
			/>

		<cfargument
			name="appSecret"
			type="string"
			required="true"
			hint="I am the Pusher application secret Key defined by Pusher."
			/>

		<!--- Store the component properties. --->
		<cfset variables.appID = arguments.appID />
		<cfset variables.appKey = arguments.appKey />
		<cfset variables.appSecret = arguments.appSecret />

		<!--- Return this object reference. --->
		<cfreturn this />
	</cffunction>


	<cffunction
		name="pushMessage"
		access="public"
		returntype="any"
		output="false"
		hint="I push the given message over the given channel. The message will be serialized internally into JSON - be mindful of the case-sensitivity required in Javascript when defininig your data. I return the HTTP response of the HTTP post.">

		<!--- Define arguments. --->
		<cfargument
			name="channel"
			type="string"
			required="true"
			hint="I am the channel over which the message will be pushed to the client (assuming they ar subscribed to the channel)."
			/>

		<cfargument
			name="event"
			type="string"
			required="true"
			hint="I am the event to trigger as part of the message transfer over the given channel."
			/>

		<cfargument
			name="message"
			type="any"
			required="true"
			hint="I am the message being pushed - this can be any kind of data that can be serialized into JSON."
			/>

		<!--- Define the local scope. --->
		<cfset var local = {} />

		<!---
			Serialize the message into JSON. All data pushed to the
			web service must be in JSON format.

			NOTE: In ColdFusion, unless you use array-notation to
			define struct keys, JSON-serialized keys are turned
			into uppercase.
		--->
		<cfset local.pusherData = serializeJSON( arguments.message ) />

		<!--- Authentication information. --->
		<cfset local.authVersion = "1.0" />
		<cfset local.authMD5Body = lcase( hash( local.pusherData, "md5" ) ) />
		<cfset local.authTimeStamp = fix( getTickCount() / 1000 ) />

		<!--- Build the post resource (the RESTfule resource). --->
		<cfset local.pusherResource = "/apps/#variables.appID#/channels/#arguments.channel#/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 raw signature data. This is the HTTP
			method, the resource, and the alpha-ordered query
			string (non-URL-encoded values).
		--->
		<cfset local.signatureData = (
			("POST" & chr( 10 )) &
			(local.pusherResource & chr( 10 )) &
			(
				"auth_key=#variables.appKey#&" &
				"auth_timestamp=#local.authTimeStamp#&" &
				"auth_version=#local.authVersion#&" &
				"body_md5=#local.authMD5Body#&" &
				"name=#arguments.event#"
			)) />

		<!---
			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 local.secretKeySpec = createObject(
			"java",
			"javax.crypto.spec.SecretKeySpec"
			).init(
				toBinary( toBase64( variables.appSecret ) ),
				"HmacSHA256"
				)
			/>

		<!---
			Create our MAC (Message Authentication Code) generator
			to encrypt the message data using the PusherApp shared
			secret key.
		--->
		<cfset local.mac = createObject( "java", "javax.crypto.Mac" )
			.getInstance( "HmacSHA256" )
			/>

		<!--- Initialize the MAC instance using our secret key. --->
		<cfset local.mac.init( local.secretKeySpec ) />

		<!---
			Complete the mac operation, encrypting the given secret
			key (that we created above).
		--->
		<cfset local.encryptedBytes = local.mac.doFinal(
			local.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 local.bigInt = createObject( "java", "java.math.BigInteger" )
			.init( 1, local.encryptedBytes )
			/>

		<!--- Convert the encrypted bytes to the HEX string. --->
		<cfset local.secureSignature = local.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 local.secureSignature = replace(
			lJustify( local.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="local.post"
			method="post"
			url="http://api.pusherapp.com#local.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="#local.authVersion#"
				/>

			<cfhttpparam
				type="url"
				name="auth_key"
				value="#variables.appKey#"
				/>

			<cfhttpparam
				type="url"
				name="auth_timestamp"
				value="#local.authTimeStamp#"
				/>

				<cfhttpparam
				type="url"
				name="body_md5"
				value="#local.authMD5Body#"
				/>

			<!--- Sent the name of the pusher event. --->
			<cfhttpparam
				type="url"
				name="name"
				value="#arguments.event#"
				/>

			<!--- Send the actual message data (JSON data). --->
			<cfhttpparam
				type="body"
				value="#local.pusherData#"
				/>

			<!--- Digitally sign the HTTP request. --->
			<cfhttpparam
				type="url"
				name="auth_signature"
				value="#local.secureSignature#"
				/>

		</cfhttp>

		<!--- Return the HTTP status code. --->
		<cfreturn local.post />
	</cffunction>

</cfcomponent>

Again, this demo doesn't have much value in and of itself; but, I hope that it can demonstrate how easily ColdFusion can be the glue behind complex work flows that power mobile and realtime messaging applications.

Want to use code from this post? Check out the license.

Reader Comments

6 Comments

Ben, this is cool creative stuff. Thanks for sharing. One you can do to simplify the demo is take Pusher out of the picture and use BlazeDS within ColdFusion 9. BlazeDS won't push, but with long-polling your client-side piece won't know the difference.

15,841 Comments

@Aaron,

I'll have to check out BlazeDS. I've heard about it forever, but I have never played with it myself. My only concern about the long-polling is that it might put undue stress on the server? What I like about Pusher is that it only communicates when necessary. Of course, that might just be an invalid concern. I'll have to look into it.

15,841 Comments

@Baz,

Not currently.

I was thinking about maybe making an SMS Guest Book for fun. Anyone think that would be a nice idea? Or would people be too turned off about the idea of their general locations (zip) being on a map?

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel