nandi/frqpublic Fork 0
35f0e9b463ef2a1f75b62c02cc7dcbf57171828a
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Resolve an identity from the phone, over the shared AT Protocol core f735240 · on 35f0e9b463ef2a1f75b62c02cc7dcbf57171828a · nandi · 7d ago
atproto.clj · 92 lines · 3.3 KBClojure Blame HistoryRaw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
(ns frq.atproto
  "The desktop's AT Protocol: `frq.atproto.core` with a socket under it.

  The protocol itself moved to common/ — the JSON, the base64url, the SASL
  payloads, and a `-req`/`-parse` pair per step of the flow. What could not
  move is this: HTTPS hand-rolled over jolt.mvn-http's TLS bindings, because
  `fetch` there writes to a file and cannot POST. That is also why sign-in is
  desktop-only on jolt — there is no libssl to load on Android — and why the
  phone has `frq.atproto.dart`, where TLS is in the runtime.

  Everything in core is re-exported here, so the twenty-nine call sites that
  say `atproto/json-str` or `atproto/request` did not move."
  (:require [clojure.string :as str]
            [frq.atproto.core :as core]
            [jolt.mvn-http :as tls]))

(def directory-host core/directory-host)
(def plc-host core/plc-host)
(def json-str core/json-str)
(def json-num core/json-num)
(def json-unescape core/json-unescape)
(def json-object core/json-object)
(def b64-encode core/b64-encode)
(def b64-decode core/b64-decode)
(def sasl-response core/sasl-response)

;; ------------------------------------------------------------------ HTTP

(defn- read-all!
  "Drain a TLS connection into a string."
  [t]
  (loop [acc ""]
    (let [b (try (tls/tls-read t) (catch Exception _ nil))]
      (if (or (nil? b) (zero? (count b)))
        acc
        (recur (str acc (String. b)))))))

(defn request
  "One HTTPS request, connection-per-request. Returns the response body.
  `body` nil makes it a GET."
  [host path body]
  (tls/ensure-native!)
  (let [t (tls/tls-connect host 443)
        payload (or body "")
        head (str (if body "POST " "GET ") path " HTTP/1.1\r\n"
                  "Host: " host "\r\n"
                  "User-Agent: frq\r\n"
                  "Accept: application/json\r\n"
                  (when body
                    (str "Content-Type: application/json\r\n"
                         "Content-Length: " (count (.getBytes payload)) "\r\n"))
                  "Connection: close\r\n\r\n")]
    (try
      (tls/tls-write t (.getBytes (str head payload)))
      (let [resp (read-all! t)
            [_ b] (str/split resp #"\r\n\r\n" 2)]
        (or b ""))
      (finally (try (tls/tls-close t) (catch Exception _ nil))))))

;; ------------------------------------------------------------------ JSON

(defn- fetch
  "Perform one `-req` descriptor."
  [{:keys [host path body]}]
  (request host path body))

;; ------------------------------------------------------------------ identity
;;
;; The flow, put back together: core says what to ask and what the answer
;; means, and this is the only part that touches a socket.

(defn resolve-handle
  "A handle (alice.bsky.social) to its DID. A DID passes through untouched."
  [handle]
  (core/resolve-handle-parse
   handle
   (when-let [req (core/resolve-handle-req handle)] (fetch req))))

(defn pds-endpoint
  "The DID's PDS service endpoint, from its DID document."
  [did]
  (core/pds-endpoint-parse did (fetch (core/pds-doc-req did))))

(defn create-session
  "Sign in to the PDS with an app password. Returns
  {:did :handle :access-jwt :pds}."
  [identifier password]
  (let [did (resolve-handle identifier)
        pds (pds-endpoint did)]
    (core/create-session-parse
     identifier did pds
     (fetch (core/create-session-req pds identifier password)))))