Files
socket.io/lib/namespace.js
Tal Beja 955e5e0d91 [feature] Add a local flag (#2628)
That new flag will prevent the adapter (redis) from publishing the emit to the pub/sub server.

When several instances of a server receive the same event from a third party (not from a client), each server instance broadcasts the event to all his clients. With the local flag, and the change in the redis adapter, each server instance send the event only to his client, so each client receive only one unique event.
2016-11-24 23:44:52 +01:00

276 lines
5.3 KiB
JavaScript

/**
* 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',
'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.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;
};