Release Healer Man 0.1.32
This commit is contained in:
1 parent
d3d6aaebdb
commit
6dcb1034e5
102 files changed
+10772
-788
No files matched your search
@@ -2,7 +2,6 @@ import { readFileSync } from "node:fs";
|
||||
import { gzipSync } from "node:zlib";
|
||||
import { runInNewContext } from "node:vm";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { ARENA_STAGES } from "../game/arenaStages";
|
||||
import { BROWSER_CONTENT_CACHE_NAME } from "./contentManager";
|
||||
|
||||
const workerSource = readFileSync(
|
||||
@@ -12,15 +11,22 @@ const workerSource = readFileSync(
|
||||
|
||||
function contentWorker(cached?: Response, bundledFiles: Record<string, unknown> = {}, object?: Response) {
|
||||
const handlers = new Map<string, (event: unknown) => void>();
|
||||
const bootstrapObjects = new Map<string, Response>();
|
||||
const fetch = vi.fn(async (url: RequestInfo | URL) => String(url) === "/content/bootstrap-manifest.json"
|
||||
? Response.json({ files: bundledFiles }) : object?.clone() ?? new Response("bundled arena", { status: 200 }));
|
||||
? Response.json({ files: bundledFiles }) : object?.clone() ?? new Response("bundled dungeon", { status: 200 }));
|
||||
const match = vi.fn(async (url: RequestInfo | URL) => String(url).includes("/content/cache-objects/") ? object?.clone() : cached?.clone());
|
||||
const bootstrapMatch = vi.fn(async (url: RequestInfo | URL) => bootstrapObjects.get(String(url))?.clone());
|
||||
const bootstrapPut = vi.fn(async (url: RequestInfo | URL, response: Response) => {
|
||||
bootstrapObjects.set(String(url), response.clone());
|
||||
});
|
||||
runInNewContext(workerSource, {
|
||||
self: {
|
||||
location: { origin: "https://healer.test" },
|
||||
addEventListener: (type: string, handler: (event: unknown) => void) => handlers.set(type, handler),
|
||||
},
|
||||
caches: { open: async () => ({ match }) },
|
||||
caches: { open: async (name: string) => name === "healer-man-bootstrap-v1"
|
||||
? { match: bootstrapMatch, put: bootstrapPut }
|
||||
: { match } },
|
||||
fetch,
|
||||
URL,
|
||||
Response,
|
||||
@@ -30,6 +36,8 @@ function contentWorker(cached?: Response, bundledFiles: Record<string, unknown>
|
||||
return {
|
||||
fetch,
|
||||
match,
|
||||
bootstrapMatch,
|
||||
bootstrapPut,
|
||||
refresh: () => handlers.get("message")!({ data: { type: "refresh-content-bootstrap" } }),
|
||||
request: (path: string) => {
|
||||
let response: Promise<Response> | undefined;
|
||||
@@ -43,20 +51,28 @@ function contentWorker(cached?: Response, bundledFiles: Record<string, unknown>
|
||||
}
|
||||
|
||||
describe("browser content cache worker", () => {
|
||||
it.each(ARENA_STAGES)("loads bundled $name assets without an optional content install", async (stage) => {
|
||||
const worker = contentWorker();
|
||||
for (const asset of stage.assets) {
|
||||
const response = await worker.request(`${asset.url}?arena-runtime=1`);
|
||||
it("loads starter-dungeon assets without an optional content install", async () => {
|
||||
const paths = [
|
||||
"/assets/game/dungeons/wailing-caverns/visual.glb",
|
||||
"/assets/creatures/wailing-caverns/raptor.glb",
|
||||
];
|
||||
const files = Object.fromEntries(paths.map((path, index) => [
|
||||
path,
|
||||
{ url: `/content/bundled/${String(index + 1).repeat(64)}.glb` },
|
||||
]));
|
||||
const worker = contentWorker(undefined, files);
|
||||
for (const path of paths) {
|
||||
const response = await worker.request(`${path}?starter-runtime=1`);
|
||||
expect(response?.status).toBe(200);
|
||||
expect(await response?.text()).toBe("bundled arena");
|
||||
expect(await response?.text()).toBe("bundled dungeon");
|
||||
}
|
||||
expect(worker.fetch).toHaveBeenCalledTimes(stage.assets.length + 1);
|
||||
expect(worker.fetch).toHaveBeenCalledTimes(paths.length + 1);
|
||||
});
|
||||
|
||||
it("prefers installed arena updates over bundled files, ignoring the runtime query", async () => {
|
||||
const worker = contentWorker(new Response("updated arena"));
|
||||
const response = await worker.request(`${ARENA_STAGES[0].assets[0].url}?arena-runtime=1`);
|
||||
expect(await response?.text()).toBe("updated arena");
|
||||
it("prefers installed dungeon updates over bundled files, ignoring the runtime query", async () => {
|
||||
const worker = contentWorker(new Response("updated dungeon"));
|
||||
const response = await worker.request("/assets/game/dungeons/wailing-caverns/visual.glb?starter-runtime=1");
|
||||
expect(await response?.text()).toBe("updated dungeon");
|
||||
expect(worker.match).toHaveBeenCalledWith(expect.any(Request), { ignoreSearch: true });
|
||||
expect(worker.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -72,6 +88,7 @@ describe("browser content cache worker", () => {
|
||||
|
||||
it("serves only cached same-origin asset requests from the active content cache", () => {
|
||||
expect(workerSource).toContain(`const CONTENT_CACHE_NAME = "${BROWSER_CONTENT_CACHE_NAME}";`);
|
||||
expect(workerSource).toContain('const BOOTSTRAP_CACHE_NAME = "healer-man-bootstrap-v1";');
|
||||
expect(workerSource).toContain('request.method !== "GET"');
|
||||
expect(workerSource).toContain("url.origin !== self.location.origin");
|
||||
expect(workerSource).toContain("MANAGED_ASSET_PREFIXES.some");
|
||||
@@ -79,6 +96,7 @@ describe("browser content cache worker", () => {
|
||||
expect(workerSource).toContain("if (!objectPath) return cached");
|
||||
expect(workerSource).toContain("BUNDLED_ASSET_PATHS.has(url.pathname)");
|
||||
expect(workerSource).toContain("BUNDLED_ASSET_PREFIXES.some");
|
||||
expect(workerSource).not.toContain('"/assets/game/arenas/",');
|
||||
expect(workerSource).not.toContain('"/assets/characters/wow335a-v1/textures/character/human/",');
|
||||
expect(workerSource).toContain('status: 404');
|
||||
expect(workerSource).not.toContain("cached ?? fetch(request)");
|
||||
@@ -93,6 +111,17 @@ describe("browser content cache worker", () => {
|
||||
expect(response?.headers.get("content-type")).toBe("model/gltf-binary");
|
||||
});
|
||||
|
||||
it("persists immutable bootstrap objects after their first use", async () => {
|
||||
const url = "/assets/game/dungeons/wailing-caverns/visual.glb";
|
||||
const objectUrl = `/content/bundled/${"b".repeat(64)}.glb`;
|
||||
const worker = contentWorker(undefined, { [url]: { url: objectUrl } }, new Response("persistent dungeon"));
|
||||
expect(await (await worker.request(url))?.text()).toBe("persistent dungeon");
|
||||
expect(await (await worker.request(url))?.text()).toBe("persistent dungeon");
|
||||
expect(worker.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(worker.bootstrapPut).toHaveBeenCalledTimes(1);
|
||||
expect(worker.bootstrapMatch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("resolves a tiny installed alias to one compressed shared object", async () => {
|
||||
const original = "glTF installed original".repeat(100);
|
||||
const worker = contentWorker(new Response(null, { headers: { "x-healer-man-content-object": "/content/cache-objects/shared.glb" } }), {},
|
||||
|
||||
Reference in new issue
Block a user