mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
Add background pre-caching for full offline support
- Service worker now pre-caches ~160 pages in background after install - Includes all resource pages, topics, study guides, stories, book pages - Caches in batches of 5 with 1-second delays to avoid overwhelming server - Progress indicator shows "Caching for offline: X%" in bottom-left - Shows "Ready for offline! (X pages)" when complete, then fades out - Bumped cache version to v2 to trigger re-cache Combined with the Bible JSON (~4.7MB), the entire site is now available offline after first visit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+238
-2
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user