how to change other users socket properties? - node.js

I am using nodejs + socket.io and have chat. Every user, when enters chat, get some data to his socket. For example:
module.exports = function(io) {
var chat = io.of('/chat').on('connection', function (socket) {
socket.on('start-chat', function(data) {
socket.user_name = data.name;
}
});
}
Question: How one user can change socket property of other? For example, i need to change others user socket.user_name, having his socket.id

You can get access to the connected clients and filter them for what ever criteria you need. IIRC you can also access them directly by ID if you happen to have the id with io.sockets.sockets[socket_id]
Another approach is to keep your own record of session. This means you can index using a key that you'd determine your self on each connection. An example:
var clientConnections = {};
sio.on('connection', function (socket) {
var key = <something unique, maybe based on socket.handshake data>;
clientConnections[key] = socket;
}
You can then just access the socket reference else where via the clientConnections hash: clientConnections[<some key].
Once you have that reference you should be able to manipulate the socket as if it was the subject of your event callback.

Related

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

How do I determine when a browser completely disconnects from my site using socket.io?

Because people can open many tabs and use many browsers, I have some troubles of determining when user close all tabs of each browser.
If all tabs are closed, the user is no longer connected so I assume you want to know on the server if he is completely disconnected?
You should hold a list of sockets against a user identifier (login name or similar) on the server, when a new tab is opened it will have a new socket connection so add it to the list.
When a socket connection is closed, remove it from the socket collection for that user.
When the user's last socket connection is closed, you know that the user has completely disconnected.
EDIT with example
something like this (untested and hastily written!)
'use strict';
var userConnections = [];
io.on('connection', function (socket) {
var username = socket.request.user.username;
var existingUser = userConnections.find(function(userConnection){
return userConnection.username === username;
})
if (!existingUser){
existingUser = {
username: username,
sockets: []
}
}
existingUser.sockets.push(socket);
socket.on('disconnect', function () {
var socketIndex = existingUser.indexOf(socket);
existingUser.sockets.splice(socketIndex, 1);
if (existingUser.sockets.length === 0){
//user has completely disconnected
}
});
});
EDIT - after clarification (see comments)
OP has indicated he wishes to know when all connections for a particular browser instance have disconnected.
Since you cannot access any system information about the browser process from javascript I don't see any way of achieving this.
It is possible to detect the browser type (Chrome/IE/Edge etc) on the client and send this information on socket connection. You could then store your socket information referencing this information. However I don't think this is what the OP wants.
Here is my solution, it depends on #Banners's one.
"socket.cookies" stores the browser's cookies
Please let me now if I was missing something.
'use strict';
var userConnections = {};
io.on('connection', function (socket) {
var username = socket.request.user.username;
var visit_id = (socket.cookies.vid) ? socket.cookies.vid : random_unique_id();
//set cookie here
setCookie('vid', visit_id, expire);
if (!userConnections[visit_id])
userConnections[visit_id] = [];
userConnections[visit_id].push(socket.id);
socket.on('disconnect', function () {
var vid = socket.cookies.vid;
if (userConnections[vid]) {
var index = userConnections[vid].indexOf(socket.id);
if (index != -1)
userConnections[vid].splice(index, 1);
if (userConnections[vid].length === 0) {
delete userConnections[vid];
//All tabs have been closed
}
}
});
});

Reconnect socket in disconnect event

I am trying to reconnecct the socket after the disconnect event is fired with same socket.id here is my socket config
var http = require('http').Server(app);
var io = require('socket.io')(http);
var connect_clients = [] //here would be the list of socket.id of connected users
http.listen(3000, function () {
console.log('listening on *:3000');
});
So on disconnect event i want to reconnect the disconnected user with same socket.id if possible
socket.on('disconnect',function(){
var disconnect_id = socket.id; //i want reconnect the users here
});
By default, Socket.IO does not have a server-side logic for reconnecting. Which means each time a client wants to connect, a new socket object is created, thus it has a new id. It's up to you to implement reconnection.
In order to do so, you will need a way to store something for this user. If you have any kind of authentication (passport for example) - using socket.request you will have the initial HTTP request fired before the upgrade happened. So from there, you can have all kind of cookies and data already stored.
If you don't want to store anything in cookies, the easiest thing to do is send back to client specific information about himself, on connect. Then, when user tries to reconnect, send this information again. Something like:
var client2socket = {};
io.on('connect', function(socket) {
var uid = Math.random(); // some really unique id :)
client2socket[uid] = socket;
socket.on('authenticate', function(userID) {
delete client2socket[uid]; // remove the "new" socket
client2socket[userID] = socket; // replace "old" socket
});
});
Keep in mind this is just a sample and you need to implement something a little bit better :) Maybe send the information as a request param, or store it another way - whatever works for you.

How to create one ws-connection from one ip-address?

I want to create only one connection from one ip-address? If there is a connection in the first window, but the user creates a new connection in a second window, then make the change and return to the old connection. Is it possible?
var sessions = {};
io.sockets.on('connection', function (socket) {
var address = socket.handshake.address.address;
if (sessions[address]) {
// what can I do here?
}
}
It's not documented, but you can use socket.disconnect(); to send a disconnect message to the client (graceful disconnection) or you can call socket.disconnect(true); to provide a forceful/abrupt disconnection.
Reference

Save Data on Socket in Socket.IO

I want to save some data on the socket, server side, so whenever the client emits any data to the server, I want that data to be available!
One use case can be storing a token on the socket. When the client is connecting for the first time, it will emit the token, if it has one, or it will show the login page and then the login data will be sent to the server. Whichever one it is, I want to store the token on the server, so that every request after that doesn't need to specify the token.
Later, I'll use RedisStore, so all the data will be accessible all the servers running the app.
My only question is, where do I store the data on the socket so it's associated with that client?
on http://socket.io/#how-to-use
scroll to: Storing data associated to a client
use socket.set and socket.get to set and get data asynchronously
I'm suffering from the same question and guessing what's going on with an example code from socket.io on version 4.x
In the example, They use middleware(use function to register a middleware)
namespace.use((socket, next) => {
// get data from client
const sessionID = socket.handshake.auth.sessionID;
const {userId, username} = yourFunction();
// set socket specific data
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
next();
});
Middlewares are executed when a socket is connected with a server.
and you can use the data afterward
note - Socket.IO reference tells use socket.data for this purpose
namespace.on('connection', socket => {
socket.emit("join", `${socket.username} has been joined`);
})
If you use multiple servers, then you have to keep in mind that the data is only valid for the server
On multiple server environment, You need a single source of data which will be used by socket servers.
namespace.use(async (socket: Socket & { sessionID?: string, userID?: string, username?: string }, next) => {
const sessionID = socket.handshake.auth.sessionID; // [socket.handshake][4]
// or other [socket related attributes][4]
if (sessionID) {
// you have to implement a function to save and retrive session info
const session = await someFunctionToRetrieveSession(sessionID);
if (session) {
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
return next();
}
}
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid username"));
}
socket.sessionID = randomId();
socket.userID = randomId();
socket.username = username;
next();
});
and one more thing as I understood the namespace.use function is called only for the namespace if your client use other namespace then default then default('/') use function will not be called.
//client side
io("/chat");
...
//server side
io.use() // == io.of('/').use() will not be called
io.of('/chat').use() // only will be called
Thanksfully the author of the example implemented a sessionStorage using redis
refer to this example code
with this info, I guess socket.io server saves sockets' info in memory and set a property of a socket will be saved and when the socket comes later the server retrives the socket and it's related data. but because it happens on memory so you can't share the info among other servers that's why you have to find a way to share the data with other servers(eg. redis)
You can save the data on the global variables when you dont want to use any database
var globalVariable = {};
io.sockets.on("connection", function (socket) {
socket.on("save-client-data", function (clientData) {
var clientId = clientData.clientId;
globalVariable[clientId] = JSON.parse(clientHandshakeData);
});
socket.on("get-client-data", function (clientId) {
var clientData = globalVariable[clientId];
socket.emit("get-client-data", JSON.stringify(clientData));
});
});
This worked for my scenario, however I'm not aware of the performance implications.

Resources