- add optional collection_photo field to OrderItem type and backend support for pickup photo uploads - implement in-memory + localStorage cache for order items to eliminate tab-switch loading skeletons - update service worker to use stale-while-revalidate for navigation requests to reduce perceived lag - fix LocationTracker to only activate when logged in and hoist to root layout for persistent tracking - adjust InstallPrompt positioning to avoid overlapping bottom navigation - throttle map cluster recomputation to reduce unnecessary work during pan/zoom - clear cached items on logout to prevent cross-driver data leakage
159 lines
No EOL
4.4 KiB
JavaScript
159 lines
No EOL
4.4 KiB
JavaScript
const CACHE_NAME = '1driver-v1';
|
|
const STATIC_CACHE = '1driver-static-v1';
|
|
const API_CACHE = '1driver-api-v1';
|
|
const IMAGE_CACHE = '1driver-images-v1';
|
|
|
|
const POCKETBASE_URL = 'https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io';
|
|
|
|
const PRECACHE_URLS = [
|
|
'/',
|
|
'/login/',
|
|
'/map/',
|
|
'/settings/',
|
|
'/manifest.json',
|
|
'/icons/icon-192.png',
|
|
'/icons/icon-512.png',
|
|
'/icons/apple-touch-icon.png',
|
|
];
|
|
|
|
const STATIC_EXTENSIONS = [
|
|
'.js', '.css', '.woff', '.woff2', '.ttf', '.eot', '.ico',
|
|
];
|
|
|
|
self.addEventListener('install', (event) => {
|
|
event.waitUntil(
|
|
caches.open(STATIC_CACHE).then((cache) => {
|
|
return cache.addAll(PRECACHE_URLS).catch(() => {
|
|
// Precache silently fails for offline-first approach
|
|
});
|
|
})
|
|
);
|
|
self.skipWaiting();
|
|
});
|
|
|
|
self.addEventListener('activate', (event) => {
|
|
event.waitUntil(
|
|
caches.keys().then((keys) =>
|
|
Promise.all(
|
|
keys
|
|
.filter((key) => key !== CACHE_NAME && key !== STATIC_CACHE && key !== API_CACHE && key !== IMAGE_CACHE)
|
|
.map((key) => caches.delete(key))
|
|
)
|
|
)
|
|
);
|
|
self.clients.claim();
|
|
});
|
|
|
|
self.addEventListener('fetch', (event) => {
|
|
const url = new URL(event.request.url);
|
|
|
|
// Skip non-GET requests
|
|
if (event.request.method !== 'GET') return;
|
|
|
|
// Skip cross-origin except PocketBase API
|
|
if (url.origin !== self.location.origin && !url.href.startsWith(POCKETBASE_URL)) return;
|
|
|
|
// PocketBase API - Network First
|
|
if (url.href.startsWith(POCKETBASE_URL)) {
|
|
event.respondWith(networkFirst(event.request, API_CACHE));
|
|
return;
|
|
}
|
|
|
|
// Map tiles - Stale While Revalidate
|
|
if (url.hostname.includes('tile.openstreetmap.org') || url.hostname.includes('openstreetmap.org')) {
|
|
event.respondWith(staleWhileRevalidate(event.request, IMAGE_CACHE));
|
|
return;
|
|
}
|
|
|
|
// Leaflet CDN assets - Cache First
|
|
if (url.hostname.includes('cdnjs.cloudflare.com')) {
|
|
event.respondWith(cacheFirst(event.request, STATIC_CACHE));
|
|
return;
|
|
}
|
|
|
|
// Static assets - Cache First
|
|
const isStatic = STATIC_EXTENSIONS.some((ext) => url.pathname.endsWith(ext));
|
|
if (isStatic) {
|
|
event.respondWith(cacheFirst(event.request, STATIC_CACHE));
|
|
return;
|
|
}
|
|
|
|
// Navigation requests (HTML pages) - Stale While Revalidate so
|
|
// repeat visits render the cached HTML instantly while we fetch
|
|
// a fresh copy in the background. Previously this used
|
|
// networkFirst, which forced a full network round-trip on every
|
|
// tab switch — the biggest contributor to "feels sluggish on PC"
|
|
// alongside the per-page loading skeleton.
|
|
if (event.request.mode === 'navigate') {
|
|
event.respondWith(staleWhileRevalidate(event.request, STATIC_CACHE));
|
|
return;
|
|
}
|
|
|
|
// Everything else - Stale While Revalidate
|
|
event.respondWith(staleWhileRevalidate(event.request, STATIC_CACHE));
|
|
});
|
|
|
|
async function cacheFirst(request, cacheName) {
|
|
const cached = await caches.match(request);
|
|
if (cached) return cached;
|
|
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok) {
|
|
const cache = await caches.open(cacheName);
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch {
|
|
return new Response('Offline', { status: 503, statusText: 'Offline' });
|
|
}
|
|
}
|
|
|
|
async function networkFirst(request, cacheName, fallbackPath) {
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok) {
|
|
const cache = await caches.open(cacheName);
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch {
|
|
const cached = await caches.match(request);
|
|
if (cached) return cached;
|
|
|
|
if (fallbackPath) {
|
|
const fallback = await caches.match(fallbackPath);
|
|
if (fallback) return fallback;
|
|
}
|
|
|
|
return new Response('Offline', { status: 503, statusText: 'Offline' });
|
|
}
|
|
}
|
|
|
|
async function staleWhileRevalidate(request, cacheName) {
|
|
const cache = await caches.open(cacheName);
|
|
const cached = await cache.match(request);
|
|
|
|
const fetchPromise = fetch(request)
|
|
.then((response) => {
|
|
if (response.ok) {
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
})
|
|
.catch(() => cached);
|
|
|
|
return cached || fetchPromise;
|
|
}
|
|
|
|
// Background sync for offline queue (future enhancement)
|
|
self.addEventListener('sync', (event) => {
|
|
if (event.tag === 'sync-collections') {
|
|
event.waitUntil(syncPendingData());
|
|
}
|
|
});
|
|
|
|
async function syncPendingData() {
|
|
// Placeholder for future offline queue sync
|
|
console.log('[SW] Sync event triggered');
|
|
} |