Now Reading
Demystifying Internet Push Notifications | PQVST

Demystifying Internet Push Notifications | PQVST

2023-11-24 04:19:38

For my current One Day Build: Expense Tracking undertaking I needed to allow notifications in a progressive internet app. ChatGPT struggled to generate any good code for me, and I additionally struggled to seek out any minimal clear explanations on-line.

This weblog submit goals to stroll by way of all of the items wanted to implement internet push notifications. I’ve additionally created a whole minimal working instance with a node.js backend, for individuals who favor to only view the code as an alternative:

How does Internet Push work?

Briefly, internet push works by your app interacting with a “push service” offered by the browser vendor. There are three important steps for this, outlined within the diagram beneath:

web push overview

1. Create Subscription

Your client-side code creates a Internet Push Subscription, and sends the subscription to your backend. A subscription is just a little bit of JSON that incorporates a singular (browser particular) endpoint and a few encryption keys. Right here’s an instance of what a subscription appears like for Firefox (therefore the endpoint).

  "endpoint": "",
  "expirationTime": null,
  "keys": {
    "auth": "...",
    "p256dh": "..."

With Safari you’ll obtain an Apple endpoint ( and in Chrome you’ll get a Google endpoint (

2. Ship Notification

Your backend code makes use of the subscription particulars to ship a push notification to the push service hosted by the browser vendor. The push service then makes certain to ship it again to your browser.

3. Deal with Notification

Your browser receives the push notification and triggers a callback in your service employee. Your service employee can then select to show a notification or do no matter else you wish to do.

Pre-Requisite: VAPID keys

VAPID keys are required to ensure Internet Push works on all the primary browsers. VAPID stands for Voluntary Application Server Identification (VAPID) and is basically only a spec for how you can generate a set of public-private keys. Regardless of the identify, they’re actually probably not voluntary, since each Chrome and Safari require you to offer VAPID keys. The one browser I’ve examined that doesn’t require them is Firefox.

In the event you attempt to subscribe to push notifications in Safari with out VAPID keys you’ll obtain the next error:

Subscribing for push requires an applicationServerKey

In Chrome you’ll obtain this:

DOMException: Registration failed - lacking applicationServerKey, and gcm_sender_id not present in manifest

When you can technically generate your VAPID keys by your self, it’s a lot simpler to make use of a generator, like, which can generate a set of keys for you.

Server-Aspect Implementation

As a way to ship internet push notifications out of your backend software server, you must assemble, encode, and encrypt the messages correctly. Relying on the programming language you’re utilizing, it’s seemingly you’ll be capable of discover a library that will help you with this.

In the event you’re utilizing a node.js backend then including web-push assist is very easy. There’s a pleasant library known as web-push that takes care of crafting internet push notifications for you.

1. Import and configure internet push

import webPush from 'web-push';

To get began with web-push merely name the operate to set your VAPID keys. You’ll want to embody an electronic mail handle as properly (prefixed with mailto:).

// TODO: Generate VAPID keys (e.g.
const vapid = {
  publicKey: '...',
  privateKey: '...',


2. Retailer subscriptions

Internet push subscriptions are generated on the consumer facet, so you’ll most probably want some technique to move subscriptions out of your frontend to your backend. You’ll then additionally wish to save these ultimately, for instance by storing it in your database or simply saving them in a persevered JSON file. In the event you don’t save the subscription knowledge you then’ll lose all of your present subscriptions when your server restarts!

app.submit('/subscribe', authenticateRequest, (req, res) => {
  const sub = req.physique;
  // TODO: Persist subscription (e.g. to db)

Broadcast notifications

The one different factor you should implement is a technique to truly create and ship new notifications. If we’re broadcasting a notification to all subscriptions then we simply merely loop by way of our array of saved subscriptions and name sendNotification utilizing the web-push library.

You’ll obtain an error if a consumer has revoked the notification permission in your web page (or if the subscription has expired). You possibly can catch these errors and take away invalid subscriptions.

async operate pushNotification(payload) {
  await Promise.all( (sub) => {
    attempt {
      await webPush.sendNotification(sub, payload); // throws if not profitable
    } catch (err) {
      console.log(sub.endpoint, '->', err.message);
      // TODO: Delete subscription (e.g. from db)

// Check ship notification
pushNotification('It is a take a look at notification!');


The client-side implementation is a little more sophisticated. You will have two recordsdata: one to your service employee and one to your important client-side software. The one factor we have to put in our server-worker is dealing with the callback for incoming notifications.

Service Employee: /sw.js

self.addEventListener('push', (occasion) => {
  const choices = {
    physique: occasion.knowledge.textual content(),
    icon: '/apple-touch-icon.png',
    badge: '/badge.png',
  occasion.waitUntil(self.registration.showNotification('My App', choices));

Why can we wrap our calls in event.waitUntil? Since service-workers run as a background course of, there’s an opportunity that the server employee pauses/terminates it. By wrapping guarantees in waitUntil we inform the browser that work is on-going and that it shouldn’t terminate our service employee till the work is completed.

Shopper App: /consumer.js

In our important software script we’ve to care for requesting the notifications permission, register our service employee, and really create a push notification subscription utilizing the browser’s pushManager API.

See Also

1. Request notifications permission

First we’d like to ensure we’ve permission to push notifications (with out this our notifications are pointless). Someplace in your web page you’ll most likely wish to show a hyperlink or button that consumer’s can click on to allow notifications.

<a id="promptLink" onclick="onPromptClick()">Allow notifications</a>

If notifications are already granted (or denied) we are able to conceal the hyperlink, or replace the UI accordingly.

operate updatePrompt() {
  if ('Notification' in window) {
    if (Notification.permission == 'granted' || Notification.permission == 'denied') { = 'none';
    } else { = 'block';

operate onPromptClick() {
  if ('Notification' in window) {
    Notification.requestPermission().then((permission) => {
      if (permission === 'granted') {
        console.log('Notification permission granted.');
      } else if (permission === 'denied') {
        console.warn('Notification permission denied.');

2. Register Service Employee and Allow Push Notifications

Subsequent we ensure service employees are supported and register our service employee in order that we are able to obtain notifications. Lastly we’ll use the browser pushManager API to request a push notification subscription, which we’ll then ship to our backend server.

For this step you’ll want your VAPID public key (ensure to solely embody your public key in client-side code, and maintain your personal key secret).

The method is fairly self explanatory. Be sure that service employees are supported, register the service employee, after which test if we have already got an energetic push notification subscription, in any other case, create a brand new subscription.

In each instances we ship the subscription knowledge to our backend to make it possible for it’s saved.

const vapidPublicKey = '...';

async operate initServiceWorker() {
  if ('serviceWorker' in navigator) {
    const swRegistration = await navigator.serviceWorker.register('sw.js');
    const subscription = await swRegistration.pushManager.getSubscription();
    if (subscription) {
      console.log('Consumer is already subscribed:', subscription);
    } else {
      const subscription = await swRegistration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: vapidPublicKey
      console.log('Consumer subscribed:', subscription);
  } else {
    console.warn('Service employee isn't supported');

operate sendSubscriptionToServer(subscription) {
  fetch('/subscribe', {
    technique: 'submit',
    physique: JSON.stringify(subscription),
    headers: { 'content-type': 'software/json' }

window.addEventListener('load', () => {

Debugging Tip: Reloading the Service Employee

Observe that the service employee does not mechanically reload if you reload the web page. In the event you’re working domestically and making modifications to the service employee, you both must manually reload the service employee in your browser’s dev instruments or you may allow the choice to mechanically reload the service employee when the web page reloads!

Bonus Characteristic: Clickable notifications

One other factor you’ll most likely wish to do is make your notifications clickable. Initially I assumed that clicking a notification would mechanically open up the related web page. Nonetheless, this isn’t the case. You’ll need to implement this your self in your service employee.

The code to realize this is a little more sophisticated than I anticipated. Right here’s one of the best instance I managed to seek out on-line, which ensures that the notification is cleared after clicking it, after which both opens a brand new browser occasion/tab or focuses the prevailing tab if it’s already open.

const targetUrl = '...';

self.addEventListener('notificationclick', (occasion) => {
  occasion.notification.shut(); // Android wants specific shut.
    shoppers.matchAll({sort: 'window'}).then( windowClients => {
      // Test if there may be already a window/tab open with the goal URL
      for (var i = 0; i < windowClients.size; i++) {
        var consumer = windowClients[i];
        // If that's the case, simply focus it.
        if (consumer.url === targetUrl && 'focus' in consumer) {
          return consumer.focus();
      // If not, then open the goal URL in a brand new window/tab.
      if (shoppers.openWindow) {
        return shoppers.openWindow(targetUrl);

From my testing up to now, this appears to work properly on all browsers (Firefox, Chrome, Safari, Android, iOS).

Source Link

What's Your Reaction?
In Love
Not Sure
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top