Build a chat application in JavaScript
Tags: javascript, node, socket, chat
Published: 2019-09-24 10:19:26

#### Why Build a Chat App

An easy way of learning something is trying to solve a problem. In my case, I wanted to build a [Node JS] App (https://nodejs.org/en/) and deploy it on cloud ( something like [Heroku](https://www.heroku.com/) which I later 🔎 out). 

![Alt Text](https://media.giphy.com/media/26n7bfS3Awhpe9CrC/giphy.gif)


>So here is the problem >> Most of my friends were working in different organizations, to communicate with each other on desktop we tried apps like hangout ...  but most of the apps were blocked in most of the organizations. WhatsApp Web was not launched by then (Aug 2014), so I decided to solve this problem by learning Node JS and socket.io.   

---

#### Getting Started
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/5m73fbr0iph8rhnod7xr.PNG)

- Install NodeJS and start a new node project with below package.json 

```javascript

{
  "name": "Chat-App",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.13.3",
    "moment": "^2.12.0",
    "socket.io": "^1.3.7"
  }
}

```
Set up [Node](https://github.com/bhupendra1011/Chat-App/blob/master/server.js) JS file to setup application 
```
var PORT = process.env.PORT || 3000;
var express = require("express");
var app = express(); // express app which is used boilerplate for HTTP
var http = require("http").Server(app);
// expose the folder via express thought
app.use(express.static(__dirname + '/public'));
http.listen(PORT, function() {
  console.log("server started");
});
```
Set up client side public folder for the UI
- Add [index.html]  (https://github.com/bhupendra1011/Chat-App/blob/master/public/index.html) to launch the landing page
![Chat_landing Page](https://thepracticaldev.s3.amazonaws.com/i/d1lj9blq293qm1b92ppc.PNG)
- Add [chat.html] (https://github.com/bhupendra1011/Chat-App/blob/master/public/chat.html) to open chat screen after logging from chat window  
![Chat-Msg](https://thepracticaldev.s3.amazonaws.com/i/ipm2qgzqfx7ol9bgiwb9.PNG)
- Add [app.js](https://github.com/bhupendra1011/Chat-App/blob/master/public/js/app.js) to show messages received from Node Server (using Socket.io).

#### Socket Communication

- Showing Welcome message

When a user joins a chat room, he is greeted by the system for logging into the system.

 ```javascript
  socket.emit("message", {
    text: "Welcome to Chat Appliction !",
    timestamp: moment().valueOf(),
    name: "System"
  });
```
*server.js*

Also if other person has joined room , client side has to emit an event
```javascript
 socket.on("connect", function() {
   console.log("Connected to Socket I/O Server!");
   console.log(name + " wants to join  " + room);
   // to join a specific room
   socket.emit('joinRoom', {
     name: name,
     room: room
   });
 });
```
*app.js*

Now server broadcast this message to all the users joined in that room
```javascript
  socket.on('joinRoom', function(req) {
    clientInfo[socket.id] = req;
    socket.join(req.room);
    //broadcast new user joined room
    socket.broadcast.to(req.room).emit("message", {
      name: "System",
      text: req.name + ' has joined',
      timestamp: moment().valueOf()
    });

  });
```
*server.js*

- Show notification when user is typing

As user is typing in the message field , client side emits that event :
```javascript
 $('#messagebox').keyup(function() {
   console.log('happening');
   typing = true;
   $("#icon-type").removeClass();
   //console.log("typing typing ....");
   //socket.emit('typing', 'typing...');
   socket.emit('typing', {
     text: name + " is typing ..."
   });
   clearTimeout(timeout);
   timeout = setTimeout(timeoutFunction, 1000);
 });
```
*app.js*

The server then broadcasts this notification:
```javascript
  socket.on('typing', function(message) { // broadcast this message to all users in that room
    socket.broadcast.to(clientInfo[socket.id].room).emit("typing", message);
  });
```
*server.js*

- When a user sends a message
  When the user submits the message form then after sanitizing the input , the message is emitted to the server
```javascript
 var $form = $("#messageForm");
 var $message1 = $form.find('input[name=message]');
 $form.on("submit", function(event) {
   event.preventDefault();
   var msg = $message1.val();
   //prevent js injection attack
   msg = msg.replace(/</g, "&lt;").replace(/>/g, "&gt;").trim();
   if (msg === "") return -1; //empty messages cannot be sent

   socket.emit("message", {
     text: msg,
     name: name
   });
   // show user messageForm
   var $messages = $(".messages");
   var $message = $('<li class = "list-group-item"></li>');

   var momentTimestamp = moment().format("h:mm a");
   // $(".messages").append($('<p>').text(message.text));
   $message.append("<strong>" + momentTimestamp + " " + name + "</strong>");
   //$message.append("<p>" + $message1.val()+ "</p>");
   $message.append($("<p>", {
     class: "mymessages",
     text: $message1.val()
   }));
   $messages.append($message);
   $message1.val('');
   // manage autoscroll
   var obj = $("ul.messages.list-group");
   var offset = obj.offset();
   var scrollLength = obj[0].scrollHeight;
   //  offset.top += 20;
   $("ul.messages.list-group").animate({
     scrollTop: scrollLength - offset.top
   });

 });
```
*app.js*

Server listens to the above client event , then after checking if the input message is not a predefined command {@currentUsers gives list of users in current chat room}, broadcasts the received message.
```javascript
  socket.on("message", function(message) {
    console.log("Message Received : " + message.text);
    // to show all current users
    if (message.text === "@currentUsers") {
      sendCurrentUsers(socket);
    } else {
      //broadcast to all users except for sender
      message.timestamp = moment().valueOf();
      //socket.broadcast.emit("message",message);
      // now message should be only sent to users who are in same room
      socket.broadcast.to(clientInfo[socket.id].room).emit("message", message);
      //socket.emit.to(clientInfo[socket.id].room).emit("message", message);
    }

```
*server.js*

- Checking if the message has been by the user or not

On Client side, when the message is received, then a check is made to see if the user has opened the chat window or not. If chat window is open, then it means, the message is seen, then an event is emitted   
```javascript
// notify, only when the user has not open chat view
   if (document[hidden]) {
     notifyMe(message);
     // also notify server that user has not seen messgae
     var umsg = {
       text: name + " has not seen message",
       read: false
     };
     socket.emit("userSeen", umsg);
   } else {
     // notify  server that user has seen message
     var umsg = {
       text: name + " has seen message",
       read: true,
       user: name
     };
     socket.emit("userSeen", umsg);
   }
 });
```
*app.js*
User gets a notification if the chat window is minimized
![chat-notify](https://thepracticaldev.s3.amazonaws.com/i/f8h7e825q56jgs9b5wr0.png)
If user clicks on the notification window , then user seen message is emitted so that blue ticks (user not message) gets converted to green (user has seen the message)
```javascript
function notifyMe(msg) {
   // Let's check if the browser supports notifications
   if (!("Notification" in window)) {
     alert("This browser does not support desktop notification,try Chromium!");
   }

   // Let's check whether notification permissions have already been granted
   else if (Notification.permission === "granted") {
     // If it's okay let's create a notification
     //  var notification = new Notification(msg);
     var notification = new Notification('Chat App', {
       body: msg.name + ": " + msg.text,
       icon: '/images/apple-icon.png' // optional
     });
     notification.onclick = function(event) {
       event.preventDefault();
       this.close();
       // assume user would see message so broadcast userSeen event
       var umsg = {
         text: name + " has seen message",
         read: true,
         user: name
       };
       socket.emit("userSeen", umsg);
       //window.open('http://www.mozilla.org', '_blank');
     };
   }
   // Otherwise, we need to ask the user for permission
   else if (Notification.permission !== 'denied') {
     Notification.requestPermission(function(permission) {
       // If the user accepts, let's create a notification
       if (permission === "granted") {
         var notification = new Notification('Chat App', {
           body: msg.name + ": " + msg.text,
           icon: '/images/apple-icon.png' // optional
         });
         notification.onclick = function(event) {
           event.preventDefault();
           this.close();
           var umsg = {
             text: name + " has seen message",
             read: true,
             user: name
           };
           socket.emit("userSeen", umsg);
           // assume user would see message so broadcast userSeen event
           
         };
       }
     });
   }

   // At last, if the user has denied notifications, and you
   // want to be respectful there is no need to bother them any more.
 }
```
*app.js* 

Server side needs to be notified when user sees the message
 ```javascript
 // to check if user seen Message
  socket.on("userSeen", function(msg) {
    socket.broadcast.to(clientInfo[socket.id].room).emit("userSeen", msg);
    //socket.emit("message", msg);

  });
```
server.js


- Deploying to the Cloud

Now code needs to be deployed to the cloud , this can be easily done through [Heroku](https://devcenter.heroku.com/articles/git). Five applications can be deployed at a time in Heroku for free.

---
### Conclusion
I got really excited when I shared this with my friends, we could chat through a web application which is not blocked due to any organization policies.
This is a 4 year old project , when I came to know about *dev.to* decided to put my learning here.

To improve this chat application, below features could be added :
- Implement authentication using OAuth2
- Add the option to change username , add a profile picture.
- Add DB to persist the chat messages.
- any other feature ...

Source Code : https://github.com/bhupendra1011/Chat-App
Demo : https://bhupendra1011.herokuapp.com/
  ---