I'm building a web application with Node (Express) and Socket.IO that has chat functionality. Because opening a new tab on a page establishes a new socket connection, I need to group all instances of a single user into their own room based on the Express session ID to enable all messages aimed at said user to appear in all duplicate tabs. This is in addition to any other room/channel they might already be logged into. At a minimum, users are subscribed to two chatrooms: the actual "real" room, and their own channel using the sessionID.
The problem is that all of the sockets for my sessionID are also in the more general room (and need to be to get messages from other users). When I send out the general chat message I'd like to omit any sockets corresponding to the sending user, as they have already received the message through their own channel. I've gone ahead and made a hash of arrays containing lists of socketIDs for that session, keyed on the sessionID. I've seen a few different syntaxes for specifying exception lists, but none seem to work for me.
The relevant code, with certain parts omitted for brevity:
var sessionSockets = {};
io.sockets.on('connection', function(socket){
if(!io.sockets.manager.rooms["/" + sessionID]) {
sessionSockets[sessionID] = [];
//send message indicating log on to all sockets except for my session
}
socket.join(sessionID); //create private channel for all sockets of the same sessionID
sessionSockets[sessionID].push(socket.id);
socket.on('chat', function(data){
var payload = {
message: data.msg,
from: data.user
};
//send back as personal message to all sockets for this session
io.sockets.in(sessionID).emit('me',payload);
//send to everyone else as regular message; WHAT SYNTAX?
io.sockets.in('').except(sessionSockets[sessionID]).emit('chat', payload);
}
}
tl;dr: How can I send a message to a subset of users in a channel/room without manually doing a comparison of arrays?
use socket.join('room') and then emit to the room by using socket.in(socket.room).broadcast.emit.
This is how you can group clients in a room and emit a perticular example to room
Related
I have used methods socket.on and io.emit, And i got response to all users. But, i want to get response for particular user.
But my application contains login functionality and i followed this post on stackoverflow, and they are saying we need unique userId and socketId in an object for a particular user to emit an event for a particular user.
But i am getting the userId after login, But we want it when user connect to app.
So can anyone please help me with the same?
In your node.js, create a global array 'aryUser', each element contains the socketid and loginid.
node.js onConnect (new connection), add a new element to the array with the socketid and set loginid = empty.
after the user login, emit an event from client to the server, e.g:
socket.emit('userloginok', loginid)
in node.js, define a function:
socket.on('userloginok', loginid)
and in this function, search the aryUser with the socketid and replace the empty loginid inside the array element with the parm loginid.
in node.js, define the function:
socket.on('disconnect')
and in this function, search the aryUser, use aryUser.splice(i,1) to remove the user just disconnected.
that means, aryUser contains all users connected, some of them logined, some of them not logined. And you can use the socketid of the array to send message to particular user, and/or all users.
Example Source Code:
server.js
http://www.zephan.top/server.js
server.html
http://www.zephan.top/server.html.txt
rename server.html.txt to server.html, put server.html and server.js in the same directory, and run:
node server.js
Yes, you definitely need socketId in order to send and receive messages between two specific users.
UserId is required just to keep track of socketId associated with the particular user or you can manage it with some other way as well that's up to you.
As per your question, you have userId of the user and you need socketId of that user! So, in this case, you can pass userId when that particular connects to a socket server from the client side as shown in below snippet,
const socket = io(this.SOCKET_SERVER_BASE_URL, { query: `userId=${userId}` });
And you can read this user on nodejs server like this,
const userId= socket.request._query['userId'],
const socketId= socket.id
Now store this socketId in somewhere, for example, Redis or some sort of caching mechanism again up to you, just make sure fetching and retrieval should be fast.
Now while sending a message just pull the socketId from your cache and emit the message on that socketId by using below code,
io.to(socket.id).emit(`message-response`, {
message: 'hello'
});
I have written a complete blog post on this topic on both Angular and AngularJs, you can refer those as well.
Edit 1:
Part 1 =>
When your user completes the login request, then make the connection to the socket server.
Assuming you are using React Or Angular After a successful login you will redirect your user to home component(page). On the Home component(page) make the socket server connect by passing the userId just like this,
const socket = io(SOCKET_SERVER_BASE_URL, { query: `userId=${userId}` });
P.S. you can get userID from URL or maybe using a cookie that is up to you.
Once you receive this socket connection request on the server, then you can read the userID query and you can get socketId associated with it and store it in cache like this,
io.use( async (socket, next) => {
try {
await addSocketIdInCache({
userId: socket.request._query['userId'],
socketId: socket.id
});
next();
} catch (error) {
// Error
console.error(error);
}
});
Part 2 =>
Now, let's say you have a list of the users on the client side, and you want to send a message to particular users.
socket.emit(`message`, {
message: 'hello',
userId: userId
});
On the server side, fetch the socketId from the cache using UserId. Once you get the socketId from cache send a specific message like this,
io.to(socketId).emit(`message-response`, {
message: 'hello'
});
Hope this helps.
how could I implement chat with different rooms? I don't want .broadcast() to send data to all logged in users.i just want to send the data to specific user
I've just completed doing something very similar with a game.
I maintain a list of all socket objects with the socket ID as the key. This allows me to emit a message at any time to one particular user.
On registering:
var sockets = [];
sockets[socket.id] = socket;
To emit:
// pass in needed socket ID from client
sockets[socketID].emit('message-name', message);
If you have specific questions share your code and I'll help as much as I can.
If you want to send a message to a group of users, you need to create a room for them & then emit a message. doc: https://socket.io/docs/rooms-and-namespaces/#joining-and-leaving. Following code snippet might be helpful
The user class used
User = {
constructor(socketID, socket) {
this.socketID = socketID;
this.socket = socket;
}
}
subscribe user's socket to the room
var users = [user1.socketID, user2.socketID, user3.socketID];
var room = 'room';
users.forEach(function (user) {
user.socket.join(room);
});
Then emit the message in the room
io.to(room).emit('messageInRoom', message);
How do I retrieve the rooms a socket is a member of?
I'm using socket.io version 1.4
I tried with this.socket.adapter.rooms but I received this error in the chrome console: Cannot read property 'rooms' of undefined
In my client code I have this method:
send(msg) {
if(msg != ''){
var clientInfo = [];
clientInfo.push(msg);
clientInfo.push(socket.id);
clientInfo.push(this.socket.adapter.rooms);
socket.emit('message', clientInfo);
}
}
On my server side:
socket.on('message', function(clientInfo){
var clientmessage = clientInfo[0];
var clientid = clientInfo[1];
var clientroom = clientInfo[2];
io.to(clientroom).emit('messageSent', clientmessage);
});
Server side, you can get a list of rooms a socket is in with:
socket.rooms
Client side, a socket does not know what rooms it is in. The whole concept of rooms is a server-side concept and all the data structures are maintained there. If a client wanted to know what rooms it was in, it would either have to keep track of what rooms it requested to be a member of or it would have to ask the server what rooms it is in.
There is one oddity about the server-side socket.rooms structure. It is apparently not updated real-time. If you do socket.join("someRoom") and then immediately look at socket.rooms, you will not see the someRoom name listed. But, if you look on process.nextTick() or on setTimeout(), you will see it in socket.rooms. I haven't delved into the socket.io source code to figure out why that is the way it is, but apparently something is only being updated asynchronously.
Object.keys(socket.rooms).forEach(function(room, idx) {
if(idx!=0){
console.log(idx,"-->",room)
}
});
By the above code you can identify all the rooms the socket have joined.
You can skip the first room which is in index 0 as it is the default room a socket will connect on connection.
Tested this with SocketIO 1.7 and works well.
Socket.io doesn't display messages send on yourself ip.
For example
var id = 333;
socket.broadcast.to(id).emit('user', user);
It working good, but message is only in client #333, but user than sent message, do not have a copy in the message client.
I wanted to solve in this way, but it does not work
socket.broadcast.to(socket.id).emit('user', user);
Why?
Without more code its hard to say what you want but one thing is certain in order to send a message to a single user you must use that socket object and use socket.emit
As far as i know broadcast is only used to tell everyone except for yourself.
What i usually do when it comes to keeping track of users is i have the following:
var userList = [];
io.on('connection', function (socket) {
socket.on('userData', function (userDetails) {
userDetails.socket = socket;
userList[userDetails.id] = userDetails
});
});
Basicly when a user connects to my socket and the page for the user is fully loaded it sends its id (or a token if you wish) i then map the user's socket into the list so i can quickly pick it up again if i wish to send to that user.
An example could be:
user.id = 33 connects to our server
Once loaded the users emits to our server userData function
The socket is then taken and put into the list at row 33
When we need to we can this use the following code to get the users socket:
socket = userList[33];
or if we have the object:
socket = userList[user.id];
I hope this helps you.
For this, you can use socket.emit('message').
socket.emit: Emit for only one socket.
Hope this will help you. You can also check out this link: socket.io send packet to sender only
I am learning NodeJS and Socket.IO and have been following the various tutorials dotted about, mainly focusing on making a basic chatroom.
My app works so far: being able to send messages with clients being able to move to different "rooms".
Now I am trying to tidy it up and make it look the part.
I am stumbling with the follow idea:
When user A moves form room A to B, an emit() method is sent to update an array with a user count, which in turn is sent back to the sender. A list of rooms in a side panel is then updated e.g. Room A (0), Room B (1).
Now, emitting the new data to the sender is easy and works, the room list gets updated, and the other rooms (that the sender is not in) have hyperlinks (this is how the user moves between rooms)
But I want to send data to the other clients, so their room lists also get updated. Sending the same data as before means the other clients's rooms list is incorrect, as the data is referencing the new room name the sender joined, not the room the other client is currently in.
Some code:
socket.on('switchRoom', function(data) {
var newroom = sanitize(data).escape().trim();
socket.leave(socket.room);
socket.join(newroom);
socket.room = newroom;
socket.emit('updaterooms', rooms, newroom);
// this is the problem area, socket.room should be the socket.room data for the non-sending client
//socket.broadcast.emit('updaterooms', rooms, socket.room);
});
How would I emit to all other clients (not the sender) with the data contained in their own socket "session" (i.e. socket.room)?
You will need to emit a generated message that's catered to each socket. What you will need to do is iterate through each socket, emitting the custom message based on each socket, for example:
io.sockets.on('connect', function(socket) {
socket.on('switchRoom', function(data) {
var newroom = sanitize(data).escape().trim();
socket.leave(socket.room);
socket.join(newroom);
socket.room = newroom;
socket.emit('updaterooms', rooms, newroom);
io.sockets.clients.forEach(function(client) {
if (socket != client) {
client.emit('updaterooms', rooms, client.room);
}
});
});
});