Skip to content

Commit

Permalink
feat(client): minimal auth and basic requests
Browse files Browse the repository at this point in the history
This commit adds some scaffolding:
- Basic configuration in the core namespace
- Exchanging app password for an auth token in the client ns
- A request function and a couple of convenience methods in client ns

Still needs tests!
  • Loading branch information
goshatch committed Nov 27, 2024
1 parent 9c9a7d3 commit d68066e
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 16 deletions.
74 changes: 63 additions & 11 deletions src/net/gosha/atproto/client.clj
Original file line number Diff line number Diff line change
@@ -1,13 +1,65 @@
(ns net.gosha.atproto.client
(:require [org.httpkit.client :as http]
[clojure.data.json :as json]))

(defn make-request
"Make an HTTP request to the ATProto API"
[method url body headers]
(let [options {:method method
:url url
:headers headers
:body (when body (json/write-str body))}
response @(http/request options)]
(update response :body json/read-str :key-fn keyword)))
[clojure.data.json :as json]
[clojure.pprint :refer [pprint]]
[net.gosha.atproto.core :as core]))

(defn request
"Make an HTTP request to the atproto API.
- `method`: HTTP method (:get, :post, etc.)
- `endpoint`: API endpoint (relative to `:base-url`)
- `body`: Request body (optional)
- `headers`: Additional headers (optional)
- `retries`: Number of retries for transient failures"
[method endpoint & [{:keys [body headers retries] :or {retries 3}}]]
(let [{:keys [base-url auth-token]} @core/config]
(when-not base-url
(throw (ex-info "SDK not initialised: missing base-url" {})))
(let [url (str base-url endpoint)
options {:method method
:url url
:headers (merge {"Authorization" (str "Bearer " auth-token)
"Content-Type" "application/json"}
headers)
:body (when body (json/write-str body))}]
(loop [attempt 0]
(let [response (try
{:success true
:result @(http/request options)}
(catch Exception e
{:success false
:error e}))]
(if (:success response)
(let [result (:result response)]
(if (<= 200 (:status result) 299)
(update result :body json/read-str :key-fn keyword)
(throw (ex-info "API request failed"
{:status (:status result)
:body (:body result)}))))
(if (>= attempt retries)
(throw (:error response))
(do
(Thread/sleep (* 100 (inc attempt)))
(recur (inc attempt))))))))))

;; Convenience functions
(defn get-req
"Perform a GET request to the atproto API."
[endpoint & [options]]
(request :get endpoint options))

(defn post-req
"Perform a POST request to the atproto API."
[endpoint body & [options]]
(request :post endpoint (merge options {:body body})))

(defn authenticate!
"Authenticate with the atproto API using an app password.
Updates configuration with auth token."
[]
(let [endpoint "/xrpc/com.atproto.server.createSession"
response (post-req endpoint {:identifier (:username @core/config)
:password (:app-password @core/config)}
{:base-url (:base-url @core/config)})
token (get-in response [:body :accessJwt])]
(swap! core/config assoc :auth-token token)))
42 changes: 37 additions & 5 deletions src/net/gosha/atproto/core.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
(ns net.gosha.atproto.core)
(ns net.gosha.atproto.core
(:require [clojure.spec.alpha :as s]))

(defn -main
"Main entry point for the atproto SDK"
[& args]
(println "Hello, world!"))
;; Spec for SDK configuration
(s/def ::base-url string?)
(s/def ::auth-token (s/nilable string?))
(s/def ::app-password (s/nilable string?))
(s/def ::username (s/nilable string?))
(s/def ::config (s/keys :req-un [::base-url]
:opt-un [::auth-token ::app-password ::username]))

(defonce config (atom {}))

(defn init
"Initialise the SDK with configuration. Supports:
- `:base-url` (required)
- `:auth-token` (optional)
- `:app-password` and `:username` (optional, used to generate a token)."
[options]
(if (s/valid? ::config options)
(do
(reset! config options)
(println "SDK initialised with configuration:" @config))
(throw (ex-info "Invalid configuration"
{:errors (s/explain-str ::config options)}))))

(comment
; Initialise configuration
(init {:base-url "https://bsky.social"
:username "someuser.bsky.social"
:app-password "some-app-password"})
; Exchange app password for auth token
(net.gosha.atproto.client/authenticate!)
; Make API requests
(net.gosha.atproto.client/get-req "/xrpc/com.atproto.server.getSession")
; ???
; Profit
)

0 comments on commit d68066e

Please sign in to comment.