diigo-cache: fill from a webkey page over Cap'n Web

Starting a fill no longer POSTs the cache id to /_fill. Instead:

- the created page links the cache's webkey, /cache/#<cache id>: a
  skeleton page, the same for every key, that reads the key from the
  fragment (never sent to the server or in a Referer)
- its script trades the key in-band for a capability over Cap'n Web at
  /api: CacheApi.cache(key) returns a CacheCap facet (fill, status,
  fingerprint, the public companion's fill) and never the DO's wiring
  methods; malformed and unknown keys get one refusal that never echoes
  the key, and each refusal is logged without it
- then calls startFill(): trade and call pipeline into one round trip
- the worker serves capnweb's browser build itself at /_capnweb.js (a
  wrangler Text rule), so no third-party script sits beside the key

The /_fill route and its forms are gone. Tests run the real capnweb
client against the worker (fetch routed to worker.fetch).

Generated-by: Claude Opus 5.5
This commit is contained in:
dckc committed 2026-10-07 04:32:44 -05:00
1 parent fbe2538345
commit 8c221abe6f
9 files changed
+397 -112

No files matched your search

+117 -32
View File
@@ -2,6 +2,8 @@ import { env } from "cloudflare:workers";
import { evictDurableObject, runDurableObjectAlarm, runInDurableObject } from "cloudflare:test";
import { describe, expect, it, vi } from "vitest";
import worker from "../src/index.js";
import { newHttpBatchRpcSession } from "capnweb";
import type { CacheApi } from "../src/api.js";
import { toWebkey } from "../src/webkeys.js";
// goal 1e (capsite checklist): a job that grabs every page. Fake
@@ -117,56 +119,139 @@ describe("fill job (goal 1e)", () => {
});
});
describe("fill routes (goal 1e)", () => {
const call = (path: string, init?: RequestInit) =>
worker.fetch(new Request(`https://diigo-cache.test${path}`, init), env as never);
const post = (body: Record<string, string>) =>
call("/_fill", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
describe("fill via webkey and Cap'n Web (goal 1e)", () => {
const ORIGIN = "https://diigo-cache.test";
const call = (path: string, init?: RequestInit) => worker.fetch(new Request(`${ORIGIN}${path}`, init), env as never);
it("POST /_fill starts the owner's job (cache id in the body) and answers 202 with its status", async () => {
const id = testEnv.CACHE_DO.newUniqueId();
await testEnv.CACHE_DO.get(id).setup({ webappId: "fake-webapp-id" });
const res = await post({ cacheId: toWebkey(id) });
expect(res.status).toBe(202);
expect(await res.json()).toMatchObject({ state: "running" });
});
/** a Cap'n Web batch session whose fetch goes to the worker; counts round trips */
const session = () => {
const trips = { n: 0 };
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation((input, init) => {
trips.n += 1;
return worker.fetch(new Request(input as RequestInfo, init), env as never);
});
const api = newHttpBatchRpcSession<CacheApi>(`${ORIGIN}/api`);
return { api, trips, done: () => fetchSpy.mockRestore() };
};
it("target=public fills the cache's public companion; action=stop stops", async () => {
const wiredRes = await call("/_wire", {
const wireJson = async (mode: "private" | "public") => {
const res = await call("/_wire", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ webappId: "fake-webapp-id", mode: "public" }),
body: JSON.stringify({ webappId: "fake-webapp-id", mode }),
});
const { cacheId } = (await wiredRes.json()) as { cacheId: string };
expect((await post({ cacheId, target: "public" })).status).toBe(202);
const stopped = await post({ cacheId, target: "public", action: "stop" });
expect(await stopped.json()).toMatchObject({ state: "stopped" });
return (await res.json()) as { cacheId: string; username?: string };
};
it("trading the key and starting the fill pipeline into one round trip", async () => {
const { cacheId } = await wireJson("private");
const { api, trips, done } = session();
try {
const status = await api.cache(cacheId).startFill();
expect(status).toMatchObject({ state: "running" });
expect(trips.n).toBe(1);
} finally {
done();
}
});
it("refuses an unknown cache id (404) and never takes the id from the URL", async () => {
expect((await post({ cacheId: toWebkey(testEnv.CACHE_DO.newUniqueId()) })).status).toBe(404);
expect((await call(`/_fill/${toWebkey(testEnv.CACHE_DO.newUniqueId())}`, { method: "POST" })).status).toBe(404);
it("the capability is a facet: fill and status, never the DO's wiring", async () => {
const { cacheId } = await wireJson("private");
const { api, done } = session();
try {
const cap = api.cache(cacheId) as unknown as Record<string, () => Promise<unknown>>;
await expect(cap.setup()).rejects.toThrow();
} finally {
done();
}
});
it("the created page offers a fetch-everything button, the id in a hidden field", async () => {
it("unknown and malformed keys get one refusal that never echoes the key", async () => {
const unknown = toWebkey(testEnv.CACHE_DO.newUniqueId());
for (const key of [unknown, "not-a-key", unknown.slice(0, 26)]) {
const { api, done } = session();
try {
const err = await api.cache(key).fillStatus().then(() => null, (e: unknown) => e);
expect(String(err)).toMatch(/no such cache/);
expect(String(err)).not.toContain(key);
} finally {
done();
}
}
});
it("publicCache() reaches the public companion's fill; refused when never published", async () => {
const pub = await wireJson("public");
const s1 = session();
try {
expect(await s1.api.cache(pub.cacheId).hasPublic()).toBe(true);
} finally {
s1.done();
}
const s2 = session();
try {
expect(await s2.api.cache(pub.cacheId).publicCache().startFill()).toMatchObject({ state: "running" });
} finally {
s2.done();
}
const s3 = session();
try {
expect(await s3.api.cache(pub.cacheId).publicCache().stopFill()).toMatchObject({ state: "stopped" });
} finally {
s3.done();
}
const priv = await wireJson("private");
const s4 = session();
try {
expect(await s4.api.cache(priv.cacheId).hasPublic()).toBe(false);
} finally {
s4.done();
}
// a batch session ends after its round trip: a fresh one per await
const s5 = session();
try {
await expect(s5.api.cache(priv.cacheId).publicCache().fillStatus()).rejects.toThrow(/no public cache/);
} finally {
s5.done();
}
});
it("the webkey page is a skeleton: the same for every key, which it reads from the fragment", async () => {
const res = await call("/cache/");
expect(res.status).toBe(200);
const html = await res.text();
expect(html).toContain("location.hash");
expect(html).toContain('from "/_capnweb.js"');
expect(html).toContain('newHttpBatchRpcSession("/api")');
expect(html).toContain('<meta name="referrer" content="no-referrer">');
});
it("serves capnweb's browser build itself (no third-party script beside the key)", async () => {
const res = await call("/_capnweb.js");
expect(res.headers.get("content-type")).toMatch(/javascript/);
expect(await res.text()).toMatch(/export \{[^}]*newHttpBatchRpcSession/);
});
it("the created page links its webkey (key in the fragment); no cache id rides a form to /_fill", async () => {
const res = await call("/_wire", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ webappId: "fake-webapp-id", mode: "private" }),
});
const html = await res.text();
expect(html).toMatch(/<form method="post" action="\/_fill">/);
expect(html).toMatch(/<input type="hidden" name="cacheId"/);
expect(html).toMatch(/<a href="\/cache\/#[a-z2-7]{52}">/);
expect(html).not.toContain("/_fill");
expect((await call("/_fill", { method: "POST" })).status).toBe(404);
});
it("the public page shows a running fill's progress", async () => {
const wiredRes = await call("/_wire", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ webappId: "fake-webapp-id", mode: "public" }),
});
const { cacheId, username } = (await wiredRes.json()) as { cacheId: string; username: string };
await post({ cacheId, target: "public" });
const { cacheId, username } = await wireJson("public");
const { api, done } = session();
try {
await api.cache(cacheId).publicCache().startFill();
} finally {
done();
}
const html = await (await call(`/public/${username}`)).text();
expect(html).toMatch(/fill(ed|ing)[^<]*\d+ of \d+ pages/);
});