Spaces:
Runtime error
Runtime error
| /* 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("/"); | |
| })()); | |
| }); | |