Chapter 53: Web Notifications API & Native Push

Push Notifications with Service Workers

**Part 11: HTML5 APIs Part 2** — Chapter 53: Notifications API

LEARNING OBJECTIVES
  • Understand the Web Push Protocol and the Push API handshake.
  • Generate VAPID public/private key pairs for secure push subscription authorization.
  • Trigger background notifications from Service Worker push event listeners.
  • Handle notification click navigation via clients.openWindow() or client.focus().
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story

Standard notifications created via new Notification() only work while your tab is open in the foreground.

Push Notifications with Service Workers connect your user directly to the operating system's Push Service (Google FCM, Apple APNs, Mozilla autopush). Even when the browser is completely closed or the phone screen is off, the server sends a wake-up packet. The OS wakes your Service Worker in the background, which calls registration.showNotification().

[Your App Server] ===(VAPID Encrypted Push)===> [Browser Vendor Push Service]
                                                               |
                                                               v
                                                 [OS / Android / macOS / Windows]
                                                               |
                                                               v
                                                 [Background Service Worker]
                                                               |
                                                               v
                                                 [registration.showNotification()]

Technical Deep Dive & Specifications

// Inside Service Worker (sw.js)
self.addEventListener('push', (event) => {
  const data = event.data ? event.data.json() : { title: 'New Alert', body: 'You have an update.' };

  const options = {
    body: data.body,
    icon: '/images/icon-192.png',
    badge: '/images/badge-72.png',
    data: { url: data.url || '/' },
    vibrate: [100, 50, 100],
    actions: [
      { action: 'open', title: 'Open App' },
      { action: 'dismiss', title: 'Dismiss' }
    ]
  };

  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  if (event.action === 'dismiss') return;

  event.waitUntil(
    clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
      // Focus existing tab if open
      for (const client of clientList) {
        if (client.url === event.notification.data.url && 'focus' in client) {
          return client.focus();
        }
      }
      // Otherwise open new window
      if (clients.openWindow) {
        return clients.openWindow(event.notification.data.url);
      }
    })
  );
});

📌 Key Takeaways

  • Use self.registration.showNotification() instead of new Notification() inside Service Workers.
  • Wrap notification creation inside event.waitUntil() to prevent the browser from killing the background worker early.
  • Always close the notification on click using event.notification.close().
  • --

❓ Knowledge Check

1. Which of the following is correct?

2. Which of the following is correct?

🏋️ Study Exercise

Task: Review the javascript example above. Identify the key directives and their purpose, then try writing your own version from memory.

// Inside Service Worker (sw.js) self.addEventListener('push', (event) => { const data = event.data ? event.data.json() : { title: 'New Alert', body: 'You have an update.' }; const options = { body: data.body, icon: '/images/icon-192.png', badge: '/images/badge-72.png', data: { url: data.url || '/' }, vibrate: [100, 50, 100], actions: [ { action: 'open', title: 'Open App' }, { action: 'dismiss', title: 'Dismiss' } ] }; event.waitUntil( self.registration.showNotification(data.title, options) ); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); if (event.action === 'dismiss') return; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { // Focus existing tab if open for (const client of clientList) { if (client.url === event.notification.data.url && 'focus' in client) { return client.focus(); } } // Otherwise open new window if (clients.openWindow) { return clients.openWindow(event.notification.data.url); } }) ); });