131 lines
5.7 KiB
JavaScript
131 lines
5.7 KiB
JavaScript
const CONTENT_CACHE_NAME = "healer-man-content-v1";
|
|
const BOOTSTRAP_CACHE_NAME = "healer-man-bootstrap-v1";
|
|
const OBJECT_HEADER = "x-healer-man-content-object";
|
|
const ENCODING_HEADER = "x-healer-man-content-encoding";
|
|
let bundledManifest;
|
|
|
|
function mimeType(path) {
|
|
const extension = path.split(".").pop();
|
|
return ({ glb: "model/gltf-binary", json: "application/json", js: "text/javascript", wasm: "application/wasm",
|
|
ktx2: "image/ktx2", png: "image/png", webp: "image/webp", avif: "image/avif", svg: "image/svg+xml",
|
|
mp3: "audio/mpeg", ogg: "audio/ogg", wav: "audio/wav", webm: "video/webm" })[extension] ?? "application/octet-stream";
|
|
}
|
|
|
|
async function decodedResponse(response, path, encoding) {
|
|
if (encoding !== "gzip") return response;
|
|
const headers = new Headers(response.headers);
|
|
headers.set("content-type", mimeType(path));
|
|
headers.delete("content-length");
|
|
headers.delete("content-encoding");
|
|
headers.delete(ENCODING_HEADER);
|
|
return new Response(response.body.pipeThrough(new DecompressionStream("gzip")), { status: 200, headers });
|
|
}
|
|
|
|
async function bundledAsset(path) {
|
|
if (!bundledManifest) {
|
|
bundledManifest = fetch("/content/bootstrap-manifest.json", { cache: "no-cache" })
|
|
.then((response) => response.ok ? response.json() : null)
|
|
.catch(() => null);
|
|
}
|
|
const manifest = await bundledManifest;
|
|
if (!manifest) { bundledManifest = null; return null; }
|
|
const entry = manifest.files?.[path];
|
|
if (!entry) return null;
|
|
const object = entry.download ?? entry;
|
|
// Older bootstrap catalogs served their original logical paths directly.
|
|
if (!object.url.startsWith("/content/bundled/")) return fetch(path);
|
|
const bootstrapCache = await caches.open(BOOTSTRAP_CACHE_NAME);
|
|
const objectKey = new URL(object.url, self.location.origin).toString();
|
|
let response = await bootstrapCache.match(objectKey);
|
|
if (!response) {
|
|
response = await fetch(object.url);
|
|
if (response.ok) await bootstrapCache.put(objectKey, response.clone());
|
|
}
|
|
if (!response.ok) return response;
|
|
if (entry.download) return decodedResponse(response, path, entry.download.encoding);
|
|
return new Response(response.body, { headers: { "content-type": mimeType(path) } });
|
|
}
|
|
const MANAGED_ASSET_PREFIXES = [
|
|
"/assets/characters/",
|
|
"/assets/creatures/",
|
|
"/assets/equipment/",
|
|
"/assets/game/",
|
|
"/assets/ui/",
|
|
"/basis/",
|
|
"/data/",
|
|
];
|
|
const BUNDLED_ASSET_PREFIXES = [
|
|
"/assets/ui/",
|
|
"/assets/equipment/",
|
|
"/assets/characters/wow335a-v1/manifest.json",
|
|
"/assets/characters/wow335a-v1/human/",
|
|
"/assets/creatures/wailing-caverns/",
|
|
"/assets/game/dungeons/wailing-caverns/",
|
|
];
|
|
const BUNDLED_ASSET_PATHS = new Set([
|
|
"/assets/characters/wow335a-v1/textures/character/human/faciallowerhair00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/facialupperhair00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/female/humanfemalefacelower00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/female/humanfemalefaceupper00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/female/humanfemalenakedpelvisskin00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/female/humanfemalenakedtorsoskin00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/female/humanfemaleskin00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/hair00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/male/humanmalefacelower00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/male/humanmalefaceupper00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/male/humanmalenakedpelvisskin00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/male/humanmaleskin00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/scalplowerhair00_00.webp",
|
|
"/assets/characters/wow335a-v1/textures/character/human/scalpupperhair00_00.webp",
|
|
]);
|
|
|
|
self.addEventListener("install", (event) => {
|
|
event.waitUntil(self.skipWaiting());
|
|
});
|
|
|
|
self.addEventListener("message", (event) => {
|
|
if (event.data?.type === "refresh-content-bootstrap") bundledManifest = null;
|
|
});
|
|
|
|
self.addEventListener("activate", (event) => {
|
|
event.waitUntil((async () => {
|
|
const names = await caches.keys();
|
|
await Promise.all(names
|
|
.filter((name) => name.startsWith(`${CONTENT_CACHE_NAME}-staging-`)
|
|
|| (name.startsWith("healer-man-bootstrap-") && name !== BOOTSTRAP_CACHE_NAME))
|
|
.map((name) => caches.delete(name)));
|
|
await self.clients.claim();
|
|
})());
|
|
});
|
|
|
|
self.addEventListener("fetch", (event) => {
|
|
const { request } = event;
|
|
if (request.method !== "GET") return;
|
|
const url = new URL(request.url);
|
|
if (url.origin !== self.location.origin
|
|
|| !MANAGED_ASSET_PREFIXES.some((prefix) => url.pathname.startsWith(prefix))) return;
|
|
|
|
event.respondWith((async () => {
|
|
const cache = await caches.open(CONTENT_CACHE_NAME);
|
|
const cached = await cache.match(request, { ignoreSearch: true });
|
|
if (cached) {
|
|
const objectPath = cached.headers.get(OBJECT_HEADER);
|
|
if (!objectPath) return cached;
|
|
if (objectPath.startsWith("/content/cache-objects/")) {
|
|
const object = await cache.match(new URL(objectPath, self.location.origin).toString());
|
|
if (object) return decodedResponse(object, url.pathname, object.headers.get(ENCODING_HEADER));
|
|
}
|
|
}
|
|
const bundled = await bundledAsset(url.pathname);
|
|
if (bundled) return bundled;
|
|
if (BUNDLED_ASSET_PATHS.has(url.pathname)
|
|
|| BUNDLED_ASSET_PREFIXES.some((prefix) => url.pathname.startsWith(prefix))) {
|
|
return fetch(request);
|
|
}
|
|
return new Response("Optional content is not installed.", {
|
|
status: 404,
|
|
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
});
|
|
})());
|
|
});
|