One of the most efficient and non-intrusive ways of providing realtime messages or information to your web application users is through the use of push notifications. It makes your application quite engaging and here at CometChat, we have put together an infrastructure to enable the process of achieving such functionality in your web app.

In this tutorial, I will show you how to build a group chat application with Vuejs and leverage on CometChat and Firebase to add web push notification to it as shown here:

Push notifications demo

We will use services offered by Firebase for push notifications and combine it with the awesome chat features from CometChat to build this.

To obtain access to the entire source code for the complete application in this tutorial, you can check this repository. You are welcome to also follow the step by step guide here in this tutorial.

Create a Vue application

We are going to use a Vue.js boilerplate from this repository to kickstart our application. The project in this repository contains a couple of required dependencies that you will need to install, the image assets specifically for this project and a stylesheet to give the app an appealing look. We will be using the main branch, which is the starter-branch as the starting place.

To begin, download or clone the repository using Git as shown here:

git clone https://github.com/cometchat-pro-tutorials/vue-push-notifications.git

Once the installation process is complete, navigate to the project directory and install all the dependencies:

// change directory
cd vue-push-notifications

// install dependencies
npm install

This will install the following crucial packages:

  • @cometchat-pro/chat: This is the JavaScript SDK of CometChat
  • firebase: the Firebase SDK package. You will get to know more about this later in the tutorial.

With the starter project which contains minimal code and functionality installed successfully, here is a quick overview of the most important files and directories in it:

  • 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 enhancement for 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 also initialized both CometChat and Firebase from here as well.

The basic setup for the Vue application has been completed and we will add more features later in the tutorial. You can run the application now with:

npm run serve

This will start the app on the development server available on http://localhost:8080. We will start making this app more functional in the next section.

Project setup

As pointed out earlier, CometChat will be used as a service provider to power our chat application and in conjunction with Firebase, a push notification will be enabled. With CometChat you can easily add voice, video, and text chat to your application. Also, it also provides extensions which leverage on other services such as Firebase.

Start with CometChat by heading over to the website and create a free CometChat Pro account. Fill in all the required information and you will have a trial account set up for you. Then, head to your CometChat dashboard, then add a new app by selecting v1 from the dropdown and enter the desired name for your app. I have aptly named mine web-notification-vue. Proceed to click on the + sign and wait for a couple of seconds for your new app to be created.

CometChat Pro App

Once this is done, you will be redirected back to the initial page where your new app will be listed. Click the Explore button and then go to the API Keys tab. 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.

Let’s proceed to set up the group chat app and add more functionalities. We will come back to the dashboard later.

Initialise CometChat

Next, create a new file named .env within the root directory of the application and paste the following code in it:

// .env
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.

Next, it is required that CometChat must be initialised in an application using the APP ID obtained from the dashboard before any method can be called from the CometChat SDK. The code to do this has been included in the downloaded sample project earlier. Open the root component in ./src/App.vue and let’s take a look at its content:

// ./src/App.vue

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
import { CometChat } from "@cometchat-pro/chat";
import "./App.css";
export default {
  created() {
    this.initializeApp();
  },
  methods: {
    initializeApp() {
      const { VUE_APP_COMMETCHAT_APP_ID } = process.env
      CometChat.init(VUE_APP_COMMETCHAT_APP_ID).then(
        () => {
          console.log("Initialization completed successfully");
        },
        error => {
          console.log("Initialization failed with error:", error);
        }
      );
    }
  }
};
</script>

Here, the <router-view> is a functional component that was included to render any matched component for a given path from Vue Router. The configuration for each routes can be found in ./src/router.js, feel free to take a look at it, to have an understanding of the components that will be rendered once we visit a particular route within the app.

Lastly in the file above, we called init() by passing the APP ID as a parameter to initialize the settings required for CometChat. With this in place, CometChat SDK will be aware that our application exists.

Authenticating users

Now that the initialization of CometChat is successful, we will add more contents to the Login component. This is to give a particular user access to send and receive messages from CometChat server.

// ./src/views/Login.vue

<template>
  <div class="login-page">
    <div class="login">
      <div class="login-container">
        <div class="login-form-column">
          <form v-on:submit.prevent="authLoginUser">
            <h3>Hello!</h3>
            <p>Welcome to our little Vue demo powered by CometChat. Login with the username "superhero1" or "superhero2" and test the chat out.
                To create your own user, see <a href="https://prodocs.cometchat.com/reference#createuser">our documentation</a>   </p>
            <div class="form-wrapper">
              <label>Username</label>
              <input type="text" name="username" id="username" v-model="username" placeholder="Enter your username" class="form-control" required>
            </div>
            <button type="submit">LOG IN &nbsp;&nbsp;<span v-if="showSpinner" class="fa fa-spin fa-spinner"></span> </button>
          </form>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { CometChat } from "@cometchat-pro/chat";
export default {
  data() {
    return {
      username: "",
      showSpinner: false
    };
  },
  methods: {
    authLoginUser() {
      var apiKey = process.env.VUE_APP_COMMETCHAT_API_KEY;
      this.showSpinner = true;
      CometChat.login(this.username, apiKey).then(
        () => {
          this.showSpinner = false;
          this.$router.push({
            name: "chat"
          });
        },
        error => {
          this.showSpinner = false;
          alert("Whops. Something went wrong. This commonly happens when you enter a username that doesn't exist. Check the console for more information")
          console.log("Login failed with error:", error.code);
        }
      );
    }
  }
};
</script>

What the method authLoginUser() does is to obtain the username inputted by a user and pass it to the Login() method from CometChat SDK. If the process of authenticating the user was successful we will redirect the user to the Chat page through the help of Vue Router; which handles navigation for the application. Otherwise, a prompt, with the appropriate message indicating that the login process failed will be displayed.

Building the Chat View

The Chat component is not anywhere close to what we want to achieve at the moment. Let us begin to flesh it up by adding the required contents to make it functional. Start by opening the ./src/views/Chat.vue and replace its content with the following code:

<template>
  <div class="booker">
    <div class="chat">
        <div class="container">
          <div class="msg-header">
              <div class="active">
                  <h5>Chat</h5>
              </div>
          </div>
          <div class="chat-page">
              <div class="msg-inbox">
                  <div class="chats" id="chats">
                      <div class="msg-page" id="msg-page">
                        <div
                          v-if="loadingMessages"
                          class="loading-messages-container"
                        >
                          <spinner :size="100"/>
                          <span class="loading-text">
                            Loading Messages
                          </span>
                        </div>
                        <div class="text-center img-fluid empty-chat" v-else-if="!groupMessages.length" >
                          <div class="empty-chat-holder">
                            <img src="../assets/empty-state.svg" class="img-res" alt="empty chat image">
                          </div>
                          <div>
                            <h2> No new message? </h2>
                            <h6 class="empty-chat-sub-title">
                              Send your first message below.
                            </h6>
                          </div>
                        </div>
                        <div v-else>
                          <div>
                            <div v-for="(message, i) in groupMessages" v-bind:key="`${i}-${message.id}`">
                            <div class="received-chats" v-if="message.sender.uid != uid">
                                <div class="received-chats-img">
                                  <img v-bind:src="message.sender.avatar" alt="" class="avatar">
                                </div>
                                <div class="received-msg">
                                    <div class="received-msg-inbox">
                                        <p><span>{{ message.sender.uid }}</span><br>{{ message.data.text }}</p>
                                    </div>
                                </div>
                              </div>

                            <div class="outgoing-chats" v-else>
                                  <div class="outgoing-chats-msg">
                                      <p>{{ message.data.text }}</p>
                                  </div>
                                  <div class="outgoing-chats-img">
                                      <img v-bind:src="message.sender.avatar" alt="" class="avatar">
                                  </div>
                              </div>
                          </div>
                          </div>
                        </div>
                      </div>
                  </div>
              </div>
              <div class="msg-bottom">
                <form class="message-form" v-on:submit.prevent="sendGroupMessage">
                  <div class="input-group">
                    <input type="text" class="form-control message-input" placeholder="Type something" v-model="chatMessage" required>
                    <spinner
                      v-if="sendingMessage"
                      class="sending-message-spinner"
                      :size="30"
                    />
                  </div>
                </form>
              </div>
          </div>
      </div>
    </div>
  </div>
</template>

Here, once a user is authenticated and redirected to the chat page, we will receive the user details and display the unique username, avatar and also an empty chat page for users who just joined a new group with no previous messages.

Also included is a form with an input field that will be used by a user to send a message to a group. The form will be processed by a method named sendGroupMessage() which will be implemented later in this section .

Get the logged in user details
We need to uniquely identify the current logged in user and to achieve that, we will retrieve the details of the user by calling a method named getLoggedInUser(). This will return a User object containing all the information related to the logged-in user. We will begin by adding this <script> section to our code. Place the contents below within the Chat.vue immediately after the closing tag of the <template> section:

// ./src/views/Chat.vue

<script>
import { CometChat } from "@cometchat-pro/chat";
import Spinner from "../components/Spinner.vue";
export default {
  name: "home",
  components: {
    Spinner
  },
  data() {
    return {
      username: "",
      avatar: "",
      uid: "",
      sendingMessage: false,
      chatMessage: "",
      loggingOut: false,
      groupMessages: [],
      loadingMessages: false
    };
  },
  created() {
    this.getLoggedInUser();
  },
  methods: {
    getLoggedInUser() {
      CometChat.getLoggedinUser().then(
        user => {
          this.username = user.name;
          this.avatar = user.avatar;
          this.uid = user.uid;
        },
        error => {
          this.$router.push({
            name: "homepage"
          });
          console.log(error);
        }
      );
    },
  }
};
</script>

Above, we imported the Spinner component, including the CometChat method from the SDK. Also, we defined some properties and corresponding initial values within the data option. And finally, once the component is created, we called a method named getLoggedInUser() to automatically retrieve the details of a logged in user and update the view alongside it.

At the moment, none of the members will be able to send real-time message to the group yet, even after being authenticated. We will change this by adding methods to send group messages, listen for any new message posted to the group in realtime and fetch all previous messages.

Send new messages
To send new message from our app to CometChat server, add a new method named sendGroupMessage() immediately after the getLoggedInUser() method:

// ./src/views/Chat.vue
<script>
...
export default {
  ...
  methods: {
    ...
    sendGroupMessage() {
      this.sendingMessage = true;
      var receiverID = process.env.VUE_APP_COMMETCHAT_GUID;
      var messageText = this.chatMessage;
      var messageType = CometChat.MESSAGE_TYPE.TEXT;
      var receiverType = CometChat.RECEIVER_TYPE.GROUP;
      let globalContext = this;
      var textMessage = new CometChat.TextMessage(
        receiverID,
        messageText,
        messageType,
        receiverType
      );
      CometChat.sendMessage(textMessage).then(
        message => {
          console.log("Message sent successfully:", message);
          this.chatMessage = "";
          this.sendingMessage = false;
          // Text Message Sent Successfully
          this.groupMessages = [...globalContext.groupMessages, message];
          this.$nextTick(() => {
            this.scrollToBottom()
          })
        },
        error => {
          console.log("Message sending failed with error:", error);
        }
      );
    }
  },
};
</script>

Sending messages to either a group or user on CometChat requires creating an object TextMessage class. Here, we instantiate this object and passed the mandatory parameters which are:

  • receiverID: This is to identify the recipient of the message, which in this case, is the GUID as obtained from the CometChat dashboard.
  • messageText: The text message that needs to be sent to the server
  • messageType: The type of message and
  • receiverType: The type of users that will receive the message.

Once the message is posted to the server, we updated the groupMessages property with the new message and added a method to scroll to the latest message. Add this scroll method after sendGroupMessage() function as shown below:

...
scrollToBottom() {
      const chat = document.getElementById("msg-page");
      chat.scrollTo(0, chat.scrollHeight + 30);
},
...

Receive incoming messages and Fetch previous messages
To receive real-time incoming messages posted to a group by its participants, we would register an event listener and pass a unique listenerID to it. Also, CometChat SDK makes provision to fetch messages in bulk using the MessagesRequestBuilder class. We will add all these functionalities within a lifecycle hook in Vuejs called mounted. Go ahead and include the content below just before the created() lifecycle hook:

// ./src/views/Chat.vue

<script>
...
export default {
...
  data() {
    ...
  },
  mounted() {
    this.loadingMessages = true;
    var listenerID = "UNIQUE_LISTENER_ID";
    const messagesRequest = new CometChat.MessagesRequestBuilder()
      .setLimit(100)
      .build();
    messagesRequest.fetchPrevious().then(
      messages => {
        console.log("Message list fetched:", messages);
        console.log("this.groupMessages", this.groupMessages);
        this.groupMessages = [...this.groupMessages, ...messages];
        this.loadingMessages = false;
        this.$nextTick(() => {
          this.scrollToBottom();
        });
      },
      error => {
        console.log("Message fetching failed with error:", error);
      }
    );
    CometChat.addMessageListener(
      listenerID,
      new CometChat.MessageListener({
        onTextMessageReceived: textMessage => {
          console.log("Text message received successfully", textMessage);
          // Handle text message
          console.log(this.groupMessages);
          this.groupMessages = [...this.groupMessages, textMessage];
          // console.log("avatar", textMessage.sender.avatar)
          this.loadingMessages = false;
          this.$nextTick(() => {
            this.scrollToBottom();
          });
        }
      })
    );
  },
  created() {
   ...
  },
  methods: {
  ...
  }
};
</script>

Once a user logs in, we will use the MessagesRequestBuilder to fetch any previous messages from the group and update the view with it.
In case you miss anything, the complete contents of the Chat.vue file can be found here.

Create a Firebase project

Firebase is a platform that provides lots of services for both web and mobile applications and facilitates the quick development of apps without managing infrastructure. To enable push notifications for our chat application, we will use a service called Firebase Cloud Messaging (FCM), which will enable us to send messages to any device using HTTP requests.

To begin, you need to have a Firebase account and add a new project within it. Click here to create a new Firebase account, if you don’t have one already. Next, from your Firebase console, create a new project by clicking on the Create a project button:

Firebase console

You will prompted to enter your project name. Once you are done, click on the </> symbol to add a new web app to your project:

Firebase app page

Enter a name for your app and register it. You will see a page similar to the one shown below afterward:

Firebase config

Copy and save the Firebase config snippet somewhere, as we will need this later.

Note: 💡 If you have an existing Firebase project for an application that you wish to integrate CometChat, you can easily select the project and download the config file instead of creating another.

Enable Push Notification Extension on CometChat.

Now that we have created a Firebase project and a web app within it, you need to go back to your CometChat dashboard and enable the push notification extension for the app that you added on CometChat earlier. Click on Extensions from the left side menu:

CometChat dashboard

Then from the list of all extensions, click on the Add Extension button on the Push Notification row:

CometChat enable push notification extension

Add FCM Server key
To complete the process of enabling Push notification extension on CometChat, a Firebase Cloud Messaging server key is required. Head back to your Firebase console and click on Project Settings. Then select the Cloud Messaging tab to obtain it as shown here:

Firebase settings page

Copy the FCM Server Key and from the CometChat dashboard, click on Actions → Settings for the installed extension:

CometChat installed extensions

Paste the Server key in the dialog box and give your notification a title:

Push notification page

Now, that you have created a Firebase project, created a web app, enable the push notifications extension on CometChat and added the FCM server key, your CometChat application is almost set to start communicating properly with Firebase. To complete the process, we will initialize and configure Firebase messaging within the chat application in the next section.

Setup and configure Firebase in the chat application

To begin, a Firebase SDK will be used here to enable communication between your chat application and Firebase. We have already installed this package earlier as it was included in the package.json file of the downloaded sample project.

Next, create a pushNotifications.js file within the src folder, then copy and paste the following code to it:

// ./src/pushNotifications.js

import firebase from 'firebase/app';
import 'firebase/messaging';

export function initializeFirebase() {
  if (firebase.messaging.isSupported()) {
      const firebaseConfig = {
        apiKey: "YOUR_FIREBASE_API_KEY",
        authDomain: "YOUR_FIREBASE_AUTH_DOMAIN",
        databaseURL: "YOUR_FIREBASE_DATABASE_URL",
        projectId: "YOUR_FIREBASE_PROJECT_ID",
        storageBucket: "",
        messagingSenderId: "YOUR_FIREBASE_MESSAGING_SENDER_ID",
        appId: "YOUR_FIREBASE_APP_ID",
        measurementId: "YOUR_FIREBASE_MEASUREMENT_ID"
      };

      firebase.initializeApp(firebaseConfig);

      const messaging = firebase.messaging();
      messaging
      .requestPermission()
      .then(() => {
      console.log("Permission granted");
      return messaging.getToken();
    })
    .then(token => {
      var userType = 'group';
      var UID = process.env.VUE_APP_COMMETCHAT_GUID;
      var appId = process.env.VUE_APP_COMMETCHAT_APP_ID;
      var topic = appId + '_' + userType + '_' + UID;
      var url = `https://ext-push-notifications.cometchat.com/fcmtokens/${token}/topics/${topic}`;
      fetch(url, {
        method: 'POST',
        headers: new Headers({
          'Content-Type': 'application/json',
        }),
        body: JSON.stringify({appId: appId}),
      })
        .then(response => {
          if (response.status < 200 || response.status >= 400) {
            console.log(
              'Error subscribing to topic: ' +
                response.status +
                ' - ' +
                response.text()
            );
          }
        })
        .catch(error => {
          console.error(error);
        });
    })
    .catch(error => {
      if (error.code === 'messaging/permission-blocked') {
        console.log('Please Unblock Notification Request Manually');
      } else {
        console.log('Error Occurred', error);
      }
    });  
  }
}

The code snippet above might look a bit daunting. Let’s try to break it down. First, we imported Firebase and created a firebaseConfig() object to hold the configuration details of the app created on Firebase console. Kindly replace all the placeholders with the appropriate credentials obtained from the Firebase config snippet earlier.

Next, because it is important to seek the consent of your app users before displaying a push notification, we used the messaging.requestPermission() function to ask a user for permission to send push notification from our chat app, once granted, we retrieved the token by using the messaging.getToken() function. If permission is denied, the Firebase console messaging registration token requests will result in an error.

We then proceeded to use the retrieved token to subscribe to a topic. Topics in Firebase cloud messaging allows messages to be sent to multiple devices that have subscribed to such a particular topic. You can create a random topic and allow any other devices to opt-in for it and they will receive notifications based on that. You can read more about this feature here. At the moment because of the subscription, your browser will receive a message object each time a chat message is being sent to the chat room.

Lastly, add the following code just after the .catch() method:

// ./src/pushNotification.js

...
export function initializeFirebase() {
  if (firebase.messaging.isSupported()) {

    ...

    messaging.onMessage(function(payload) {
        var sender = JSON.parse(payload.data.message);
        console.log('Receiving foreground message', JSON.parse(payload.data.message));
        // Customize notification here
        if (sender.data.entities.sender.entity.uid !== firebaseUid) {
          var notificationTitle = 'CometChat Pro Notification';
          var notificationOptions = {
            body: payload.data.alert,
            icon: sender.data.entities.sender.entity.avatar,
          };
          var notification = new Notification(notificationTitle, notificationOptions);
          notification.onclick = function(event) {
            notification.close();
            console.log(event);
          };
        }
    });
      
  }
}

This will ensure that all the message object sent to the browser as a result of the topic subscribed to, will be handled properly. Here, we used the Notification API to create a new push notification. Before creating the notification payload, we checked to ensure that the sender is not the currently logged in user. This will enable us to control when to display push notifications. The rationale behind this ensures that a user should not receive push notifications for his message.

To track the details of the currently logged in user, add the following code within ./src/pushNotification.js:

// ./src/pushNotification.js
...
let firebaseUid = '';

export function initializeFirebase(){
...
}
...
export function updateLoggedInUser(uid){
  if (firebase.messaging.isSupported()) {
    firebaseUid = uid;
  }
}

Finally, call the newly created method within ./src/views/Chat.vue as shown below:

// ./src/views/Chat.vue
...
import { updateLoggedInUser } from './../pushNotification';

methods: {
    getLoggedInUser() {
      CometChat.getLoggedinUser().then(
        user => {
          ...
          updateLoggedInUser(this.uid)
        },
        ...
      );
    },
  }

Next, you will need to create an instance of the initializeFirebase() function created here once the application start running.

Initialize Firebase instance
Go back to the root component for this application in App.vue and import the configuration above:

// ./src/App.vue
...
<script>
...
import { initializeFirebase } from './pushNotification';

export default {
  created() {
    ...
    initializeFirebase();
  },
 ...
};
</script>

Here, you imported the initializeFirebase() function from pushNotification.js and invoke it. This will ensure that a new Firebase instance will be initialized whenever the application is running.

Receive notifications in the background using service worker

To keep receiving notifications even when your application is in the background, you will need to add a service worker. This is a script that your browser will keep running in the background, thereby ensuring that you keep receiving push notifications. By default, when you start Firebase, it looks for a file called firebase-messaging-sw.js in a location where your files are served.

Create this file within the ./public and paste the following code in it:

// ./public/firebase-messaging-sw.js

importScripts("https://www.gstatic.com/firebasejs/6.4.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/6.4.0/firebase-messaging.js");

if (firebase.messaging.isSupported()) {
  var firebaseConfig = {
    messagingSenderId: "YOUR_FIREBASE_MESSAGING_SENDER_ID"
  };
  firebase.initializeApp(firebaseConfig);
  const messaging = firebase.messaging();
  messaging.setBackgroundMessageHandler(function(payload) {
    console.log(' Received background message ', payload);
    var sender = JSON.parse(payload.data.message);
    var notificationTitle = 'CometChat Pro Notification';
    var notificationOptions = {
      body: payload.data.alert,
      icon: sender.data.entities.sender.entity.avatar,
    };
    return self.registration.showNotification(
      notificationTitle,
      notificationOptions
    );
  });
  self.addEventListener('notificationclick', function(event) {
    event.notification.close();
    //handle click event onClick on Web Push Notification
  });
}

The messaging.setBackgroundMessageHandler() function will listened and received messages as payload and use it to create a push notification. Replace the YOUR_FIREBASE_MESSAGING_SENDER_ID placeholder with your Firebase app messagingSenderId

Test the application

Go ahead and restart the application with npm run serve if its currently not running. Open two different tabs and try to send message from one browser. You will receive push notifications

Conclusion

In this tutorial, you have learned how to build a Vuejs group chat application and add push notifications feature to it by using CometChat JavaScript SDK and Firebase.One important takeaway here is the usage of service worker which ensures that your web push notifications show even when your website is closed.

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