Files
microdao-daarion/apps/web/public/sw.js
Apple a3e632b9e7 feat: Add PWA support
- PWA_MOBILE_SPEC.md documentation
- manifest.json with app metadata
- Service Worker with caching strategies
- Offline page
- PWA registration in layout
- App icons (placeholder)
2025-11-26 13:56:22 -08:00

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' }
}
);
}
}