import { assets as __assets__, shell as __shell__, routes as __routes__ } from '../__sapper__/service-worker.js' import { get, post } from './routes/_utils/ajax' const timestamp = process.env.SAPPER_TIMESTAMP const ASSETS = `assets_${timestamp}` const WEBPACK_ASSETS = `webpack_assets_${timestamp}` // `static` is an array of everything in the `static` directory const assets = __assets__ .map(file => file.startsWith('/') ? file : `/${file}`) .filter(filename => !filename.startsWith('/apple-icon')) .filter(filename => !filename.endsWith('.map')) .filter(filename => filename !== '/robots.txt') // `shell` is an array of all the files generated by webpack // also contains '/index.html' for some reason const webpackAssets = __shell__ .filter(filename => !filename.endsWith('.map')) // don't bother with sourcemaps // `routes` is an array of `{ pattern: RegExp }` objects that // match the pages in your src const routes = __routes__ self.addEventListener('install', event => { event.waitUntil((async () => { await Promise.all([ caches.open(WEBPACK_ASSETS).then(cache => cache.addAll(webpackAssets)), caches.open(ASSETS).then(cache => cache.addAll(assets)) ]) self.skipWaiting() })()) }) self.addEventListener('activate', event => { event.waitUntil((async () => { let keys = await caches.keys() // delete old asset/ondemand caches for (let key of keys) { if (key !== ASSETS && !key.startsWith('webpack_assets_')) { await caches.delete(key) } } // for webpack static, keep the two latest builds because we may need // them when the service worker has installed but the page has not // yet reloaded (e.g. when it gives the toast saying "please reload" // but then you don't refresh and instead load an async chunk) let webpackKeysToDelete = keys .filter(key => key.startsWith('webpack_assets_')) .sort((a, b) => { let aTimestamp = parseInt(a.substring(15), 10) let bTimestamp = parseInt(b.substring(15), 10) return bTimestamp < aTimestamp ? -1 : 1 }) .slice(2) for (let key of webpackKeysToDelete) { await caches.delete(key) } await self.clients.claim() })()) }) self.addEventListener('fetch', event => { const req = event.request const url = new URL(req.url) // don't try to handle e.g. data: URIs if (!url.protocol.startsWith('http')) { return } event.respondWith((async () => { let sameOrigin = url.origin === self.origin if (sameOrigin) { // always serve webpack-generated resources and // static from the cache if possible let response = await caches.match(req) if (response) { return response } // for routes, serve the /service-worker-index.html file from the most recent // static cache if (routes.find(route => route.pattern.test(url.pathname))) { let response = await caches.match('/service-worker-index.html') if (response) { return response } } } // for everything else, go network-only return fetch(req) })()) }) self.addEventListener('push', event => { event.waitUntil((async () => { const data = event.data.json() const { origin } = new URL(data.icon) try { const notification = await get(`${origin}/api/v1/notifications/${data.notification_id}`, { 'Authorization': `Bearer ${data.access_token}` }, { timeout: 2000 }) await showRichNotification(data, notification) } catch (e) { await showSimpleNotification(data) } })()) }) async function showSimpleNotification (data) { await self.registration.showNotification(data.title, { icon: data.icon, body: data.body }) } async function showRichNotification (data, notification) { const { origin } = new URL(data.icon) switch (notification.type) { case 'follow': { await self.registration.showNotification(data.title, { icon: data.icon, body: data.body, tag: notification.id, data: { url: `${self.location.origin}/accounts/${notification.account.id}` } }) break } case 'mention': { const actions = [{ action: 'favourite', title: 'Favourite' }] if ('reply' in NotificationEvent.prototype) { actions.splice(0, 0, { action: 'reply', type: 'text', title: 'Reply' }) } if (['public', 'unlisted'].includes(notification.status.visibility)) { actions.push({ action: 'reblog', title: 'Boost' }) } await self.registration.showNotification(data.title, { icon: data.icon, body: data.body, tag: notification.id, data: { instance: origin, status_id: notification.status.id, access_token: data.access_token, url: `${self.location.origin}/statuses/${notification.status.id}` }, actions }) break } case 'reblog': { await self.registration.showNotification(data.title, { icon: data.icon, body: data.body, tag: notification.id, data: { url: `${self.location.origin}/statuses/${notification.status.id}` } }) break } case 'favourite': { await self.registration.showNotification(data.title, { icon: data.icon, body: data.body, tag: notification.id, data: { url: `${self.location.origin}/statuses/${notification.status.id}` } }) break } } } const cloneNotification = notification => { const clone = { } // Object.assign() does not work with notifications for (let k in notification) { clone[k] = notification[k] } return clone } const updateNotificationWithoutAction = (notification, action) => { const newNotification = cloneNotification(notification) newNotification.actions = newNotification.actions.filter(item => item.action !== action) return self.registration.showNotification(newNotification.title, newNotification) } self.addEventListener('notificationclick', event => { event.waitUntil((async () => { switch (event.action) { case 'reply': { await post(`${event.notification.data.instance}/api/v1/statuses/`, { status: event.reply, in_reply_to_id: event.notification.data.status_id }, { 'Authorization': `Bearer ${event.notification.data.access_token}` }) await updateNotificationWithoutAction(event.notification, 'reply') break } case 'reblog': { await post(`${event.notification.data.instance}/api/v1/statuses/${event.notification.data.status_id}/reblog`, null, { 'Authorization': `Bearer ${event.notification.data.access_token}` }) await updateNotificationWithoutAction(event.notification, 'reblog') break } case 'favourite': { await post(`${event.notification.data.instance}/api/v1/statuses/${event.notification.data.status_id}/favourite`, null, { 'Authorization': `Bearer ${event.notification.data.access_token}` }) await updateNotificationWithoutAction(event.notification, 'favourite') break } default: { await self.clients.openWindow(event.notification.data.url) await event.notification.close() break } } })()) })