const STATIC_CACHE_NAME = "vitrown-static-v2"; const DYNAMIC_CACHE_NAME = "vitrown-dynamic-v2"; const STATIC_ASSETS = [ "/", "/resources/manifest.webmanifest", "/favicon.ico", "/icon-192x192.png", "/icon-512x512.png", "/apple-touch-icon.png", ]; // Install event - cache static assets self.addEventListener("install", (event) => { event.waitUntil( caches.open(STATIC_CACHE_NAME).then((cache) => { return cache.addAll(STATIC_ASSETS); }) ); self.skipWaiting(); }); // Activate event - clean up old caches self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((cacheName) => { return ( cacheName.startsWith("vitrown-") && cacheName !== STATIC_CACHE_NAME && cacheName !== DYNAMIC_CACHE_NAME ); }) .map((cacheName) => caches.delete(cacheName)) ); }) ); self.clients.claim(); }); // Fetch event - network first, cache fallback strategy self.addEventListener("fetch", (event) => { const { request } = event; const url = new URL(request.url); // Skip non-GET requests if (request.method !== "GET") { return; } // Skip non-HTTP(S) requests (chrome-extension://, etc.) if (!url.protocol.startsWith("http")) { return; } // Skip WebSocket, SSE, and API requests if ( url.pathname.startsWith("/api/") || url.protocol === "ws:" || url.protocol === "wss:" || request.headers.get("accept")?.includes("text/event-stream") ) { return; } // For navigation requests, try network first if (request.mode === "navigate") { event.respondWith( fetch(request) .then((response) => { const responseToCache = response.clone(); caches.open(DYNAMIC_CACHE_NAME).then((cache) => { cache.put(request, responseToCache); }); return response; }) .catch(() => { return caches.match(request); }) ); return; } // For static assets (CSS, JS, images), use cache first if ( request.destination === "style" || request.destination === "script" || request.destination === "image" || request.destination === "font" ) { event.respondWith( caches.match(request).then((cachedResponse) => { if (cachedResponse) { // Update cache in background fetch(request).then((response) => { if (response.status === 200) { const responseToCache = response.clone(); caches.open(STATIC_CACHE_NAME).then((cache) => { cache.put(request, responseToCache); }); } }); return cachedResponse; } return fetch(request).then((response) => { if (response.status === 200) { const responseToCache = response.clone(); caches.open(STATIC_CACHE_NAME).then((cache) => { cache.put(request, responseToCache); }); } return response; }); }) ); return; } // For other requests, network first event.respondWith( fetch(request) .then((response) => { if (response.status === 200) { const responseToCache = response.clone(); caches.open(DYNAMIC_CACHE_NAME).then((cache) => { cache.put(request, responseToCache); }); } return response; }) .catch(() => { return caches.match(request); }) ); }); // Listen for messages from the client self.addEventListener("message", (event) => { if (event.data && event.data.type === "SKIP_WAITING") { self.skipWaiting(); } }); // ---- Web Push ---- // Show a notification when a push arrives. Payload is JSON: // { title, body, url, tag, notification_id } self.addEventListener("push", (event) => { let data = {}; try { data = event.data ? event.data.json() : {}; } catch (e) { data = { title: "ویترون", body: event.data ? event.data.text() : "" }; } const title = data.title || "ویترون"; const options = { body: data.body || "", icon: "/icon-192x192.png", badge: "/icon-96x96.png", dir: "rtl", lang: "fa", tag: data.tag || "vitrown", renotify: true, data: { url: data.url || "/", notificationId: data.notification_id }, }; event.waitUntil(self.registration.showNotification(title, options)); }); // Focus an existing tab (or open a new one) on the target URL when clicked. self.addEventListener("notificationclick", (event) => { event.notification.close(); const targetUrl = (event.notification.data && event.notification.data.url) || "/"; event.waitUntil( clients .matchAll({ type: "window", includeUncontrolled: true }) .then((clientList) => { for (const client of clientList) { // If a tab is already open, navigate it and focus. if ("focus" in client) { client.focus(); if ("navigate" in client) { client.navigate(targetUrl).catch(() => {}); } return; } } if (clients.openWindow) { return clients.openWindow(targetUrl); } }) ); });