-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(client): minimal auth and basic requests
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
Showing
2 changed files
with
100 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |