Push Notifications

Push has four parts: permission, service worker, browser push subscription, and a server that sends Web Push messages.

sequenceDiagram
    participant App as Web app
    participant SW as Service worker
    participant Push as Browser push service
    participant API as App server

    App->>App: Ask permission after user intent
    App->>SW: Ensure registration
    App->>Push: subscribe with applicationServerKey
    Push-->>App: PushSubscription
    App->>API: Store subscription for user
    API->>Push: Send Web Push payload
    Push->>SW: push event
    SW->>SW: showNotification

Subscription Sketch

const permission = await Notification.requestPermission();

if (permission === 'granted') {
  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: publicVapidKey,
  });

  await fetch('/api/push-subscriptions', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(subscription),
  });
}

Service Worker Handler

self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? { title: 'Update' };

  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192.png',
      data: { url: data.url ?? '/' },
    })
  );
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  const url = event.notification.data?.url ?? '/';
  event.waitUntil(clients.openWindow(url));
});

Rules

  • Ask permission from a user gesture after explaining value.
  • Treat push subscriptions as secret capability URLs.
  • Support unsubscribe and expired subscription cleanup.
  • Test denied permission, quiet UI, expired subscription, and click routing.
  • Do not promise uniform behavior across all browsers and OS versions without device tests.