| |
| /** |
| * Module dependencies. |
| */ |
| |
| var Socket = require('./socket'); |
| var Emitter = require('events').EventEmitter; |
| var parser = require('socket.io-parser'); |
| var hasBin = require('has-binary2'); |
| var debug = require('debug')('socket.io:namespace'); |
| |
| /** |
| * Module exports. |
| */ |
| |
| module.exports = exports = Namespace; |
| |
| /** |
| * Blacklisted events. |
| */ |
| |
| exports.events = [ |
| 'connect', // for symmetry with client |
| 'connection', |
| 'newListener' |
| ]; |
| |
| /** |
| * Flags. |
| */ |
| |
| exports.flags = [ |
| 'json', |
| 'volatile', |
| 'local' |
| ]; |
| |
| /** |
| * `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.rooms = []; |
| this.flags = {}; |
| 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[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){ |
| if (this.server.eio && this.name === '/') { |
| debug('removing initial packet'); |
| delete this.server.eio.initialPacket; |
| } |
| 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){ |
| 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); |
| return this; |
| } |
| // set up packet object |
| var args = Array.prototype.slice.call(arguments); |
| var packet = { |
| type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT, |
| data: args |
| }; |
| |
| if ('function' == typeof args[args.length - 1]) { |
| throw new Error('Callbacks are not supported when broadcasting'); |
| } |
| |
| var rooms = this.rooms.slice(0); |
| var flags = Object.assign({}, this.flags); |
| |
| // reset flags |
| this.rooms = []; |
| this.flags = {}; |
| |
| this.adapter.broadcast(packet, { |
| rooms: rooms, |
| flags: 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); |
| // reset rooms for scenario: |
| // .in('room').clients() (GH-1978) |
| 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.compress = compress; |
| return this; |
| }; |
| |
| /** |
| * Sets the binary flag |
| * |
| * @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false` |
| * @return {Socket} self |
| * @api public |
| */ |
| |
| Namespace.prototype.binary = function (binary) { |
| this.flags.binary = binary; |
| return this; |
| }; |