Socket.io session without express.js? - node.js

I want to make a sessionhandling over websockets via node.js and socket.io without necessarily using cookies and avoiding express.js, because there should be also clients not running in a browser environment. Somebody did this already or got some experience with a proof of concept?

Before socket.io connection is established, there is a handshake mechanism, by default, all properly incoming requests successfully shake hands. However there is a method to get socket data during handshake and return true or false depending on your choice which accepts or denies the incoming connection request. Here is example from socket.io docs:
Because the handshakeData is stored after the authorization you can actually add or remove data from this object.
var io = require('socket.io').listen(80);
io.configure(function (){
io.set('authorization', function (handshakeData, callback) {
// findDatabyip is an async example function
findDatabyIP(handshakeData.address.address, function (err, data) {
if (err) return callback(err);
if (data.authorized) {
handshakeData.foo = 'bar';
for(var prop in data) handshakeData[prop] = data[prop];
callback(null, true);
} else {
callback(null, false);
}
})
});
});
The first argument of callback function is error, you can provide a string here, which will automatically refuse the client if not set to null. Second argument is boolean, whether you want to accept the incoming request or not.

This should be helpful, https://github.com/LearnBoost/socket.io/wiki/Authorizing
You could keep track of all session variables and uniquely identify users using a combination of the following available in handshakeData
{
headers: req.headers // <Object> the headers of the request
, time: (new Date) +'' // <String> date time of the connection
, address: socket.address() // <Object> remoteAddress and remotePort object
, xdomain: !!headers.origin // <Boolean> was it a cross domain request?
, secure: socket.secure // <Boolean> https connection
, issued: +date // <Number> EPOCH of when the handshake was created
, url: request.url // <String> the entrance path of the request
, query: data.query // <Object> the result of url.parse().query or a empty object
}
This example may help as well, just have your non-browser clients supply the information in a different way:
SocketIO + MySQL Authentication

Related

Socket.IO: "UnhandledPromiseRejectionWarning: Error: Callbacks are not supported when broadcasting"

im trying to create a login system with Node.js, Socket.IO and MongoDB.
At one point i have to get a certain cookie of the client.
So i "send" an event to the client which should return the cookie so i can work with that data within the same function.
My code is as follows:
Server:
async function checklogin(user) {
user = user;
console.log("user:", user);
await User.find({username:user}).then(function(docs) {
servercookieid = docs[0].cookieid;
servercookiedate = docs[0].cookiedate;
});
io.emit('getCookie', function(responseData) {
console.log(responseData)
}).catch(error)
}
Client:
socket.on('getCookie', function(callback) {
console.log('getting cookie...');
var Cookie = document.cookie;
callback(Cookie)
});
I really dont know why i get this error, because as you can see i am not even broadcasting, sooo...
/shrug
If you need more information, please dont hesitate to ask.
The error message says:
Socket.IO: “UnhandledPromiseRejectionWarning: Error: Callbacks are not
supported when broadcasting”
But in fact when you do io.emit you're broadcasting.
Currently you're broadcasting when you do the command:
io.emit('getCookie', function(responseData) {
console.log(responseData)
}).catch(error)
But for broadcasting you just need set a parameter, it's not a function.
Just fix your code to fix the error.
Changing to:
io.emit('getCookie', responseData);//must set the cookie
by the code you provided responseData is not defined. But by the name it should be a cookie.

Tracking WS Clients in Nodejs

I am using ws through an express plugin, express-ws. My back end needs to track clients and, send message only to a specific or clients. Right now, I am just saving the client socket along with a token. So, when the client connects, a message is sent like the following by the client:
{"path":"/openUserSocket","token":"<CLIENT_TOKEN_VALUE>"}
This registers a socket in the back end. The socket is managed by a class wscManager which, saves the socket in a plain object. So, to register a socket, I do this:
// adding web socket support
const expressWs = require('express-ws')(app, null, {
wsOptions: {
clientTracking: true
}
});
let man = new wscManager();
// then in the web socket handler
app.ws('/', (sck,req) => {
sck.on('message',(msg)=>{
// handle messages
if(msg.path === '/openUserSocket'){
man.set(msg.token, sck);
return sck.send(JSON.stringify({ status: 200, message: 'Ok' }));
}
else if(msg.path === '/<SOME_OTHER_PATH>'){
// use that socket
}
// ... other route handling
}
}
Now, as I showed in the code above, I am trying to save the socket for later use. set just uses the token as a key for saving the socket object in another object. Later, get(token) can be used with that token to use the socket. I also extended the express app prototype to allow other route handles to use the wscManager:
app.response._man = man;
Now, my question is this the right approach to client tracking? In other route handlers, the manager is used like this:
// in route handler
res._man.send(JSON.stringify({ status: <STATUS>, message: <MSG> }));
Thanks for your time and patience.

How to check if ElasticSearch client is connected?

I'm working with elasticsearch-js (NodeJS) and everything works just fine as long as long as ElasticSearch is running. However, I'd like to know that my connection is alive before trying to invoke one of the client's methods. I'm doing things in a bit of synchronous fashion, but only for the purpose of performance testing (e.g., check that I have an empty index to work in, ingest some data, query the data). Looking at a snippet like this :
var elasticClient = new elasticsearch.Client({
host: ((options.host || 'localhost') + ':' + (options.port || '9200'))
});
// Note, I already have promise handling implemented, omitting it for brevity though
var promise = elasticClient.indices.delete({index: "_all"});
/// ...
Is there some mechanism to send in on the client config to fail fast, or some test I can perform on the client to make sure it's open before invoking delete?
Update: 2015-05-22
I'm not sure if this is correct, but perhaps attempting to get client stats is reasonable?
var getStats = elasticClient.nodes.stats();
getStats.then(function(o){
console.log(o);
})
.catch(function(e){
console.log(e);
throw e;
});
Via node-debug, I am seeing the promise rejected when ElasticSearch is down / inaccessible with: "Error: No Living connections". When it does connect, o in my then handler seems to have details about connection state. Would this approach be correct or is there a preferred way to check connection viability?
Getting stats can be a heavy call to simply ensure your client is connected. You should use ping, see 2nd example https://github.com/elastic/elasticsearch-js#examples
We are using ping too, after instantiating elasticsearch-js client connection on start up.
// example from above link
var elasticsearch = require('elasticsearch');
var client = new elasticsearch.Client({
host: 'localhost:9200',
log: 'trace'
});
client.ping({
// ping usually has a 3000ms timeout
requestTimeout: Infinity,
// undocumented params are appended to the query string
hello: "elasticsearch!"
}, function (error) {
if (error) {
console.trace('elasticsearch cluster is down!');
} else {
console.log('All is well');
}
});

Test NodeJS socket.io with custom headers

I'm trying to write simple test to test my NodeJS socket.io app. Problem is that on handshake phase I do require certain values to be there (two cookies and headers). This is what I now do have:
var options = {
transports: ['websocket'],
'force new connection': true,
headers: {'accept-langauge': 'foo'}
};
it("send data", function(done) {
var client = io.connect('http://localhost:3000', options);
client.once("connect", function(s) {
expect(s.handshake).to.not.be(undefined);
expect(s.handshake.headers).to.be.an('object');
expect(s.handshake.headers['accept-language']).to.be('en');
client.once("send_premise_to_snet", function(id) {
id.should.equal("123");
client.disconnect();
done();
});
client.emit("send_data", 123);
});
});
I would like to be able to set accept-language and cookies so that they would appear in handshake and thus would be accessible through handshake property.
In normal browser request browser would fill headers properly, and now I would like to do it in the test phase as well.
After a successful hit with proper Google query words I managed to find out more information. It seems that currently it is impossible to do with current socket.io and socket.io-client implementations.
There exists few alternative approaches taken but none of them have managed to get through to actual packages.
For further information:
https://github.com/Automattic/socket.io/issues/2036
https://github.com/Automattic/socket.io-client/issues/648

Node.js server side connection to Socket.io

I have a Node.js application with a frontend app and a backend app, the backend will manage the list and "push" an update to the frontend app, the call to the frontend app will trigger a list update so that all clients receive the correct list data.
The problem is on the backend side, when I press the button, I perform an AJAX call, and that AJAX call will perform the following code (trimmed some operations out of it):
Lists.findOne({_id: active_settings.active_id}, function(error, lists_result) {
var song_list = new Array();
for (i=0; i < lists_result.songs.length; i++) {
song_list.push(lists_result.songs[i].ref);
}
Song.find({
'_id': {$in: song_list}
}, function(error, songs){
// DO STUFF WITH THE SONGS
// UPDATE SETTINGS (code trimmed)
active_settings.save(function(error, updated_settings) {
list = {
settings: updated_settings,
};
var io = require('socket.io-client');
var socket = io.connect(config.app_url);
socket.on('connect', function () {
socket.emit('update_list', {key: config.socket_key});
});
response.json({
status: true,
list: list
});
response.end();
}
});
});
However the response.end never seems to work, the call keeps hanging, further more, the list doesn't always get refreshed so there is an issue with the socket.emit code. And the socket connection stays open I assume because the response isn't ended?
I have never done this server side before so any help would be much appreciated. (the active_settings etc exists)
I see some issues that might or might not be causing your problems:
list isn't properly scoped, since you don't prefix it with var; essentially, you're creating a global variable which might get overwritten when there are multiple requests being handled;
response.json() calls .end() itself; it doesn't hurt to call response.end() again yourself, but not necessary;
since you're not closing the socket(.io) connection anywhere, it will probably always stay open;
it sounds more appropriate to not set up a new socket.io connection for each request, but just once at your app startup and just re-use that;

Resources