乐闻世界logo
搜索文章和话题

How to implement push notification functionality in Service Worker?

3月7日 12:06

Service Worker Push Notification Implementation

Push notifications are one of the important features of Service Worker, allowing servers to send messages to users even when they haven't opened the website.

Push Notification Architecture

shell
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Server │────▶│ Push Service│────▶│ Browser │ (Web App) │ │(FCM/APNs) │ │(Service Worker) └─────────────┘ └─────────────┘ └─────────────┘

Implementation Steps

1. Request Notification Permission

javascript
// main.js async function requestNotificationPermission() { const permission = await Notification.requestPermission(); if (permission === 'granted') { console.log('Notification permission granted'); await subscribeUserToPush(); } else if (permission === 'denied') { console.log('Notification permission denied'); } else { console.log('Notification permission pending'); } } // Check current permission status console.log('Current permission:', Notification.permission); // 'default' | 'granted' | 'denied'

2. Subscribe to Push Service

javascript
// main.js async function subscribeUserToPush() { const registration = await navigator.serviceWorker.ready; // Get or create subscription let subscription = await registration.pushManager.getSubscription(); if (!subscription) { // Get VAPID public key from server const vapidPublicKey = await fetch('/api/vapid-public-key').then(r => r.text()); // Convert to Uint8Array const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey); // Create subscription subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, // Must be set to true applicationServerKey: convertedVapidKey }); console.log('Push subscription successful:', subscription); } // Send subscription info to server await fetch('/api/save-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription) }); return subscription; } // VAPID public key conversion function 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; }

3. Service Worker Receives Push

javascript
// sw.js // Listen for push events self.addEventListener('push', event => { console.log('Push message received:', event); let data = {}; if (event.data) { data = event.data.json(); } const options = { body: data.body || 'You have a new message', icon: '/icons/icon-192x192.png', badge: '/icons/badge-72x72.png', image: data.image || '/images/notification-banner.jpg', tag: data.tag || 'default', requireInteraction: false, // Keep notification until user interaction actions: [ { action: 'open', title: 'Open', icon: '/icons/open.png' }, { action: 'dismiss', title: 'Dismiss', icon: '/icons/close.png' } ], data: { url: data.url || '/', timestamp: Date.now() } }; event.waitUntil( self.registration.showNotification(data.title || 'New Notification', options) ); });

4. Handle Notification Click

javascript
// sw.js // Listen for notification click events self.addEventListener('notificationclick', event => { console.log('Notification clicked:', event); event.notification.close(); const { action, notification } = event; const data = notification.data || {}; if (action === 'dismiss') { // User clicked dismiss button return; } // Default behavior or click open button event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }) .then(clientList => { const urlToOpen = data.url || '/'; // Check if window is already open for (const client of clientList) { if (client.url === urlToOpen && 'focus' in client) { return client.focus(); } } // Open new window if not if (clients.openWindow) { return clients.openWindow(urlToOpen); } }) ); }); // Listen for notification close events self.addEventListener('notificationclose', event => { console.log('Notification closed:', event); // Can record statistics here });

Server-Side Push Sending

Node.js Example (using web-push library)

javascript
// server.js const 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 ); // Store subscription info (use database in production) let subscriptions = []; // Save subscription app.post('/api/save-subscription', (req, res) => { const subscription = req.body; subscriptions.push(subscription); res.json({ success: true }); }); // Send push app.post('/api/send-push', async (req, res) => { const { title, body, url } = req.body; const payload = JSON.stringify({ title, body, url, icon: '/icons/icon-192x192.png' }); const results = await Promise.allSettled( subscriptions.map(sub => webpush.sendNotification(sub, payload)) ); // Clean invalid subscriptions results.forEach((result, index) => { if (result.status === 'rejected' && result.reason.statusCode === 410) { subscriptions.splice(index, 1); } }); res.json({ success: true, sent: results.length }); });

Advanced Features

1. Scheduled Push (Periodic Background Sync)

javascript
// Main thread requests periodic sync navigator.serviceWorker.ready.then(registration => { registration.periodicSync.register('daily-news', { minInterval: 24 * 60 * 60 * 1000 // 24 hours }); }); // sw.js listens self.addEventListener('periodicsync', event => { if (event.tag === 'daily-news') { event.waitUntil(showDailyNewsNotification()); } });

2. Rich Media Notifications

javascript
const options = { body: 'Click to view details', icon: '/icon.png', image: '/banner.jpg', // Large image badge: '/badge.png', // Small icon vibrate: [200, 100, 200], // Vibration pattern sound: '/notification.mp3', // Sound (partial browser support) dir: 'ltr', // Text direction lang: 'en-US', timestamp: Date.now(), requireInteraction: true, actions: [ { action: 'reply', title: 'Reply', icon: '/reply.png' }, { action: 'archive', title: 'Archive', icon: '/archive.png' } ] };

3. Unsubscribe

javascript
// main.js async function unsubscribeFromPush() { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { await subscription.unsubscribe(); // Notify server to delete subscription await fetch('/api/remove-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ endpoint: subscription.endpoint }) }); console.log('Push subscription cancelled'); } }

Browser Compatibility

FeatureChromeFirefoxSafariEdge
Push API✅ (16.4+)
Notification
Actions
Badge

Best Practices

  1. Respect Users: Don't send notifications frequently, provide unsubscribe option
  2. Update Regularly: Clean invalid subscriptions periodically
  3. Personalized Content: Send relevant notifications based on user preferences
  4. Graceful Degradation: Provide alternative for browsers without push support
  5. HTTPS Required: Push functionality must run in HTTPS environment
标签:Service Worker