diigo-cache: bookmark cache in Durable Objects (goal 1c)

The dump worker becomes a page cache: a hit is served from DO SQLite
storage with no diigo call; a miss asks the webapp instance in the
cache's wiring and etches the page.

- two independent caches, one behavior (PagedCache): DiigoCache keeps
  every item; PublicCache keeps shared items only, dropping private
  rows before they are stored; served at /public/<username> (HTML page
  form, or JSON)
- create flow on one port: /_connect relays webapp's mint form, /_mint
  proxies the mint, /_wire wires a private or public cache; /_publish
  adds a public cache later
- ids are webkeys: kept out of URLs (POST bodies), shown and logged
  only as a hash fingerprint; saved to the password manager under the
  holder's petname (never sent to the server)
- items keyed on diigo's link_id; username from u_name; unkeyable
  items and every failure path are logged; each page read logs hit/miss
- tests run the real worker + DOs in the vitest pool against a fake
  webapp (tests/fake-webapp.js)

Generated-by: opencode (GLM)
Generated-by: Claude Opus 5.5
This commit is contained in:
dckc committed 2026-10-07 04:19:54 -05:00
1 parent c5e433cc9c
commit 6f409fc83a
15 files changed
+1664 -211

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
/**
* @file the password-manager save — ids reach the browser's storage only by
* this gesture-gated path (or copy), never localStorage/IndexedDB, which
* are ambient and XSS-reachable. It runs in the *browser*: pages embed it
* via `pmSave.toString()`, so it must be self-contained (no imports, no
* closure, no inner named functions — wrangler's keepNames would wrap
* those in an `__name` helper the browser doesn't have). Its powers
* (the petname field, the credential store, the console) are passed in,
* so tests can stand in for the browser.
*/
/** the browser powers pmSave uses */
export type PmPowers = {
petname: { value: string };
credentials?: { store(credential: unknown): Promise<unknown> };
PasswordCredential?: new (init: { id: string; password: string }) => unknown;
log: Pick<Console, "info" | "warn" | "error">;
};
/** the button pmSave reports on */
export type PmButton = { textContent: string | null; disabled: boolean };
/**
* gesture-gated save of `value` under the holder's petname (the PM
* username).
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/store
*/
export function pmSave(btn: PmButton, value: string, powers: PmPowers): Promise<void> {
const label = powers.petname.value.trim();
if (!label) {
btn.textContent = "give it a petname first";
return Promise.resolve();
}
btn.disabled = true;
if (!powers.PasswordCredential || !powers.credentials) {
powers.log.warn({ event: "pm-save", outcome: "unsupported", detail: "no PasswordCredential / navigator.credentials" });
btn.textContent = "browser has no credential store — copy instead";
btn.disabled = false;
return Promise.resolve();
}
return powers.credentials
.store(new powers.PasswordCredential({ id: label, password: value }))
.then(() => {
// store() resolves with undefined per spec, whether the holder
// accepted the browser's prompt or not (or no prompt showed):
// we can only say it was offered — the PM's own UI is the truth
powers.log.info({ event: "pm-save", outcome: "offered", label });
btn.textContent = "offered to the password manager ✓ (see its prompt)";
btn.disabled = false;
})
.catch((err) => {
powers.log.error({ event: "pm-save", error: String(err) });
btn.textContent = "save failed — copy instead";
btn.disabled = false;
});
}