Adding data to a socket.io socket object - node.js

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.

Related

Authenticating using ws WebSocket

I'm using ws webSocket (can't use socket.io) and I'm a bit new to it. I know how it works, but don't fully get a couple of things.
How can I authenticate using jwt? The docs say that using the upgrade event is the correct way to go about it, but when the client connected, the code in the upgrade function didn't run.
What's the correct way to add an event type? In socket.io, one can listen using client.on('eventType', function). What's the webSocket ws equivalent?
When and why would one use paths in webSocket ws?
Update
The update event doesn't get fired on my end. Here's the code I have:
File 1
const server = require('http').createServer(app);
require('./socket/socket')(server);
File 2
module.exports = function(server) {
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
server.on('upgrade', function(request, socket, head) {
console.log('Upgraded');
wss.handleUpgrade(request, socket, head, function(ws) {
wss.emit('connection', ws, request);
});
});
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
});
};
How can I authenticate using jwt? The docs say that using the upgrade event is the correct way to go about it, but when the client connected, the code in the upgrade function didn't run.
Well, there are lots of different choices. You could pass a token as a query parameter of a custom header when first making the connection. You could require that the first data sent over the webSocket is a token and disconnect if such a token does not arrive soon and first. If the client was already authenticated and there's an http cookie that indicates that, you could examine that cookie upon websocket connection.
What's the correct way to add an event type? In socket.io, one can listen using client.on('eventType', function). What's the webSocket ws equivalent?
The socket.io message types (or event types as you call them) are something that socket.io adds on top of webSocket. The webSocket protocol itself does not have such a thing. You send a data packet and that data packet arrives at the other end. All data packets are of the same webSocket "type". If you want to identify your data packets as being a certain type of message, then you have to invent a way of doing that inside your data back. For example, if your data was JSON formatted, you could add a type: "someType" property to event message and then examine that property upon receipt (this is essentially what socket.io does although it does it outside of the socket.io payload).
When and why would one use paths in webSocket ws?
You may have two completely separate parts of code that use a webSocket for different things that have nothing in common and reside in separate modules and it's just more straightforward for each to have its own webSocket connection. And, they may be used by two completely separate types of clients.
How to access the orignal URL when a webSocket client connects to your server is shown here on your server's upgrade event used with the ws library.
You will note in that example that the upgrade event occurs on the http server, not on the websocket server instance.
ws provide auth examples.
These work when a HTTP server first handle the auth requests. Then pass a
HTTP upgrade request to ws rather than ws itself listening on a port. Note
the noServer: true option they include in the setup:
const wss = new WebSocket.Server({ clientTracking: false, noServer: true });
The jwt component will be easier using koa or express in HTTP
first then doing the upgrade in this way. Otherwise you would need to
write a ws message handler to look for some token data and verify it.
The message is the event in ws. You get to write anything more specific on top of the message event, which is a big reason to use socket.io that has done all that for you (including client callback functions, which are super helpful).
Using a URI path in the ws:// or http upgrade request would usually be to connect to a separate ws context on the server side. A simple way to namespace or separate connection into discreet groups.

What is the proper way to emit an event with socket.io?

I want to emit an event to the client when a long fucntion comes to an end.
This will show a hidden div with a link - on the client side.
This is the approach i tested:
//server.js
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
require('./app/routes.js')(app, io);
//routes.js
app.post('/pst', function(req, res) {
var url = req.body.convo;
res.render('processing.ejs');
myAsyncFunction(url).then(result => {
console.log('Async Function completed');
socket.emit('dlReady', { description: 'Your file is ready!'});
//do some other stuff here
}).catch(err => {
console.log(err);
res.render('error.ejs');
})
});
I get this
ERROR: ReferenceError: socket is not defined
If i change the socket.emit() line to this:
io.on('connection', function(socket) {
socket.emit('dlReady', { description: 'Your file is ready!'});
});
Then i don't receive an error, but nothing happens at the client.
This is the client code:
<script>
document.querySelector('.container2').style.display = "none";
var socket = io();
socket.on('dlReady', function(data) { //When you receive dlReady event from socket.io, show the link part
document.querySelector('.container1').style.display = "none";
document.querySelector('.container2').style.display = "block";
});
</script>
This whole concept is likely a bit flawed. Let me state some facts about this environment that you must fully understand before you can follow what needs to happen:
When the browser does a POST, there's an existing page in the browser that issues the post.
If that POST is issued from a form post (not a post from Javascript in the page), then when you send back the response with res.render(), the browser will close down the previous page and render the new page.
Any socket.io connection from the previous page will be closed. If the new page from the res.render() has Javascript in it, when that Javascript runs, it may or may not create a new socket.io connection to your server. In any case, that won't happen until some time AFTER the res.render() is called as the browser has to receive the new page, parse it, then run the Javascript in it which has to then connect socket.io to your server again.
Remember that servers handle lots of clients. They are a one-to-many environment. So, you could easily have hundreds or thousands of clients that all have a socket.io connection to your server. So, your server can never assume there is ONE socket.io connection and sending to that one connection will go to a particular page. The server must keep track of N socket.io connections.
If the server ever wants to emit to a particular page, it has to create a means of figuring out which exact socket.io connect belongs to the page that it is trying to emit to, get that particular socket and call socket.emit() only on that particular socket. The server can never do this by creating some server-wide variable named socket and using that. A multi-user server can never do that.
The usual way to "track" a given client as it returns time after time to a server is by setting a unique cookie when the client first connects to your server. From then on, every connection from that client to your server (until the cookie expires or is somehow deleted by the browser) whether the client is connection for an http request or is making a socket.io connection (which also starts with an http request) will present the cookie and you can then tell which client it is from that cookie.
So, my understanding of your problem is that you'd like to get a form POST from the client, return back to the client a rendered processing.ejs and then sometime later, you'd like to communicate with that rendered page in the client via socket.io. To do that, the following steps must occur.
Whenever the client makes the POST to your server, you must make sure there is a unique cookie sent back to that client. If the cookie already exists, you can leave it. If it does not exist, you must create a new one. This can be done manually, or you can use express-session to do it for you. I'd suggest using express-session because it will make the following steps easier and I will outline steps assuming you are using express-session.
Your processing.ejs page must have Javascript in it that makes a socket.io connection to your server and registers a message listener for your "dlready" message that your server will emit.
You will need a top-level io.on('connection', ...) on your server that puts the socket into the session object. Because the client can connect from multiple tabs, if you don't want that to cause trouble, you probably have to maintain an array of sockets in the session object.
You will need a socket.on('disconnect', ...) handler on your server that can remove a socket from the session object it's been stored in when it disconnects.
In your app.post() handler, when you are ready to send the dlready message, you will have to find the appropriate socket for that browser in the session object for that page and emit to that socket(s). If there are none because the page you rendered has not yet connected, you will have to wait for it to connect (this is tricky to do efficiently).
If the POST request comes in from Javascript in the page rather than from a form post, then things are slightly simpler because the browser won't close the current page and start a new page and thus the current socket.io connection will stay connected. You could still completely change the page visuals using client-side Javascript if you wanted. I would recommend this option.

How to set id of a socket in WebSocket?

I'm using: https://github.com/websockets/ws as websockets.
In socket.io you could give an id to your sockets like:
io.emit('id','myText');
io.on('id',function(data){
//read data
});
In websocket on the server you can read sockets by id.
ws.on('id', function (data) {
};
You can read sockets by id on the client too.
ws.addEventListener('id', function (data) {
};
But I couldn't find how to send a socket with a specific id, I've also checked the code base. Am I missing something or is this impossible? Or are there some hacks that could achieve this?
//I want this:
ws.send('id','myText');
I'll format my comments into an answer since it appears to have explained things for you:
There is no handler for your own message names like this in webSocket:
ws.on('id', function(data) { ... });
Instead, you listen for data with this:
ws.on('message', function(data) { ... });
If you want to send a particular message name, you send that inside the data that is sent. For example, data could be an object that you had data.messageName set to whatever you want for each message.
It appears like you're trying to use a socket.io API with a webSocket. That simply doesn't work. socket.io adds a layer on top of webSocket to add things like message names. If you want to use that type of layer with a plain webSocket, you have to implement it yourself on top of a webSocket (or just use socket.io at both ends).

socketIO handshake behavior when connecting to different namespaces

I am having troubles with socketIO (nodejs) and using multiple namespaces. Maybe I misunderstood how to use it, but this is how I understood it should work.
Server side (nodejs)
io.of('/game').on('connection', socket => {
console.log(socket.handshake.query);
console.log('user connected to game socket', socket.id);
})
io.of('/api').on('connection',socket => {
console.log(socket.handshake.query);
console.log('user connected to api socket', socket.id);
}
Client side (browser)
function test (){
gameSocketTest = io.connect('http://localhost:4003/game',{query:{test:'test'}});
apiSocketTest = io.connect('http://localhost:4003/api');
}
The weird thing is that the query (that I only send to the "game" namespace) arrives in both handlers, also the one of the api.
If I inverse the order of the client side code to:
function test (){
apiSocketTest = io.connect('http://localhost:4003/api');
gameSocketTest = io.connect('http://localhost:4003/game',{query:{test:'test'}});
}
the query in the handshake is empty in both handlers server side. So my conclusion is that one way or another the same handshake is used for both connections (however the socket ids are different for both).
Is it "uberhaupt" possible to do what I am trying to do? If so, what am I not understanding with this weird handshake behavior.
In my understanding calling on the client side "io.connect()" should create 2 separate socket io clients, each with their own handshake.
Thanks for any help!
Andries
You must open new connection explicitly as Socket.IO tries to be smart and re-use connections (which causes a lot of connection issues actually) The way around this is use the force new connection option in your io.connect
gameSocketTest = io.connect('http://localhost:4003/game',{query:{test:'test'}});
apiSocketTest = io.connect('http://localhost:4003/api', {'force new connection': true});
From docs
When called, it creates a new Manager for the given URL, and attempts
to reuse an existing Manager for subsequent calls, unless the
multiplex option is passed with false. Passing this option is the equivalent of passing 'force new connection': true

socket.io client connection cannot be made on the 2nd time

Currently, I am implementing an API using nodejs express, then it needs to connect to socket.io and send event.
The API is located in socket.io-client (client), and it connects to socket.io (server)
1st API call: success
The connection is made for the 1st call of the API, message is sent and socket can be disconnected, with the 'disconnect' callback is invoked both on client and server side.
2nd API call: failure
When the API is invoked the 2nd time, the connection to server cannot be made, 'client' callback on client side is not called.
3rd API call: success
Then I tried to restart the client side, keeping other things unchanged. The API is called again, and the connection to socket.io is made successfully and everything is fine.
Can anyone explain the logistics behind this?
Updated
client.js
App.getByUserId(message.to_id, function(error, app) {
var socket = io.connect('http://127.0.0.1:9002');
socket.on('connect', function(){
console.log("client connect socket id:" + socket.id);
console.log("appkey:" + app.private_token);
socket.emit('appkey.check',{appkey: app.private_token, uuid: message.to_id.uuid}, function(data){
socket.emit("forceDisconnect");
socket = null;
});
});
You just hit one of Socket.IO's many "features" or "bugs" depending how you see this. Socket.IO tries to be smart and re-use connections (which causes a lot of connection issues actually) The way around this is use the force new connection option in your io.connect:
io.connect('http://127.0.0.1:9002', { 'force new connection': true });
What you could also do is use https://github.com/primus/primus which wraps Socket.IO if you use the socket.io transformer. Internally, it completely removes the use of the io.connect and uses the much more lower level io.Socket constructor to create more stable connections that you would get with a stock socket.io.
With socket 1.0+, you have to use this for forcing new connection.
io.connect(SERVER_IP, { 'forceNew': true });

Resources