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

ModuleWhat it does
atproto/browser/xrpcAsync Client, XrpcError, get/post_json/etc., fetch_client()
atproto/browser/dpopWebCrypto DPoP (RFC 9449) key generation + proof signing
atproto/browser/identityresolve_pds via Slingshot resolveMiniDoc
atproto/browser/oauth/flowstart(): resolve -> discover -> PKCE -> DPoP key -> PAR
atproto/browser/oauth/tokensexchange_code/refresh/revoke
atproto/browser/oauth/resourceWraps 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.

Search Document