Browse files

Fixed minor bugs. Modifying connection and authentication methods.

Fixed: When numeric message was sent, client would only see {message}
Partially Added: REST API, which will replace the Memcache API
Changed: Session creation and user creation separated.
  • Loading branch information...
1 parent 3da4903 commit 1308dca732d804d3578cd9717e36c4d65a466e84 Joshua Gross committed Mar 21, 2010
Showing with 654 additions and 95 deletions.
  1. +1 −1 js/im.js
  2. +7 −0 server/config.js
  3. +379 −0 server/md5.js
  4. +267 −94 server/server.js
View
2 js/im.js
1 addition, 1 deletion not shown because the diff is too large. Please use a local Git client to view these changes.
View
7 server/config.js
@@ -17,6 +17,13 @@ exports.ports = {
private: [11998, 'localhost']
};
+// === API Key ===
+//
+// This is the **private** API key that is used for any REST calls to the
+// server. Please change this key to something long and random. You should
+// never use this key on the client side!
+exports.api_key = 'FG34tbNW$n5aw4E6Y&U&6inBFDs';
+
// === {{{ cookie }}} ===
//
// Define the cookie name and how long a session will be stored on the server.
View
379 server/md5.js
@@ -0,0 +1,379 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+exports.hex_md5 = function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
+exports.b64_md5 = function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
+exports.any_md5 = function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
+exports.hex_hmac_md5 = function hex_hmac_md5(k, d)
+ { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
+exports.b64_hmac_md5 = function b64_hmac_md5(k, d)
+ { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
+exports.any_hmac_md5 = function any_hmac_md5(k, d, e)
+ { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+ return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of a raw string
+ */
+function rstr_md5(s)
+{
+ return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data (raw strings)
+ */
+function rstr_hmac_md5(key, data)
+{
+ var bkey = rstr2binl(key);
+ if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
+ return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
+}
+
+/*
+ * Convert a raw string to a hex string
+ */
+function rstr2hex(input)
+{
+ try { hexcase } catch(e) { hexcase=0; }
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var output = "";
+ var x;
+ for(var i = 0; i < input.length; i++)
+ {
+ x = input.charCodeAt(i);
+ output += hex_tab.charAt((x >>> 4) & 0x0F)
+ + hex_tab.charAt( x & 0x0F);
+ }
+ return output;
+}
+
+/*
+ * Convert a raw string to a base-64 string
+ */
+function rstr2b64(input)
+{
+ try { b64pad } catch(e) { b64pad=''; }
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var output = "";
+ var len = input.length;
+ for(var i = 0; i < len; i += 3)
+ {
+ var triplet = (input.charCodeAt(i) << 16)
+ | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
+ | (i + 2 < len ? input.charCodeAt(i+2) : 0);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > input.length * 8) output += b64pad;
+ else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
+ }
+ }
+ return output;
+}
+
+/*
+ * Convert a raw string to an arbitrary string encoding
+ */
+function rstr2any(input, encoding)
+{
+ var divisor = encoding.length;
+ var i, j, q, x, quotient;
+
+ /* Convert to an array of 16-bit big-endian values, forming the dividend */
+ var dividend = Array(Math.ceil(input.length / 2));
+ for(i = 0; i < dividend.length; i++)
+ {
+ dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
+ }
+
+ /*
+ * Repeatedly perform a long division. The binary array forms the dividend,
+ * the length of the encoding is the divisor. Once computed, the quotient
+ * forms the dividend for the next step. All remainders are stored for later
+ * use.
+ */
+ var full_length = Math.ceil(input.length * 8 /
+ (Math.log(encoding.length) / Math.log(2)));
+ var remainders = Array(full_length);
+ for(j = 0; j < full_length; j++)
+ {
+ quotient = Array();
+ x = 0;
+ for(i = 0; i < dividend.length; i++)
+ {
+ x = (x << 16) + dividend[i];
+ q = Math.floor(x / divisor);
+ x -= q * divisor;
+ if(quotient.length > 0 || q > 0)
+ quotient[quotient.length] = q;
+ }
+ remainders[j] = x;
+ dividend = quotient;
+ }
+
+ /* Convert the remainders to the output string */
+ var output = "";
+ for(i = remainders.length - 1; i >= 0; i--)
+ output += encoding.charAt(remainders[i]);
+
+ return output;
+}
+
+/*
+ * Encode a string as utf-8.
+ * For efficiency, this assumes the input is valid utf-16.
+ */
+function str2rstr_utf8(input)
+{
+ var output = "";
+ var i = -1;
+ var x, y;
+
+ while(++i < input.length)
+ {
+ /* Decode utf-16 surrogate pairs */
+ x = input.charCodeAt(i);
+ y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
+ if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
+ {
+ x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
+ i++;
+ }
+
+ /* Encode output as utf-8 */
+ if(x <= 0x7F)
+ output += String.fromCharCode(x);
+ else if(x <= 0x7FF)
+ output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
+ 0x80 | ( x & 0x3F));
+ else if(x <= 0xFFFF)
+ output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
+ 0x80 | ((x >>> 6 ) & 0x3F),
+ 0x80 | ( x & 0x3F));
+ else if(x <= 0x1FFFFF)
+ output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
+ 0x80 | ((x >>> 12) & 0x3F),
+ 0x80 | ((x >>> 6 ) & 0x3F),
+ 0x80 | ( x & 0x3F));
+ }
+ return output;
+}
+
+/*
+ * Encode a string as utf-16
+ */
+function str2rstr_utf16le(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length; i++)
+ output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
+ (input.charCodeAt(i) >>> 8) & 0xFF);
+ return output;
+}
+
+function str2rstr_utf16be(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length; i++)
+ output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
+ input.charCodeAt(i) & 0xFF);
+ return output;
+}
+
+/*
+ * Convert a raw string to an array of little-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+function rstr2binl(input)
+{
+ var output = Array(input.length >> 2);
+ for(var i = 0; i < output.length; i++)
+ output[i] = 0;
+ for(var i = 0; i < input.length * 8; i += 8)
+ output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
+ return output;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2rstr(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length * 32; i += 8)
+ output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
+ return output;
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length.
+ */
+function binl_md5(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << ((len) % 32);
+ x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+
+ a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+ d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+ c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
+ b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+ a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+ d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
+ c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+ b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+ a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
+ d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+ c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+ b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+ a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
+ d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+ c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+ b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
+
+ a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+ d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+ c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
+ b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+ a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+ d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
+ c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+ b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+ a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
+ d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+ c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+ b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
+ a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+ d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+ c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
+ b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+ a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+ d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+ c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
+ b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+ a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+ d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
+ c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+ b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+ a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
+ d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+ c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+ b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
+ a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+ d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+ c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
+ b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+ a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+ d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
+ c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+ b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+ a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
+ d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+ c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+ b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+ a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
+ d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+ c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+ b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
+ a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+ d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+ c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
+ b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ }
+ return Array(a, b, c, d);
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+ return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+ return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+ return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+ return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+ return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
View
361 server/server.js
@@ -31,7 +31,9 @@ var sys = require('sys'),
http = require('http'),
tcp = require('tcp'),
config = require('./config'),
- url = require('url');
+ md5 = require('./md5'),
+ url = require('url'),
+ querystring = require('querystring');
var AjaxIM = function(config) {
var self = this;
@@ -53,6 +55,7 @@ var AjaxIM = function(config) {
// * {{{sessions}}} is a hash table of session ids with usernames as values.
// * {{{debug}}} enables or disables debug messages from being output to the console. Set manually.
// * {{{onlineCount}}} contains the number of users online at any one moment.
+ // * {{{onlineList}}} is a JSON string of currently online users.
this.users = {};
this.sessions = {};
@@ -61,6 +64,7 @@ var AjaxIM = function(config) {
this.debug = true;
this.onlineCount = 0;
+ this.onlineList = '{}';
// === {{{ AjaxIM.init() }}} ===
//
@@ -78,7 +82,20 @@ var AjaxIM = function(config) {
['^/send$', this.send],
['^/status$', this.status],
['^/resume$', this.resume],
- ['^/online$', this.online]
+ ['^/online$', this.online],
+ ['^/online_count$', this.onlineCount]
+ ]);
+ this.server.post([
+ ['^/user$', this.initUser],
+ ['^/send$', this.send],
+ ['^/send_raw$', this.sendRaw]
+ ['^/status$', this.status]
+ ]);
+ this.server.put([
+ ['^/user/friends$', this.addFriend]
+ ]);
+ this.server.del([
+ ['^/user/friends$', this.removeFriend]
]);
this.server.start();
@@ -122,6 +139,7 @@ var AjaxIM = function(config) {
this.internalServer.start();
this._start_gc();
+ this._start_cache();
}
// === //private//\\ {{{ AjaxIM._start_gc() }}} ===
@@ -161,6 +179,31 @@ var AjaxIM = function(config) {
}, 3600000); // 1 hour
};
+ // === //private//\\ {{{ AjaxIM._start_cache() }}} ===
+ //
+ // Starts any caching operations for specific data sets, such as the online
+ // user list.
+ this._start_cache = function() {
+ var self = this;
+
+ // Cache user list in memory
+ setInterval(function() {
+ var users = [];
+ for(var username in self.users) {
+ var user = self.users[username];
+ if(user.status.s > 0) {
+ users.push({
+ username: user.username,
+ user_id: user.user_id,
+ status: user.status,
+ session_id: user.session_id
+ });
+ }
+ }
+ self.onlineList = self.apiGetUserList();
+ }, 2000);
+ };
+
// === //private//\\ {{{ AjaxIM._session(request, provide) }}} ===
//
// Returns the requested session information, {{{provide}}}, based on
@@ -218,19 +261,50 @@ var AjaxIM = function(config) {
//
// ==== Parameters ====
// * {{{str}}} is the debug string.
- this._d = function(str) {
+ this._d = function(str) {
if(this.debug) {
var addZero = function(str) { return str < 10 ? '0' + str : str; };
var d = new Date();
var d_str = addZero(d.getMonth() + 1) + '/' + addZero(d.getDate()) + '/' + d.getFullYear() + ' ' +
addZero(d.getHours()) + ':' + addZero(d.getMinutes()) + ':' + addZero(d.getSeconds());
- sys.puts('[' + d_str + '] ' + str);
+ sys.puts(arguments.callee.caller.name + ': [' + d_str + '] ' + str);
+ }
+ };
+
+ // === //private//\\ {{{ AjaxIM._authReq(request) }}} ===
+ //
+ // Checks that the private REST API key was sent in the request. If not, the
+ // server responds with a 403 error code ("Forbidden").
+ this._authReq = function(qs, response) {
+ if(!('api_key' in qs) ||
+ qs.api_key != this.config.api_key) {
+
+ response.reply(403, {'e': 'forbidden'});
+ return false;
+ }
+
+ return true;
+ };
+
+ // === //private//\\ {{{ AjaxIM._verifyReq(request) }}} ===
+ //
+ // Verifies that the request is actually from somewhere in our domain.
+ // If not, the server responds with a 403 error code ("Forbidden").
+ this._verifyReq = function(request, response) {
+ if(!('xreq' in request.cookies[this.config.cookie.name]) ||
+ !('xreq' in request.uri.params) ||
+ request.uri.params.xreq != request.cookies[this.config.cookie.name].xreq) {
+
+ response.reply(403, {'e': 'forbidden'});
+ return false;
}
+
+ return true;
};
- // === //private//\\ {{{ AjaxIM._initUser(username, data) }}} ===
+ // === {{{ AjaxIM.initSession(username, data) }}} ===
//
// Initializes a user session and adds the user to the users list. Additionally,
// it stores a callback function which will push data directly to this user. A
@@ -244,58 +318,59 @@ var AjaxIM = function(config) {
// ** {{{session_id}}} is the user's unique session id (used to (re)connect to the server).
// ** {{{friends}}} is the friends list.
// ** {{{guest}}} (optional) defines whether or not this is a "guest" (temporary) user.
- this._initUser = function(username, data) {
- if(data['user_id']) {
- self._d('User [' + username + '] has connected. Adding to user hash and notifying friends.');
-
- if(!(username in self.users)) {
- self.users[username] = {
- username: username,
- user_id: data.user_id,
- session_id: data.session_id,
- last_active: Date.now(),
- friends: data.friends,
- status: {s: 1, m: ''},
- guest: data['guest'] ? true : false,
- callback: function(msg) {
- var cbs = self.users[username]._callbacks;
- if(msg)
- self.users[username]._queue.push(msg);
-
- if(cbs.length) {
- for(var i = 0; i < cbs.length; i++)
- cbs[i](self.users[username]._queue);
- self.users[username]._callbacks = [];
- self.users[username]._queue = [];
- } else {
- if(self.users[username]._callbackAttempts < 3) {
- setTimeout(function() {
- try {
- self.users[username].callback();
- self.users[username]._callbackAttempts++;
- } catch(e) {}
- }, 150);
- } else {
- self.users[username]._callbackAttempts = 0;
- }
- }
- },
- _callbacks: [],
- _queue: [],
- _callbackAttempts: 0
- };
- self.onlineCount++;
+ this.initSession = function() {
+ if(!self._authReq(this.request)) return false;
+
+ if('username' in this.params) {
+ if(username in self.users) {
+ self._d('Session already exists for user [' + username + '].');
+
+ this.response.reply(200, {r: 'success', session_id: self.users[username].session_id});
+ } else {
+ self._d('Session created for user [' + username + ']. Adding to session hash.');
+
+ var session_id;
+ do {
+ session_id = md5.hex_md5(Date.now().toString() + username)
+ } while(session_id in self.sessions);
+
+ self.sessions[session_id] = new Session(username, this.params.friends);
+
+ this.response.reply(200, {r: 'success', session_id: session_id});
}
-
- self.sessions[data.session_id] = {
- username: username,
- user_id: data.user_id,
- friends: data.friends,
- last_active: Date.now()
- };
- return true;
} else {
- return false;
+ this.response.reply(200, {r: 'error', e: 'missing username'});
+ }
+ };
+
+ this.initUser = function() {
+ var session_id = this.params['session_id'] ||
+ (self.config.cookie.name in this.request.cookies ?
+ this.request.cookies[self.config.cookie.name].sid :
+ '');
+
+ if(session_id && (this.params.session_id in self.sessions)) {
+ var session = self.sessions[this.params.session_id];
+
+ var friends = {};
+ for(var i = 0, l = session.friends.length; i < l; i++) {
+ var friend = session.friends[i][0];
+ var group = session.friends[i][1];
+
+ friends[friend] = {g: group}
+ if(friend in self.users)
+ friends[friend].s = self.users[friend].status;
+ else
+ friends[friend].s = {s: 0, m: ''};
+ }
+
+ if(!(username in self.users)) {
+ self.users[username] =
+ new User(session.username, friends, this.params.session_id, this.params['guest'] || false);
+ self.onlineCount++;
+ }
+
+ this.response.reply(200, {r: 'connected', f: friends});
}
};
@@ -309,16 +384,16 @@ var AjaxIM = function(config) {
return false;
var user = self.users[username];
- if(user['guest']) {
+ if(user.guest) {
// User was a guest, so notify everyone that they logged off and remove their session.
self.apiBroadcastRaw('from', username, {t: 's', s: username, r: '', m: '0:'});
delete self.sessions[self.users[username].session_id];
} else {
user.friends.forEach(function(f) {
if(f.u in self.users) {
- for(var i=0; i < self.users[f.u]['friends'].length; i++) {
- if(self.users[f.u]['friends'][i].u == username && self.users[f.u]['friends'][i].u != user.username) {
- self.users[f.u]['friends'][username].s = 0;
+ for(var i=0; i < self.users[f.u].friends.length; i++) {
+ if(self.users[f.u].friends[i].u == username && self.users[f.u].friends[i].u != user.username) {
+ self.users[f.u].friends[i].s = 0;
break;
}
}
@@ -355,8 +430,8 @@ var AjaxIM = function(config) {
var response = this.response;
user._callbacks.push(function(msg) { response.reply(200, msg); });
- self.users[user.username].last_active = Date.now();
- self.sessions[user.session_id].last_active = Date.now();
+ self.users[user.username].active();
+ self.sessions[user.session_id].active();
}
};
@@ -370,7 +445,7 @@ var AjaxIM = function(config) {
} else {
if(!(user in self.users)) {
session = self._session(this.request, 'session');
- session['session_id'] = this.request.cookies[self.config.cookie.name].sid;
+ session.session_id = this.request.cookies[self.config.cookie.name].sid;
self._initUser(user, session);
}
@@ -398,7 +473,7 @@ var AjaxIM = function(config) {
to in self.users &&
self.users[to].callback
) {
- var time = Math.round((new Date()).getTime() / 1000);
+ var time = Math.round(Date.now() / 1000);
self.users[to].callback({
t: 'm',
s: user.username,
@@ -410,8 +485,8 @@ var AjaxIM = function(config) {
self._d('User [' + user.username + '] sent a message to [' + to + '] ' + (sent ? 'successfully.' : 'UNSUCCESSFULLY.'));
- self.users[user.username].last_active = Date.now();
- self.sessions[user.session_id].last_active = Date.now();
+ self.users[user.username].active();
+ self.sessions[user.session_id].active();
this.response.reply(200, {'sent': sent});
};
@@ -452,15 +527,30 @@ var AjaxIM = function(config) {
self._d('User [' + user.username + '] set his/her status to [' + statusMsg + ']. Friends notified.');
self.users[user.username].status = {s: status, m: this.request.uri.params.message};
- self.users[user.username].last_active = Date.now();
- self.sessions[user.session_id].last_active = Date.now();
+ self.users[user.username].active();
+ self.sessions[user.session_id].active();
this.response.reply(200, {status_updated: status_updated});
};
// === {{{ AjaxIM.online() }}} ===
//
- // Return a count of the number of online users.
+ // Return a list of currently signed in users and their statuses
+ // sans the status messages.
this.online = function() {
+ var user = self._session(this.request, 'object');
+
+ if(!user) {
+ self._d('An unknown user tried to retrieve a list of online users without being authenticated.');
+ return this.response.reply(200, {'r': 'error', 'e': 'no session found'});
+ }
+
+ this.response.reply(200, this.onlineList);
+ };
+
+ // === {{{ AjaxIM.onlineTotal() }}} ===
+ //
+ // Return a count of the number of online users.
+ this.onlineTotal = function() {
this.response.reply(200, {count: self.onlineCount});
};
@@ -655,29 +745,92 @@ var AjaxIM = function(config) {
};
};
+// == User Object ==
+function User(username, friends, user_id, session_id, guest) {
+ this.username = username;
+ this.friends = friends;
+ this.statue = {s: 1, m: ''};
+ this.user_id = user_id || 0;
+ this.session_id = session_id;
+ this.last_active = Date.now();
+ this.guest = guest || false;
+
+ this._callbacks = [];
+ this._queue = [];
+ this._callbackAttempts = [];
+
+ this.callback = function(msg) {
+ if(msg)
+ this._queue.push(msg);
+
+ if(this._callbacks.length) {
+ for(var i = 0; i < this._callbacks.length; i++)
+ this._callbacks[i](this._queue);
+ this._callbacks = [];
+ this._queue = [];
+ } else {
+ if(this._callbackAttempts < 3) {
+ var self = this;
+ setTimeout(function() {
+ try {
+ self.callback();
+ self._callbackAttempts++;
+ } catch(e) {}
+ }, 150);
+ } else {
+ this._callbackAttempts = 0;
+ }
+ }
+ };
+
+ this.active = function() {
+ this.last_active = Date.now();
+ };
+}
+
+// == Session Object ==
+function Session(username, friends, user_id) {
+ this.username = username;
+ this.friends = friends;
+ this.user_id = user_id;
+ this.last_active = Date.now();
+
+ this.active = function() {
+ this.last_active = Date.now();
+ }
+};
+
// == WebServer Class ==
function WebServer(host, port) {
var self = this;
this.host = host;
this.port = port;
-
this.urlMap = [];
+
this.get = function(path, handler) {
- if(typeof path == 'object') {
- for(var i = 0, l = path.length; i < l; i++)
- this._map(path[i][0], 'GET', path[i][1]);
- } else {
- this._map(path, 'GET', handler);
- }
+ this._map(path, 'GET', handler);
};
this.post = function(path, handler) {
this._map(path, 'POST', handler);
};
+ this.put = function(path, handler) {
+ this._map(path, 'PUT', handler);
+ };
+
+ this.del = function(path, handler) {
+ this._map(path, 'DELETE', handler);
+ };
+
this._map = function(path, type, handler) {
- var path_regex = new RegExp(path);
- this.urlMap.push([path_regex, type, handler]);
+ if(typeof path != 'object')
+ path = [[path, handler]];
+
+ for(var i = 0, l = path.length; i < l; i++) {
+ var path_regex = new RegExp(path[i][0]);
+ this.urlMap.push([path_regex, type, path[i][1]]);
+ }
};
this._notFound = function() {
@@ -729,23 +882,43 @@ function WebServer(host, port) {
}
request.setBodyEncoding('utf8');
- request.cookies = self._parseCookies(request.headers['cookie']);
- response.reply = function(code, obj) {
- var content = JSON.stringify(obj);
- content = request.uri.params.callback + '(' + content + ');';
-
- response.sendHeader(code, {
- 'content-type': 'text/html',
- 'content-length': content.length,
- 'expires': 'Mon, 26 Jul 1997 05:00:00 GMT',
- 'cache-control': 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
- 'pragma': 'no-cache'
- });
- response.write(content);
- response.close();
- }
-
- handler.apply({request: request, response: response}, args.slice(1));
+ request.addListener('data', function(body) {
+ if(request.method != 'GET')
+ request.uri.params = querystring.parse(body);
+ }).addListener('end', function() {
+ request.cookies = self._parseCookies(request.headers['cookie']);
+ response._cookies = [];
+ response.setCookie = function(name, value, expires, path, domain) {
+ expires = expires || new Date(+new Date + 30 * 24 * 60 * 60 * 1000);
+ response._cookies.push(name + '=' + value +
+ '; expires=' + expires +
+ (path ? '; path=' + path : '') +
+ (domain ? '; domain=' + domain : '')
+ );
+ };
+ response.clearCookie = function(name, path, domain) {
+ var expires = new Date(+new Date - 30 * 24 * 60 * 60 * 1000);
+ response.setCookie(name, '', expires, path, domain);
+ };
+ response.reply = function(code, obj) {
+ var content = JSON.stringify(obj);
+ content = request.uri.params.callback + '(' + content + ');';
+ var headers = {
+ 'content-type': 'text/html',
+ 'content-length': content.length,
+ 'expires': 'Mon, 26 Jul 1997 05:00:00 GMT',
+ 'cache-control': 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
+ 'pragma': 'no-cache'
+ };
+ if(response._cookies.length)
+ headers['set-cookie'] = response._cookies.join(', ');
+ response.sendHeader(code, headers);
+ response.write(content);
+ response.close();
+ };
+
+ handler.apply({request: request, response: response, params: request.uri.params}, args.slice(1));
+ });
}).listen(this.port, this.host);
};
}

0 comments on commit 1308dca

Please sign in to comment.