Handle variables from multiple login for real-time chat - node.js

I'm writing a nodejs server that combine a login function and real-time chat. The problem came when I try to get all the users that logged in and connected with my real-time chat server then return to the client (so they can choose a specific user to chat with).
This is my code for log in function. I declared a variable so when client request the /doLogin, the username will be stored in it
var userLogged = {name: null, socketid: null};
app.post('/doLogin', function(req,res){
db.users.findOne({username: req.body.username}, function(err, user) {
if( err ) {
console.log("Login fail");
}
else if (user != null) {
if (req.body.password == user.password) {
req.session.user_role = "user";
userLogged.name = req.body.username;
} else {
req.session.user_role = "null";
}
}
res.send({redirect: "/"});
});
});
And in real-time chat function (using Socket.io), when user connect (after logged in), I will store the socket.id into the variable before, and save that variable in mongodb.
io.sockets.on('connection', function(socket){
var address = socket.handshake.address;
userLogged.socketid = socket.id;
db.clientList.save({name: userLogged.name, socketid: userLogged.socketid}, function(err, saved){
});
console.log("Connection " + address.address + " accepted.");
//
//
socket.on('disconnect', function(){
db.clientList.remove({socketid: userLogged.socketid});
});
});
And the problem is, when other users log in, the variable change so I cannot save the right information in to database.
Please help me!

socket.io allow you to read cookies:
// app.js
io = io.listen(server);
io.set('authorization', function (handshakeData, accept) {
handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);
});
the right way to do is:
cookies --> sessionID --> user --> username
document: http://howtonode.org/socket-io-auth
More simple:
if security is not important to you, you can set username to cookie, then you read username in easily in server: cookies->username
// login handler
res.cookie('username', username)
res.send({redirect: "/"});
// io connection handler
io = io.listen(server);
io.set('authorization', function (handshakeData, accept) {
console.log(handshakeData.headers.cookie)
}
note that, any web user can change their own cookie to fake your name, so this is for test, you should NOT use this for production.

Related

How to send message to specific user using socket_io_client flutter package

I am trying to build an chat apps using socket.io and node.js for backend and flutter for frontend... so far I have been trying to send message to all connected user, is there a way to send message to specific user? here is part of my backend code
io.on('connection', (socket) => {
console.log(`id: ${socket.id}`)
socket.on('send_message', (msg) => {
var detail = JSON.parse(msg)
socket.in(detail["receiver"]).emit('to_user', msg)
})
});
in flutter I am using socket_io_client package (https://pub.flutter-io.cn/packages/socket_io_client) but I don't know how to emit message for specific user
here is part of code for frontend
StreamController<String> _data = StreamController<String>();
socket.on('send_message', (x) {
_data.sink.add(x);
});
sendChat(String msg, String sender, String receiver) {
Map map = {"msg": msg, "snd": sender, "rcv": receiver};
var mapbody = json.encode(map);
socket.emit('send_message', mapbody);
}
Stream<String> get sendChat => _data.stream;
you have to have the socket.id and use io.sockets.socket(SocketId).emit(msg) to send message
var express = require("express");
var redis = require("redis");
var sio = require("socket.io");
var client = redis.createClient()
var app = express.createServer();
var io = sio.listen(app);
io.set("store", new sio.RedisStore);
// In this example we have one master client socket
// that receives messages from others.
io.sockets.on('connection', function(socket) {
// Promote this socket as master
socket.on("I'm the master", function() {
// Save the socket id to Redis so that all processes can access it.
client.set("mastersocket", socket.id, function(err) {
if (err) throw err;
console.log("Master socket is now" + socket.id);
});
});
socket.on("message to master", function(msg) {
// Fetch the socket id from Redis
client.get("mastersocket", function(err, socketId) {
if (err) throw err;
io.sockets.socket(socketId).emit(msg);
});
});
});
some options you have here.
first :
you can store your connected clients ids to redis .
use io-redis :
and store your client id in it:
for storing ids in redis:
await redisClient.lpush(`socket_members`, socket.id);
for getting the specefic id:
let client = await redisClient.lrange(
`socket_members`,
0,
socket.id
);
the second option is you can create an authentication middleware
const jwt = async(socket, next) => {
let token = socket.handshake.query.token
let verify = jwt.verify(socket.handshake.query.token, jwt_encryption, async(err, decoded) => {
// find user with decoded token
if (!user) {
return next(new Error(JSON.stringify({ status: 401, message: 'unuthorized' })));
}
socket.user = user._doc
return next()
})
};
and use it in socket io instance :
io.use(jwt);
the authenticated user is in socket.user.
hope this would help

render dynamic value from passport.socketio to jade view

I'm relatively new to node and socketio. What I'm trying to achieve is authenticate users using passport and send total number logged user count to the view. Such that if a logged in user can see the total number of current-logged-in users and if a user logs that the count decreases or increases when someone else log-ins respectively.
Using passport.socketio to access authenticated passport user information from a socket.io connection.
In the callback, I'm storing the username in a mongoose collection, and accordingly on the logout removing the user from the collection. I get a count of the number of users in the model which I need to pass and bind to the view. Jade being the template engine. Below is how my onAuthorizeSuccess callback looks like where I try to pass the count to home.jade.
function onAuthorizeSuccess(data, accept) {
var username = data.user.username;
var User = mongoose.model('loggedusers', userSchema);
var user = new User({
username: username
});
user.save(function (err, data) {
if (err) console.log(err);
else {
console.log('Saved : ', data);
}
User.count({}, function (err, c) {
console.log('Count is ' + c);
app.get('/', function (req, res) {
res.render('home', {
count: {
countData: c
}
});
});
});
});
console.log('successful connection to socket.io ');
accept(); //Let the user through
}
And in the jade view I try to set it using
li Logged Users ---> #{countData.c}
But, countData is undefined in the view.
How should I go about rendering a dynamic value from the server to the view in jade?
Any assistance much appreciated.
Thanks,
Arnab
Your variable is wrong you should use instead #{count} with:
res.render('home', {count: c});
Figured this out.
Made a function to server content over a socket, that the control helps updating on the front-end
module.exports = function (socket) {
setInterval(function () {
var UserSchema = require('mongoose').model('loggedusers');
UserSchema.count({}, function(err, c){
console.log('Count is ' + c);
socket.emit('send:count', {
count: c
});
});
}, 1000);
};
And the angular controller
angular.module('myApp.controllers', []).
controller('AppCtrl', function ($scope, socket) {
socket.on('send:count', function (data) {
$scope.count = data.count;
});
});
and in jade {{count}} and add div.container(ng-controller='AppCtrl') should give the updated count on the front-end.
Arnab

Socket 1.0 repeating connect multiple times

I'm using Heroku + RedisToGo + Express 4.0 + socket.io 1.0.6.
I just recently upgraded to 1.0 from 0.9, and it's half working now. I've hacked together a app from tutorials but my lack of understanding socket.io is showing, so I'm taking a step back. The first question I have is that now socket.on('connect') is happening repeatedly, without stop, even when a connection is successful. My client-side console.log just keeps going and going. Here's client-side:
// Connect the user
socket.on('connect', function(){
var currentUserId = '<%= currentUser.id %>';
// Add user to redis
socket.emit('login', { userID: currentUserId});
// Retrieve presence info
socket.emit('presence');
});
// Show Presence
socket.on('presence', function(data) {
var userID = data.user;
var presence = data.presence;
if (presence) {
$('#red-dot-' + userID).css("display", "none");
$('#green-dot-' + userID).css("display", "inline");
// Show the hangout button
$('#hangout-' + userID).show();
$('#hangout-unavail-' + userID).hide();
}
else {
$('#red-dot-' + userID).css("display", "inline");
$('#green-dot-' + userID).css("display", "none");
}
});
And server-side:
io.sockets.on('connection', function (socket) {
var savedUserID;
socket.on('login', function(data){
var userID = data.userID;
savedUserID = userID;
// add first user
redis.sadd("users", userID);
redis.hmset("users:"+userID, "socketID", socket.id, "userID", userID);
});
socket.on('presence', function(){
// Get the list of online users and show Presence
redis.smembers("users", function(err,results) {
var onlineUsers = results;
for (var i in onlineUsers) {
var userID = redis.hget("users:"+onlineUsers[i],"userID", function(err,reply) {
var userID = reply;
// Emit presence
io.sockets.emit('presence', {
user: userID,
presence: "true"
});
});
}
});
});
As you can see I'm manually adding users to redis.
I found the answer in this Google Group thread.
The problem occurred because I had socket.emit('presence'); in my socket.on('connect', function(){}); client-side.
This in turn called socket.on('presence', function(){}); server-side, which was the problem.

Socket.io disconnects when refreshed?

I am trying to build a live chat, I am using mongodb and socket.io to store the messages and users.
When a new user is created that user is stored in the mongodb and in the socket object.
If a user refreshes the page the user is removed from the socket object, meaning now in order for that person to get back in they have to create a new username and that generates a new socket.
Here is what my server side code looks like
/*
|--------------------------------------------------------------------------
| Live socket communication with front end:
|--------------------------------------------------------------------------
|
|
*/
var users = {};
io.sockets.on('connection', function (socket) {
// Listen to a new user then emit the user to all clients
socket.on('new user', function (data) {
// Check mongodb to see if the user exists and emit proper message
models.Message.findOne({username:data},function(err,user){
if(err){
console.log('something went wrong')
}
else if(user){
socket.emit('username taken', 'something');
}
else{
socket.emit('create user', data);
socket.userName = data;
socket.connected = true;
users[socket.userName] = socket;
io.sockets.emit('user name', Object.keys(users));
}
});
});
socket.on('facebook id', function(data) {
models.User.findOne({username:data.name}, function(err, user) {
if (user) {
console.log('User already exists');
socket.userName = data.name;
socket.facebook_id = data.id;
socket.connected = true;
users[socket.userName] = socket;
io.sockets.emit('user name', Object.keys(users));
}
else {
var newUser = new models.User({
username: data.name,
facebook_id: data.id
});
newUser.save(function(err, user) {
console.log('successfully inserted user/user: ' + user._id);
});
}
});
});
// Listen to a new message then emit the message to all clients
socket.on('send message', function (data, callback) {
io.sockets.emit('new message', {message: data, username: socket.userName, facebook_id: socket.facebook_id});
});
// Logic when client disconnects
socket.on('disconnect', function (data) {
if(!socket.userName) return;
seeder.disconnect(socket.userName);
delete users[socket.userName]
io.sockets.emit('user disconnected', Object.keys(users));
});
});
You see in my disconnect I remove the socket from the users object.
My question would be is there a way to save the socket info on disconnect then if the same socket tries to connect have it recognize the user and continue?
Additonal: I am thinking maybe I need to focus on creating a user login with mongodb first, then using that log in session data and pass that to the socket, creating a socket object with current database details? Does that sound like something that makes more sense, is that possible?
You can use cookies to identify users. Generate a random hash, put it in cookies, and thus this data will be transferred when establishing connection between client and server.
The client code may look like:
function generateHash(len) {
var symbols = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
var hash = '';
for (var i = 0; i < len; i++) {
var symIndex = Math.floor(Math.random() * symbols.length);
hash += symbols.charAt(symIndex);
}
return hash;
}
if (!/\buser_id=/.test(document.cookie)) { //if no 'user_id' in cookies
document.cookie = 'user_id=' + generateHash(32); //add cookie 'user_id'
}
//here goes establishing connection to server via `io.connect`
On the server-side you can write:
io.sockets.on('connection', function (socket) {
var cookie = socket.handshake.headers.cookie;
var match = cookie.match(/\buser_id=([a-zA-Z0-9]{32})/); //parse cookie header
var userId = match ? match[1] : null;
//...
Thus you have userId variable which is unique for each user. Then you can comment this line:
delete users[socket.userName]
because you should keep the user data.
You may now store your users object with userId (not username) as a key, and on each connection check whether users[userId] != null. And if such user exists, use their socket info

How to keep persistent ftp connection in nodejs

Can you please help me make a connection persistent script. I used jsftp node module to connect to ftp server. What I need to do is to check if the user is already authenticated every time he send a request. Thanks in advance! here's my code:
var Ftp = require('jsftp');
var dumpLog = function (event){
console.log('Code: '+ event.code);
console.log('Message: '+ event.text);
}
var FtpController = {
index : function (req , res) {
res.view('ftp/login');
},
auth : function (req , res){
// Initialize some common variables
var user = req.param('user');
var pass = req.param('pass');
var ftp = new Ftp({
host: req.param('host'),
port: req.param('port') // Defaults to 21
});
ftp.auth( user, pass, function (err , auth_res){
if (err) throw err;
dumpLog(auth_res);
});
res.view('ftp/folder');
},
serve_folder : function(req,res){
res.view('ftp/folder');
},
};
module.exports = FtpController;
Best way to do stuff like this is probably a policy, since you'll want to be able to apply the check to various controllers as you build out your app. Here's what your policy might look like:
// policies/ftpAuthenticated.js
module.exports = function loginToFTP (req, res, next) {
if (req.session.ftpAuthenticated) {
// Onward!
next();
}
else {
// authenticate here (we assume it works in this example)
var success = true;
if (success) {
// Track that the user is connected via ftp for next time
req.session.ftpAuthenticated = true;
// save the connection object
req.session.ftp = theFTPConnectionThing;
next();
}
// if an error occurs, use the default error handler
else {
next( new Error('Sorry, an error occurred authenticating with FTP') );
}
}
}

Resources