2010/11/05

The long dark tea-time of the node

Looking back I would've never guessed this project would have had so many twists and turns. I set out to build a game that was about synchronizing with computer generated rhythms. Ie. Make the box move at the same rhythm as a sine wave. I really liked the idea of something so simple requiring intense concentration. It's immersive but really really simple. Anyways, I set out to craft this thing with some jQuery, plain js, and recursive timeouts. At some point I thought it might be fun to track the score and even make an easy way to share your score on Twitter or Facebook. I figured I'd make some type of nodejs app to handle the sharing and the rest could be static html pages.

What followed was a journey through two different types of oauth implementations, several nodejs modules and many greps through codebases to figure out how and why this worked or didn't. Since much of this will be useful to some other poor sap building with the same tools, I'll outline here what I did and how it all works together.

Animation

Javascript, front to back

Building the game itself took some time but was pretty straight-forward. jQuery is extremely useful for binding events and such, but I tried to use bare js inside the loops for efficiency. Since I'm not using canvas here, just manipulating positions of dom objects, it's fairly processor intensive. With that said Chrome and Safari handle it with ease. Firefox will make your processor fan spin up.

The core of the animation stuff is variations on this simple recursive timeout:

block = $('#block');
move_timeout = null;
(function send_it_down() {
    move_timeout = setTimeout(function() {
        block.css('top',block.css('top')+1);
        send_it_down();
    },30);
})();

// to stop animation
// clearTimeout(move_timeout);

jQuery has some nice animation functionality but it's all based on a fixed time of execution in which each step's timeout is based on the amount of steps to execute and the total time for the animation to run. Rolling my own animation code allowed me to control an infinitely executing constant speed animation.

The jQuery api docs and w3schools js ref got me through anything tricky and Wolframs Mathworld was the key to the color game (intially it was a spirograph pattern, then a simpler nephroid, finally a circle).

OAuth

First thing's first. What is OAuth and how do I use it? In order to make sharing your score easy I had to write an app that interfaced with both Twitter and Facebook's apis. I heard Twitter was simpler and their docs were better so I started there. A link in the twitter docs to this Oauth Guide made for an easy introduction. Twitter is currently using an OAuth 1.0 implementation that is pretty true to what you would expect after reading the guide. The only difference was that Twitter seems to store your callback url for you on your request token call so you don't need to supply it on your auth token call. Facebook has an OAuth 2.0 implementation that requires ssl and has fewer steps. The only catch on Facebook is they're sticklers for your callback url. It must be present in both your authorize call as well as your auth token call and it must match exactly.

Now that I know what OAuth is and have a basic understanding of how to interface with Twitter and Facebook's apis, I searched out an OAuth module for nodejs.

Nodejs

I found some buzz around Ciaranj's node-oauth and found it to a pretty good wrapper for the functionality I would need. It's lacking a way to make POST requrests in the OAuth2 code, but writing my own wasn't too tough. I had my eye on Expressjs as a framework for building http handlers for this app. It's a sweet little implementation with a very simple premise and good docs. For my datastore I decided to take another crack at MongoDB. I've built PHP apps that interfaced with Mongo but nothing with nodejs yet.

So far Nodejs has been a great tool for ad hoc api clients and prototypes. This is the first server app I've written with nodejs. A few things I noticed that were unexpected. One, nesting callbacks can become tedious. This was especially true with the MongoDB module. I don't see any way to get around it, but it's ugly. The other thing was that error handling with callbacks is much different than exception handling within a single thread of execution. Wrap anything with a callback in a try/catch and you'll see it's useless. So becoming expectant on an onError() method for all of the connections is the norm.

Other challenges with nodejs included: since my nodejs app is listening on a different port than my html pages are being served from, I had to do some extra redirection at the end of my OAuth process so that my final landing page of my popup would be able to call back to the main window and trigger a success event. And any exception that I didn't catch takes down my app completely.

With that aside I was able to craft a sweet little app in less than a couple hundred lines that is fast and easy. I can manage the sharing process with a couple ajax calls and a bind to my custom success event.

nDistro

Installing nodejs modules can be done a few different ways. Initially I was using npm. It worked ok but since it installs to a central npm repository, and nodejs and it's modules version so quickly, it made more sense to use nDistro. It allows you to manage your nodejs versions as well as your module versions from a simple config file. This is super useful since I can literally break my app with the wrong version.

I found one oddity using nDistro to install MongoDB's node module. If you compile the C version of the BSON parser and intend to use it with your code, it will not be available. The reason for this is that nDistro symlinks each module to a common lib/node directory. There is a relative path that is broken in one of mongodb's require statements for including the compiled BSON parser. I was able to fix this by setting up a symlink in the lib directory. Another issue with the MongoDB module is that in order to compile the native BSON parser you need to have a full nodejs install with the node-waf binary in an available path. The node binary installed by nDistro is not sufficient. After all of this I had errors compiling BSON on my vps that were not on my mac.. The js parser will work fine given the size of my records.

MongoDB

MongoDB showed up on my radar over a year ago and since then it's gotten easier to use and more reliable. The 10gen developers have put a lot of effort into building good libraries in many languages to interface with MongoDB. With the postmortem after FourSquare's recent outage, I got a little window into the scale that mongo seems to be handling well. FTA the outage was due to overactivity in one of the shards that was running too close to memory capacity. Given that FourSquare is running multiple 66GB RAM EC2 mongodbs and they just hit their 200 millionth checkin I am confident that my 2 dozen friend's tweet/post counts will be handled with grace and speed. :)

Installing MongoDB is pretty much a matter of unzipping a tgz and running mongod from the command line.

Putting it all together

This post has been short on code snippets cus I'm hosting the entire codebase at the Github. This is my first run at a nodejs app with mongo running live and I am curious to see how stable everything is. My new linode server is purring like a kitten with anything I throw at it. Hopefully the tunr app continues the trend.

Play tunr here