RoyAalekh's picture
fix: restore missing <style> tag in index.html to prevent raw CSS rendering; bump cache-busting timestamp
d7493ec
raw
history blame
7.13 kB
// TreeTrack Service Worker - PWA and Offline Support
const VERSION = 1755116236; // 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)
);
}
});