atproto_browser
Alpha, pre-release. Not yet published to Hex. Proven via the manual smoke scripts below, not yet used in a real browser tab or backed by persistent key storage. Expect breaking changes.
A browser-native atproto OAuth + XRPC client for
Gleam: WebCrypto DPoP and PKCE, async fetch transport, and the full
PAR/token-exchange/authenticated-request flow for a public client. Separate
from atproto_client because real browser fetch is
async and DPoP/PKCE need browser WebCrypto; this package is
javascript-target-only and owns the atproto/browser/* namespace.
Installation
Monorepo path dependency (javascript target only):
[dependencies]
atproto_browser = { path = "../atproto_browser" }
Usage
import atproto/browser/oauth/flow
import atproto/browser/xrpc
// resolve handle -> discover auth server -> PKCE -> DPoP key -> PAR;
// returns the authorization URL to redirect the user to, plus the Flow
// state to stash for the callback.
flow.start(
xrpc.fetch_client(),
resolver: "https://slingshot.microcosm.blue",
identifier: handle,
client_id: client_id,
redirect_uri: redirect_uri,
scope: "atproto transition:generic",
extra_form: [],
)
After the redirect back, exchange the code with oauth/tokens and wrap a
client with oauth/resource so every request is DPoP-signed.
Architecture
| Module | What it does |
|---|---|
atproto/browser/xrpc | Async Client, XrpcError, get/post_json/etc., fetch_client() |
atproto/browser/dpop | WebCrypto DPoP (RFC 9449) key generation + proof signing |
atproto/browser/identity | resolve_pds via Slingshot resolveMiniDoc |
atproto/browser/oauth/flow | start(): resolve -> discover -> PKCE -> DPoP key -> PAR |
atproto/browser/oauth/tokens | exchange_code/refresh/revoke |
atproto/browser/oauth/resource | Wraps a Client so every request is DPoP-signed against a token |
The rest (pkce, digest, oauth/runner) is internal plumbing behind
these six: PKCE/digest primitives and the effect-kernel runner that flow,
tokens, and resource interpret against.
Development
gleam test # pure, synchronous pieces
node test/manual/dpop_smoke.ts # real WebCrypto DPoP sign + verify
node test/manual/oauth_flow_smoke.ts # PAR + token exchange + authed call
The smoke tests run the actual compiled output against real WebCrypto and a
fake-but-realistic PDS/authorization server, parameterized over proof shapes
and server behaviors (including the use_dpop_nonce retry); gleeunit has no
Promise-test support, so they are the regression check for the async paths
and CI runs them on every push. They are TypeScript, run via node’s type
stripping (node 23.6+, or --experimental-strip-types on 22.6+), with
editor types from the typescript_declarations build output.