A set of utilities for working with the AT Protocol in Elixir.
at v0.2.0 2.7 kB view raw
1defmodule Atex.XRPC.Client do 2 @doc """ 3 Struct to store client information for ATProto XRPC. 4 """ 5 6 alias Atex.XRPC 7 use TypedStruct 8 9 typedstruct do 10 field :endpoint, String.t(), enforce: true 11 field :access_token, String.t() | nil 12 field :refresh_token, String.t() | nil 13 end 14 15 @doc """ 16 Create a new `Atex.XRPC.Client` from an endpoint, and optionally an 17 access/refresh token. 18 19 Endpoint should be the base URL of a PDS, or an AppView in the case of 20 unauthenticated requests (like Bluesky's public API), e.g. 21 `https://bsky.social`. 22 """ 23 @spec new(String.t()) :: t() 24 @spec new(String.t(), String.t() | nil) :: t() 25 @spec new(String.t(), String.t() | nil, String.t() | nil) :: t() 26 def new(endpoint, access_token \\ nil, refresh_token \\ nil) do 27 %__MODULE__{endpoint: endpoint, access_token: access_token, refresh_token: refresh_token} 28 end 29 30 @doc """ 31 Create a new `Atex.XRPC.Client` by logging in with an `identifier` and 32 `password` to fetch an initial pair of access & refresh tokens. 33 34 Uses `com.atproto.server.createSession` under the hood, so `identifier` can be 35 either a handle or a DID. 36 37 ## Examples 38 39 iex> Atex.XRPC.Client.login("https://bsky.social", "example.com", "password123") 40 {:ok, %Atex.XRPC.Client{...}} 41 """ 42 @spec login(String.t(), String.t(), String.t()) :: {:ok, t()} | XRPC.Adapter.error() 43 @spec login(String.t(), String.t(), String.t(), String.t() | nil) :: 44 {:ok, t()} | XRPC.Adapter.error() 45 def login(endpoint, identifier, password, auth_factor_token \\ nil) do 46 json = 47 %{identifier: identifier, password: password} 48 |> then( 49 &if auth_factor_token do 50 Map.merge(&1, %{authFactorToken: auth_factor_token}) 51 else 52 &1 53 end 54 ) 55 56 response = XRPC.unauthed_post(endpoint, "com.atproto.server.createSession", json: json) 57 58 case response do 59 {:ok, %{"accessJwt" => access_token, "refreshJwt" => refresh_token}} -> 60 {:ok, new(endpoint, access_token, refresh_token)} 61 62 err -> 63 err 64 end 65 end 66 67 @doc """ 68 Request a new `refresh_token` for the given client. 69 """ 70 @spec refresh(t()) :: {:ok, t()} | XRPC.Adapter.error() 71 def refresh(%__MODULE__{endpoint: endpoint, refresh_token: refresh_token} = client) do 72 response = 73 XRPC.unauthed_post( 74 endpoint, 75 "com.atproto.server.refreshSession", 76 XRPC.put_auth([], refresh_token) 77 ) 78 79 case response do 80 {:ok, %{"accessJwt" => access_token, "refreshJwt" => refresh_token}} -> 81 %{client | access_token: access_token, refresh_token: refresh_token} 82 83 err -> 84 err 85 end 86 end 87end