Click here to Skip to main content
15,887,135 members
Please Sign up or sign in to vote.
1.44/5 (2 votes)
I have the following issue: I'm building a chat using Node.js, Socket.IO, and MongoDB. Everything is working perfectly, except for the real-time aspect. For instance, when 'Sebas' sends a message to 'Gene', 'Sebas' can see that their message has been sent, but 'Gene' doesn't see it, and vice versa. Could you please help me? I'm struggling to solve this part.

This is my chat.html, the display for my private rooms.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, 
        initial-scale=1.0">
  <title>Chat Personal</title>
</head>
<body>
  <h1>Chat Personal</h1>
  <p>Conversación ID: <span id="conversationId"></span></p>
  <div id="messages"></div>
  
  <!-- Campo de entrada de texto para mensajes -->
  <input type="text" id="messageInput" 
            placeholder="Escribe un mensaje">
  <!-- Botón para enviar mensajes -->
  <button id="sendButton">Enviar</button>

  <!-- Incluir la biblioteca de Socket.IO -->
  <script src="/socket.io/socket.io.js"></script>
  <script>
    // Inicializar el cliente de Socket.IO
    const socket = io();

    // Obtener elementos HTML por su ID
    const messageInput = document.getElementById('messageInput');
    const sendButton = document.getElementById('sendButton');
    const messagesDiv = document.getElementById('messages');
    const conversationIdSpan = document.getElementById('conversationId');
    
    // Obtener emisor y receptor (de la URL)
    const emisor = getUsernameFromSession();
    const receptor = getReceptorFromURL();
    // Generar el ID de conversación
    const conversacionId = generateConversationId(emisor, receptor);

    // Mostrar el ID de conversación en el HTML
    conversationIdSpan.textContent = conversacionId;

    // Manejar el evento de clic en el botón Enviar
    sendButton.addEventListener('click', () => {
      const message = messageInput.value;
      // Enviar el mensaje personal al servidor
      socket.emit('personal message', 
           { emisor, receptor, conversacionId, mensaje: message });
      // Limpiar el campo de entrada
      messageInput.value = '';
    });

    // Escuchar por mensajes enviados por el usuario actual
    socket.on('personal message sent', (data) => {
      if (data.emisor === emisor) {
        appendMessage(`Tú: ${data.mensaje}`);
      }
    });

    // Escuchar por mensajes recibidos de otros usuarios
    socket.on('personal message received', (data) => {
      if (data.emisor !== emisor) {
        if (data.conversacionId === conversacionId) {
          appendMessage(`${data.emisor}: ${data.mensaje}`);
        }
      }
    });

    // Solicitar el historial de mensajes al cargar la página
    socket.emit('get message history', conversacionId);

    // Escuchar el evento 'message history' 
    // para recibir el historial de mensajes
    socket.on('message history', (messageHistory) => {
      messageHistory.forEach((message) => {
        const messageText = `${message.emisor}: ${message.mensaje}`;
        appendMessage(messageText);
      });
    });

    // Obtener el nombre de usuario desde localStorage
    function getUsernameFromSession() {
      const username = localStorage.getItem('username');
      return username;
    }

    // Obtener el receptor de la URL
    function getReceptorFromURL() {
      const urlParams = new URLSearchParams(window.location.search);
      return urlParams.get('user');
    }

    // Generar el ID de conversación
    function generateConversationId(user1, user2) {
      const sortedUsers = [user1, user2].sort();
      const concatenatedUsers = sortedUsers.join('-');
      const hash = md5(concatenatedUsers);
      return hash;
    }

    // Función simple para calcular el hash MD5
    function md5(str) {
      let md5hash = '';
      for (let i = 0; i < str.length; i++) {
        const char = str.charCodeAt(i);
        md5hash += char.toString(16);
      }
      return md5hash;
    }

    // Función para agregar un mensaje al historial
    function appendMessage(message) {
      const messageElement = document.createElement('p');
      messageElement.textContent = message;
      messagesDiv.appendChild(messageElement);
      // Scroll to the bottom of the messagesDiv 
      // to show the latest message
      messagesDiv.scrollTop = messagesDiv.scrollHeight;
    }
  </script>
</body>
</html>
And this is my socket.js.
JavaScript
// Requerir los modelos necesarios
const Message = require('./models/Message');
const UserModel = require('./models/user');
const ConversacionPersonal = require('./models/ConversacionPersonal');

// Configurar Socket.IO
const setupSocketIO = (io, users) => {
  // Manejar la conexión de un nuevo cliente
  io.on('connection', async (socket) => {
    console.log('Nuevo usuario conectado');

    try {
      // Obtener el historial de mensajes desde la base 
      // de datos y enviarlo al cliente
      const messages = await Message.find().exec();
      socket.emit('chat history', messages);
    } catch (err) {
      console.error('Error al obtener el historial de mensajes:', err);
    }

    // Manejar el evento 'set username' 
    // para establecer el nombre de usuario del cliente
    socket.on('set username', (username) => {
      socket.username = username;
    });

    // Filtrar la lista de usuarios para enviarla al cliente
    const filteredUsers = users.filter((user) => 
                          user.username !== socket.username);
    socket.emit('users', filteredUsers);

    // Manejar el evento 'chat message' 
    // para guardar y emitir mensajes de chat general
    socket.on('chat message', async (msg) => {
      try {
        // Crear y guardar el mensaje en el modelo Message
        const newMessage = new Message
           ({ usuarios: socket.username, message: msg });
        const savedMessage = await newMessage.save();
        console.log('Mensaje guardado en MongoDB:', savedMessage._id);

        // Emitir el mensaje a todos los clientes conectados
        io.emit('chat message', { user: socket.username, message: msg });
      } catch (err) {
        console.error('Error al guardar el mensaje:', err);
      }
    });

    // Manejar el evento 'personal message' 
    // para guardar y emitir mensajes de chat personal
    socket.on('personal message', async (data) => {
      try {
        // Guarda el mensaje en la base de datos, 
        // incluyendo el conversacionId
        const newPersonalMessage = new ConversacionPersonal({
          emisor: data.emisor,
          receptor: data.receptor,
          conversacionId: data.conversacionId,
          mensaje: data.mensaje
        });
        const savedPersonalMessage = await newPersonalMessage.save();
        
        // Emitir el mensaje personal 
        // a los usuarios involucrados en la conversación
        socket.emit('personal message sent', savedPersonalMessage);
        io.to(data.conversacionId).emit
             ('personal message received', savedPersonalMessage);
    
        // Agregar el ID de conversación a la base de datos 
        // (solo si es la primera vez)
        const existingConversation = await ConversacionPersonal.findOne
                           ({ conversacionId: data.conversacionId });
        if (!existingConversation) {
          const newConversation = new ConversacionPersonal({
            emisor: data.emisor,
            receptor: data.receptor,
            conversacionId: data.conversacionId,
            mensaje: `Conversación iniciada con ID: ${data.conversacionId}`
          });
          await newConversation.save();
        }
      } catch (err) {
        console.error('Error al guardar el mensaje personal:', err);
      }
    });
    // Manejar la solicitud de historial 
    // de mensajes y enviarlo al cliente
socket.on('get message history', async (conversacionId) => {
  try {
    const messageHistory = await ConversacionPersonal.find
              ({ conversacionId });
    socket.emit('message history', messageHistory);
  } catch (err) {
    console.error('Error al cargar el historial de mensajes:', err);
  }
});
    // Manejar la desconexión del cliente
    socket.on('disconnect', () => {
      console.log('Usuario desconectado');
    });
  });
};

// Exportar la función de configuración de Socket.IO
module.exports = { setupSocketIO };
My native language is Spanish, but usually the English community has more responses.
I can translate the comments of the code if you need it.

What I have tried:

I've tried a thousand ways to achieve real-time conversation. In short, I need that when a message is sent, the recipient can see it without reloading the page.
Posted
Updated 14-Sep-23 9:48am
v3

It's a well documented sample application for learning WebSocket (using socket.io & react/node.js).

References:
Video: Realtime Chat App with React, Node.js, Socket.io and MongoDB - YouTube[^]
How to Create a Professional Chat API Solution with Sockets in NodeJS [Beginner level][^]
How to Build a Real-time Chat App With NodeJS, Socket.IO, and MongoDB[^]

High level you need:
Quote:

- Create an express server
- Do API validations
- Create basic skeleton for the entire application
- Setting up MongoDB (installation, setup in express)
- Creating users API + Database (Create a user, Get a user by id, Get all users, Delete a user by id)
- JWT (JSON web tokens) authentication (decode/encode) - Login middleware
- Web socket class that handles events when a user disconnects, adds its identity, joins a chat room, wants to mute a chat room
- Define chat room & chat message database model
- Initiate a chat between users
- Create a message in chat room
- See conversation for a chat room by its id

There are many more reference articles if you do a quick web search. Try out and see.
 
Share this answer
 
a Few changes is needed to make sure your message is sent and viewed -
In chat.html -
1) Add a check to see if the user enters a message before sending it.
2) 'Listening' for the 'Enter' key press in the input field to send messages -
HTML
<script src="/socket.io/socket.io.js"></script>
<script>
  //Initialize the client-side Socket.IO instance...
  const socket = io();

  //All your existing socket event listeners code here...

  //Handle the "Enter" key press in the message input field...
  messageInput.addEventListener('keydown', (event) => {
    if (event.key === 'Enter' && !event.shiftKey) {
      event.preventDefault();
      sendButton.click(); //Simulate/automate the click on the send button...
    }
  });
</script>


In 'socket.js'
In your server-side code, you are emitting the 'personal message sent' event to the sender's socket however, you should emit this event to the recipient's socket as well. Make sure that both the sender and recipient join the same room using the 'conversacionId' created -
JavaScript
//All your existing server-side socket code...

//Handle the personal message event...
socket.on('personal message', async (data) => {
  try {
    //Your existing code...

    //Emit the personal message to both sender and recipient...
    socket.emit('personal message sent', savedPersonalMessage);
    socket.to(data.conversacionId).emit('personal message received', savedPersonalMessage);
  } catch (err) {
    console.error('Error al guardar el mensaje personal:', err);
  }
});

//All other existing socket event handlers code here...
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900