- PWA_MOBILE_SPEC.md documentation - manifest.json with app metadata - Service Worker with caching strategies - Offline page - PWA registration in layout - App icons (placeholder)
136 lines
3.7 KiB
JavaScript
136 lines
3.7 KiB
JavaScript
// DAARION Service Worker v1.0
|
|
const CACHE_VERSION = 'v1';
|
|
const STATIC_CACHE = `daarion-static-${CACHE_VERSION}`;
|
|
const PAGES_CACHE = `daarion-pages-${CACHE_VERSION}`;
|
|
|
|
// Files to precache
|
|
const PRECACHE_URLS = [
|
|
'/',
|
|
'/city',
|
|
'/offline',
|
|
'/manifest.json',
|
|
'/icons/icon-192x192.png',
|
|
'/icons/icon-512x512.png'
|
|
];
|
|
|
|
// Install event - precache static assets
|
|
self.addEventListener('install', (event) => {
|
|
console.log('[SW] Installing...');
|
|
event.waitUntil(
|
|
caches.open(STATIC_CACHE)
|
|
.then((cache) => {
|
|
console.log('[SW] Precaching app shell');
|
|
return cache.addAll(PRECACHE_URLS);
|
|
})
|
|
.then(() => self.skipWaiting())
|
|
);
|
|
});
|
|
|
|
// Activate event - clean old caches
|
|
self.addEventListener('activate', (event) => {
|
|
console.log('[SW] Activating...');
|
|
event.waitUntil(
|
|
caches.keys().then((cacheNames) => {
|
|
return Promise.all(
|
|
cacheNames
|
|
.filter((name) => name.startsWith('daarion-') && name !== STATIC_CACHE && name !== PAGES_CACHE)
|
|
.map((name) => {
|
|
console.log('[SW] Deleting old cache:', name);
|
|
return caches.delete(name);
|
|
})
|
|
);
|
|
}).then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
// Fetch event - handle requests
|
|
self.addEventListener('fetch', (event) => {
|
|
const { request } = event;
|
|
const url = new URL(request.url);
|
|
|
|
// Skip non-GET requests
|
|
if (request.method !== 'GET') return;
|
|
|
|
// Skip API and WebSocket requests
|
|
if (url.pathname.startsWith('/api/') ||
|
|
url.pathname.startsWith('/ws/') ||
|
|
url.pathname.startsWith('/_matrix/')) {
|
|
return;
|
|
}
|
|
|
|
// Static assets - Cache First
|
|
if (url.pathname.startsWith('/_next/static/') ||
|
|
url.pathname.match(/\.(js|css|png|jpg|jpeg|gif|svg|woff|woff2)$/)) {
|
|
event.respondWith(cacheFirst(request, STATIC_CACHE));
|
|
return;
|
|
}
|
|
|
|
// HTML pages - Network First with fallback
|
|
if (request.headers.get('accept')?.includes('text/html')) {
|
|
event.respondWith(networkFirstWithFallback(request, PAGES_CACHE));
|
|
return;
|
|
}
|
|
|
|
// Default - try network
|
|
event.respondWith(fetch(request));
|
|
});
|
|
|
|
// Cache First strategy
|
|
async function cacheFirst(request, cacheName) {
|
|
const cachedResponse = await caches.match(request);
|
|
if (cachedResponse) {
|
|
// Update cache in background
|
|
fetch(request).then((response) => {
|
|
if (response.ok) {
|
|
caches.open(cacheName).then((cache) => cache.put(request, response));
|
|
}
|
|
}).catch(() => {});
|
|
return cachedResponse;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok) {
|
|
const cache = await caches.open(cacheName);
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch (error) {
|
|
console.log('[SW] Cache first failed:', request.url);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Network First with fallback strategy
|
|
async function networkFirstWithFallback(request, cacheName) {
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok) {
|
|
const cache = await caches.open(cacheName);
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch (error) {
|
|
console.log('[SW] Network failed, trying cache:', request.url);
|
|
const cachedResponse = await caches.match(request);
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
|
|
// Return offline page for navigation requests
|
|
const offlineResponse = await caches.match('/offline');
|
|
if (offlineResponse) {
|
|
return offlineResponse;
|
|
}
|
|
|
|
// Return a basic offline response
|
|
return new Response(
|
|
'<html><body><h1>Ви офлайн</h1><p>Перевірте підключення до інтернету</p></body></html>',
|
|
{
|
|
status: 503,
|
|
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
}
|
|
);
|
|
}
|
|
}
|