Search code examples
node.jssocket.iochatmern

Socket.io one to one chat


As per the documentation one to one chat happens when we emit the message to the specific socket.id. for example

io.to(socket.id).emit("event","message")

This will only work if two users are connected at the same time. If we are to add chat feature in our application where registered users can chat with each other (1-1).What is the optimal solution for that.I have implemented the chat using io.to(socket.id),but when one of the user is offline socket.id is not available.

-----------------------------------Client-------------------------------

function Chat() {
const [conversations, setConversations] = useState([]);
const [currentChat, setCurrentChat] = useState(null);
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
const [arrivalMessage, setArrivalMessage] = useState(null);
const scrollRef = useRef();
const socket = useRef();
const client = isAuthenticated()

const clientId = isAuthenticated().client._id;
const token = isAuthenticated().client.token
console.log(clientId)



useEffect(() => {
    socket.current = io("ws://localhost:8900");
    socket.current.on("getMessage", (data) => {
      setArrivalMessage({
        sender: data.senderId,
        text: data.text,
        createdAt: Date.now(),
      });
    });
  }, []);



  useEffect(() => {
    arrivalMessage &&
      currentChat?.members.includes(arrivalMessage.sender) &&
      setMessages((prev) => [...prev, arrivalMessage]);
  }, [arrivalMessage, currentChat]);

  useEffect(() => {
    socket.current.emit("addUser",clientId );
  
  }, [client]);



useEffect(() => {
getconversation(clientId,token).then((data)=>{
    if(data.error){
        console.log(data.error)
    }else{
        console.log(data)
        setConversations(data)
        
    }
})
}, [])

useEffect(() => {
const getMessages = async () => {
  try {
    const res = await axios.get("http://localhost:8080/api/messages/" + currentChat?._id);
    setMessages(res.data);
  } catch (err) {
    console.log(err);
  }
};
getMessages();
  }, [currentChat]);


useEffect(() => {
    scrollRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

const handleSubmit = async (e) => {
    e.preventDefault();
    const message = {
      sender:clientId,
      text: newMessage,
      conversationId: currentChat._id,
    };

    const receiverId = currentChat.members.find(
      (member) => member !== clientId
    );

    socket.current.emit("sendMessage", {
      senderId: clientId,
      receiverId,
      text: newMessage,
    });

    try {
      const res = await axios.post("http://localhost:8080/api/messages", message);
      setMessages([...messages, res.data]);
      setNewMessage("");
    } catch (err) {
      console.log(err);
    }
  };

-------------------------Server-----------------------------------------

const io = require("socket.io")(8900, {
cors: {
  origin: "http://localhost:3000",
},
});

 let users = [];

 const addUser = (userId, socketId) => {
 !users.some((user) => user.userId === userId) &&
  users.push({ userId, socketId });
  };

  const removeUser = (socketId) => {
   users = users.filter((user) => user.socketId !== socketId);
   };

  const getUser = (userId) => {
    return users.find((user) => user.userId === userId);
    };

  io.on("connection", (socket) => {
    //when connect
    console.log("a user connected."+socket.id);


    //take userId and socketId from user
    socket.on("addUser", (userId) => {
  
      addUser(userId, socket.id);
      io.emit("getUsers", users);
      });

    //send and get message
    socket.on("sendMessage", ({ senderId, receiverId, text }) => {
    console.log("receiverid=====>>>",receiverId)
    console.log("users ",users)
    const user = getUser(receiverId);
    console.log("userSocket Id",user)
    io.to(user.socketId).emit("getMessage", {
    senderId,
    text,
     });
    });

//when disconnect
socket.on("disconnect", () => {
  console.log("a user disconnected!");
  removeUser(socket.id);
  io.emit("getUsers", users);
});
});

Solution

  • First it is needed to store the messages within your app, along with the details of the sender, receiver and timestamp. Then use the web sockets / socket.io to complement the functionality and inform each user real time, if they are online. This approach is what other chat apps follow eg Slack.

    So the main properties of your app should be,

    1. When user sends a message, the client should post it to the back end/server for storage.
    2. The server should emit through sockets the message. This will get more sophisticated with pub-sub services etc once you get bigger and have clusters.
    3. The users that are online will receive the message and will be rendered on the chat screen.
    4. If users refresh or user is offline and returns, all messages for that user will be fetched from the server.

    About the emitting part, use unique identifiers eg a unique identifier (uuid) of the session generated between one or more users, to target a room (the users should listen to this identifier) or the user ids and emit to each user id (the user can listen to their own user identifier once they are online). The details of the message should be enough to help the app render it on the right screen.