Reputation: 161
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:
If I then refresh the page, this is what it looks like:
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
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