Private messaging - Part III
This guide has four distinct parts:
- Part I: initial implementation
- Part II : persistent user ID
- Part III (current): persistent messages
- Part IV: scaling up
Here's where we were at the end of the 2nd part:
![Chat (v2)](/images/private-messaging-part-2-chat.gif)
All is working pretty well, but there is a last issue which is quite annoying:
- when the sender gets disconnected, all the packets it sends are buffered until reconnection (which is great in this case)
![Chat with sender that gets disconnected](/images/private-messaging-part-2-sender-offline.gif)
- but when the recipient gets disconnected, the packets are lost, since there is no listening Socket instance in the given room
![Chat with recipient that gets disconnected](/images/private-messaging-part-2-recipient-offline.gif)
There are multiple solutions to this problem, and we will go for the easiest to implement: store all the messages on the server-side.
Note: this 3rd part will be brief, but it underlines an important property of Socket.IO: you cannot rely on the status of the connection. It should be up most of the time but there is a myriad of things that can kill a TCP connection (this is particularly true on mobile browsers).
#
InstallationLet's checkout the branch for part III:
git checkout examples/private-messaging-part-3
Here's what you should see in the current directory:
├── babel.config.js├── package.json├── public│ ├── favicon.ico│ ├── fonts│ │ └── Lato-Regular.ttf│ └── index.html├── README.md├── server│ ├── index.js (updated)│ ├── messageStore.js (created)│ ├── package.json│ └── sessionStore.js└── src ├── App.vue ├── components │ ├── Chat.vue (updated) │ ├── MessagePanel.vue │ ├── SelectUsername.vue │ ├── StatusIcon.vue │ └── User.vue ├── main.js └── socket.js
The complete diff can be found here.
#
How it works#
Persistent messagesOn the server-side (server/index.js
), we now persist the message in our new store:
io.on("connection", (socket) => { // ... socket.on("private message", ({ content, to }) => { const message = { content, from: socket.userID, to, }; socket.to(to).to(socket.userID).emit("private message", message); messageStore.saveMessage(message); }); // ...});
And we fetch the list of messages upon connection:
io.on("connection", (socket) => { // ... const users = []; const messagesPerUser = new Map(); messageStore.findMessagesForUser(socket.userID).forEach((message) => { const { from, to } = message; const otherUser = socket.userID === from ? to : from; if (messagesPerUser.has(otherUser)) { messagesPerUser.get(otherUser).push(message); } else { messagesPerUser.set(otherUser, [message]); } }); sessionStore.findAllSessions().forEach((session) => { users.push({ userID: session.userID, username: session.username, connected: session.connected, messages: messagesPerUser.get(session.userID) || [], }); }); socket.emit("users", users); // ...});
The code is quite straightforward. We shouldn't lose messages upon disconnection anymore:
![Chat (v3)](/images/private-messaging-part-3-chat.gif)
#
ReviewNow that we have a functional chat, we will see in the 4th part of this guide how to scale to multiple Socket.IO servers.
Thanks for reading!