Socket.io - tell client if he creates or joins a room - javascript

I currently use the following code to allow users to create/join a specific room in socket.io:
socket.on('create', function (roomId) {
...
socket.join(roomId);
...
});
since the join function is used for both creating AND joining the same room, I would like tell the client if he was the one that created the room or just joined an existing one.
WHAT I TRIED TO DO
I tried to store the Id (and other informations not related to the problem), then check whenever that id already exists and emit a different message depending on the results. something like:
// "repo" is a variable declared globally
socket.on('create', function (roomId) {
...
socket.join(roomId);
if (repo.hasRoom(roomId)) {
app.io.sockets.in(roomId).emit('someone Joined');
} else {
app.io.sockets.in(roomId).emit('someone Created this room');
repo.saveRoom(roomId);
}
...
});
the above code doesn't work, as the client is unable to receive the message emitted (I guess because is on the "create" event). to be clear, I CAN determine on the server if a room is created, but the CLIENT doesn't know.
Is there an effective way to notify the client if he was responsible for creating or just joining a room?

Related

Trouble understanding sockets.io room

I am trying to configure a 1v1 game with multiple rooms on my server with sockets.io. When the user create a room, a random alphanumeric identifier of 5 letters is generated and the user joins the room.
function handleNewGame() {
let roomName = makeid(5);
client.join(roomName);
}
Then another user is supposed to join with the room name. The room name is taken from a tag inside the html main page and then is sent by a function.
This is the function that handles game joining on server side
function handleJoinGame(roomName) {
const room = io.sockets.adapter.rooms[roomName];
}
All of this is wrapped inside an
io.on('connection', client => {
}
The problem is that at this point, before the other user even tries to join, the room is undefined.
console.log(room);
This gives me undefined.
If I try to actually print out all the rooms, it actually exist, but the method can't find it. What did I do wrong?
Forget the library for a moment. Conceptually, a room is just a group of users. an array of socket connections to whom you broadcast using a for loop. It should come with a room manager since you want to have many rooms otherwise you had single room anyway.
Let's assume an object of arrays.
var roomManager = {
"room12345" : [socket1, socket2, socket5],
"room76432" : [socket3, socket4],
}
and when socket1 wants to broadcast, you know its room room12345 so you broadcast to all others in that room.
So really you can implement this logic yourself quite easily.
Actually, the io.sockets.adapter.rooms returns a Map. Said so, I had to get the room with a key, using
const room = io.sockets.adapter.rooms.get(roomName);
I've solved the issue.

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.

Global variables in asynchronous code (socket.io) show an error in an unexpected way

The answer from Why shouldn't I use global variables in JavaScript for something that's constant? listed 8 issues with global variables, the # 2 is
If you have any asynchronous code that modifies globals or
timer-driven code that modifies globals and more than one asynchronous
operation can be in flight at the same time, the multiple async
operations can step on each other through the modification of the same
globals.
While the statement is easy to understand I came across a strange socket.io problem I can't figure out why: I find that if one global socket.io client is used for 2 connections (wrongly), the 2nd connection gets the 1st connection message. I create a sample project https://github.com/qiulang/2sockets to demonstrate the problem.
The server logic is simple, when client connects it needs to send a login message with user_id , if the server finds the same user_id login with a different socket.id, it decides that this is the case that the same user login from a different client so it will emit logoff message to the first socket.io client. When the client gets logoff message it will close its connection.
let records = {}
io.on("connection", (socket) => {
socket.on("login",({user_id,client}) =>{
let old_socket = records[user_id]
if ( old_socket ) {
log(`The same ${user_id} with ${old_socket} has logged in, let him log off first`)
io.to(old_socket).emit('logoff', 'another login')
}
records[user_id] = socket.id
})
socket.on("disconnect",(reason) => {
log(`${socket.id} disconnected with reason ${reason}`)
})
});
The client uses a global variable instead of function scope variables. But in main() there are 2 connections, then the second connection gets the logoff message wrongly.
function setupWS(client) {
// let socket = io(server, { transports: ['websocket']}) //Should use a function scope variable
socket = io(server, { transports: ['websocket']}) //wrong!! Should NOT use a global variable
socket.on('connect', function () {
log(`${client} connected to local ws server with ${socket.id}`)
socket.emit('login', { user_id, client })
})
socket.on('logoff', (msg) => {
log(`${socket.id}:${client} should be kicked off for: ${msg}`)
socket.close()
})
}
function main() {
setupWS('nodejs')
setTimeout(()=> {
log('open another connection in 5 seconds')
setupWS("browser")
},5000)
}
main()
When run the code from the client side, I will see log like,
nodejs connected to local ws server with ijqTzPl2SXHmB-U0AAAC
browser connected to local ws server with l7vCcbeOmVU5d_TSAAAE
l7vCcbeOmVU5d_TSAAAE:nodejs should be kicked off for: another login
From the server side, I will see log like,
The same 1_2_1000 with ijqTzPl2SXHmB-U0AAAC has logged in, let him log off first
l7vCcbeOmVU5d_TSAAAE disconnected with reason client namespace disconnect
So the server correctly sent to the 1st socket.id ijqTzPl2SXHmB-U0AAAC the logoff message while at the client side the log is l7vCcbeOmVU5d_TSAAAE:nodejs (NOT l7vCcbeOmVU5d_TSAAAE:browser) should be kicked off. And it is indeed the 2nd socket.id l7vCcbeOmVU5d_TSAAAE called close()
--- update ---
With jfriend00 answer I understand my problem. What I want to add is this problem was introduced by #6 problem in his answer Why shouldn't I use global variables in JavaScript for something that's constant?
A simple omission of the keyword "var" on a local variable makes it a
global variable and can confuse the heck out of code
Normally we require each node.js file adding "use strict" the first line, but now I realize running node --use_strict is much safer.
BTW, if I may add one more problem of using global variables to his excellent answer there, I will add To figure out where and how global variables are modified is quite painful.
When you call setupWs() the second time, it overwrites the global socket variable. Then, when a message comes in to the first connection, you log socket.id, but you aren't logging the connection that actually just got the message, you're logging from the socket global variable which now points to the 2nd connection.
So, your logging is at fault here. It will log socket.id from the same socket global variable, no matter which connection is actually getting a message. So, faulty logging is making it appear that a different connection is getting the message than is actually the case.
And, in addition to the logging, the two places you use socket inside a message handler are also referring to the wrong socket. So, you need to store the socket locally. If you want global access to the last socket you created, you can also store it globally.
function setupWS(client) {
const activeSocket = io(server, { transports: ['websocket']});
// store the latest socket globally so outside code can send
// messages on the latest socket we connected
socket = activeSocket;
activeSocket.on('connect', function () {
log(`${client} connected to local ws server with ${activeSocket.id}`)
activeSocket.emit('login', { user_id, client })
})
activeSocket.on('logoff', (msg) => {
log(`${activeSocket.id}:${client} should be kicked off for: ${msg}`)
activeSocket.close()
})
}
And, as you have seen and apparently know already, this is an example of the kind of problem using a global variable can create.

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