5

Im building a chat application in node.js , socket.io and mongoose. My goal is to build a one on one chat, it is similar like facebook web chat. So basically whenever I click on a user it will go to that user url and possibly chat with him/her

But the problem, that I'm facing is that I could only emit the message to the targeted user, but not to myself.

Here's the current code

Serverside

socket.on('chatTo', function(data){
      User.findOne({ username: data.destinationUsername, socketId: { '$ne': null}}, function(err, foundUser) {
        if (foundUser) {
          io.to(foundUser.socketId).emit('incomingChat', { sender: user.username, message: data.message });

        } else {
          io.to(socket.id).emit('deliverError', { error: foundUser + ' is not online' });

        }
      });
    });

Clientside

$(function() {

  var socket = io();


  function chatTo(message, destinationUsername) {
    socket.emit('chatTo', { message: message, destinationUsername });
  }


  $('#sendMessage').submit(function(){
    var input = $('#message').val();
    var username = $('#username').val();
    chatTo(input, username);
    $('#message').val('');
    return false;
  });


  socket.on('incomingChat', function(data) {
    var html = data; // Messages to append to media-list

    $('.media-list').append(html);
  });
});

So what is happening here is that, on the clientside, User A clicks form submit , to submit the message, it will invoke chatTo function and emit the data. Just want to let you guys know, input and username are from html page. It would look something like this

input = "Hello";

username = "jackmoscovi" // user's username

then it will emit both of these data back to server, which is socket.on('chatTo') is listening to. After some MongoDB operation, if found the user then emit to that specific socket.

The result will be

enter image description here

The message that was emitted by batman only shows up on joker's page but not on Batman's page. I know that because I specifically emit to that socket. The real question is How do I force both of these user to be in a unique room? Should I create a new Room mongoose model? and then call socket.join('uniqueRoom')?

and then simply io.to('uniqueRoom').emit("Message?");

How would I implement this room mechanism?

I have been struggling for 2 days already, please someone end this misery :(

Jack Moscovi
  • 2,195
  • 7
  • 30
  • 49

2 Answers2

1

First create a mongoose schema like this

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;
var messageSchema=new Schema({
    sender : {
        type : String,
    },
    receiver : {
        type : String.
    },
    message : {
        type : String
    }
})
mongoose.model('messages',messageSchema);

Then in server code

message = mongoose.model('messages')
socket.on('chatTo', function(data){  // data is json containing sender, receiver and message
    User.findOne({ username: data.destinationUsername, socketId: { '$ne': null}}, function(err, foundUser) {
        if (foundUser) {
            io.to(foundUser.socketId).emit('incomingChat', { sender: user.username, message: data.message });
            socket.emit('incomingChat', { sender: user.username, message: data.message });
            var newMessage = new message({
                sender : data.sender,
                receiver : data.receiver,
                message : data.message
            })
            newMessage.save(function (err, data){
                if(err)
                    console.log(err)
            })
        } else {
            io.to(socket.id).emit('deliverError', { error: foundUser + ' is not online' });

        }
    });
});
Nijeesh
  • 848
  • 7
  • 11
  • The problem with this approach, that I need to save the messages, to a room. So that it would be easier for rendering the data later on. – Jack Moscovi Jan 30 '16 at 13:03
  • well you could simply create a mongoose message schema containing unique usernames of sender,receiver and the message. Something resulting in a structure like::: var messageSchema = new mongoose.Schema({"sender":String, "receiver":String, "message"}); – Nijeesh Jan 30 '16 at 16:54
  • var messageSchema = new mongoose.Schema({"sender":String, "receiver":String, "message":String}); So when you want to fetch messages from database you could query them using sender and receiver fields. Since its a one to ne chat i dont see the point of creating rooms – Nijeesh Jan 30 '16 at 17:01
  • Thanks for the answer, but there is one more problem that I just realized. When it comes to rendering the data from the Message Model, how exactly am I going to render it ? – Jack Moscovi Jan 30 '16 at 21:50
  • Sorry if i don't get you. What exactly do you mean to do when you say render? – Nijeesh Jan 30 '16 at 21:53
  • render the data on HTML. I'm using ejs . For example, how does the forloop will know which message belong to which sender or receiver. If you take a look at facebook chat then you know what im talking about – Jack Moscovi Jan 30 '16 at 22:00
  • You have to write query for that. According to your example users are batman and joker. So write a mongoose query to find all messages from model that have either sender=baman & receiver = joker or vice versa. Result of this query should be displayed in your html view. – Nijeesh Jan 30 '16 at 22:09
-1

This is a little bit of a difficult one to answer because it feels like you are actually asking the wrong question. To understand how you are "supposed" to do this you really have to have solved quite a few other architectural issues first.

The first thing you need to solve is the Authentication/Authorisation problem i.e. when you connect a socket from the browser to the server how does your server know who you are and know that you're not an imposter? I don't have a best practice for this but you might want to check out the socketio-auth package which might give you a few ideas.

The second thing you need to solve is how to associate the authenticated user with the socket. The only source that I could find that has any reference to this is in the chat demo on the socket.io website.

At this point, you should have a collection of sockets in your server that have associated users. When Batman comes online you should check the database for any Chats that he is part of and have him connect to that chat room by id:

Chat.find({usernames: "Batman"}).exec().then(function(chats){
    chats.forEach(function(chat){
        socket.join(chat.id);
    })
});

So this so far is assuming that Joker hasn't already created a chat with Batman, and hence there is not a chat in the database that connects them both.

When Joker wants to chat to batman you need to send a "connect" message down the socket that

  1. creates a chat with both of them in it
  2. finds both their current sockets if they are currently connected to the server and
  3. sends a message that they have connected to each other

example:

socket.on('startChat', function(data){
    //assuming that you have assocated the current user with the socket
    var currentUser = socket.user;

    //who the current user (Joker) wants to connect to
    var connectToUser = data.connectToUser; // Batman

    Chat.create({
        usernames: [currentUser.username, connectToUser.username] // ["Joker", "Batman"]
    }).then(function(newChat){
        socket.join(newChat.id); // add current user (Joker) to that socket.io room

        // this is overly simplified: assuming you have an array collectionOfConnectedSockets which
        // contains all currently connected sockets
        var otherUserSocket = collectionOfConnectedSockets[connectToUser.username];

        //otherUserSocket now is Batman's socket - so make him join the room
        otherUserSocket.join(newChat.id);

        //send them both a message that a chat has been connected
        io.to(newChat.id).emit('You have been connected to a new chat');
    });
});

Now this brushes over a lot of the details, and each of those details depend on your application's architecture. Also my syntax may be a little off, the examples are only given as an indication of you should be trying to do and not as a direct solution to your problem.

real_ate
  • 10,861
  • 3
  • 27
  • 48