support offline by service worker
Deploy app frontend / app frontend (push) Successful in 1m50s

This commit is contained in:
2024-12-08 13:34:21 +02:00
parent e6f6c9d52d
commit d7b92b2d70
7 changed files with 328 additions and 23 deletions
+142 -12
View File
@@ -1,25 +1,155 @@
// const VER = 14
// const VER = 18
self.addEventListener('message', async (event) => {
event.waitUntil(debug(Object.assign({ answer: true }, event.data)))
})
const CACHE_NAME = 'cache-v1'
const TEMP_PAGE_URL = '/offline.html'
const STATIC_ASSETS = [TEMP_PAGE_URL, '/css/offline.css']
self.addEventListener('install', (event) => {
// The promise that skipWaiting() returns can be safely ignored.
event.waitUntil(self.skipWaiting().then(() => debug({ installed: true })))
// Perform any other actions required for your
// service worker to install, potentially inside
// of event.waitUntil();
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS)))
})
self.addEventListener('activate', (event) => {
event.waitUntil(
// Привязка нового сервис-воркера к текущим клиентам
clients.claim().then(() => debug(event.source, { activated: true })),
caches.keys().then((cacheNames) =>
Promise.all(
cacheNames.map((cache) => {
if (cache !== CACHE_NAME) {
return caches.delete(cache)
}
}),
),
),
)
})
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
// Обрабатываем только HTTP/HTTPS запросы
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
return
}
if (event.request.mode === 'navigate') {
// Для навигационных запросов сначала отдаём `/offline.html`, пока основная страница загружается
event.respondWith(handleNavigationRequest(event.request))
} else if (STATIC_ASSETS.includes(url.pathname)) {
// Кэшируем только offline.html и его CSS
event.respondWith(caches.match(event.request))
}
})
const TEMP_CACHE = new Map() // Используем карту для временного хранения
async function handleNavigationRequest(request) {
if (TEMP_CACHE.has(request.url) && navigator.onLine) {
return TEMP_CACHE.get(request.url).clone()
}
const cache = await caches.open(CACHE_NAME)
// Отдаём временную страницу из кеша
const tempResponse = await cache.match(TEMP_PAGE_URL)
// Попытка загрузить основную страницу в фоновом режиме
fetch(request)
.then(async (networkResponse) => {
if (networkResponse && networkResponse.ok) {
// Сохраняем клон в TEMP_CACHE
TEMP_CACHE.set(request.url, networkResponse)
// Сообщаем клиенту, что основная страница готова
const allClients = await clients.matchAll({ type: 'window' })
allClients.forEach((client) =>
client.postMessage({
type: 'MAIN_PAGE_READY',
}),
)
// Удаляем из TEMP_CACHE через минимальное время
setTimeout(() => {
TEMP_CACHE.delete(request.url)
}, 5000)
} else {
throw new Error('Failed to fetch main page')
}
})
.catch(async (error) => {
console.error('Error loading main page:', error)
// Сообщаем клиенту об ошибке сети
const allClients = await clients.matchAll({ type: 'window' })
allClients.forEach((client) =>
client.postMessage({
type: 'NETWORK_ERROR',
}),
)
})
// Возвращаем временную страницу
return tempResponse
}
async function updateStaticAssetsCache() {
const cache = await caches.open(CACHE_NAME)
// Обновляем ресурсы
try {
await Promise.all(
STATIC_ASSETS.map(async (asset) => {
const response = await fetch(asset, { cache: 'no-store' })
if (response.ok) {
await cache.put(asset, response.clone())
} else {
console.error(`Failed to fetch ${asset}:`, response.status)
}
}),
)
console.log('STATIC_ASSETS обновлены.')
} catch (error) {
console.error('Ошибка обновления STATIC_ASSETS:', error)
}
}
self.addEventListener('message', async (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
} else if (event.data && event.data.type === 'CHECK_ONLINE_STATUS') {
const isOnline = navigator.onLine // Проверяем состояние сети
event.ports[0].postMessage({ online: isOnline }) // Отправляем результат обратно
} else if (event.data && event.data.type === 'UPDATE_STATIC_ASSETS') {
event.waitUntil(updateStaticAssetsCache())
} else {
event.waitUntil(debug(Object.assign({ answer: true }, event.data)))
}
})
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(STATIC_ASSETS)
}),
)
self.skipWaiting().then(() => debug({ installed: true }))
})
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) =>
Promise.all(
cacheNames.map((cache) => {
if (cache !== CACHE_NAME) {
return caches.delete(cache)
}
}),
),
),
)
clients.claim().then(() => debug(event.source, { activated: true }))
})
self.addEventListener('push', async (event) => {
debug({
event: 'push',