Skip to content
114 changes: 56 additions & 58 deletions public/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ const cacheOfflinePageAndResources = async service => {
console.log(`[SW v${version}] Cached offline page for ${service}`);

const html = await resp.text();
const scriptSrcs = [
...html.matchAll(/<script[^>]+src=["']([^"']+)["']/g),
].map(m => m[1]);
const linkHrefs = [...html.matchAll(/<link[^>]+href=["']([^"']+)["']/g)].map(
m => m[1],
const doc = new DOMParser().parseFromString(html, 'text/html');
const scriptSrcs = Array.from(doc.querySelectorAll('script[src]')).map(el =>
el.getAttribute('src'),
);
const linkHrefs = Array.from(doc.querySelectorAll('link[href]')).map(el =>
el.getAttribute('href'),
);

const resources = [...scriptSrcs, ...linkHrefs]
.filter(Boolean)
.filter(url => url.startsWith('/') || url.startsWith(self.location.origin))
.map(url => new URL(url, self.location.origin).href);

Expand Down Expand Up @@ -79,38 +82,7 @@ const WEBP_IMAGE =
// -------------Install event -------
self.addEventListener('install', event => {
console.log(`[SW v${version}] Installing...`);

event.waitUntil(
(async () => {
const cache = await caches.open(cacheName);
const clients = await self.clients.matchAll({ type: 'window' });

// Get unique services from PWA clients only
const pwaServices = [
...new Set(
clients
.filter(client => pwaClients.get(client.id))
.map(client => getServiceFromUrl(client.url))
.filter(Boolean),
),
];

if (pwaServices.length > 0) {
console.log(
`[SW v${version}] Caching offline pages for PWA:`,
pwaServices,
);
}

// Cache offline pages for PWA services only
await Promise.allSettled(
pwaServices.map(async service => {
return cacheOfflinePageAndResources(service);
}),
);
self.skipWaiting();
})(),
);
self.skipWaiting();
});

// -------Activate Handler-------------
Expand All @@ -134,16 +106,18 @@ self.addEventListener('message', async event => {
if (event.data?.type === 'PWA_STATUS') {
const clientId = event.source.id;
const { isPWA } = event.data;
pwaClients.set(clientId, isPWA);
// pwaClients.set(clientId, isPWA);

if (isPWA) {
const cache = await caches.open(cacheName);
await cache.put('pwa_installed', new Response('true'));
// const cache = await caches.open(cacheName);
// await cache.put('pwa_installed', new Response('true'));
pwaClients.set(clientId, isPWA);

const service = getServiceFromUrl(event.source.url);
await cacheOfflinePageAndResources(service);
} else {
const cache = await caches.open(cacheName);
await cache.delete('pwa_installed');
// const cache = await caches.open(cacheName);
// await cache.delete('pwa_installed');
}
}
});
Expand Down Expand Up @@ -188,12 +162,43 @@ const fetchEventHandler = async event => {
return response;
})(),
);
} else if (event.request.url.includes('/_next/static/')) {
// Network-first for Next.js chunks (dev mode compatibility)
event.respondWith(
(async () => {
try {
const networkResp = await fetch(event.request);
const cache = await caches.open(cacheName);
cache.put(event.request, networkResp.clone());
return networkResp;
} catch (err) {
const cache = await caches.open(cacheName);
const cachedResp = await cache.match(event.request);
if (cachedResp) return cachedResp;
throw err;
}
})(),
);
} else if (event.request.mode === 'navigate') {
const { url } = event.request;
console.log(`[SW FETCH] Navigation: ${url}`);

event.respondWith(
(async () => {
const client = await self.clients.get(event.clientId);
const isPWA = client && pwaClients.get(client.id);
const cache = await caches.open(cacheName);

// const pwaMarkerExists = await cache.match('pwa_installed');
// if (!isPWA && pwaMarkerExists && isPWA !== undefined) {
// await cache.delete('pwa_installed');
// }
console.log('[SW FETCH] Navigation', {
url: event.request.url,
clientId: event.clientId,
isPWA,
client,
event,
});
try {
// Use preload if available
const preloadResp = await event.preloadResponse;
Expand All @@ -204,8 +209,8 @@ const fetchEventHandler = async event => {
// Cache offline page if in PWA mode
if (networkResp && networkResp.ok && event.clientId) {
console.log('[SW] Caching offline page if PWA if network is ok');
const client = await self.clients.get(event.clientId);
const isPWA = client && pwaClients.get(client.id);
// const client = await self.clients.get(event.clientId);
// const isPWA = client && pwaClients.get(client.id);
if (isPWA) {
const service = getServiceFromUrl(url);
cacheOfflinePageAndResources(service).catch(err =>
Expand All @@ -218,12 +223,11 @@ const fetchEventHandler = async event => {
} catch (err) {
console.log('[SW] Navigation failed:', url, err);

const cache = await caches.open(cacheName);
const pwaMarker = await cache.match('pwa_installed');
console.log('[SW] PWA Marker:', pwaMarker);
// const pwaMarker = await cache.match('pwa_installed');
// console.log('[SW] PWA Marker:', pwaMarker);

// Only show offline page for installed PWA
if (pwaMarker) {
if (isPWA) {
const service = getServiceFromUrl(url);
const offlineUrl = new URL(
getOfflinePageUrl(service),
Expand All @@ -232,18 +236,12 @@ const fetchEventHandler = async event => {

const cachedOffline = await cache.match(offlineUrl);
if (cachedOffline) {
console.log('[SW] Serving cached offline page');
return cachedOffline;
}
}

// Canonical site offline fallback
return new Response(
'You are offline. Please check your network and reload the page',
{
status: 503,
headers: { 'Content-Type': 'text/plain' },
},
);
// fallback to browser default behavior
return Response.error();
}
})(),
);
Expand Down