There is no in-built way to do this. So first let's look up how the broadcast works:
https://github.com/Automattic/socket.io/blob/master/lib/namespace.js 206...221-224...230
this.adapter.broadcast(packet, {
rooms: this.rooms,
flags: this.flags
});
Now we know every broadcast creates a bunch of temp objects, indexOf lookups, arguments slices... And then calls the broadcast method of the adapter. Lets take a look at that one:
https://github.com/Automattic/socket.io-adapter/blob/master/index.js 111-151
Now we are creating even more temp objects and loop through all clients in the rooms or all clients if no room was selected. The loop happens in the encode callback. That method can be found here:
https://github.com/socketio/socket.io-parser/blob/master/index.js
But what if we are not sending our packets via broadcast
but to each client separately after looping through the rooms and finding clients that exist both in room A and room B?
socket.emit
is defined here: https://github.com/Automattic/socket.io/blob/master/lib/socket.js
Which brings us to the packet
method of the client.js
:
https://github.com/Automattic/socket.io/blob/master/lib/client.js
Each directly emitted packet will be separately encoded, which again, is expensive. Because we are sending the exact same packet to all users.
To answer your question:
Either change the socket.io adapter class and modify the broadcast method, add your own methods to the prototype or roll your own adapter by inheriting from the adapter class). (var io = require('socket.io')(server, { adapter: yourCustomAdapter });
)
Or overwrite the join
and leave
methods of the socket.js
. Which is rather convenient considering that those methods are not called very often and you don't have the hassle of editing through multiple files.
Socket.prototype.join = (function() {
// the original join method
var oldJoin = Socket.prototype.join;
return function(room, fn) {
// join the room as usual
oldJoin.call(this, room, fn);
// if we join A and are alreadymember of B, we can join C
if(room === "A" && ~this.rooms.indexOf("B")) {
this.join("C");
} else if(room === "B" && ~this.rooms.indexOf("A")) {
this.join("C");
}
};
})();
Socket.prototype.leave = (function() {
// the original leave method
var oldLeave = Socket.prototype.leave;
return function(room, fn) {
// leave the room as usual
oldLeave.call(this, room, fn);
if(room === "A" || room === "B") {
this.leave("C");
}
};
})();
And then broadcast to C
if you want to broadcast to all users in A
and B
.
This is just an example code, you could further improve this by not hard coding the roomnames but using an array or object instead to loop over possible room combinations.
As custom Adapter to make socket.broadcast.in("A").in("B").emit()
work:
var Adapter = require('socket.io-adapter');
module.exports = CustomAdapter;
function CustomAdapter(nsp) {
Adapter.call(this, nsp);
};
CustomAdapter.prototype = Object.create(Adapter.prototype);
CustomAdapter.prototype.constructor = CustomAdapter;
CustomAdapter.prototype.broadcast = function(packet, opts){
var rooms = opts.rooms || [];
var except = opts.except || [];
var flags = opts.flags || {};
var packetOpts = {
preEncoded: true,
volatile: flags.volatile,
compress: flags.compress
};
var ids = {};
var self = this;
var socket;
packet.nsp = this.nsp.name;
this.encoder.encode(packet, function(encodedPackets) {
if (rooms.length) {
for (var i = 0; i < rooms.length; i++) {
var room = self.rooms[rooms[i]];
if (!room) continue;
for (var id in room) {
if (room.hasOwnProperty(id)) {
if (~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) {
ids[id] = ids[id] || 0;
if(++ids[id] === rooms.length){
socket.packet(encodedPackets, packetOpts);
}
}
}
}
}
} else {
for (var id in self.sids) {
if (self.sids.hasOwnProperty(id)) {
if (~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) socket.packet(encodedPackets, packetOpts);
}
}
}
});
};
And in your app file:
var io = require('socket.io')(server, {
adapter: require('./CustomAdapter')
});