Implement comprehensive PWA with offline Bible reading capabilities
- Add Web App Manifest with app metadata, icons, and installation support - Create Service Worker with intelligent caching strategies for Bible content, static assets, and dynamic content - Implement IndexedDB-based offline storage system for Bible versions, books, chapters, and verses - Add offline download manager component for browsing and downloading Bible versions - Create offline Bible reader component for seamless offline reading experience - Integrate PWA install prompt with platform-specific instructions - Add offline reading interface to existing Bible reader with download buttons - Create dedicated offline page with tabbed interface for reading and downloading - Add PWA and offline-related translations for English and Romanian locales - Implement background sync for Bible downloads and cache management - Add storage usage monitoring and management utilities - Ensure SSR-safe implementation with dynamic imports for client-side components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
BIN
public/icon-192.png
Normal file
BIN
public/icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
public/icon-512.png
Normal file
BIN
public/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 185 KiB |
91
public/manifest.json
Normal file
91
public/manifest.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "Biblical Guide - Ghidul Biblic",
|
||||
"short_name": "Biblical Guide",
|
||||
"description": "Complete Bible study app with offline reading, AI chat, and multilingual support",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#009688",
|
||||
"orientation": "portrait-primary",
|
||||
"scope": "/",
|
||||
"categories": ["books", "education", "lifestyle"],
|
||||
"lang": "en",
|
||||
"dir": "ltr",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/biblical-guide-og-image.png",
|
||||
"sizes": "1200x630",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Read Bible",
|
||||
"short_name": "Bible",
|
||||
"description": "Start reading the Bible",
|
||||
"url": "/bible",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Daily Verse",
|
||||
"short_name": "Verse",
|
||||
"description": "Read today's verse",
|
||||
"url": "/?daily=true",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AI Chat",
|
||||
"short_name": "Chat",
|
||||
"description": "Ask biblical questions",
|
||||
"url": "/?chat=true",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Prayer Requests",
|
||||
"short_name": "Prayers",
|
||||
"description": "View prayer requests",
|
||||
"url": "/prayers",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"features": [
|
||||
"offline-reading",
|
||||
"background-sync",
|
||||
"push-notifications"
|
||||
],
|
||||
"related_applications": [],
|
||||
"prefer_related_applications": false
|
||||
}
|
||||
152
public/offline.html
Normal file
152
public/offline.html
Normal file
@@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Offline - Biblical Guide</title>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #009688 0%, #00796B 100%);
|
||||
color: white;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
.container {
|
||||
max-width: 400px;
|
||||
padding: 2rem;
|
||||
}
|
||||
.icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
p {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
button {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
button:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.status {
|
||||
margin-top: 1rem;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.offline-content {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.verse {
|
||||
font-style: italic;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.reference {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="icon">📖</div>
|
||||
<h1>You're Offline</h1>
|
||||
<p>No internet connection detected. You can still access your downloaded Bible versions and continue reading.</p>
|
||||
|
||||
<div class="actions">
|
||||
<button onclick="checkConnection()">Check Connection</button>
|
||||
<button onclick="goToHomePage()">Continue Offline</button>
|
||||
<button onclick="viewDownloads()">View Downloads</button>
|
||||
</div>
|
||||
|
||||
<div class="offline-content">
|
||||
<div class="verse">
|
||||
"For the word of God is alive and active. Sharper than any double-edged sword..."
|
||||
</div>
|
||||
<div class="reference">Hebrews 4:12</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">
|
||||
Offline mode - Limited functionality available
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Check if we're online
|
||||
function updateStatus() {
|
||||
const status = document.getElementById('status');
|
||||
if (navigator.onLine) {
|
||||
status.textContent = 'Connection restored! You can refresh the page.';
|
||||
status.style.color = '#4CAF50';
|
||||
} else {
|
||||
status.textContent = 'Offline mode - Limited functionality available';
|
||||
status.style.color = 'rgba(255, 255, 255, 0.7)';
|
||||
}
|
||||
}
|
||||
|
||||
function checkConnection() {
|
||||
updateStatus();
|
||||
if (navigator.onLine) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function goToHomePage() {
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
function viewDownloads() {
|
||||
window.location.href = '/bible?offline=true';
|
||||
}
|
||||
|
||||
// Listen for online/offline events
|
||||
window.addEventListener('online', updateStatus);
|
||||
window.addEventListener('offline', updateStatus);
|
||||
|
||||
// Initial status check
|
||||
updateStatus();
|
||||
|
||||
// Auto-refresh when connection is restored
|
||||
window.addEventListener('online', () => {
|
||||
setTimeout(() => {
|
||||
if (navigator.onLine) {
|
||||
window.location.reload();
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
432
public/sw.js
Normal file
432
public/sw.js
Normal file
@@ -0,0 +1,432 @@
|
||||
// Biblical Guide Service Worker
|
||||
const CACHE_NAME = 'biblical-guide-v1.0.0';
|
||||
const STATIC_CACHE = 'biblical-guide-static-v1.0.0';
|
||||
const DYNAMIC_CACHE = 'biblical-guide-dynamic-v1.0.0';
|
||||
const BIBLE_CACHE = 'biblical-guide-bible-v1.0.0';
|
||||
|
||||
// Static resources that should be cached immediately
|
||||
const STATIC_ASSETS = [
|
||||
'/',
|
||||
'/manifest.json',
|
||||
'/icon-192.png',
|
||||
'/icon-512.png',
|
||||
'/biblical-guide-og-image.png',
|
||||
'/offline.html', // We'll create this fallback page
|
||||
];
|
||||
|
||||
// Bible API endpoints that should be cached
|
||||
const BIBLE_API_PATTERNS = [
|
||||
/\/api\/bible\/versions/,
|
||||
/\/api\/bible\/books/,
|
||||
/\/api\/bible\/chapter/,
|
||||
/\/api\/bible\/verses/,
|
||||
];
|
||||
|
||||
// Dynamic content that should be cached with network-first strategy
|
||||
const DYNAMIC_PATTERNS = [
|
||||
/\/api\/daily-verse/,
|
||||
/\/api\/search/,
|
||||
];
|
||||
|
||||
// Install event - cache static assets
|
||||
self.addEventListener('install', (event) => {
|
||||
console.log('[SW] Installing service worker...');
|
||||
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
caches.open(STATIC_CACHE).then((cache) => {
|
||||
console.log('[SW] Caching static assets');
|
||||
return cache.addAll(STATIC_ASSETS.map(url => new Request(url, {credentials: 'same-origin'})));
|
||||
}),
|
||||
caches.open(BIBLE_CACHE).then(() => {
|
||||
console.log('[SW] Bible cache initialized');
|
||||
})
|
||||
]).then(() => {
|
||||
console.log('[SW] Installation complete');
|
||||
// Skip waiting to activate immediately
|
||||
return self.skipWaiting();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', (event) => {
|
||||
console.log('[SW] Activating service worker...');
|
||||
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
// Clean up old caches
|
||||
caches.keys().then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames.map((cacheName) => {
|
||||
if (cacheName !== STATIC_CACHE &&
|
||||
cacheName !== DYNAMIC_CACHE &&
|
||||
cacheName !== BIBLE_CACHE &&
|
||||
cacheName !== CACHE_NAME) {
|
||||
console.log('[SW] Deleting old cache:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
}),
|
||||
// Take control of all pages immediately
|
||||
self.clients.claim()
|
||||
]).then(() => {
|
||||
console.log('[SW] Activation complete');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event - handle all network requests
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const { request } = event;
|
||||
const { url, method } = request;
|
||||
|
||||
// Only handle GET requests
|
||||
if (method !== 'GET') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Bible content downloads (store in Bible cache)
|
||||
if (BIBLE_API_PATTERNS.some(pattern => pattern.test(url))) {
|
||||
event.respondWith(handleBibleRequest(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle dynamic content (network-first with cache fallback)
|
||||
if (DYNAMIC_PATTERNS.some(pattern => pattern.test(url))) {
|
||||
event.respondWith(handleDynamicRequest(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle static assets (cache-first)
|
||||
if (isStaticAsset(url)) {
|
||||
event.respondWith(handleStaticRequest(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle navigation requests (HTML pages)
|
||||
if (request.mode === 'navigate') {
|
||||
event.respondWith(handleNavigationRequest(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: network-first for everything else
|
||||
event.respondWith(handleDefaultRequest(request));
|
||||
});
|
||||
|
||||
// Bible content strategy: Cache-first with network update
|
||||
async function handleBibleRequest(request) {
|
||||
const cache = await caches.open(BIBLE_CACHE);
|
||||
const cached = await cache.match(request);
|
||||
|
||||
if (cached) {
|
||||
console.log('[SW] Serving Bible content from cache:', request.url);
|
||||
// Update cache in background
|
||||
updateBibleCache(request, cache);
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[SW] Fetching Bible content from network:', request.url);
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
// Clone before caching
|
||||
const responseClone = response.clone();
|
||||
await cache.put(request, responseClone);
|
||||
console.log('[SW] Bible content cached:', request.url);
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[SW] Bible content fetch failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Update Bible cache in background
|
||||
async function updateBibleCache(request, cache) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
await cache.put(request, response.clone());
|
||||
console.log('[SW] Bible cache updated:', request.url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[SW] Background Bible cache update failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic content strategy: Network-first with cache fallback
|
||||
async function handleDynamicRequest(request) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE);
|
||||
|
||||
try {
|
||||
console.log('[SW] Fetching dynamic content from network:', request.url);
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
// Cache successful responses
|
||||
await cache.put(request, response.clone());
|
||||
console.log('[SW] Dynamic content cached:', request.url);
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log('[SW] Network failed, checking cache for:', request.url);
|
||||
const cached = await cache.match(request);
|
||||
if (cached) {
|
||||
console.log('[SW] Serving dynamic content from cache:', request.url);
|
||||
return cached;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Static assets strategy: Cache-first
|
||||
async function handleStaticRequest(request) {
|
||||
const cache = await caches.open(STATIC_CACHE);
|
||||
const cached = await cache.match(request);
|
||||
|
||||
if (cached) {
|
||||
console.log('[SW] Serving static asset from cache:', request.url);
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[SW] Fetching static asset from network:', request.url);
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
await cache.put(request, response.clone());
|
||||
console.log('[SW] Static asset cached:', request.url);
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[SW] Static asset fetch failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation strategy: Network-first with offline fallback
|
||||
async function handleNavigationRequest(request) {
|
||||
try {
|
||||
console.log('[SW] Fetching navigation from network:', request.url);
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
// Cache successful navigation responses
|
||||
const cache = await caches.open(DYNAMIC_CACHE);
|
||||
await cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log('[SW] Navigation network failed, checking cache:', request.url);
|
||||
const cache = await caches.open(DYNAMIC_CACHE);
|
||||
const cached = await cache.match(request);
|
||||
|
||||
if (cached) {
|
||||
console.log('[SW] Serving navigation from cache:', request.url);
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Return offline fallback page
|
||||
console.log('[SW] Serving offline fallback page');
|
||||
const offlineCache = await caches.open(STATIC_CACHE);
|
||||
const offlinePage = await offlineCache.match('/offline.html');
|
||||
return offlinePage || new Response('Offline - Please check your connection', {
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable',
|
||||
headers: { 'Content-Type': 'text/plain' }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Default strategy: Network-first
|
||||
async function handleDefaultRequest(request) {
|
||||
try {
|
||||
return await fetch(request);
|
||||
} catch (error) {
|
||||
console.log('[SW] Default request failed:', request.url);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if URL is a static asset
|
||||
function isStaticAsset(url) {
|
||||
return url.includes('/_next/') ||
|
||||
url.includes('/icon-') ||
|
||||
url.includes('/manifest.json') ||
|
||||
url.includes('/biblical-guide-og-image.png') ||
|
||||
url.includes('.css') ||
|
||||
url.includes('.js') ||
|
||||
url.includes('.woff') ||
|
||||
url.includes('.woff2');
|
||||
}
|
||||
|
||||
// Background sync for Bible downloads
|
||||
self.addEventListener('sync', (event) => {
|
||||
console.log('[SW] Background sync triggered:', event.tag);
|
||||
|
||||
if (event.tag === 'download-bible-version') {
|
||||
event.waitUntil(handleBibleDownloadSync());
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Bible version download in background
|
||||
async function handleBibleDownloadSync() {
|
||||
try {
|
||||
console.log('[SW] Processing background Bible download');
|
||||
// Get pending downloads from IndexedDB
|
||||
const pendingDownloads = await getPendingBibleDownloads();
|
||||
|
||||
for (const download of pendingDownloads) {
|
||||
await downloadBibleVersion(download);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[SW] Background Bible download failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get pending downloads from IndexedDB
|
||||
async function getPendingBibleDownloads() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('BibleStorage', 1);
|
||||
|
||||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
const transaction = db.transaction(['downloads'], 'readonly');
|
||||
const store = transaction.objectStore('downloads');
|
||||
const getAllRequest = store.getAll();
|
||||
|
||||
getAllRequest.onsuccess = () => {
|
||||
resolve(getAllRequest.result.filter(d => d.status === 'pending'));
|
||||
};
|
||||
|
||||
getAllRequest.onerror = () => reject(getAllRequest.error);
|
||||
};
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
// Download a Bible version
|
||||
async function downloadBibleVersion(download) {
|
||||
try {
|
||||
console.log('[SW] Downloading Bible version:', download.versionId);
|
||||
|
||||
// Download books list
|
||||
const booksResponse = await fetch(`/api/bible/books?version=${download.versionId}`);
|
||||
if (!booksResponse.ok) throw new Error('Failed to fetch books');
|
||||
|
||||
const books = await booksResponse.json();
|
||||
|
||||
// Download each chapter
|
||||
for (const book of books.books) {
|
||||
for (let chapter = 1; chapter <= book.chaptersCount; chapter++) {
|
||||
const chapterUrl = `/api/bible/chapter?book=${book.id}&chapter=${chapter}&version=${download.versionId}`;
|
||||
await fetch(chapterUrl); // This will be cached by the fetch handler
|
||||
}
|
||||
}
|
||||
|
||||
// Mark download as complete
|
||||
await updateDownloadStatus(download.versionId, 'completed');
|
||||
|
||||
console.log('[SW] Bible version download completed:', download.versionId);
|
||||
} catch (error) {
|
||||
console.error('[SW] Bible version download failed:', error);
|
||||
await updateDownloadStatus(download.versionId, 'failed');
|
||||
}
|
||||
}
|
||||
|
||||
// Update download status in IndexedDB
|
||||
async function updateDownloadStatus(versionId, status) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('BibleStorage', 1);
|
||||
|
||||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
const transaction = db.transaction(['downloads'], 'readwrite');
|
||||
const store = transaction.objectStore('downloads');
|
||||
|
||||
const getRequest = store.get(versionId);
|
||||
getRequest.onsuccess = () => {
|
||||
const download = getRequest.result;
|
||||
if (download) {
|
||||
download.status = status;
|
||||
download.updatedAt = new Date().toISOString();
|
||||
const putRequest = store.put(download);
|
||||
putRequest.onsuccess = () => resolve();
|
||||
putRequest.onerror = () => reject(putRequest.error);
|
||||
} else {
|
||||
resolve(); // Download not found, ignore
|
||||
}
|
||||
};
|
||||
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
};
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
// Message handling for communication with the main thread
|
||||
self.addEventListener('message', (event) => {
|
||||
console.log('[SW] Message received:', event.data);
|
||||
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
|
||||
if (event.data && event.data.type === 'CLEAR_CACHE') {
|
||||
clearAllCaches().then(() => {
|
||||
event.ports[0].postMessage({success: true});
|
||||
});
|
||||
}
|
||||
|
||||
if (event.data && event.data.type === 'CACHE_BIBLE_VERSION') {
|
||||
const { versionId } = event.data;
|
||||
cacheBibleVersion(versionId).then(() => {
|
||||
event.ports[0].postMessage({success: true, versionId});
|
||||
}).catch((error) => {
|
||||
event.ports[0].postMessage({success: false, error: error.message, versionId});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Clear all caches
|
||||
async function clearAllCaches() {
|
||||
const cacheNames = await caches.keys();
|
||||
await Promise.all(cacheNames.map(name => caches.delete(name)));
|
||||
console.log('[SW] All caches cleared');
|
||||
}
|
||||
|
||||
// Cache a specific Bible version
|
||||
async function cacheBibleVersion(versionId) {
|
||||
console.log('[SW] Caching Bible version:', versionId);
|
||||
|
||||
try {
|
||||
// Download and cache all content for this version
|
||||
const booksResponse = await fetch(`/api/bible/books?version=${versionId}`);
|
||||
if (!booksResponse.ok) throw new Error('Failed to fetch books');
|
||||
|
||||
const books = await booksResponse.json();
|
||||
const cache = await caches.open(BIBLE_CACHE);
|
||||
|
||||
// Cache books list
|
||||
await cache.put(`/api/bible/books?version=${versionId}`, booksResponse.clone());
|
||||
|
||||
// Cache each chapter
|
||||
for (const book of books.books) {
|
||||
for (let chapter = 1; chapter <= book.chaptersCount; chapter++) {
|
||||
const chapterUrl = `/api/bible/chapter?book=${book.id}&chapter=${chapter}&version=${versionId}`;
|
||||
const chapterResponse = await fetch(chapterUrl);
|
||||
if (chapterResponse.ok) {
|
||||
await cache.put(chapterUrl, chapterResponse.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[SW] Bible version cached successfully:', versionId);
|
||||
} catch (error) {
|
||||
console.error('[SW] Failed to cache Bible version:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[SW] Service worker script loaded');
|
||||
Reference in New Issue
Block a user