Socket.io: First Client disconnecting on Second connection - javascript

I am creating a socket.io app. When I create a connection to the socket on firefox, it established just fine. When I am using chrome to init another connection to the socket, the first client gets disconnected and then the two clients reconnect again (with the same socket.id)
This is the code I have been working with:
app.post('/auth', function(req, res){ // routes.auth(hash, db, io, pseudoArray, connections, Session));
var username = req.body.username,
password = req.body.password;
if (username != "" && password != ""){
authenticate(username, password, db, hash, function(err, user){
if (user) {
// Regenerate session when signing in
// to prevent fixation
console.log('user authenticated');
req.session.regenerate(function(){
req.session.user = user.name;
req.session.success = 'Authenticated as ' + user.name
+ ' click to logout. '
+ ' You may now access /restricted.';
var Session = connect.middleware.session.Session;
io.sockets.on('connection', function (socket) {
var hs = socket.handshake;
console.log('A socket with sessionID ' + hs.sessionID + ' connected!');
socket.set('pseudo', req.session.user, function(){
pseudoArray.push(req.session.user);
socket.emit('pseudoStatus', req.session.user);
console.log("user " + req.session.user + " connected");
});
socket.on('pseudoOk', function(data){
// connections[data] = socket;
connections[data] = socket.id; // connected user with its socket.id
connectedUsers[socket.id] = socket;
console.log('----CONNECTIONS----');
console.log(connections);
console.log('++ USERS ++ ');
console.log(connectedUsers);
console.log('----END CONNECTIONS----');
});
socket.on('disconnect', function () {
console.log('A socket with sessionID ' + hs.sessionID + ' disconnected!');
// clear the socket interval to stop refreshing the session
// clearInterval(intervalID);
});
});
res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true });
res.redirect('home');
});
} else {
console.log('auth failed');
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.redirect('login');
}
});
} else {
res.redirect('connect');
}
});
Any clues?
The main question is: Will a socket connection on a single computer (localhost) work within two different browsers on the same machine? What I am thinking is that since I am using two browsers on a single machine, I am getting the same socket id for both the browsers. Is that the case?

You're creating an event listener on the socket.io connection event for every POST request to /auth. That's going to result in undefined behaviour and possibly memory leaks. Express and socket.io are two separate subsystems running within the same server application. As such, they shouldn't be mixed like that.
A 'normal' Express/socket.io setup looks like this:
// your Express routes
app.post('/auth', function(req, res) { ... });
...
// socket.io setup (completely separate)
io.sockets.on('connection', function(socket) {
...
});
If you want to share session objects between Express and socket.io: that isn't trivial. Like I said before, both systems should be considered separate from each other. However, there have been some clever people that got it to work. I don't know how up-to-date that blogpost is, though, and it requires quite in-depth knowledge of both Express and socket.io.

Related

nodejs and undefined variable, possible scope issue

I have started learning Node JS today and have made a chatroom.
When a user connects, it sends their user information to Node and that will store that in a var called user which is created in the anonymous function of io.on('connecting', function ... so I can have multiples of them for multiple users (I think that would work)
When the user disconnects, I remove an object of their user from usersOnline which is indexed by their user.id
I keep getting this error that tells me it is undefined:
Error TypeError: Cannot read property 'id' of undefined
at Socket.<anonymous> (/var/www/html/projects/hbc_chat/index.js:28:27)
The error is where I use delete usersOnline[user.id]
And here is the code:
// server user
var srvUser = {
id: 0,
display_name: 'HabboCreate',
fancy_display_name: 'HabboCreate',
picture: 'http://www.habbocreate.com/userdata/uploads/2f48a19f199bb5f018b3089fd4967902'
};
var usersOnline = {};
io.on('connection', function(socket) {
var user;
socket.on('connected', function(userObj) {
// store user
user = userObj;
// add to users online list
usersOnline[user.id] = user;
// send to clients
updateUsersOnline();
// send joined message
sendMessage(srvUser, user.display_name + ' joined the chat');
console.log(user.display_name + ' connected');
});
socket.on('disconnect', function() {
try {
// remove their user from the online list
delete usersOnline[user.id];
// send to client
updateUsersOnline();
// send left message
sendMessage(srvUser, user.display_name + ' left the chat');
}
catch (err) {
console.log('Error', err);
}
});
....
This does not seem to be a scope issue. Is it possible updateUsersOnline() changes the user info? I just ran this exact code, very similar to yours, without errors:
var usersOnline = {};
io.on('connection', function (socket) {
var user;
socket.on('connected', function (userObject) {
user = userObject;
console.log('user: ' + JSON.stringify(user));
usersOnline[user.id] = user;
console.log(JSON.stringify(usersOnline) + '\n');
});
socket.on('disconnect', function () {
try {
console.log('user: ' + JSON.stringify(user));
delete usersOnline[user.id];
console.log(JSON.stringify(usersOnline) + '\n');
} catch (err) {
console.log('err: ' + err);
}
});
});
I passed a user created like this in the client 'connected' event:
var userObject = {
id: new Date(),
display_name: 'some_name',
};
my_connection.emit('connected', userObject);
After the connected event I closed the connection. This is what the server logged:
user: {"id":"2016-12-17T04:49:05.123Z","display_name":"some_name"}
{"2016-12-17T04:49:05.123Z":{"id":"2016-12-17T04:49:05.123Z","display_name":"some_name"}}
user: {"id":"2016-12-17T04:49:05.123Z","display_name":"some_name"}
{}
No errors, so it seems something is removing the user object or at least its id property in between your connected and disconnect events.
right, well I've figured out what was wrong.
It was where I was on the chatroom page, I would restart the node server, my JavaScript wouldnt emit the user details (It only done so upon load), so when i went to refresh or whatever, disconnect event wouldn't be able to find info in the user variable and error
My fix was to make a request user event client side and then emit that on the server when there's a new connection, as to not rely on the page load sending the details

Single object of socket IO

I am trying to create an application based on socket. Looking at the console it can be said that the client to trying to socket server twice; which should not be allowed.
Is there any way to stop that?
If have modular javascript file where multiple script want to access same socket connection how is that possible?
<!DOCTYPE html>
<html>
<head>
<title>socket</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
</head>
<body>
<script type="text/javascript">
$(function(){
var socket = io();
console.log(typeof socket); // object
var socket1 = io();
console.log(socket === socket1); // false
})
</script>
</body>
</html>
Don't connect the client to socket twice....
A global variable will allow you to access the socket from all your script files
Eg)
//you can access this variable from all your scripts
var socket;
$(function(){
socket = io();
console.log(typeof socket);
})
Note that in javascript polluting the global scope with variables and functions is considered bad practice, but that's for another post.
The obvious suggestion is to stop having your client connect twice.
But, if you're trying to prevent multiple connections on the server and you really don't want any end user to be able to use more than one window at a time to your web site, then you can prevent another connection once there is already a connection.
The key would be to identify each browser with some sort of unique cookie (user ID or uniquely generated ID number). You can then implement socket.io authentication that keeps track of which user IDs already have a connection and if a connection is already live, then subsequent attempts to connect will fail the authentication step and will be disconnected.
Supposed you have a cookie set for each browser that's called userId. Then, here's a sample app that makes sure the userId cookie is present and then uses it to deny more than one socket.io connection:
const express = require('express');
const app = express();
const cookieParser = require('cookie-parser');
const cookie = require('cookie');
app.use(cookieParser());
let userIdCntr = 1;
const tenDays = 1000 * 3600 * 24 * 10;
app.use(function(req, res, next) {
// make sure there's always a userId cookie
if (!req.cookies || !req.cookies.userId) {
// coin unique user ID which consists of increasing counter and current time
let userId = userIdCntr++ + "_" + Date.now();
console.log("coining userId: " + userId);
res.cookie('userId', userId, { maxAge: Date.now() + tenDays , httpOnly: true });
}
next();
});
app.get('/test.html', function (req, res) {
console.log('Cookies: ', req.cookies)
res.sendFile( __dirname + "/" + "test.html" );
});
const server = app.listen(80);
const io = require('socket.io')(server);
// which users are currently connected
var users = new Set();
// make sure cookie is parsed
io.use(function(socket, next) {
if (!socket.handshake.headers.cookieParsed) {
socket.handshake.headers.cookieParsed = cookie.parse(socket.handshake.headers.cookie || "");
}
next();
});
// now do auth to fail duplicate connections
io.use(function(socket, next) {
console.log("io.use() - auth");
// if no userId present, fail the auth
let userId = socket.handshake.headers.cookieParsed.userId;
if (!userId) {
next(new Error('Authentication error - no userId'));
return;
}
// if already logged in
if (users.has(userId)) {
console.log("Failing user " + userId);
next(new Error('Authentication error - duplicate connection for this userId'));
return;
} else {
console.log("adding user " + userId);
users.add(userId);
}
next();
});
io.on('connection', function(socket) {
console.log("socket.io connection cookies: ", socket.handshake.headers.cookieParsed);
socket.on('disconnect', function() {
console.log("socket.io disconnect");
let userId = socket.handshake.headers.cookieParsed.userId;
users.delete(userId);
});
// test message just to see if we're connected
socket.on("buttonSend", function(data) {
console.log("buttonSend: ", data);
});
});
P.S. You really have to think through the situation where a user with more than one computer (like home/work) leaves one computer on and open to your page and thus connected via socket.io and then attempts to user your site from the other computer. If you refused the second connection, this will refuse to allow them access from their second computer if the first computer happened to be left open and on.

Using rooms with socket.io

In the socket.io documentation I see an example of rooms
io.on('connection', function(socket){
socket.on('say to someone', function(id, msg){
socket.broadcast.to(id).emit('my message', msg);
});
});
I have a route /rooms/:roomId.
Is it possible to make the sockets being sent between the server and the client only hits the specific room?
I guess the server should be something like
io.on('connection', function(socket){
socket.on('new msg from client', function(roomId, msg){
io.to(id).emit('new msg from server', msg);
});
});
above and the client should send messages with
socket.emit('new msg from client', roomId, msg);
and get new messages simply with
socket.on('new msg from server', function () {
document.getElementById('msgs').appendChild(...);
});
But will this work? Shouldn't I join the room with socket.join(...) before I can do this?
For a haiku sharing application I made I have something like this:
io.on('connection', function(socket) {
var socket_id = socket.id;
var client_ip = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address.address;
clients.push(socket);
console.info('New client connected (id=' + socket.id + ').');
number_of_clients_connected++;
console.log('[' + client_ip + '] connected, ' + number_of_clients_connected + ' total users online.');
//when a socket is disconnected or closed, .on('disconnect') is fired
socket.on('disconnect', function() {
number_of_clients_connected--;
console.log('[' + client_ip + '] disconnected, ' + number_of_clients_connected + ' total users online.');
//on disconnect, remove from clients array
var index = clients.indexOf(socket);
if (index != -1) {
clients.splice(index, 1);
//console.info('Client gone (id=' + socket.id + ').');
}
});
So it keeps an array of clients and when certain messages need relaying you can specify the client socket ID...
//reads from latest_haikus_cache and sends them
socket.on('request_haiku_cache', function() {
latest_haikus_cache.forEach(function(a_latest_haiku) {
clients[clients.indexOf(socket)].emit('load_haiku_from_cache', a_latest_haiku);
});
});
The server is allowed to broadcast to any room so you can support letting a client ask the server to send to a room without that client being in the room. It really depends upon what you want to do.
So, if you want your server to have this code that lets any client send a message to any room of their choosing:
io.on('connection', function(socket){
socket.on('new msg from client', function(roomId, msg){
io.to(roomId).emit('new msg from server', msg);
});
});
Then, you can indeed do that and it will work. Whether it's appropriate or not is entirely up to your application and whether you want any client to be able broadcast to any room that it has the name of.
But will this work?
Yes, it will work.
Shouldn't I join the room with socket.join(...) before I can do this?
There is no need to have the client join the room unless it wants to receive messages for that room. You don't have be in the room in order to ask the server to send to that room if that's how you want to do it. So, all this is entirely up to your application and what is appropriate.
I have a route /rooms/:roomId.
Is it possible to make the sockets being sent between the server and
the client only hits the specific room?
I could not figure out what this part of your question means. If you care to explain further, I will try to help with this part too.

Nodejs ssh2 run multiple command only one terminal

I am in a trouble while coding ssh2 module in my project. I tried to run multiple commands on one terminal for ruling remote Linux system. For example "bc" command provides you a basic calculator and you can run it for basic operations. but that kind of processes need to be awake when you are using (it will accepts two or more input and it will give a response as a result).
I need to create a system like work with websocket and ssh. When a websocket received a command ,ssh node need to execute this message and Module need to send it's response via websocket.send()
I am using Node.js websocket,ssh2 client.
Here my code :
#!/usr/bin/node
var Connection = require('ssh2');
var conn = new Connection();
var command="";
var http = require('http');
var WebSocketServer = require('websocket').server;
var firstcom=true;
conn.on('ready', function() {
console.log('Connection :: ready');
// conn.shell(onShell);
});
var onShell = function(err, stream) {
// stream.write(command+'\n');
stream.on('data', function(data) {
console.log('STDOUT: ' + data);
});
stream.stderr.on('data', function(data) {
console.log('STDERR: ' + data);
});
}
var webSocketsServerPort=5000;
var ssh2ConnectionControl=false;
var server = http.createServer(function (req, res) {
//blahbalh
}).listen(webSocketsServerPort, function() {
console.log((new Date()) + " Server is listening on port:: " + webSocketsServerPort);
});
//console.log((new Date()) + 'server created');
wsServer = new WebSocketServer({
httpServer: server,
// autoAcceptConnections: false
});
wsServer.on('request', function(request) {
console.log((new Date()) + ' Connection from origin ' + request.origin + '.');
var wsconnection = request.accept('echo-protocol', request.origin);
console.log((new Date()) + ' Connection accepted.');
if(!ssh2ConnectionControl){
conn.connect({
host: 'localhost',
port: 22,
username: 'attilaakinci',
password: '1'
});
ssh2ConnectionControl=true;
console.log('SSH Connected.');
}
wsconnection.on('message', function(message) {
if (message.type === 'utf8') {
console.log('Received Message: ' + message.utf8Data);
command=message.utf8Data;
//if(firstcom){
// conn.shell(onShell);
// firstcom=false;
//}else{
conn.exec(message.utf8Data,onShell);
//}
wsconnection.send(message.utf8Data);
}
else{
console.log('Invalid message');
}
});
wsconnection.on('close', function(reasonCode, description) {
console.log((new Date()) + ' Peer ' + wsconnection.remoteAddress + ' disconnected.');
});
});
You should use conn.shell() instead of conn.exec() if you want a real interactive shell. conn.exec() is typically for executing one-liner commands, so it does not persist "shell state" between conn.exec() calls (e.g. working directory, etc.).
You should also be aware of possible limits by your SSH server has set up as far as how many simultaneous shell/exec requests are allowed per connection. I think the default limit for this on OpenSSH's server is 10.
This is an old question but I wanted to provide a alternative method usings sh2shell which wraps ssh2.shell by mscdex, used above. The example below only covers making the ssh connection, running the commands and processing the result.
Using ssh2shel it is possible to run any number of commands sequentually in the context of the previous commands in the same shell session and then return the output for each command (onCommandComplete event) and/or return all session text on disconnection using a callback function.
See the ssh2shell readme for examples and lots of info. There are also tested scripts for working code examples.
var host = {
//ssh2.client.connect options
server: {
host: 120.0.0.1,
port: 22,
userName: username,
password: password
},
debug: false,
//array of commands run in the same session
commands: [
"echo $(pwd)",
command1,
command2,
command3
],
//process each command response
onCommandComplete: function( command, response, sshObj) {
//handle just one command or do it for all of the each time
if (command === "echo $(pwd)"){
this.emit("msg", response);
}
}
};
//host object can be defined earlier and host.commands = [....] set repeatedly later with each reconnection.
var SSH2Shell = require ('ssh2shell');
var SSH = new SSH2Shell(host),
callback = function( sessionText ){
console.log ( "-----Callback session text:\n" + sessionText);
console.log ( "-----Callback end" );
}
SSH.connect(callback)
To see what is happening at process level set debug to true.

Duplicate Events Socket.io and Node.js over STOMP

I need some help about my node.js+socket.io implementation.
This service expose a server that connects to an ActiveMQ broker over the STOMP protocol, using the stomp-js node.js module to receive events; that then are displayed in a web front end through websockets using socket.io.
So, everything was fine until I started use the Filters feature of ActiveMQ, but this was not the failure point because of my and my team researching, we found the way to ensure the implementation was fine, the problem comes with the connections: So here's the thing, I receive the filters to subscribe, I successfully subscribe to but when I receive a new set of filters is when comes the duplicated, triplicated and more and more messages depending the number of times that I subscribe-unsubscribe to.
So making some debug, I cannot see what's the problem but I'm almost sure that is some bad implementation of the callbacks or the program flow, I'll attach my code to read your comments about it.
Thanks a lot!
var sys = require('util');
var stomp = require('stomp');
var io = require('socket.io').listen(3000);
var socket = io.sockets.on('connection', function (socket) {
var stomp_args = {
port: 61616,
host: 'IP.ADDRESS',
debug: true,
};
var headers;
var client = new stomp.Stomp(stomp_args);
var setFilters = false;
socket.on('filtros', function (message) {
console.log('DEBUG: Getting filters');
if(setFilters){
client.unsubscribe(headers);
}
else{
client.connect();
}
var selector = '';
headers = '';
for(var attributename in message){
console.log(attributename+" : " + message[attributename]);
if(message[attributename] != ''){
selector += ' ' + attributename + '=\'' + message[attributename] + '\' AND ';
}
}
selector = selector.substring(0, selector.length - 4)
console.log('DEBUG: Selector String: ' + selector);
headers = {
destination: '/topic/virtualtopic',
ack: 'client',
selector: selector
};
if(setFilters)
client.subscribe(headers);
client.on('connected', function() {
client.subscribe(headers);
console.log('DEBUG: Client Connected');
setFilters = true;
});
});
var bufferMessage;
client.on('message', function(message) {
console.log("Got message: " + message.headers['message-id']);
var jsonMessage = JSON.parse(message.body);
if(bufferMessage === jsonMessage){
console.log('DEBUG: recibo un mensaje repetido');
return 0;
}
else{
console.log('DEBUG: Cool');
socket.emit('eventoCajero', jsonMessage);
}
client.ack(message.headers['message-id']);
bufferMessage = jsonMessage;
});
socket.on('disconnect', function(){
console.log('DEBUG: Client disconnected');
if(setFilters){
console.log('DEBUG: Consumer disconnected');
client.disconnect();
}
});
client.on('error', function(error_frame) {
console.log(error_frame.body);
});
});
Looking in the Socket.IO documentation, I've found that this is a known issue (I think critical known issue) and they have not fixed it yet. So, to correct this is necessary to reconnect to the socket in the client side to avoid duplicate messages, using:
socket.socket.reconnect();
function to force reconnection explicitly.

Categories