92 lines
2.8 KiB
JavaScript
92 lines
2.8 KiB
JavaScript
|
|
/*
|
||
|
|
* Сервис-воркер glchat (AGENT.md, Фаза 5): офлайн-заглушка и кэш статики.
|
||
|
|
*
|
||
|
|
* Правила простые и безопасные:
|
||
|
|
* - навигация — сеть, при ошибке офлайн-страница (данные всегда свежие);
|
||
|
|
* - собранная статика `/assets/*` (хешированные имена) — из кэша;
|
||
|
|
* - API, шлюз и файлы никогда не кэшируются: это приватные данные.
|
||
|
|
*/
|
||
|
|
const VERSION = 'glchat-v1';
|
||
|
|
const OFFLINE_URL = '/offline.html';
|
||
|
|
|
||
|
|
self.addEventListener('install', (event) => {
|
||
|
|
event.waitUntil(
|
||
|
|
caches.open(VERSION).then((cache) => cache.addAll([OFFLINE_URL, '/manifest.webmanifest'])),
|
||
|
|
);
|
||
|
|
self.skipWaiting();
|
||
|
|
});
|
||
|
|
|
||
|
|
self.addEventListener('activate', (event) => {
|
||
|
|
event.waitUntil(
|
||
|
|
caches
|
||
|
|
.keys()
|
||
|
|
.then((keys) =>
|
||
|
|
Promise.all(keys.filter((key) => key !== VERSION).map((key) => caches.delete(key))),
|
||
|
|
)
|
||
|
|
.then(() => self.clients.claim()),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
/** Приватные запросы: их нельзя ни кэшировать, ни отдавать из кэша. */
|
||
|
|
function isPrivate(url) {
|
||
|
|
return (
|
||
|
|
url.pathname.startsWith('/api/') ||
|
||
|
|
url.pathname.startsWith('/files/') ||
|
||
|
|
url.pathname.startsWith('/gateway') ||
|
||
|
|
url.pathname.startsWith('/rtc')
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
self.addEventListener('fetch', (event) => {
|
||
|
|
const request = event.request;
|
||
|
|
if (request.method !== 'GET') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const url = new URL(request.url);
|
||
|
|
if (url.origin !== self.location.origin || isPrivate(url)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (request.mode === 'navigate') {
|
||
|
|
event.respondWith(
|
||
|
|
fetch(request).catch(() =>
|
||
|
|
caches.match(request).then((cached) => cached ?? caches.match(OFFLINE_URL)),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (url.pathname.startsWith('/assets/')) {
|
||
|
|
event.respondWith(
|
||
|
|
caches.match(request).then(
|
||
|
|
(cached) =>
|
||
|
|
cached ??
|
||
|
|
fetch(request).then((response) => {
|
||
|
|
if (response.ok) {
|
||
|
|
const copy = response.clone();
|
||
|
|
void caches.open(VERSION).then((cache) => cache.put(request, copy));
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
}),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Иконки, манифест и прочая статика: сеть, при недоступности — кэш.
|
||
|
|
event.respondWith(
|
||
|
|
fetch(request)
|
||
|
|
.then((response) => {
|
||
|
|
if (
|
||
|
|
response.ok &&
|
||
|
|
(url.pathname.startsWith('/icons/') || url.pathname === '/manifest.webmanifest')
|
||
|
|
) {
|
||
|
|
const copy = response.clone();
|
||
|
|
void caches.open(VERSION).then((cache) => cache.put(request, copy));
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
})
|
||
|
|
.catch(() => caches.match(request).then((cached) => cached ?? Response.error())),
|
||
|
|
);
|
||
|
|
});
|