diff --git a/kjvstudy_org/static/sw.js b/kjvstudy_org/static/sw.js index 6d54cb9..461cd0f 100644 --- a/kjvstudy_org/static/sw.js +++ b/kjvstudy_org/static/sw.js @@ -1,5 +1,5 @@ // KJV Study Service Worker - Offline Bible Access -const CACHE_VERSION = 'v1'; +const CACHE_VERSION = 'v2'; const STATIC_CACHE = 'kjvstudy-static-' + CACHE_VERSION; const BIBLE_CACHE = 'kjvstudy-bible-' + CACHE_VERSION; const PAGE_CACHE = 'kjvstudy-pages-' + CACHE_VERSION; @@ -16,6 +16,169 @@ const STATIC_ASSETS = [ '/books' ]; +// All pages to pre-cache in background (185 pages) +const PAGES_TO_CACHE = [ + "/angels", + "/apostles", + "/biblical-timeline", + "/book/1 Chronicles", + "/book/1 Corinthians", + "/book/1 John", + "/book/1 Kings", + "/book/1 Peter", + "/book/1 Samuel", + "/book/1 Thessalonians", + "/book/1 Timothy", + "/book/2 Chronicles", + "/book/2 Corinthians", + "/book/2 John", + "/book/2 Kings", + "/book/2 Peter", + "/book/2 Samuel", + "/book/2 Thessalonians", + "/book/2 Timothy", + "/book/3 John", + "/book/Acts", + "/book/Amos", + "/book/Colossians", + "/book/Daniel", + "/book/Deuteronomy", + "/book/Ecclesiastes", + "/book/Ephesians", + "/book/Esther", + "/book/Exodus", + "/book/Ezekiel", + "/book/Ezra", + "/book/Galatians", + "/book/Genesis", + "/book/Habakkuk", + "/book/Haggai", + "/book/Hebrews", + "/book/Hosea", + "/book/Isaiah", + "/book/James", + "/book/Jeremiah", + "/book/Job", + "/book/Joel", + "/book/John", + "/book/Jonah", + "/book/Joshua", + "/book/Jude", + "/book/Judges", + "/book/Lamentations", + "/book/Leviticus", + "/book/Luke", + "/book/Malachi", + "/book/Mark", + "/book/Matthew", + "/book/Micah", + "/book/Nahum", + "/book/Nehemiah", + "/book/Numbers", + "/book/Obadiah", + "/book/Philemon", + "/book/Philippians", + "/book/Proverbs", + "/book/Psalms", + "/book/Revelation", + "/book/Romans", + "/book/Ruth", + "/book/Song of Solomon", + "/book/Titus", + "/book/Zechariah", + "/book/Zephaniah", + "/covenants", + "/family-tree", + "/festivals", + "/fruits-of-spirit", + "/interlinear", + "/names-of-god", + "/parables", + "/prophets", + "/reading-plans", + "/reading-plans/chronological", + "/reading-plans/gospels-acts-30", + "/reading-plans/nt-90-days", + "/reading-plans/one-year", + "/reading-plans/paul-epistles-30", + "/reading-plans/psalms-proverbs", + "/resources", + "/search", + "/stories", + "/stories/kids", + "/strongs", + "/strongs/greek", + "/strongs/hebrew", + "/study-guides", + "/study-guides/attributes-of-god", + "/study-guides/biblical-marriage", + "/study-guides/christian-living", + "/study-guides/covenant-theology", + "/study-guides/doctrine-of-scripture", + "/study-guides/faith-and-works", + "/study-guides/fruits-spirit", + "/study-guides/gods-love", + "/study-guides/gospel", + "/study-guides/gospel-in-ot", + "/study-guides/heaven-eternity", + "/study-guides/hope-comfort", + "/study-guides/law-and-christian", + "/study-guides/money-stewardship", + "/study-guides/new-believer", + "/study-guides/prayer-faith", + "/study-guides/problem-of-evil", + "/study-guides/raising-children", + "/study-guides/resurrection", + "/study-guides/salvation", + "/study-guides/scarlet-thread", + "/study-guides/sovereignty-of-god", + "/study-guides/spirits-demons", + "/study-guides/trinity", + "/study-guides/wisdom-guidance", + "/topics", + "/topics/anxiety", + "/topics/baptism", + "/topics/communion", + "/topics/contentment", + "/topics/faith", + "/topics/fasting", + "/topics/forgiveness", + "/topics/generosity", + "/topics/grace", + "/topics/heaven", + "/topics/holiness", + "/topics/holy-spirit", + "/topics/hope", + "/topics/humility", + "/topics/joy", + "/topics/judgment", + "/topics/love", + "/topics/marriage", + "/topics/mental-health", + "/topics/obedience", + "/topics/parenting", + "/topics/patience", + "/topics/peace", + "/topics/prayer", + "/topics/repentance", + "/topics/rest", + "/topics/salvation", + "/topics/service", + "/topics/spiritual-warfare", + "/topics/stewardship", + "/topics/suffering", + "/topics/temptation", + "/topics/the-church", + "/topics/wisdom", + "/topics/work", + "/topics/worship", + "/twelve-apostles", + "/verse-of-the-day", + "/women", + "/christology", + "/blood-in-scripture" +]; + // Install event - cache static assets self.addEventListener('install', (event) => { console.log('[SW] Installing service worker...'); @@ -35,7 +198,7 @@ self.addEventListener('install', (event) => { ); }); -// Activate event - clean up old caches +// Activate event - clean up old caches and start background caching self.addEventListener('activate', (event) => { console.log('[SW] Activating service worker...'); event.waitUntil( @@ -58,9 +221,82 @@ self.addEventListener('activate', (event) => { console.log('[SW] Service worker activated'); return self.clients.claim(); }) + .then(() => { + // Start background pre-caching after activation + startBackgroundCaching(); + }) ); }); +// Background pre-caching - cache all pages gradually +let cachingInProgress = false; +let cachedCount = 0; + +async function startBackgroundCaching() { + if (cachingInProgress) return; + cachingInProgress = true; + + console.log('[SW] Starting background pre-cache of', PAGES_TO_CACHE.length, 'pages...'); + + const cache = await caches.open(PAGE_CACHE); + + // Check which pages are already cached + const uncachedPages = []; + for (const url of PAGES_TO_CACHE) { + const cached = await cache.match(url); + if (!cached) { + uncachedPages.push(url); + } + } + + console.log('[SW] Need to cache', uncachedPages.length, 'pages'); + + // Cache pages in batches with delay to avoid overwhelming the server + const BATCH_SIZE = 5; + const BATCH_DELAY = 1000; // 1 second between batches + + for (let i = 0; i < uncachedPages.length; i += BATCH_SIZE) { + const batch = uncachedPages.slice(i, i + BATCH_SIZE); + + await Promise.all( + batch.map(async (url) => { + try { + const response = await fetch(url); + if (response.ok) { + await cache.put(url, response); + cachedCount++; + // Notify clients of progress + notifyClients({ + type: 'CACHE_PROGRESS', + cached: cachedCount, + total: uncachedPages.length + }); + } + } catch (err) { + console.log('[SW] Failed to cache:', url); + } + }) + ); + + // Wait between batches + if (i + BATCH_SIZE < uncachedPages.length) { + await new Promise(resolve => setTimeout(resolve, BATCH_DELAY)); + } + } + + console.log('[SW] Background caching complete!', cachedCount, 'pages cached'); + cachingInProgress = false; + + // Notify clients that caching is complete + notifyClients({ type: 'CACHE_COMPLETE', total: cachedCount }); +} + +// Notify all clients of caching progress +async function notifyClients(message) { + const clients = await self.clients.matchAll(); + clients.forEach(client => client.postMessage(message)); +} + // Fetch event - serve from cache, fallback to network self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); diff --git a/kjvstudy_org/templates/base.html b/kjvstudy_org/templates/base.html index b9afe34..d5f9e16 100644 --- a/kjvstudy_org/templates/base.html +++ b/kjvstudy_org/templates/base.html @@ -2264,6 +2264,45 @@ console.log('[App] Service Worker registration failed:', error); }); }); + + // Listen for cache progress messages from service worker + navigator.serviceWorker.addEventListener('message', function(event) { + var data = event.data; + if (data.type === 'CACHE_PROGRESS') { + showCacheProgress(data.cached, data.total); + } else if (data.type === 'CACHE_COMPLETE') { + showCacheComplete(data.total); + } + }); + } + + // Cache progress indicator + var cacheIndicator = null; + function showCacheProgress(cached, total) { + if (!cacheIndicator) { + cacheIndicator = document.createElement('div'); + cacheIndicator.id = 'cache-indicator'; + cacheIndicator.style.cssText = 'position:fixed;bottom:1rem;left:1rem;padding:0.5rem 1rem;background:#4a7c59;color:white;border-radius:4px;font-size:0.85rem;z-index:9999;box-shadow:0 2px 8px rgba(0,0,0,0.2);transition:opacity 0.3s;'; + document.body.appendChild(cacheIndicator); + } + var pct = Math.round((cached / total) * 100); + cacheIndicator.innerHTML = 'Caching for offline: ' + pct + '%'; + cacheIndicator.style.opacity = '1'; + } + + function showCacheComplete(total) { + if (cacheIndicator) { + cacheIndicator.innerHTML = 'Ready for offline! (' + total + ' pages)'; + setTimeout(function() { + cacheIndicator.style.opacity = '0'; + setTimeout(function() { + if (cacheIndicator && cacheIndicator.parentNode) { + cacheIndicator.parentNode.removeChild(cacheIndicator); + cacheIndicator = null; + } + }, 300); + }, 3000); + } } // Offline/Online indicator