/* SalGram service worker: оболочка PWA + Web Push уведомления. Версию кэша поднимать при изменении статики, чтобы клиенты обновились. */ const CACHE = "salgram-v3"; const SHELL = ["/", "/index.html", "/app.js", "/style.css", "/manifest.webmanifest", "/icon-192.png", "/icon-512.png"]; self.addEventListener("install", e => { self.skipWaiting(); e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL).catch(() => {}))); }); self.addEventListener("activate", e => { e.waitUntil((async () => { const keys = await caches.keys(); await Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))); await self.clients.claim(); })()); }); /* Оболочка кэшируется по стратегии «сеть в приоритете» (network-first): пока есть связь, всегда отдаём свежий код и обновляем кэш — так правки в app.js/style.css доходят до клиента сразу, без ручного поднятия версии. Без сети — отдаём из кэша. API, медиа и WebSocket всегда идут напрямую в сеть. */ self.addEventListener("fetch", e => { const req = e.request; const url = new URL(req.url); if (req.method !== "GET" || url.origin !== location.origin) return; if (url.pathname.startsWith("/api/") || url.pathname === "/ws") return; if (req.mode === "navigate") { e.respondWith(networkFirst(req, "/index.html")); return; } if (SHELL.includes(url.pathname)) e.respondWith(networkFirst(req, url.pathname)); }); /* Берём из сети, по пути кладём свежую копию в кэш; офлайн — отдаём из кэша. */ async function networkFirst(req, cacheKey) { try { const res = await fetch(req); if (res && res.ok) { const copy = res.clone(); caches.open(CACHE).then(c => c.put(cacheKey, copy)).catch(() => {}); } return res; } catch { return (await caches.match(cacheKey)) || (await caches.match(req)) || Response.error(); } } /* Web Push: уведомление о новом сообщении, когда приложение закрыто. */ self.addEventListener("push", e => { let d = {}; try { d = e.data ? e.data.json() : {}; } catch { /* ignore */ } e.waitUntil(self.registration.showNotification(d.title || "SalGram", { body: d.body || "Новое сообщение", icon: "/icon-192.png", badge: "/icon-192.png", tag: d.peer ? "dm-" + d.peer : d.group ? "grp-" + d.group : "salgram", renotify: true, data: d, })); }); /* Клик по уведомлению: фокус на открытое окно или открытие приложения. */ self.addEventListener("notificationclick", e => { e.notification.close(); const data = e.notification.data || {}; e.waitUntil((async () => { const all = await self.clients.matchAll({ type: "window", includeUncontrolled: true }); for (const c of all) { if ("focus" in c) { await c.focus(); c.postMessage({ type: "notification-click", data }); return; } } if (self.clients.openWindow) await self.clients.openWindow("/"); })()); });