In this tutorial, learn how you can build a message delivery status feature in realtime, for your chat app with JavaScript and Pusher.
When building realtime applications, we often want to know the actual time when a process or event occurs. For example, in an instant messaging application we want to know if and when our message was delivered to the intended client. We see this in WhatsApp where messages are sent in realtime and you see status of each message when it is delivered and read, with double grey tick when delivered and double blue tick when read. We can easily build a message delivery status using Pusher and JavaScript.
Pusher has a concept of channels and event which are fundamental to it. We can send a message to a client through a channel and have that client notify us of a read receipt by triggering an event which the sender will listen to and react accordingly.
Channels provide a way of filtering data and controlling access to different streams of information, while events are the primary method of packaging messages in the Pusher system which forms the basis of all communication.
To implement a message delivery status with Pusher, we’ll have to subscribe to a channel and listen for events on the channel. We’ll build a simple chat application in JavaScript that will send out messages to a client and the client will trigger an event when received.
To use Pusher API we have to signup and create a Pusher app from the dashboard. We can create as many applications as we want and each one will get an application id and secret key which we’ll use to initialise a Pusher instance on client or server side code.
We will use channels as a means to send messages and trigger events through the channel. There are 3 types of channels in Pusher:
Clients needs to be authenticated to use the private and presence channels. For the sample app, we’ll build the client using vanilla JS and server (for authentication) using NodeJS. Because I don’t want message to go through the server, but from client to client, and I don’t need to know if the user is online or away, I’ll use a private channel for this demonstration, but the same technique will apply using any channel type. Client events can only be triggered in private or presence channels, and to use any of these channel types, the user/client must be authenticated, therefore the need for NodeJS back-end for authentication.
Also, to use client events, they must be enabled for the application. Go to your Pusher dashboard and on the App Settings tab, select “Enable Client Event” and update.
Since we’re building our backend in Node using Express, let’s initialise a new node app and install the needed dependencies. Run the following command:
npm init
and select the default optionsnpm i --save body-parser express pusher
to install express and the Pusher node packageAdd a new file called server.js
which will contain logic to authenticate the Pusher client and also render the static files we’ll be adding later. This file will contain the content below
1var express = require('express'); 2var bodyParser = require('body-parser'); 3 4var Pusher = require('pusher'); 5 6var app = express(); 7app.use(bodyParser.json()); 8app.use(bodyParser.urlencoded({ extended: false })); 9 10var pusher = new Pusher({ appId: APP_ID, key: APP_KEY, secret: APP_SECRET, cluster: APP_Cluster }); 11 12app.get('/',function(req,res){ 13 res.sendFile('index.html', {root: __dirname }); 14}); 15 16app.use(express.static(__dirname + '/')); 17 18app.post('/pusher/auth', function(req, res) { 19 var socketId = req.body.socket_id; 20 var channel = req.body.channel_name; 21 var auth = pusher.authenticate(socketId, channel); 22 res.send(auth); 23}); 24 25var port = process.env.PORT || 5000; 26app.listen(port, function () { 27 console.log(`Example app listening on port ${port}!`) 28});
We instantiate Pusher by passing in an object that contains the details of our app ID and secret key, which can be found in the Pusher dashboard, on the App Keys tab. The line var auth = pusher.authenticate(socketId, channel);
authenticates the client with Pusher and returns an authentication code to the calling client. To allow this file to run when we start npm, we update package.json with the following value:
1"scripts": { 2 "start": "node server.js", 3 "test": "echo \"Error: no test specified\" && exit 1" 4 },
With the back-end in place, we now move on to crafting the front-end. We’ll be using the template from this site with a slight modification.
Add a new file named index.html
and style.css
with the following content in each file:
Index.html
1<!DOCTYPE html> 2<html> 3<head> 4 5 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> 6 7 8 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> 9 10 <scriptsrc="https://code.jquery.com/jquery-2.2.4.min.js" 11 integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" 12 crossorigin="anonymous"></script> 13 14 15 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> 16 17 <link rel="stylesheet" href="style.css"> 18 <script src="https://js.pusher.com/4.0/pusher.min.js"></script> 19 <script src="index.js"></script> 20</head> 21<body> 22 <div class="container"> 23 <div class="row form-group"> 24 <div class="col-xs-12 col-md-offset-2 col-md-8 col-lg-8 col-lg-offset-2"> 25 <div class="panel panel-primary"> 26 <div class="panel-heading"> 27 <span class="glyphicon glyphicon-comment"></span> Comments 28 <div class="btn-group pull-right"> 29 <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown"> 30 <span class="glyphicon glyphicon-chevron-down"></span> 31 </button> 32 <ul class="dropdown-menu slidedown"> 33 <li><a href="http://www.jquery2dotnet.com"><span class="glyphicon glyphicon-refresh"> 34 </span>Refresh</a></li> 35 <li><a href="http://www.jquery2dotnet.com"><span class="glyphicon glyphicon-ok-sign"> 36 </span>Available</a></li> 37 <li><a href="http://www.jquery2dotnet.com"><span class="glyphicon glyphicon-remove"> 38 </span>Busy</a></li> 39 <li><a href="http://www.jquery2dotnet.com"><span class="glyphicon glyphicon-time"></span> 40 Away</a></li> 41 <li class="divider"></li> 42 <li><a href="http://www.jquery2dotnet.com"><span class="glyphicon glyphicon-off"></span> 43 Sign Out</a></li> 44 </ul> 45 </div> 46 </div> 47 <div class="panel-body body-panel"> 48 <ul class="chat"> 49 50 </ul> 51 </div> 52 <div class="panel-footer clearfix"> 53 <textarea id="message" class="form-control" rows="3"></textarea> 54 <span class="col-lg-6 col-lg-offset-3 col-md-6 col-md-offset-3 col-xs-12" style="margin-top: 10px"> 55 <button class="btn btn-warning btn-lg btn-block" id="btn-chat">Send</button> 56 </span> 57 </div> 58 </div> 59 </div> 60 </div> 61</div> 62 63<script id="new-message-other" type="text/template"> 64 <li class="left clearfix"> 65 <span class="chat-img pull-left"> 66 <img src="http://placehold.it/50/55C1E7/fff&text=U" alt="User Avatar" class="img-circle" /> 67 </span> 68 <div class="chat-body clearfix"> 69 <p> 70 {{body}} 71 </p> 72 </div> 73 </li> 74</script> 75 76<script id="new-message-me" type="text/template"> 77 <li id="{{id}}" class="right clearfix"> 78 <span class="chat-img pull-right"> 79 <img src="http://placehold.it/50/FA6F57/fff&text=ME" alt="User Avatar" class="img-circle" /> 80 </span> 81 <div class="chat-body clearfix"> 82 <div class="header"> 83 <small class="text-muted">{{status}}</small> 84 85 </div> 86 <p> 87 {{body}} 88 </p> 89 </div> 90 </li> 91</script> 92 93</body> 94</html>
style.css
1@import url("http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"); 2.chat 3{ 4 list-style: none; 5 margin: 0; 6 padding: 0; 7} 8 9.chat li 10{ 11 margin-bottom: 10px; 12 padding-bottom: 5px; 13 border-bottom: 1px dotted #B3A9A9; 14} 15 16.chat li.left .chat-body 17{ 18 margin-left: 60px; 19} 20 21.chat li.right .chat-body 22{ 23 margin-right: 60px; 24} 25 26 27.chat li .chat-body p 28{ 29 margin: 0; 30 color: #777777; 31} 32 33.panel .slidedown .glyphicon, .chat .glyphicon 34{ 35 margin-right: 5px; 36} 37 38.body-panel 39{ 40 overflow-y: scroll; 41 height: 250px; 42} 43 44::-webkit-scrollbar-track 45{ 46 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 47 background-color: #F5F5F5; 48} 49 50::-webkit-scrollbar 51{ 52 width: 12px; 53 background-color: #F5F5F5; 54} 55 56::-webkit-scrollbar-thumb 57{ 58 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 59 background-color: #555; 60}
The page we added holds a 1-to-1 chat template. At line 18 we added script to load the Pusher JavaScript library, and at 19 we’re loading a custom JavaScript file which we will use to handle interactions from the page. Add this file with the following content:
index.js
1$(document).ready(function(){ 2 // Enable pusher logging - don't include this in production 3 Pusher.logToConsole = true; 4 5 var pusher = new Pusher('APP_KEY', { 6 cluster: 'eu', 7 encrypted: false 8 }); 9 10 var channel = pusher.subscribe('private-channel'); 11 //channel name prefixed with 'private' because it'll be a private channel 12});
From the code above we first connect to Pusher by creating a Pusher object with the App_Key and cluster. These values are gotten from the Pusher dashboard. encrypted
is set to false to allow it to send information on an unencrypted connection.
Afterwards, we subscribe to a channel which is to be used for sending out messages. Channel names can be anything, but must be a maximum of 164 characters. Another restriction on a private channel is that it must be prefixed with private-
.
Next we bind to events. This way we can receive messages from a client through the channel we subscribed to. Add the following line to index.js
1channel.bind('client-message-added', onMessageAdded); 2channel.bind('client-message-delivered', onMessageDelivered); 3 4$('#btn-chat').click(function(){ 5 const id = generateId(); 6 const message = $("#message").val(); 7 $("#message").val(""); 8 9 let template = $("#new-message-me").html(); 10 template = template.replace("{{id}}", id); 11 template = template.replace("{{body}}", message); 12 template = template.replace("{{status}}", ""); 13 14 $(".chat").append(template); 15 16 //send message 17 channel.trigger("client-message-added", { id, message }); 18}); 19function generateId() { 20 return Math.round(new Date().getTime() + (Math.random() * 100)); 21} 22 23function onMessageAdded(data) { 24 let template = $("#new-message-other").html(); 25 template = template.replace("{{body}}", data.message); 26 27 $(".chat").append(template); 28 29 //notify sender 30 channel.trigger("client-message-delivered", { id: data.id }); 31} 32 33function onMessageDelivered(data) { 34 $("#" + data.id).find("small").html("Delivered"); 35}
I will be triggering events from the client and don’t want it to go through the back-end or be validated. This is just for this demo. Client events must be prefixed by client-
that is why I have done so with the code above. Events with any other prefix will be rejected by the Pusher server, as will events sent to channels to which the client is not subscribed.
It is important that you apply additional care when when triggering client events. Since these originate from other users, and could be subject to tampering by a malicious user of your site.
client-message-added
will be triggered when a user enters a new message. Once the other user gets the message, it is displayed on the page, and client-message-delivered
event is triggered to notify the sender of receipt. This way we can achieve the objective of getting notified about message delivery status in our application.
Run the application and see how it works.
With what you’ve seen so far, and knowing that Channels and Events are the fundamentals of Pusher, I hope I’ve shown you how to implement a message delivery status using Pusher and JavaScript. You can find the code on GitHub