Sending messages to multiple rooms using Socket.io? - node.js

Is it possible to send messages to multiple rooms using socket.io?
Sending to 1 room:
io.sockets.in(room).emit("id", {})
Sending to N rooms:
io.sockets.in(room1, room2, roomN).emit("id", {})

Yes, it's possible to emit to multiple rooms altogether. From the tests:
socket.on('emit', function(room){
sio.in('woot').in('test').emit('a');
sio.in('third').emit('b');
});
That's because when you use to or in you're appending the room to the list of rooms to be targeted. From the source code (lib/socket.js):
Socket.prototype.to =
Socket.prototype.in = function(name){
this._rooms = this._rooms || [];
if (!~this._rooms.indexOf(name)) this._rooms.push(name);
return this;
};

The sockets.in method only accepts one room as an argument, so to broadcast to multiple rooms you would have to reset the room, in between emissions. Something like this should work:
['room1', 'room2', 'room3'].forEach(function(room){
io.sockets.in(room).emit("id", {});
});

Updated as of Socket.IO v2.0.3
// sending to all clients in 'game1' and/or in 'game2' room, except sender
socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
https://socket.io/docs/emit-cheatsheet/

UPDATE:
As of Socket.IO v4.0.0, emission to multiple rooms is now possible. Per the docs, this is how it should be done with the modified to() method:
io.to(["room1", "room2", "room3"]).emit(/* ... */);
socket.to(["room1", "room2", "room3"]).emit(/* ... */);
Link to the docs themselves: https://socket.io/blog/socket-io-4-release/

Related

Socket.io not sending to one socket in a room

I'm using socket.io to handle some of the server-client communication and matchmaking for a simple multiplayer game.
(for now) im automatically joining 2 players together by adding them into a socket.io room. When a room has 2 players in it I emit a "startGame" event to the room using socket.to(ROOM).emit(EVENT, EVENT_MSG) after doing a check
server side nodeJS:
game_start_state = checkRooms(socket, freeRooms);
if (game_start_state){
console.log("told room", game_start_state, "to start their game!")
socket.to(game_start_state).emit("startGame", game_start_state);
}
but so far only the first socket that gets connected to the room receives the "startGame" event message, I've looked around and havent seen anyone else with the same problem. Below is the code that is fired after the client emits an event saying it wants to join a room.
server side nodeJS:
function checkRooms(socket, roomArray) {
// auto-matchmaking logic
if(!roomArray || !roomArray.length){
//if there is no room with space create a new one
const room = uuid();
r_list.push(room); // r_list is just an array used to keep track of opened rooms for displaying to the user through some html
freeRooms.push(room); // freeRooms is an array with rooms with just 1 socket connected
joinRoom(socket, room);
return(null);
} else {
// if there is a room with a space, try to connect the client to it
const room = freeRooms[0];
console.log(socket.id, "wants to join", room);
// connect client to rooms
joinRoom(socket, room);
// room is now full so start the game
freeRooms.pop(room);
return(room);
}
}
because for now there is only auto matchmaking, there will only be 1 room in the freeRooms array so I'm not worries about this.
Does anyone know where I could be messing up? Can provide more code examples if necessary.
socket.to(room).emit(...)
sends to every member of the room EXCEPT the referenced socket. You can see that documented here.
If you want to send to everyone in the room, then use:
io.to(room).emit(...)

Socket io. Disable automatical joining a room identified by socket's id

In docs it is said: "Each Socket in Socket.IO is identified by a random, unguessable, unique identifier Socket#id. For your convenience, each socket automatically joins a room identified by this id."
I am wondering if there is an option to disable such feature.
My solution was:
io.on('connection', function (socket) {
leaveDefRoom(socket);
[...]
}
function leaveDefRoom(socket){
var room = socket.adapter.rooms;
for (var key in room){
if (key.charAt(0) == '/') {
socket.leave(key);
return;
}
}
}
In socket.io. Every time you emit event. socket.io send the event to the client in this room. If you remove user from the room, you cannot send this user messages. Even broadcast will not work.
Anyway, if you really want, you can leave this room, like any other room:
You can change socket.js file and disable this option:
https://github.com/socketio/socket.io/blob/master/lib/socket.js#L289
Socket.prototype.onconnect = function(){
debug('socket connected - writing packet');
this.nsp.connected[this.id] = this;
// You have to remove this line below:
this.join(this.id);
this.packet({ type: parser.CONNECT });
};

how to create a room based on 2 users in socket.io?

My goal is to create a one to one chat based on two different users. The only way that I could think of is to use socket.io rooms
But the problem right now is that how do i create unique room?
For example
socket.on('join', function(room) {
socket.join(room);
});
Do i need to emit the room from the client, if so , how do I make it unique. Imagine there are thousands of users.
The chat application, is similar like facebook chat application. Where you can chat one on one.
Do i need redis or mongodb to store the room? Anyone of you who have experience using socket.io in scale, please do share your opinion
Thanks!
A room always will be unique, when you do socket.join('roomname') if the room not exist it will created and this socket will join it, if exist the socket just will join it.
So if you want that client A join in the room where is client B for example, from client A you can send a event like:
socket.emit('joinroom', roomname);
On sever:
socket.on('joinroom', function(data){
socket.join(data)
})
Anyway when a socket connect , it create and join his own room automatically , the name of this room will be the id of the socket, so i think is not neccessary create new rooms for a chat based on two different users.
Everything that you need is link the socket id with a permanent property of the user.
EDIT:
I leave you here a simple chat app example where you can open multiple conversations:
server.js: https://gist.github.com/pacmanmulet/b30d26b9e932316f54b2
index.html: https://gist.github.com/pacmanmulet/6481791089effb79f25f
You can test it here :https://chat-socket-io-example.herokuapp.com/
I did not use rooms, it have more sense when you want emit to a group of sockets, not to a only one.
Hope you can understand better my idea with that.
you need to store the room number somewhere(any database).You have to do this because you have to keep your server stateless.
Let us assume that you are creating a private chat only for two people.The room number has to be unique. so one approach is to use the user's email id and join them to create a new string and emit it back to the users.this is tricky because we don't know the order in which the strings are joined. so we join them by a string not used in normal email name(eg :'"#!#!#!!#!#!#!').we can split it on the server side and compare emit the results.
The actual message body will be
{
room:a#gmail.comb#gmail.com,
from:a,
message:'hi buddy how are you?'
}
CLIENT side code
const roomName = a#gmail.com+'#!#!2!#!#"+b#gmail.com
socket.emit('room', { room: roomName });
this.socket.on('joined', data => {
console.log('i have joined', data.room)
store the room name room: data.room })
})
socket.on('chat',data=>console.log(`received chat from ${data.from} from the message room ${data.room}`)
used '#!#!2#!#' just because we can separate them on the server side and check if the room already exists.
SERVER side code
const room =[]//this variable you have store in database and retrieve it when needed.
socket.on('room',data=>{
if(room.length!=0){
const temp = data.room.split('!#!#2#!#!').reverse().join('!#!#2#!#!');
if(room.includes(temp)){
socket.join(temp)
console.log('joined room',temp)
socket.emit('joined',{room:temp})
console.log(room);
} else if(room.includes(data.room)){
socket.join(data.room)
console.log('joined room', data.room)
socket.emit('joined', { room: data.room})
console.log(room);
}
}else{
socket.join(data.room);
room.push(data.room)
console.log('joined room',data.room);
socket.emit('joined', { room: data.room })
console.log(room);
}
})
I tried to do a minimal example of where you can only be in one room at a time (apart from your default socket.id room) and only other sockets in the same room as you will receive your messages. Also you can change rooms.
The basic premise is, if socket A is in room 'xyz' and so is socket B, on the server side you can do socket.to('xyz').emit('message', 'hello') for socket A, and socket B will receive the message, but another connected socket C which isn't in room 'xyz' won't.
You can create room at server runtime, I used both users id as room id, Ex : '100-200' for demo purpose. May be you can use some more complex approach.

One-line check if socket is in given room?

I'm using Node.js with socket.io for a multiplayer card game, and there are game rooms which players can join.
For joining a room, I simply use:
io.sockets.on('connection', function (socket) {
socket.on('joinRoom', function (gid) {
//gid is game ID - create room name based on this and join the room
var room = 'game'+gid;
socket.join(room);
});
});
My question is, what is the quickest way to check if a socket is connected to a certain room? I know I could get all sockets in that room in an array and then check whether the target socket is in the array, but I'm guessing there should be a more basic syntax for this. What I'm looking for (in pseudo-code) would be
if(socket with ID "z8b7dbf98i" is in room "game10")
//do something
For the documentation, socket.io doesn't seem to have any simple way to do that. You really need to check if the client is in the room array, or the opposite: if the room is in the client array.
This can be done with an oneliner using indexOf:
if(socket.rooms.indexOf(room) >= 0)
Or the opposite search:
if(io.sockets.manager.rooms['/' + room].indexOf(socket.id) >= 0)
2021 response:
This was such a headache for me, but currently in version 4.0.2 of Socket IO, socket.rooms is a Javascript Set, so you can check if the given socket is in the room using .has():
if (socket.rooms.has('abc')) {
// Do something if socket is in room 'abc'
} else {
// Do something if socket is NOT in room 'abc'
}
If you need to check if the user is not in the room, you can simply use !:
if (!socket.rooms.has('abc')) {
// Do something if socket is NOT in room 'abc'
}
You can simply check like this
io.sockets.adapter.rooms['roomId']
This returns you a object with sId e.g.
{"1sEAEIZeuMgdhF35AAAA":true}
Updates specific to versions:
3.0+:
io.sockets.adapter.get('roomId')
1.4:
io.sockets.adapter.rooms['roomId']
1.3.x:
io.sockets.adapter.rooms['roomId'];
1.0.x to 1.2.x:
io.adapter.rooms['roomId'];
**Update:**
However one can check socket Id is in a given room or not with one-line as mentioned above only if server architecture has a single node server/single node process.
If you are using multi-node server, i.e. separate node process with load balanced.
Point to note here is, that the sockets are only registered on the process that they first connected to. So, you need to use socket.io-redis to connect all your nodes together to sync events, and what you can do to maintain list of socket Ids across multi-node is broadcast an event each time a client connects/disconnects, so that each node updates & maintains the real-time list of all the clients/socket Ids.
Background/Details:
The redis adapter extends the base adapter, but it only overrides/adds the following properties:
clients
broadcast
add
del
delAll
With the following code:
io.sockets.adapter.rooms["roomId"]; //Any of the above examples specific to versions mentioned above
you are querying the rooms property on socket.io-redis adapter. This wasn't overridden by the redis adapter, so you're actually querying the base adapter, which only knows about rooms/clients in the current process.
Why didn't the redis adapter override the rooms property? Might be because it would have to query the redis database instance to construct an object containing all rooms and connections on every access of this property. Not a good idea?
So as of this writing answer, you'll have to add that functionality to the adapter itself with a method like this:
/**
* One-Line code/property to check if the socket id is in given room.
*
* #param {String} room id
* #param {Function} callback (optional)
* #api public
*/
Redis.prototype.isSidExistsInRoom = function(room, fn){ ... }
where you will hit the redis database instance.
This should be part of the base adapter interface for all other adapters to implement. It's a common problem everyone will face one day, when they scale their servers ;)
P.S. Just a hint on another approach is to use the customRequest/customHook methods in socket.io-redis 3.1.0.
**Update with ver 5.2.0: (relevant multi node servers)**
Now redis adapter gives you rooms across processes/nodes as of 5.2.0
Source: [RedisAdapter#clients(rooms:Array, fn:Function)][5]
> Returns the list of client IDs connected to rooms across all nodes. See [Namespace#clients(fn:Function)][6]
io.of('/').adapter.clients((err, clients) => {
console.log(clients); // an array containing all connected socket ids
});
io.of('/').adapter.clients(['room1', 'room2'], (err, clients) => {
console.log(clients); // an array containing socket ids in 'room1' and/or 'room2'
});
// you can also use
io.in('room3').clients((err, clients) => {
console.log(clients); // an array containing socket ids in 'room3'
});
Happy Coding!
using "socket.io": "^2.3.0" this worked for me
if (!(io.sockets.adapter.rooms[room] && io.sockets.adapter.rooms[room].sockets[socket.id]))
// Join room or do any stuff
socket.join('product_' + product_id);
For current socket.io (1.0+ I suppose) structure of io object was changed, therefore you can now find out is there a user with given socketid in the room with given socket roomid by:
if(io.sockets.adapter.rooms[roomid][socketid])
This seems to have changed quite a lot with versions of socket.io, but as of this writing (version 1.7.2), this looks like it's stored in socket.rooms. It's an object that looks like this:
{
room_name: 'room_name',
second_room_name: 'second_room_name',
...
}
Before your socket has joined any rooms, as documented, you'll see that the socket is already in a room with it's own id, so socket.rooms will look something like:
{ PxtiIs22S7GhWPTSAAAA: 'PxtiIs22S7GhWPTSAAAA'}
That means you can check if a socket is in a room something like this:
io.on('connection', function(socket){
if(socket.rooms[myRoomName]){
// in the room
}else{
// not in the room
}
});
now socket.rooms looks like that:
{
"room1":"room1",
"room2":"room2",
...
"room100":"room100"
}
way to check if a socket is connected to a certain room:
if(socket.rooms[roomID]) return true;
answers from link
https://github.com/socketio/socket.io/issues/2890
If you still need it you can do next:
socket.room = "someRoom";
and then simply check it:
if (socket.room !== undefined){
socket.leave(socket.room);
}
"socket.io": "^4.4.1"
socket.rooms.has('roomName')worked for me.
return true if exist other wise false

socket.io get rooms which socket is currently in

Is it possible to get rooms which socket is currently in, without calling
io.sockets.clients(roomName)
for every room name and looking for this socket in results
In socket.io version 1+ the syntax is:
socket.rooms
Cross-compatible way
var rooms = Object.keys(io.sockets.adapter.sids[socket.id]);
// returns [socket.id, 'room-x'] or [socket.id, 'room-1', 'room-2', ..., 'room-x']
Update: Socket.io 3.0 Released
With 3.x Socket.rooms is Set now, which means that values in the rooms may only occur once.
Structure example: Set(4) {"<socket ID>", "room1", "room2", "room3"}
io.on("connect", (socket) => {
console.log(socket.rooms); // Set { <socket.id> }
socket.join("room1");
console.log(socket.rooms); // Set { <socket.id>, "room1" }
});
To check for specific room:
socket.rooms.has("room1"); // true
More about Set and available methods: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
Migration Docs: https://socket.io/docs/migrating-from-2-x-to-3-0/
From the Socket.IO Room doc:
io.sockets.manager.roomClients[socket.id]
When using a non-default adapter, such as socket.io-redis, socket.rooms doesn't seem to do the trick. The way I managed to get the rooms for a specific client without looping was to use io.sockets.adapter.sids[socket.id], which returns the rooms as an object.
{ 'R-ZRgSf7h4wfPatcAAAC': true, ROOM: true, ROOM_2: true }
Note that this doesn't list sockets on other processes, though!
socket.io v1.3.7, socket.io-redis 1.0.0
Version 1.7.3, socket.rooms contains socket.id, so remove it and get the list of rooms:
Object.keys(socket.rooms).filter(item => item!=socket.id);
In other version, you can print the socket and find the rooms.
Socket.io v2.1.1
So make sure you aren't accessing the sockets rooms in the disconnect event like I was, as they have already left the rooms by the time that event is triggered. If you want to do that try it in the disconnecting event - https://github.com/socketio/socket.io/pull/2332/files
Then you can use any of the following:
Object.keys(socket.adapter.rooms)
Object.keys(socket.adapter.sids)
Object.keys(socket.rooms)
Version 2.0.3
io.sockets.sockets[yourSocketID].rooms
That equal with
socket.rooms
Being sure that socket is in only one room at a time, my solution was:
var currentRoom = Object.keys(io.sockets.adapter.sids[socket.id]).filter(item => item!=socket.id);
socket.io 1.7.3 +
var currentRoom = socket.rooms[Object.keys(socket.rooms)[0]];//returns name of room
You can save room in socket itself when it joins a room
// join room
socket.join(room);
// update socket's rooms
if (socket.rooms) {
socket.rooms.push(room);
} else {
socket.rooms = [room];
}
Later you can retrieve all rooms that the socket is in by simply
socket.rooms
From the Server API documentation:
socket.rooms (object)
A hash of strings identifying the rooms this client is in, indexed by room name.
https://socket.io/docs/server-api/#socket-rooms
With socket.rooms you will get a set of socketId and its rooms.
So you can convert it into array and then slice it to get only the rooms like this:
[...socket.rooms].slice(1, );
And then we can iterate through that array or access any room, for example:
[...socket.rooms].slice(1, )[1] // to get first room

Resources