ivy-rss-hub / service-worker.js
ijohn07's picture
Upload 16 files
790eee5 verified
/* ============================================
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();
}
});