salgram / static /sw.js
ChessVania's picture
Deploy SalGram (Docker)
484ec23 verified
Raw
History Blame Contribute Delete
3.32 kB
/* 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("/");
})());
});