grechka-game/sw.js

107 lines
4.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Service Worker для GrechkaCraft.
//
// Раньше на любую сетевую ошибку отдавался `new Response('Offline', {status: 503})`.
// Из-за этого обычный обрыв сети (моргнувший Wi-Fi, потерянный пакет) выглядел
// как «сервер упал»: страница грузилась, но весь JS и мир приходили как 503,
// и пользователь видел либо пустой экран, либо сообщение об ошибке.
//
// Теперь:
// - статика (HTML/JS/CSS/ассеты) кэшируется и отдаётся из кэша при сбое;
// - навигация при сбое отдаёт закэшированную копию страницы;
// - если кэша нет — отдаём 200 с минимальной страницей-заглушкой, а НЕ 503,
// потому что 503 для пользователя неотличим от «сервис умер».
const CACHE = 'grechka-v3';
const PRECACHE_URLS = [
'./',
'./index.html',
'./index-v2.html',
'./game.js',
'./game3d.js',
'./style.css',
'./manifest.json',
];
self.addEventListener('install', (e) => {
self.skipWaiting();
e.waitUntil(
caches.open(CACHE)
// addAll падает целиком, если хоть один URL недоступен — кэшируем по одному.
.then((c) => Promise.all(
PRECACHE_URLS.map((u) => c.add(u).catch(() => null))
))
.then(() => self.clients.claim())
);
});
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 OFFLINE_PAGE = `<!doctype html><html lang="ru"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Гречка — нет связи</title>
<style>
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;
background:#0f1720;color:#e8f0f8;font:16px/1.5 system-ui,sans-serif;text-align:center;padding:24px}
.b{max-width:340px}
h1{font-size:20px;margin:0 0 10px}
p{color:#9fb0c4;margin:0 0 18px}
button{font:inherit;padding:11px 22px;border:0;border-radius:9px;background:#5a8f3c;
color:#fff;cursor:pointer}
</style></head><body><div class="b">
<h1>Нет связи с сервером</h1>
<p>Игра не смогла загрузиться. Проверь интернет и нажми «Попробовать снова».</p>
<button onclick="location.reload()">Попробовать снова</button>
</div></body></html>`;
self.addEventListener('fetch', (e) => {
const req = e.request;
if (req.method !== 'GET') return;
const url = new URL(req.url);
// Кросс-доменные запросы (socket.io CDN, apigrech, telegram) не трогаем.
if (url.origin !== self.location.origin) return;
// Навигация: сначала сеть, при сбое — кэш страницы, иначе заглушка.
if (req.mode === 'navigate') {
e.respondWith(
fetch(req)
.then((res) => {
const copy = res.clone();
caches.open(CACHE).then((c) => c.put(req, copy));
return res;
})
.catch(() => caches.match(req, { ignoreSearch: true })
.then((r) => r || caches.match('./index.html'))
.then((r) => r || new Response(OFFLINE_PAGE, {
status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' },
})))
);
return;
}
// Статика: сначала сеть, кэшируем свежее; при сбое — из кэша.
e.respondWith(
fetch(req)
.then((res) => {
if (res && res.ok && res.type === 'basic') {
const copy = res.clone();
caches.open(CACHE).then((c) => c.put(req, copy));
}
return res;
})
.catch(() => caches.match(req, { ignoreSearch: true })
.then((r) => r || new Response('', { status: 200 })))
);
});