How to connect clients with a preset ID on a socket server? - node.js

My setup looks like this:
Client --> AuthenticationService --> REST-API --> MessageBroker(WebSocket) --> Client(s)
When a client is authenticated in the system, it gets a token ID. Now, when any client is changing a value via the REST-API, I want to push this change through websockets to every client BUT the one who changed it.
Therefore I want to filter my socket.clients[] through their token IDs.
When I transmit the change, it is easy: I'll just send the token-ID until the REST-API.
But I need to somehow connect to the socket with the exact same
tokenID. How can I accomplish this? Any best practices here?
That's the test client code:
(function() {
const ws = new WebSocket('ws://localhost:8080');
const id = 123123
ws.onopen = () => {
console.log('websocket is connected ...')
// sending the id to the socket after connecting
ws.send('connected', id)
}
})
But now the problem is: How do I know on the socket which message is meant to transport the id and which are the just normal messages?
I don't want to check EVERY message from the client and see if it's an
id message.
Any help here? What's a good practice to connect clients with a socket with a preset ID?

To answer my own question:
When the client is connecting to the websocket, just pass a parameter to the url:
const webSocket = new WebSocket('ws://127.0.0.1:8080?tokenID=123')
In the backend you can get the tokenID via url.parse:
const id = url.parse(req.url, true).query.tokenID

Related

optimize number of redis connections with a node.js-application

I have a question about redis connections.
I'm developing an app in react native which will use websockets for chat messages. My backend consists of a node.js-app with redis as pubsub mechanism for socket.io.
I'm planning on deploying on heruko. I'm currently on the free hobby plan, which has a limit of 20 connections to redis.
My question now is: how can I optimize my code so that a minimum of connections are used. I'm ofc planning to upgrade my heroku plan once I launch, but then still I want to optimize.
My node.js-code looks like this (simplified):
const Redis = require('ioredis');
const pubClient = new Redis(/* redis url */);
const subClient = new Redis(/* redis url */);
const socketClient = new Redis(/* redis url */);
const io = require('socket.io')(server);
io.on('connection', async (socket) => {
// store socket.id in redis so I can send messages to individual users
// based on the user ID
const userId = socket.handshake.query.userId;
await socketClient.hset('socketIds', userId, socket.id);
socket.on('message', async (data) => {
/**
* data {
* userId,
* message
* }
*/
const data2 = JSON.parse(data);
// get the socket.id based on the user ID
const socketId = await socketClient.hget('socketIds', data2.userId);
// send the message to the correct socket.id
io.to(socketId).emit('message', data.message);
};
});
So when I deploy this code to heroku, when started, it will create 3 connections to the same redis server. But what if 2-3-4-... people connect to this node.js-server? If 2 people connect, will there be 6 redis-connections, or only 3? Like: will the node.js-server initiate every time a users accesses the server 3 new redis connections, or will it always be 3 connections?
I'm trying to track all connections with CLIENT LIST in redis-cli, but I does not give me the correct thing I guess. I was just testing my code with only one user connection to the socket server and it gave me 1 client in redis (instead of 3 connections).
Thanks in advance.
It doesn't matter how many people are using the app, each client instance will have only 1 socket at any time, which means you'll see at most 3 clients per node process.
You see only 1 connection because by default ioredis initiates the connection when the first command is executed, and not when the client is created. You can call client.connect() in order to initiate the socket without executing a command.

Websockets & NodeJS - Changing Browser Tabs & Sessions

I've started writing a node.js websocket solution using socket.io.
The browsers connects to the node server successfully and I get see the socket.id and all config associated with console.log(socket). I also pass a userid back with the initial connection and can see this on the server side to.
Question: I'm not sure the best way to associate a user with a connection. I can see the socket.id changes every page change and when a tab is opened up. How can I track a user and send 'a message' to all required sockets. (Could be one page or could be 3 tabs etc).
I tried to have a look at 'express-socket.io-session' but I'm unsure how to code for it and this situation.
Question: I have 'io' and 'app' variables below. Is it possible to use the 2 together? app.use(io);
Essentially I want to be able to track users (I guess by session - but unsure of how to handle different socket id's for tabs etc) and know how to reply to user or one or more sockets.
thankyou
The best way to handle the situation is rely on SocketIO's rooms. Name the room after the user's unique ID. This will support multiple connections out of the box. Then, whenever you need to communicate with a particular user, simply call the message function and pass in their id, the event, and any relevant data. You don't need to worry about explicitly leaving a room, SocketIO does that for you whenever their session times out or they close their browser tab. (We do explicitly leave a room whenever they log out though obviously)
On the server:
var express = require('express');
var socketio = require('socket.io');
var app = express();
var server = http.createServer(app);
var io = socketio(server);
io.on('connect', function (socket) {
socket.on('userConnected', socket.join); // Client sends userId
socket.on('userDisconnected', socket.leave); // Cliend sends userId
});
// Export this function to be used throughout the server
function message (userId, event, data) {
io.sockets.to(userId).emit(event, data);
}
On the client:
var socket = io('http://localhost:9000'); // Server endpoint
socket.on('connect', connectUser);
socket.on('message', function (data) {
console.log(data);
});
// Call whenever a user logs in or is already authenticated
function connectUser () {
var userId = ... // Retrieve userId somehow
if (!userId) return;
socket.emit('userConnected', userId);
}
// Call whenever a user disconnects
function disconnectUser () {
var userId = ... // Retrieve userId somehow
if (!userId) return;
socket.emit('userDisconnected', userId);
}

Identify a websocket from an ExpressJS request

How can one use an Express POST request as the basis for a socket.io broadcast (instead of a socket message)?
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
app.post('/campaigns', function(req, res, next) {
var campaign = new Campaign(req.body);
campaign.save(function(err, campaign) {
if (err) {
next(err);
} else {
res.json(campaign);
// how to broadcast a message to everyone
// except the sender of this POST request?
io.of('/campaigns').emit('new', campaign);
}
});
});
Very simple.
Each time you establish a connection to server, your socket object will have id associated with this connection. Save it on client side in some variable.
Then each time you post a request to server add socket id to query parameters. Upon receiving POST request on server side broadcast the message to all users and socket id to message. On client side modify on message event by adding a logic to skip/hide received message if client's socket id is equal to socket id received as a extra parameter in message.
Pitfalls
May not work if for some reason after POST request client was disconnected and reconnected to server - in this case upon receiving a message back client will have new socket id that is different from what it had before sending POST request. In that case you will have to come up with some more connection-independent client ID scheme to identify client and use it instead of socket.id as identifier. You may try to use sessions.
Client will still receive message even if it won't be displayed.
Not good to combine client-server "intercommunication" methods and use one from another and vice versa.

Socket.io 1.1 - How to sent data from client when connecting initially

I want functionality similar to this
Client
var socket=io.connect(url,data);
Server
io.on('connection',function(socket){
//get data here in the socket object or in other way
})
I know that the data can be obtained by emitting after connecting but I want to know whether there's a way to send data when connecting initially.
The best you can do is pass data along with the URL
var socket = io.connect(url + "?data=value");
Which will be available in socket.handshake.query on the server
io.on('connection',function(socket){
var data = socket.handshake.query.data; // => "value"
});
Or if you're using passport.socketio you'll have the socket.request.user to identify the user.

Destroying a handshake after logout. socket.io

Hello I am trying to build chat into an application. What I am wondering is when the user logs out of the website how do I also destroy the socket.io handshake associated with that session so the user cannot send messages from say another tab when he is logged out.
I am using expressjs if that is any help.
Well in case anyone ever find this and wants to know I did figure it out.
You can access the sockets disconnect function. I had object of users ids and their socket id so when someone logged out I called
app.get("/logout", function(req,res){
//do other logging out stuff
sockets.disconnectUser(req.session.user_id);
}
// Disconnect User function
sockets.disconnectUser = function(user_id){
sockets.socket(users[user_id]).disconnect();
}
The socket.io object contains information about all connected sockets and the sessionID of each socket. Thus, it is possible to iterate through the connected sockets and disconnect those which are associated with the sessionID that is logging out. There is no need to manually track user and socket ids in this approach.
Example code tested with socket.io#2.2.0, express#4.17.1 and express-session#1.16.2.
const SocketIO = require('socket.io');
let sio = new SocketIO;
app.get('/logout', function(req, res) {
//do other logging out stuff
logoutSocketsIO(req.sessionID);
});
// Iterate through all connected sockets and close those which are associated
// with the given sessionID
// Note: One sessionID can have multiple sockets (e.g. many browser tabs)
function logoutSocketsIO(sessionID) {
let connections = sio.sockets.connected;
for(let c in connections) {
let socketSessionID = connections[c].conn.request.sessionID;
if(sessionID === socketSessionID) {
connections[c].disconnect();
}
}
}

Resources