Socket.io broadcasts everything to everybody in node/express app - node.js

I wondered if someone could help figure out what I am doing wrong:
My client web page initiates a connection with my server, and listens to a long running process whose state is getting updated in the db by a worker process on another thread, emitting updates back to the browser. I define a socket.io connection in the app.post() method. This is handled by the poll() function below (scroll down a bit past the invite checking code)
However, when a new web client connects, it's messages get added to the previous client's as if there were just one channel. Why isn't there a separate unique channel for each browser?
//Create server
var express = require('express'),
app = express(),
http = require('http'),
server = http.createServer(app),
io = require('socket.io').listen(server);
io.set('log level', 1); // reduce logging
io.configure(function () {
io.set("transports", ["xhr-polling"]);
io.set("polling duration", 10);
});
app.post('/api/users', function (req, res) {
if (!req.body.auth.accessToken) {
req.body.auth.accessToken = req.body.auth.authResponse.accessToken;
} //fb return object is different depending on whether it is a first login or subsequent
logger.log('debug', '/api/users:POST', req.body);
io.sockets.on('connection', function (socket) {
socket = socket;
socket.emit('update', {
status: 200 //send initialization ping
});
//check if user has valid invite, if not try to invite
db.getTotalUserInvites(function (err_inv, res_total) {
db.getUserInvite(req.body.fid, function (err_check, res_check) {
logger.log('debug', 'Total invites issued=' + res_total);
//process report - all we need is accesToken, processReport will do the rest
mine_fb.processUser(req.body.auth.accessToken, socket, function (User,socket) { //pass channel properly
db.getReportStatus(User.fid,socket, function (result,socket) {
logger.log('debug', 'report status', result);
if (result) {
if (socket && (result.report_status == -1)) {
logger.log('debug', 'report already processed. retrieving uniq_id ' + result.uniq_id);
socket.emit('update', {
status: -1,
uniq_id: result.uniq_id
});
return true;
} else {
if (socket && (result.report_status >= 0)) {
logger.log('debug', 'we are in the middle of processing report ' + result.uniq_id);
//in this case we become a listener and not a speaker
function poll(socket) {
db.getReportStatus(User.fid, socket,function (r,socket) {
socket.emit('update', { //!!!! THIS EMITS TO ALL CONNECTED BROWSERS
status: r.report_status,
uniq_id: r.uniq_id
}); //...socket
if ((r.report_status >= 0) && (socket)) {
logger.log('debug', 'polling...');
_.delay(poll, 2000, socket);
}
}); //get rerpot
}; //end poll
socket.on('disconnect', function () {
socket=null;
});
poll(socket);
} // else we're in the middle
} //done checking status
} //end of seq
});
return res.send();
});
});
});
});
});

While it is not clear how to help you I can tell what's going on in your code:
app.post('/api/users', function (req, res) {
// some code
io.sockets.on('connection', function (socket) {
// some code
});
});
Whenever a user POSTs something to /api/users a new handler is attached to io.sockets (that's what .on does). But these handlers are never removed, so each time a new connection is established all attached handlers fire. That's where your broadcasting comes from.
You have to separate app.post(...) from io.sockets.on('connection',...) (they should be independent, both defined at module level, not nested). I'm sure it won't be easy (you will probably have to authenticate a user twice for example) but that's the only reasonable way.

You shouldn't put your io.sockets.on('connection', function (socket) inside the app.post scope.
Just put it outside and try again, it will probably work correctly.
Listening to connexion should be done once when the server starts, not each time a client hits some URL.

Related

Socket.io add listeners in Express 4 routes

I declare socket server in separate module. I have access to the object of the server everywhere in application, for example I can emmit. But I can't add listener in the route, for example:
router.post('/example', function(req, res, next) {
var socketio = req.app.get('sock');
socketio.sockets.on('connection', function (socket) {
socket.on('message', function (text) {
console.log('ok');
});
});
Is there any way to do this?
If there is just a single device that you want to communicate with and it should have already connected to your server and req.app.get('sock') is how you get access to the socketio server object, then you can do it like this:
var theDeviceSocket;
req.app.get('sock').on('connection', function(socket) {
theDeviceSocket = socket;
});
router.post('/example', function(req, res, next) {
if (theDeviceSocket) {
theDeviceSocket.emit("someMsg", "someData");
}
// send whatever response you want to send
res.end();
});
If you were trying to get a response from the single device and return that as the response to the POST, then you could so something like this:
// store the one connection to/from the special device
var theDeviceSocket;
// keep a request cntr so we can tell which response goes with which request
var requestCntr = 0;
req.app.get('sock').on('connection', function(socket) {
theDeviceSocket = socket;
});
router.post('/example', function(req, res, next) {
var timer, thisRequestId;
function gotData(data) {
// if this is our specific response
if (data.requestId === thisRequestId) {
theDeviceSocket.removeListener("someMsgResponse", gotData);
res.send(data);
clearTimeout(timer);
}
}
if (theDeviceSocket) {
theDeviceSocket.on("someMsgResponse", gotData);
thisRequestId = requestCntr++;
theDeviceSocket.emit("someMsg", {requestId: thisRequestId});
// set up a timeout in case we don't get the proper response
timer = setTimeout(function() {
theDeviceSocket.removeListener("someMsgResponse", gotData);
res.send("error");
}, 5000);
}
});
This second scheme is complicated by the fact that you have architected this to be a request/response scheme, but socket.io is not a request/response protocol. So, in order to know which response belongs to which request (when there are potentially multiple clients interacting with the server at the same time), you have to implement some sort of requestId in the data you send and receive over socket.io. This means you have to change the device to echo back the requestId you sent it with its response. All of this would not be necessary if you used a protocol that is designed for request/response like HTTP instead of socket.io.

node.js: How to sync socket receive in 2 different routes

I am still learning node.js basics. My flow is like this,
browser<-->node<-->backend server doing calculation.
node and backend uses socket to communicate.
From the browser there are start/stop buttons to ask backend to start/stop the
calculation.
When node asks backend to start/stop, it must query to see if backend is
alive first.
My code is like this -
app.get('/stopCmd', function(req, res)
{
socketToBackendServer.write("status", function() {
console.log("Sending:", 'Node asking for STATUS');
});
socketToBackendServer.on("data", function() {
if(status is ok) // pseudo code
{
socketToBackendServer.write("stop", function() {
console.log("Sending:", 'Node sending STOP');
});
} else {
console.log("backend server is NOT ready");
}
});
});
app.get('/startCmd', function(req, res)
{
// do similar things as stopCmd
});
/////////////////////////////////////////////////
var socketToBackendServer = net.connect(2899);
function openSocket() {
socketToBackendServer.setKeepAlive(true);
socketToBackendServer.on('connect', onConnect.bind({}, socketToBackendServer));
socketToBackendServer.on('error', onError.bind({}, socketToBackendServer));
}
function onConnect(socket) {
var myData;
console.log('Socket is open!');
socket.on('data', function(data) {
console.log('Received:', data);
io.emit('time', { time: data.toJSON() });
});
}
function onError(socket) {
console.log('Socket error!');
// Kill socket
clearInterval(interval);
socket.destroy();
socket.unref();
// Re-open socket
setTimeout(openSocket, 1e3);
}
openSocket();
server.listen(7778);
if using the same browser, if i go crazy clicking start/stop... for the "
stopCmd", how to make sure when it queries "status", the response is caught
by its function, not "startCmd"'s ?
it's this line
socketToBackendServer.on("data", function()
Thank you again !
You can use multiple connections to the backend server, so one function can freely use one channel, the responses won't mix.
Or you can use a multiplexer function, that you call from both of your functions:
It could work if you can identify your requests, like you send and id with the status, for example socketToBackendServer.write("status 1", ... , and you send the id with the status response back from the backend server (if it yours). In this way you can send multiple requests at the same time, and when the response come, you can identify it, and call the callback function that you stored in an array with the ids.
You only send one request, and you wait for the response before you send another one. You must use a waiting queue, where you store the request, and the callback functions.

Express + Socket.io: find key in query string for every event except one

I'd like permit any socket connection to my server but in the case the client connects without specifying a 'username' in socket.handshake.query, I want to just let him create a new account and then the server should automatically disconnect him.
The only way I could achieve it was validating socket.handshake.query on every event except the signUp event -this means runing validateQuery() on almost every event!-. I feel that this may be a very rough way. I have also tryed to implement this with middleware but I was not able to achieve the desired behaviour (I'm pretty new to Node).
This is my code, please tell me if you know a better way to implement this :)
NOTE: A session handler is not mandatory for this case.
'use strict';
/*** BASE SETUP: Main objects & Dependencies */
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
var io = require('socket.io');
// Set port in which the server will listen
var port = process.env.PORT || 3000;
// Start servers (HTTP is temporal and just for personal tests...)
var io = io.listen(server);
server.listen(port, function() {
console.log('>> Express server listening on port ' + port + ' <<');
});
/*
* A dummy socket validation function.
*/
var validateQuery = function(socket, next) {
console.log("> Verifying username...");
if ( typeof socket.handshake.query.username === 'undefined' || socket.handshake.query.username === "" ) {
console.log("-> Authentification failed!");
socket.disconnect();
return next(new Error("{ code: 403, description: 'You must specify a username in your query' }"));
}
};
/*** SOCKET IO Events */
io.sockets.on('connection', function(socket) {
console.log("> New connection stablished: "+ socket.id);
// # Disconnect
//
socket.on('disconnect', function() {
// clients.splice(clients.indexOf(socket.id), 1);
console.log("> Triggered 'Disconnect' # " + socket.id);
});
// # SignUp - Creates new users
//
socket.on('signUp', function( requestData ) {
console.log("> Triggered 'signUp' # " + socket.id);
socket.emit('onSignUp', { username : requestData.username, dsp : requestData.dsp });
socket.disconnect();
});
// # getUserList - Gets a list of all registered users
//
socket.on('getUserList', function( requestData ) {
// Validate query
validateQuery(socket);
// Send the user's list
socket.emit( 'onGetUserList', userList );
});
// # getUserProfile - Gets a user's profile. Will return the requester's profile if none specified
//
socket.on('getUserProfile', function( requestData ) {
// Validate username
validateQuery(socket);
var userProfile = { username : "User1", dsp : "Ron" };
socket.emit('onGetUserProfile', userProfile);
});
});
If you want to allow the connection without the auth, but then only allow some events to be processed if they are not authenticated, then you will have to check validation on every event that requires auth. There's no other way around it if you're going to allow some events through without auth.
You could make the auth check a little simpler though. When the socket first connects, you couuld do the validation and then set a custom flag on the socket object. From then on, you can just check that flag if an event requires auth (which should be a little more efficient).
If any follow on events might change the auth status, you can then update the flag.
You could also make some shortcuts for your event handlers that require auth.
function addAuthEventListener(sock, event, fn) {
sock.on(event, function (data) {
if ( typeof sock.handshake.query.username === 'undefined' || sock.handshake.query.username === "" ) {
sock.disconnect();
} else {
return fn.apply(this, arguments)
}
});
}
So, then any event handler that requires auth could just be installed like this:
addAuthEventListener(socket, 'onGetUserList', function(requestData) {
// Send the user's list
socket.emit( 'onGetUserList', userList );
});
And the auth checks would happen automatically.

Socket.io connected but not communicating

I have a very simple configuration in a node server with socket.io installed (a little bit more complex but essentially like this one):
var main = require('express')();
server = require('http').createServer(main);
io = require('socket.io')(server);
io.use(function(socket, next) {
console.log("middleware!");
next();
});
io.on('connection', function (socket) {
console.log('connected...');
socket.on('pong', function (data) {
console.log(data.message);
});
setTimeout(function() {
console.log("Saying hello");
socket.emit('ping', { message: 'Hello from server ' + Date.now() });
}, 1000);
});
server.listen(2080, function onCreateServerMain() {
console.log('Server main is listening on port 2080';
console.log('************************************************************');
});
In the client:
var socketIoScript,
loadSocketTimeout,
trialsToLoadSocketIo = 0,
APP_CFG = {baseUrl : "http://192.168.1.13:2080"};
function loadSocketIo(socketIoIp) {
socketIoScript = document.createElement('script');
socketIoScript.setAttribute('src', socketIoIp);
socketIoScript.setAttribute('onload', 'onSocketLoaded();');
document.head.appendChild(socketIoScript);
}
window.onSocketLoaded = function onSocketLoaded() {
if (typeof(io.connect) === 'function') {
var mSocket,
mIoSocket;
$timeout.cancel(loadSocketTimeout);
mIoSocket = new io.Manager(APP_CFG.baseUrl);
mIoSocket.connect(function(socket) {
console.log('Connected!!');
});
mIoSocket.on('error', function onSocketError(e) {
console.log('WebSocket Error ' + error);
});
mIoSocket.on('ping', function onPingReceived(e) {
console.log('Server emitted ping: ' + e.data);
mSocket.emit('pong', 'hi server!');
});
}
}
~(function onLoadSocketTimeout() {
var nextTimeout;
if (trialsToLoadSocketIo < 10) {
nextTimeout = 5000;
} else if (trialsToLoadSocketIo > 60) {
nextTimeout = 60000;
} else {
nextTimeout = 1000 * trialsToLoadSocketIo;
}
if (socketIoScript) {
document.head.removeChild(socketIoScript);
}
loadSocketIo(APP_CFG.baseUrl + '/socket.io/socket.io.js#' + trialsToLoadSocketIo);
loadSocketTimeout = $timeout(onLoadSocketTimeout, nextTimeout);
trialsToLoadSocketIo += 1;
})();
(I'm doing like this because it's mobile app so it may have not connection). I'm testing it with Brackets and Chrome. Server and client are in the same machine. In the app the script is loaded fine and it connects to the server as I can it see in node log (edit: and this is all what I get in the node console):
Server main is listening on port 2080
************************************************************
middleware!
connected...
Saying hello
Edit: in Chrome console I don't get any message, and any breakpoint stops at on listeners. If I stop node, the console for the Chrome immediately starts logging that it has been disconnected:
GET http://192.168.1.13:2080/socket.io/?EIO=3&transport=polling&t=1413066902601-6 net::ERR_CONNECTION_REFUSED
GET http://192.168.1.13:2080/socket.io/?EIO=3&transport=polling&t=1413066906606-7 net::ERR_CONNECTION_REFUSED
But I can't see any incoming message. In the app I don't receive any incoming message. Is there any reason why I could not communicate in this environment even if socket is successfully connected?
EDIT
No app is receiving events sent from the other side. Logs from node show this, logs from Chrome are empty.
EDIT
In Chrome app I don't receive console.log("Connected!");. But neither I receive ERR_CONNECTION_REFUSED errors: I don't receive anything.
EDIT
I managed to get console.log("Connected!"); in the app by changing Manager options:
mIoSocket = new io.Manager(APP_CFG.baseUrl, { autoConnect: false });
As it was auto connecting and the events were attached after connection was made, "Connected" was never reached. But I'm still not receiving any event in any app.
I had a similar issue were event callbacks on the server were not firing when emitting. My event names were ping and pong. As soon as I renamed these events everything worked.
I suspect the event names ping and pong are reserved by socket.io and so cannot be used.
Ok, so a few things :
First, var mSocket doesn't seem to be initialized, so it may be difficult for it to emit() anything (am I missing something?)
Second, when you do :
socket.on('pong', function (data) {
console.log(data.message);
});
the server expects to receive an object containing a message property, eg : data = {message:'hi server'} In your case, you send a string, so data is 'Hi server !' and your log will say 'undefined'. You should change this bit to :
socket.on('pong', function (data) {
console.log(data);
});
and you have a similar problem the other way around, you send an object : { message: 'Hello from server ' + Date.now() }, and are trying to log a data property which does not exist. Change this bit to :
console.log('Server emitted ping: ' + e.message);
And third , you have to listen for events on the socket, not the 'manager'
Client :
mIoSocket.connect(function(socket) {
console.log('Connected!!');
socket.emit('pong');
socket.on('error', function onSocketError(e) {
console.log('WebSocket Error ' + error);
});
socket.on('ping', function onPingReceived(e) {
console.log('Server emitted ping: ' + e.data);
socket.emit('pong', 'hi server!');
});
});
Server :
io.on('connection', function (socket) {
console.log('connected...');
socket.on('pong', function (data) {
console.log(data);
});
setTimeout(function() {
console.log("Saying hello");
socket.emit('ping', { message: 'Hello from server ' + Date.now() });
}, 1000);
});

Socket.io : How do I handle all incoming messages on the server?

I want to be able to handle all messages that are coming in from clients in a single handler.
Example client code:
var socket = io.connect('http://localhost');
socket.emit('news', { hello: 'test' });
socket.emit('chat', { hello: 'test' });
Example server code:
io.sockets.on('connection', function (socket) {
socket.on('message', function (data) {
console.log(data);
}); });
I'd like to be able to log every message even if its sent on news, chat or whatever other name using emit. Is this possible?
Note: The above server code does not work. There is nothing currently logged. I am just wondering if there is a single event which could be handled for all messages for every emit name.
That is possible by overriding socket.$emit function
//Original func
var x = socket.$emit;
socket.$emit = function(){
var event = arguments[0];
var feed = arguments[1];
//Log
console.log(event + ":" + feed);
//To pass listener
x.apply(this, Array.prototype.slice.call(arguments));
};
It's even easier on Socket.Io >3 using the socket.onAny(listener):
this.socket.onAny(m => {
..
});
This is supported out of the box now as of Socket-io 2.0.4, you simply need to register a middle ware (source from socketio-wildcard):
As of Socket.io v2.0.4 (commit), you can use a socket middleware to
catch every incoming Packet, which satisfies most of
socketio-wildcard's use cases.
io.on('connection', (socket) => {
socket.use((packet, next) => {
// Handler
next();
});
});
This is an open issue with Socket.IO.
At this time, if you really need it, you will probably have to fork Socket.IO. See 3rd-Edens comment for how to do it.

Resources