PWA Adjustments:
- Better iOS handling - Force reload on SW change
This commit is contained in:
1 parent
21f91975fb
commit
06ce6cabeb
4 files changed
+80
-30
No files matched your search
+5
-1
@@ -11,7 +11,11 @@
|
||||
<!-- PWA Requirements -->
|
||||
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
|
||||
<link rel="apple-touch-icon" href="%sveltekit.assets%/icons/apple-touch-icon.png" />
|
||||
<meta name="theme-color" content="#4fd1d1" />
|
||||
<meta name="theme-color" content="#3c6f79" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="OwlBoard" />
|
||||
<link rel="canonical" href="https://owlboard.info" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://owlboard.info" />
|
||||
|
||||
@@ -31,18 +31,44 @@
|
||||
// Initialize location store
|
||||
LOCATIONS.init();
|
||||
|
||||
// Register service worker
|
||||
// Register service worker with aggressive update listener
|
||||
if ('serviceWorker' in navigator) {
|
||||
let refreshing = false;
|
||||
|
||||
// Reload when active service worker updates to prevent version mixing
|
||||
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||
if (!refreshing) {
|
||||
refreshing = true;
|
||||
console.info('[SW] New version activated, reloading for clean state.');
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
navigator.serviceWorker
|
||||
.register('/service-worker.js', {
|
||||
type: 'module'
|
||||
})
|
||||
.then((registration) => {
|
||||
console.info('OwlBoard Service Worker registered', registration.scope);
|
||||
registration.update().catch(() => {});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Service Worker installation failed: ', error);
|
||||
});
|
||||
|
||||
// Check for updates when tab or app comes into focus
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
navigator.serviceWorker.ready
|
||||
.then((registration) => registration.update())
|
||||
.catch(() => {});
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+44
-24
@@ -14,19 +14,36 @@ const ASSETS = [
|
||||
|
||||
sw.addEventListener('install', (event) => {
|
||||
sw.skipWaiting();
|
||||
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)));
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then(async (cache) => {
|
||||
// Cache assets resiliently so a single transient failure does not abort installation
|
||||
await Promise.all(
|
||||
ASSETS.map((asset) =>
|
||||
cache.add(asset).catch((err) => {
|
||||
console.warn(`[SW] Failed to precache ${asset}:`, err);
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
sw.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
caches.keys().then((keys) => {
|
||||
return Promise.all(
|
||||
keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))
|
||||
);
|
||||
}),
|
||||
sw.clients.claim()
|
||||
])
|
||||
(async () => {
|
||||
// Purge all legacy and outdated caches to ensure a clean break
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(
|
||||
keys
|
||||
.filter((key) => key !== CACHE_NAME)
|
||||
.map((key) => {
|
||||
console.info(`[SW] Purging outdated cache: ${key}`);
|
||||
return caches.delete(key);
|
||||
})
|
||||
);
|
||||
// Take control of all open client tabs immediately
|
||||
await sw.clients.claim();
|
||||
})()
|
||||
);
|
||||
});
|
||||
|
||||
@@ -36,19 +53,33 @@ sw.addEventListener('fetch', (event) => {
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Uncached static assets
|
||||
// 1. Uncached static assets (manifest and dynamic icons)
|
||||
if (url.pathname.endsWith('manifest.json') || url.pathname.includes('/icons/')) {
|
||||
event.respondWith(fetch(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Static Assets (Cache-First)
|
||||
// 2. Navigation requests: Network-First with offline fallback
|
||||
// Always fetch fresh HTML from the network when online so new deployments are served immediately.
|
||||
// Falls back to the cached root/index page when offline.
|
||||
if (request.mode === 'navigate' || request.headers.get('accept')?.includes('text/html')) {
|
||||
event.respondWith(
|
||||
fetch(request).catch(async () => {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
return (await cache.match('/')) || ((await cache.match('index.html')) as Response);
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Static Assets: Cache-First
|
||||
// Hashed assets (/_app/immutable/...) and precached assets
|
||||
if (ASSETS.includes(url.pathname) || url.pathname.startsWith('/_app/')) {
|
||||
event.respondWith(caches.match(request).then((res) => res || fetch(request)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Cachable API Responses - stale-while-revalidate
|
||||
// 4. Cachable API Responses - stale-while-revalidate
|
||||
if (url.pathname === '/api/v3/locationFilter/data') {
|
||||
event.respondWith(
|
||||
caches.open('ob-dynamic-cache').then(async (cache) => {
|
||||
@@ -88,7 +119,7 @@ sw.addEventListener('fetch', (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Offline API Fallback (Network-First)
|
||||
// 5. Offline API Fallback (Network-First)
|
||||
if (url.pathname.startsWith('/api') || url.hostname.includes('api')) {
|
||||
event.respondWith(
|
||||
fetch(request).catch(() => {
|
||||
@@ -107,15 +138,4 @@ sw.addEventListener('fetch', (event) => {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Nav fallback - return fallback page, if offline and page requested
|
||||
if (request.mode === 'navigate' || request.headers.get('accept')?.includes('text/html')) {
|
||||
event.respondWith(
|
||||
fetch(request).catch(() => {
|
||||
// Serve svelte fallback page
|
||||
return caches.match('/') as Promise<Response>;
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
@@ -7,13 +7,12 @@
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"display_override": [
|
||||
"window-controls-overlay",
|
||||
"standalone",
|
||||
"minimal-ui"
|
||||
],
|
||||
"background_color": "#404c55",
|
||||
"theme_color": "#4fd1d1",
|
||||
"orientation": "portrait",
|
||||
"theme_color": "#3c6f79",
|
||||
"orientation": "any",
|
||||
"categories": [
|
||||
"transportation",
|
||||
"utilities"
|
||||
@@ -50,7 +49,8 @@
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-192.png",
|
||||
"sizes": "192x192"
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user