How JavaScript Push API Manages Push Notifications
JavaScript manages push notifications through a coordinated workflow between the Push API, the Notifications API, and Service Workers. This architecture enables web applications to receive messages from a backend server and display interactive notifications on a user’s device, even when the web page is not actively open in the browser. The following breakdown explains the core components, registration process, and event handling that make JavaScript push notifications functional.
1. The Three Core Components
Push messaging relies on three distinct layers: * The Client
Application and Service Worker: JavaScript running in the
browser and in a background thread (Service Worker) that
listens for network events. * The Application Server:
Your backend server, which creates notifications and sends them to the
push service. * The Push Service: A
browser-vendor-operated server (such as Google FCM or Mozilla Autopush)
that routes messages to the specific browser instance.
2. Requesting User Permission
Before subscribing to push messages, the application must obtain explicit user consent using the Notifications API.
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
throw new Error('Notification permission denied');
}
return permission;
}3. Registering the Service Worker and Subscribing
Once permission is granted, JavaScript registers a Service Worker.
The Service Worker exposes the PushManager interface, which
allows the client to subscribe to the vendor’s Push Service using a
public VAPID (Voluntary Application Server Identification) key.
async function subscribeUserToPush() {
const registration = await navigator.serviceWorker.register('/sw.js');
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY')
});
// Send the subscription object to your backend database
await fetch('/api/save-subscription', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription)
});
}The resulting PushSubscription object contains an
endpoint URL and cryptographic encryption keys (p256dh and
auth), which your backend server stores to address messages
to this specific client.
4. Sending a Push Message from the Server
When your backend needs to send a notification, it encrypts the payload and sends an HTTP POST request to the subscription endpoint using standard Web Push protocols. The browser vendor’s Push Service receives this request, identifies the target device, and queues or delivers the message.
5. Handling the
push Event in the Service Worker
When the message reaches the device, the browser wakes up the
registered Service Worker (if it is not already running) and fires a
push event. The Service Worker extracts the payload and
displays the notification using showNotification().
// sw.js
self.addEventListener('push', (event) => {
let data = { title: 'New Notification', body: 'You have an update.' };
if (event.data) {
data = event.data.json();
}
const options = {
body: data.body,
icon: '/images/icon.png',
badge: '/images/badge.png',
data: { url: data.url || '/' }
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});The event.waitUntil() method ensures the browser does
not terminate the Service Worker before the notification has been
successfully rendered.
6. Handling User Interaction
JavaScript handles clicks on the generated notification through the
notificationclick event inside the Service Worker, allowing
developers to focus an existing browser tab or open a new URL.
// sw.js
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const targetUrl = event.notification.data.url;
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => {
for (const client of windowClients) {
if (client.url === targetUrl && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(targetUrl);
}
})
);
});