Refactor server for flexibility.

Esse commit está contido em:
Daniel Howard
2014-10-09 14:26:31 -07:00
commit 5805778a17
14 arquivos alterados com 640 adições e 651 exclusões
+1 -1
Ver Arquivo
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<title>Ajax IM</title>
<script src="js/jquery-1.11.0.js" type="text/javascript"></script>
<script src="js/jquery-1.11.1.js" type="text/javascript"></script>
<script src="js/md5.js" type="text/javascript"></script>
<script src="js/store.js" type="text/javascript"></script>
<script src="js/dateformat.js" type="text/javascript"></script>
+46 -44
Ver Arquivo
@@ -55,7 +55,7 @@ AjaxIM = function(options, actions) {
}, actions);
if (!store.get('sessionid')) {
store.set('sessionid', uid(40));
store.set('sessionid', AjaxIM.uid(40));
}
// If Socket.IO is available, create a socket
@@ -63,24 +63,26 @@ AjaxIM = function(options, actions) {
$.getScript(this.settings.pollServer+'/socket.io/socket.io.js', function(){
self.socket = io(self.settings.pollServer);
self.socket.on('client', function(event) {
event = $.extend(true, {}, event);
self.dispatchEvent(event);
event = $.extend(true, {}, event);
self.dispatchEvent(event);
});
var event = {type: 'hello', from: store.get('user'), sessionID: store.get('sessionid')};
self.sendEvent(event);
var event = {type: 'hello', from: store.get('user')};
self.sendEvent(event, function() {}, function() {});
});
// We load the theme dynamically based on the passed
// settings. If the theme is set to false, we assume
// that the user is going to load it himself.
this.themeLoaded = false;
if(this.settings.theme) {
if(typeof document.createStyleSheet == 'function')
if (this.settings.theme) {
if(typeof document.createStyleSheet == 'function') {
document.createStyleSheet(this.settings.theme + '/theme.css');
else
} else {
$('body').append('<link rel="stylesheet" href="' +
this.settings.theme + '/theme.css" />');
$('<div>').appendTo('body').load(this.settings.theme + '/theme.html #imjs-bar, .imjs-tooltip',
}
$('<div>').appendTo('body').load(this.settings.theme +
'/theme.html #imjs-bar, .imjs-tooltip',
function() {
self.themeLoaded = true;
self.setup();
@@ -115,7 +117,6 @@ AjaxIM = function(options, actions) {
var obj = $(this);
self.send(obj.parents('.imjs-chatbox').data('username'), obj.val());
}
var obj = $(this);
obj.val('');
obj.height(obj.data('height'));
@@ -409,7 +410,7 @@ $.extend(AjaxIM.prototype, {
self.request(
this.actions.listen,
'GET',
{},
{type: 'hello', from: store.get('user'), sessionid: store.get('sessionid')},
function(event) {
if($.isArray(event)) {
$.each(event, function(key, event) {
@@ -471,7 +472,7 @@ $.extend(AjaxIM.prototype, {
},
onMessage: function(event) {
this.incoming(event.from, event.body);
this.incoming(event.from, event.body);
},
onStatus: function(event) {
@@ -969,6 +970,10 @@ $.extend(AjaxIM.prototype, {
});
},
signOff: function() {
this.status('offline', '');
},
// === {{{AjaxIM.}}}**{{{status(s, message)}}}** ===
//
// Sets the user's status and status message. It is possible to not
@@ -1001,7 +1006,7 @@ $.extend(AjaxIM.prototype, {
self.offline = true;
$('.imjs-input').attr('disabled', true);
var event = {type: 'signoff'};
var event = {type: 'signoff', message: message};
this.sendEvent(event, function(result) {
if(result.type == 'success')
$(self).trigger('changeStatusSuccessful', [value, null]);
@@ -1302,8 +1307,8 @@ $.extend(AjaxIM.prototype, {
var tab_pos = tab.position();
if(tab_pos.top >= $('#imjs-bar').height() ||
tab_pos.left < 0 ||
tab_pos.right > $(document).width()) {
tab_pos.left < 0 ||
tab_pos.right > $(document).width()) {
$('.imjs-scroll').css('display', '');
tab.css('display', 'none');
needScrollers = true;
@@ -1356,6 +1361,7 @@ $.extend(AjaxIM.prototype, {
},
sendEvent: function(event, successFunc, failureFunc) {
event.sessionid = store.get('sessionid');
event.id = this.eventId++;
var evt = $.extend({}, event);
evt['_status'] = {
@@ -1402,17 +1408,17 @@ $.extend(AjaxIM.prototype, {
},
dispatchEvent: function(event) {
if (event.id && this.unconfirmedEvents[event.id]) {
event['_status'] = $.extend({}, this.unconfirmedEvents[event.id]['_status'], event['_status']);
delete this.unconfirmedEvents[event.id];
if (event['_status']['sent']) {
event['_status']['successFunc'](event);
} else {
event['_status']['failureFunc'](event);
}
} else {
$(this).trigger(event.type, event);
}
if (event.id && this.unconfirmedEvents[event.id]) {
event['_status'] = $.extend({}, this.unconfirmedEvents[event.id]['_status'], event['_status']);
delete this.unconfirmedEvents[event.id];
if (event['_status']['sent']) {
event['_status']['successFunc'](event);
} else {
event['_status']['failureFunc'](event);
}
} else {
$(this).trigger(event.type, event);
}
},
// === {{{AjaxIM.}}}**{{{request(url, data, successFunc, failureFunc)}}}** ===
@@ -1437,7 +1443,6 @@ $.extend(AjaxIM.prototype, {
var jsonp = (url.substring(0, 1) !== '/');
var success = false;
data['sessionid'] = store.get('sessionid');
$.ajax({
url: url,
data: data,
@@ -1465,8 +1470,7 @@ $.extend(AjaxIM.prototype, {
};
var noopfn = function() {
var noopdone = false;
var event = {type: 'noop'};
event['sessionid'] = store.get('sessionid');
var event = {type: 'noop', from: store.get('user'), sessionid: store.get('sessionid')};
$.ajax({
url: self.actions.noop,
data: event,
@@ -1548,7 +1552,19 @@ AjaxIM.incoming = function(event) {
$(AjaxIM.client).trigger(event.type, event);
};
AjaxIM.eventID = 1;
AjaxIM.uid = function(n){
var chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', nn='';
for(var c=0; c < n; c++){
nn += chars.substr(0|Math.random() * chars.length, 1);
}
return nn;
};
AjaxIM.onObj = function(obj, func) {
return function(evt, event) {
$.proxy(func, obj)(event);
};
};
// === {{{AjaxIM.}}}**{{{l10n}}}** ===
//
@@ -1584,21 +1600,7 @@ AjaxIM.l10n = {
defaultAway: 'I\'m away.'
};
AjaxIM.onObj = function(obj, func) {
return function(evt, event) {
$.proxy(func, obj)(event);
};
};
AjaxIM.debug = true;
function _dbg(msg) {
if(AjaxIM.debug && window.console) console.log(msg);
}
function uid(n){
var chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', nn='';
for(var c=0; c < n; c++){
nn += chars.substr(0|Math.random() * chars.length, 1);
}
return nn;
}
+1 -1
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
-22
Ver Arquivo
@@ -1,22 +0,0 @@
/*!
* Ajax IM
* Ajax Instant Messeneger
* http://ajaxim.com/
*
* Copyright 2010, Joshua Gross
* Licensed under the MIT license.
* [License URL]
*
* Includes:
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* v2.1, Copyright 2002, Paul Johnston
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info
*
* Date Format 1.2.3
* http://blog.stevenlevithan.com/archives/date-time-format
* Copyright 2009 Steven Levithan
* MIT license
*/
(function($) {
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
-2
Ver Arquivo
@@ -1,2 +0,0 @@
})(jQuery || $);
+63 -64
Ver Arquivo
@@ -2,26 +2,27 @@
var express = require('express'),
app = express(),
http = require('http').Server(app),
io = require('socket.io')(http),
// io = require('socket.io')(http), // uncomment to enable Socket.IO
bodyParser = require('body-parser'),
sys = require('sys'),
packages = require('./libs/packages'),
o_ = require('./libs/utils');
o_.merge(global, require('./settings'));
try { o_.merge(global, require('./settings.local')); } catch(e) {}
var reapInterval = (typeof io === 'undefined')? 60 * 1000: -1;
//app.set('env', 'development');
app.use(require('method-override')());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(bodyParser.urlencoded({extended: true}));
var mw = require('./middleware/im')({
maxAge: 60 * 1000,
reapInterval: 60 * 1000,
authentication: require('./libs/authentication/' + AUTH_LIBRARY)
maxAge: 60 * 1000,
reapInterval: reapInterval,
authentication: require('./libs/authentication/' + AUTH_LIBRARY)
});
app.use(mw.session);
var hub = mw.hub;
var store = mw.store;
app.set('root', __dirname);
@@ -29,29 +30,53 @@ if ('development' == app.get('env')) {
app.set('views', __dirname + '/dev/views');
app.set('view engine', 'jade');
app.use(require("morgan")());
app.use(require("morgan")('combined'));
require('./dev/app')('/dev', app);
app.use(express.static(
require('path').join(__dirname, '../client')));
app.use(require('express-error-handler')({dumpExceptions: true, showStack: true}));
}
io.on('connection', function(socket){
socket.on('server', function(event) {
if (event.type == 'hello') {
event.socketio = socket;
hub.get(event, function(err, sess) {
sess.touch();
store.set(event.sessionID, sess);
});
} else {
hub.find(event.from, function(from) {
from.socketio = socket;
from.dispatch(hub, event);
});
}
// Socket.IO handlers
if (typeof io !== 'undefined') {
io.on('connection', function(socket){
var username = null;
socket.on('server', function(event) {
event.reply = function(status) {
if (status) {
this._status = status;
}
delete this.reply;
socket.emit('client', this);
};
var unauthenticated = function() {
event.reply({sent: false, e: 'unauthenticated'});
};
store.get(event, function(event, user) {
if ((event.type == 'hello') && user) {
username = user.data('username');
store.set(username, user);
event.reply({sent: true});
} else if ((event.type != 'hello')) {
store.find('username', event.from, function(from) {
if (from) {
from.dispatch(event);
} else {
unauthenticated();
}
});
} else {
unauthenticated();
}
}, socket);
});
socket.on('disconnect', function() {
if (username) {
store.reap(username);
}
});
});
});
}
http.listen(APP_PORT, APP_HOST, function(){
console.log('Ajax IM server started...');
@@ -60,50 +85,24 @@ http.listen(APP_PORT, APP_HOST, function(){
// Listener endpoint; handled in middleware
app.get('/app/listen', function(){});
app.use('/app/message', function(req, res) {
res.find(req.param('to'), function(to) {
if(to) {
res.message(to, req.event);
} else {
req.event._status = {sent: false, e: 'not online'};
res.jsonp(req.event);
}
});
});
app.use('/app/message/typing', function(req, res) {
if(~packages.TYPING_STATES.indexOf('typing' + req.param('state'))) {
res.find(req.param('to'), function(user) {
if(user) {
req.event.status = 'typing' + req.param('state');
res.message(user, req.event);
} 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);
}
});
} else {
req.event._status = {sent: false, e: 'invalid state'};
res.jsonp(req.event);
}
});
app.use('/app/status', function(req, res) {
if(~packages.STATUSES.indexOf(req.param('status'))) {
res.status(req.event);
} else {
req.event._status = {sent: false, e: 'invalid status'};
res.jsonp(req.event);
}
});
// HTTP handlers
app.use('/app/noop', function(req, res) {
req.event._status = {sent: true};
res.jsonp(req.event);
});
app.use('/app/signoff', function(req, res) {
res.signOff(req.event);
app.use('/app/message', function(req, res) {
res.message();
});
app.use('/app/message/typing', function(req, res) {
res.typing();
});
app.use('/app/status', function(req, res) {
res.status();
});
app.use('/app/signoff', function(req, res) {
res.signOff();
});
+1 -1
Ver Arquivo
@@ -2,7 +2,7 @@ doctype html
html(lang="en")
head
title Ajax IM
script(src='/js/jquery-1.11.0.js', type='text/javascript')
script(src='/js/jquery-1.11.1.js', type='text/javascript')
script(src='/js/md5.js', type='text/javascript')
script(src='/js/store.js', type='text/javascript')
script(src='/js/dateformat.js', type='text/javascript')
+49 -18
Ver Arquivo
@@ -1,22 +1,53 @@
var o_ = require('../../utils');
var User = require('../../../middleware/im/user');
exports.authenticate = function(request, callback, hub) {
// Verify user based on request.
// On failure, redirect user to auth form
var username = request.from;
if (!username) {
username = 'username' + (++hub.uid);
exports.authenticate = function(store, event, callback) {
// remove authentication from event
var sessionid = event.sessionid;
delete event.sessionid;
// find the user
var user = event.from? store.sessions[event.from]: undefined;
// create, validate or reject user
if (user) {
// found the user so check authentication
if (sessionid && (sessionid === user.data('sessionid'))) {
event.from = user.data('username');
callback(event, user);
} else {
event._status = {sent: false, e: 'not authenticated'};
callback(event);
}
} else if (event.type == 'hello') {
store.find('sessionid', sessionid, function(user) {
if (user) {
// relogin as same user
event.from = user.data('username');
callback(event, user);
} else {
// if no username requested, assign user name
if (!event.from) {
event.from = 'username' + (++store.uid);
}
// everybody is your friend!
var friends = o_.values(store.sessions).map(function(friend) {
return friend.data('username');
});
// you're even friends with yourself!
friends.push(event.from);
// create new user
user = new User(store, {
username: event.from,
sessionid: sessionid,
displayname: 'John Smith',
otherinfo: 'any other relevant key/values'
}, friends);
callback(event, user);
}
});
} else {
event._status = {sent: false, e: 'not authenticated'};
callback(event);
}
callback({
username: username,
displayname: 'John Smith',
otherinfo: 'any other relevant key/values'
});
};
exports.friends = function(request, data, callback, hub) {
// Create a friends list based on given user data
callback(o_.values(hub.sessions).map(function(friend) {
return friend.data('username');
}));
};
+55 -40
Ver Arquivo
@@ -1,63 +1,80 @@
var url = require('url'),
Hub = require('./im/hub'),
packages = require('../libs/packages'),
o_ = require('../libs/utils');
module.exports = function setupHub(options) {
options = options || {};
store = new Hub(options);
var store = new Hub(options);
return {hub: store, session: function session(req, res, next) {
return {store: store, session: function session(req, res, next) {
req.sessionStore = store;
req.sessionID = req.param('sessionid');
// create the event object
req.event = o_.extend({}, req.query, req.body, req.params);
o_.deletekey(req.event, 'callback');
o_.deletekey(req.event, '_');
if(url.parse(req.url).pathname.substring(0, 5) !== '/app/') {
next();
return;
}
req.event.reply = function(status) {
if (status) {
this._status = status;
}
delete this.reply;
res.jsonp(this);
};
if(req.sessionID) {
store.get(req, function(err, sess) {
if(err) {
next(err);
var unauthenticated = function() {
req.event.reply({sent: false, e: 'unauthenticated'});
};
// set event handlers to unauthenticated by default
res.message = res.typing = res.status = res.signOff = unauthenticated;
if(url.parse(req.url).pathname.substring(0, 5) === '/app/') {
store.get(req.event, function(event, user) {
if(!user) {
next();
return;
}
if(!sess) {
next(new Error(JSON.stringify({
type: 'error',
error: 'not authenticated'})));
return;
}
sess.touch();
user.touch();
if(url.parse(req.url).pathname === '/app/listen') {
req.connection.setTimeout(5 * 60 * 1000);
sess.listener(res);
store.set(req.sessionID, sess);
user.listener(res);
store.set(user.data('username'), user);
if(msg = sess.message_queue.shift())
sess._send.apply(sess, msg);
if(msg = user.message_queue.shift()) {
user._send.apply(user, msg);
}
} else {
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.event.from = user.data('username');
}
req.session = sess;
res.session = sess;
res.find = store.find.bind(store);
res.message = function(to, event) {
store.message(res, to, event);
res.message = function() {
user.message(req.event);
};
res.status = function(event) {
req.session.status(res, event);
res.typing = function() {
if(~packages.TYPING_STATES.indexOf('typing' + req.event.state)) {
store.find('username', req.event.to, function(to) {
if(to) {
req.event.status = 'typing' + req.event.state;
user.message(req.event);
} else {
// Typing updates do not receive confirmations,
// as they are not important enough.
req.event.reply({sent: true});
}
});
} else {
req.event.reply({sent: false, e: 'invalid state'});
}
};
res.signOff = function(event) {
store.signOff(req.sessionID, res, event);
res.status = function() {
user.status(req.event);
};
res.signOff = function() {
user.signOff(req.event);
};
if(url.parse(req.url).pathname !== '/app/listen') {
@@ -65,9 +82,7 @@ module.exports = function setupHub(options) {
}
});
} else {
next(new Error(JSON.stringify({
type: 'error',
error: 'not authenticated'})));
next();
}
}};
};
+57 -85
Ver Arquivo
@@ -15,7 +15,7 @@ var Hub = module.exports = function Hub(options) {
if(this.reapInterval !== -1) {
setInterval(function(self) {
self.reap(self.maxAge);
self.reapCheck(self.maxAge);
}, this.reapInterval, this);
}
@@ -24,9 +24,10 @@ var Hub = module.exports = function Hub(options) {
var sids = Object.keys(this.sessions), sid, sess;
for(sid in this.sessions) {
sess = this.sessions[sid];
if(sess.data('username') == event.from) {
if(sess.listeners.length)
if (sess.data('username') == event.from) {
if (sess.listeners.length) {
sess.send({type: 'goodbye'});
}
delete this.sessions[sid];
break;
}
@@ -35,66 +36,56 @@ var Hub = module.exports = function Hub(options) {
}, this));
};
Hub.prototype.destroy = function(sid, fn) {
this.set(sid, null, fn);
};
Hub.prototype.reap = function(ms) {
var threshold = +new Date - ms,
sids = Object.keys(this.sessions);
Hub.prototype.reapCheck = function(ms) {
var threshold = +new Date - ms;
var sids = Object.keys(this.sessions);
for(var i = 0, len = sids.length; i < len; ++i) {
var sid = sids[i], sess = this.sessions[sid];
if(sess.lastAccess < threshold) {
var event = {type: 'status', from: sess.data('username'), status: 'offline', message: ''};
this.events.emit('update', event);
delete this.sessions[sid];
sess.close();
var sid = sids[i];
if(this.sessions[sid].lastAccess < threshold) {
this.reap(sid);
}
}
};
Hub.prototype.get = function(req, fn) {
if(this.sessions[req.sessionID]) {
if (!this.sessions[req.sessionID].req) {
this.sessions[req.sessionID].req = req;
}
fn(null, this.sessions[req.sessionID]);
} else {
this.auth.authenticate(req, o_.bind(function(data) {
if(data) {
var session = new User(req, data);
if (req.socketio) {
session.socketio = req.socketio;
}
this.set(req.sessionID, session);
Hub.prototype.reap = function(sid) {
var sess = this.sessions[sid];
console.log('reaping '+sess.data('username'));
var event = {type: 'status', from: sess.data('username'), status: 'offline', message: ''};
this.events.emit('update', event);
delete this.sessions[sid];
sess.close();
};
this.auth.friends(req, data, o_.bind(function(friends) {
var friends_copy = friends.slice();
o_.values(this.sessions).filter(function(friend) {
return ~friends.indexOf(friend.data('username'));
}).forEach(function(friend) {
var username = friend.data('username');
friends_copy[friends_copy.indexOf(username)] =
[username, friend.status()];
}, this);
session._friends(friends_copy);
session.events.addListener('status',
o_.bind(function(value, message) {
var event = {type: 'status', from: session.data('username'), status: value, message: message};
this.events.emit('update', event);
}, this));
this.events.addListener('update',
o_.bind(session.receivedUpdate, session));
this.set(req.sessionID, session);
fn(null, session);
}, this), this);
session.status(null, {status: packages.STATUSES[0], message: ''});
} else {
fn();
Hub.prototype.get = function(event, fn, socket) {
this.auth.authenticate(this, event, o_.bind(function(event, user) {
if (user) {
if (socket) {
user.socketio = socket;
}
}, this), this);
}
if (this.sessions[user.data('username')]) {
fn(event, user);
return;
}
this.set(user.data('username'), user);
var friends = user.friends;
var friends_copy = friends.slice();
o_.values(this.sessions).filter(function(friend) {
return ~friends.indexOf(friend.data('username'));
}).forEach(function(friend) {
var username = friend.data('username');
friends_copy[friends_copy.indexOf(username)] = [username, friend.status()];
}, this);
user._friends(friends_copy);
user.events.addListener('status', o_.bind(function(value, message) {
var event = {type: 'status', from: user.data('username'), status: value, message: message};
this.events.emit('update', event);
}, this));
this.events.addListener('update', o_.bind(user.receivedUpdate, user));
fn(event, user);
} else {
fn(event);
}
}, this));
};
Hub.prototype.set = function(sid, sess, fn) {
@@ -102,11 +93,11 @@ Hub.prototype.set = function(sid, sess, fn) {
fn && fn();
};
Hub.prototype.find = function(username, fn) {
Hub.prototype.find = function(key, value, fn) {
for(var sid in this.sessions) {
var session = this.sessions[sid],
sess_username = session.data('username');
if(sess_username == username) {
var session = this.sessions[sid];
var user_value = session.data(key);
if (user_value == value) {
fn(session);
return;
}
@@ -114,31 +105,12 @@ Hub.prototype.find = function(username, fn) {
fn(false);
};
Hub.prototype.message = function(res, to, event) {
try {
to.send(event);
event._status = {sent: true};
if (res) {
res.jsonp(event);
Hub.prototype.dispatch = function(event) {
this.find('username', event.from, function(from) {
if (from) {
from.dispatch(event);
} else {
this.find(event.from, function(from) {
from.socketio.emit('client', event);
});
event.reply({sent: false, e: 'not authenticated'});
}
} catch(e) {
event._status = {sent: false, e: e.description};
res.jsonp(event);
}
};
Hub.prototype.signOff = function(sid, res, event) {
if (sid in this.sessions) {
event.status = 'offline';
event.message = '';
this.events.emit('update', event);
}
event._status = {sent: true};
if (res) {
res.jsonp(event);
}
});
};
+38 -15
Ver Arquivo
@@ -2,14 +2,13 @@ var events = require('events'),
packages = require('../../libs/packages'),
o_ = require('../../libs/utils');
var User = module.exports = function(req, data) {
this.req = req;
this.id = req.sessionID;
var User = module.exports = function(store, data, friends) {
this.store = store;
this.connection = null;
this.listeners = [];
this.message_queue = [];
this.convos = {};
this._data = data;
this.friends = friends;
this.events = new events.EventEmitter();
this._status = packages.STATUSES[0];
@@ -102,7 +101,6 @@ User.prototype._send = function(type, event, res) {
};
User.prototype.data = function(key, def) {
if(key == 'id') return this.id;
return this._data[key] || this['_' + key] ||
(typeof this[key] != 'function' && this[key]) ||
def || false;
@@ -112,24 +110,49 @@ User.prototype.touch = function() {
this.lastAccess = +new Date;
};
User.prototype.status = function(res, event) {
if(!event)
User.prototype.message = function(event) {
var self = this;
try {
self.store.find('username', event.to, function(to) {
if(to) {
to.send(event);
event.reply({sent: true});
} else {
event.reply({sent: false, e: 'not online'});
}
});
} catch(e) {
event.reply({sent: false, e: e.description});
}
};
User.prototype.status = function(event) {
if (!event) {
return this._status;
}
this._status = event.status;
this._status_message = event.message;
this.events.emit('status', event.status, event.message);
event._status = {sent: true};
if (res) {
res.jsonp(event);
if (event.reply) {
event.reply({sent: true});
}
};
User.prototype.dispatch = function(hub, event) {
User.prototype.signOff = function(event) {
event.status = 'offline';
this.store.events.emit('update', event);
event.reply({sent: true});
};
User.prototype.dispatch = function(event) {
if (event.type == 'message') {
hub.find(event.to, function(to) {
hub.message(null, to, event);
});
this.message(event);
} else if (event.type == 'status') {
this.status(event);
} else if (event.type == 'signoff') {
this.signOff(event);
} else {
event.reply({sent: false, e: 'invalid event type'});
}
};