Initial commit: Second Brain Platform
Complete platform with unified design system and real API integration. Apps: Dashboard, Fitness, Budget, Inventory, Trips, Reader, Media, Settings Infrastructure: SvelteKit + Python gateway + Docker Compose
This commit is contained in:
649
services/trips/sw.js
Normal file
649
services/trips/sw.js
Normal file
@@ -0,0 +1,649 @@
|
||||
const CACHE_NAME = 'trips-v17';
|
||||
const STATIC_CACHE = 'trips-static-v17';
|
||||
const DATA_CACHE = 'trips-data-v17';
|
||||
const IMAGE_CACHE = 'trips-images-v17';
|
||||
|
||||
// Static assets to cache on install
|
||||
const STATIC_ASSETS = [
|
||||
'/'
|
||||
];
|
||||
|
||||
// Install event - cache static assets
|
||||
self.addEventListener('install', (event) => {
|
||||
console.log('[SW] Installing service worker...');
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE)
|
||||
.then((cache) => {
|
||||
console.log('[SW] Caching static assets');
|
||||
return cache.addAll(STATIC_ASSETS);
|
||||
})
|
||||
.then(() => self.skipWaiting())
|
||||
);
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', (event) => {
|
||||
console.log('[SW] Activating service worker...');
|
||||
event.waitUntil(
|
||||
caches.keys().then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames.map((cacheName) => {
|
||||
// Delete old version caches
|
||||
if (cacheName.startsWith('trips-') &&
|
||||
cacheName !== STATIC_CACHE &&
|
||||
cacheName !== DATA_CACHE &&
|
||||
cacheName !== IMAGE_CACHE) {
|
||||
console.log('[SW] Deleting old cache:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
}).then(() => self.clients.claim())
|
||||
);
|
||||
});
|
||||
|
||||
// Check if response is valid (not error page, not Cloudflare error)
|
||||
function isValidResponse(response) {
|
||||
if (!response) return false;
|
||||
if (!response.ok) return false;
|
||||
if (response.redirected) return false;
|
||||
if (response.status !== 200) return false;
|
||||
|
||||
// Check content-type to detect error pages
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
|
||||
// If we expect HTML but got something else, it might be an error
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if response is a Cloudflare/proxy error page
|
||||
async function isErrorPage(response) {
|
||||
if (!response) return true;
|
||||
if (response.status >= 500) return true;
|
||||
if (response.status === 0) return true;
|
||||
|
||||
// Clone to read body without consuming
|
||||
const clone = response.clone();
|
||||
try {
|
||||
const text = await clone.text();
|
||||
// Detect common error page signatures
|
||||
if (text.includes('cloudflare') && text.includes('Error')) return true;
|
||||
if (text.includes('502 Bad Gateway')) return true;
|
||||
if (text.includes('503 Service')) return true;
|
||||
if (text.includes('504 Gateway')) return true;
|
||||
} catch (e) {
|
||||
// If we can't read it, assume it's fine
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fetch event - serve from cache, fallback to network
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// Skip non-GET requests
|
||||
if (event.request.method !== 'GET') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Never cache auth pages - always go to network
|
||||
if (url.pathname === '/login' || url.pathname === '/logout' || url.pathname.startsWith('/auth')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle CDN requests (Quill, etc.) - cache first
|
||||
if (url.origin !== location.origin) {
|
||||
if (url.hostname.includes('cdn.jsdelivr.net')) {
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(cached => {
|
||||
return cached || fetch(event.request);
|
||||
})
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle API requests
|
||||
if (url.pathname.startsWith('/api/')) {
|
||||
event.respondWith(handleApiRequest(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle image requests
|
||||
if (url.pathname.startsWith('/images/')) {
|
||||
event.respondWith(handleImageRequest(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle document requests
|
||||
if (url.pathname.startsWith('/documents/')) {
|
||||
event.respondWith(handleDocumentRequest(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle trip pages - CACHE FIRST for offline support
|
||||
if (url.pathname.startsWith('/trip/')) {
|
||||
event.respondWith(handleTripPageRequest(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle main page - cache first, then pre-cache all trips
|
||||
if (url.pathname === '/') {
|
||||
event.respondWith(handleMainPageRequest(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: cache first, network fallback
|
||||
event.respondWith(handleStaticRequest(event.request));
|
||||
});
|
||||
|
||||
// Handle main page - NETWORK FIRST, only cache when offline
|
||||
async function handleMainPageRequest(request) {
|
||||
// Try network first
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
|
||||
// If redirected (to login), clear cache and return the redirect
|
||||
if (response.redirected) {
|
||||
console.log('[SW] Main page redirected (likely to login), clearing cache');
|
||||
const cache = await caches.open(STATIC_CACHE);
|
||||
cache.delete(request);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
// Clone to read HTML and cache main page images
|
||||
const htmlText = await response.clone().text();
|
||||
|
||||
const cache = await caches.open(STATIC_CACHE);
|
||||
cache.put(request, response.clone());
|
||||
|
||||
// Cache main page images in background
|
||||
cacheMainPageImages(htmlText);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Non-OK response, just return it
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Network failed - we're offline, try cache
|
||||
console.log('[SW] Main page network failed, trying cache');
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) return cachedResponse;
|
||||
return createOfflineResponse('Main page not available offline');
|
||||
}
|
||||
}
|
||||
|
||||
// Cache images from main page
|
||||
async function cacheMainPageImages(html) {
|
||||
const imageCache = await caches.open(IMAGE_CACHE);
|
||||
const imageUrls = extractUrls(html, /\/images\/[^"'\s<>]+/g);
|
||||
|
||||
for (const imgUrl of imageUrls) {
|
||||
try {
|
||||
const existing = await caches.match(imgUrl);
|
||||
if (!existing) {
|
||||
const imgResponse = await fetch(imgUrl);
|
||||
if (imgResponse.ok) {
|
||||
await imageCache.put(imgUrl, imgResponse);
|
||||
console.log('[SW] Cached main page image:', imgUrl);
|
||||
}
|
||||
}
|
||||
} catch (e) { /* skip */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Handle trip pages - NETWORK FIRST, fall back to cache only when offline
|
||||
async function handleTripPageRequest(request) {
|
||||
// Always try network first
|
||||
try {
|
||||
console.log('[SW] Fetching trip from network:', request.url);
|
||||
const response = await fetch(request);
|
||||
|
||||
// Check if response is valid
|
||||
if (response.ok && response.status === 200) {
|
||||
// Check if we got redirected to login/auth page (auth failure)
|
||||
const finalUrl = response.url || '';
|
||||
if (finalUrl.includes('/login') || finalUrl.includes('pocket.') || finalUrl.includes('/authorize') || finalUrl.includes('/oauth')) {
|
||||
console.log('[SW] Redirected to auth, not caching:', finalUrl);
|
||||
// Clear any cached version of this page since user is logged out
|
||||
const cache = await caches.open(DATA_CACHE);
|
||||
cache.delete(request);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Cache and return the valid response
|
||||
const cache = await caches.open(DATA_CACHE);
|
||||
cache.put(request, response.clone());
|
||||
console.log('[SW] Cached trip page:', request.url);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Non-200 response, just return it (might be login redirect)
|
||||
console.log('[SW] Non-200 response:', response.status, request.url);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Network failed - we're offline, try cache
|
||||
console.log('[SW] Network failed, trying cache for trip:', request.url);
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
console.log('[SW] Serving trip from cache (offline):', request.url);
|
||||
// Add header to indicate offline mode
|
||||
const headers = new Headers(cachedResponse.headers);
|
||||
headers.set('X-From-Cache', 'true');
|
||||
return new Response(cachedResponse.body, {
|
||||
status: cachedResponse.status,
|
||||
statusText: cachedResponse.statusText,
|
||||
headers: headers
|
||||
});
|
||||
}
|
||||
return createOfflineResponse('Trip not available offline. Download this trip while online first.');
|
||||
}
|
||||
}
|
||||
|
||||
// Update trip cache in background
|
||||
async function updateTripCache(request) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok && response.status === 200) {
|
||||
// Skip if redirected to login/auth
|
||||
const finalUrl = response.url || '';
|
||||
if (finalUrl.includes('/login') || finalUrl.includes('pocket.') || finalUrl.includes('/authorize') || finalUrl.includes('/oauth')) return;
|
||||
|
||||
const cache = await caches.open(DATA_CACHE);
|
||||
cache.put(request, response.clone());
|
||||
console.log('[SW] Updated cache for:', request.url);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent fail - we already served from cache
|
||||
}
|
||||
}
|
||||
|
||||
// Send progress message to all clients
|
||||
async function sendProgress(message, progress, total) {
|
||||
const clients = await self.clients.matchAll();
|
||||
clients.forEach(client => {
|
||||
client.postMessage({
|
||||
type: 'CACHE_PROGRESS',
|
||||
message,
|
||||
progress,
|
||||
total
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Pre-cache all trips when main page loads
|
||||
let preCacheInProgress = false;
|
||||
async function preCacheAllTrips() {
|
||||
// Prevent duplicate runs
|
||||
if (preCacheInProgress) return;
|
||||
preCacheInProgress = true;
|
||||
|
||||
console.log('[SW] Pre-caching all trips (full offline mode)...');
|
||||
|
||||
// Small delay to ensure page JS has loaded
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/trips', { credentials: 'include' });
|
||||
if (!response.ok) {
|
||||
console.log('[SW] Failed to fetch trips list:', response.status);
|
||||
await sendProgress('Offline sync failed - not logged in?', 0, 0);
|
||||
preCacheInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const trips = await response.json();
|
||||
const dataCache = await caches.open(DATA_CACHE);
|
||||
const imageCache = await caches.open(IMAGE_CACHE);
|
||||
|
||||
const totalTrips = trips.length;
|
||||
let currentTrip = 0;
|
||||
let totalAssets = 0;
|
||||
let cachedAssets = 0;
|
||||
|
||||
await sendProgress('Starting offline sync...', 0, totalTrips);
|
||||
|
||||
// Cache the trips list API response
|
||||
const tripsResponse = await fetch('/api/trips', { credentials: 'include' });
|
||||
if (tripsResponse.ok) {
|
||||
dataCache.put('/api/trips', tripsResponse);
|
||||
}
|
||||
|
||||
// Cache each trip page and its assets
|
||||
for (const trip of trips) {
|
||||
currentTrip++;
|
||||
const tripUrl = `/trip/${trip.id}`;
|
||||
|
||||
await sendProgress(`Caching: ${trip.name}`, currentTrip, totalTrips);
|
||||
|
||||
try {
|
||||
const tripResponse = await fetch(tripUrl, { credentials: 'include' });
|
||||
if (tripResponse.ok && tripResponse.status === 200) {
|
||||
// Skip if redirected to login/auth
|
||||
const finalUrl = tripResponse.url || '';
|
||||
if (finalUrl.includes('/login') || finalUrl.includes('pocket.') || finalUrl.includes('/authorize') || finalUrl.includes('/oauth')) {
|
||||
console.log('[SW] Auth required for trip:', trip.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clone response to read HTML and still cache it
|
||||
const htmlText = await tripResponse.clone().text();
|
||||
await dataCache.put(tripUrl, tripResponse);
|
||||
console.log('[SW] Pre-cached trip:', trip.name);
|
||||
|
||||
// Extract and cache all images
|
||||
const imageUrls = extractUrls(htmlText, /\/images\/[^"'\s<>]+/g);
|
||||
totalAssets += imageUrls.length;
|
||||
for (const imgUrl of imageUrls) {
|
||||
try {
|
||||
const existing = await caches.match(imgUrl);
|
||||
if (!existing) {
|
||||
const imgResponse = await fetch(imgUrl);
|
||||
if (imgResponse.ok) {
|
||||
await imageCache.put(imgUrl, imgResponse);
|
||||
cachedAssets++;
|
||||
console.log('[SW] Cached image:', imgUrl);
|
||||
}
|
||||
} else {
|
||||
cachedAssets++;
|
||||
}
|
||||
} catch (e) { /* skip failed images */ }
|
||||
}
|
||||
|
||||
// Extract and cache all documents
|
||||
const docUrls = extractUrls(htmlText, /\/documents\/[^"'\s<>]+/g);
|
||||
totalAssets += docUrls.length;
|
||||
for (const docUrl of docUrls) {
|
||||
try {
|
||||
const existing = await caches.match(docUrl);
|
||||
if (!existing) {
|
||||
const docResponse = await fetch(docUrl);
|
||||
if (docResponse.ok) {
|
||||
await dataCache.put(docUrl, docResponse);
|
||||
cachedAssets++;
|
||||
console.log('[SW] Cached document:', docUrl);
|
||||
}
|
||||
} else {
|
||||
cachedAssets++;
|
||||
}
|
||||
} catch (e) { /* skip failed docs */ }
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[SW] Failed to pre-cache trip:', trip.name, e);
|
||||
}
|
||||
}
|
||||
|
||||
await sendProgress('complete', totalTrips, totalTrips);
|
||||
console.log('[SW] Full pre-caching complete!', totalTrips, 'trips,', cachedAssets, 'files');
|
||||
preCacheInProgress = false;
|
||||
} catch (error) {
|
||||
console.error('[SW] Pre-caching failed:', error);
|
||||
await sendProgress('Offline sync failed', 0, 0);
|
||||
preCacheInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to extract URLs from HTML using regex
|
||||
function extractUrls(html, pattern) {
|
||||
const matches = html.match(pattern) || [];
|
||||
// Dedupe and clean
|
||||
return [...new Set(matches.map(url => {
|
||||
// Decode HTML entities and remove trailing quotes
|
||||
return url.replace(/&/g, '&').replace(/["'<>]/g, '');
|
||||
}))];
|
||||
}
|
||||
|
||||
// Handle static assets - cache first
|
||||
async function handleStaticRequest(request) {
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
// Return cached, but also update cache in background
|
||||
fetchAndCache(request, STATIC_CACHE);
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
return fetchAndCache(request, STATIC_CACHE);
|
||||
}
|
||||
|
||||
// Handle API requests - cache first for trip data, network for others
|
||||
async function handleApiRequest(request) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// These endpoints can be cached for offline use
|
||||
const cacheableEndpoints = [
|
||||
'/api/trips',
|
||||
'/api/trip/'
|
||||
];
|
||||
|
||||
const shouldCache = cacheableEndpoints.some(endpoint => url.pathname.startsWith(endpoint));
|
||||
|
||||
// NETWORK FIRST - always try network, only use cache when offline
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok && shouldCache) {
|
||||
const cache = await caches.open(DATA_CACHE);
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Network failed - we're offline, try cache
|
||||
console.log('[SW] API request failed (offline), trying cache:', request.url);
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
const headers = new Headers(cachedResponse.headers);
|
||||
headers.set('X-From-Cache', 'true');
|
||||
return new Response(cachedResponse.body, {
|
||||
status: cachedResponse.status,
|
||||
statusText: cachedResponse.statusText,
|
||||
headers: headers
|
||||
});
|
||||
}
|
||||
return createOfflineJsonResponse({ error: 'Offline - data not cached' });
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch and cache API in background
|
||||
async function fetchAndCacheApi(request) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
const cache = await caches.open(DATA_CACHE);
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent fail
|
||||
}
|
||||
}
|
||||
|
||||
// Handle image requests - cache first (images don't change)
|
||||
async function handleImageRequest(request) {
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
const cache = await caches.open(IMAGE_CACHE);
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log('[SW] Image not available offline:', request.url);
|
||||
return createPlaceholderImage();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle document requests - cache first
|
||||
async function handleDocumentRequest(request) {
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
const cache = await caches.open(DATA_CACHE);
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log('[SW] Document not available offline:', request.url);
|
||||
return createOfflineResponse('Document not available offline');
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: fetch and cache
|
||||
async function fetchAndCache(request, cacheName) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
const cache = await caches.open(cacheName);
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: create offline HTML response
|
||||
function createOfflineResponse(message) {
|
||||
return new Response(
|
||||
`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Offline - Trips</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.offline-container {
|
||||
padding: 40px;
|
||||
}
|
||||
.offline-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1 { color: #fff; margin-bottom: 10px; }
|
||||
p { color: #aaa; }
|
||||
button {
|
||||
background: #4a9eff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover { background: #3a8eef; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="offline-container">
|
||||
<div class="offline-icon">📡</div>
|
||||
<h1>You're Offline</h1>
|
||||
<p>${message}</p>
|
||||
<button onclick="location.reload()">Try Again</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>`,
|
||||
{
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'text/html' }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Helper: create offline JSON response
|
||||
function createOfflineJsonResponse(data) {
|
||||
return new Response(JSON.stringify(data), {
|
||||
status: 503,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-From-Cache': 'false'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper: create placeholder image
|
||||
function createPlaceholderImage() {
|
||||
// 1x1 transparent PNG
|
||||
const placeholder = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
||||
const binary = atob(placeholder);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return new Response(bytes, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'image/png' }
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for messages from the main thread
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
|
||||
// Cache specific trip data on demand
|
||||
if (event.data && event.data.type === 'CACHE_TRIP') {
|
||||
const tripId = event.data.tripId;
|
||||
cacheTripData(tripId);
|
||||
}
|
||||
|
||||
// Clear all caches
|
||||
if (event.data && event.data.type === 'CLEAR_CACHE') {
|
||||
caches.keys().then((names) => {
|
||||
names.forEach((name) => {
|
||||
if (name.startsWith('trips-')) {
|
||||
caches.delete(name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Force pre-cache all trips
|
||||
if (event.data && event.data.type === 'PRECACHE_ALL') {
|
||||
preCacheAllTrips();
|
||||
}
|
||||
});
|
||||
|
||||
// Cache trip data for offline use
|
||||
async function cacheTripData(tripId) {
|
||||
const cache = await caches.open(DATA_CACHE);
|
||||
|
||||
try {
|
||||
// Cache trip page
|
||||
const tripPageUrl = `/trip/${tripId}`;
|
||||
const tripPageResponse = await fetch(tripPageUrl);
|
||||
if (tripPageResponse.ok) {
|
||||
await cache.put(tripPageUrl, tripPageResponse);
|
||||
}
|
||||
|
||||
console.log('[SW] Cached trip data for:', tripId);
|
||||
} catch (error) {
|
||||
console.error('[SW] Failed to cache trip:', error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user