One-line check if socket is in given room? - node.js

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

Related

Custom room Socket.io NodeJS

I am making an application with sockets and the need arises to broadcast information, but only to people who are inside a room.
This is my code from the server.ts
// Dependencies
import express from 'express'
import http from 'http'
import socket from 'socket.io';
import {connect, disconnect, orderChanged} from './sockets/socket';
import {config} from 'dotenv';
config ();
// Main class
export default class server {
_private static instance: server
public app: express.Application
public port: number
http: http.Server private
public io: socket.Server
// Initialize variables and methods
// Singleton pattern implementation
private constructor () {
this.app = express ()
this.port = Number (process.env.SRV_PORT)
this.http = new http.Server (this.app)
this.io = new socket.Server (this.http, {
cors: {
origin: true,
credentials: true
}
})
this.listenSockets ();
}
// Return the instance running Singleton pattern
public static get instance () {
returns this._instance || (this._instance = new Server ())
}
// Method to start the server
start (callback: any) {
this.http.listen (this.port, callback)
}
private listenSockets (): void {
console.log ('Listening Sockets');
this.io.on ('connection', client => {
console.log ('Connected to room', client.rooms, '-', client.id);
// User disconnected
disconnect (client);
connect (client);
});
}
}
Since node starts, an instance is created in DP Singleton and the socket listener is launched
When an operation happens in the database, anywhere in the app, I send it to call and send information to the front-end which is correctly received by the front-end and does what it has to do. Example url / edit-products
import server from '../core/server';
// Socket broadcast, new information
const __id = String (req.headers.id);
const updatedData = await getNewData (__id);
Server.instance.io.emit ('data changed', updatedData);
The problem is that this information is sent indiscriminately to all users connected to the socket. Now, I have a unique ID that brings multiple users together in a MongoDB model. You could use that ID to broadcast only to users with that ID. There is a logic that implies that if the user connects from Mexico, add it to an Array of people in MongoDB, otherwise it will add it to another MongoDB document, then they are two different IDs.
I would love the room to be that ID.
I saw that I could use the socket's join () method, but that function derives from the connected client, not from the server itself. I try to issue the information like this
// Socket broadcast, new information
const __id = String (req.headers.id);
const updatedData = await getNewData (__id);
Server.instance.io.in (updatedData._id) .emit ('data changed', updatedData);
But at no point did I set up that "ROOM". When the user login, he could add it but I don't know how to create a custom room, he tried something like this
const user = await UserModel.find (_data);
Server.instance.io.join (user.channel._id);
But that function within io does not exist.
It exists this way, but it doesn't work for me
Server.instance.io.on ('user-join', (socket: Socket) => {
console.log (plug);
socket.join (uuid);
});
Server.instance.io.emit ('user join');
What could I do?
.join() is a method on an individual socket. That's how you use it as socket.join(roomName). When the first user joins a room, the room is created automatically and other users can also join it. When the last user leaves the room, the room is removed automatically from the server. So, you join a user's socket to a room - you don't join something to the server.
Similarly, when you tried this:
Server.instance.io.on ('user-join', (socket: Socket) => {
console.log (plug);
socket.join (uuid);
});
That doesn't work because you don't listen for incoming messages from a socket on the server (except for the connection message - which introduces the socket object). You listen for incoming client messages on a socket itself:
Server.instance.io.on ('connection', (socket: Socket) => {
socket.on('user-join', () => {
// you will have to find the room name that goes with this socket
socket.join(someRoomName);
});
});
Also, note that this code:
private listenSockets (): void {
console.log ('Listening Sockets');
this.io.on ('connection', client => {
console.log ('Connected to room', client.rooms, '-', client.id);
// User disconnected
disconnect (client);
connect (client);
});
}
looks problematic. Why would you disconnect a client when they connect? You don't show those functions disconnect() and connect() so it's unclear what they actually do - I would guess they keep track of connected clients somehow. If you're just trying to clean up any state that might have been previously left hanging, then you should be doing something like this:
private listenSockets (): void {
console.log ('Listening Sockets');
this.io.on ('connection', client => {
console.log ('Connected to room', client.rooms, '-', client.id);
client.on('disconnect', () => {
// User disconnected
disconnect(client);
});
// user connected now
connect(client);
});
}
You don't have to worry about inaccurate housekeeping on whether a socket is connected or not. You will always get a disconnect event for a socket when it disconnects. This is for two reasons. For a browser window that closes or a page that the user navigates away from, the browser cleans up all objects associated with that page, including the open socket.io connection. This will always close the socket and cause a disconnect event. Second, socket.io uses ping and pong messages to regularly check if an existing connection is still working. If it's not, it will get disconnected. The client may or may not retry to open a new connection depending upon the circumstance. But, any disfunctional connection (one that isn't respond to ping messages) will get closed by the server and a disconnect event will occur for that too. So, those two circumstances make sure that a disconnect event always happens.
Server.instance.io.emit ('data changed', updatedData); The problem is that this information is sent indiscriminately to all users connected to the socket.
This sends to all users connected to your server and is how it was designed.
To send to a single socket, you would use:
socket.emit(...);
where socket is what you're code calls client, the object you get from the connection event.
To send to all sockets who have joined a room, you would use:
io.in(roomName).emit(...)
where io is the socket.io server instance.
And, there are many, many more variations of .emit() depending upon exactly what you're trying to send to.
Now, I have a unique ID that brings multiple users together in a MongoDB model. You could use that ID to broadcast only to users with that ID. There is a logic that implies that if the user connects from Mexico, add it to an Array of people in MongoDB, otherwise it will add it to another MongoDB document, then they are two different IDs. I would love the room to be that ID.
I don't completely follow what you're trying to do, but it seems like inside your connect(client) function, you could just call client.join(uniqueIDForMultipleUsers) and that would create a room with this uniqueID and add this client to that room. In the future, you can send to everyone in that room with io.in(uniqueIDForMultipleUsers).emit(...).
But at no point did I set up that "ROOM". When the user login, he could add it but I don't know how to create a custom room, he tried something like this
You don't create rooms manually. You just use socket.join(roomName) and the socket.io infrastructure automatically creates the room if it doesn't already exist. Similarly when the last socket in a room either leaves the room or disconnects, the room is automatically removed. So you just don't have to manage the room creation or deletion yourself. In fact, a room object is not something you ever deal with directly - it's a housekeeping item inside of the socket.io server that contains a list of sockets that are currently in the room. A socket can be in as many rooms as it wants to be. You use these on the server:
socket.join(roomName); // add a client's socket to a room
socket.leave(roomName); // remove a client's socket from a room
io.in(roomName).emit(...); // broadcast a message to every socket in a room
What is sometimes a bit confusing about the above logic is that socket.join() and socket.leave() are socket methods, but they actually modify a data structure in the server (where the list of rooms/sockets are kept). For whatever reason, that's just how they chose to originally design the API. Logically, it's more like io.join(socket, roomName) since it's modifying something on the server. But, since the socket knows the server object it's part of, they can leave that off and just do socket.join(roomName).

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.

Rooms are not removed after everyone leaves

As far i read from the doc
that Rooms are left automatically upon disconnection and they are automatically removed when everyone leaves. But this is not the case of my actual code:
io.on('connection', function(socket) {
socket.join(MainRoom);
io.sockets.adapter.rooms[socket.id].owner = socket.username;
//send the list of available rooms on connection
socket.to(MainRoom).emit('updateList',io.sockets.adapter.rooms);
socket.on('getUpdateList',function() {
io.to(MainRoom).emit('updateList',io.sockets.adapter.rooms);
});
socket.on('msg', function(msg) {
io.to(MainRoom).emit('msgFront',msg);
});
socket.on('disconnect', function() {
console.log('leaving '+socket.id);
io.to(MainRoom).emit('updateList',io.sockets.adapter.rooms);
});
});
Notice that I'm using a MainRoom where all client are forced to join it just to make sure that everyone can talk to each other.
By default 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.
My problem is that after closing/refreshing the browser tab, all previously joined rooms are still there, and the number of rooms is incremented(on connection the socket join new rooms automatically..)
Anyone can explain this behavior ?
Solved:
The problem is that i extended the rooms object bu adding owner attribute :
io.sockets.adapter.rooms[socket.id].owner = socket.username;
So that the extended room can't be removed. the solution that i found is to store owner attribute outside within an associative array, that's it.

How to get room's clients list in socket.io 1.0

I can get room's clients list with this code in socket.io 0.9.
io.sockets.clients(roomName)
How can I do this in socket.io 1.0?
Consider this rather more complete answer linked in a comment above on the question: https://stackoverflow.com/a/24425207/1449799
The clients in a room can be found at
io.nsps[yourNamespace].adapter.rooms[roomName]
This is an associative array with keys that are socket ids. In our case, we wanted to know the number of clients in a room, so we did Object.keys(io.nsps[yourNamespace].adapter.rooms[roomName]).length
In case you haven't seen/used namespaces (like this guy[me]), you can learn about them here http://socket.io/docs/rooms-and-namespaces/ (importantly: the default namespace is '/')
Updated (esp. for #Zettam):
checkout this repo to see this working: https://github.com/thegreatmichael/socket-io-clients
Using #ryan_Hdot link, I made a small temporary function in my code, which avoids maintaining a patch. Here it is :
function getClient(roomId) {
var res = [],
room = io.sockets.adapter.rooms[roomId];
if (room) {
for (var id in room) {
res.push(io.sockets.adapter.nsp.connected[id]);
}
}
return res;
}
If using a namespace :
function getClient (ns, id) {
return io.nsps[ns].adapter.rooms[id]
}
Which I use as a temporary fix for io.sockets.clients(roomId) which becomes findClientsSocketByRoomId(roomId).
EDIT :
Most of the time it is worth considering avoiding using this method if possible.
What I do now is that I usually put a client in it's own room (ie. in a room whose name is it's clientID). I found the code more readable that way, and I don't have to rely on this workaround anymore.
Also, I haven't tested this with a Redis adapter.
If you have to, also see this related question if you are using namespaces.
For those of you using namespaces I made a function too that can handle different namespaces. It's quite the same as the answer of nha.
function get_users_by_room(nsp, room) {
var users = []
for (var id in io.of(nsp).adapter.rooms[room]) {
users.push(io.of(nsp).adapter.nsp.connected[id]);
};
return users;
};
As of at least 1.4.5 nha’s method doesn’t work anymore either, and there is still no public api for getting clients in a room. Here is what works for me.
io.sockets.adapter.rooms[roomId] returns an object that has two properties, sockets, and length. The first is another object that has socketId’s for keys, and boolean’s as the values:
Room {
sockets:
{ '/#vQh0q0gVKgtLGIQGAAAB': true,
'/#p9Z7l6UeYwhBQkdoAAAD': true },
length: 2 }
So my code to get clients looks like this:
var sioRoom = io.sockets.adapter.rooms[roomId];
if( sioRoom ) {
Object.keys(sioRoom.sockets).forEach( function(socketId){
console.log("sioRoom client socket Id: " + socketId );
});
}
You can see this github pull request for discussion on the topic, however, it seems as though that functionality has been stripped from the 1.0 pre release candidate for SocketIO.

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