Save socket.io ID for all open tabs in browser - node.js

Can you please tell how to solve the problem of preserving the identity socket.io, for all open tabs in the browser.
Example:
I have a page open (/index, for example), I set it to connect to socket.io and get a unique session ID. But when I open the same page on another tab - ID creates another, that is, there are already two compounds with two different ID. That is right, it is logical. But, I need to send messages to these ID, simultaneously in all the tabs (including binding to user_id), and because the session ID are different - it is impossible.

If you don't want to (or can't) use some kind of session store, and want to send messages to multiple sockets with the same user_id, you can store a map with user_id as key and an array of socket as value
var socketMap = {};
And store a reference to the socket with your handshake data
io.sockets.on('connection', function (socket) {
var userId = socket.handshake.userId;
if(!socketMap[userId]) socketMap[userId] = [];
socketMap[userId].push(socket);
});
Or with some normal event data
socket.on('auth', function(data) {
var userId = data.userId;
var authToken = data.authToken;
... // verify the data...
if(!socketMap[userId]) socketMap[userId] = [];
socketMap[userId].push(socket);
});
When you want a list of socket to send message to
var sockets = socketMap['some_user_id'];

ID of the socket will be different in any cases, and should be not used to identify user in any case. It is only used to identify Socket it self.
What you are looking for is session. Ability to relate socket connection to session - is what you are looking for.
In order to do so, you need to have sessions created for new http connections as well as restore this session during handshake process of socket.io.
That way you will be able to identify that specific socket belongs to specific session.
And then user data and other stuff you keep to session. This helps in many cases - restore session on page refresh or have single session in multiple tabs as you need it.
Read this answer of how to restore socket.io session id from handshake process: http://www.danielbaulig.de/socket-ioexpress/

Related

Is there something I can use to uniquely identify a device connecting to a socket with Socket.IO?

I'm working on an app that keeps users together in a room using socket.io. While using the app, I'm keeping track of actions the users take. If a user disconnects accidentally (in my use case, their phone rings, or the screen shuts off), I want them to be able to re-enter the room as the same 'user' without requiring a login so the actions they've tracked stay with them. I tried using socket.conn.remoteAddress, but that doesn't seem to be consistent enough to rely on.
For now, I'm requiring the user to manually enter a username and match to the user with that name on the server, but I'd rather it be automatic and invisible to the user, not to mention more reliable than what each user inputs.
Use a cookie. When they connect, check if their unique cookie already exists. If not, create it with a unique ID in it. If it does already exist, use the unique ID in it to identify the user.
From the connect event in socket.io, you can get the cookies here.
const socketCookieName = "socketUser";
const cookieParser = require('socket.io-cookie-parser');
io.use(cookieParser());
io.on('connection', function(socket) {
// all parsed cookies in socket.request.cookies
let user = socket.request.cookies[socketCookieName];
if (!user) {
// create unique userID and set it in a cookie
user = /* create some unique userID here */;
// set this into a cookie
}
// now user will be your socket.io userID
});

How can I identify user when user reload the page

I'm creating a card game like crazy8. And I already publish prototype.
Look here http://himapoyo.com
My problem is, when I reload the page during the game, socket is disconnected and my socket.id is changed.
So server side program can't identify me. Now, server remove player from table when socket is disconnected.(because if server don't remove the player who server can't identify, game is stopped, so I program). But if I can identify user, I don't want to remove player who just reload the page from table.
QUESTION: how can I identify user when user reload the page?
I think using cookie as ID is best for this problem. Are there Other solutions?
Other options would include:
Using local storage on the client
Passing query string values in the url
Posting the user id as part as the refresh
Storing the user in a server side session
Storing user information in redis cache.
I'm sure there are many more but this should be enough
After reading your question I understand (correct me if I'm wrong) that once a user refreshes the page (reconnects his socket) the socket ID changes (obviously) and you can't identify the user.
First of all, you shouldn't keep track of user using his socket ID as the socket ID will change every time the user reconnects. You should keep track of the socket ID for communication with the user AFTER you have identified the user.
So, what I would do is make an "authenticate" event on the server side and disconnect those sockets who don't emit the "authenticate" event. This event can expect any form of credentials (JWT, username:password etc).
After the user has been authenticated, you can take that socket ID and map that to the user ID and use the socket ID for further communication.
So the server side flow will be like this:
io.on("connect", function (socket) {
socket.on("authenticate", function(data) {
// do auth and then make other events
// if auth
socket.auth = true;
socket.emit("message", "User authenticated");
});
// If socket fails to authenticate within 3 seconds after connect, disconnect it
setTimeout(function() {
if (!socket.auth) {
socket.disconnect();
}
}, 3000);
});
After going to your app, I found that there is no authentication required. This should not be the case as I can highjack any user's slot at the table by just sending his/her name.
If you are okay with that then perhaps you can just keep track of the user name and map it to socket ID on connect.

Is there an alternate way of sending a private message with Socket.io (1.0+)?

Im working on a simple session based app shared by a session code in the URL. I decided to generate and assign a shorter user friendly unique ID for each client who connects to a socket, and the client who creates a session causes a socket.io room to be created with his ID.
I didnt realize until later that the private messaging mechanism in socket.io relied on each client being assigned to a room named by their ID. This means that because my room for a session is named after the creator's socket ID, using .to() will not message that client, but rather all of the clients now assigned to that room.
I could remedy this in ways that would require some re-design, but first I wanted to ask if there is an alternate way of sending a message to a specific client via his/her ID.
/*create an array of clients, where key is the name of user and value is its unique socket id(generated by socket only, you do not have to generate it) during connection.*/
var clients = {};
clients[data.username] = {
"socket": socket.id
};
//on server side
socket.on('private-message', function(data){
io.sockets.connected[clients[data.username].socket].emit("add- message", data);
});
//on client side
socket.emit("private-message", {
"username": userName,
"content": $(this).find("textarea").val()
});
socket.on("add-message", function(data){
notifyMe(data.content,data.username);
});

How can I enforce a maximum number of connections from one user with Socket.IO?

I have a very simple Socket.IO group chat system (this one). I want to prevent people from spamming new connections from their browser using JavaScript trickery. This was a problem the one time I publicized the website.
What measure should I use to prevent this? I want to count the number of connections from a browser and it it goes over a threshold, I want to drop all connections. Should I use the IP address? Is the socket.id unique to a user? How should I do it?
If you want to look at the Socket.IO code, see the highlighted code here.
I had the same issue in a multiplayer game, to prevent a user playing through more than one tabs.
There's so many ways a client can refuse to be identified, and having their way of logging in with more than one ID. But since you only want to prevent a user in the same browser it's relatively easy.
First, use passport for authentication and passport.socketio to "bridge" req.user from Express middleware to socket.request.user
Next, since io.on('connection') will always fire for the latest socket request from the client, you can use this as an opportunity to "expire" any old sockets still connected from the same client. This is where socket.request.user will come in handy to identify whether it's the same user client. Something like this:
io.on('connection', function(socket) {
var user = socket.request.user;
// Suppose this user has connected from another tab,
// then the socket.id from current tab will be
// different from [see *1]
var current_socket_id = socket.id;
var last_socket_id = user.latest_socket_id;
if (last_socket_id) {
// If the user has an existing socket connection
// from the previously opened tab,
// Send disconnection request to the previous socket
io.to(last_socket_id).emit('disconnect');
// client-side will look like
// socket.on('disconnect', function(){
// alert('You connected from a new tab, this tab will close now.')
// window.close();
// });
}
// [*1] current socket.id is stored in the user
user.latest_socket_id = socket.id;
user.save();
});

reuse socket id on reconnect, socket.io, node.js

is it possible to reuse a socket.id or use it multiple times?
Let's assume a user views multiple pages of the same site in different browser tabs. I want to use a single socket.id, socket to handle them all.
If a user receives a notification it should popup on all tabs with a single socket.emit.
It is possible
From the dates of previous responses I assume it might not have been possible in previous versions of socket.io, but I can confirm that I'm successfully reusing socket ids on reconnections with socket.io 2.3.0.
You just need to override io.engine.generateId. Whatever that method returns will be the id assigned to the socket. Here are the docs about generateId.
As far as I've experimented myself, there are two situations when that method is called. During connections and reconnections.
The method io.engine.generateId receives the originating request object as the argument, so we can use it to figure out if we want to reuse the id or get a fresh new one.
Example
As an example, I'll show how you would reuse an id sent from the client, or create a new one when the client doesn't send it. The id will be sent on the handshake request as the query param socketId.
1. Override io.engine.generateId
First you need to override io.engine.generateId, which is the method that assigns IDs. On the server you need to do something like this.
const url = require('url')
const base64id = require('base64id')
io.engine.generateId = req => {
const parsedUrl = new url.parse(req.url)
const prevId = parsedUrl.searchParams.get('socketId')
// prevId is either a valid id or an empty string
if (prevId) {
return prevId
}
return base64id.generateId()
}
That way, whenever you send the query param socketId in the handshake request, it will be set as the socket id. If you don't send it you'll generate a new one using base64id. The reason to use that library in particular is because that's what the original method does. Here you can find the source code.
2. Send the information on the connection request
Once you have that, you need to send the socketId param from the client. This is described in the docs.
const socket = io.connect(process.env.WEBSOCKET_URL, {
query: {
socketId: existingSocketId || ''
}
})
process.env.WEBSOCKET_URL would be the URL where your web socket is listening.
Note that this will work when connecting, but you might want to update the query on reconnection.
3. Send the information on the reconnection request
On the same section of the docs it explains how to update the query params before reconnection. You just need to do something like this.
socket.on('reconnect_attempt', () => {
socket.io.opts.query = {
socketId: existingSocketId || ''
}
});
Just like that you'll be reusing the same socket id as long as it is sent from the client.
Security concerns
It's probably a bad idea to trust information sent from the client to assign the socket id. I'd recommend sending a cryptographically signed payload, storing that payload in the client, and send it back to the server when connecting and reconnecting. That way the server can check that the payload can be trusted by verifying the signature.
Using the same example above, we would send something like this to the client, maybe .on('connect'):
{
socketId: 'foo',
signature: SHA_256('foo' + VERY_SECRET_PASSWORD)
}
The client would store that payload and send it back on connecting or reconnecting, in the same way we were sending socketId before.
Once the server receives the signed payload, inside io.engine.generateId we could check that the signature in the payload matches the hash we produce using the ID and the VERY_SECRET_PASSWORD.
You can't reuse Socket.IO connection IDs since they are created during the client-server handshake, but there are alternative methods. I don't have any examples, but you can modify the Socket.IO client to pass along a query string when the handshake is being performed. Then you can tell the server to handle the client based on the query string, and later fetch all client IDs with a certain query string.
Another method you could use would be to use namespaces. Assuming you have some type of session system, you could create a session-specific namespace, and connect clients with that session ID straight to that namespace.
Multiple sites?
No, that's not possible. It would be possible if you open those sites into iframes in your webapp, I guess.
Another option would be to build a browser plugin that opens a socket connection.

Resources