Matching a socket.io socket with an express.js session - javascript

I'm trying to hookup a front-end server to our notification service, and then the front-end server to the browser. The notification service is realtime, and includes the recipients user id in the sent notification data. I'm not having problems with the notification service sending the data and the front-end server receiving it. And I can have it connect and disconnect to the browser just fine, like this:
io.on('connection', function (socket) {
console.log('connected');
socket.on('disconnect', function () {
console.log('disconnected');
});
});
I need to now know what user is signed in to the front-end server so I know which user/socket to send the notification to. We can get this data from our express session if I put the connection listener in a middleware function like so:
var allClients = {};
function useSocket (req, res, next) {
io.on('connection', function (socket) {
allClients[req.session.userId] = socket;
socket.on('disconnect', function () {
console.log('disconnected');
allClients[req.session.userId].removeAllListeners();
// we were also suggested things like socket.close() which did not help
});
});
next();
}
app.get('*', useSocket, function (req, res){
res.render('../views/index');
});
except the code above creates a new connection on every browser refresh, so a user gets a dupe notification for as many times as any browser has connected.
This is my code that talks to the notification service:
var notificationServiceSocket = require('socket.io-client').connect(notificationServiceUrl);
notificationServiceSocket.on('notification', function (data) {
// data.user_id is the intended recipient
});
How can I determine which socket is related to the recipient without creating infinite connections?

Related

Is there a way to trigger the socket.io connection event from NodeJS?

I'm trying some things with socket.io on NodeJS and I can't figure out how to trigger the the socket (only) from NodeJS.
Till now I was using socket.io by calling it from the front end but I wonder if is it possible to do the same thing I did on the front end but this time on the nodeJS part(server side).
My guess is it's not possible because is required a kind of connection(I like to call it a TCP connection,but I'm not sure if that's true or not) and without a second participant in the connection the socket won't work.That's my guess.
So what I'm doing now is :
app.js(server file)
...
const ioLib = require('./path/io.js')(io);
...
...
...
path/io.js(socket file)
module.exports = function(io){
io.on('connection', async function(socket) {
console.log('socket talks : a user connected');
...
...
});
module.exports.io = io;
}
And from an file.ejs file I do :
var socket = io("url");
So with this,let's call it schema,I do the following :
When I access that webpage the 'connection' event is triggered in the sockets.
My question is,and I'm trying to formulate it as simple as I can :
How can I do the same but without a webpage?Is it possible to trigger the sockets inside the NodeJS?
What do you think?
It is possible to connect a Socket.IO server from a stand alone Node.js application (does not really matter where it runs) rather than a web front-end, accessed via a web browser. In order to achieve this, you should use socket.io-client. An example client usage might be as follows:
// Node.js app: client.js
const io = require('socket.io-client');
const socket = io.connect('http://SERVER_IP:SERVER_PORT', {
reconnect: true
});
socket.on('connect', function (socket) {
console.log('Connected to the server!');
});
socket.emit('connected', 'Hi from the client side!');
In this case, your server side application should include something as follows:
// Node.js app: server.js
io.on('connection', function(socket) {
console.log('socket talks: a user connected');
// Print the message that comes from the socket client
socket.on('connected', function (msg) {
console.log(msg);
});
});
As you can see above, fundamentally, the architecture remains the same as server-client. Now, let's go one step further and put all those codes in a single js file, and see how it works:
// server/client together: crazy-socketapp.js
const io_server = require('socket.io').listen(3030);
io_server.sockets.on('connection', function (socket) {
console.log('A client is connected!');
socket.on('connected', function (msg) {
console.log(msg);
});
});
const io_client = require('socket.io-client');
const socket = io_client.connect('http://localhost:3030', {
reconnect: true
});
socket.on('connect', function (socket) {
console.log('Connected to the server!');
});
socket.emit('connected', 'Hi from the client side! ');
The output of the app:
> A client is connected!
> Connected to the server!
> Hi from the client side!
Hope this helps!

Socket doesn't emit within route

I'm building a web app where the view needs to be updated on a data change. For this purpose I'm using socket.io. This is the setup in the server.js file:
const io = require('socket.io')(http);
/* WEB SOCKET INITIALISATION
----------------------------------------- */
io.on('connection', function(socket) {
io.emit('new connection', 'new connection');
socket.on('disconnect', function () {
io.emit('offline', 'offline')
});
});
When looking into my console, it log's 'new connection' as soon as I'm on the page. Now I want to use the socket in one of the routes i've made. To make this possible I bind io to the place where i require the route, like this:
const dashboardRouter = require('./routes/dashboard')(io);
This is how a part of the route looks like:
/* MAKE SOCKET.IO ACCESSABLE
--------------------------------------------------------------- */
module.exports = function(io) {
/* INDEX ROUTE
-------------------------------------------------------------- */
router.get('/', account.checkSession, playwalls.statistics, playwalls.getYours, function(req, res, next) {
io.emit('update', 'testen');
res.locals.username = req.session.username;
res.render('dashboard/index');
});
/* EXPORT ROUTER
--------------------------------------------------------------- */
return router;
}
The problem is that when I emit update, it doesn't log in the console. When I do console.log(io) inside the route, it looks like socket.io is available though. I get no errors inside the terminal console.
This is what the client side JavaScript file looks like:
(function() {
const socket = io();
socket.on('update', function(data) {
console.log('Works');
});
socket.on('new connection', function(data) {
console.log(data);
});
}());
What am I doing wrong here? How can I make sure that the socket emits the message? Hope someone can help me with this!
Thanks in advance.
This route:
router.get('/', account.checkSession, playwalls.statistics, playwalls.getYours, function(req, res, next) {
io.emit('update', 'testen');
res.locals.username = req.session.username;
res.render('dashboard/index');
});
Will broadcast to all the other connected pages, but will not show anything for the particular user that triggered this route. That's because the user that triggered this route is in the middle of requesting and loading a page and they are not yet connected to socket.io.
The sequence of operations for the user who made this requests is as follows:
Browser closes down previous web page which includes disconnecting socket.io and shutting down Javascript.
Browser requests / URL.
Server calls your route handler for that URL.
Your route handler calls io.emit() which sends to all currently connected clients, but the client making this request is not currently connected.
Route handler renders the page and sends it back to the browser.
Browser receives and renders the page
Browser runs Javascript in the page, resulting in socket.io connection being made.
As you can see from this sequence, the browser making the request is not connected with socket.io when you call io.emit(). Basically, you can't emit to the page that is being rendered in the same route that renders it.

Why isn't socket.io callback being triggered inside an express route

I've got the following setup (important bits only for brevity):
app.js
...
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
server.listen(port, function() {
console.log(`Server is listening on port: ${port}`);
});
io.on('connection', function (socket) {
console.log('connection');
});
const routes = require('./routes/index')(io, passport);
app.use('/', routes);
index.js (server)
router.get('/game/:id', isAuthenticated, (req, res) => {
if (req.id)
{
var game = Game.findOne({_id: req.id}, (err, obj) => {
io.on('getGameInfo', (socket) => {
io.emit('gameInfo', obj);
});
res.render('game', obj);
});
}
else
{
// Id not valid, do something
}
});
client:
const socket = io('http://localhost:3000');
socket.on('gameInfo', function(data) {
console.log(data);
}.bind(this));
socket.on('connect', () => {
socket.emit('getGameInfo');
});
So basically I want to emit a getGameInfo call once I know the client has connected, and the getGameInfo listener has been set up in the game route. But when I emit the getGameInfo from the client, the server callback isn't being hit. I'm not sure if I'm missing something obvious, or if this is a closure issue, or if I'm just having one of those days, or if I'm going about this entirely the wrong way.
There are multiple problems here. I'll start by showing the correct way to listen for an incoming socket.io message on the server:
io.on('connection', function (socket) {
// here's where you have a new socket and you can listen for messages
// on that socket
console.log('connection');
socket.on('gameInfo', (data) => {
socket.emit('gameInfo', obj);
});
});
Some of the issues:
On the server, you listen for messages via the socket object, not via the io object. So, you would typically add these event listeners in the io.on('connection', ...) handler because that's where you first see newly connected sockets.
You pretty much never want to add event listeners inside an Express route handler because that is called many times. In addition, at the moment the route handler is called, the browser has not yet received the page and will not yet be connected so even if this was an OK place to do stuff, the page is not yet connected anyway.
When you want to send a message back to just one connection, you send it with socket.emit(), not io.emit(). io.emit() broadcasts to all connected clients which I don't think is what you want.
I'd suggest you not overload the same message name for client and server to mean two different things as this can lead to confusion when reading code or if you ever share some code between client and server. You client is really sending a "getGameInfo" message and then your server responds with a "gameInfo" message that contains the gameInfo.
If, in a route handler, you want to .emit() to the socket from that page which it looks like you are trying to do, then you have to do some work to create a link between the session of the current page and the socket for that page. There are a number of ways to do that. If you're using any session middleware, you can record the socket in the session at the point the socket connects. Then, from your express routes, you can get that socket from the session object at any time.

Update all clients using Socket.io?

Is it possible to force all clients to update using socket.io? I've tried the following, but it doesn't seem to update other clients when a new client connects:
Serverside JavaScript:
I'm attempting to send a message to all clients, which contains the current number of connected users, it correctly sends the amount of users.... however the client itself doesn't seem to update until the page has been refreshed. I want this to happen is realtime.
var clients = 0;
io.sockets.on('connection', function (socket) {
++clients;
socket.emit('users_count', clients);
socket.on('disconnect', function () {
--clients;
});
});
Clientside JavaScript:
var socket = io.connect('http://localhost');
socket.on('connect', function(){
socket.on('users_count', function(data){
$('#client_count').text(data);
console.log("Connection");
});
});
It's not actually sending an update to the other clients at all, instead it's just emitting to the client that just connected (which is why you see the update when you first load)
// socket is the *current* socket of the client that just connected
socket.emit('users_count', clients);
Instead, you want to emit to all sockets
io.sockets.emit('users_count', clients);
Alternatively, you can use the broadcast function, which sends a message to everyone except the socket that starts it:
socket.broadcast.emit('users_count', clients);
I found that using socket.broadcast.emit() will only broadcast to the current "connection", but io.sockets.emit will broadcast to all the clients.
here the server is listening to "two connections", which are exactlly 2 socket namespaces
io.of('/namespace').on('connection', function(){
socket.broadcast.emit("hello");
});
io.of('/other namespace').on('connection',function(){/*...*/});
i have try to use io.sockets.emit() in one namespace but it was received by the client in the other namespace. however socket.broadcast.emit() will just broadcast the current socket namespace.
As of socket.io version 0.9, "emit" no longer worked for me, and I've been using "send"
Here's what I'm doing:
Server Side:
var num_of_clients = io.sockets.clients().length;
io.sockets.send(num_of_clients);
Client Side:
ws = io.connect...
ws.on('message', function(data)
{
var sampleAttributes = fullData.split(',');
if (sampleAttributes[0]=="NumberOfClients")
{
console.log("number of connected clients = "+sampleAttributes[1]);
}
});
You can follow this example for implementing your scenario.
You can let all of clients to join a common room for sending some updates.
Every socket can join room like this:
currentSocket.join("client-presence") //can be any name for room
Then you can have clients key in you sockets which contains multiple client's data(id and status) and if one client's status changes you can receive change event on socket like this:
socket.on('STATUS_CHANGE',emitClientsPresence(io,namespace,currentSocket); //event name should be same on client & server side for catching and emiting
and now you want all other clients to get updated, so you can do something like this:
emitClientsPresence => (io,namespace,currentSocket) {
io.of(namespace)
.to(client-presence)
.emit('STATUS_CHANGE', { id: "client 1", status: "changed status" });
}
This will emit STATUS_CHANGE event to all sockets that have joined "client-presence" room and then you can catch same event on client side and update other client's status.
According to this Broadcasting.
With nodejs server, you can use this:
io.emit('event_id', {your_property: 'your_property_field'});
Be sure to initialise websocket, for example:
var express = require('express');
var http = require('http');
var app = express();
var server = http.Server(app);
var io = require('socket.io')(server);
app.get('/', function (req, res) {
res.send('Hello World!');
io.emit('event_hello', {message: 'Hello Socket'});
});
server.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
In this case, when user reach your server, there will be "event_hello" broadcasted to all web-socket clients with a json object {message: 'Hello Socket'}.

Watch for connection termination on incoming HTTP requests - Node.JS

When making requests in Node.JS to another HTTP server, you can listen for when the server closes the connection with request.on('close', function(){});. I've been searching for similar functionality within Node's http.createServer(). I assumed that that request variable passed in to the callback had an on() function which I could call to listen for when the client closes the connection.
var http = require('http');
http.createServer(function(req, res) {
req.on('close', function() { console.log("Connection closed"); });
}).listen(80, '127.0.0.1');
However, after reading the 0.4.9 docs on streams, I noticed that under the close event it said:
Not all streams will emit this. (For example, an incoming HTTP request will not emit 'close'.)
Is there a way to listen for client connection terminations from the server in Node?
Well, you could access the socket directly by doing:
req.socket.on('close', ...);
However, doing this inside a request listener will bind the event for every request. So instead you should bind to the connection event:
var server = http.createServer();
server.on('request', function(req, res) { ... });
server.on('connection', function(socket) {
socket.on('close', function() {
console.log(socket.remoteAddress + '\'s keep-alive session died!');
});
});
EDIT:
Maybe I should have mentioned, yes, technically the socket object for an http server will carry a reference to the response on the _httpMessage property. However, I would advise against using this as it is part of the internal API and is subject to change without any warning.
It would be better to add your own property to get a reference to the last request made:
server.on('request', function(req, res) {
req.socket.currentRequest = req;
});
server.on('connection', function(socket) {
socket.on('close', function() {
var req = socket.currentRequest;
console.log('Socket closed. Last request for: ' + req.url);
});
});
Socket.io
When you want to know if a socket has closed, which only makes sense when you are do a hanging requests(real-time) I advice you to use socket.io instead, which takes care of all this:
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
io.sockets.emit('this', { will: 'be received by everyone');
socket.on('private message', function (from, msg) {
console.log('I received a private message by ', from, ' saying ', msg);
});
socket.on('disconnect', function () {
sockets.emit('user disconnected');
});
});
The nice thing about socket.io is that it's support by every browser(major) without you having to worry about the little details(which make you go insane). They have abstracted that with a nice API.
Express:
Or when using Express
var app = require('express').createServer();
app.get('/', function(req, res){
req.on('close', function () {
console.log('closed');
});
});
app.listen(3000, '127.0.0.1');

Categories

Resources