List of connected clients username using socket io - node.js

I've made a chat client with different chat rooms in NodeJS, socketIO and Express. I am trying to display an updated list over connected users for each room.
Is there a way to connect a username to an object so I could see all the usernames when I do:
var users = io.sockets.clients('room')
and then do something like this:
users[0].username
In what other ways can I do this?
Solved:
This is sort of a duplicate, but the solution is not written out very clearly anywhere so I'd thought I write it down here. This is the solution of the post by Andy Hin which was answered by mak. And also the comments in this post.
Just to make things a bit clearer. If you want to store anything on a socket object you can do this:
socket.set('nickname', 'Guest');
sockets also has a get method, so if you want all of the users do:
for (var socketId in io.sockets.sockets) {
io.sockets.sockets[socketId].get('nickname', function(err, nickname) {
console.log(nickname);
});
}
As alessioalex pointed out, the API might change and it is safer to keep track of user by yourself. You can do so this by using the socket id on disconnect.
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function() {
console.log(socket.id + ' disconnected');
//remove user from db
}
});

There are similar questions that will help you with this:
Socket.IO - how do I get a list of connected sockets/clients?
Create a list of Connected Clients using socket.io
My advice is to keep track yourself of the list of connected clients, because you never know when the internal API of Socket.IO may change. So on each connect add the client to an array (or to the database) and on each disconnect remove him.

In socket.io#2.3.0 you can use:
Object.keys(io.sockets).forEach((socketId) => {
const socket = io.sockets[socketId];
})

Related

socket.io broadcast on api call to everyone except "sender"

I'm making an API call from a client to the server, and I don't want to send the message in the websocket as well.
What's the best way for the server to broadcast to all other clients when this happens?
I was thinking maybe to attach the client socketId in the headers, and reading that on the server, and looping over all the connected clients and send them, something like:
Object.keys(io.sockets.sockets).forEach((socketId) => {
if (socketId === socketIdFromHeader) return
socket.broadcast.to(socketId).emit('message', 'my message');
});
I see a couple of problems here:
it feels hacky
If I have a large scale of connected clients, running a separate function for each of them will be expensive.
Any ideas how'd you go about it?
Thanks!
PS. I'm new to Socket.io and websockets
You need to know that your API connection is completely different from your web socket connection. So if you want to send data to all users but the sender, you should store a map from usersId (any identification you have of users which you have access in both socket.io connection and your API) to users socket id.
for example store the data in redis. to get user in io connection your client can send a token in query string, and then you find the user in your database using this token or decode the user If you are using JWT.
io.on('connection', socket => {
let user = await getUserUsingToken(socket.handshake.query.token);
await redis.set(user._id, socket.id);
}
and in your api get socket id from Redis
let sid = await redis.get(user._id);
Object.keys(io.sockets.sockets).forEach((socketId) => {
if (socketId === sid) return
socket.broadcast.to(socketId).emit('message', 'my message');
});
You can store this mapping anywhere you like. I just showed you Redis as an example.

Socket.io send messges to only some users in room

I want to send message in a room using socket.io but i some cases i want to skip some users in room so how can i do that.
Currently i am using below code to send message in group
io.on('connection', function (socket) {
sockets[socket.handshake.query.id]=socket;
console.log("user_connect",socket.handshake.query );
if(socket.handshake.query.user_type=="DRIVER"){
socket.join('orderPool');
}
socket.join('USER_'+socket.handshake.query.id);
socket.on('disconnect', function () {
delete sockets[socket.handshake.query.id];
console.log('user disconnected', socket.handshake.query.id);
});
});
io.sockets.in("ROOMNAME").emit("POOL_EVENT_RECIEVED", data);
One solution is to simultaneously keep another room "<room name>-selected users" with only selected users you want to send the messages. Don't add to this room the users you want to skip.
So, when you send messages to this room, users who weren't added to this room won't receive those messages.
sometimes io disconnected and try to reconnect again but I think it's not your problem.
Note that you can emit to rooms that exists in particular namespace. if you use default namespace '/' so try it:
io.of('/').in("ROOMNAME").emit("POOL_EVENT_RECIEVED", data);
also you can read socket.io emit cheatsheet: https://socket.io/docs/emit-cheatsheet/

How to change status of perticular user when socket is disconnect?

II am here to resolve my problem in socket connection and disconnection. I want something like this
1. connection done. (ok) // working
2. on perticular event I am doing to update location of user in database { Here user is online }.
3. But somehow network disconnection the socket will be disconnect and user must be offline. this can be using disconnect event. but I am not aware how to recognise that perticular user has to be updated offline because I don't have user_id in `socket.on('disconnect')` event. please help me out. I googled it everywhere but not helped me out.
sorry for wanting logic but it is really needed to me.
Thanks in advance. :)
On disconnect, like you mentioned, you can track the id based on this.id or socket.id.
socket.on('disconnect', function() {
console.log(this.id);
console.log(socket.id);
});
Alternatively, you can handle sockets by using a global array, as suggested in this answer.
If netiher of those are suitable, you can do some dependency injection on the socket object
io.on('connection', function(socket) {
//Obtain the user_id somehow and add it into the socket object
socket.user_id = "hello_world";
socket.on('disconnect', function() {
console.log(socket.user_id);
});
});

attaching data to sockets

i want to have some clients connect to a room with socket.io, then store their information into an object (like a user-list)
if they leave the channel, i want that entry to be auto-deleted.
how can i hook some "outside" information to a socket? is it possible?
example:
user named "joe" connects
socket.emit('joinRoom', {username: 'joe'});
on the server, i want to do something like
socket.on('joinRoom', function(msg) {
userData.push(msg.username); // <-- how can i simplify/automatize this?
}
is there something built-in to manage users?
(the problem arises from me wanting to hook passport-user information to sockets. joe is logged in with passport, the server reqknows that. but the socket doesn't, because there is no req at all)
eventually, i want to be able to say "send a socket message to the user that is logged in as joe". if joe leaves the channel, remove him from the userlist etc
is that possible? how would you do it? is my approach wrong?
thanks
You can add properties to the socket object:
socket.on('joinRoom', function(msg) {
socket.username=msg.username;
}
If the socket you want modify is not the transmitter you can do it through
io.sockets.connected[socket.id].username=msg.username
but you will need his id.

How can I enforce a maximum number of connections from one user with Socket.IO?

I have a very simple Socket.IO group chat system (this one). I want to prevent people from spamming new connections from their browser using JavaScript trickery. This was a problem the one time I publicized the website.
What measure should I use to prevent this? I want to count the number of connections from a browser and it it goes over a threshold, I want to drop all connections. Should I use the IP address? Is the socket.id unique to a user? How should I do it?
If you want to look at the Socket.IO code, see the highlighted code here.
I had the same issue in a multiplayer game, to prevent a user playing through more than one tabs.
There's so many ways a client can refuse to be identified, and having their way of logging in with more than one ID. But since you only want to prevent a user in the same browser it's relatively easy.
First, use passport for authentication and passport.socketio to "bridge" req.user from Express middleware to socket.request.user
Next, since io.on('connection') will always fire for the latest socket request from the client, you can use this as an opportunity to "expire" any old sockets still connected from the same client. This is where socket.request.user will come in handy to identify whether it's the same user client. Something like this:
io.on('connection', function(socket) {
var user = socket.request.user;
// Suppose this user has connected from another tab,
// then the socket.id from current tab will be
// different from [see *1]
var current_socket_id = socket.id;
var last_socket_id = user.latest_socket_id;
if (last_socket_id) {
// If the user has an existing socket connection
// from the previously opened tab,
// Send disconnection request to the previous socket
io.to(last_socket_id).emit('disconnect');
// client-side will look like
// socket.on('disconnect', function(){
// alert('You connected from a new tab, this tab will close now.')
// window.close();
// });
}
// [*1] current socket.id is stored in the user
user.latest_socket_id = socket.id;
user.save();
});

Resources