Documentation
¶
Overview ¶
General-purpose client for atproto "XRPC" HTTP API endpoints.
APIClient wraps an http.Client and provides an ergonomic atproto-specific (but not Lexicon-specific) interface for "Query" (GET) and "Procedure" (POST) endpoints. It does not support "Event Stream" (WebSocket) endpoints. The client is expected to be used with a single host at a time, though it does have special support (APIClient.WithService) for proxied service requests when connected to a PDS host. The client does not authenticate requests by default, but supports pluggable authentication methods (see below). The [APIReponse] struct represents a generic API request, and helps with conversion to an http.Request.
The APIError struct can represent a generic API error response (eg, an HTTP response with a 4xx or 5xx response code), including the 'error' and 'message' JSON response fields expected with atproto. It is intended to be used with errors.Is in error handling, or to provide helpful error messages.
The AuthMethod interface allows APIClient to work with multiple forms of authentication in atproto. It is expected that more complex auth systems (eg, those using signed JWTs) will be implemented in separate packages, but this package does include two simple auth methods:
- PasswordAuth is the original PDS user auth method, using access and refresh tokens. - AdminAuth is simple HTTP Basic authentication for administrative requests, as implemented by many atproto services (Relay, Ozone, PDS, etc).
## Design Notes
Several AuthMethod implementations are expected to require retrying entire request at unexpected times. For example, unexpected OAuth DPoP nonce changes, or unexpected password session token refreshes. The auth method may also need to make requests to other servers as part of the refresh process (eg, OAuth when working with a PDS/entryway split). This means that requests should be "retryable" as often as possible. This is mostly a concern for Procedures (HTTP POST) with a non-empty body. The http.Client will attempt to "unclose" some common io.ReadCloser types (like bytes.Buffer), but others may need special handling, using the [APIRequest.GetBody] method. This package will try to make types implementing io.Seeker tryable; this helps with things like passing in a open file descriptor for file uploads.
In theory, the http.RoundTripper interface could have been used instead of AuthMethod; or auth methods could have been injected in to http.Client instances directly. This package avoids this pattern for a few reasons. The first is that wrangling layered stacks of http.RoundTripper can become cumbersome. Calling code may want to use http.Client variants which add observability, retries, circuit-breaking, or other non-auth customization. Secondly, some atproto auth methods will require requests to other servers or endpoints, and having a common http.Client to re-use for these requests makes sense. Finally, several atproto auth methods need to know the target endpoint as an NSID; while this could be re-parsed from the request URL, it is simpler and more reliable to pass it as an argument.
This package tries to use minimal dependencies beyond the Go standard library, to make it easy to reference as a dependency. It does require the github.com/bluesky-social/indigo/atproto/syntax and github.com/bluesky-social/indigo/atproto/identity sibling packages. In particular, this package does not include any auth methods requiring JWTs, to avoid adding any specific JWT implementation as a dependency.
Index ¶
- Variables
- func ParseParams(raw map[string]any) (url.Values, error)
- type APIClient
- func LoginWithPassword(ctx context.Context, dir identity.Directory, username syntax.AtIdentifier, ...) (*APIClient, error)
- func LoginWithPasswordHost(ctx context.Context, host, username, password, authToken string, ...) (*APIClient, error)
- func NewAPIClient(host string) *APIClient
- func NewAdminClient(host, password string) *APIClient
- func ResumePasswordSession(data PasswordSessionData, cb RefreshCallback) *APIClient
- func (c *APIClient) Do(ctx context.Context, req *APIRequest) (*http.Response, error)
- func (c *APIClient) Get(ctx context.Context, endpoint syntax.NSID, params map[string]any, out any) error
- func (c *APIClient) LexDo(ctx context.Context, method string, inputEncoding string, endpoint string, ...) error
- func (c *APIClient) Post(ctx context.Context, endpoint syntax.NSID, body any, out any) error
- func (c *APIClient) SetLabelers(redact, other []syntax.DID)
- func (c *APIClient) WithService(ref string) *APIClient
- type APIError
- type APIRequest
- type AdminAuth
- type AuthMethod
- type ErrorBody
- type PasswordAuth
- func (a *PasswordAuth) DoWithAuth(c *http.Client, req *http.Request, endpoint syntax.NSID) (*http.Response, error)
- func (a *PasswordAuth) GetTokens() (string, string)
- func (a *PasswordAuth) Logout(ctx context.Context, c *http.Client) error
- func (a *PasswordAuth) Refresh(ctx context.Context, c *http.Client, priorRefreshToken string) error
- type PasswordSessionData
- type RefreshCallback
Constants ¶
This section is empty.
Variables ¶
var ( // atproto API "Query" Lexicon method, which is HTTP GET. Not to be confused with the IETF draft "HTTP QUERY" method. MethodQuery = http.MethodGet // atproto API "Procedure" Lexicon method, which is HTTP POST. MethodProcedure = http.MethodPost )
Functions ¶
Types ¶
type APIClient ¶
type APIClient struct { // Inner HTTP client. May be customized after the overall [APIClient] struct is created; for example to set a default request timeout. Client *http.Client // Host URL prefix: scheme, hostname, and port. This field is required. Host string // Optional auth client "middleware". Auth AuthMethod // Optional HTTP headers which will be included in all requests. Only a single value per key is included; request-level headers will override any client-level defaults. Headers http.Header // optional authenticated account DID for this client. Does not change client behavior; this field is included as a convenience for calling code, logging, etc. AccountDID *syntax.DID }
General purpose client for atproto "XRPC" API endpoints.
func LoginWithPassword ¶
func LoginWithPassword(ctx context.Context, dir identity.Directory, username syntax.AtIdentifier, password, authToken string, cb RefreshCallback) (*APIClient, error)
Creates a new APIClient with PasswordAuth for the provided user. The provided identity directory is used to resolve the PDS host for the account.
`authToken` is optional; is used when multi-factor authentication is enabled for the account.
`cb` is an optional callback which will be called with updated session data after any token refresh.
func LoginWithPasswordHost ¶
func LoginWithPasswordHost(ctx context.Context, host, username, password, authToken string, cb RefreshCallback) (*APIClient, error)
Creates a new APIClient with PasswordAuth, based on a login to the provided host. Note that with some PDS implementations, 'username' could be an email address. This login method also works in situations where an account's network identity does not resolve to this specific host.
`authToken` is optional; is used when multi-factor authentication is enabled for the account.
`cb` is an optional callback which will be called with updated session data after any token refresh.
func NewAPIClient ¶
Creates a simple APIClient for the provided host. This is appropriate for use with unauthenticated ("public") atproto API endpoints, or to use as a base client to add authentication.
Uses http.DefaultClient, and sets a default User-Agent.
func NewAdminClient ¶
func ResumePasswordSession ¶
func ResumePasswordSession(data PasswordSessionData, cb RefreshCallback) *APIClient
Creates an APIClient using PasswordAuth, based on existing session data.
`cb` is an optional callback which will be called with updated session data after any token refresh.
func (*APIClient) Do ¶
Full-featured method for atproto API requests.
TODO: this does not currently parse API error response JSON body to APIError, thought it might in the future.
func (*APIClient) Get ¶
func (c *APIClient) Get(ctx context.Context, endpoint syntax.NSID, params map[string]any, out any) error
High-level helper for simple JSON "Query" API calls.
This method automatically parses non-successful responses to APIError.
For Query endpoints which return non-JSON data, or other situations needing complete configuration of the request and response, use the APIClient.Do method.
func (*APIClient) LexDo ¶
func (c *APIClient) LexDo(ctx context.Context, method string, inputEncoding string, endpoint string, params map[string]any, bodyData any, out any) error
Implements the github.com/bluesky-social/indigo/lex/util.LexClient interface, for use with code-generated API helpers.
func (*APIClient) Post ¶
High-level helper for simple JSON-to-JSON "Procedure" API calls, with no query params.
This method automatically parses non-successful responses to APIError.
For Query endpoints which expect non-JSON request bodies; return non-JSON responses; direct use of io.Reader for the request body; or other situations needing complete configuration of the request and response, use the APIClient.Do method.
func (*APIClient) SetLabelers ¶
Configures labeler header ('Atproto-Accept-Labelers') with the indicated "redact" level labelers, and regular labelers.
Overwrites any existing client-level header value.
func (*APIClient) WithService ¶
Returns a shallow copy of the APIClient with the provided service ref configured as a proxy header.
To configure service proxying without creating a copy, simply set the 'Atproto-Proxy' header.
type APIRequest ¶
type APIRequest struct { // HTTP method as a string (eg "GET") (required) Method string // atproto API endpoint, as NSID (required) Endpoint syntax.NSID // Optional request body (may be nil). If this is provided, then 'Content-Type' header should be specified Body io.Reader // Optional function to return new reader for request body; used for retries. Strongly recommended if Body is defined. Body still needs to be defined, even if this function is provided. GetBody func() (io.ReadCloser, error) // Optional query parameters (field may be nil). These will be encoded as provided. QueryParams url.Values // Optional HTTP headers (field bay be nil). Only the first value will be included for each header key ("Set" behavior). Headers http.Header }
func NewAPIRequest ¶
Initializes a new request struct. Initializes Headers and QueryParams so they can be manipulated immediately.
If body is provided (it can be nil), will try to turn it in to the most retry-able form (and wrap as io.ReadCloser).
func (*APIRequest) HTTPRequest ¶
func (r *APIRequest) HTTPRequest(ctx context.Context, host string, clientHeaders http.Header) (*http.Request, error)
Creates an http.Request for this API request.
`host` parameter should be a URL prefix: schema, hostname, port (required)
`clientHeaders`, if provided, is treated as client-level defaults. Only a single value is allowed per key ("Set" behavior), and will be clobbered by any request-level header values. (optional; may be nil)
type AdminAuth ¶
type AdminAuth struct {
Password string
}
Simple AuthMethod implementation for atproto "admin auth".
type AuthMethod ¶
type AuthMethod interface { // Endpoint parameter is included for auth methods which need to include the NSID in authorization tokens DoWithAuth(c *http.Client, req *http.Request, endpoint syntax.NSID) (*http.Response, error) }
Interface for auth implementations which can be used with APIClient.
type PasswordAuth ¶
type PasswordAuth struct { Session PasswordSessionData // Optional callback function which gets called with updated session data whenever a successful token refresh happens. // // Note that this function is called while a lock is being held on the overall client, and with a context usually tied to a regular API request call. The callback should either return quickly, or spawn a goroutine. Because of the lock, this callback will never be called concurrently for a single client, but may be called currently across clients. RefreshCallback RefreshCallback // contains filtered or unexported fields }
Implementation of AuthMethod for password-based auth sessions with atproto PDS hosts. Automatically refreshes "access token" using a "refresh token" when needed.
It is safe to use this auth method concurrently from multiple goroutines.
func (*PasswordAuth) DoWithAuth ¶
func (*PasswordAuth) GetTokens ¶
func (a *PasswordAuth) GetTokens() (string, string)
Returns current access and refresh tokens (take a read-lock on session data)
type PasswordSessionData ¶
type PasswordSessionData struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` AccountDID syntax.DID `json:"account_did"` Host string `json:"host"` }
Data about a PDS password auth session which can be persisted and then used to resume the session later.
func (*PasswordSessionData) Clone ¶
func (sd *PasswordSessionData) Clone() PasswordSessionData
Creates a deep copy of the session data.
type RefreshCallback ¶
type RefreshCallback = func(ctx context.Context, data PasswordSessionData)