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:
1 parent
c5e433cc9c
commit
6f409fc83a
15 files changed
+1664
-211
No files matched your search
@@ -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;
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user