rc-web@69: /** rc-web@69: * Module dependencies. rc-web@69: */ rc-web@69: rc-web@69: var Socket = require('./socket') rc-web@69: , EventEmitter = process.EventEmitter rc-web@69: , parser = require('./parser') rc-web@69: , util = require('./util'); rc-web@69: rc-web@69: /** rc-web@69: * Exports the constructor. rc-web@69: */ rc-web@69: rc-web@69: exports = module.exports = SocketNamespace; rc-web@69: rc-web@69: /** rc-web@69: * Constructor. rc-web@69: * rc-web@69: * @api public. rc-web@69: */ rc-web@69: rc-web@69: function SocketNamespace (mgr, name) { rc-web@69: this.manager = mgr; rc-web@69: this.name = name || ''; rc-web@69: this.sockets = {}; rc-web@69: this.auth = false; rc-web@69: this.setFlags(); rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Inherits from EventEmitter. rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.__proto__ = EventEmitter.prototype; rc-web@69: rc-web@69: /** rc-web@69: * Copies emit since we override it. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.$emit = EventEmitter.prototype.emit; rc-web@69: rc-web@69: /** rc-web@69: * Retrieves all clients as Socket instances as an array. rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.clients = function (room) { rc-web@69: var room = this.name + (room !== undefined ? rc-web@69: '/' + room : ''); rc-web@69: rc-web@69: if (!this.manager.rooms[room]) { rc-web@69: return []; rc-web@69: } rc-web@69: rc-web@69: return this.manager.rooms[room].map(function (id) { rc-web@69: return this.socket(id); rc-web@69: }, this); rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Access logger interface. rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.__defineGetter__('log', function () { rc-web@69: return this.manager.log; rc-web@69: }); rc-web@69: rc-web@69: /** rc-web@69: * Access store. rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.__defineGetter__('store', function () { rc-web@69: return this.manager.store; rc-web@69: }); rc-web@69: rc-web@69: /** rc-web@69: * JSON message flag. rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.__defineGetter__('json', function () { rc-web@69: this.flags.json = true; rc-web@69: return this; rc-web@69: }); rc-web@69: rc-web@69: /** rc-web@69: * Volatile message flag. rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.__defineGetter__('volatile', function () { rc-web@69: this.flags.volatile = true; rc-web@69: return this; rc-web@69: }); rc-web@69: rc-web@69: /** rc-web@69: * Overrides the room to relay messages to (flag). rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) { rc-web@69: this.flags.endpoint = this.name + (room ? '/' + room : ''); rc-web@69: return this; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Adds a session id we should prevent relaying messages to (flag). rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.except = function (id) { rc-web@69: this.flags.exceptions.push(id); rc-web@69: return this; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Sets the default flags. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.setFlags = function () { rc-web@69: this.flags = { rc-web@69: endpoint: this.name rc-web@69: , exceptions: [] rc-web@69: }; rc-web@69: return this; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Sends out a packet. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.packet = function (packet) { rc-web@69: packet.endpoint = this.name; rc-web@69: rc-web@69: var store = this.store rc-web@69: , log = this.log rc-web@69: , volatile = this.flags.volatile rc-web@69: , exceptions = this.flags.exceptions rc-web@69: , packet = parser.encodePacket(packet); rc-web@69: rc-web@69: this.manager.onDispatch(this.flags.endpoint, packet, volatile, exceptions); rc-web@69: this.store.publish('dispatch', this.flags.endpoint, packet, volatile, exceptions); rc-web@69: rc-web@69: this.setFlags(); rc-web@69: rc-web@69: return this; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Sends to everyone. rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.send = function (data) { rc-web@69: return this.packet({ rc-web@69: type: this.flags.json ? 'json' : 'message' rc-web@69: , data: data rc-web@69: }); rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Emits to everyone (override). rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.emit = function (name) { rc-web@69: if (name == 'newListener') { rc-web@69: return this.$emit.apply(this, arguments); rc-web@69: } rc-web@69: rc-web@69: return this.packet({ rc-web@69: type: 'event' rc-web@69: , name: name rc-web@69: , args: util.toArray(arguments).slice(1) rc-web@69: }); rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Retrieves or creates a write-only socket for a client, unless specified. rc-web@69: * rc-web@69: * @param {Boolean} whether the socket will be readable when initialized rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.socket = function (sid, readable) { rc-web@69: if (!this.sockets[sid]) { rc-web@69: this.sockets[sid] = new Socket(this.manager, sid, this, readable); rc-web@69: } rc-web@69: rc-web@69: return this.sockets[sid]; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Sets authorization for this namespace. rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.authorization = function (fn) { rc-web@69: this.auth = fn; rc-web@69: return this; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Called when a socket disconnects entirely. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.handleDisconnect = function (sid, reason, raiseOnDisconnect) { rc-web@69: if (this.sockets[sid] && this.sockets[sid].readable) { rc-web@69: if (raiseOnDisconnect) this.sockets[sid].onDisconnect(reason); rc-web@69: delete this.sockets[sid]; rc-web@69: } rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Performs authentication. rc-web@69: * rc-web@69: * @param Object client request data rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.authorize = function (data, fn) { rc-web@69: if (this.auth) { rc-web@69: var self = this; rc-web@69: rc-web@69: this.auth.call(this, data, function (err, authorized) { rc-web@69: self.log.debug('client ' + rc-web@69: (authorized ? '' : 'un') + 'authorized for ' + self.name); rc-web@69: fn(err, authorized); rc-web@69: }); rc-web@69: } else { rc-web@69: this.log.debug('client authorized for ' + this.name); rc-web@69: fn(null, true); rc-web@69: } rc-web@69: rc-web@69: return this; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Handles a packet. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: SocketNamespace.prototype.handlePacket = function (sessid, packet) { rc-web@69: var socket = this.socket(sessid) rc-web@69: , dataAck = packet.ack == 'data' rc-web@69: , manager = this.manager rc-web@69: , self = this; rc-web@69: rc-web@69: function ack () { rc-web@69: self.log.debug('sending data ack packet'); rc-web@69: socket.packet({ rc-web@69: type: 'ack' rc-web@69: , args: util.toArray(arguments) rc-web@69: , ackId: packet.id rc-web@69: }); rc-web@69: }; rc-web@69: rc-web@69: function error (err) { rc-web@69: self.log.warn('handshake error ' + err + ' for ' + self.name); rc-web@69: socket.packet({ type: 'error', reason: err }); rc-web@69: }; rc-web@69: rc-web@69: function connect () { rc-web@69: self.manager.onJoin(sessid, self.name); rc-web@69: self.store.publish('join', sessid, self.name); rc-web@69: rc-web@69: // packet echo rc-web@69: socket.packet({ type: 'connect' }); rc-web@69: rc-web@69: // emit connection event rc-web@69: self.$emit('connection', socket); rc-web@69: }; rc-web@69: rc-web@69: switch (packet.type) { rc-web@69: case 'connect': rc-web@69: if (packet.endpoint == '') { rc-web@69: connect(); rc-web@69: } else { rc-web@69: var handshakeData = manager.handshaken[sessid]; rc-web@69: rc-web@69: this.authorize(handshakeData, function (err, authorized, newData) { rc-web@69: if (err) return error(err); rc-web@69: rc-web@69: if (authorized) { rc-web@69: manager.onHandshake(sessid, newData || handshakeData); rc-web@69: self.store.publish('handshake', sessid, newData || handshakeData); rc-web@69: connect(); rc-web@69: } else { rc-web@69: error('unauthorized'); rc-web@69: } rc-web@69: }); rc-web@69: } rc-web@69: break; rc-web@69: rc-web@69: case 'ack': rc-web@69: if (socket.acks[packet.ackId]) { rc-web@69: socket.acks[packet.ackId].apply(socket, packet.args); rc-web@69: } else { rc-web@69: this.log.info('unknown ack packet'); rc-web@69: } rc-web@69: break; rc-web@69: rc-web@69: case 'event': rc-web@69: // check if the emitted event is not blacklisted rc-web@69: if (-~manager.get('blacklist').indexOf(packet.name)) { rc-web@69: this.log.debug('ignoring blacklisted event `' + packet.name + '`'); rc-web@69: } else { rc-web@69: var params = [packet.name].concat(packet.args); rc-web@69: rc-web@69: if (dataAck) { rc-web@69: params.push(ack); rc-web@69: } rc-web@69: rc-web@69: socket.$emit.apply(socket, params); rc-web@69: } rc-web@69: break; rc-web@69: rc-web@69: case 'disconnect': rc-web@69: this.manager.onLeave(sessid, this.name); rc-web@69: this.store.publish('leave', sessid, this.name); rc-web@69: rc-web@69: socket.$emit('disconnect', packet.reason || 'packet'); rc-web@69: break; rc-web@69: rc-web@69: case 'json': rc-web@69: case 'message': rc-web@69: var params = ['message', packet.data]; rc-web@69: rc-web@69: if (dataAck) rc-web@69: params.push(ack); rc-web@69: rc-web@69: socket.$emit.apply(socket, params); rc-web@69: }; rc-web@69: };