53 lines
2.8 KiB
JavaScript
53 lines
2.8 KiB
JavaScript
// 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);
|
|
}));
|
|
});
|