Monday, June 28, 2010

Cookies in Fab.js

‹prev | My Chain | next›

Up today, using cookies in fab.js to better maintain state in my (fab) game.

I will be working on the comet initialization section of the game:
  ( /^\/comet_view/ )
( broadcast_new )
( init_comet )
( player_from_querystring )
This stack of fab apps pulls the player out from the querystring, initializes a comet session for that player, then broadcasts the new player to everyone in the game. First up, I add a time string as the unique session ID for the player in player_from_querystring:
function player_from_querystring() {
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
var uniq_id = "" + (new Date()).getTime();
if (/MYFABID=(\w+)/.test(head.headers.cookie)) {
uniq_id = RegExp.$1;
}


var search = head.url.search.substring(1);
var q = require('querystring').parse(search);
var app = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0, uniq_id: uniq_id} });
if ( app ) app();
}
else {
out();
}
};
}
Nothing too special going on there—it is a typical (fab) unary / upstream application. It returns the anonymous function so that it can pull in the header information, primarily for the query string info, but now also for the cookies. Here I am not setting the cookie, just reading it. Fab.js/node.js do not have great bulit-in cookie parsing support, but fortunately I do not need it. I use a simple RegExp to pull out cookies that look like: Cookie: MYFABID=123456789. If the cookie is present, I will use that to uniquely identify the player, otherwise, I will use the current time.

Moving back down the stack (toward the browser), I use the uniq_id to set the player's session ID in cookies:
function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
out({ headers: { "Content-type": "text/html",
"Set-Cookie": "MYFABID=" + obj.body.uniq_id } })

({body: "<html><body>\n" })

}
return listener;
});
};
}
Now I am setting the cookie and using it. Last up is the broadcast_new, which is slightly overloaded. In addition to broadcasting the new player to existing game players, it also stores the new player in a local store. The whole point of this exercise was to get this working through a browser reload. Hopefully this will do:

// TODO: test and/or shrink
function broadcast_new (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body && obj.body.id) {
for (var id in players) {
out({body: comet_new_player(JSON.stringify(players[id].status))});
}

var new_id = obj.body.id;
if (!players[new_id]) {
puts("[broadcast_new] adding: " + new_id);
add_player(obj.body, out);

idle_watch(new_id);
setTimeout(function(){keepalive(new_id);}, 30*1000);
}
else if (players[new_id].uniq_id == obj.body.uniq_id) {
puts("[broadcast_new] refreshing session: " + new_id);
add_player(obj.body, out);
}

else {
out();
}
}
else {
out(obj);
}
return listener;
});
};
}
I retain the bit about telling the current user about all players in the current room. Also retained is the conditional that only adds players if they are not already in the room. Newly added here is a follow-up conditional that refreshes the comet session if the new comet session is initialized with the same unique identifier with which the player was first assigned upon entering the room.

This means that no one else can impersonate (at least not without hijacking the session) my player. That is a good stopping point for today. Up tomorrow: replacing the unique ID with a md5sum and some cleanup.

Day #148

No comments:

Post a Comment