isle.pizza/sw.js
2025-07-19 08:40:13 -07:00

208 lines
7.8 KiB
JavaScript

importScripts('/workbox/workbox-sw.js');
workbox.setConfig({
modulePathPrefix: '/workbox/'
});
const { registerRoute } = workbox.routing;
const { StaleWhileRevalidate, CacheFirst, Strategy } = workbox.strategies;
const { CacheableResponsePlugin } = workbox.cacheableResponse;
const { RangeRequestsPlugin } = workbox.rangeRequests;
const coreAppFiles = [
'/', '/index.html', '/cancel_off.webp', '/cancel_on.webp', '/cdspin.gif',
'/configure_off.webp', '/configure_on.webp', '/favicon.png', '/favicon.svg',
'/free_stuff_off.webp', '/free_stuff_on.webp', '/install_off.webp', '/install_on.webp',
'/island.webp', '/isle.js', '/isle.wasm', '/poster.pdf', '/read_me_off.webp',
'/read_me_on.webp', '/run_game_off.webp', '/run_game_on.webp', '/shark.webp',
'/uninstall_off.webp', '/uninstall_on.webp', '/app.js', '/style.css', '/manifest.json',
'/install.webp', '/install.mp3'
];
const gameFiles = [
"/LEGO/Scripts/CREDITS.SI", "/LEGO/Scripts/INTRO.SI", "/LEGO/Scripts/NOCD.SI", "/LEGO/Scripts/SNDANIM.SI",
"/LEGO/Scripts/Act2/ACT2MAIN.SI", "/LEGO/Scripts/Act3/ACT3.SI", "/LEGO/Scripts/Build/COPTER.SI",
"/LEGO/Scripts/Build/DUNECAR.SI", "/LEGO/Scripts/Build/JETSKI.SI", "/LEGO/Scripts/Build/RACECAR.SI",
"/LEGO/Scripts/Garage/GARAGE.SI", "/LEGO/Scripts/Hospital/HOSPITAL.SI", "/LEGO/Scripts/Infocntr/ELEVBOTT.SI",
"/LEGO/Scripts/Infocntr/HISTBOOK.SI", "/LEGO/Scripts/Infocntr/INFODOOR.SI", "/LEGO/Scripts/Infocntr/INFOMAIN.SI",
"/LEGO/Scripts/Infocntr/INFOSCOR.SI", "/LEGO/Scripts/Infocntr/REGBOOK.SI", "/LEGO/Scripts/Isle/ISLE.SI",
"/LEGO/Scripts/Isle/JUKEBOX.SI", "/LEGO/Scripts/Isle/JUKEBOXW.SI", "/LEGO/Scripts/Police/POLICE.SI",
"/LEGO/Scripts/Race/CARRACE.SI", "/LEGO/Scripts/Race/CARRACER.SI", "/LEGO/Scripts/Race/JETRACE.SI",
"/LEGO/Scripts/Race/JETRACER.SI", "/LEGO/data/WORLD.WDB"
];
const STATIC_CACHE_NAME = 'static-assets-v1';
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE_NAME).then((cache) => {
return cache.addAll(coreAppFiles);
})
);
self.skipWaiting();
});
registerRoute(
({ url }) => coreAppFiles.includes(url.pathname),
new StaleWhileRevalidate({
cacheName: STATIC_CACHE_NAME,
})
);
const rangeRequestsPlugin = new RangeRequestsPlugin();
const normalizePathPlugin = {
cacheKeyWillBeUsed: async ({ request }) => {
const url = new URL(request.url);
const normalizedPath = url.pathname.replace(/\/{2,}/g, '/');
const normalizedUrl = url.origin + normalizedPath;
if (request.url === normalizedUrl) {
return request;
}
return new Request(normalizedUrl, {
headers: request.headers, method: request.method,
credentials: request.credentials, redirect: request.redirect,
referrer: request.referrer, body: request.body,
});
},
};
class LegoCacheStrategy extends Strategy {
async _handle(request, handler) {
const cacheKeyRequest = await normalizePathPlugin.cacheKeyWillBeUsed({ request });
const cachedResponse = await caches.match(cacheKeyRequest);
if (cachedResponse) {
return await rangeRequestsPlugin.cachedResponseWillBeUsed({
request: cacheKeyRequest,
cachedResponse: cachedResponse,
});
}
return handler.fetch(request);
}
}
registerRoute(
({ url }) => url.pathname.startsWith('/LEGO/'),
new LegoCacheStrategy()
);
self.addEventListener('message', (event) => {
if (event.data && event.data.action) {
switch (event.data.action) {
case 'install_language_pack':
installLanguagePack(event.data.language, event.source);
break;
case 'uninstall_language_pack':
uninstallLanguagePack(event.data.language, event.source);
break;
case 'check_cache_status':
checkCacheStatus(event.data.language, event.source);
break;
}
}
});
const getLanguageCacheName = (language) => `game-assets-${language}`;
async function installLanguagePack(language, client) {
const THROTTLE_MS = 100;
const cacheName = getLanguageCacheName(language);
try {
const fileMetadataPromises = gameFiles.map(fileUrl =>
fetch(fileUrl, { method: 'HEAD', headers: { 'Accept-Language': language } })
.then(response => {
if (!response.ok) throw new Error(`Failed to HEAD ${fileUrl}`);
return { url: fileUrl, size: Number(response.headers.get('content-length')) || 0 };
})
);
const fileMetadata = await Promise.all(fileMetadataPromises);
const totalBytesToDownload = fileMetadata.reduce((sum, file) => sum + file.size, 0);
let bytesDownloaded = 0;
let lastProgressUpdate = 0;
const cache = await caches.open(cacheName);
for (const file of fileMetadata) {
const request = new Request(file.url, { headers: { 'Accept-Language': language } });
const response = await fetch(request);
if (!response.ok || !response.body) {
throw new Error(`Failed to fetch ${file.url}`);
}
const [streamForCaching, streamForProgress] = response.body.tee();
const responseToCache = new Response(streamForCaching, response);
const cachePromise = cache.put(request, responseToCache);
const reader = streamForProgress.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
bytesDownloaded += value.length;
const now = Date.now();
if (now - lastProgressUpdate > THROTTLE_MS) {
lastProgressUpdate = now;
client.postMessage({
action: 'install_progress',
progress: (bytesDownloaded / totalBytesToDownload) * 100,
language: language
});
}
}
await cachePromise;
}
client.postMessage({
action: 'install_progress',
progress: 100,
language: language
});
client.postMessage({ action: 'install_complete', success: true, language: language });
} catch (error) {
console.error("Aborting installation due to an error:", error);
await caches.delete(cacheName);
client.postMessage({ action: 'install_failed', language: language });
}
}
async function uninstallLanguagePack(language, client) {
const cacheName = getLanguageCacheName(language);
try {
const deleted = await caches.delete(cacheName);
if (deleted) {
console.log(`Cache ${cacheName} deleted successfully.`);
}
client.postMessage({ action: 'uninstall_complete', success: deleted, language: language });
} catch (error) {
console.error('Error during language pack uninstallation:', error);
client.postMessage({ action: 'uninstall_complete', success: false, language: language, error: error.message });
}
}
async function checkCacheStatus(language, client) {
const cacheName = getLanguageCacheName(language);
const hasCache = await caches.has(cacheName);
client.postMessage({ action: 'cache_status', exists: hasCache, language: language });
}
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName.startsWith('static-assets-') && cacheName !== STATIC_CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
event.waitUntil(self.clients.claim());
});