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
Related
After receive message from client, I have to connect db(mysql) and save data and need to response the result to client and inform to other(admin) client.
So I need to get current socket client and special client(admin) from the socket list.
Is it possible to get current socket outside of wss connection block?
Thanks.
const WebSocketServer = require('ws');
// Creating a new websocket server
const wss = new WebSocketServer.Server({ port: 8080 });
const clients = new Map();
// Creating connection using websocket
wss.on("connection", ws => {
console.log("new client connected");
client_id = Date.now();
clients.set(client_id, ws);
// sending message
ws.on('message', function(message) {
//wss.broadcast(JSON.stringify(message));
console.log('Received: ' + message);
BuyCoin(message);
//console.log()
});
ws.on("close", () => {
console.log("the client has connected");
});
ws.onerror = function () {
console.log("Some Error occurred")
}
// ws.send('You successfully connected to the websocket.');
});
function BuyCoin(strValue){
const req_info = JSON.parse(strValue);
console.log(req_info.user_id)
console.log('betting!');
var sql = 'SELECT * from users where id = ? LIMIT 1'
connection.query(sql, req_info.user_id, (ws)=>{
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
}
}
You have several options:
#1: You can put the BuyCoin function logic inside the ws scope to make it a local function that is in scope of the ws variable for the current connection like this:
const WebSocketServer = require('ws');
// Creating a new websocket server
const wss = new WebSocketServer.Server({ port: 8080 });
const clients = new Map();
// Creating connection using websocket
wss.on("connection", ws => {
console.log("new client connected");
client_id = Date.now();
clients.set(client_id, ws);
// sending message
function BuyCoin(strValue) {
const req_info = JSON.parse(strValue);
console.log(req_info.user_id)
console.log('betting!');
var sql = 'SELECT * from users where id = ? LIMIT 1'
connection.query(sql, req_info.user_id, (ws) => {
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
}
ws.on('message', function(message) {
//wss.broadcast(JSON.stringify(message));
console.log('Received: ' + message);
BuyCoin(message);
//console.log()
});
ws.on("close", () => {
console.log("the client has connected");
});
ws.onerror = function() {
console.log("Some Error occurred")
}
// ws.send('You successfully connected to the websocket.');
});
#2: You can pass the ws value to your BuyCoin() function as an argument by just changing the function call from this:
BuyCoin(message);
to this:
BuyCoin(ws, message);
And, then changing your function declaration from this:
function BuyCoin(strValue) {...}
to this:
function BuyCoin(ws, strValue) {...}
Is it possible to get current socket outside of wss connection block?
No, there really is no such thing as the current socket. When using asynchronous code in nodejs, lots of different pieces of code can be "in-flight" at the same time so there is no global sense of the current socket. Instead, you have manage data specific to your current operation either by using scope, by passing as an argument or by setting as a properties on some other object that is passed as an argument. Since there is no natural object that BuyCoin() already has access to here that is specific to the user with the activity, then that leaves the first two options (using scope and passing as an argument).
FYI, this code looks a bit problematic because you're allowing the webSocket to send in the user_id that will be operated on without any visible authentication. That exposes you to rogue sockets that can pretend to be users that they aren't.
Also, it doesn't appear you have code that removes webSockets from the clients Map object when they disconnect so that Map object will just get larger and larger and contain lots of dead connections.
Another thing that needs fixing is that your connection.query() code is declaring a callback that does nothing but return another function and it tried to make up a value of ws that would never actually be passed. That function you create inside the callback is never called. Change from this:
connection.query(sql, req_info.user_id, (ws) => {
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
to this:
connection.query(sql, req_info.user_id, (err, rows, fields) => {
//console.log("ix="+ix);
ws.send(rows[0]);
});
And, combine that with one of the above two solutions to get access to the ws value.
So I'm pretty new to it, so I apologize if I this is an entirely wrong approach of doing this, but I am currently programming a little chat application using node.js with express and socket.io. I managed to get a login message logged to the console but I am really struggling with the logout one. When a user opens up the page I take the roomname aswell as the username out of a cookie and send it to the server like shown here:
var socket = io();
var roomname = readCookie('Roomname');
var nickname = readCookie('Nickname');
var data = {'type': 'user', 'channel': roomname, 'user': nickname, 'data': "joined"};
socket.emit('chat', data);
After that I filter the message on the server side and send the answer to all clients like this:
case 'user':
io.emit('chat/' + msg.channel, {type: 'user', user: msg.user, data: msg.data});
break;
I always send the string "chat" followed by the roomname as a channel name so only the users that are in the right rooms can see the message. After that I sort out what kind of message was received by the client side:
case 'user':
console.log(msg.user + " just " + msg.data + " the room!");
break;
Now the only thing I need to do is somehow getting the room- and username to the server side once the user closes the page so I can send the message but with "left". (like this)
var data = {'type': 'user', 'channel': roomname, 'user': nickname, 'data': "left"};
socket.emit('chat', data);
My approach was finding an event that fires the code at the right time and I have been looking through stack overflow for a good hour now and I just couldn't find anything that did the job. I've tryed out all the variations of window.onbeforeunload I could find and none of them seemed to work. I could log some stuff to the console with some of them but I was never able to emit a message to the server side. I am aware that this may not be possible in the way I would like it to, but I really ran out of ideas what to try next. Can someone help me out?
You can add information about the client's username to the socket object, when the client connects for the first time. Then you will be able to use socket.username when client disconnects.
Client:
var socket = io();
var roomname = readCookie('Roomname');
var nickname = readCookie('Nickname');
socket.on('connect', () => {
socket.emit("hello", {nick:nickname, room:roomname});
});
Server:
socket.on("hello", (data) => {
socket.username = data.nick;
socket.join(data.room);
socket.room = data.room;
});
socket.on("disconnect", () => {
var data = {'type':'user', 'channel':socket.room, 'user':socket.username, 'data':'left'};
socket.broadcast.to(socket.room).emit('chat', data};
socket.leave(socket.room);
});
I am using NodeJS with the express and mqtt packages.
Whenever the user pushes the button with the value 'test' a MQTT message should be sent.
However, whenever I send the mqtt message it is send either once when I use 'client.end()' or it keeps on sending the message constantly. I can´t send it twice when I push the button again
I use following code:
module.exports =
{
Send
};
function Send(User){
client.on('connect', function() {
client.publish('alarm/reset', 'Hallo' + Test);
client.end();
});
}
In the '\' following code is used
router.post('/', Authencitation, function(req,res){
var test = req.body.test;
if (test == 'test')
{
reset.Send(req.session.user);
console.log('inside reset');
}
res.redirect('/');
});
However, I alway get inside the function inside reset whenever the button is clicked. It seems it is a mistake made in the function Send(User) but I cannot spot the error.
Following solution worked for me:
function Send(Test){
var mqtt = require('mqtt');
var client = mqtt.connect()
client.on('connect', function() {
client.publish('Test', 'Hallo' + username);
client.end();
});
}
I've the following code working in my server-side, it's all ok. But, I want to keep the same connection between n tabs, because when I open a new tab, looks like I've disconnected from the first tab... So, how can I keep the same connection?
client.js
socket.emit("connected", {user: inputUser.val()};
app.js
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
users[socket.user] = socket;
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
users[userMsg].emit("typing", {user: socket.user});
}
});
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
delete users[socket.user];
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
Update:
The typing event above works fine... So I tried the typing event according to the answer:
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// add this socket to the Set of sockets for this user
if (!users[socket.user]) {
users[socket.user] = new Set();
}
users[socket.user].add(socket);
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
users[userMsg].emit("typing", {user: socket.user});
}
});
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// remove socket for this user
// and remove user if socket count hits zero
if (users[socket.user]) {
users[socket.user].delete(socket);
if (users[socket.user].size === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
But it is giving the following error:
users[userMsg].emit("typing", {user: socket.user});
^
TypeError: users[userMsg].emit is not a function
Update²:
To fix the typing event error, I just changed to:
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
for(let userSet of users[userMsg]) {
userSet.emit("typing", {user: socket.user});
}
}
});
There is no simple way to share a single socket.io connection among multiple tabs in the same browser. The usual model for multiple tabs would be that each tab just has its own socket.io connection.
The opening of a new tab and a new socket.io connection should not, on its own, cause your server to think anything was disconnected. If your code is doing that, then that is a fault in your code and it is probably easier to fix that particular fault.
In fact, if you want to explicitly support multiple tabs and be able to recognize that multiple tabs may all be used by the same user, then you may want to change your server side code so that it can keep track of multiple sockets for a single user, rather than how it is currently coded to only keep track of one socket per user.
If your server code is really just trying to keep track of which users online, then there's probably an easier way to do that by referencing counting each user. I will post a code example in a bit.
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// increment reference count for this user
if (!users[socket.user]) {
users[socket.user] = 0;
}
++users[socket.user];
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// decrement reference count for this user
// and remove user if reference count hits zero
if (users.hasOwnProperty(socket.user)) {
--users[socket.user];
if (users[socket.user] === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
If you need the users object to have the socket object in it, then you can change what is stored in the users object to be a Set of sockets like this:
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// add this socket to the Set of sockets for this user
if (!users[socket.user]) {
users[socket.user] = new Set();
}
users[socket.user].add(socket);
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// remove socket for this user
// and remove user if socket count hits zero
if (users[socket.user]) {
users[socket.user].delete(socket);
if (users[socket.user].size === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
For anyone still having this issue. here is how i fixed it.
let me explain.
once the page refreshes or a new tab is opened, socket dosen't really care so it opens a new connection every time . this is more of a advantage than disadvantage. the best way to tackle the issue is on the server side, once a user logs in with his or her user name , you can send that name along with the query options on the client so it can be used as a unique identifier. in my case i used a token
this.socket = io.connect(`${environment.domain}` , {
query: {token: this.authservice.authToken}
});
then on the server side you can create an empty array to a key and an array of values. the username of the user will be used as a key and the corresponding array of socket as the value. in my own case like i said i used a token
const users = [ ]
socket.nickname = (decoded token username);
users[socket.nickname] = [socket];
then you can perform a simple logic to check if a user already exists in an array, if it does, push the new socket to the array of the user
if ( user.username in users) {
console.log('already exists')
users[user.username].push(socket);
}
if it dosent, just create a new key and add the socket as the key.(make sure its an array because a user can always refresh or open a new tab with the same account and you dont want the chat message to deliver in one tab and not deliver in another)
else {
socket.nickname = username;
users[socket.nickname] = [socket];
}
then to emit a message you simply loop through the array and emit the message accordingly. this way each tab gets the message
socket.on('chat', (data) => {
if (data.to in users) {
for(let i = 0; i < users[data.to].length; i++) {
users[data.to][i].emit('chat', data)
}
for(let i = 0; i < users[data.user].length; i++) {
users[data.user][i].emit('chat', data)
}
}
})
you can add a disconnect logic to remove the socket from the users array too to save memory, so only currently open tabs acre active and closed tabs are removed. i hope it solved your problem
My solution is joining socket to a room with specific user Id.
io.on('connection', async (socket) => {
socket.join('user:' + socket.handshake.headers.uid) // The right way is getting `uid` from cookie/token and verifying user
})
One advantage is sending data to specific user (sending to all tabs)
io.to('user:' + uid).emit('hello');
Hope it's helpful!
I belive the best way is create a channel for the user and unique it by their ID, so, when you need to receive or send something you use the channel and every socket connected to it will receive.
Another solution is to save the flag to localStorage and use eventListener to change localStorage.
Do not connect when another connection exists.
and save message in local storage for send with master tab.
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.