Is it possible to make socket.io broadcast to all users of a namespace who are in both room A and room B but not those who are just in room A or room B?
If not, how would I go about implementing this myself? Is there a way to retrieve all users in a namespace who are in a given room?
I am working with socket.io 1.0 in node
Edit:
If there is no native method, how would I go about to create my own syntax such as:
socket.broadcast.in('room1').in('room2').emit(...)
?
info. WebSocket is a communication protocol which provides a full-duplex and low-latency channel between the server and the browser. More information can be found here.
Joining and leaving In that case, a union is performed: every socket that is at least in one of the rooms will get the event once (even if the socket is in two or more rooms). In that case, every socket in the room excluding the sender will get the event. To leave a channel you call leave in the same fashion as join .
socket.io rooms are a lightweight data structure. They are simply an array of connections that are associated with that room. You can have as many as you want (within normal memory usage limits). There is no heavyweight thing that makes a room expensive in terms of resources.
You can look up all the users of a room using (ref How to update socket object for all clients in room? (socket.io) )
var clients = io.sockets.adapter.room["Room Name"]
So given two arrays for your 2 rooms' roster list, you can compute the intersection using something like the answer here (ref: Simplest code for array intersection in javascript)
And finally you can take that list of users in both rooms and emit events using (ref: How to update socket object for all clients in room? (socket.io) )
//this is the socket of each client in the room.
var clientSocket = io.sockets.connected[clientId];
//you can do whatever you need with this
clientSocket.emit('new event', "Updates");
The alternate ofcourse is to have hidden rooms, where you maintain a combination of all rooms, and add users to those rooms behind the scenes, and then you are able to just simply emit to those hidden rooms. But that suffers from an exponential growth problem.
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')
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With