Storing custom objects in WebSocket in Node.js - node.js

I'm using a WebSocketServer. When a new client connects I need to store data about that client as a custom object for each client and I want to be able to store it in the WebSocket so when the server receives a new message from that client I can quickly access my custom object from the WebSocket itself. Is there any way to do this or do I just have to create a map for all my objects and lookup the object from the WebSocket each time based on it's ID? Thanks

The Map idea will work to lookup each connection's data, but there's also no reason why you can't just add your own custom property to the webSocket object.
wss.on('connection', socket => {
socket.userInfo = {someProperty: 'someValue'};
socket.on('message', data => {
console.log(socket.userInfo);
});
});
Then, the .userInfo property will be available any time you get a message on that socket object.
Keep in mind that this info is temporal and only good for the lifetime of the socket object. So, if the client disconnects and reconnects (such as when changing web pages), that will generate a new socket object that will not have the previous .userInfo property on it. So, if that information needs to persist longer than the lifetime of one connection, you will have to store it elsewhere and index it by some persistent user property, not just the socket object.

Related

What is the preferred way of persisting data across web socket connections in server memory?

I am making a multiplayer game using web sockets. I'd like client sockets to send data to the server, which is then persisted as part of the game state for a specific room for the session. Other sockets in this room can both access and modify this data.
What is the recommended way to store this data?
What I'm currently doing is storing data for a specific room as properties on a "live" reference to the room, via the io adapter and Map.prototype.get method. A simplified example of this is below:
//Socket.IO server
io.on('connection`, async socket => {
const { roomId, data1} = socket.handshake.query;
await socket.join(roomId);
const store = io.adapter.rooms.get(roomId);
(store.data = store.data || []).push(data1)
socket.on('request-data', () => {
io.to(roomId).emit('show-data', store.data1)
});
})
So, this way, each roomId is associated with it's own store, all new socket connections get access to the store.data for the roomId they provide, and these same sockets can modify the actual store object. The downside IMO is that the actual store is pretty nested and requires the Map.get() method to get access to the live reference.
It just seems to me there should be more formal, well-defined way to persist data between socket connections on the server. Is there a preferred or better way to do this?

get contents of the request object in socket.io

I am new to socket.io and I try to tell apart every client that connects to my node server.
I want to send data to just one client, not broadcast the same data to all of them. So, I have to find a unique piece of each client.
If I got it, I have to do something like
io.on('connection', function (socket) {
var origin = socket.request;
console.log("hey, so , new ws connection is > "+origin);
});
but I get [object, Object].
What are the contents of this object, so I can get something unique like an id. Also what other data this object contains that may come handy in the future?
Thanks
UPDATE
I saw the socket.id. But, everytime I refresh, the id changes. I want something to be stable, otherwise if the client refreshes for some reason, will no longer be related with the first id. Any tips?
every connect have socket.id is unique, if you use socket-io adapter you can use socket handshake info. on Browser for geting socket.id try
$(function () {
setTimeout(function () {
$("#socketId").val(socket.id);
},100);
});
if console.log you will see socket.id in future
You'd use the socket.id property to get hold of the unique identifier for the socket (client socket).
As for other data this object contains, there are plenty that are useful, but the usefulness is dependant on the application. Have a look at the following link for available properties/methods on the socket object. http://socket.io/docs/server-api/
Some very useful methods are emit, join, leave, to, in and properties include id and rooms.

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.

Adding data to a socket.io socket object

I am trying to add some custom information to my socket object on connect, so that when I disconnect the socket, I can read that custom information.
IE:
// (Client)
socket.on('connect', function(data){
socket.customInfo = 'customdata';
});
// (server)
socket.on('disconnect', function () {
console.log(socket.customInfo);
});
Since it is JavaScript you can freely add attributes to any object (just as you did). However socket.io does give you a built-in way to do that (so you won't have to worry about naming conflicts):
socket.set('nickname', name, function () {
socket.emit('ready');
});
socket.get('nickname', function (err, name) {
console.log('Chat message by ', name);
});
Note that this is only on one side (either client or server). Obviously you can't share data between client and server without communication (that's what your example suggests).
The socket in your browser and the socket in the server won't share the same properties if you set them.
Basically you have set the data only at the client side (which is in your browsers memory NOT on the server).
For anyone still looking for an answer, there are a couple of things you can do to share data from the client to the server without actually sending a message.
Add a custom property to the auth property in the client socketIo options. This will be available to the server event handlers in socket.handshake.auth.xxxx.
Add a custom header to the transportOptions.polling.extraHeaders property in the client socketIo options. This will ONLY be presented when the socket.io client is connected via polling and not when "upgraded" to websockets (as you can't have custom headers then).
Add a custom query property to the client socketIo options. I don't recommend this since it potentially exposes the data to intermediate proxies.

Overwriting Backbone.sync for socket.io

Im working on a socket.io based server/client connection instead of ajax.
Client uses Backbone and I overwritten the Backbone.sync function with one
half assed of my own:
Backbone.sync = function (method, collection, options) {
// use the window.io variable that was attached on init
var socket = window.io.connect('http://localhost:3000');
// emit the collection/model data with standard ajax method names and options
socket.emit(method,{collection:collection.name,url:collection.url});
// create a model in the collection for each frame coming in through that connection
socket.on(collection.url,function(socket_frame){
collection.create(socket_frame['model']);
})
};
Instead of ajax calls I simply emit through socket attached to window.io
global var. Server listens to those emits and based on the model url, I don't want to change that behaviour and I use the default crud method names (read,patch...) inside each emited frame. The logic behind it (its a bit far thought, but who knows) that in case the client doesn't support Websockets I can easily fallback to default jQuery ajax. I attached the orginal Backbone.sync to a var so I can pass the same arguments to it when no websocket is available.
All it that greatness behalves properly and the server answers to the client events. The server emits then each model data as a seperate websocket frames in one connection.
I see the frames in the Network/Websocket filter as one (concurrent/established) connection
and things seems to be working
Currently the function assumes I pass a collection and not a model.
Questions:
Is that approach ok with you?
How can I use the socket.io callbacks on 'success' and 'failure' etc in Backbone the right way so I don't have to call the collection.create function 'by-hand'?
Is it better to establish different concurrent connections for models/collections or use the one already established instead?

Resources