Duplex
Duplex is a Full-stack chat web application. It is a dual identity messenger supporting instant text messaging, video call, and voice chat.
Background
Our project aims to build a messenger web application, extending to desktop and mobile apps. As people tend to use different messengers in different situations. e.g. Whatsapp for formal conversation, Discord for gaming chats.
We would like to build an integrated application, called Duplex, such that users no longer need to switch between different messengers anymore.
Features
Dual Identity for Chats and Conversation
Duplex allows users to create individual profiles for both Chats and Conversation. The reason why we let users have dual identity is for separation of content. Users can chat with their friends casually in Chats, with informal profile pictures, personal info while starting a serious discussion in Conversation.
Spaces
We introduce the Space concept in our application. It acts like a group/ channel in other applications which provide an environment for multiple users to chat.
Video and Audio Chats
Besides the instant text messaging function, Duplex also allows users to have a real-time voice chat and video call. Not only limited to a single connection, users can conduct a group video/ voice chat with a mesh connection.
With the popup ChatDrawer shown in pictures, users can enjoy voice-only chat with controls via WebRTC. Users can also switch to video calls if they want, which have a grid layout.
Implementation
We used TypeScript.
This is our first time moving JavaScript to TypeScript, which gives us a robust development experience on type checking.
As part of our primary architecture, we also leveraged MERN stack. Here is the architecture diagram of our project.
Cloud Hosting and Deployment
Duplex’s integrated server (web and API) is hosted on Heroku. For the MongoDB cluster and static assets storage, the prior one is hosted on AWS via Atlas while the latter is hosted on AWS S3.
WebRTC
We used simple-peer library. Simple-peer is a simplified WebRTC library for creating peer-to-peer video and audio chats.
Socket.io
Instant text messaging is implemented using Socket.io, as well as to establish the connection to the server for voice & video chats. Using Socket.io helps us to build a real-time application which is important especially for messaging applications.
For text messaging, the client/server listens and emits events to clients which have connected through websockets. We mainly focus on 4 actions,
- Events when users send/receive a new message
- Events when users read a message
- Events when users join a room/leave can help us get the online users list
- Events when users join a space from invite link/ created a new chat
For voice chat/ video call, the client/server listens and emits the following events to establish the chat.
- Events when users join/leave a voice/video room
- Events when users send/receive a signal
JWT & Http-only Cookie
To identify the current user, we have used the JsonWebToken. When a user makes a login POST request, the server will send back the http-only cookie along with the response. We created a middleware layer to verify the JWT for both REST apis and sockets for authentication.
// Login controller
const token = jwt.sign({ email: user.email, userId: user._id }, process.env.JWT_TOKEN || '', {
expiresIn: ONE_DAY,
})
user.password = null
return res
.status(200)
.cookie('token', token, {
sameSite: 'strict',
path: '/',
maxAge: ONE_DAY,
httpOnly: true,
})
.send({ message: 'logged in', user: user })
// Middleware verification
jwt.verify(req.cookies?.token, process.env.JWT_TOKEN || '', async (verifyErr: any, decoded: any) => { ... }
Hashing & Encryption
To enhance the security and privacy of our application, we apply hashing to the password and aes-256-cbc encryption method with the crypto module before saving into the database.
let iv = crypto.randomBytes(16)
let cipher = crypto.createCipheriv('aes-256-cbc', process.env.AES_KEY || '', iv)
let encryptedMessage = cipher.update(content, 'utf-8', 'hex')
encryptedMessage = cipher.final('hex')
// Prepend the iv with the encrypted message
{
content: iv.toString('hex') + ':' + encryptedMessage,
}
User Interfaces
Clean design is a key component of our designs. We used React and Material UI for building our interface.
We offered light and dark modes for users to choose between. Creating custom components and icon button, also using tooltips or toast to provide an informative but user-friendly interface.
Users can create new spaces or send messages to their friends using these fancy popup modals. They can also generate a url to invite their friends to their space.
Challenges and Limitation
A robust and detailed messenger application requires lots of testing and user reviews. Within the short period of time, we manage to finish the main feature, while putting the minor functionality in the future plan (e.g. timespan of messages, chat search, email verification, OAuth etc…).
There are also minor bugs and places that are inconsiderate for users. We would definitely fix henceforward.
Cross-platform compatibility is amazing while causing many troubles in development. We mainly focused on Google Chrome for development, but there are also issues caused by browsers and OS.
Besides CSS styling problems, playing along with userstream is also challenging. Safari on iOS does not work as expected for the video streaming while macOS has some permission problems on Electron.