May 25, 2020

Add read receipts to your Vue app

Olususi Oluyemi

  • As a recipient, how do I inform the sender that I have received their message?
  • As a sender, how do I know my message has been delivered or read?


Answering these questions will not only improve the conversations within a group but also enable users to know whether the person they’re messaging is currently available.


In this tutorial, I will teach you how to add the delivery and read receipt feature so you can show either a single or double ticks, or check marks next to a message using the CometChat Pro JavaScript SDK. Take a look at the image shown below:



As seen in the image above, once Iron Man ( a test user) sends a message to a group, if no one is online, the message will be marked with a single green tick. As soon as any other member of the group, Captain America for example, comesback online, any new message sent to the group will be marked with double green ticks or checkmarks, and then changed to double blue ticks afterwards. This indicates that the message sent to the group has been read by the participants.


Getting started

Now to the major objective of this tutorial. We will start setting up a Vue project by cloning or downloading this boilerplate from GitHub. This starter project is a replica of the structure of the group chat application that was discussed here in one of our previous tutorials. It contains the code to run a basic group chat application. If you wish to learn about how it was built, kindly follow this link, make sure to return here once you are done!


Run the following command to clone the repository:


{% c-line %} git clone https://github.com/yemiwebby/vue-comet-chat-starter {% c-line-end %}


Once the installation process is complete, move into the application and install all the required dependencies:


// change directory

cd vue-comet-chat-starter


// install dependencies

npm install


Aside from Vue and its dependencies, the preceding command will also install the JavaScript SDK of CometChat.


With the starter project installed successfully, here is a quick overview of its most important files and directories:

  • public/index.html: this is the main app file and contains a simple element <div id="app"></div> which the Vue application will use to attach to the DOM.
  • src/assets: this folder houses the image for the chat app.
  • src/components: all existing and yet to be created reusable components will be stored and accessed from this folder.
  • src/views: contains every necessary page created for the application. The router file can be configured to render the contents of this folder.
  • src/App.css: stylesheet to add more custom styles, which includes colors and other user interface enhancementsfor the application.
  • src/main.js: the entry point of the application.
  • src/router.js: holds the configuration of routes within the application.
  • src/App.vue: the root component used for rendering other components and views within the application. We will initialize CometChat from here as well.


The basic setup for the Vue application has been completed and we will add more features later in the tutorial. But before we run the application, we need to create a CometChat account. We will quickly set that up in the next section.

What is CometChat?

A CometChat account is required for us to gain access to the awesome features offered by its JavaScript SDK. But before we create an account, here is a quick introduction to CometChat. CometChat is a service built in the form of a robust SDK and API. CometChat is designed as a developer tool to help save hours of development whenever you need to add a chat feature to your application. With CometChat, you can also easily access other power-packed suites of chat features such as one-on-one chat, group chat, voice and video and so on. More information can be found here on the official CometChat website.



Create a free CometChat account

Start with CometChat by heading over to the website and creating a free CometChat Pro account. Fill in all the required information and you will have a trial account set up for you. Next, proceed to your CometChat dashboard and add a new app by selecting your region from the dropdown and entering the desired name for your app. I have aptly named mine read-receipt-app. Proceed to click on the + sign and wait for a couple of seconds for your new app to be created.



Once this is done, you will be redirected back to the initial page where your new app will be listed. Next, click the Explore button and then go to the API Keys tab. From there, copy and save both your APP ID and the API Key with fullAccess scope that was automatically generated for your application, somewhere, as you will need it later.


Run the group chat application

Before you start the application,  open the .env file within the root directory of the application and paste the following code into it:

VUE_APP_COMMETCHAT_API_KEY=YOUR_API_KEY                

VUE_APP_COMMETCHAT_APP_ID=YOUR_APP_ID

VUE_APP_COMMETCHAT_GUID=YOUR_GUID



Replace YOUR_API_KEY, YOUR_APP_ID, YOUR_GUID placeholder with the appropriate credentials as obtained from your CometChat dashboard.


You can now run the application:


npm run serve


This will start the app on the development server available on http://localhost:8080:



This is pretty much what we expected, a functional group chat application. This looks good. We can now proceed to add the message delivery and read receipt feature.


Adding delivery and read receipt

As mentioned earlier, we want to be able to add the following to enhance our chat application:

  • Show a single green tick once a message has been sent during a chat.
  • Show double green ticks when the message is delivered.
  • And finally, show double blue ticks if the message has been sent, delivered and read.


To achieve this, we will leverage the real time event methods onMessagesRead() and  onMessagesDelivered() to indicate whether the message has been read or delivered. Those methods are available within the  MessageListener  class provided by the CometChat SDK. Depending on the status of the message at a point in time, that is:

  • Sent,
  • Delivered, or
  • Read.

We will alter the object of such a message by including the boolean fields above to indicate its current status. In a nutshell, to show a tick indicating that a message has been delivered we will add the following fields as shown here:


{ delivered: true, read: false, sent: false }


We can then track these values within our view to show the tick that will appear next to each message. If this is a bit confusing, don’t sweat it, it will all be clear in a bit.


Message Sent

We will begin by changing the status of a message and marking it as sent. To do that, open src/views/Chat.vue and update the sendGroupMessage() method by including a new line as shown below:


{% c-block language="javascript" %}
sendGroupMessage() {
     this.sendingMessage = true;
     var receiverID = process.env.VUE_APP_COMMETCHAT_GUID;
     var messageText = this.chatMessage;
     var receiverType = CometChat.RECEIVER_TYPE.GROUP;
     let globalContext = this;
     var textMessage = new CometChat.TextMessage(
       receiverID,
       messageText,
       receiverType
     );
     CometChat.sendMessage(textMessage).then(
       message => {
         console.log("Message sent successfully:", message);
         this.chatMessage = "";
         this.sendingMessage = false;
         // Text Message Sent Successfully
         const newMessage = {...message, read: false, delivered: false, sent: true} // add this line
         this.groupMessages = [...globalContext.groupMessages, newMessage]; //inlude newMessage
         this.$nextTick(() => {
           this.scrollToBottom();
         });
       },
       error => {
         console.log("Message sending failed with error:", error);
       }
     );
   }
{% c-block-end %}

With this in place, the message will have a new field with sent set to true.


Message Received

The onTextMessageReceived() method is always fired from the user.

Once a participant is online and receives a message, we will include a method markAsRead() to mark the message as read. For this, you need to update the onTextMessageReceived() method within the CometChat.addMessageListener()class, as shown below:


{% c-block language="javascript" %}
CometChat.addMessageListener(
     listenerID,
     new CometChat.MessageListener({
       onTextMessageReceived: textMessage => {
         console.log("Text message received successfully", textMessage);
         this.groupMessages = [...this.groupMessages, textMessage];
         CometChat.markAsRead(textMessage.id, textMessage.receiverId, textMessage.receiverType); // add this line
         this.loadingMessages = false;
         this.$nextTick(() => {
           this.scrollToBottom();
         });
       },
     })
   );
{% c-block-end %}


Message Delivered

CometChat SDK automatically marks messages as delivered whenever the message is received by the recipient. To track this and show double ticks, we will include the real-time events method that will be fired by the SDK once a message has been delivered, and then alter the message object to indicate that  delivered is true.


Still within the src/views/Chat.vue file, update the  CometChat.addMessageListener class by including a new method named onMessagesDelivered() as shown below:

{% c-block language="javascript" %}
CometChat.addMessageListener(
   listenerID,
   new CometChat.MessageListener({
    ...
    onMessagesDelivered: messageReceipt => {
     console.log("MessageDeliverd", { messageReceipt });
     const content = this.groupMessages.map(message => {
      if (message.id === messageReceipt.messageId) {
       return {...message, delivered: true, read: false, sent: false}
      }
      return message;
     });
     this.groupMessages = content;
     CometChat.markAsRead(messageReceipt.messageId, messageReceipt.receiver, messageReceipt.receiverType);
    },
   })
  );
{% c-block-end %}


From the snippet above, we created an updated message object and toggled the value of delivered to true. Also, once a message is delivered we marked the message as read using the markAsRead() method from the SDK. This method takes three parameters as input:


  • messageId: the ID of the message for a particular conversation to be marked as read.
  • receiverId: this is the messages received Id for a group.
  • receiverType: Type of the receiver, which is a group in this case.



Message Read

Lastly, to listen to the real-time event that will indicate that a message has been read by the recipient(s), update the same  CometChat.addMessageListener as shown here:


{% c-block language="javascript" %}
CometChat.addMessageListener(
   listenerID,
   new CometChat.MessageListener({
    ...
    onMessagesRead: messageReceipt => {
     console.log("MessageRead", { messageReceipt });
     const updatedContent = this.groupMessages.map(message => {
      if (message.id === messageReceipt.messageId) {
       return {...message, read: true, delivered: false, sent: false}
      }
      return message;
     });
     this.groupMessages = updatedContent;
    }
   })
  );
{% c-block-end %}


From the snippet above, we toggled the value of read to true and update the message object with the appropriate values.


Now that we are done adding the necessary methods from the CometChat SDK, we will proceed to include a checkmark next to each message within the view.


Updating the view

Within the <template></template> of Chat.vue file, locate the <div> with a class of .outgoing-chats and update it as shown here:


{% c-block language="javascript" %}
<div class="outgoing-chats" v-else>
                                  <div class="outgoing-chats-msg" style="position: relative;">
                                      <p>{{ message.data.text }}</p>
                                      <div v-if="message.sent" style="position: absolute; right: 0; bottom: 0;padding: 10px 10px 0 0;">
                                        <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="23" height="23" viewBox="0 0 226 226" style=" fill:#000000;">
                                          <g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray
                                              stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal">
                                            <path d="M0,226v-226h226v226z" fill="none" />
                                            <g fill="#2ecc71">
                                              <path d="M212.68483,42.375l-109.1015,109.90192l-43.17071,-43.98525l-13.32929,13.43758l56.5,57.18742l122.41667,-123.396z"/>
                                            </g>
                                          </g>
                                        </svg>
                                      </div>
                                      <div v-if="message.delivered" style="position: absolute; right: 0; bottom: 0;padding: 10px 10px 0 0;">
                                        <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="23" height="23" viewBox="0 0 226 226" style=" fill:#000000;">
                                          <g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray
                                              stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal">
                                              <path d="M0,226v-226h226v226z" fill="none" />
                                            <g fill="#2ecc71">
                                              <path d="M212.68483,42.375l-109.1015,109.90192l-43.17071,-43.98525l-13.32929,13.43758l56.5,57.18742l122.41667,-123.396z"/>
                                              <path d="M165.6015,42.375l-109.1015,109.90192l-43.17071,-43.98525l-13.32929,13.43758l56.5,57.18742l122.41667,-123.396z"/>
                                            </g>
                                          </g>
                                        </svg>
                                      </div>                                      
                                      <div v-else-if="message.read" style="position: absolute; right: 0; bottom: 0;padding: 10px 10px 0 0;">
                                         <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="23" height="23" viewBox="0 0 226 226" style=" fill:#000000;">
                                          <g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray
                                            stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal">
                                            <path d="M0,226v-226h226v226z" fill="none" />
                                            <g>
                                              <path d="M212.68483,42.375l-109.1015,109.90192l-43.17071,-43.98525l-13.32929,13.43758l56.5,57.18742l122.41667,-123.396z" fill="#0c9eff"/>
                                              <path d="M165.6015,42.375l-109.1015,109.90192l-43.17071,-43.98525l-13.32929,13.43758l56.5,57.18742l122.41667,-123.396z" fill="#06a3ff"/>
                                            </g>
                                          </g>
                                        </svg>
                                      </div>
                                  </div>
                                  <div class="outgoing-chats-img">
                                      <img v-bind:src="message.sender.avatar" alt="" class="avatar">
                                  </div>
                              </div>
{% c-block-end %}


Here, we conditionally rendered different checkmarks based on the current status of the message. The complete Chat.vuefile can be found here on GitHub, in case you missed anything.


Test the application

Go ahead and restart the application with npm run serve if it’s currently not running. Open two different tabs:



Then login from one of the browsers and try to send a message:



Did you notice that the message was marked as sent with a single tick because no other user is online? Now log in as superhero2, a sample user for our application, and send another message:



There you have it, the messages will be marked as read, as the other user is in the chat window and receives the messages in real time.


Conclusion

As seen in this tutorial, you were able to set up a Vue group chat application powered by CometChat. You then proceeded to add a read receipt feature. Two blue check marks appear when your messages have been read by other participants in a group. If you don't see two blue checkmarks next to your sent message, then your message has not been delivered because none of the participants were online.


I do hope that you found this tutorial helpful. The complete source code for the application built here can be found in the completed-read-receipt branch of this repository.

A couple of questions come to mind during a chat: