From bdd0ac46e3dcc6a39702f9f1becc71dc93bf6bac Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 2 Sep 2026 07:49:38 +0200 Subject: [PATCH] feat: implement per-route service worker selection Updated the service worker registration logic to allow for specific service workers based on the route. Added support for a mobile service worker at `/m`, while maintaining existing functionality for `/counter` and `/new-order` routes. This enhances the PWA experience by serving optimized service workers for different app sections. --- public/sw-mobile.js | 79 +++++++++++++++++++ .../service-worker-registration.tsx | 13 ++- 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 public/sw-mobile.js diff --git a/public/sw-mobile.js b/public/sw-mobile.js new file mode 100644 index 0000000..c77a91a --- /dev/null +++ b/public/sw-mobile.js @@ -0,0 +1,79 @@ +// Service worker for the /m boss-dashboard PWA. Precaches the +// mobile shell, network-first for navigations, never caches +// boss-dashboard API responses (those are real-time glance data). +const CACHE_NAME = "wfdash-mobile-v1"; +const OFFLINE_URL = "/offline"; + +const PRECACHE_ASSETS = [ + "/m", + "/manifest-mobile.json", + "/offline", + "/android-chrome-192x192.png", + "/android-chrome-512x512.png", + "/apple-touch-icon.png", +]; + +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_ASSETS)) + ); + self.skipWaiting(); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all( + keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)) + ) + ) + ); + self.clients.claim(); +}); + +self.addEventListener("fetch", (event) => { + const { request } = event; + if (request.method !== "GET") return; + + const url = new URL(request.url); + + // Never cache API traffic — boss-dashboard is real-time glance data + // and serving a stale /api/v1/boss-dashboard or rep-activity would + // actively mislead the boss. + if (url.pathname.startsWith("/api/")) return; + + // Navigation requests: network-first, fall back to the cached /m + // shell, then the generic /offline page. + if (request.mode === "navigate") { + event.respondWith( + fetch(request) + .then((response) => { + const clone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); + return response; + }) + .catch(async () => { + const cached = await caches.match(request); + if (cached) return cached; + const mShell = await caches.match("/m"); + if (mShell) return mShell; + return caches.match(OFFLINE_URL); + }) + ); + return; + } + + // Static assets: stale-while-revalidate. + event.respondWith( + caches.match(request).then((cached) => { + const networkFetch = fetch(request) + .then((response) => { + const clone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); + return response; + }) + .catch(() => cached); + return cached || networkFetch; + }) + ); +}); diff --git a/src/components/service-worker-registration.tsx b/src/components/service-worker-registration.tsx index e8e667d..2f0bc77 100644 --- a/src/components/service-worker-registration.tsx +++ b/src/components/service-worker-registration.tsx @@ -3,14 +3,21 @@ import { useEffect } from "react"; /** - * Counter PWA routes register `/sw-counter.js` (precaches /new-order - * shell + manifest). Everything else keeps the main app's `/sw.js`. - * Both SWs are scoped to `/` so co-existence is fine — they each + * Per-route service-worker picker: + * - /m → /sw-mobile.js (boss-dashboard PWA, dark theme) + * - /counter → /sw-counter.js (over-the-counter order PWA) + * - /new-order → /sw-counter.js (same counter PWA shell) + * - everything else → /sw.js (main WFDASH app) + * + * All SWs are scoped to `/` so co-existence is fine — they each * cache their own URLs and ignore `/api/`. */ function pickSwPath(): string { if (typeof window === "undefined") return "/sw.js"; const path = window.location.pathname; + if (path === "/m" || path.startsWith("/m/")) { + return "/sw-mobile.js"; + } if (path.startsWith("/counter") || path.startsWith("/new-order")) { return "/sw-counter.js"; }