The logic for serving and storing associations between users and apps
README.md

loggedin.fyi Val Town demos #

Two AT Proto client demos that share one login workflow and both talk to the loggedin.fyi Cloudflare Worker.

They must be two origins. Logging into Lightbox heartbeats a DID; Cratewalk can then offer that account as a normal OAuth hint.

flowchart LR
  A[Lightbox] -->|OAuth + heartbeat| W[loggedin.fyi Worker]
  B[Cratewalk] -->|list hinted DIDs| W
  A -->|AT Proto OAuth| P[User PDS]
  B -->|AT Proto OAuth| P
Demo HTTP file Planned subdomain After login
A · Lightbox lightbox.ts lightbox-loggedin.val.run Images from the current feed, plus a feed picker
B · Cratewalk cratewalk.ts cratewalk-loggedin.val.run Posts arrive in Habbo-style boxes on a half-width conveyor

Deploy as two vals #

Copy this folder into two Val Town vals (or remix twice). Mark lightbox.ts as the HTTP trigger in one and cratewalk.ts in the other.

Claim kebab-case subdomains:

  • lightbox-loggedin
  • cratewalk-loggedin

If a name is taken, add a random 1–100 suffix.

Environment variables #

Each val needs a cookie secret of at least 32 characters:

👉 Add COOKIE_SECRET here: https://www.val.town/x/HANDLE/VAL_NAME/environment-variables?key=COOKIE_SECRET

Optional:

👉 Add PUBLIC_BASE_URL here: https://www.val.town/x/HANDLE/VAL_NAME/environment-variables?key=PUBLIC_BASE_URL

👉 Add LOGGEDIN_ORIGIN here: https://www.val.town/x/HANDLE/VAL_NAME/environment-variables?key=LOGGEDIN_ORIGIN

👉 Add SISTER_APP_URL here: https://www.val.town/x/HANDLE/VAL_NAME/environment-variables?key=SISTER_APP_URL

Key Default Notes
COOKIE_SECRET — Required. 32+ random characters.
PUBLIC_BASE_URL request origin Set to https://lightbox-loggedin.val.run (or Cratewalk) after the subdomain is claimed. This is the OAuth client_id origin.
LOGGEDIN_ORIGIN https://loggedin.fyi Use http://localhost:8787 against a local Worker.
SISTER_APP_URL unset The other demo’s URL, for the header link.

Replace HANDLE/VAL_NAME with each val after it exists.

Client metadata #

Each demo must serve /oauth-client-metadata.json on its origin (the shared OAuth helper already does). The Worker treats a valid document as membership — there is no static allowlist. If you use generated *.web.val.run URLs, ensure that origin’s metadata URL is reachable before testing the embed.

The login and session iframes set allow="storage-access" and call Loggedin.mountEmbed so the Worker can use Storage Access Headers / requestStorageAccess(). A grant is per site: Continue on Lightbox does not cover Cratewalk.

Local UI preview #

node val-town/preview/serve.mjs

Then open http://localhost:4173/?authed=1 (Lightbox) and http://localhost:4174/?authed=0 (Cratewalk login). Preview talks to https://loggedin.fyi unless LOGGEDIN_ORIGIN is set.

After login #

Both apps:

  1. Show a handle field and Sign in with Bluesky (GET /login?handle=… → PDS OAuth).
  2. Embed LOGGEDIN_ORIGIN/embed and render hinted DIDs from the Worker.
  3. Heartbeat the signed-in DID (Chrome silent path). On Safari / iOS, heartbeat() opens a first-party /save window; /enroll remains as a fallback.
  4. Fetch the home timeline or a picked generator through the user’s PDS (atproto-proxy to the Bluesky AppView).