Socket.io + Redis - clients are joining each others' "private" rooms - node.js

I've just started working with Socket.io and Redis for pub/sub messaging and it's pretty great. One important feature of my application is that the server needs to be able to broadcast messages to all subscribers of a room, and also choose 1 subscriber in that room and narrowcast a message just to them. For now, that subscriber is chosen at random. Based on reading socket.io's documentation, I think I can accomplish this.
However, I've come across something I don't understand. In Socket.io's Default Room documentation (https://socket.io/docs/rooms-and-namespaces/#default-room), they say that each socket automatically joins a room named after its socket ID. This looks like it would solve my narrowcast requirement -- look at the list of client IDs connected to my "big" room, choose one at random, and then send a message to the room with the same name as the chosen ID.
However, it doesn't work because for some reason all clients are joining each others' default rooms. I'm not seeing any exceptions in my code, but my "narrowcast" messages are going to all clients.
Here's my server-side code:
var io = require('socket.io');
var redisAdapter = require('socket.io-redis');
var server = io();
server.adapter(redisAdapter({ host: 'localhost', port: 6379 }))
server.on('connect', (socket) => {
console.log(`${socket.id} connected!`);
socket.join('new');
server.emit('welcome', `Please give a warm welcome to ${socket.id}!`);
server.to(socket.id).emit('private', 'Just between you and me, I think you are going to like it here');
});
server.listen(3000);
setInterval(whisperAtRandom, 2000);
function whisperAtRandom() {
server.in('new').adapter.clients((err, clients) => {
if (err) throw err;
console.log('Clients on channel "new": ', clients);
chosenOne = clients[Math.floor(Math.random()*clients.length)];
console.log(`Whispering to ${chosenOne}`);
server.to(chosenOne).emit('private', { message: `Psssst... hey there ${chosenOne}`});
server.in(chosenOne).adapter.clients((err, clients) => {
console.log(`Clients in ${chosenOne}: ${clients}`)
})
});
}
It stands up the server, listens on port 3000, and then every 2 seconds sends a message to a random client in the "new" room. It also logs out the list of clients who are connected to the "new" room and the "" room.
Here's the client-side code:
var sock = require('socket.io-client')('http://localhost:3000');
sock.connect();
sock.on('update', (data) => {
console.log('Updating...');
console.log(data);
});
sock.on('new', (data) => {
console.log(`${sock.id} accepting request for new`);
console.log(data.message);
});
sock.on('welcome', (data) => {
console.log('A new challenger enters the ring');
console.log(data);
});
sock.on('private', (data) => {
console.log(`A private message for me, ${sock.id}???`);
console.log(data);
});
My problem is that all my clients are connected to each others' "" room. Here's a sample from my logs:
0|socket-s | Clients on channel "new": [ 'dJaoZd6amTfdQy5NAAAA', 'bwG1yTT46dr5R_G6AAAB' ]
0|socket-s | Whispering to bwG1yTT46dr5R_G6AAAB
2|socket-c | A private message for me, dJaoZd6amTfdQy5NAAAA???
2|socket-c | Psssst... hey there bwG1yTT46dr5R_G6AAAB
1|socket-c | A private message for me, bwG1yTT46dr5R_G6AAAB???
1|socket-c | Psssst... hey there bwG1yTT46dr5R_G6AAAB
0|socket-s | Clients in bwG1yTT46dr5R_G6AAAB: dJaoZd6amTfdQy5NAAAA,bwG1yTT46dr5R_G6AAAB
You can see that the "private" message is received by both clients, dJaoZ... and bwG1y..., and that both clients are connected to the default room for bwG1y....
Why is this? Does it have something to do with the fact that both of my clients are running on the same machine (with different Node processes)? Am I missing something in Socket.io's documentation? Any help is appreciated!
PS -- to add even more confusion, the private messaging that occurs in server.on('connect', ... works! Each clients receives the "Just between you and me ..." message exactly once, right after they connect to the server.

Your main problem could caused by server.to in whisperAtRandom function, use socket instead of server for the private message:
socket.to(chosenOne).emit('private', { message: `Psssst... hey there ${chosenOne}`});
To answer your other problems, try to change these:
server.emit('welcome', `Please give a warm welcome to ${socket.id}!`);
server.to(socket.id).emit('private', 'Just between you and me, I think you are going to like it here');
To:
socket.broadcast.emit('welcome', `Please give a warm welcome to ${socket.id}!`);
socket.emit('private', 'Just between you and me, I think you are going to like it here');
Check out my Socket.IO Cheatsheet:
// Socket.IO Cheatsheet
// Add socket to room
socket.join('some room');
// Remove socket from room
socket.leave('some room');
// Send to current client
socket.emit('message', 'this is a test');
// Send to all clients include sender
io.sockets.emit('message', 'this is a test');
// Send to all clients except sender
socket.broadcast.emit('message', 'this is a test');
// Send to all clients in 'game' room(channel) except sender
socket.broadcast.to('game').emit('message', 'this is a test');
// Send to all clients in 'game' room(channel) include sender
io.sockets.in('game').emit('message', 'this is a test');
// sending to individual socket id
socket.to(socketId).emit('hey', 'I just met you');

Related

socket io room authorization for listeners

suppose I have an room_id and I am registering my users to it.
let room_id = "2133"
socket.join(room_id, function() {
socket.emit(room_id, { message: `application joined: ${room_id}` })
});
and this works fine. but listening to this room is open for everyone.
if user=admin1 registers to room_id="2133", the user="james" can listen to the room_id="2133" and get all the traffics.
how to add authorize lvl to room?
I confused about the definition of room and namespace.
joining the clients only can be done on the server-side.
as #Halil Çakar said in the comment, you can emit messages directly to rooms with event namespace.
io.in(room_id).emit('your events', data)

How to send multiple client using socket.id that are connected to socket (Nodejs, Socket.io)

socket.on('private-message', function(data){
console.log("Sending: " + data.content + " to " + data.username);
console.log(clients[data.username].socket.join(', '));
if (clients[data.username]){
io.sockets.connected[clients[data.username].socket].emit("add-message", data);
} else {
console.log("User does not exist: " + data.username);
}
});
This code is working fine but what I want is, I want to send multiple connected clients connected to the socket using their socket id.
io.sockets.connected[reciver.socketid].emit("add-message", data);
Is there any way or is it is possible to write the group of receiver socket.id. I don't want to use for loop.
Feel free to use my Socket.IO Cheatsheet!
// Add socket to room
socket.join('some room');
// Remove socket from room
socket.leave('some room');
// Send to current client
socket.emit('message', 'this is a test');
// Send to all clients include sender
io.sockets.emit('message', 'this is a test');
// Send to all clients except sender
socket.broadcast.emit('message', 'this is a test');
// Send to all clients in 'game' room(channel) except sender
socket.broadcast.to('game').emit('message', 'this is a test');
// Send to all clients in 'game' room(channel) include sender
io.sockets.in('game').emit('message', 'this is a test');
// Send to individual socket id
io.sockets.socket(socketId).emit('message', 'this is a test');
Update 2021
Here's the official cheatsheet for Socket.IO.
Yes its solved now!! Here we go with answer.
io.to(socketid1).to(socketid2).emit("add-message", data);
Or you can do this by joining clients to a group. And emit message to that group.
join
socket.join(data.username);
emit
socket.join(data.username);
This saved me from writing lot of codes
You can make subscribe to all user to a room and then you can emit message.
socket.join('some room');
Ah! I didn't read the docs correct! thanks all!: )
I was looking for this... io.sockets.emit('Test1', response);
I was using this... socket.emit("Test1", response);
:)

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 });
};

node.js only one client is receiving the messages

As far as I know, unless told otherwise, if the server sends a message, all clients should receive it. But in my case only one client is getting the messages.
client :
(function () {
window.Network = {
socket : null,
initialize : function(socketURL) {
this.socket = io.connect(socketURL);
this.socket.on('new move', this.add);
},
add : function(data) {
var msg = $('<div class="msg"></div>')
.append('<span class="text">' + data.x+'/'+data.y + '</span>');
$('#messages')
.append(msg)
.animate({scrollTop: $('#messages').prop('scrollHeight')}, 0);
},
send : function(data) {
this.socket.emit("move",data);
return false;
}
};
}());
server:
io.sockets.on('connection', function (socket) {
socket.on('move', function (data) {
console.log(data);
socket.emit("new move",data);
});
});
If I open several clients and use "send" function, only the client that sent it receives the emit from the server.
Any idea what I am doing wrong?
Thanks
To emit globally on the server side use this :
io.sockets.emit('new move', 'data1');
To emit to the current socket :
socket.emit('function', 'data1', 'data2');
To broadcast to everyone but the client :
socket.broadcast.emit('function', 'data1', 'data2');
Source
When you are using socket, you are talking directly to the connected client.
You can use rooms however, un this snippet i add the socket to room1
// Add the player socket, to the room.
socket.join('room1');
Then you can emit to all client in a room by
io.sockets.in('room1').emit('startGame', true);
The code is working just as it is supposed to. You are doing socket.emit("new move",data);, which will emit a new move event back to that specific socket, which in your case is the same socket that connected.
If you also want it to be sent to other clients that are connected, then you need to explicitly emit the event to other clients.
socket.emit("new move",data);
socket.broadcast.emit("new move",data);

socket.io private message

I've beeen scouring the Net with no luck. I'm trying to figure out how to send a private message from one user to another. There are lots of snippets, but I'm not sure about the client/server interaction. If I have the ID of the socket I want to send to, how do I send it to the server, and how do I ensure the server only sends the message to that one receiver socket?
Is there a tutorial or walkthrough that anyone knows of?
No tutorial needed. The Socket.IO FAQ is pretty straightforward on this one:
socket.emit('news', { hello: 'world' });
EDIT: Folks are linking to this question when asking about how to get that socket object later. There is no need to. When a new client connects, save a reference to that socket on whatever object you're keeping your user information on. My comment from below:
In the top of your script somewhere, setup an object to hold your users' information.
var connectedUsers = {};
In your .on('connection') function, add that socket to your new object. connectedUsers[USER_NAME_HERE] = socket; Then you can easily retrieve it later. connectedUsers[USER_NAME_HERE].emit('something', 'something');
Here's a code snippet that should help:
Client-side (sending message)
socket.emit("private", { msg: chatMsg.val(), to: selected.text() });
where to refers to the id to send a private message to and msg is the content.
Client-side (receiving message)
socket.on("private", function(data) {
chatLog.append('<li class="private"><em><strong>'+ data.from +' -> '+ data.to +'</strong>: '+ data.msg +'</em></li>');
});
where chatLog is a div displaying the chat messages.
Server-side
client.on("private", function(data) {
io.sockets.sockets[data.to].emit("private", { from: client.id, to: data.to, msg: data.msg });
client.emit("private", { from: client.id, to: data.to, msg: data.msg });
});
Although we have nice answers here. However, I couldn't grasp the whole client server unique user identification pretty fast, so I'm posting this simple steps in order to help whoever is struggling as i did.....
At the client side, Get the user's ID, in my case I'm getting the username...
Client side user registration
//Connect socket.io
var systemUrl = 'http://localhost:4000';
var socket = io.connect(systemUrl);
//Collect User identity from the client side
var username = prompt('Enter your Username');
socket.emit('register',username);
The Listen to register on the server side to register user's socket to connected socket
Serve side code User registration
/*Craete an empty object to collect connected users*/
var connectedUsers = {};
io.on('connection',function(socket){
/*Register connected user*/
socket.on('register',function(username){
socket.username = username;
connectedUsers[username] = socket;
});
});
Send Message from the client side
$(document).on('click','.username',function(){
var username = $(this).text(),
message = prompt("type your message");
socket.emit('private_chat',{
to : username,
message : message
});
});
Receive message on server and emit it to private user
/*Private chat*/
socket.on('private_chat',function(data){
const to = data.to,
message = data.message;
if(connectedUsers.hasOwnProperty(to)){
connectedUsers[to].emit('private_chat',{
//The sender's username
username : socket.username,
//Message sent to receiver
message : message
});
}
});
Receive message on client and display it
/*Received private messages*/
socket.on('private_chat',function(data){
var username = data.username;
var message = data.message;
alert(username+': '+message);
});
This is not the best, however you can start from here....
The easiest way I can think of is to have an hash of all the users on using their id or name as the key and have their socket as part of the value then when you want to send a message to just them you pull that socket and emit on it... something like this:
users[toUser].emit('msg',"Hello, "+toUser+"!");
if you have a web site that has register users with uid then you can create a room for each user and name the room by uid of the user.
first connect client to the server using :
var socket = io('server_url');
on the server side create an event for detecting client connection:
io.on('connection', function (socket) {}
then you can emit to client inside it using socket.emit(); and ask uid of current user.
on the client side create an event for this request and then send uid of the user to server.
now on server side join the connected socket to room using :
socket.join(uid);
console.log('user ' + socket.user + ' connected \n');
now you can send private message to a user using following line:
io.to(uid).emit();
if you use the code above, it doesn't matter how many tab user has already open from your web site . each tab will connect to the same room.
in socket.io 1.0 use
io.sockets.connected[<socketid>]
you can store just socket id. like so:
var users = {};
users[USER_NAME] = socket.id;
then:
io.sockets.connected[users[USER_NAME]]
.emit('private', {
msg:'private message for user with name '+ USER_NAME
});
You can create an unique room for messaging between USER_A and USER_B and both users must join to this room. You may use an UUID as a ROOM_ID (the 'socketId' in the following example).
io.on('connection', socket => {
socket.on('message', message => {
socket.to(message.socketId).emit('message', message);
});
socket.on('join', socketId => {
socket.join(socketId);
});
});
See Joining Rooms and Emit Cheatsheet
The way I got it to work is by introducing one more event "registered user". This basically triggers on the client side as soon as there is a registered user and then emits this even and passes "currentUser._id"
Client Side:
var socket = io();
<% if (currentUser) { %>
socket.emit('registered user', "<%= currentUser._id %>");
<% } %>
Server Side: Once the "registered user" even triggers, it joins the current user socket to the new room with the name which is that user id. So basically each registered user will be in a room which is his/her uid.
socket.on('registered user', function (uid) {
socket.join(uid)
});
Server Side: One there is a message sent, I pass the id of the user the message is sent to in the msg object and then emit to that specific room.
// Private chat
socket.on('chat message', function (msg) {
const uidTo = msg.uidTo
socket.in(uidTo).emit('chat message', msg )
}); `
});

Resources