Possibility of writing to a wrong object with callback - node.js

I'm using a NodeJS Socket.IO server for handling realtime things. There is also an event in Socket.IO for authenticating a user. The code looks as follows:
var io = require('socket.io').listen(8080)
io.on('connection', function(socket) {
socket.on('auth', function(id) {
conn.query('SELECT id FROM u WHERE id = ?', id, function(e, result) {
socket.id = result[0].id
})
})
)}
My worries are about this line here:
socket.id = result[0].id
This is in a callback. When the DB executed the query.
So my question is: Is there a possibility that there can be wrong assignment with a lot of of connections at the same time? Seems that this happened already and data got assigned to the wrong socket object. Are there better methods for reliably do things like this?

The Socket object has a built-in field id. From http://socket.io/docs/server-api/#
strong textSocket#id:String
A unique identifier for the socket session, that comes from the
underlying Client.
Your code is trying to assign a different value to this built-in field, which seems like a bad idea. You should try to rename "your" id field to something else like authId or userId to avoid this conflict.

Related

How to safely get the current user id in socket.io/Node.JS?

I am developing a simple API for a chat application on Node.js Express, and by assignment, it is required to make it possible to communicate between two users using socket.іо. I am faced with the problem that I cannot "safely" transfer information about the current user to the socket in any way. Information about the user with whom the correspondence is conducted can be specified in the socket parameters when connecting, which I do, but what about the current user (me)?
For example, I can do this:
const {receiverId, myId} = socket.handshake.query;
That is, specify both ids when connecting. But this is very unsafe because anyone who will join the socket can specify any id and write anything on behalf of someone else (for example, through Postman WebSockets).
Another option I was considering is making a post request in which a connection to the socket will be created using request.user.id and the request parameter. Then the post request will look like this:
router.post('/chat/:receiver', function (req,res){
const {receiver} = req.params
const socket = io.connect('/')
socket.emit('initMyUserId', {
myId: req.user,
});
})
But this option also did not work, because the file where this function is located and the file where the io variable is initialized are different, and I am not sure that it is generally advisable to transfer and use it in this way. Moreover, this approach will not allow me to check the operation of sockets via Postman, because the connection will occur in a post request, and not manually.
Are there working options to safely transfer the current user id with the ability to test it normally in postman? Or at least just safely pass the current user id if it doesn't work with Postman.
Here is the full code snippet for the socket events handlers:
module.exports = function(io) {
io.on('connection', (socket)=>{
const {id} = socket;
console.log(Socket connected: ${id});
const {idUser} = socket.handshake.query;
console.log(Socket idUser: ${idUser});
socket.on('message-to-user', (msg) => {
msg.type = user: ${idUser};
socket.to(idUser).emit('message-to-user', msg);
socket.emit('message-to-user', msg);
});
socket.on('disconnect', () => {
console.log(Socket disconnected: ${id});
});
});
}

How to get current socket object or id with in a sails controller?

I would like to access the currently connected socket id with in a sails.js (v0.12 ) controller function.
sails.sockets.getId(req.socket); is showing undefined since this is not a socket request
My objective is to set the online status of my user in the database when he logged in successfully
login: function (req, res) {
Util.login(req, function(){
var socketId = sails.sockets.getId(req.socket);
console.log('socketId ===', socketId); // return undefined
});
},
Basically i would like to access the current user's socket object in a controller or access current user's session object with in a socket on method
Also i'm not sure that how can i rewrite my old sockets.onConnect
handler
onConnect: function(session, socket) {
// Proceed only if the user is logged in
if (session.me) {
//console.log('test',session);
User.findOne({id: session.me}).exec(function(err, user) {
var socketId = sails.sockets.getId(socket);
user.status = 'online';
user.ip = socket.handshake.address;
user.save(function(err) {
// Publish this user creation event to every socket watching the User model via User.watch()
User.publishCreate(user, socket);
});
// Create the session.users hash if it doesn't exist already
session.users = session.users || {};
// Save this user in the session, indexed by their socket ID.
// This way we can look the user up by socket ID later.
session.users[socketId] = user;
// Persist the session
//session.save();
// Get updates about users being created
User.watch(socket);
// Send a message to the client with information about the new user
sails.sockets.broadcast(socketId, 'user', {
verb :'list',
data:session.users
});
});
}
},
You need to pass the req object to the method.
if (req.isSocket) {
let socketId = sails.sockets.getId(req);
sails.log('socket id: ' + socketId);
}
Since the request is not a socket request, you might need to do something like
Send back some identifier to the client once logged in.
Use the identifier to join a room. (One user per room. )
Broadcast messages to the room with the identifier whenever you need to send message to client.
https://gist.github.com/crtr0/2896891
Update:
From sails migration guide
The onConnect lifecycle callback has been deprecated. Instead, if you need to do something when a new socket is connected, send a request from the newly-connected client to do so. The purpose of onConnect was always for optimizing performance (eliminating the need to do this initial extra round-trip with the server), yet its use can lead to confusion and race conditions. If you desperately need to eliminate the server roundtrip, you can bind a handler directly on sails.io.on('connect', function (newlyConnectedSocket){}) in your bootstrap function (config/bootstrap.js). However, note that this is discouraged. Unless you're facing true production performance issues, you should use the strategy mentioned above for your "on connection" logic (i.e. send an initial request from the client after the socket connects). Socket requests are lightweight, so this doesn't add any tangible overhead to your application, and it will help make your code more predictable.
// in some controller
if (req.isSocket) {
let handshake = req.socket.manager.handshaken[sails.sockets.getId(req)];
if (handshake) {
session = handshake.session;
}
}

Random chat with two users at a time (Socket.io)

I just started learning NodeJS and Socket.io. Until now I have this demo code, from official socket.io site:
http://socket.io/demos/chat/
I am able to get the unique client's ID of each user (socket) which connects, I am still trying to figure out, How can I make my code to only connect with 1 random user at a time when somebody runs the application. I just want to make random chat like Omegle (http://www.omegle.com/).
Only two users should randomly connect and chat with each other till they re-run the app, if they come back they should get connected with someone else who is in the online queue.
What changes do I need to do to have a similar behaviour?
Update
Added Client site code, main.js
$(function() {
var FADE_TIME = 150; // ms
var TYPING_TIMER_LENGTH = 400; // ms
var COLORS = [
'#e21400', '#91580f', '#f8a700', '#f78b00',
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
];
// Initialize variables
var $window = $(window);
var $usernameInput = $('.usernameInput'); // Input for username
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.inputMessage'); // Input message input box
var $loginPage = $('.login.page'); // The login page
var $chatPage = $('.chat.page'); // The chatroom page
// Prompt for setting a username
var username;
var connected = false;
var typing = false;
var lastTypingTime;
var $currentInput = $usernameInput.focus();
//Own Global
var room = '';
var socket = io();
function addParticipantsMessage (data) {
var message = '';
if (data.numUsers === 1) {
// message += "there's 1 participant";
// Status Message
message += "Waiting to connect with someone";
} else {
// message += "there are " + data.numUsers + " participants";
//Status message update
message = "You are connected to a stranger! Say Hey!";
}
log(message);
}
// Sets the client's username
function setUsername () {
username = cleanInput($usernameInput.val().trim());
// If the username is valid
if (username) {
$loginPage.fadeOut();
$chatPage.show();
$loginPage.off('click');
$currentInput = $inputMessage.focus();
// Tell the server your username
socket.emit('add user', username);
// Own
socket.emit('login', {'username' : 'Faizan'});
}
}
Although I would close this question because it's too vague, I feel obliged to give you some insight since I worked way too much with websockets in the last years (although not that much with socketio & nodejs). I suppose some simple guide and relevant links could help you. So first,
Kind of relevant intro
You should already know that Socket.io is a WebSocket implementation.
WebSockets (WS) allow server to send data whenever it wants, as long as the connection is still open, as opposed to old way: client querying all the time asking, if there is an update on the server.
You can imagine a woman and a man at the end of a party: "Thanks for tonight, I'd love to repeat it sometimes soon. Would you give me your number?" - asks the old man. "Ughhh, you know what, better give me yours, I promise I will call you!"
If the girl were to give him her number, he'd call a few times a day asking if she'd go somewhere (and she'd reply no). The other way around, she would call him only if she wanted to go and he would go. Of course he would.
I got a bit carried away, but you get the picture. The woman is a server, the guy is a client.
What is important to understand
(Absolute basic, you should know this =>)
When client connect to your server, (s)he should be served a html page and some javascript, which establishes connection to your WS server. In the code you've posted, Express is used as http server. Check this example to see how you should give user html&js.
You'll also notice namespaces and rooms in most of these tutorials. These are used for separating users into subcategories. One server may contain multiple namespaces (by default only one) and each namespace may contain multiple rooms. You probably won't need to bother with namespaces, one is just enough for your case. You will, however, need to understand rooms (more on that later).
Next thing, taken from your code
io.on('connection', function (socket) {
It's important to know, that socket here basically represent one connected client (in one namespace, but possibly in multiple rooms). You can do all sort of stuff with it, most notably:
install event handlers on it (that's what you do when you call socket.on(event, handler(data))
send events to it with socket.emit(event, data)
send broadcast event to all users with socket.broadcast.emit(event, data)
add/remove it to/from room with socket.join(room), socket.leave(room) respectively.
work with it as with an ordinary variable - store it wherever you want and then reuse it
Do you see the definition of numUsers in your code? That's a global variable which is shared with all clients, since nodejs is single-threaded. In the example it is being incremented inside one of the event handlers. Think we could use something like that? YES.
We can define global variable, queue for example. Or Q if you want. Point is, it can be an array used to store sockets, or rather clients, which are not currently in chat room.
At the end of this section I'd like to point out another obvious thing.
io.on('connection', handler); defines an event handler for 'connection' event happening on the io object (WS server). This is triggered each time client makes connection to your WS server (in your case, through javascript ran inside client's browser). Argument to the method is socket and it is this method where you should add event listeners for each client (that you already do in the code, particularly handling events 'new message', 'add user', 'typing', 'stop typing' and 'disconnect').
What events shall you need
That really depends on how complex you want your app to be. In my opinion, the bare minimum would be (note that you can change the event names, but 'disconnect' should stay 'disconnect'):
event name -> data given
Events handled on server side
login -> username (how the user should be called), possibly password if you want to enable registration
message -> text (content of the message being sent)
leave room -> room name
disconnect
Event handled on client side
connect
chat start -> name (second client's name), room (so we can leave it)
chat end -> no data required if you want to allow only one chat at the same time. In case of multiple chats you should also include which chat got closed
disconnect
Last note before we get started
This is only a rough sketch. There are multiple different crossroads along the way and which path you take mostly depends on your idea of the app. If you want to have multiple chats opened at the same time, you'll need to do some modifications. The same goes if you want to have more than two people connected to the same chat. Here I'll describe the simplest case possible, one chat, to people, no registration. Possibly what you want, judging from your post. Could be wrong.
Workflow
User opens your page in their web browser. You serve them html and javascript. The javascript will start new connection to your websocket server. Also, handlers for desired events should be defined at this point.
When the connection is established, this will be happening:
ON SERVER SIDE
io.on('connection', handler) will be fired. Only appropriate handlers for new socket will be installed, not doing anything else at this point.
ON CLIENT SIDE
socket.on('connect', handler) will be fired. Client should at that point have username stored somewhere. If not, no problem. The connection will be alive for quite some time. You can just call socket.emit('login', {'username':name) any time you wish after you are connected (in the example below I set up variable connected, which defaults to false but will be set to true as soon as connection is established.)
After you send login event from client, server registers it and saves it somewhere. Possibilities are endless, in this case I'll create global dictionary which maps socket.id to username. After that, user socket should be either paired with another one or added to queue.
So, if the queue is empty, simply append socket to global variable (it doesn't have to be an array, since we will pair the first available sockets together, however you may want to implement some history of users so they won't get connected to the same person again). If the queue is not empty, we pull one socket out of the Q and add them to the same room. Room name can be random or whatever you want, I'll use (socket1.id+'#'+socket2.id (if you wanted to have more users in one chat, this would have to be changed).
After you add them both, you'll need to notify them that their chat has started and send them the other peer's name. You will emit event 'chat start'.
Clients will catch the event and open new window. After that, whenever user types something and sends it, client emits event 'message' with payload {'message': user_inserted_text}. Server will capture it in the .on('message' handler and broadcast it to the room. Note:
Broadcasting means sending a message to everyone else except for the socket that starts it.
Note: I am really confused about socketio code right now. Look at this and tell me, if socket.rooms is an array or an object (socket.rooms[room] = room; ?? why?)
To avoid dealing with this not-straightforward code, lets create another global object, rooms, which will store the room names for us. We will map socket.id -> roomName there.
So when message comes, we can get name of the room by calling rooms[socket.id]. Then we broadcast the message like this:
socket.broadcast.to(room).emit('message', data);
Where data is what we received from the sender, therefore object {'text': 'some nice message'}. Your peer will then receive it (you won't) and display it (you should display it when you are sending it).
So the chat continues like this for a while, then one of the users decides (s)he wants to leave / chat with somebody else. They will close window and client will emit event 'leave room'. Server will capture it and send to the other party that her/his peer has disconnected. The same should happen if the client disconnects. After everything is closed, add both users to queue (or only one, if the other has disconnected from the server). In my code I will not make sure they won't get paired again. That is for the OP to code (can't be hard).
So, if you read this far, you deserve some actual code. Although I say actual, it's actually untested. But you know, it should work like this.
Some code
Client side
var connected = false;
var username = 'Faizan';
var room = '';
var socket = io('http://localhost');
socket.on('connect', function (data) { // we are connected, should send our name
connected = true;
if (username) socket.emit('login', {'username' : username});
});
socket.on('chat start', function(data) {
room = data.room;
show_chat_window(data.name); // some method which will show chat window
});
socket.on('chat end', function(data) {
hide_chat_window(); // this will close chat window and alert user that the peer ended chat
socket.leave(room); // it's possible to leave from both server and client, hoever it is better to be done by the client in this case
room = '';
});
socket.on('disconnect', function(data) { // handle server/connection falling
console.log('Connection fell or your browser is closing.');
});
var send_message = function(text) { // method, which you will call when user hits enter in input field
if (connected) socket.emit('message', {'text': text});
};
var leave_chat = function() { // call this when user want to end current chat
if (connected) {
socket.emit('leave room');
socket.leave(room);
room = '';
}
};
Server side
Not including initial requires and html/js serving., only global definitions and main io handler.
var queue = []; // list of sockets waiting for peers
var rooms = {}; // map socket.id => room
var names = {}; // map socket.id => name
var allUsers = {}; // map socket.id => socket
var findPeerForLoneSocket = function(socket) {
// this is place for possibly some extensive logic
// which can involve preventing two people pairing multiple times
if (queue) {
// somebody is in queue, pair them!
var peer = queue.pop();
var room = socket.id + '#' + peer.id;
// join them both
peer.join(room);
socket.join(room);
// register rooms to their names
rooms[peer.id] = room;
rooms[socket.id] = room;
// exchange names between the two of them and start the chat
peer.emit('chat start', {'name': names[socket.id], 'room':room});
socket.emit('chat start', {'name': names[peer.id], 'room':room});
} else {
// queue is empty, add our lone socket
queue.push(socket);
}
}
io.on('connection', function (socket) {
console.log('User '+socket.id + ' connected');
socket.on('login', function (data) {
names[socket.id] = data.username;
allUsers[socket.id] = socket;
// now check if sb is in queue
findPeerForLoneSocket(socket);
});
socket.on('message', function (data) {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('message', data);
});
socket.on('leave room', function () {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('chat end');
var peerID = room.split('#');
peerID = peerID[0] === socket.id ? peerID[1] : peerID[0];
// add both current and peer to the queue
findPeerForLoneSocket(allUsers[peerID]);
findPeerForLoneSocket(socket);
});
socket.on('disconnect', function () {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('chat end');
var peerID = room.split('#');
peerID = peerID[0] === socket.id ? peerID[1] : peerID[0];
// current socket left, add the other one to the queue
findPeerForLoneSocket(allUsers[peerID]);
});
});
P.S.
The code above got a bit messy in the end. It can be done better and I encourage you to do better job than I did. Having this material at hand, go through it step by step and try to understand. I think I commented most, if not all of it. Good luck.
Tl;dr
I am not even surprised. Here, read a comic

One-line check if socket is in given room?

I'm using Node.js with socket.io for a multiplayer card game, and there are game rooms which players can join.
For joining a room, I simply use:
io.sockets.on('connection', function (socket) {
socket.on('joinRoom', function (gid) {
//gid is game ID - create room name based on this and join the room
var room = 'game'+gid;
socket.join(room);
});
});
My question is, what is the quickest way to check if a socket is connected to a certain room? I know I could get all sockets in that room in an array and then check whether the target socket is in the array, but I'm guessing there should be a more basic syntax for this. What I'm looking for (in pseudo-code) would be
if(socket with ID "z8b7dbf98i" is in room "game10")
//do something
For the documentation, socket.io doesn't seem to have any simple way to do that. You really need to check if the client is in the room array, or the opposite: if the room is in the client array.
This can be done with an oneliner using indexOf:
if(socket.rooms.indexOf(room) >= 0)
Or the opposite search:
if(io.sockets.manager.rooms['/' + room].indexOf(socket.id) >= 0)
2021 response:
This was such a headache for me, but currently in version 4.0.2 of Socket IO, socket.rooms is a Javascript Set, so you can check if the given socket is in the room using .has():
if (socket.rooms.has('abc')) {
// Do something if socket is in room 'abc'
} else {
// Do something if socket is NOT in room 'abc'
}
If you need to check if the user is not in the room, you can simply use !:
if (!socket.rooms.has('abc')) {
// Do something if socket is NOT in room 'abc'
}
You can simply check like this
io.sockets.adapter.rooms['roomId']
This returns you a object with sId e.g.
{"1sEAEIZeuMgdhF35AAAA":true}
Updates specific to versions:
3.0+:
io.sockets.adapter.get('roomId')
1.4:
io.sockets.adapter.rooms['roomId']
1.3.x:
io.sockets.adapter.rooms['roomId'];
1.0.x to 1.2.x:
io.adapter.rooms['roomId'];
**Update:**
However one can check socket Id is in a given room or not with one-line as mentioned above only if server architecture has a single node server/single node process.
If you are using multi-node server, i.e. separate node process with load balanced.
Point to note here is, that the sockets are only registered on the process that they first connected to. So, you need to use socket.io-redis to connect all your nodes together to sync events, and what you can do to maintain list of socket Ids across multi-node is broadcast an event each time a client connects/disconnects, so that each node updates & maintains the real-time list of all the clients/socket Ids.
Background/Details:
The redis adapter extends the base adapter, but it only overrides/adds the following properties:
clients
broadcast
add
del
delAll
With the following code:
io.sockets.adapter.rooms["roomId"]; //Any of the above examples specific to versions mentioned above
you are querying the rooms property on socket.io-redis adapter. This wasn't overridden by the redis adapter, so you're actually querying the base adapter, which only knows about rooms/clients in the current process.
Why didn't the redis adapter override the rooms property? Might be because it would have to query the redis database instance to construct an object containing all rooms and connections on every access of this property. Not a good idea?
So as of this writing answer, you'll have to add that functionality to the adapter itself with a method like this:
/**
* One-Line code/property to check if the socket id is in given room.
*
* #param {String} room id
* #param {Function} callback (optional)
* #api public
*/
Redis.prototype.isSidExistsInRoom = function(room, fn){ ... }
where you will hit the redis database instance.
This should be part of the base adapter interface for all other adapters to implement. It's a common problem everyone will face one day, when they scale their servers ;)
P.S. Just a hint on another approach is to use the customRequest/customHook methods in socket.io-redis 3.1.0.
**Update with ver 5.2.0: (relevant multi node servers)**
Now redis adapter gives you rooms across processes/nodes as of 5.2.0
Source: [RedisAdapter#clients(rooms:Array, fn:Function)][5]
> Returns the list of client IDs connected to rooms across all nodes. See [Namespace#clients(fn:Function)][6]
io.of('/').adapter.clients((err, clients) => {
console.log(clients); // an array containing all connected socket ids
});
io.of('/').adapter.clients(['room1', 'room2'], (err, clients) => {
console.log(clients); // an array containing socket ids in 'room1' and/or 'room2'
});
// you can also use
io.in('room3').clients((err, clients) => {
console.log(clients); // an array containing socket ids in 'room3'
});
Happy Coding!
using "socket.io": "^2.3.0" this worked for me
if (!(io.sockets.adapter.rooms[room] && io.sockets.adapter.rooms[room].sockets[socket.id]))
// Join room or do any stuff
socket.join('product_' + product_id);
For current socket.io (1.0+ I suppose) structure of io object was changed, therefore you can now find out is there a user with given socketid in the room with given socket roomid by:
if(io.sockets.adapter.rooms[roomid][socketid])
This seems to have changed quite a lot with versions of socket.io, but as of this writing (version 1.7.2), this looks like it's stored in socket.rooms. It's an object that looks like this:
{
room_name: 'room_name',
second_room_name: 'second_room_name',
...
}
Before your socket has joined any rooms, as documented, you'll see that the socket is already in a room with it's own id, so socket.rooms will look something like:
{ PxtiIs22S7GhWPTSAAAA: 'PxtiIs22S7GhWPTSAAAA'}
That means you can check if a socket is in a room something like this:
io.on('connection', function(socket){
if(socket.rooms[myRoomName]){
// in the room
}else{
// not in the room
}
});
now socket.rooms looks like that:
{
"room1":"room1",
"room2":"room2",
...
"room100":"room100"
}
way to check if a socket is connected to a certain room:
if(socket.rooms[roomID]) return true;
answers from link
https://github.com/socketio/socket.io/issues/2890
If you still need it you can do next:
socket.room = "someRoom";
and then simply check it:
if (socket.room !== undefined){
socket.leave(socket.room);
}
"socket.io": "^4.4.1"
socket.rooms.has('roomName')worked for me.
return true if exist other wise false

How to get multiple socket attributes in socket.io?

I'm building a chat app on which, for every client connection, i need to set some attributes to every client instance using socket.io. When i save the attribute, i use:
client.set('name', name, function () {});
client.set('email', email, function () {});
....
and it runs fine.
When i need to get all the client properties, i have not found a better way than this:
client.get("name",function(err,name) {
client.get("email",function(err,email) {
.......
}
}
I need to nest all the "get" to asynchronously get data; but if i had 10 properties to get, do i need to nest all the 10 items? There must be a better way to do it, can anyone help me?
I don't attach attributes to the socket.
io.sockets.on('connection', function(socket) {
var username = "xx";
var email = "xx";
socket.on('doX', function(data) {
socket.emit('ackX', {username: username, email: email});
});
});
I don't know if it's the best solution, but I have seen many examples like that.
EDIT : socket.io - getting more than one field for a socket?
The correct answer may fit your needs

Resources