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/ )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
( broadcast_new )
( init_comet )
( player_from_querystring )
player_from_querystring
:function player_from_querystring() {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
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();
}
};
}
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) {Now I am setting the cookie and using it. Last up is the
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;
});
};
}
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: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.
// 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;
});
};
}
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