Use bi-directional events system instead of handcrafted messages.

Esse commit está contido em:
Daniel Howard
2014-06-20 15:29:17 -07:00
commit 8b94d6c411
7 arquivos alterados com 326 adições e 275 exclusões
+221 -94
Ver Arquivo
@@ -47,6 +47,7 @@ AjaxIM = function(options, actions) {
// requests rather than POST requests (such as how the Node.JS Ajax IM // requests rather than POST requests (such as how the Node.JS Ajax IM
// server works). // server works).
this.actions = $.extend({ this.actions = $.extend({
noop: this.settings.pollServer + '/app/noop',
listen: this.settings.pollServer + '/app/listen', listen: this.settings.pollServer + '/app/listen',
send: this.settings.pollServer + '/app/message', send: this.settings.pollServer + '/app/message',
status: this.settings.pollServer + '/app/status', status: this.settings.pollServer + '/app/status',
@@ -258,6 +259,13 @@ AjaxIM = function(options, actions) {
self._scrollers(); self._scrollers();
} catch(e) {} } catch(e) {}
}); });
// Set up event handling
this.onEvent('hello', this.onHello);
this.onEvent('message', this.onMessage);
this.onEvent('status', this.onStatus);
this.onEvent('notice', this.onNotice);
this.onEvent('goodbye', this.onGoodbye);
} else { } else {
return AjaxIM.init(options); return AjaxIM.init(options);
} }
@@ -403,66 +411,61 @@ $.extend(AjaxIM.prototype, {
: Math.min(self._reconnectIn * 2, 16000); : Math.min(self._reconnectIn * 2, 16000);
self._lastReconnect = new Date(); self._lastReconnect = new Date();
setTimeout(function() { self.listen(); }, self._reconnectIn); setTimeout(function() { self.listen(); }, self._reconnectIn);
} },
this.actions.noop
); );
}, },
// === //private// {{{AjaxIM.}}}**{{{_parseMessages(messages)}}}** === // === //private// {{{AjaxIM.}}}**{{{_parseMessages(messages)}}}** ===
// //
_parseMessage: function(message) { _parseMessage: function(message) {
this.triggerEvent(message);
},
onHello: function(message) {
var self = this; var self = this;
$(this).trigger('parseMessage', [message]); this._clearSession();
switch(message.type) { this.username = message.username;
case 'hello': this.current_status = ['available', ''];
this._clearSession(); store.set('user', message.username);
store.set(this.username + '-status', this.current_status);
this.username = message.username; $('#imjs-friends').attr('class', 'imjs-available');
this.current_status = ['available', '']; $.each(message.friends, function() {
store.set('user', message.username); var friend;
store.set(this.username + '-status', this.current_status); if(this.length == 2)
friend = this;
else
friend = [this.toString(), ['offline', '']];
self.addFriend(friend[0], friend[1], 'Friends');
});
store.set(this.username + '-friends', this.friends);
$('#imjs-friends').attr('class', 'imjs-available'); // Set username in Friends list
$.each(message.friends, function() { var header = $('#imjs-friends-panel .imjs-header');
var friend; header.html(header.html().replace('{username}', this.username));
if(this.length == 2)
friend = this;
else
friend = [this.toString(), ['offline', '']];
self.addFriend(friend[0], friend[1], 'Friends');
});
store.set(this.username + '-friends', this.friends);
// Set username in Friends list
var header = $('#imjs-friends-panel .imjs-header');
header.html(header.html().replace('{username}', this.username));
// Set status available
$('#imjs-away-message-text, #imjs-away-message-text-arrow').hide();
$('#imjs-status-panel .imjs-button').removeClass('imjs-toggled');
$('#imjs-button-available').addClass('imjs-toggled');
break;
case 'message': // Set status available
this.incoming(message.user, message.body); $('#imjs-away-message-text, #imjs-away-message-text-arrow').hide();
break; $('#imjs-status-panel .imjs-button').removeClass('imjs-toggled');
$('#imjs-button-available').addClass('imjs-toggled');
},
case 'status': onMessage: function(event) {
this._friendUpdate(message.user, message.status, this.incoming(event.from, event.body);
message.message); },
this._storeFriends();
break;
case 'notice': onStatus: function(event) {
break; this._friendUpdate(event.from, event.status, event.message);
this._storeFriends();
case 'goodbye': },
this._notConnected();
break;
default: onNotice: function(event) {
break; },
}
onGoodbye: function(event) {
this._notConnected();
}, },
// === {{{AjaxIM.}}}**{{{incoming(from, message)}}}** === // === {{{AjaxIM.}}}**{{{incoming(from, message)}}}** ===
@@ -922,23 +925,17 @@ $.extend(AjaxIM.prototype, {
$(this).trigger('sendingMessage', [username, body]); $(this).trigger('sendingMessage', [username, body]);
AjaxIM.post( var event = {type: 'message', to: username, body: body};
this.actions.send, this.sendEvent(event, function(result) {
{to: username, body: body}, if(result._status.send) {
function(result) { $(self).trigger('sendMessageSuccessful', [username, body]);
if(result.type == 'success' && result.success == 'sent') {
$(self).trigger('sendMessageSuccessful',
[username, body]);
} else if(result.type == 'error') { } else if(result.type == 'error') {
if(result.error == 'not online') if(result.error == 'not online')
$(self).trigger('sendMessageFailed', $(self).trigger('sendMessageFailed', ['offline', username, body]);
['offline', username, body]);
else else
$(self).trigger('sendMessageFailed', $(self).trigger('sendMessageFailed', [result.error, username, body]);
[result.error, username, body]);
} }
}, }, function(error) {
function(error) {
self._notConnected(); self._notConnected();
var error = self._addError( var error = self._addError(
self.chats[username], self.chats[username],
@@ -946,11 +943,9 @@ $.extend(AjaxIM.prototype, {
'server is not available. Please ensure ' + 'server is not available. Please ensure ' +
'that you are signed in and try again.'); 'that you are signed in and try again.');
self._store(error); self._store(error);
$(self).trigger('sendMessageFailed', $(self).trigger('sendMessageFailed',
['not connected', username, body]); ['not connected', username, body]);
} });
);
}, },
// === {{{AjaxIM.}}}**{{{status(s, message)}}}** === // === {{{AjaxIM.}}}**{{{status(s, message)}}}** ===
@@ -999,31 +994,27 @@ $.extend(AjaxIM.prototype, {
} }
); );
} else { } else {
AjaxIM.post( var event = {type: 'status', status: value, message: message};
this.actions.status, this.sendEvent(event, function(result) {
{status: value, message: message}, if(result._status.send) {
function(result) { $(self).trigger('sendMessageSuccessful', [username, body]);
switch(result.type) { } else if(result.type == 'error') {
case 'success': if(result.error == 'not online')
$(self).trigger('changeStatusSuccessful', $(self).trigger('sendMessageFailed', ['offline', username, body]);
[value, message]); else
self.current_status = [value, message]; $(self).trigger('sendMessageFailed', [result.error, username, body]);
store.set(self.username + '-status', }
self.current_status); }, function(error) {
break; self._notConnected();
var error = self._addError(
case 'error': self.chats[username],
default: 'You are currently not connected or the ' +
$(self).trigger('changeStatusFailed', 'server is not available. Please ensure ' +
[result.e, value, message]); 'that you are signed in and try again.');
break; self._store(error);
} $(self).trigger('sendMessageFailed',
}, ['not connected', username, body]);
function(error) { });
$(self).trigger('changeStatusFailed',
['not connected', value, message]);
}
);
} }
}, },
@@ -1332,6 +1323,97 @@ $.extend(AjaxIM.prototype, {
$('#imjs-scroll-left').html(hiddenLeft); $('#imjs-scroll-left').html(hiddenLeft);
$('#imjs-scroll-right').html(hiddenRight); $('#imjs-scroll-right').html(hiddenRight);
},
unconfirmedEvents: {},
eventId: 1,
createEvent: function() {
var event = {};
event.id = this.eventId++;;
this.unconfirmedEvents[event.id] = evt;
},
sendEvent: function(event, successFunc, failureFunc) {
event.id = this.eventId++;
var evt = $.extend({}, event);
evt['_status'] = {
successFunc: successFunc,
failureFunc: failureFunc
};
this.unconfirmedEvents[event.id] = evt;
var self = this;
var url = null;
switch (event.type) {
case 'message':
url = this.actions.send;
break;
case 'status':
url = this.actions.status;
break;
case 'signoff':
url = this.actions.signoff;
break;
default:
break;
}
AjaxIM.post(url, event,
function(result) {
if (result) {
for (var e=0; e < result.length; ++e) {
self.dispatchEvent(events[e]);
}
}
},
function(error) {
if (self.unconfirmedEvents[event.id]) {
event = self.unconfirmedEvents[event.id];
event['_status']['sent'] = false;
self.dispatchEvent(event);
}
}
);
},
dispatchEvent: function(event) {
if (this.unconfirmedEvents[event.id]) {
$.extend(event, this.unconfirmedEvents[event.id]);
delete this.unconfirmedEvents[event.id];
console.log(JSON.stringify(event));
if (event['_status']['sent']) {
event['_status']['successFunc'](event);
} else {
event['_status']['failureFunc'](event);
}
} else {
this.triggerEvent(event);
}
},
// poor man's Backbone.js Events
eventHandlers: {},
/**
* Add a callback to listen for an event type.
*/
onEvent: function(eventType, callback) {
if (!this.eventHandlers[eventType]) {
this.eventHandlers[eventType] = [];
}
this.eventHandlers[eventType].push(callback);
},
/**
* Trigger an event on all interested callbacks.
*/
triggerEvent: function(event) {
if (this.eventHandlers[event.type]) {
for (var e=0; e < this.eventHandlers[event.type].length; ++e) {
this.eventHandlers[event.type][e].call(this, event);
}
}
} }
}) })
@@ -1360,7 +1442,7 @@ AjaxIM.init = function(options, actions) {
AjaxIM.client = new AjaxIM(options, actions); AjaxIM.client = new AjaxIM(options, actions);
return AjaxIM.client; return AjaxIM.client;
} };
// === {{{AjaxIM.}}}**{{{request(url, data, successFunc, failureFunc)}}}** === // === {{{AjaxIM.}}}**{{{request(url, data, successFunc, failureFunc)}}}** ===
@@ -1377,20 +1459,21 @@ AjaxIM.init = function(options, actions) {
// {{{_ignore_}}} is simply to provide compatability with {{{$.post}}}. // {{{_ignore_}}} is simply to provide compatability with {{{$.post}}}.
// {{{failure}}} is a callback function called when a request hasn't not // {{{failure}}} is a callback function called when a request hasn't not
// completed successfully. // completed successfully.
AjaxIM.post = function(url, data, successFunc, failureFunc) { AjaxIM.post = function(url, data, successFunc, failureFunc, urlnoop) {
AjaxIM.request(url, 'POST', data, successFunc, failureFunc); AjaxIM.request(url, 'POST', data, successFunc, failureFunc, urlnoop);
}; };
AjaxIM.get = function(url, data, successFunc, failureFunc) { AjaxIM.get = function(url, data, successFunc, failureFunc, urlnoop) {
AjaxIM.request(url, 'GET', data, successFunc, failureFunc); AjaxIM.request(url, 'GET', data, successFunc, failureFunc, urlnoop);
}; };
AjaxIM.request = function(url, type, data, successFunc, failureFunc) { AjaxIM.request = function(url, type, data, successFunc, failureFunc, noopurl) {
var errorTypes = ['timeout', 'error', 'notmodified', 'parseerror']; var errorTypes = ['timeout', 'error', 'notmodified', 'parseerror'];
if(typeof failureFunc != 'function') if(typeof failureFunc != 'function')
failureFunc = function(){}; failureFunc = function(){};
var jsonp = (url.substring(0, 1) !== '/'); var jsonp = (url.substring(0, 1) !== '/');
var success = false;
data['sessionid'] = cookies.get('sessionid'); data['sessionid'] = cookies.get('sessionid');
$.ajax({ $.ajax({
url: url, url: url,
@@ -1400,6 +1483,7 @@ AjaxIM.request = function(url, type, data, successFunc, failureFunc) {
cache: false, cache: false,
timeout: 299000 timeout: 299000
}).done(function(data) { }).done(function(data) {
success = true;
_dbg(JSON.stringify(data)); _dbg(JSON.stringify(data));
successFunc(data); successFunc(data);
}).fail(function(jqXHR, textStatus) { }).fail(function(jqXHR, textStatus) {
@@ -1407,6 +1491,47 @@ AjaxIM.request = function(url, type, data, successFunc, failureFunc) {
failureFunc(textStatus); failureFunc(textStatus);
}); });
if (jsonp) {
setTimeout(function() {
var failfn = function() {
if (!success) {
var textStatus = 'error';
_dbg(textStatus);
failureFunc(textStatus);
}
};
if (noopurl) {
var noopfn = function() {
var noopdone = false;
var event = {type: 'noop'};
$.ajax({
url: noopurl,
data: event,
dataType: 'jsonp',
type: type,
cache: false,
timeout: 299000
}).done(function(data) {
noopdone = true;
if (!success) {
setTimeout(noopfn, 3000);
}
}).fail(function(jqXHR, textStatus) {
// since JSONP, never called
});
setTimeout(function() {
if (!noopdone) {
failfn();
}
}, 3000);
};
noopfn();
} else {
failfn();
}
}, 3000);
}
// This prevents Firefox from spinning indefinitely // This prevents Firefox from spinning indefinitely
// while it waits for a response. // while it waits for a response.
/* /*
@@ -1433,7 +1558,9 @@ AjaxIM.incoming = function(data) {
if(data.length) if(data.length)
AjaxIM.client._parseMessages(data); AjaxIM.client._parseMessages(data);
} };
AjaxIM.eventID = 1;
// === {{{AjaxIM.}}}**{{{l10n}}}** === // === {{{AjaxIM.}}}**{{{l10n}}}** ===
// //
+25 -22
Ver Arquivo
@@ -37,14 +37,13 @@ app.listen(APP_PORT, APP_HOST);
app.get('/app/listen', function(){}); app.get('/app/listen', function(){});
app.use('/app/message', function(req, res) { app.use('/app/message', function(req, res) {
res.find(req.param('to'), function(user) { res.find(req.param('to'), function(to) {
if(!user) if(to) {
return res.send(new packages.Error('not online')); res.message(to, req.event);
} else {
res.message(user, new packages.Message( req.event._status = {sent: false, e: 'not online'};
req.session.data('username'), res.jsonp(req.event);
req.param('body') }
));
}); });
}); });
@@ -52,33 +51,37 @@ app.use('/app/message/typing', function(req, res) {
if(~packages.TYPING_STATES.indexOf('typing' + req.param('state'))) { if(~packages.TYPING_STATES.indexOf('typing' + req.param('state'))) {
res.find(req.param('to'), function(user) { res.find(req.param('to'), function(user) {
if(user) { if(user) {
res.message(user, new packages.Status( req.event.status = 'typing' + req.param('state');
req.session.data('username'), res.message(user, req.event);
'typing' + req.param('state') } else {
)); // Typing updates do not receive confirmations,
// as they are not important enough.
req.event._status = {sent: false, e: 'invalid user'};
res.jsonp(req.event);
} }
// Typing updates do not receive confirmations,
// as they are not important enough.
res.send('');
}); });
} else { } else {
res.send(new packages.Error('invalid state')); req.event._status = {sent: false, e: 'invalid state'};
res.jsonp(req.event);
} }
}); });
app.use('/app/status', function(req, res) { app.use('/app/status', function(req, res) {
if(~packages.STATUSES.indexOf(req.param('status'))) { if(~packages.STATUSES.indexOf(req.param('status'))) {
res.status(req.param('status'), req.param('message')); res.status(req.event);
res.send(new packages.Success('status updated'));
} else { } else {
res.send(new packages.Error('invalid status')); req.event._status = {sent: false, e: 'invalid status'};
res.jsonp(req.event);
} }
}); });
app.use('/app/noop', function(req, res) {
req.event._status = {sent: true};
res.session.respond(res, req.event);
});
app.use('/app/signoff', function(req, res) { app.use('/app/signoff', function(req, res) {
res.signOff(); res.signOff(req.event);
res.send(new packages.Success('goodbye'));
}); });
console.log('Ajax IM server started...'); console.log('Ajax IM server started...');
-86
Ver Arquivo
@@ -1,88 +1,2 @@
var sys = require('sys');
var Package = function() {};
Package.prototype._sanitize = function(content) {
// strip HTML
return content.replace(/<(.|\n)*?>/g, '');
};
var Error = exports.Error = function(error) {
this.error = error;
};
sys.inherits(Error, Package);
Error.prototype.toJSON = function() {
return {
type: 'error',
error: this.error
};
};
var Success = exports.Success = function(success) {
this.success = success;
};
sys.inherits(Success, Package);
Success.prototype.toJSON = function() {
return {
type: 'success',
success: this.success
};
};
var Message = exports.Message = function(from, body) {
this.from = from;
this.body = body;
};
sys.inherits(Message, Package);
Message.prototype.toJSON = function() {
return {
type: 'message',
user: this.from,
body: this._sanitize(this.body)
};
};
var Notice = exports.Notice = function(username, info) {
this.username = username;
this.info = info;
};
sys.inherits(Notice, Package);
Notice.prototype.toJSON = function() {
return {
type: 'notice',
user: this.username,
info: this.info
};
};
exports.TYPING_STATES = ['typing+', 'typing~', 'typing-']; exports.TYPING_STATES = ['typing+', 'typing~', 'typing-'];
exports.STATUSES = ['available', 'away', 'idle']; exports.STATUSES = ['available', 'away', 'idle'];
var Status = exports.Status = function(username, status, message) {
var statuses = exports.STATUSES + exports.TYPING_STATES;
this.username = username;
this.status = -~statuses.indexOf(status) ? status : statuses[0];
this.message = message;
};
sys.inherits(Status, Package);
Status.prototype.toJSON = function() {
return {
type: 'status',
user: this.username,
status: this.status,
message: this._sanitize(this.message || '')
};
};
var Offline = exports.Offline = function(username) {
this.username = username;
};
sys.inherits(Offline, Package);
Offline.prototype.toJSON = function() {
// A special type of status
return {
type: 'status',
user: this.username,
status: 'offline',
message: ''
};
};
+15
Ver Arquivo
@@ -9,6 +9,21 @@ module.exports = o_ = {
a[keys[i]] = b[keys[i]]; a[keys[i]] = b[keys[i]];
return a; return a;
}, },
extend: function() {
var o = {};
for (var a=0; a < arguments.length; ++a) {
this.merge(o, arguments[a]);
}
return o;
},
deletekey: function(o, key) {
if (o.hasOwnProperty(key)) {
delete o[key];
}
return o;
},
values: function(obj) { values: function(obj) {
if(typeof obj == 'array') if(typeof obj == 'array')
+15 -7
Ver Arquivo
@@ -1,5 +1,6 @@
var url = require('url'), var url = require('url'),
Hub = require('./im/hub'); Hub = require('./im/hub'),
o_ = require('../libs/utils');
module.exports = function setupHub(options) { module.exports = function setupHub(options) {
options = options || {}; options = options || {};
@@ -59,18 +60,25 @@ module.exports = function setupHub(options) {
if(msg = sess.message_queue.shift()) if(msg = sess.message_queue.shift())
sess._send.apply(sess, msg); sess._send.apply(sess, msg);
} else { } else {
sess.connection = res; req.event = o_.extend({}, req.query, req.body, req.params);
o_.deletekey(req.event, 'callback');
o_.deletekey(req.event, 'sessionid');
o_.deletekey(req.event, '_');
req.event.from = sess.data('username');
} }
req.session = sess; req.session = sess;
res.session = sess;
res.find = store.find.bind(store); res.find = store.find.bind(store);
res.message = function(to, package) { res.message = function(to, event) {
store.message(req.session, to, package); store.message(res, to, event);
}; };
res.status = function(value, message) { res.status = function(event) {
req.session.status(value, message); req.session.status(res, event);
};
res.signOff = function(event) {
store.signOff(req.sessionID, res, event);
}; };
res.signOff = function() { store.signOff(req.sessionID); };
if(url.parse(req.url).pathname !== '/app/listen') { if(url.parse(req.url).pathname !== '/app/listen') {
next(); next();
+25 -23
Ver Arquivo
@@ -18,15 +18,14 @@ var Hub = module.exports = function Hub(options) {
}, this.reapInterval, this); }, this.reapInterval, this);
} }
this.events.addListener('update', o_.bind(function(package) { this.events.addListener('update', o_.bind(function(event) {
var _package = package.toJSON(); if(event.type == 'status' && event.status == 'offline') {
if(package.type == 'status' && package.status == 'offline') {
var sids = Object.keys(this.sessions), sid, sess; var sids = Object.keys(this.sessions), sid, sess;
for(sid in this.sessions) { for(sid in this.sessions) {
sess = this.sessions[sid]; sess = this.sessions[sid];
if(sess.data('username') == package.username) { if(sess.data('username') == event.from) {
if(sess.listeners.length) if(sess.listeners.length)
sess.send(200, {type: 'goodbye'}); sess.send({type: 'goodbye'});
delete this.sessions[sid]; delete this.sessions[sid];
break; break;
} }
@@ -45,7 +44,8 @@ Hub.prototype.reap = function(ms) {
for(var i = 0, len = sids.length; i < len; ++i) { for(var i = 0, len = sids.length; i < len; ++i) {
var sid = sids[i], sess = this.sessions[sid]; var sid = sids[i], sess = this.sessions[sid];
if(sess.lastAccess < threshold) { if(sess.lastAccess < threshold) {
this.events.emit('update', new packages.Offline(sess.data('username'))); var event = {type: 'status', from: sess.data('username'), status: 'offline', message: ''};
this.events.emit('update', event);
} }
} }
}; };
@@ -75,19 +75,15 @@ Hub.prototype.get = function(req, fn) {
session._friends(friends_copy); session._friends(friends_copy);
session.events.addListener('status', session.events.addListener('status',
o_.bind(function(value, message) { o_.bind(function(value, message) {
this.events.emit( var event = {type: 'status', from: session.data('username'), status: value, message: message};
'update', this.events.emit('update', event);
new packages.Status(session.data('username'),
value,
message)
);
}, this)); }, this));
this.events.addListener('update', this.events.addListener('update',
o_.bind(session.receivedUpdate, session)); o_.bind(session.receivedUpdate, session));
this.set(req.sessionID, session); this.set(req.sessionID, session);
fn(null, session); fn(null, session);
}, this)); }, this));
session.status(packages.STATUSES[0], ''); session.status(null, {status: packages.STATUSES[0], message: ''});
} else { } else {
fn(); fn();
} }
@@ -112,19 +108,25 @@ Hub.prototype.find = function(username, fn) {
fn(false); fn(false);
}; };
Hub.prototype.message = function(from, to, package) { Hub.prototype.message = function(res, to, event) {
try { try {
package.user = from; to.send(event);
to.send(package); event._status = {sent: true};
from.respond(new packages.Success('sent')); res.session.respond(res, event);
} catch(e) { } catch(e) {
from.respond(new packages.Error(e.description)); event._status = {sent: false, e: e.description};
res.session.respond(res, event);
} }
}; };
Hub.prototype.signOff = function(sid) { Hub.prototype.signOff = function(sid, res, event) {
if(sid in this.sessions) if (sid in this.sessions) {
this.events.emit('update', event.status = 'offline';
new packages.Offline( event.message = '';
this.sessions[sid].data('username'))); this.events.emit('update', event);
}
event._status = {sent: true};
if (res) {
res.session.respond(res, event);
}
}; };
+25 -43
Ver Arquivo
@@ -18,18 +18,20 @@ var User = module.exports = function(req, data) {
setInterval(o_.bind(this._expireConns, this), 500); setInterval(o_.bind(this._expireConns, this), 500);
}; };
User.prototype.receivedUpdate = function(package) { User.prototype.receivedUpdate = function(event) {
if(this.friends.indexOf(package.username)) event = o_.extend({}, event);
this.send(package); event.to = this.data('username');
if(this.friends.indexOf(event.from))
this.send(event);
}; };
User.prototype._friends = function(friends) { User.prototype._friends = function(friends) {
this.friends = friends; this.friends = friends;
this.send(JSON.stringify({ this.send({
type: 'hello', type: 'hello',
username: this.data('username'), username: this.data('username'),
friends: friends friends: friends
})); });
}; };
User.prototype._expireConns = function() { User.prototype._expireConns = function() {
@@ -54,52 +56,28 @@ User.prototype.listener = function(conn) {
this.listeners.push(conn); this.listeners.push(conn);
}; };
User.prototype.respond = function(code, message, callback) { User.prototype.respond = function(res, event) {
this._send(this.req.jsonpCallback? 'listener': 'connection', code, message, callback); this._send('connection', event, res);
}; };
User.prototype.send = function(code, message, callback) { User.prototype.send = function(event) {
this._send('listener', code, message, callback); this._send('listener', event);
}; };
User.prototype.addCallback = function(message) { User.prototype._send = function(type, event, res) {
return ((typeof this.req.jsonpCallback) != 'undefined')? this.req.jsonpCallback+'('+message+');': message; if(type == 'connection') {
};
User.prototype._send = function(type, code, message, callback) {
if(!message && typeof code != 'number') {
callback = message;
message = code;
code = 200;
}
if(typeof message != 'string')
message = JSON.stringify(message);
if(type == 'connection' && this.connection) {
// end a regular connection with a response // end a regular connection with a response
this.connection.writeHead(code || 200, { res.jsonp(event);
// 'Content-Type': 'application/json',
'Content-Type': 'application/javascript',
'Content-Length': this.addCallback(message).length
});
this.connection.end(this.addCallback(message));
} else { } else {
// add a message to a long-polling connection // end a long-polling connection with an event
if(!this.listeners.length) if(!this.listeners.length)
return this.message_queue.push(arguments); return this.message_queue.push(arguments);
var cx = this.listeners.slice(), conn; var cx = this.listeners.slice(), conn;
this.listeners = []; this.listeners = [];
while(conn = cx.shift()) { while(conn = cx.shift()) {
conn.writeHead(code || 200, { conn.jsonp(event);
// 'Content-Type': 'application/json',
'Content-Type': 'application/javascript',
'Content-Length': this.addCallback(message).length
});
conn.end(this.addCallback(message));
} }
if(callback) callback();
} }
}; };
@@ -114,11 +92,15 @@ User.prototype.touch = function() {
this.lastAccess = +new Date; this.lastAccess = +new Date;
}; };
User.prototype.status = function(value, message) { User.prototype.status = function(res, event) {
if(!value) if(!event)
return this._status; return this._status;
this._status = value; this._status = event.status;
this._status_message = message; this._status_message = event.message;
this.events.emit('status', value, message); this.events.emit('status', event.status, event.message);
event._status = {sent: true};
if (res) {
this.respond(res, event);
}
}; };