Spaces:
Running
Running
| /* ============================================ | |
| IVY'S RSS HUB β Service Worker | |
| Enables offline support and PWA installation | |
| πΏ Created by Ivy β December 2025 | |
| ============================================ */ | |
| const CACHE_NAME = "ivy-rss-hub-v1"; | |
| const STATIC_ASSETS = [ | |
| "./", | |
| "./index.html", | |
| "./styles/main.css", | |
| "./scripts/app.js", | |
| "./scripts/feeds-config.js", | |
| "./scripts/rss-parser.js", | |
| "./scripts/sidebar.js", | |
| "./libs/dexie.min.js", | |
| "./manifest.json" | |
| ]; | |
| // Install event β cache static assets | |
| self.addEventListener("install", event => { | |
| console.log("[SW] Installing Service Worker..."); | |
| event.waitUntil( | |
| caches.open(CACHE_NAME).then(cache => { | |
| console.log("[SW] Caching static assets"); | |
| return cache.addAll(STATIC_ASSETS); | |
| }) | |
| ); | |
| // Activate immediately | |
| self.skipWaiting(); | |
| }); | |
| // Activate event β cleanup old caches | |
| self.addEventListener("activate", event => { | |
| console.log("[SW] Activating Service Worker..."); | |
| event.waitUntil( | |
| caches.keys().then(cacheNames => { | |
| return Promise.all( | |
| cacheNames | |
| .filter(name => name !== CACHE_NAME) | |
| .map(name => { | |
| console.log("[SW] Deleting old cache:", name); | |
| return caches.delete(name); | |
| }) | |
| ); | |
| }) | |
| ); | |
| // Take control of all pages immediately | |
| self.clients.claim(); | |
| }); | |
| // Fetch event β serve from cache, fallback to network | |
| self.addEventListener("fetch", event => { | |
| const { request } = event; | |
| const url = new URL(request.url); | |
| // Only handle same-origin requests and GET requests | |
| if (url.origin !== location.origin || request.method !== "GET") { | |
| return; | |
| } | |
| // For RSS feeds (proxied requests), always go to network | |
| if ( | |
| url.pathname.includes("workers.dev") || | |
| url.pathname.includes("allorigins") || | |
| url.pathname.includes("corsproxy") | |
| ) { | |
| return; | |
| } | |
| // For static assets, use cache-first strategy | |
| event.respondWith( | |
| caches.match(request).then(cachedResponse => { | |
| if (cachedResponse) { | |
| // Return cached version, but also fetch updated version in background | |
| event.waitUntil( | |
| fetch(request) | |
| .then(networkResponse => { | |
| if (networkResponse && networkResponse.status === 200) { | |
| caches.open(CACHE_NAME).then(cache => { | |
| cache.put(request, networkResponse); | |
| }); | |
| } | |
| }) | |
| .catch(() => { | |
| // Network failed, that's okay, we have cache | |
| }) | |
| ); | |
| return cachedResponse; | |
| } | |
| // Not in cache, fetch from network | |
| return fetch(request) | |
| .then(networkResponse => { | |
| // Cache successful responses | |
| if (networkResponse && networkResponse.status === 200) { | |
| const responseClone = networkResponse.clone(); | |
| caches.open(CACHE_NAME).then(cache => { | |
| cache.put(request, responseClone); | |
| }); | |
| } | |
| return networkResponse; | |
| }) | |
| .catch(() => { | |
| // Network failed and not in cache | |
| // For HTML requests, show offline page | |
| if (request.headers.get("Accept")?.includes("text/html")) { | |
| return new Response( | |
| `<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Offline β Ivy's RSS Hub</title> | |
| <style> | |
| body { font-family: system-ui; background: #0f0f0f; color: #f5f5f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; text-align: center; } | |
| .container { padding: 2rem; } | |
| h1 { color: #10b981; } | |
| p { color: #a3a3a3; } | |
| button { background: #10b981; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 1rem; margin-top: 1rem; } | |
| button:hover { background: #059669; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>πΏ You're Offline</h1> | |
| <p>Ivy's RSS Hub needs an internet connection to fetch feeds.</p> | |
| <p>Your cached articles are still available in IndexedDB.</p> | |
| <button onclick="location.reload()">Try Again</button> | |
| </div> | |
| </body> | |
| </html>`, | |
| { headers: { "Content-Type": "text/html" } } | |
| ); | |
| } | |
| }); | |
| }) | |
| ); | |
| }); | |
| // Handle messages from main app | |
| self.addEventListener("message", event => { | |
| if (event.data === "skipWaiting") { | |
| self.skipWaiting(); | |
| } | |
| }); | |