socket io not emitting messages to rooms - javascript

I have been stuck with this problem for a while now . I want to emit to all the connected clients of a room when a new client joins the room. I have already seen the https://socket.io/docs/v3/emit-cheatsheet/index.html and tried all the emit methods for rooms as seen below in server code and none of them work except the one which emits to all the connected clients and not to a specific room.
Server code
socket.on("newUser", (data) => {
socket.join(data.sessionId);
//not a single one of these works to emit to a specific room
socket.broadcast.to(data.sessionId).emit("userJoin", data);
io.to(data.sessionId).emit("userJoin", data);
io.in(data.sessionId).emit("userJoin", data);
io.sockets.in(data.sessionId).emit("userJoin", data);
//only this works but send to all the clients
io.emit("userJoin", data);
});
client code
//simply adds a div with info to inform other users that a new user has joined
//works only on io.emit() which emits to all the connected clients of every room
socket.on("userJoin",(data)=>{
const users=document.getElementById('users');
const noUsers=document.getElementById('noUsers');
if(data.action==='join'){
const user = document.createElement('article');
user.classList.add('card');
user.classList.add('lobby-item');
user.innerHTML=`<header class='card__header'>
<h1 class='session__id'>
${data.user.name}
</h1>
</header>`;
users.appendChild(user);
}
});
I think the problem with all the room emit methods not working might be with the rooms being formed wrongly or are empty.But still none of the methods for rooms work here.

Don't process overlep the socket event name and Check that the data object was sent correctly
example
io.to(data.sessionId).emit("userRoomJoin", data);
io.emit("userJoin", data);
Split the socket event name.
If it still doesn't work.
console.log(data.sessionId);
Check that the data object was not undefinded.

when you use an object into your code try to check is it undefinded or no !
so try this command :
console.log(data);

In socket io if there is a redirect from the app or a refresh from the client there is a new socket.id created which means that the old socket was disconnected and a new one was created.
In my app i was first doing socket.emit() on the join in route and then redirecting the user to a different lobby route which meant that the socket which joined the room had been disconnected due to redirecting it to lobby and a new socket was created which wasn't in any room.
Therefore io.to(data.sessionId).emit() wasn't working as all the socket that joined the room were disconnected due to redirecting. io.emit() works as it emits to all the sockets that are currently connected

Related

Ideal way to handle multiple socket rooms?

So I am creating a chat application and I want to handle multiple chat rooms. Now I watched some tutorials and came up with a way.
const io = require("socket.io")(http);
io.on("connection", (socket) => {
socket.on("joinRoom", (roomid) => {
//Joining the room
socket.join(roomid)
//Broadcasting all previous messages
io.to(roomid).emit("messages",allPreviousMessages)
})
socket.on("chatMessage", (data) => {
//Saving msg to dB then broadcasting
io.to(roomid).emit("message",receivedMessage)
})
socket.on("disconnect",(data) => {
//updating user's lastSeen info in dB
})
})
So on my frontend when user clicks on a chatroom we call the "joinRoom" event and connect to the room and on clicking another chatroom make the same process of joining room.
Is this an ideal way for handling multiple chatrooms? If not so please let me know a better solution.
I think the best way to implement private rooms or channels or chats is this way. I have implemented an example for these three sections. Link
User token and api must be authenticated before connecting to socket.io. If this part is ok it will connect to socket.io otherwise it can't cause you to see the event that is there. Something happens by calling each of them. For example, by calling this onNotificationForVoiceCall event, the received data is first checked, then it is checked whether this user is present in the list of online users or not, and the state of the next step is checked. Whether or not this room has already been created in the database, the response of all these operations is returned to the user by socket.emit,
And I fixed some bug in project.

Socket IO and Dynamic Rooms

If anyone is experienced with Websockets / Socket IO hopefully you can point me in the right direction.
I made a Discord Clone and I'm trying to optimize it to scale better. Right now when a user sends a message I query the DB for all users part of that server, and emit a message to their specific socket. This is obviously not going to scale well as every message requires a expensive query and lookup in the client list
// Emit messages to only users part of specific server
// Will only return list of users part of server and active in last 10 minutes
sqlQuery = `SELECT userservers.user_id FROM userservers
JOIN users ON users.user_id = userservers.user_id AND users.user_last_active > (NOW() - INTERVAL 10 minute)
WHERE server_id = ${sql.escape(serverId)}`;
const users = await sql.query(sqlQuery);
action = { type: "message", payload: msg };
// Iterate over users, and find them in clients list
// Emit over socket only to that user
users.forEach((user) => {
clients.forEach((client) => {
if (client.userId === user.user_id) {
io.to(client.id).emit(user.user_id, action);
}
})
});
However using Rooms for each Sever would eliminate my need to query the DB. I understand I can do this when the socket server first starts
// Get server list from Mysql DB
servers.forEach((server) => {
socket.join(server.name);
}
However my issue becomes, when a user create a new server once the application is already running It will not update the list.
I am probably missing some concept on creating dynamic rooms.
EDIT : I am thinking the solution could be that every time a "server" is created, I send a message to the socket server so it can join that "room"
Right now when a user sends a message I query the DB for all users part of that server
I think you can submit broad cast message to all online users, so instead of forEach client => io.to(clientId) you can submit broad cast message to all connected users io.emit('some event', { for: 'everyone' });
also I'm wondering why you are creating many servers? you can divide your server into namespaces by using const namespace = io.of('/thisIsASeparateNamespace'); and also you can submit broadcast messages to all users inside this name space by namespace.emit('some event', { for: 'everyone in name space' });
So your chat structure can be like this
Server
Namespaces // for separate chat app / or like slack work spaces
Rooms // for group chatting
ClientID // for one to one

Browser connecting with multiple sockets using JS socket.io

I'm using node.js with a React front end. I'm building a GPS based MMO type of game. I recently decided to drop most HTTP requests and go with sockets, so I can emit data whenever I want and the front end only has to worry about what to do with it when it receives it.
I've built sites with sockets before, but never ran into this issue.
Basically, every time my browser opens a socket connection with node, it opens 2-3 connections at once(?). When it disconnects, I get the console.log stating that 3 socket connectionss have been closed. It'll look like this:
User disconnected
User disconnected
A user has connected to the system with id: nUMbkgX6gleq-JZQAAAD
A user has connected to the system with id: CzFtR2K5NJ1SoiHLAAAE
A user has connected to the system with id: tgGYhpXuOONmL0rMAAAF
For now, it wouldn't be an issue, however I'm only getting the FIRST 'inventory' emit to work. Later when I call the function to emit inventory again, the browser doesn't seem to get it. But the console logs in the node function will trigger correctly.
I have a feeling that this has something to do with multiple sockets opening.
Here is the React Event:
this.socket.on("inventory", charData => {
console.log('heres the data', charData)
props.setCharStats(charData[0])
})
Here's the node emitter:
const getCharInventory = (charId, userId) => {
dbInstance
.getCharInventory(charId)
.then(response => {
console.log( // this console.log happens just fine
"emmited inventory, userId is: ", userId, " with this response: ", response)
socket.emit("inventory", response)
})
.catch(err => console.error(err))
}
I'm such a dork. I was starting multiple connections within multiple connections.
In more than one component, I had this...
this.socket = socketIOClient(`${process.env.REACT_APP_proxy}`)
Sooo..... yeah. Just an FYI to others, it's better to connect in 1 file and import it into others.

socket.io - How to join a room

I am writing a chatting app. Right now, when a new conversation between user A and user b is initiated by user A, user A sendS a socket message to server with user B's userId.
The Server checks whether there's a conversation existing between the two users, if not, creates one, and have user A join the new conversation(clientA.join(newConversationId)). But I don't know how to have user B join the room too if user B actually has a connected socket now.
What I think might work is keeping an object mapping userId to socket.id, so I can get user B's socket id by B's userId sent along with A's origin message. And then I'll get B's socket by its socket ID, and have it join the conversation too.
The problem is, I don't know how to get a socket by a socket ID. I don't think there's an official document of this. Or is there other better way to deal with something like this?
Yes, you have to keep track of your users id.
This code may help you a little with that.
var io = require("socket.io").listen(conf.port);
var customIds = [];
io.on("connection", function (socket) {
socket.on("login" function (data) {
customIds[socket.id] = data.userId;
});
/**
* Executes when a client disconnect.
* It deletes this client and updates and emits the client new client list
*/
socket.on("disconnect", function () {
// leave the current room
//socket.leave(socket.room);
// emit event
//socket.broadcast.to(socket.room).emit("clientDisconnected",customIds[socket.id]));
// delete the custom id from the custom id array.
customIds.splice(socket.id, 1);
});
}
You can also save your userid like this (Do not modify socket.id)
socket.userId=XXXX
Get a list of clients and look for the user id you need
io.sockets.clients();

socket.io assigning custom socket.id

I keep track the list of every users connected in the array.
So if there is a new connection, it will check whether the user is already on the list or not, if he was already on the list, then assign their socket.id with the corresponding socket.id on the list, otherwise just add them to the list.
It's for preventing same user counted as 2 user while he attempt to do multi-login.
Object.keys(client).forEach(function (key) {
if (client[key].id == data.id){
is_connected = true;
socket.id = key;
}
});
I have no problem handling the messages/chat that was sent/received by the user who attempt multi-login.
socket.on('chat', function(msg){
var data = {"name": client[socket.id].name, "message": msg};
io.emit('chat', data);
});
The io.emit for the chat message was succesfully sent to the user who attempting multi-login.
The problem I got was whenever the user decide to logout/disconnect from the server.
io.emit('user_leave', client[socket.id].id);
[Multi-Login Case] -> Multi-User and Dual-User are same user attempting Multi-Login
Whenever the Main-User disconnected from the server, the Dual-User received 'user_leave' sent by the server, because io.emit supposed to send it to all sockets.
But not otherwise, while the Sub-User disconnected from the server, the Main-user do not receive 'user_leave' emitted by the server.
*Note: Main-User is login first, then the Dual-User. So the Main-User information was saved directly in the array, while the Sub-User socket.id was assigned with the Main-User socket.id
[Update]
B2 socket.id was assigned with B1 socket.id, the io.emit for chat work perfectly while io.emit for disconnect only emitted to All except Dual-User(B2)
socket.id is used internally by socket.io for its own socket list. You cannot overwrite that or you break some of its ability to maintain its own data structures.
You have two choices:
You can use the existing socket.id value as is (without overwriting it) so you don't break existing behavior. It is already guaranteed to be unique on the server.
You can use a different property name for your own id such as socket.userId and then you won't conflict.
If you need to, you can maintain a map between your own custom id and the socket.io socket.id so you could get to one from the other.
Similar question here: Socket.io custom client ID
generateId prop of io.engine object can be used for to set the custom id.
Using this way, the all socket ids can be created on the server side without any issue.
Actually I wrote an answer for a similar question today.
An example:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
io.engine.generateId = function (req) {
// generate a new custom id here
return 1
}
io.on('connection', function (socket) {
// listing the default namespace rooms
console.log("rooms: ", io.nsps["/"].adapter.rooms);
})
The console output would be as the following:
rooms: { '/#1': Room { sockets: { '/#1': true }, length: 1 } }
It seems to be it has been handled.
It must be in mind that socket id must be unpredictable and unique value with considering security and the app operations!
Extra: If socket.id is returned as undefined because of your intense processes on your generateId method, async/await combination can be used to overcome this issue on node.js version 7.6.0 and later. handshake method of node_modules/engine.io/lib/server.js file should be changed as following:
former:
Server.prototype.handshake = function (transportName, req) {
var id = this.generateId(req);
...
}
new:
Server.prototype.handshake = async function (transportName, req) {
var id = await this.generateId(req);
...
}

Categories