You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
7.1 KiB
257 lines
7.1 KiB
// HikeMap Service Worker
|
|
// Increment version to force cache refresh
|
|
const CACHE_NAME = 'hikemap-v1.0.8';
|
|
const urlsToCache = [
|
|
'/',
|
|
'/index.html',
|
|
'/manifest.json',
|
|
'/default.kml',
|
|
'/icon-192x192.png',
|
|
'/icon-512x512.png',
|
|
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css',
|
|
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js',
|
|
'https://unpkg.com/leaflet-rotate@0.2.8/dist/leaflet-rotate-src.js',
|
|
'https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css'
|
|
];
|
|
|
|
// Cache map tiles separately with a different strategy
|
|
const MAP_TILE_CACHE = 'hikemap-tiles-v1';
|
|
const GEOCACHE_CACHE = 'hikemap-geocaches-v1';
|
|
|
|
// Install event - cache essential files
|
|
self.addEventListener('install', event => {
|
|
event.waitUntil(
|
|
caches.open(CACHE_NAME)
|
|
.then(cache => {
|
|
console.log('Opened cache');
|
|
return cache.addAll(urlsToCache);
|
|
})
|
|
.then(() => self.skipWaiting())
|
|
);
|
|
});
|
|
|
|
// Activate event - clean up old caches
|
|
self.addEventListener('activate', event => {
|
|
event.waitUntil(
|
|
caches.keys().then(cacheNames => {
|
|
return Promise.all(
|
|
cacheNames.map(cacheName => {
|
|
if (cacheName !== CACHE_NAME &&
|
|
cacheName !== MAP_TILE_CACHE &&
|
|
cacheName !== GEOCACHE_CACHE) {
|
|
console.log('Deleting old cache:', cacheName);
|
|
return caches.delete(cacheName);
|
|
}
|
|
})
|
|
);
|
|
}).then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
// Fetch event - serve from cache when possible
|
|
self.addEventListener('fetch', event => {
|
|
const url = new URL(event.request.url);
|
|
|
|
// Skip non-http(s) requests (chrome-extension://, etc.)
|
|
if (!url.protocol.startsWith('http')) {
|
|
return;
|
|
}
|
|
|
|
// Handle map tiles with cache-first strategy
|
|
if (url.hostname.includes('tile.openstreetmap.org') ||
|
|
url.hostname.includes('mt0.google.com') ||
|
|
url.hostname.includes('mt1.google.com')) {
|
|
event.respondWith(
|
|
caches.open(MAP_TILE_CACHE).then(cache => {
|
|
return cache.match(event.request).then(response => {
|
|
if (response) {
|
|
return response;
|
|
}
|
|
// Fetch and cache the tile
|
|
return fetch(event.request).then(response => {
|
|
// Only cache successful responses
|
|
if (response.status === 200) {
|
|
cache.put(event.request, response.clone());
|
|
}
|
|
return response;
|
|
}).catch(() => {
|
|
// Return a placeholder tile if offline
|
|
return new Response('', { status: 204 });
|
|
});
|
|
});
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Handle ALL API calls with network-first strategy
|
|
if (url.pathname.startsWith('/api/')) {
|
|
event.respondWith(
|
|
fetch(event.request)
|
|
.then(response => {
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
// Fall back to cache for API calls if offline
|
|
return caches.match(event.request);
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Handle HTML files with network-first strategy (always get fresh version)
|
|
if (event.request.destination === 'document' ||
|
|
url.pathname === '/' ||
|
|
url.pathname.endsWith('.html')) {
|
|
event.respondWith(
|
|
fetch(event.request)
|
|
.then(response => {
|
|
// Cache the fresh version
|
|
if (response.status === 200) {
|
|
const responseToCache = response.clone();
|
|
caches.open(CACHE_NAME).then(cache => {
|
|
cache.put(event.request, responseToCache);
|
|
});
|
|
}
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
// Fall back to cache only if network fails
|
|
return caches.match(event.request);
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Handle geocache/KML data with network-first
|
|
if (url.pathname.includes('/save-kml') ||
|
|
url.pathname.includes('/geocaches')) {
|
|
event.respondWith(
|
|
fetch(event.request)
|
|
.then(response => {
|
|
if (response.status === 200) {
|
|
const responseToCache = response.clone();
|
|
caches.open(GEOCACHE_CACHE).then(cache => {
|
|
cache.put(event.request, responseToCache);
|
|
});
|
|
}
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
return caches.match(event.request);
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Default strategy: cache-first for static assets (CSS, JS libraries, images)
|
|
event.respondWith(
|
|
caches.match(event.request)
|
|
.then(response => {
|
|
if (response) {
|
|
return response;
|
|
}
|
|
// Clone the request because it's a stream
|
|
const fetchRequest = event.request.clone();
|
|
|
|
return fetch(fetchRequest).then(response => {
|
|
// Check if valid response
|
|
if (!response || response.status !== 200 || response.type !== 'basic') {
|
|
return response;
|
|
}
|
|
|
|
// Clone the response because it's a stream
|
|
const responseToCache = response.clone();
|
|
|
|
caches.open(CACHE_NAME).then(cache => {
|
|
cache.put(event.request, responseToCache);
|
|
});
|
|
|
|
return response;
|
|
});
|
|
})
|
|
.catch(() => {
|
|
// Offline fallback
|
|
if (event.request.destination === 'document') {
|
|
return caches.match('/index.html');
|
|
}
|
|
})
|
|
);
|
|
});
|
|
|
|
// Background sync for uploading tracks when back online
|
|
self.addEventListener('sync', event => {
|
|
if (event.tag === 'sync-tracks') {
|
|
event.waitUntil(syncTracks());
|
|
}
|
|
});
|
|
|
|
async function syncTracks() {
|
|
// This would sync any offline changes when connection is restored
|
|
console.log('Syncing tracks with server...');
|
|
// Implementation would go here
|
|
}
|
|
|
|
// Handle messages from the main app
|
|
self.addEventListener('message', event => {
|
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
|
self.skipWaiting();
|
|
}
|
|
});
|
|
|
|
// Handle push notifications
|
|
self.addEventListener('push', event => {
|
|
if (!event.data) {
|
|
console.log('Push notification without data');
|
|
return;
|
|
}
|
|
|
|
const options = event.data.json();
|
|
|
|
event.waitUntil(
|
|
self.registration.showNotification(options.title || 'HikeMap Alert', {
|
|
body: options.body || 'You have a new notification',
|
|
icon: options.icon || '/icon-192x192.png',
|
|
badge: options.badge || '/icon-72x72.png',
|
|
vibrate: [200, 100, 200],
|
|
tag: options.tag || 'hikemap-notification',
|
|
data: options.data || {},
|
|
actions: [
|
|
{
|
|
action: 'view',
|
|
title: 'View',
|
|
icon: '/icon-72x72.png'
|
|
},
|
|
{
|
|
action: 'close',
|
|
title: 'Dismiss'
|
|
}
|
|
]
|
|
})
|
|
);
|
|
});
|
|
|
|
// Handle notification clicks
|
|
self.addEventListener('notificationclick', event => {
|
|
event.notification.close();
|
|
|
|
if (event.action === 'close') {
|
|
return;
|
|
}
|
|
|
|
// Open or focus the app
|
|
event.waitUntil(
|
|
clients.matchAll({ type: 'window' }).then(clientList => {
|
|
// If app is already open, focus it
|
|
for (const client of clientList) {
|
|
if (client.url.includes('maps.bibbit.duckdns.org') && 'focus' in client) {
|
|
return client.focus();
|
|
}
|
|
}
|
|
// If app is not open, open it
|
|
if (clients.openWindow) {
|
|
return clients.openWindow('/');
|
|
}
|
|
})
|
|
);
|
|
});
|