···11+package client
22+33+import (
44+ "context"
55+ "encoding/json"
66+ "io"
77+ "net/http"
88+99+ "github.com/bluesky-social/indigo/atproto/syntax"
1010+)
1111+1212+// NOTE: this is an interface so it can be wrapped/extended. eg, a variant with a bunch of retries, or caching, or whatever. maybe that is too complex and we should have simple struct type, more like the existing `indigo/xrpc` package? hrm.
1313+1414+type APIClient interface {
1515+ // Full-power method for making atproto API requests.
1616+ Do(ctx context.Context, req *APIRequest) (*http.Response, error)
1717+1818+ // High-level helper for simple JSON "Query" API calls.
1919+ //
2020+ // Does not work with all API endpoints. For more control, use the Do() method with APIRequest.
2121+ Get(ctx context.Context, endpoint syntax.NSID, params map[string]string) (*json.RawMessage, error)
2222+2323+ // High-level helper for simple JSON-to-JSON "Procedure" API calls.
2424+ //
2525+ // Does not work with all API endpoints. For more control, use the Do() method with APIRequest.
2626+ // TODO: what is the right type for body, to indicate it can be marshaled as JSON?
2727+ Post(ctx context.Context, endpoint syntax.NSID, body any) (*json.RawMessage, error)
2828+2929+ // Returns the currently-authenticated account DID, or empty string if not available.
3030+ AuthDID() syntax.DID
3131+}
3232+3333+type APIRequest struct {
3434+ HTTPVerb string // TODO: type?
3535+ Endpoint syntax.NSID
3636+ Body io.Reader
3737+ QueryParams map[string]string // TODO: better type for this?
3838+ Headers map[string]string
3939+}
4040+4141+func (r *APIRequest) HTTPRequest(ctx context.Context, host string, headers map[string]string) (*http.Request, error) {
4242+ // TODO: use 'url' to safely construct the request URL
4343+ u := host + "/xrpc/" + r.Endpoint.String()
4444+ // XXX: query params
4545+ httpReq, err := http.NewRequestWithContext(ctx, r.HTTPVerb, u, r.Body)
4646+ if err != nil {
4747+ return nil, err
4848+ }
4949+5050+ // first set default headers
5151+ if headers != nil {
5252+ for k, v := range headers {
5353+ httpReq.Header.Set(k, v)
5454+ }
5555+ }
5656+5757+ // then request-specific take priority (overwrite)
5858+ if r.Headers != nil {
5959+ for k, v := range r.Headers {
6060+ httpReq.Header.Set(k, v)
6161+ }
6262+ }
6363+6464+ return httpReq, nil
6565+}
···11+package client
22+33+import (
44+ "context"
55+ "encoding/json"
66+ "errors"
77+ "io"
88+99+ "github.com/bluesky-social/indigo/atproto/syntax"
1010+)
1111+1212+// API for clients which pull data from the public atproto network.
1313+//
1414+// Implementations of this interface might resolve PDS instances for DIDs, and fetch data from there. Or they might talk to an archival relay or other network mirroring service.
1515+type NetClient interface {
1616+ // Fetches record JSON, without verification or validation. A version (CID) can optionally be specified; use empty string to fetch the latest.
1717+ // Returns the record as JSON, and the CID indicated by the server. Does not verify that the data (as CBOR) matches the CID, and does not cryptographically verify a "proof chain" to the record.
1818+ GetRecordJSON(ctx context.Context, aturi syntax.ATURI, version syntax.CID) (*json.RawMessage, *syntax.CID, error)
1919+2020+ // Fetches the indicated record as CBOR, and authenticates it by checking both the cryptographic signature and Merkle Tree hashes from the current repo revision. A version (CID) can optionally be specified; use empty string to fetch the latest.
2121+ // Returns the record as CBOR; the CID of the validated record, and the repo commit revision.
2222+ VerifyRecordCBOR(ctx context.Context, aturi syntax.ATURI, version syntax.CID) (*[]byte, *syntax.CID, string, error)
2323+2424+ // Fetches repo export (CAR file). Optionally attempts to fetch only the diff "since" an earlier repo revision.
2525+ GetRepoCAR(ctx context.Context, did syntax.DID, since string) (*io.Reader, error)
2626+2727+ // Fetches indicated blob. Does not validate the CID. Returns a reader (which calling code is responsible for closing).
2828+ GetBlob(ctx context.Context, did syntax.DID, cid syntax.CID) (*io.Reader, error)
2929+ CheckAccountStatus(ctx context.Context, did syntax.DID) (*AccountStatus, error)
3030+}
3131+3232+// XXX: type alias to codegen? or just copy? this is protocol-level
3333+type AccountStatus struct {
3434+}
3535+3636+func VerifyBlobCID(blob []byte, cid syntax.CID) error {
3737+ // XXX: compute hash, check against provided CID
3838+ return errors.New("Not Implemented")
3939+}
+31
atproto/client/refresh_auth.go
···11+package client
22+33+import (
44+ "context"
55+ "net/http"
66+77+ "github.com/bluesky-social/indigo/atproto/syntax"
88+)
99+1010+type RefreshAuth struct {
1111+ AccessToken string
1212+ RefreshToken string
1313+ DID syntax.DID
1414+ // The AuthHost might different from any APIClient host, if there is an entryway involved
1515+ AuthHost string
1616+}
1717+1818+// TODO:
1919+//func NewRefreshAuth(pdsHost, accountIdentifier, password string) (*RefreshAuth, error) {
2020+2121+func (a *RefreshAuth) DoWithAuth(ctx context.Context, httpReq *http.Request, httpClient *http.Client) (*http.Response, error) {
2222+ httpReq.Header.Set("Authorization", "Bearer "+a.AccessToken)
2323+ // XXX: check response. if it is 403, because access token is expired, then take a lock and do a refresh
2424+ // TODO: when doing a refresh request, copy at least the User-Agent header from httpReq, and re-use httpClient
2525+ return httpClient.Do(httpReq)
2626+}
2727+2828+// Admin bearer token auth does not involve an account DID
2929+func (a *RefreshAuth) AccountDID() syntax.DID {
3030+ return a.DID
3131+}