/** * Module dependencies. */ var Socket = require('./socket'); var Emitter = require('events').EventEmitter; var parser = require('socket.io-parser'); var debug = require('debug')('socket.io:namespace'); var hasBin = require('has-binary'); /** * Module exports. */ module.exports = exports = Namespace; /** * Blacklisted events. */ exports.events = [ 'connect', // for symmetry with client 'connection', 'newListener' ]; /** * Flags. */ exports.flags = [ 'json', 'volatile' ]; /** * `EventEmitter#emit` reference. */ var emit = Emitter.prototype.emit; /** * Namespace constructor. * * @param {Server} server instance * @param {Socket} name * @api private */ function Namespace(server, name){ this.name = name; this.server = server; this.sockets = {}; this.connected = {}; this.fns = []; this.ids = 0; this.initAdapter(); } /** * Inherits from `EventEmitter`. */ Namespace.prototype.__proto__ = Emitter.prototype; /** * Apply flags from `Socket`. */ exports.flags.forEach(function(flag){ Object.defineProperty(Namespace.prototype, flag, { get: function() { this.flags = this.flags || {}; this.flags[flag] = true; return this; } }); }); /** * Initializes the `Adapter` for this nsp. * Run upon changing adapter by `Server#adapter` * in addition to the constructor. * * @api private */ Namespace.prototype.initAdapter = function(){ this.adapter = new (this.server.adapter())(this); }; /** * Sets up namespace middleware. * * @return {Namespace} self * @api public */ Namespace.prototype.use = function(fn){ this.fns.push(fn); return this; }; /** * Executes the middleware for an incoming client. * * @param {Socket} socket that will get added * @param {Function} fn last fn call in the middleware * @api private */ Namespace.prototype.run = function(socket, fn){ var fns = this.fns.slice(0); if (!fns.length) return fn(null); function run(i){ fns[i](socket, function(err){ // upon error, short-circuit if (err) return fn(err); // if no middleware left, summon callback if (!fns[i + 1]) return fn(null); // go on to next run(i + 1); }); } run(0); }; /** * Targets a room when emitting. * * @param {String} name * @return {Namespace} self * @api public */ Namespace.prototype.to = Namespace.prototype.in = function(name){ this.rooms = this.rooms || []; if (!~this.rooms.indexOf(name)) this.rooms.push(name); return this; }; /** * Adds a new client. * * @return {Socket} * @api private */ Namespace.prototype.add = function(client, query, fn){ debug('adding socket to nsp %s', this.name); var socket = new Socket(this, client, query); var self = this; this.run(socket, function(err){ process.nextTick(function(){ if ('open' == client.conn.readyState) { if (err) return socket.error(err.data || err.message); // track socket self.sockets[socket.id] = socket; // it's paramount that the internal `onconnect` logic // fires before user-set events to prevent state order // violations (such as a disconnection before the connection // logic is complete) socket.onconnect(); if (fn) fn(); // fire user-set events self.emit('connect', socket); self.emit('connection', socket); } else { debug('next called after client was closed - ignoring socket'); } }); }); return socket; }; /** * Removes a client. Called by each `Socket`. * * @api private */ Namespace.prototype.remove = function(socket){ if (this.sockets.hasOwnProperty(socket.id)) { delete this.sockets[socket.id]; } else { debug('ignoring remove for %s', socket.id); } }; /** * Emits to all clients. * * @return {Namespace} self * @api public */ Namespace.prototype.emit = function(ev){ if (~exports.events.indexOf(ev)) { emit.apply(this, arguments); } else { // set up packet object var args = Array.prototype.slice.call(arguments); var parserType = parser.EVENT; // default if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary var packet = { type: parserType, data: args }; if ('function' == typeof args[args.length - 1]) { throw new Error('Callbacks are not supported when broadcasting'); } this.adapter.broadcast(packet, { rooms: this.rooms, flags: this.flags }); delete this.rooms; delete this.flags; } return this; }; /** * Sends a `message` event to all clients. * * @return {Namespace} self * @api public */ Namespace.prototype.send = Namespace.prototype.write = function(){ var args = Array.prototype.slice.call(arguments); args.unshift('message'); this.emit.apply(this, args); return this; }; /** * Gets a list of clients. * * @return {Namespace} self * @api public */ Namespace.prototype.clients = function(fn){ this.adapter.clients(this.rooms, fn); // delete rooms flag for scenario: // .in('room').clients() (GH-1978) delete this.rooms; return this; }; /** * Sets the compress flag. * * @param {Boolean} compress if `true`, compresses the sending data * @return {Socket} self * @api public */ Namespace.prototype.compress = function(compress){ this.flags = this.flags || {}; this.flags.compress = compress; return this; };