Files
captoys/webapp/README.md
T
dckc 10414c2ce6 capsite/webapp: rotate the cookie in place
The cookie moves out of the immutable init into a MapStore (key shape
"cookie", value M.string()) in the instance's DO storage. Handles get
its read-only view via the new WebApp.withHeaderMap, which reads at
every send, so a rotation reaches handles already out and the webapp
id, and with it the cache's wiring, stays put (checklist goal 6).

Whoever holds the id can rotate, by RPC (WebAppFactory.cookies(id)) or
POST /at/<id>/cookie; the diigo account owner revokes by logging out.

Durable state: instances minted earlier keep the cookie in
init.headers. The first handle() moves it to the store and rewrites
init without it, in one transaction. Code from before this commit
would then see no cookie, so rolling back past it needs the reverse
move.

webapp gains a package.json and a vitest workerd pool for the tests.

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00

2.9 KiB

webapp

DO-backed fluent web client factory: site-agnostic. Deploy once; instances are attenuated handles whose ids are bearer capabilities — anyone holding the id string can drive the instance; nobody without it can. TODO: this proxy-page flow (see diigo-cache CONTRIBUTING) vs running webapp as the primary worker (wrangler dev -c ../webapp/wrangler.jsonc -c ../diigo-cache/wrangler.jsonc) so its own port serves the mint form directly; reconsider when the UI grows past a form or two.

HTTP surface

  • GET / — the mint form. Prefill via query string: ?url=…&headers=<urlencoded JSON>&msPerCall=…&title=…; secret header keys (cookie, authorization, proxy-authorization) render blank — paste them at mint time; never echoed back. A dedicated cookie field is sugar for the cookie header (if the JSON also has one, they must agree).
  • GET /form?render=1&… — the same form for a proxying worker (diigo-cache's /_connect): the action is left as an ACTION placeholder for the proxy to point at its own relay.
  • POST / — mint. Form-encoded (that page) or JSON {url, headers, msPerCall} (service-style). Responds {id} (and x-webapp-id). The id is not logged: possession is authority.
  • POST /at/<id> — drive an instance by capability: JSON {path?, query?, method?, body?} → the fluent chain → {status, text} (or {error}; messages carry no header/param values).
  • POST /at/<id>/cookie — rotate the instance's cookie in place: JSON {cookie} → {ok: true}. The id that drives an instance can also replace its cookie; the account owner revokes by logging out.

RPC surface (same-account workers; binding entrypoint: "WebAppFactory")

  • fetch(request) — the HTTP surface above, over the binding
  • newWebApp({url, headers?, msPerCall?}) → {id, app} — the fluent stub; promise pipelining makes a whole pathjoin().query().get() chain a single round trip
  • at(id) — the fluent stub for a previously-minted instance
  • cookies(id) — the instance's cookie store (a MapStore from the capsite lib with key shape "cookie"): set("cookie", …) rotates it under every handle already out

An instance is fixed at creation except for its cookie, which lives in a store rather than in init; every send reads it. Instances minted before the store held their cookie in init.headers; the first handle() moves it over in one transaction.

Run

pnpm test          # cookie store + rotation (vitest, workerd pool)
npx wrangler dev   # → its own ready line (usually :8787 with the
                   # two-config diigo-cache run; standalone, :8788)

Status: mint + drive exercised end-to-end in the combined dev run (through diigo-cache's create flow, 2026-10-07); not yet deployed. See ../capsite-checklist.md — the single source for run/connect/deploy instructions.