JavaScript video tutorial: Progressive Web App: Push notification
After discovering the principle of progressive web apps, I suggest you explore service workers in more depth with the implementation of a Push notification system.
The different steps
We ask for permission
To start, we will check if the user has not already activated the permission to receive notifications for the current page via Notification.permission
and propose a button to launch the activation of notifications on the site. When clicking on this button we will ask for permission to notify the user.
async function askPermission () {
const permission = await Notification.requestPermission ();
if (permission === "granted") {
registerServiceWorker ();
}
}
The user is subscribed via the PushManager
If the user accepts the permission, we will be able to subscribe to the push system offered by the browser through the PushManager.
This PushManager
is accessible via the ServiceWorkerRegistration interface that can be obtained when registering a Service Worker. When the user subscribes, it will be necessary to communicate to the browser a public VAPID key which will ensure the origin of future push messages.
async function registerServiceWorker () {
const registration = await navigator.serviceWorker.register ("/ sw.js");
let subscription = await registration.pushManager.getSubscription ();
// The user is not already subscribed, we subscribe to the push notification
if (! subscription) {
subscription = await registration.pushManager.subscribe ({
userVisibleOnly: true,
applicationServerKey: await getPublicKey (),
});
}
await saveSubscription (subscription);
}
async function getPublicKey () {
const {key} = await fetch ("/ push / key", {
headers: {
Accept: "application / json",
},
}). then ((r) => r.json ());
return key;
}
In response to the subscription, we will retrieve an object of type PushSubscription which will contain information about the user's subscription
- endpoint, which will be the point of entry to contact to send the notification
- options, an object that will contain the options used to create the subscription
We save the subscription on our server
Once the subscription has been recovered, we will be able to send the information to our server which will save the information in the database.
/ **
* @param {PushSubscription} subscription
* @returns {Promise}
* /
async function saveSubscription (subscription) {
await fetch ("/ push / subscribe", {
method: "post",
headers: {
"Content-Type": "application / json",
Accept: "application / json",
},
body: subscription.toJSON (),
});
}
Our server will receive a JSON that will look like:
{
"endpoint": "https://push.service.ltd/bd2715f4-ca9f-11eb-b8bc-0242ac130003"
"keys": {
"p256dh": "BNhaJR_GbGj4oLX7dV1xVIVLSmSo-gQVKhxEx5CNj-JapZg4PyQp6aSh-3SBzzZZcO-z7yIn7qfSvHAzYoLtB6E",
"auth": "YwWWBhWbopKvge1eBT82AA"
}
}
Service Worker
When our user receives a notification, the service worker will be notified through an event push
and will have to react accordingly.
self.addEventListener ("install", () => {
self.skipWaiting ();
});
self.addEventListener ("push", (event) => {
const data = event.data? event.data.json (): {};
event.waitUntil (self.registration.showNotification (data.title, data));
});
We can also detect the click on the notification and act accordingly
self.addEventListener ("notificationclick", (event) => {
event.notification.close ();
event.waitUntil (openUrl ("http://grafikart.fr"));
});
/ **
* Open the url or focus the page that is already open on this url
* @param {string} url
** /
async function openUrl (url) {
const windowClients = await self.clients.matchAll ({
type: "window",
includeUncontrolled: true,
});
for (let i = 0; i <windowClients.length; i ++) {
const client = windowClients (i);
if (client.url === url && "focus" in client) {
return client.focus ();
}
}
if (self.clients.openWindow) {
return self.clients.openWindow (url);
}
return null;
}
Sending server side notifications
The sending of push notifications is done on the server side by contacting the entry point received during the user's subscription. The keys will also be used to authenticate the user and encrypt the message to be sent to the push service. Our private key will be used to sign the message and prove that we are indeed at the origin of the request. As the protocol is quite complex, we can rely on third-party libraries to simplify the work.
Here is a small example for PHP:
$ webPush = new WebPush ((
'VAPID' => (
'subject' => 'mailto: contact@grafikart.fr',
'publicKey' => env ('VAPID_PUBLIC_KEY'),
'privateKey' => env ('VAPID_PRIVATE_KEY'),
),
));
foreach ($ user-> subscriptions as $ subscription) {
$ webPush-> queueNotification (
Subscription :: create ((
'endpoint' => $ subscription-> endpoint,
'publicKey' => $ subscription-> public_key,
'authToken' => $ subscription-> auth_token,
)),
json_encode ((
'message' => 'Hello people',
'title' => 'My title'
));
);
}
foreach ($ webPush-> flush () as $ report) {
$ endpoint = $ report-> getRequest () -> getUri () -> __ toString ();
if ($ report-> isSuccess ()) {
dump ("(v) Message has been sent {$ endpoint}.");
} else {
dump ("(x) Unable to send message {$ endpoint}: {$ report-> getReason ()}");
}
}
When an error is obtained in return for sending a message, you can remove the user's subscription from your server because this means that the user does not wish (or cannot following an uninstallation) to receive notifications.