1// Service Worker for Web Push Notifications 2 3const APP_URL = self.location.origin 4 5// Handle push notifications 6self.addEventListener('push', (event) => { 7 const handlePush = async () => { 8 if (!event.data) { 9 console.warn('Push event received but no data') 10 return 11 } 12 13 let data 14 try { 15 data = event.data.json() 16 } catch (e) { 17 console.error('Failed to parse push data:', e) 18 return 19 } 20 21 const title = data.title || 'Postpone' 22 const options = { 23 body: data.body || 'You have a new notification', 24 icon: data.icon || '/img/logo/logomark.svg', 25 badge: '/img/logo/logomark.svg', 26 tag: data.tag || 'postpone-notification', 27 data: data.data || {}, 28 requireInteraction: true 29 } 30 31 await self.registration.showNotification(title, options) 32 } 33 34 event.waitUntil(handlePush()) 35}) 36 37// Handle notification clicks 38self.addEventListener('notificationclick', (event) => { 39 event.notification.close() 40 41 const notificationData = event.notification.data || {} 42 43 // Use URL from payload if provided, otherwise build from data 44 let targetUrl = notificationData.url || null 45 46 if (!targetUrl) { 47 targetUrl = APP_URL + '/overdue' 48 49 // Build URL based on notification type 50 if (notificationData.type === 'post.manual_publish') { 51 const platform = (notificationData.platform || '').toLowerCase() 52 const submissionId = notificationData.submissionId 53 const postId = notificationData.postId 54 55 if (platform && (submissionId || postId)) { 56 targetUrl = `${APP_URL}/overdue?platform=${platform}` 57 if (submissionId) { 58 targetUrl += `&submissionId=${submissionId}` 59 } 60 if (postId) { 61 targetUrl += `&postId=${postId}` 62 } 63 } 64 } 65 } else if (!targetUrl.startsWith('http')) { 66 // Handle relative URLs 67 targetUrl = APP_URL + targetUrl 68 } 69 70 const handleClick = async () => { 71 // Try to focus an existing window first 72 const windowClients = await clients.matchAll({ 73 type: 'window', 74 includeUncontrolled: true 75 }) 76 77 for (const client of windowClients) { 78 if (client.url.startsWith(APP_URL) && 'focus' in client) { 79 await client.focus() 80 if ('navigate' in client) { 81 await client.navigate(targetUrl) 82 } 83 return 84 } 85 } 86 87 // No existing window found, open a new one 88 await clients.openWindow(targetUrl) 89 } 90 91 event.waitUntil(handleClick()) 92}) 93 94// Handle push subscription change (e.g., when browser refreshes the subscription) 95self.addEventListener('pushsubscriptionchange', (event) => { 96 event.waitUntil( 97 (async () => { 98 // Notify all open clients about the subscription change 99 const allClients = await clients.matchAll({ type: 'window' }) 100 for (const client of allClients) { 101 client.postMessage({ 102 type: 'PUSH_SUBSCRIPTION_CHANGED', 103 oldEndpoint: event.oldSubscription 104 ? event.oldSubscription.endpoint 105 : null 106 }) 107 } 108 })() 109 ) 110}) 111 112// Service worker installation 113self.addEventListener('install', (event) => { 114 self.skipWaiting() 115}) 116 117// Service worker activation 118self.addEventListener('activate', (event) => { 119 event.waitUntil(clients.claim()) 120})
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.