p2p-cljs / src / ird / p2p / descriptor.cljs
547 words
  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
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
(ns ird.p2p.descriptor
  "The app's self-description at `GET https://<host>/.well-known/ap2p`:
  endpoints, optional relay and mailbox blocks (present only when those
  features are enabled — feature-detect from their presence), and the
  node's limits, which the SDK paces against and never hardcodes.

  Keys keep the service's snake_case names as keywords (:max_frame_bytes,
  :mint_endpoint, ...); unknown keys and blocks are kept. When the fetch
  fails the descriptor is synthesized from the documented default paths and
  node-wide default limits, flagged :is-fallback true."
  (:require [clojure.string :as str]
            [cljs.core.async :as async :refer [go <!]]
            [ird.p2p.async :as a]
            [ird.p2p.errors :as err]
            [ird.p2p.impl.http :as http]
            [ird.p2p.impl.json :as json]))

(def default-limits
  "The node-wide defaults, used only when the descriptor cannot be fetched."
  {:max_rooms 500
   :max_room_peers 16
   :max_conns_per_ip 20
   :max_bytes_per_sec 262144
   :messages_per_sec 30
   :message_burst 60
   :max_frame_bytes 65536})

(defn descriptor-url [host] (str "https://" host "/.well-known/ap2p"))

(defn fallback
  "What the descriptor of HOST would say if only the documented default
  paths and node-wide limits were known. No mailbox (cannot be detected
  offline)."
  [host]
  {:protocol "ap2p-signal"
   :version "1"
   :endpoints {:ws (str "wss://" host "/ws")
               :turn_credentials (str "https://" host "/turn-credentials")}
   :limits default-limits
   :is-fallback true})

(defn- valid-limits? [l]
  (and (map? l) (every? #(integer? (get l %)) (keys default-limits))))

(defn- valid-mailbox? [m]
  (and (map? m) (string? (:endpoint m)) (integer? (:ttl_hours m))
       (integer? (:max_mb m)) (integer? (:max_message_kb m)) (boolean? (:open_mint m))))

(defn- valid-relay? [r]
  (and (map? r) (string? (:peer_id r)) (vector? (:multiaddrs r)) (every? string? (:multiaddrs r))))

(defn parse
  "A descriptor map from the document TEXT (or an already-parsed map);
  nil when it is not a usable descriptor (no endpoints). Missing
  `protocol`/`version`/`limits` take their defaults; malformed optional
  blocks are dropped, unknown keys kept."
  [text-or-map]
  (let [v (if (string? text-or-map) (json/parse text-or-map) text-or-map)]
    (when (and (map? v) (map? (:endpoints v))
               (string? (get-in v [:endpoints :ws]))
               (string? (get-in v [:endpoints :turn_credentials])))
      (cond-> (assoc v
                     :protocol (if (string? (:protocol v)) (:protocol v) "ap2p-signal")
                     :version (if (string? (:version v)) (:version v) "1")
                     :limits (if (valid-limits? (:limits v)) (:limits v) default-limits)
                     :is-fallback false)
        (and (contains? v :relay) (not (valid-relay? (:relay v)))) (dissoc :relay)
        (and (contains? v :mailbox) (not (valid-mailbox? (:mailbox v)))) (dissoc :mailbox)))))

(defn fetch
  "Fetch the descriptor of HOST. OPTS: :origin (sent as the Origin header),
  :http-fn (transport, default fetch over the global fetch), :timeout-ms
  (default 3000). Channel of the descriptor — the fallback (never an error)
  when the request fails or the body is not a descriptor."
  [{:keys [host origin http-fn timeout-ms]}]
  (go
    (let [http (or http-fn http/fetch-http)
          resp (<! (http {:method :get
                          :url (descriptor-url host)
                          :headers (cond-> {"Accept" "application/json"} origin (assoc "Origin" origin))
                          :timeout-ms (or timeout-ms 3000)}))]
      (or (when (and (map? resp) (<= 200 (:status resp) 299))
            (parse (:body resp)))
          (fallback host)))))

(defn limits
  "The limits block of DESCRIPTOR (always present)."
  [descriptor]
  (or (:limits descriptor) default-limits))

(defn mailbox-enabled?
  "True when the app has its mailbox enabled (a mailbox block is present)."
  [descriptor]
  (some? (:mailbox descriptor)))

(defn open-mint?
  "True when the mailbox mints tokens openly (with an allowed Origin)."
  [descriptor]
  (boolean (get-in descriptor [:mailbox :open_mint])))

(defn resolved-endpoints
  "The URLs the SDK talks to: explicit OVERRIDES win, then the descriptor.
  {:ws :turn-credentials :mailbox :mailbox-mint} (the last two nil when absent)."
  ([descriptor] (resolved-endpoints descriptor nil))
  ([descriptor overrides]
   {:ws (or (:ws overrides) (get-in descriptor [:endpoints :ws]))
    :turn-credentials (or (:turn-credentials overrides) (get-in descriptor [:endpoints :turn_credentials]))
    :mailbox (or (:mailbox overrides) (get-in descriptor [:mailbox :endpoint]))
    :mailbox-mint (or (:mailbox-mint overrides)
                      (when (open-mint? descriptor) (get-in descriptor [:mailbox :mint_endpoint])))}))

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/p2p-cljs.git