Created
February 14, 2024 14:46
-
-
Save deshario/578c9f7a07ef671cd684c323786ce854 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-env serviceworker */ | |
/* eslint-disable no-console */ | |
import localForage from 'localforage' | |
import { matchPrecache, precacheAndRoute } from 'workbox-precaching' | |
import { registerRoute, setCatchHandler } from 'workbox-routing' | |
import { CacheFirst, NetworkFirst } from 'workbox-strategies' | |
self.addEventListener('install', (event) => { | |
self.skipWaiting() | |
console.log('Service worker installed') | |
}) | |
self.addEventListener('activate', (event) => { | |
self.clients.claim() | |
console.log('Service worker activated') | |
}) | |
// Keep last used tab | |
let lastTabId | |
function postMessage(tab, message) { | |
if (!tab) { | |
return | |
} | |
lastTabId = tab.id | |
tab.postMessage(message) | |
} | |
function getUserNameFromBody(body) { | |
return (body || '').split(':')[0] | |
} | |
self.addEventListener('push', (event) => { | |
// This should not happen at all, our server always send notification with data | |
// So `event.data` should always exists | |
// but just in case, we provide nice notification that something is updated | |
// Chrome and some browsers not allowed us to ignore notification | |
// They will display 'Site have been updated' automatically if don't show notification | |
// which will confused user | |
// So in the worst-case, we display this | |
if (!event.data) { | |
event.waitUntil( | |
self.registration.showNotification('Taskworld', { | |
body: 'There are some updates in your workspace.', | |
tag: 'unknown-group', | |
}) | |
) | |
return | |
} | |
const data = event.data.json() | |
const receivedDate = new Date().toISOString() | |
event.waitUntil( | |
tryGetActiveTab().then((tab) => { | |
localForage.getItem('doNotDisturbEnabled').then((doNotDisturbEnabled) => { | |
if (doNotDisturbEnabled) { | |
return | |
} | |
// This will play the notification sound in WebNotificationContainer. | |
// See https://github.com/taskworld/tw-frontend/blob/8fb350a3e9cf5ac97121238052a6022ab7a9d673/client/src/react/components/v2/notifications/WebNotificationContainer.react.tsx#L186-L188 | |
// | |
// We need notification sound both when the tab is active and inactive. | |
postMessage(tab, { | |
topic: 'noti', | |
send_date: data.sendDate, | |
received_date: receivedDate, | |
// See https://github.com/taskworld/tw-frontend/pull/7477 | |
web_notification_sound: data.web_notification_sound, | |
}) | |
const userName = getUserNameFromBody(data.body) | |
self.registration.showNotification(`${userName} Taskworld`, { | |
body: data.body, | |
icon: data.icon, | |
tag: data.key || data.webpath, | |
data, | |
receivedDate, | |
}) | |
}) | |
}) | |
) | |
}) | |
function tryGetActiveTab() { | |
return clients | |
.matchAll({ | |
type: 'window', | |
includeUncontrolled: true, | |
}) | |
.then((tabs) => { | |
if (!tabs || tabs.length === 0) { | |
return | |
} | |
const focused = tabs.find((tab) => tab.focused === true) | |
if (focused) { | |
return focused | |
} | |
const lastTab = tabs.find((tab) => tab.id === lastTabId) | |
return lastTab || tabs[0] | |
}) | |
} | |
self.addEventListener('notificationclick', (event) => { | |
event.notification.close() | |
if (!event || !event.notification || !event.notification.data) { | |
console.error('invalid event notification data set', event) | |
return | |
} | |
event.waitUntil( | |
tryGetActiveTab().then((tab) => { | |
if (!tab && self.clients && self.clients.openWindow) { | |
return self.clients.openWindow(event.notification.data.webpath) | |
} | |
postMessage(tab, { | |
topic: 'open', | |
payload: { | |
webpath: event.notification.data.webpath, | |
}, | |
}) | |
return tab.focus() | |
}) | |
) | |
}) | |
// These workbox entries in console log are too much for me. And I do not think | |
// we are going to debug workbox everyday, so I will disable this by default. | |
// See https://developers.google.com/web/tools/workbox/guides/configure-workbox#disable_logging | |
// Feel free to comment out the line below when you want to debug workbox. | |
self.__WB_DISABLE_DEV_LOGS = true | |
// All these caching stuffs done by workbox are for frontend assets only. To | |
// allow PWA to goes offline without crashing the entire app. Backend API calls | |
// and external calls to 3rd-party services will still failed when offline. And | |
// it should not affect Google Lighthouse score (hopefully). | |
registerRoute(({ url }) => { | |
const urlString = url.toString() | |
if (urlString.match(/__dev\.\w+$/)) { | |
return false | |
} | |
return ( | |
urlString.startsWith('https://d30795irbdecem.cloudfront.net/assets/') || | |
// PWA icons | |
urlString.startsWith('https://taskworld.github.io/icons/') || | |
urlString.startsWith(`${location.origin}/assets/`) || | |
// Static files e.g. manifest.json | |
urlString.startsWith(`${location.origin}/static__`) || | |
urlString.endsWith(`${location.origin}/offline.html`) || | |
urlString.endsWith('/unsupported-browsers-page.html') || | |
urlString.endsWith('/favicon.ico') | |
) | |
}, new CacheFirst()) | |
registerRoute(({ url }) => { | |
const urlString = url.toString() | |
return ( | |
urlString.startsWith(`${location.origin}/`) && | |
// API resources doesn't need caching. | |
// Also, workbox requests will fail with websockets. | |
!urlString.startsWith(`${location.origin}/sockjs-node/`) && | |
!urlString.startsWith(`${location.origin}/api/`) | |
) | |
}, new NetworkFirst()) | |
precacheAndRoute(self.__WB_MANIFEST) | |
setCatchHandler(async ({ event }) => { | |
if (event.request.destination === 'document') { | |
return matchPrecache('/offline.html') | |
} | |
return Response.error() | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment