How to add push notifications to a progressive web app

Pim Hooghiemstra
Nov 21, 2018 · 14 min read
Image for post
Image for post
Photo by Sara Kurfess on Unsplash

Introduction

This is the second post on building progressive web apps (PWA) using the new Vue CLI 3. So far, as a first step towards push notifications, we created a PWA that sends a (normal) notification when a user clicks the button. If you haven’t read this part, it is available here.

In this post push notifications will be introduced. The terminology is a bit difficult because we’ll use notifications, messages and so on, and it’s easy to get lost. Let me try to elaborate a bit more by showing a diagram that visualizes how push notifications work.

Image for post
Image for post
1] incoming: “new post published”, 2a] select subscribed users and create push notifications, 2b] send push notifications to unique endpoints, 3] send push event and 4] show notification in OS of user.

So let’s assume that a new post has been added on a blog website (for example Medium.com). Furthermore, assume that you follow the author and you have enabled notifications for new posts.

At the moment the new post is published by the author (1), all followers of that author need to get notified using push notifications. Hence, the code (running on the server) that handles the submission of the new post looks up all the followers (2a) and sends a push notification to each one of them (2b). Each push notification first ends up at the browser vendor and it is this browser vendor that sends the actual push event to the user’s browser (3). A service worker listens for this push event and creates a notification using the Notification API of the browser to show a message like ‘Hey we have a new post for you’ to the follower (4).

A short note on the browser vendor. I like to think about it as a server from the browser builder. So for the Chrome browser it is as server from Google. So the browser vendor and the user’s browser ar two different things here.

What’s in this post?

In this post we’ll build a simplified version of this flow: a button click will act as the trigger to send a push notification (comparable with the publication of a new post). To make the notification message a bit more dynamic we’ll add a textarea. This way you can send your own message in the push notification.

To be able to send a push notification, we need to have users. Hence, as soon as someone enables notifications, we create a dummy user (and subscription) on the server, and store the id of the new user on the client (e.g., in localStorage). With the users setup this way, we ensure that when type some text in the textarea and click the new notify button, a push notification showing this text will be sent.

To achieve this, we’ll set up a backend (API) for the demo app we build before. We create this API using Laravel. To make the push notifications work, we’ll use the webpush package for Laravel that extends the built in notification channels.

However, work needs to be done on the frontend as well. To be able to receive a push notifications, a user needs to be subscribed to push notifications. This is done by using the browser’s Push API.

It is going to be quite a long ride, so let’s summarise the steps:

  1. Install Laravel API

The code is available on Github (Laravel API, Vue PWA). We also have a demo setup!

Install Laravel API

To build the API in Laravel, start by creating a fresh installation of Laravel as described in the docs.

For simplicity we forget about authentication for the moment (for a production app you better take care of this though). Hence, we change the existing migration for creating the users table:

Schema::create('users', function (Blueprint $table) { 
$table->increments('id');
$table->string('name');
$table->timestamps();
});

In order to create a subscription for a user, we need to have users. As mentioned before, a good moment to create a user is once they click the button ‘enable notifications’ in your app. A request should be send to the Laravel API to create a new user. Upon creation, the API should respond with the newly created user.

Create an API endpoint to create a user

In our Larevel API, we’ll create the following endpoint in routes/api.php

Route::post('user', 'UserController@createOrRetrieve');

We create the UserController using the artisan command

php artisan make:controller UserController

The store function simply creates (and returns) a new user with a unique random string name.

/** 
* If the request contains a username, retrieve this user from
* the database and return it.
* Otherwise, create a new user with a random name and return it.
* @return new user object as json
*/
public function createOrRetrieve(Request $request) {
if ($request->has('username') && !is_null($request->username)) {
$user = User::where(['name' => $request->username])->first(); }
else {
// create a new user instance.
// name of the user is just a hashed string
$user = User::create(['name' => Str::uuid()]);
}
return response()->json(compact('user'));
}

We can test this API function using Postman and see that a new user has been created.

Image for post
Image for post
The api endpoint ‘user’ return a new user.

Installing the Webpush package

The next step is to setup the subscriptions in order for people to get notified when ‘something happens’. Subscriptions are part of the ‘Web push notifications channel for Laravel’ package, so we follow their installation guide all the way to (and including) generation of the VAPID keys. VAPID keys are the de facto way of encrypting your push notifications and without them it is not possible to send additional data with your notifications. I’ll explain more on this in a bit.

After installation, running the migrations and generating the VAPID keys we have a new table push_subscriptions in the database. The package offers two methods to write to this table (updatePushSubscription and deletePushSubscription), we’ll make use of these methods in the next step.

With this package installed, we can add two more endpoints to our API. The first one to add/ update a subscription, the second one to delete a subscription. Again, we can test these endpoints using postman, but be sure to pass the correct payload (outlined in the controller code below) to your requests. Don’t forget to create the SubscriptionController.

// create or update a subscription for a user Route::post('subscription', 'SubscriptionController@store'); // delete a subscription for a user Route::post('subscription/delete', SubscriptionController@destroy');

With these two endpoints we conclude the backend part for now. It is time to setup the subscription in the frontend. That is, we are going to talk to the ‘user’ endpoint to create a user, then we create a subscription on the frontend (using the VAPID public key) and finally, we’ll talk to the ‘subscription’ endpoint to create a subscription on the backend.

In the end we have all ingredients in place to be able to trigger a real push notification.

Creating subscriptions on the frontend

In my previous post, I described how I started with the development of the frontend. We implemented a rather simple app that triggers a Notifications once the user clicks the button. So far, we handled the click on the green button ‘Enable notifications’ in the Home component. Two methods were defined for this component, askPermission and showNotification. Here we’ll extend the behavior. Let me explain what we are after here.

Users visiting the page either visit for the first time, or they come back and have (or have not) enabled the notifications in an earlier visit. Hence, for returning users we have to check if they have notifications enabled. We’ll do this in the mounted hook of the Home component, which fires once the component is mounted on the page. If notifications are enabled, the text on the button needs to be changed from ‘Enable notifications’ (the default) to ‘Disable notifications’. This way, notifications can be turned off by the user. If notifications are not enabled, the user is treated as a first time visitor.

For a user, everything starts with clicking the button. Let’s assume the user clicks ‘Enable notifications’, which steps are involved?

  1. We have to check if notifications and service workers are supported by the browser;

These steps are implemented in the toggleSubscription method of the Home component shown below.

Some explanation of this code is required, so let’s go through each of the steps mentioned above.

  1. Support for notifications and service workers is implemented in the created hook of the component (lines 162–164). We simply toggle the notificationsSupported flag based on support of the browser.

A returning user and disabling notifications

A returning user may have enabled the notifications in a previous visit. Hence, the flow for this user type is

  1. Check if a subscription is available (on the frontend, using pushManager);

It becomes more interesting when a user clicks ‘disable notifications’. In that case we need to unsubscribe, both on the client and on the server. This is also coded in the toggleSubscription method of the component:

  1. The subscription that was found using the Pushmanager is used to identify the user on the backend. We use the ‘endpoint’ field in the subscription as a key, since such an endpoint is unique for each subscription. We call the API endpoint ‘subscription/delete’ passing the ‘endpoint’ field.

With these flows in place a user can now toggle and untoggle the button several times. Initially a user is created. Because we store the username on the frontend, subsequent subscriptions use this user on the backend. Keep in mind that this app doesn’t have authentication, because we use it only to demonstrate the technique behind push notifications. In a real application we would implement a proper login flow and users would need to login before enabling the notifications.

A note on client side subscriptions

The subscription we create on the frontend using the pushManager is unique for the combination of device and browser. This means that when visiting the page in another browser or on another device no subscription will be found when executing the getSubscription method of the pushManager.

Furthermore, a subscription is also connected to the active service worker. If a user clears his cookies and other browser settings including the service worker, the subscription is also lost.

Trigger a push notification

We have the subscriptions in place, let’s move on to the fun part of this post: sending push messages. We need a way to trigger a push notification. In this simple setup we just add another button, which is shown as soon as there is a valid subscription found for the user. In a real live application, the push notification would be triggered when someone adds a new post to a blog or someone else comments on a post. Clicking this button triggers the server to send a push message. The server uses the endpoint found in the subscription (which was stored in the database on the backend, i.e., https://fcm.googleapis.com/fcm/send/dkjhsdkfj ) and creates a new notification. The notification will be send to the endpoint, which in turn will send it to the client (as a push event). On the client, it is the service worker that needs to listen to this push event to be able to create a notification for the user. Let’s see this in action.

In the Home component we add an extra block (lines 6–11) that is only shown when notifications are enabled. It gives the user the possibility to enter some text in a textarea and send it to the backend by clicking the button ‘Notify with Push’. Our app now looks like this:

Image for post
Image for post

The click on ‘Notify with Push’ triggers the createPushNotification method (lines 135–145). This method simply sends the username (which was stored in localStorage and is used to identify the correct user to be notified) and the message to the server using a new API endpoint ‘notify’. When the promise resolves, the textarea is cleared.

Creating a push notification on the server

We have to create the new endpoint ‘notify’ and do so in routes/api.php in the Laravel API. This endpoint is handled by the NotificationController which we add as well.

In this controller we add a function notify. This function starts by looking at the passed username. It is required and should not be empty, otherwise we can’t find the corresponding user in the database. Assuming that we do find this user, we create a message (the body of the Push notification) by looking at the text passed with the request. If it is empty we come up with a standard text ourselves, otherwise we just use the passed in text. Finally, we use the notification system in Laravel to notify the user.

First, we create a notification (SayHello). Next we call the notify method on the user instance and pass this notification. A notification is simply created using an artisan command

php artisan make:notification SayHello

The notification can be send via various channels, and WebPush is just one of those channels. For testing purposes, we also like to add the notification to the database. We don’t want to send it by mail though, so we get rid of that in the via method. Here is the code of the SayHello notification:

Please note the two use statements (lines 10,11) at the top of the file regarding WebPushMessage and WebPushChannel.

The constructor accepts the message, this is the message the user send with the request. As discussed, the via method is updated to send via WebPush and to add a record to the database.

Therefore, we need a toDatabase method (which simply returns the message) and a toWebPush method. Here we set up the push notification by returning a new WebPushMessage (lines 47–55). In this example we simply add a title and a body but we can add a lot more typical notification properties as you can see in the repository of the package.

With this setup, the API endpoint is finished.

Listening for a push event

When the notification is sent to the WebPush channel, it will be forwarded to the frontend. The service worker is responsible to catch this event and does so by implementing the listener for the push event. Hence, we add this to the service worker code:

// Listen to Push 
self.addEventListener('push', (e) => {
let data
if (e.data) {
data = e.data.json()
}
console.log('data for notification', data); const options = {
body: data.body,
icon: '/img/icons/android-chrome-192x192.png',
image: '/img/autumn-forest.png', vibrate: [300, 200, 300],
badge: '/img/icons/plint-badge-96x96.png',
}

console.log('options passed to Notification', options);
e.waitUntil(self.registration.showNotification(data.title, options))
})

The incoming event (e) may contain data which we retrieve using the e.data.json() method. Then we create a notification options object which is passed to the showNotification method.

Build for production and test

Now all we have to do is build the app for production and try it out. Cross your fingers and type some text in the textarea and hit the button…

Image for post
Image for post

Concluding remarks

This concludes this post on setting up Push notifications using the new Vue CLI with a Laravel API. I tried to keep everything as simple and to the point as possible, but in a real world app there are many things that you also have to consider which might serve as a subject for future posts. Some of these things include

  • How do we handle updates of the service worker code

and many more.

The code for this post can be found on GitHub where I have made a second repository for the Laravel API. For the frontend app, I just build upon the repo created for the previous post. The code for this post is tagged v0.2.

As before, the demo is available at our demo website.


Originally published at www.blog.plint-sites.nl on November 21, 2018.

PLint-sites

Dutch based webdevelopment company, focussing on modern…

Pim Hooghiemstra

Written by

Love to build Laravel + Vue applications! Founder of PLint-sites. https://plint-sites.nl

PLint-sites

Dutch based webdevelopment company, focussing on modern webapplications using Laravel and Vue. Mainly writes about stuff to achieve cool things!

Pim Hooghiemstra

Written by

Love to build Laravel + Vue applications! Founder of PLint-sites. https://plint-sites.nl

PLint-sites

Dutch based webdevelopment company, focussing on modern webapplications using Laravel and Vue. Mainly writes about stuff to achieve cool things!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store