// Service worker: push-уведомления + офлайн-оболочка мобильной версии (/m). const CACHE = "shell-m-v3"; const SHELL = ["/m", "/static/m/m.js", "/static/m/m.css", "/static/m/manifest.webmanifest", "/static/m/icon-192.png", "/static/m/icon-512.png", "/static/m/apple-touch-icon.png", "/static/assistant.js"]; self.addEventListener("install", e => { e.waitUntil(caches.open(CACHE).then(c => Promise.allSettled(SHELL.map(u => c.add(u)))).then(() => self.skipWaiting())); }); self.addEventListener("activate", e => { e.waitUntil(caches.keys().then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))).then(() => self.clients.claim())); }); const isShell = url => url.origin === self.location.origin && (url.pathname === "/m" || url.pathname === "/m/" || url.pathname.startsWith("/static/m/") || url.pathname === "/static/assistant.js"); // Сеть в приоритете (чтобы обновления приходили сразу), при отсутствии связи — кэш. API не перехватываем. self.addEventListener("fetch", e => { const req = e.request, url = new URL(req.url); if (req.method !== "GET" || !isShell(url)) return; e.respondWith((async () => { const cache = await caches.open(CACHE); try { const net = await Promise.race([fetch(req), new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), 4000))]); if (net && net.ok) cache.put(url.pathname === "/m/" ? "/m" : req, net.clone()); return net; } catch (err) { const hit = await cache.match(url.pathname === "/m/" ? "/m" : req, { ignoreSearch: true }); if (hit) return hit; throw err; } })()); }); self.addEventListener("push", e => { let d = {}; try { d = e.data.json(); } catch (_) { d = { title: "Чек-листы", body: e.data ? e.data.text() : "" }; } e.waitUntil(self.registration.showNotification(d.title || "Чек-листы", { body: d.body || "", data: { url: d.url || "/" }, tag: d.url || undefined, icon: "/static/m/icon-192.png", badge: "/static/m/icon-192.png", })); }); self.addEventListener("notificationclick", e => { e.notification.close(); let target = (e.notification.data && e.notification.data.url) || "/"; // на телефоне (по User-Agent) открываем мобильную версию: /#/order/5 -> /m#/o/5 const m = target.match(/#\/order\/(\d+)/); if (m && /Android|iPhone|iPad|iPod/i.test(self.navigator.userAgent)) target = "/m#/o/" + m[1]; const url = new URL(target, self.location.origin).href; e.waitUntil(self.clients.matchAll({ type: "window", includeUncontrolled: true }).then(list => { for (const c of list) if ("focus" in c) { c.navigate(url); return c.focus(); } return self.clients.openWindow(url); })); });