Spaces:
Runtime error
Runtime error
| // TreeTrack Service Worker - PWA and Offline Support | |
| const VERSION = 1762284025; // Cache busting bump - force clients to fetch new static assets and header image change | |
| const CACHE_NAME = `treetrack-v${VERSION}`; | |
| const STATIC_CACHE = `static-v${VERSION}`; | |
| const API_CACHE = `api-v${VERSION}`; | |
| // Check if we're in development mode | |
| const isDevelopment = self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1'; | |
| const urlsToCache = [ | |
| '/static/', | |
| '/static/index.html', | |
| '/static/map.html', | |
| '/static/js/tree-track-app.js', | |
| '/static/js/modules/auth-manager.js', | |
| '/static/js/modules/api-client.js', | |
| '/static/js/modules/ui-manager.js', | |
| '/static/js/modules/form-manager.js', | |
| '/static/js/modules/autocomplete-manager.js', | |
| '/static/js/modules/media-manager.js', | |
| '/static/map.js', | |
| 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', | |
| 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', | |
| 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap' | |
| ]; | |
| // Install event - cache resources | |
| self.addEventListener('install', event => { | |
| event.waitUntil( | |
| caches.open(CACHE_NAME) | |
| .then(cache => { | |
| console.log('Opened cache'); | |
| return cache.addAll(urlsToCache.map(url => new Request(url, {cache: 'reload'}))); | |
| }) | |
| .catch(error => { | |
| console.log('Cache install failed:', error); | |
| }) | |
| ); | |
| }); | |
| // Fetch event - serve cached content when offline | |
| self.addEventListener('fetch', event => { | |
| // Skip non-GET requests | |
| if (event.request.method !== 'GET') { | |
| return; | |
| } | |
| // Skip requests to API endpoints - let them fail gracefully | |
| if (event.request.url.includes('/api/') || event.request.url.includes('/trees')) { | |
| return; | |
| } | |
| // In development mode, always fetch fresh content for static files | |
| // Also detect Hugging Face Spaces development environment | |
| const isHFDevelopment = self.location.hostname.includes('hf.space') || | |
| self.location.hostname.includes('huggingface.co'); | |
| if ((isDevelopment || isHFDevelopment) && event.request.url.includes('/static/')) { | |
| console.log('Development mode: bypassing cache for', event.request.url); | |
| event.respondWith( | |
| fetch(event.request, { | |
| cache: 'no-cache', | |
| headers: { | |
| 'Cache-Control': 'no-cache, no-store, must-revalidate', | |
| 'Pragma': 'no-cache' | |
| } | |
| }) | |
| .catch(() => { | |
| console.log('Network failed, using cache fallback for', event.request.url); | |
| return caches.match(event.request); | |
| }) | |
| ); | |
| return; | |
| } | |
| event.respondWith( | |
| caches.match(event.request) | |
| .then(response => { | |
| // In production, use cache-first strategy | |
| if (response && !isDevelopment) { | |
| return response; | |
| } | |
| // Fetch with cache busting in development | |
| const fetchRequest = isDevelopment ? | |
| new Request(event.request.url + '?_=' + Date.now()) : | |
| event.request; | |
| return fetch(fetchRequest).then(response => { | |
| // Check if we received a valid response | |
| if (!response || response.status !== 200 || response.type !== 'basic') { | |
| return response; | |
| } | |
| // Clone the response | |
| const responseToCache = response.clone(); | |
| // Only cache in production or for offline fallbacks | |
| if (!isDevelopment) { | |
| caches.open(CACHE_NAME) | |
| .then(cache => { | |
| cache.put(event.request, responseToCache); | |
| }); | |
| } | |
| return response; | |
| }).catch(() => { | |
| // If fetch fails, try to return a cached fallback | |
| if (event.request.destination === 'document') { | |
| return caches.match('/static/index.html'); | |
| } | |
| }); | |
| }) | |
| ); | |
| }); | |
| // Activate event - clean up old caches | |
| self.addEventListener('activate', event => { | |
| const cacheWhitelist = [CACHE_NAME]; | |
| event.waitUntil( | |
| caches.keys().then(cacheNames => { | |
| return Promise.all( | |
| cacheNames.map(cacheName => { | |
| if (cacheWhitelist.indexOf(cacheName) === -1) { | |
| return caches.delete(cacheName); | |
| } | |
| }) | |
| ); | |
| }) | |
| ); | |
| }); | |
| // Background sync for offline data submission | |
| self.addEventListener('sync', event => { | |
| if (event.tag === 'background-sync') { | |
| event.waitUntil(doBackgroundSync()); | |
| } | |
| }); | |
| async function doBackgroundSync() { | |
| // Handle any queued tree submissions when back online | |
| try { | |
| const cache = await caches.open('offline-data'); | |
| const requests = await cache.keys(); | |
| for (const request of requests) { | |
| if (request.url.includes('offline-tree-')) { | |
| const response = await cache.match(request); | |
| const treeData = await response.json(); | |
| // Try to submit the data | |
| try { | |
| const submitResponse = await fetch('/api/trees', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(treeData) | |
| }); | |
| if (submitResponse.ok) { | |
| // Remove from cache if successful | |
| await cache.delete(request); | |
| console.log('Offline tree data synced successfully'); | |
| } | |
| } catch (error) { | |
| console.log('Failed to sync offline data:', error); | |
| } | |
| } | |
| } | |
| } catch (error) { | |
| console.log('Background sync failed:', error); | |
| } | |
| } | |
| // Push notifications (for future enhancement) | |
| self.addEventListener('push', event => { | |
| const options = { | |
| body: event.data ? event.data.text() : 'New tree data available!', | |
| icon: '/static/icon-192x192.png', | |
| badge: '/static/badge-72x72.png', | |
| tag: 'treetrack-notification', | |
| data: { | |
| url: '/static/map.html' | |
| } | |
| }; | |
| event.waitUntil( | |
| self.registration.showNotification('TreeTrack', options) | |
| ); | |
| }); | |
| // Handle notification clicks | |
| self.addEventListener('notificationclick', event => { | |
| event.notification.close(); | |
| if (event.notification.data && event.notification.data.url) { | |
| event.waitUntil( | |
| clients.openWindow(event.notification.data.url) | |
| ); | |
| } | |
| }); | |