90 lines
2.2 KiB
JavaScript
90 lines
2.2 KiB
JavaScript
|
|
// Falah PWA Service Worker
|
||
|
|
const CACHE = "falah-cache-v1";
|
||
|
|
const IMMUTABLE_CACHE = "falah-immutable-v1";
|
||
|
|
|
||
|
|
// Assets to pre-cache on install
|
||
|
|
const PRECACHE_ASSETS = [
|
||
|
|
"/mobile/manifest.json",
|
||
|
|
"/mobile/icons/icon-192x192.png",
|
||
|
|
"/mobile/icons/icon-512x512.png",
|
||
|
|
"/mobile/apple-touch-icon.png",
|
||
|
|
"/mobile/offline"
|
||
|
|
];
|
||
|
|
|
||
|
|
// Install: pre-cache core assets
|
||
|
|
self.addEventListener("install", (event) => {
|
||
|
|
event.waitUntil(
|
||
|
|
caches.open(IMMUTABLE_CACHE).then((cache) => cache.addAll(PRECACHE_ASSETS))
|
||
|
|
);
|
||
|
|
self.skipWaiting();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Activate: clean old caches
|
||
|
|
self.addEventListener("activate", (event) => {
|
||
|
|
event.waitUntil(
|
||
|
|
caches.keys().then((keys) =>
|
||
|
|
Promise.all(
|
||
|
|
keys
|
||
|
|
.filter((k) => k !== CACHE && k !== IMMUTABLE_CACHE)
|
||
|
|
.map((k) => caches.delete(k))
|
||
|
|
)
|
||
|
|
)
|
||
|
|
);
|
||
|
|
self.clients.claim();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Fetch: network-first for pages, cache-first for static assets
|
||
|
|
self.addEventListener("fetch", (event) => {
|
||
|
|
const { request } = event;
|
||
|
|
const url = new URL(request.url);
|
||
|
|
|
||
|
|
// Only handle same-origin /mobile requests
|
||
|
|
if (!url.pathname.startsWith("/mobile")) return;
|
||
|
|
|
||
|
|
// API calls — never cache
|
||
|
|
if (url.pathname.startsWith("/mobile/api/")) return;
|
||
|
|
|
||
|
|
// Static assets (icons, fonts, images) — cache-first
|
||
|
|
if (
|
||
|
|
url.pathname.match(/\.(png|jpg|jpeg|gif|svg|ico|webp|woff2?|css)$/)
|
||
|
|
) {
|
||
|
|
event.respondWith(cacheFirst(request));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Navigation & pages — network-first with fallback
|
||
|
|
if (request.mode === "navigate") {
|
||
|
|
event.respondWith(networkFirst(request));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Everything else — network-only
|
||
|
|
event.respondWith(fetch(request).catch(() => caches.match(request)));
|
||
|
|
});
|
||
|
|
|
||
|
|
async function networkFirst(request) {
|
||
|
|
try {
|
||
|
|
const response = await fetch(request);
|
||
|
|
if (response.ok) {
|
||
|
|
const cache = await caches.open(CACHE);
|
||
|
|
cache.put(request, response.clone());
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
} catch {
|
||
|
|
const cached = await caches.match(request);
|
||
|
|
if (cached) return cached;
|
||
|
|
// Offline fallback page
|
||
|
|
return caches.match("/mobile/offline");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function cacheFirst(request) {
|
||
|
|
const cached = await caches.match(request);
|
||
|
|
if (cached) return cached;
|
||
|
|
try {
|
||
|
|
return await fetch(request);
|
||
|
|
} catch {
|
||
|
|
return new Response("", { status: 408 });
|
||
|
|
}
|
||
|
|
}
|