/* eslint-env serviceworker */ /* global workbox */ /* eslint-disable */ /* ... because old-browser-compatible files in this directory are combined into a single script during `npm run build` */ importScripts( "https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js", ); function logConsoleAndDb(message, arg1, arg2) { // in chrome://serviceworker-internals note that the arg1 and arg2 here will show as "[object Object]" in that page but will show as expandable objects in the console console.log(`${new Date().toISOString()} ${message}`, arg1, arg2); // appendDailyLog is injected at build time by the vue.config.js configureWebpack apply plugin // eslint-disable-next-line no-undef if (appendDailyLog) { let fullMessage = `${new Date().toISOString()} ${message}`; if (arg1) { fullMessage += `\n${JSON.stringify(arg1)}`; } if (arg2) { fullMessage += `\n${JSON.stringify(arg2)}`; } // appendDailyLog is injected from safari-notifications.js at build time by the vue.config.js configureWebpack apply plugin // eslint-disable-next-line no-undef appendDailyLog(fullMessage); } else { // sometimes we get the error: "Uncaught TypeError: appendDailyLog is not a function" console.log( "Not logging to DB (often because appendDailyLog doesn't exist).", ); } } self.addEventListener("install", async (/* event */) => { logConsoleAndDb("Service worker finished installation."); }); self.addEventListener("activate", (event) => { logConsoleAndDb("Service worker is activating...", event); // see https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim // and https://web.dev/articles/service-worker-lifecycle#clientsclaim event.waitUntil(clients.claim()); logConsoleAndDb("Service worker is activated."); }); self.addEventListener("push", function (event) { let text = null; if (event.data) { text = event.data.text(); } logConsoleAndDb("Service worker received a push event.", text, event); event.waitUntil( (async () => { try { let payload; if (text) { try { payload = JSON.parse(text); } catch (e) { // don't use payload since it is not JSON } } // This is a special value that tells the service worker to trigger its daily check. // See https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/c1ed026662e754348a5f91542680bd4f57e5b81e/app.py#L217 const DAILY_UPDATE_TITLE = "DAILY_CHECK"; // This is a special value that tells the service worker to send a direct notification to the device, skipping filters. // This is shared with the notification-test code and should be a constant. Look for the same name in HelpNotificationsView.vue // Make sure it is something other than the DAILY_UPDATE_TITLE. const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION"; let title; let message = "Got some empty message."; if (payload && payload.title == DIRECT_PUSH_TITLE) { // skip any search logic and show the message directly title = "Direct Notification"; message = payload.message || "No details were provided."; } else { // any other title will run through regular filtering logic if (payload && payload.title === DAILY_UPDATE_TITLE) { title = "Daily Update"; } else { title = payload.title || "Update"; } // getNotificationCount is injected from safari-notifications.js at build time by the sw_combine.js script // eslint-disable-next-line no-undef message = await getNotificationCount(); } if (message) { const options = { body: message, icon: payload ? payload.icon : "icon.png", badge: payload ? payload.badge : "badge.png", }; await self.registration.showNotification(title, options); logConsoleAndDb("Notified user:", options); } else { logConsoleAndDb("No notification message."); } } catch (error) { logConsoleAndDb("Error with push event", event, error); } })(), ); }); self.addEventListener("message", (event) => { logConsoleAndDb("Service worker got a message...", event); if (event.data && event.data.type === "SEND_LOCAL_DATA") { self.secret = event.data.data; event.ports[0].postMessage({ success: true }); } logConsoleAndDb("Service worker posted a message."); }); self.addEventListener("notificationclick", (event) => { logConsoleAndDb("Notification got clicked.", event); event.notification.close(); // from https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event // ... though I don't see any benefit over just "clients.openWindow" event.waitUntil( clients .matchAll({ type: "window", }) .then((clientList) => { for (const client of clientList) { if (client.url === "/" && "focus" in client) return client.focus(); } if (clients.openWindow) return clients.openWindow("/"); }), ); }); // This is invoked when the user chooses this as a share_target, mapped to share-target in the manifest. self.addEventListener("fetch", (event) => { logConsoleAndDb("Service worker got fetch event.", event); // Bypass any regular requests not related to Web Share Target // and also requests that are not exactly to the timesafari.app // (because Chrome will send subdomain requests like image-api.timesafari.app through this service worker). if ( event.request.method !== "POST" || (event.request.url.hostname !== "timesafari.app" && event.request.url.hostname !== "test.timesafari.app" && event.request.url.hostname !== "localhost") ) { event.respondWith(fetch(event.request)); return; } // Requests related to Web Share share-target Target. event.respondWith( (async () => { const formData = await event.request.formData(); const photo = formData.get("photo"); // savePhoto is injected from safari-notifications.js at build time by the sw_combine.js script // eslint-disable-next-line no-undef await savePhoto(photo); return Response.redirect("/shared-photo", 303); })(), ); }); self.addEventListener("error", (event) => { logConsoleAndDb("Service worker error", event); console.error("Full Error:", event); console.error("Message:", event.message); console.error("File:", event.filename); console.error("Line:", event.lineno); console.error("Column:", event.colno); console.error("Error Object:", event.error); }); workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);