Packages

A browser-native atproto OAuth + XRPC client for Gleam: WebCrypto DPoP, PKCE, and async transport, for building a public-client SPA with no server-side session custody.

Current section

Files

Jump to
atproto_browser src atproto browser oauth resource.gleam
Raw

src/atproto/browser/oauth/resource.gleam

//// Async mirror of `atproto/oauth/resource.gleam`: wraps a `Client` so every
//// request carries `Authorization: DPoP <token>` and a proof bound to its
//// method, URL, and access-token hash, with the server-nonce retry. Token
//// refresh is the caller's job (it owns session persistence); this module
//// only signs.
////
//// A thin async wrapper over the shared effect kernel in
//// `atproto_core/oauth/resource`: it computes the
//// access-token hash (`ath`) once with WebCrypto and runs the per-request
//// signing effect against the base client.
import atproto/browser/digest
import atproto/browser/dpop.{type DpopKey}
import atproto/browser/oauth/runner
import atproto/browser/xrpc.{type Client}
import atproto_core/oauth/resource as core
import atproto_core/xrpc as core_xrpc
import gleam/bit_array
import gleam/javascript/promise.{type Promise}
import gleam/result
/// A client that signs every request against the given DPoP-bound access
/// token.
pub fn client(
base: Client,
access_token access_token: String,
dpop_key dpop_key: DpopKey,
) -> Promise(Client) {
use ath <- promise.map(digest.sha256(bit_array.from_string(access_token)))
let ath = base64url(ath)
xrpc.Client(send: fn(req) {
runner.run(core.send(req, access_token:, ath:), base, dpop_key)
|> promise.map(result.map_error(_, core_xrpc.Other))
})
}
fn base64url(bits: BitArray) -> String {
bit_array.base64_url_encode(bits, False)
}