unable to broadcast a user disconnection in socket.io - javascript

i am new to socket.io & nodejs. i have been writing a tictactoe game and i have a problem in it, my purpose is if someone refresh the page or close the browser let his/her opponent know . i know i should do this with disconnect event and here is what i try, but it is not working .
server side
socket.on('disconnect', function() {
socket.emit('user:disconnect');
});
client side
socket.on('user:disconnect', function() {
var message = player.getPlayerName() + ' leaves';
socket.emit('gameEnded', {room: this.getRoomId(), message: message,id:player.getPlayerId(),gameid: this.getGameId(),winType:"leave"});
});
also, i need to know how to get the room of disconnected user .
i already saw this but i just want to send the users in a room not all users in the whole application.

server.js
socket.on('disconnect', function() {
socket.emit('user:disconnect');
});
In the above code if a user named 'xxx' is disconnected, server is emitting 'user:disconnect' to the same user. You have to find the socket connection of other player and emit the event to that client.
You can achieve your goal by joining both the clients in a room and send message to other user in the room.
socket.join('room1');
io.sockets.in('room1').emit('user:disconnect');
Otherwise you have to store all clients as mentioned below and send message to the specific client using the following code.
var users = {
'client1': [socket object],
'client2': [socket object],
'client3': [socket object]
}
users['client3'].emit('user:disconnect')

Related

SignalR multi user live chat desynchronisation

I have a live chat in which multiple people would be connected simultaneously. All public messaging works fine, but sometimes private messaging to a specific id doesn't work. I believe i narrowed it down to when people disconnected and reconnected that they connected to a different instance (perhaps IIS had recycled and started a new hub).
I thought I had fixed it, but I haven't and now I'm here because I'm stuck. What I thought would fix it was changing the connection variable within the startChat() function to refresh it with the correct information.
This is a cut down version of the code, as I didnt thing the rest would be necesary.
Issue is that when connected to signalR recipients of a message directly to them doean't come through, even though the chat Id it's being sent to it correct. Possible hub/socket mismatch?
var chat = $.connection.chatHub;
$(document).ready(function () {
// Start the chat connection.
startChat();
//restart chat if disconnected
$.connection.hub.disconnected(function () {
setTimeout(startChat(), 5000);
});
$.connection.hub.error(function (error) {
$('#messagebar').html('Chat ' + error + '. If this message doesn\'t go away, refresh your page.');
});
chat.client.addToChat = function (response) {
$('#chat-' + response.Type).prepend(response.Message);
};
});
function startChat() {
chat = $.connection.chatHub;
$.connection.hub.start().done(function () {
//get recent chat from db and insert to page.
//also saves user's chat id to their user for lookup when private messaging
$.ajax({
method: 'POST',
url: '/api/Chat/SetupChat/'
});
$('#messagebar').html('Connected to chat.');
});
}
Any help appreciated, Thanks.
Not sure exactly how your message is going missing, but you should send messages to a User instead of by connection id. This way you be able to identify on the server that a User has at least one connection, and send messages to all connections for that User. if a given connection id is no longer valid, but another one is (because the client has refreshed the page for example) the message wont be lost.
From the docs https://learn.microsoft.com/en-us/aspnet/core/signalr/groups:
The user identifier for a connection can be accessed by the
Context.UserIdentifier property in the hub.
public Task SendPrivateMessage(string user, string message)
{
return Clients.User(user).SendAsync("ReceiveMessage", message);
}
Not sure how this relates exactly to your server code, but you could add it if you're unclear how to progress.

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

i would like to private message a user [duplicate]

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

Socket.io disconnection event triggers after long

I am just trying to create a simple app using Socket.io. I am trying to send a notification (technically, emit an event) to all the connected users, if a particular user, User X, has disconnected, that User X has left or has disconnected.
Here is my server side code.
io.on('connection', function(socket){
console.log('Connected! ' + socket.id);
socket.on('disconnect', function(){
for(var i=0;i<onlineUsers.length;i++)
{
if(onlineUsers[i].socketId == socket.id)
{
var newMessage = {};
newMessage.socketId = onlineUsers[i].socketId;
newMessage.fullName = onlineUsers[i].fullName;
newMessage.message = newMessage.fullName + " IS DISCONNETED!";
io.emit('newMessage', newMessage);
onlineUsers.splice(i,1);
console.log('splicing id ' + socket.id);
}
}
}
I guess that the code is executing but takes long to emit the disconnection event. I receive the disconnection message about 2 minutes after the client had disconnected. Why does it take so long? What might be the wrong in this code?
I also cannot understand when exactly the client is considered as disconnected. If the app closes, or the browser closes or the internet goes down?
Thanks in advance.
Update: I have a test deployment on Cloud9 and my live deployment is on Azure Cloud. I have realized that Cloud9 works just perfect, this seems like an issue with Azure.
Update 2: Whenever I disconnect/close/refresh my web client, I immediately get the disconnected event emitted by the server. The same does not happen for my mobile client. Why?

JavaScript code substitution on client side

I'm now learning some new technologies (such as node.js, socket.io, redis etc.) and making some simple test applications to see how it can work.
My question is about security on a client-side javascript code: for example, i have a chat-server on node.js+express and when a user connects to this chat, server should assign his registred username (authorisation through oldschool php+mysql is used) to his socket. The question is, can user modify his client-side script and connect to chat under different users' names?
Some code is given below:
(server-side part of assigning username, which is just getting the username from client-side call)
// when the client emits 'adduser', this listens and executes
socket.on('adduser', function(username){
// store the username in the socket session for this client
socket.username = username;
// store the room name in the socket session for this client
socket.room = 'General';
// add the client's username to the global list
usernames[username] = username;
// send client to room 1
socket.join('General');
// echo to client they've connected
socket.emit('updatechat', 'SERVER', 'you have connected to General');
// echo to room 1 that a person has connected to their room
socket.broadcast.to('General').emit('updatechat', 'SERVER', username + ' has connected to this room');
socket.emit('updaterooms', rooms, 'General');
});
(client-side part of sending username to server, it looks like 'var username = "User";' for a particular user)
Yii::$app->view->registerJs('var username = "'.$user->identity->username.'";', yii\web\View::POS_HEAD);
(connect function)
chat.on('connect', function(){
// call the server-side function 'adduser' and send one parameter (value of prompt)
chat.emit('adduser', username);
});
SO the question is: can user change (for example, through chrome development tools) his username in line 'var username ...' and connect to chat under the different name?
P.S. this particular situation is just an example, obviously, changed nicknames in chat are not more than a simple joke, but similar situations can appear in other projects...
Supposing your variables are protected in closures and that it's not trivial to change them by typing username='root' in the console, a user could simply replace the whole code.
Everything that happens client side is totally out of your control.
The good news is that they are solutions not involving a duplicate authentication. Supposing you already authenticate the user in your express application, you can get the session and the user from that.
See how I do it in my chat server :
var sessionSockets = new SessionSockets(io, sessionStore, cookieParser);
sessionSockets.on('connection', function (err, socket, session) {
function die(err){
console.log('ERR', err);
socket.emit('error', err.toString());
socket.disconnect();
}
if (! (session && session.passport && session.passport.user && session.room)) return die ('invalid session');
var userId = session.passport.user;
if (!userId) return die('no authenticated user in session');
... handling socket for authenticated user
});
Basically, it uses the session.socket.io module to propagate the session from the standard http requests (authenticated using passport) to the socket.io connection. And everything that isn't supposed to be provided by the user is taken from the session/db/server.

Categories