1'use strict' 2 3// ################################## CONSTANTS ################################# 4 5const CACHE_NAME = 'xf-offline' 6const CACHE_ROUTE = 'index.php?sw/cache.json' 7const OFFLINE_ROUTE = 'index.php?sw/offline' 8 9const supportPreloading = false 10 11// ############################### EVENT LISTENERS ############################## 12 13self.addEventListener('install', event => 14{ 15 self.skipWaiting() 16 17 event.waitUntil(createCache()) 18}) 19 20self.addEventListener('activate', event => 21{ 22 self.clients.claim() 23 24 event.waitUntil( 25 new Promise(resolve => 26 { 27 if (self.registration.navigationPreload) 28 { 29 self.registration.navigationPreload[supportPreloading ? 'enable' : 'disable']() 30 } 31 32 resolve() 33 }), 34 ) 35}) 36 37self.addEventListener('message', event => 38{ 39 const clientId = event.source.id 40 const message = event.data 41 if (typeof message !== 'object' || message === null) 42 { 43 console.error('Invalid message:', message) 44 return 45 } 46 47 recieveMessage(clientId, message.type, message.payload) 48}) 49 50self.addEventListener('fetch', event => 51{ 52 const request = event.request 53 const accept = request.headers.get('accept') 54 55 if ( 56 request.mode !== 'navigate' || 57 request.method !== 'GET' || 58 (accept && !accept.includes('text/html')) 59 ) 60 { 61 return 62 } 63 64 // bypasses for: HTTP basic auth issues, file download issues (iOS), common ad blocker issues 65 if (request.url.match(/\/admin\.php|\/install\/|\/download($|&|\?)|[/?]attachments\/|google-ad|adsense/)) 66 { 67 if (supportPreloading && event.preloadResponse) 68 { 69 event.respondWith(event.preloadResponse) 70 } 71 72 return 73 } 74 75 const response = Promise.resolve(event.preloadResponse) 76 .then(r => r || fetch(request)) 77 78 event.respondWith( 79 response 80 .catch(error => caches.open(getCacheName()) 81 .then(cache => cache.match(OFFLINE_ROUTE))), 82 ) 83}) 84 85self.addEventListener('push', event => 86{ 87 if (!(self.Notification && self.Notification.permission === 'granted')) 88 { 89 return 90 } 91 92 let data 93 94 try 95 { 96 data = event.data.json() 97 } 98 catch (e) 99 { 100 console.warn('Received push notification but payload not in the expected format.', e) 101 console.warn('Received data:', event.data.text()) 102 return 103 } 104 105 if (!data || !data.title || !data.body) 106 { 107 console.warn('Received push notification but no payload data or required fields missing.', data) 108 return 109 } 110 111 const options = { 112 body: data.body, 113 dir: data.dir || 'ltr', 114 data: data, 115 } 116 if (data.badge) 117 { 118 options.badge = data.badge 119 } 120 if (data.icon) 121 { 122 options.icon = data.icon 123 } 124 125 const notificationPromise = self.registration.showNotification(data.title, options) 126 127 if ('setAppBadge' in self.navigator && 'clearAppBadge' in self.navigator) 128 { 129 let newCount = parseInt(String(data.total_unread).replace(/[,. ]/g, '')) 130 131 if (newCount) 132 { 133 self.navigator.setAppBadge(newCount) 134 } 135 else 136 { 137 self.navigator.clearAppBadge() 138 } 139 } 140 141 event.waitUntil(notificationPromise) 142}) 143 144self.addEventListener('notificationclick', event => 145{ 146 const notification = event.notification 147 notification.close() 148 149 const url = notification.data.url 150 if (!url) 151 { 152 return 153 } 154 155 const urlToOpen = new URL(url, self.location.origin).href 156 157 const promiseChain = clients 158 .matchAll({ 159 type: 'window', 160 includeUncontrolled: true, 161 }) 162 .then(windowClients => 163 { 164 let matchingClient = null 165 166 for (const windowClient of windowClients) 167 { 168 if (windowClient.url === urlToOpen && 'navigate' in windowClient) 169 { 170 matchingClient = windowClient 171 break 172 } 173 } 174 175 if (matchingClient) 176 { 177 return matchingClient.navigate(urlToOpen).then(client => 178 { 179 if (client) 180 { 181 client.focus() 182 } 183 else 184 { 185 return clients.openWindow(urlToOpen) 186 } 187 }) 188 } 189 else 190 { 191 return clients.openWindow(urlToOpen) 192 } 193 }) 194 195 event.waitUntil(promiseChain) 196}) 197 198// ################################## MESSAGING ################################# 199 200function sendMessage (clientId, type, payload) 201{ 202 if (typeof type !== 'string' || type === '') 203 { 204 console.error('Invalid message type:', type) 205 return 206 } 207 208 if (typeof payload === 'undefined') 209 { 210 payload = {} 211 } 212 else if (typeof payload !== 'object' || payload === null) 213 { 214 console.error('Invalid message payload:', payload) 215 return 216 } 217 218 clients.get(clientId) 219 .then(client => 220 { 221 client.postMessage({ 222 type: type, 223 payload: payload, 224 }) 225 }) 226 .catch(error => 227 {
228 console.error('An error occurred while sending a message:', error) 229 }) 230} 231 232const messageHandlers = {} 233 234function recieveMessage (clientId, type, payload) 235{ 236 if (typeof type !== 'string' || type === '') 237 { 238 console.error('Invalid message type:', type) 239 return 240 } 241 242 if (typeof payload !== 'object' || payload === null) 243 { 244 console.error('Invalid message payload:', payload) 245 return 246 } 247 248 const handler = messageHandlers[type] 249 if (typeof handler === 'undefined') 250 { 251 console.error('No handler available for message type:', type) 252 return 253 } 254 255 handler(clientId, payload) 256} 257 258// ################################### CACHING ################################## 259 260function getCacheName () 261{ 262 const match = self.location.pathname.match(/^\/(.*)\/[^/]+$/) 263 let cacheModifier 264 if (match && match[1].length) 265 { 266 cacheModifier = match[1].replace(/[^a-zA-Z0-9_-]/g, '') 267 } 268 else 269 { 270 cacheModifier = '' 271 } 272 273 return CACHE_NAME + (cacheModifier.length ? '-' : '') + cacheModifier 274} 275 276function createCache () 277{ 278 const cacheName = getCacheName() 279 280 return caches.delete(cacheName) 281 .then(() => caches.open(cacheName)) 282 .then(cache => fetch(CACHE_ROUTE) 283 .then(response => response.json()) 284 .then(response => 285 { 286 const key = response.key || null 287 const files = response.files || [] 288 files.push(OFFLINE_ROUTE) 289 290 return cache.addAll(files) 291 .then(() => key) 292 })) 293 .catch(error => 294 { 295 console.error('There was an error setting up the cache:', error) 296 }) 297} 298 299function updateCacheKey (clientId, key) 300{ 301 sendMessage(clientId, 'updateCacheKey', { 'key': key }) 302} 303 304messageHandlers.updateCache = (clientId, payload) => 305{ 306 createCache() 307}
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.