// src/sw.ts // Make sure this file is in src/ as configured in vite.config.ts (srcDir & filename) // Ensure globalThis.skipWaiting and clientsClaim are available in the SW scope /// declare const self: ServiceWorkerGlobalScope & typeof globalThis & { skipWaiting: () => Promise; __WB_MANIFEST: Array<{ url: string; revision: string | null }>; }; import { clientsClaim } from 'workbox-core'; import { precacheAndRoute, cleanupOutdatedCaches, createHandlerBoundToURL, } from 'workbox-precaching'; import { registerRoute, NavigationRoute } from 'workbox-routing'; import { CacheFirst, NetworkFirst } from 'workbox-strategies'; import { ExpirationPlugin } from 'workbox-expiration'; import { CacheableResponsePlugin } from 'workbox-cacheable-response'; import { BackgroundSyncPlugin } from 'workbox-background-sync'; import type { WorkboxPlugin } from 'workbox-core/types'; // Create a background sync plugin instance const bgSyncPlugin = new BackgroundSyncPlugin('offline-actions-queue', { maxRetentionTime: 24 * 60, // Retry for max of 24 Hours (specified in minutes) }); // Initialize service worker const initializeSW = async () => { try { await self.skipWaiting(); clientsClaim(); console.log('Service Worker initialized successfully'); } catch (error) { console.error('Error during service worker initialization:', error); } }; // Use with precache injection // vite-plugin-pwa will populate self.__WB_MANIFEST precacheAndRoute(self.__WB_MANIFEST); cleanupOutdatedCaches(); // Cache app shell and static assets with Cache First strategy registerRoute( ({ request }) => request.destination === 'style' || request.destination === 'script' || request.destination === 'image' || request.destination === 'font', new CacheFirst({ cacheName: 'static-assets', plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }) as WorkboxPlugin, new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days }) as WorkboxPlugin, ], }) ); // Cache API calls with Network First strategy and Background Sync for failed requests registerRoute( ({ url }) => url.pathname.startsWith('/api/'), // Make sure this matches your actual API path structure new NetworkFirst({ cacheName: 'api-cache', plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }) as WorkboxPlugin, new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 24 * 60 * 60, // 24 hours }) as WorkboxPlugin, bgSyncPlugin, // Add background sync plugin for failed requests ], }) ); // Non-SSR fallbacks to index.html // Production SSR fallbacks to offline.html (except for dev) // Using environment variables defined in vite.config.ts and injected by Vite declare const __PWA_FALLBACK_HTML__: string; declare const __PWA_SERVICE_WORKER_REGEX__: string; // Use fallback values if not defined const PWA_FALLBACK_HTML = typeof __PWA_FALLBACK_HTML__ !== 'undefined' ? __PWA_FALLBACK_HTML__ : '/index.html'; const PWA_SERVICE_WORKER_REGEX = typeof __PWA_SERVICE_WORKER_REGEX__ !== 'undefined' ? new RegExp(__PWA_SERVICE_WORKER_REGEX__) : /^(sw|workbox)-.*\.js$/; // Register navigation route for SPA fallback if (import.meta.env.MODE !== 'ssr' || import.meta.env.PROD) { // Cache the index.html explicitly for navigation fallback registerRoute( ({ request }) => request.mode === 'navigate', new NetworkFirst({ cacheName: 'navigation-cache', plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }) as WorkboxPlugin, ], }) ); // Register fallback for offline navigation registerRoute( new NavigationRoute(createHandlerBoundToURL(PWA_FALLBACK_HTML), { denylist: [PWA_SERVICE_WORKER_REGEX, /workbox-(.)*\.js$/], }), ); } // Initialize the service worker initializeSW();