Mike Roberts
Mike Roberts

Reputation: 161

socket.io output is undefined until I refresh the page, then it shows up?

Ok, I'm stumped.

I have been following some tutorials and have managed to create a basic user registration/login app in nodejs/express. I've also added in a socket.io chat. When the user logs in they are directed to the chat page where they can chat in realtime.

I currently have it set up so that when a user posts a message, we get their username from their session cookie and then use it when inserting the message into mongoDB. When a new message is sent, the socket emits it out to everyone who's connected to the chat.

My problem is that when I send a message, it initially shows up as "undefined: message here" on the screen. If I then refresh the whole page, the actual username shows up like: "Mike: message here". I've console logged the username during the submit routine and it does show up, so I'm baffled as to why it won't print out on the page when I send a message.

Here is a screenshot of how it looks when I send a message:

enter image description here

If I then refresh the page, this is what it looks like:

enter image description here

Here is my app.js:

var mongo = require('mongodb').MongoClient;
var bodyParser = require('body-parser');
var bcrypt = require('bcryptjs');
var csrf = require('csurf');
var path = require ('path');
var express = require('express');
var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');
var ios = require('socket.io-express-session');




var moment = require('moment');
var now = moment().format('L');
var http = require('http');
var connect = require('connect');
var secret = 'mysecret';
var cookie = require('cookie');
var cookieParser = require('cookie-parser');
var session = require('express-session')({

    secret: 'mysecret',
    resave:false,
   saveUninitialized: false,
    stringify:true,
    httpOnly: false

});
 var sharedsession = require("express-socket.io-session");
//var MongoStore = require('connect-mongo')(session);


var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

UserSchema = new Schema({
    //id: ObjectId,

    firstName: String,
    lastName: String,    
    username: {
        type: String,
        unique: true,
        uniqueCaseInsensitive:true
    },
    password: String,
    email: {
        type:String,
         unique: true,
        uniqueCaseInsensitive:true
    },
    accountType: String,
    accountStatus: String,
    acctActivation:{ 
        type:String,
    unique:true
},
joinDate: String

});
UserSchema.plugin(uniqueValidator,{ message: 'Error,  {PATH} {VALUE} has already been registered.\r' });
var User = mongoose.model('User', UserSchema);

var app = express();
app.engine('ejs', require('ejs').renderFile);

app.locals.pretty = true;

//connect to mongo
mongoose.connect('mongodb://localhost/userDB');

//create server

var server = http.createServer(app).listen(3000);
var io = require('socket.io')(server);
console.log('listening on port 3000');


//middleware

app.use(express.static('public'));
app.use(bodyParser.urlencoded({extended:true}));


app.use(session);
  io.use(sharedsession(session, {
  autoSave: true
}));


app.use(csrf());

app.use(function(req,res,next){ // check to see if user already has a session, if so, query mongodb and update the user object
    if(req.session && req.session.user){
        User.findOne({email: req.session.user.email}, function(err, user){
            if(user){
                req.user = user;
                delete req.user.password; // remove password field from session

                req.session.user = req.user;
                res.locals.user = req.user;
            }
            next();
        });
    }else{
        next(); 
    }
});

function requireLogin(req,res,next){ //  check to see if user is logged in, if not, boot em
    if(!req.user){
        res.redirect('/login');
    }else{
        next();
    }
};
function requireAdmin(req,res,next){ // check to see if accountType = Developer (or admin later) - if not, send them to dashboard
    if(req.user.accountType !== 'Developer'){
        res.redirect('/dashboard');
    }else{
        next();
    }
}; 
app.get('/', function(req, res){
    if(req.user){
          res.render('dashboard.ejs');
    }else{
          res.render('index.ejs');
    }

});

app.get('/register', function(req,res){    
    res.render('register.ejs', {csrfToken: req.csrfToken(),
    error:false});
});

app.post('/register', function(req,res){        
    var hash = bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10));

    var user = new User({
        firstName: req.body.firstName,
        lastName: req.body.lastName,
        username: req.body.username,
        password: hash,
        email: req.body.email,
        accountType: 'Standard',
        accountStatus: 'Active',        
        joinDate: now
    });

    user.save(function(err){
        if(err){
            console.log(err);

            res.render('register.ejs', {csrfToken: req.csrfToken(),
                error: err});

    }else{
       req.session.user = user; //set-cookie
        res.redirect('/dashboard');
    }

    }); 

});

app.get('/login', function(req,res){

    res.render('login.ejs', {
        csrfToken: req.csrfToken(),error:false});
});


app.post('/login', function(req, res){
    User.findOne({username: {$regex: new RegExp('^' + req.body.username, 'i')}}, function(err, user){
        if(!user){

            res.render('login.ejs', {error: 'Invalid username or password combination.',
             csrfToken: req.csrfToken()});
        }else{
            if(bcrypt.compareSync(req.body.password, user.password)){

                req.session.user = user; //set-cookie

                res.redirect('/chat');
            }else{

                res.render('login.ejs', {error: 'Invalid username or password combination.',
                 csrfToken: req.csrfToken()});
            }
        }
    });
});

app.get('/dashboard', requireLogin, function(req,res){   
       res.render('dashboard.ejs');
});
app.get('/chat', requireLogin, function(req,res){   
       res.render('chat.ejs');
});
app.get('/admin', requireLogin, requireAdmin, function(req,res){   //required logged in AND admin status
  // var userlist = User.find({});

   User.find({},{},function(err,docs){

       res.render('admin.ejs',{ "userlist": docs

       }); 

   }) ;



      // res.render('admin.ejs');
});

app.get('/logout', function(req,res){
    req.session.destroy();
    res.redirect('/');
});

mongo.connect('mongodb://127.0.0.1/chat', function(err,db){
    if(err) throw err; 




io.on('connection', function(socket){

var userData = socket.handshake.session;

 var chatUserName = userData.user.username;

  socket.emit('status', 'chatting as '+ chatUserName);
    var col = db.collection('messages');
    sendStatus = function(s){
      socket.emit('status', s);  
    }; 

    //emit all messages (shows old room data)
    col.find().sort({_id: -1}).limit(100).toArray(function(err, res){ 
        if(err) throw err;

        socket.emit('output',res);


    }); 

    //wait for input
    socket.on('input', function(data){

      name = chatUserName,
      message = data.message,
      whitespacePattern = /^\s*$/;

      if(whitespacePattern.test(message)){
          sendStatus('You cannot send an empty message');
      }else{
          col.insert({name: chatUserName, message: message}, function(){
              //emit latest message to all clients 

              io.emit('output', [data]);

         sendStatus({
             message: "Message sent",
             clear: true
         });

          });  
      }

    });

});

});

And here is my chat.ejs file which renders the chat page:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Test </title>

        <link rel="stylesheet" href="/css/main.css">


        </head>
     <body>
<h1>Chat</h1>

<div class="chat">

    <div class="chat-messages"><div class="chat-message"></div></div>
    <textarea class="chat-textarea" placeholder="type your message"></textarea>
    <div class="chat-status">Status: <span>Idle</span></div>

</div>
<script src="http://example.com:3000/socket.io/socket.io.js"></script>
<script>
    (function(){
        var getNode = function(s){
            return document.querySelector(s);
        },
        // get required nodes
        status = getNode('.chat-status span'),
        messages = getNode('.chat-messages');
        textarea = getNode('.chat-textarea'),

        statusDefault = status.textContent,


        setStatus = function(s){
            status.textContent = s;

            if(s !== statusDefault){
                var delay = setTimeout(function(){
                  setStatus(statusDefault);
                  clearInterval(delay);
                },3000);
            }
    };

        try{
            var socket = io.connect('http://example.com:3000');
        }catch(e){
            //set status to warn user

        }

        if(socket !== undefined){
            //listen for output

            socket.on('output', function(data){
                data.reverse();
                if(data.length){
                    //loop through results
                    for(var x = 0;x< data.length;x++){

                      var message = document.createElement('div');
                      message.setAttribute('class', 'chat-message');
                       message.textContent = data[x].name + ': ' + data[x].message;

                        //append
                       messages.insertBefore(message, messages.firstChild);
                       messages.appendChild(message);
                       messages.scrollTop = messages.scrollHeight;
          }

                }
            });

            //listen for status
           socket.on('status', function(data){
               setStatus((typeof data === 'object') ? data.message : data);

            if(data.clear === true){
                textarea.value = '';
            }
           });
            //listen for keydown

            textarea.addEventListener('keydown', function(event){
                var self = this;


                if(event.which === 13 && event.shiftKey === false){ //13 is enter, shifKey is shift
                   //send the data
                   socket.emit('input', {

                       message: self.value
                   });

                   event.preventDefault();
                }
            });
        }
    })();
    </script>

If anyone could point out what I'm missing, I would be extremely appreciative! I know that I need to separate my code out into modules, etc and clean it up -- but I was trying to get this part fixed before I did that. I'm new to node and express, so please pardon my ignorance in all of this.

Thank you very much for your time

Upvotes: 0

Views: 978

Answers (1)

thewatcheruatu
thewatcheruatu

Reputation: 342

I'm pretty sure your problem is this:

io.emit('output', [data]);

On new inputs, your data only contains the message:

socket.emit('input', { 
    message: self.value
});

When you refresh, you're getting the values from the database, where you correctly stored the username.

So basically, you could just attach chatUserName to data before you emit it back and that would probably solve your problem.

data.name = chatUserName;
io.emit('output', [data]);

Upvotes: 1

Related Questions