PWA push notification functionality uses Web Push API and Service Worker to send notifications when the user hasn't opened the app. Here's a complete implementation:
Core Components of Push Notifications
1. Push API
Used to receive messages pushed from the server
2. Notification API
Used to display notifications
3. Service Worker
Handles push events in the background
Steps to Implement Push Notifications
Step 1: Generate VAPID Keys
VAPID (Voluntary Application Server Identification) is used to verify the identity of the push server.
bash# Generate keys using web-push npm install -g web-push web-push generate-vapid-keys
Generated result:
shellPublic Key: <your-public-key> Private Key: <your-private-key>
Step 2: Request Push Subscription Permission
javascript// Request subscription in main thread async function subscribeUser() { // Check browser support if (!('serviceWorker' in navigator) || !('PushManager' in window)) { console.log('Push messaging is not supported'); return; } try { // Get Service Worker registration const registration = await navigator.serviceWorker.ready; // Request subscription const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array('<your-public-key>') }); console.log('User is subscribed:', subscription); // Send subscription info to server await saveSubscriptionToServer(subscription); } catch (error) { console.log('Failed to subscribe the user:', error); } } // Convert Base64 string to Uint8Array function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }
Step 3: Handle Push Events in Service Worker
javascript// sw.js self.addEventListener('push', event => { console.log('Push event received:', event); let data = { title: 'New Message', body: 'You have a new message', icon: '/icons/icon-192x192.png', badge: '/icons/badge-72x72.png' }; // Parse push data if (event.data) { try { data = { ...data, ...event.data.json() }; } catch (error) { data.body = event.data.text(); } } // Show notification const options = { body: data.body, icon: data.icon, badge: data.badge, vibrate: [200, 100, 200], data: { dateOfArrival: Date.now(), primaryKey: 1 }, actions: [ { action: 'explore', title: 'View Details', icon: '/icons/explore.png' }, { action: 'close', title: 'Close', icon: '/icons/close.png' } ] }; event.waitUntil( self.registration.showNotification(data.title, options) ); });
Step 4: Handle Notification Click Events
javascriptself.addEventListener('notificationclick', event => { console.log('Notification click received:', event); event.notification.close(); // Handle different actions if (event.action === 'explore') { // Open specific page event.waitUntil( clients.openWindow('/details') ); } else if (event.action === 'close') { // Close notification, do nothing else return; } else { // Default action: open app home page event.waitUntil( clients.matchAll({ type: 'window' }).then(clientList => { // If there's an open window, focus on it for (const client of clientList) { if (client.url === '/' && 'focus' in client) { return client.focus(); } } // Otherwise open new window if (clients.openWindow) { return clients.openWindow('/'); } }) ); } });
Step 5: Send Push Messages from Server
Using Node.js and web-push library:
javascriptconst webpush = require('web-push'); // Set VAPID keys const vapidKeys = { publicKey: '<your-public-key>', privateKey: '<your-private-key>' }; webpush.setVapidDetails( 'mailto:your-email@example.com', vapidKeys.publicKey, vapidKeys.privateKey ); // Send push notification async function sendPushNotification(subscription, data) { try { await webpush.sendNotification(subscription, JSON.stringify(data)); console.log('Push notification sent successfully'); } catch (error) { console.error('Error sending push notification:', error); // If subscription is invalid, remove from database if (error.statusCode === 410) { await removeSubscriptionFromDatabase(subscription); } } } // Example: Send notification to all subscribers async function sendToAllSubscribers(message) { const subscriptions = await getAllSubscribersFromDatabase(); const promises = subscriptions.map(subscription => { return sendPushNotification(subscription, { title: 'New Notification', body: message, icon: '/icons/icon-192x192.png' }); }); await Promise.allSettled(promises); }
Advanced Push Notification Features
1. Silent Push
javascriptself.addEventListener('push', event => { if (!event.data) return; const data = event.data.json(); // If silent push, don't show notification if (data.silent) { event.waitUntil( // Execute background tasks, like syncing data syncData() ); return; } // Otherwise show notification event.waitUntil( self.registration.showNotification(data.title, { body: data.body, icon: data.icon }) ); });
2. Scheduled Push
javascript// Use setTimeout for delayed push self.addEventListener('push', event => { const data = event.data.json(); if (data.delay) { setTimeout(() => { self.registration.showNotification(data.title, { body: data.body }); }, data.delay); } else { self.registration.showNotification(data.title, { body: data.body }); } });
3. Rich Media Notifications
javascriptself.addEventListener('push', event => { const data = event.data.json(); const options = { body: data.body, icon: data.icon, image: data.image, // Large image badge: data.badge, // Small icon vibrate: [200, 100, 200], sound: '/sounds/notification.mp3', tag: 'unique-tag', // Used to replace notifications with same tag renotify: true, // Alert user when notification is repeated requireInteraction: true, // Requires user interaction to close actions: [ { action: 'reply', title: 'Reply', icon: '/icons/reply.png', type: 'text', placeholder: 'Enter reply' } ], data: { // Custom data url: data.url } }; event.waitUntil( self.registration.showNotification(data.title, options) ); });
Best Practices for Push Notifications
- Timing of Permission Request: Request when user has clear need, not on page load
- Notification Content: Provide valuable information, avoid spam notifications
- Frequency Control: Don't send notifications too frequently
- Personalization: Customize notification content based on user preferences
- Actionable: Provide useful action buttons
- Error Handling: Properly handle subscription invalidation
- Testing: Test notification effects on different devices and browsers
Browser Compatibility
- Chrome, Edge, Firefox: Full support
- Safari: Partial support (iOS 16.4+)
- Requires user authorization
Debugging Push Notifications
Use Chrome DevTools:
- Open Application panel
- View Service Workers tab
- Click "Push" button to simulate push
- View notification display effect