peer-kit / src / ardegazu / peer / core / node.cljs
  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
111
112
113
114
115
;; ported-from: src/core/node.ts @ v1.3.0
;;
;; libp2p node factory — Node port of the browser original. The peer listens on
;; the app relay (circuit-relay-v2); WebRTC is attempted when available so
;; browser peers can upgrade to direct connections, and skipped cleanly when the
;; runtime lacks it (circuit-only still carries every app frame — the shared net
;; layer runs all protocols with runOnLimitedConnection).
;;
;; TWO-STACK LAW (the suite's house rule 11): every specifier below is libp2p
;; v3 / node-datachannel 0.33. Nothing here may reach for rooms-kit,
;; @ipshipyard/node-datachannel or libp2p v2, and all of them stay EXTERNAL
;; imports (:js-options {:js-provider :import}) so the native builds are never
;; bundled.
(ns ardegazu.peer.core.node
  (:require ["libp2p" :refer (createLibp2p)]
            ["@libp2p/websockets" :refer (webSockets)]
            ["@libp2p/circuit-relay-v2" :refer (circuitRelayTransport)]
            ["@chainsafe/libp2p-noise" :refer (noise)]
            ["@chainsafe/libp2p-yamux" :refer (yamux)]
            ["@libp2p/gossipsub" :refer (gossipsub)]
            ["@libp2p/pubsub-peer-discovery" :refer (pubsubPeerDiscovery)]
            ["@libp2p/identify" :refer (identify)]
            ["@multiformats/multiaddr" :refer (multiaddr)]
            [shadow.esm :refer (dynamic-import)]
            [ardegazu.peer.core.turn :as turn]
            [ardegazu.peer.node.env :as env]
            [shadow.cljs.modern :refer (defclass js-await)]))

(defclass NetNode
  (constructor [this node relay webrtc-enabled]
    (unchecked-set this "node" node)
    (unchecked-set this "relayId" (unchecked-get relay "peerId"))
    ;; True when the WebRTC transport actually loaded in this runtime.
    (unchecked-set this "webrtcEnabled" webrtc-enabled)
    (unchecked-set this "_relayMaddr" (multiaddr (aget (unchecked-get relay "multiaddrs") 0)))
    (unchecked-set this "_redialing" false)))

(defn relay-connected? [self]
  (let [^js node (unchecked-get self "node")
        relay-id (unchecked-get self "relayId")]
    (.some (.getConnections node)
           (fn [^js c] (identical? (.toString (.-remotePeer c)) relay-id)))))

(defn redial-relay
  "Dial (or re-dial after a network flap) the relay; the circuit listener
   re-takes its reservation."
  [self]
  (-> (js/Promise.resolve nil)
      (.then
       (fn [_]
         (when-not (or (unchecked-get self "_redialing") (relay-connected? self))
           (unchecked-set self "_redialing" true)
           (-> (js/Promise.resolve nil)
               (.then
                (fn [_]
                  ;; bind to a ^js local first: a ^js hint placed on an
                  ;; `unchecked-get` macro form is LOST (dev/docs/CLJS.md)
                  (let [^js node (unchecked-get self "node")]
                    (js-await [_r (.dial node
                                         (unchecked-get self "_relayMaddr")
                                         (js-obj "signal" (js/AbortSignal.timeout 15000)))]
                      nil))))
               (.catch (fn [err] (js/console.warn "relay dial failed" err)))
               (.finally (fn [] (unchecked-set self "_redialing" false)))))))))

(defn create
  "opts = #js {:webrtc bool} — try the WebRTC transport (direct browser
   upgrades). Default true."
  ([relay ice] (create relay ice js/undefined))
  ([relay ice opts]
   (-> (js/Promise.resolve nil)
       (.then
        (fn [_]
          (let [o (if (identical? opts js/undefined) (js-obj) opts)
                maddr0 (aget (unchecked-get relay "multiaddrs") 0)
                listen #js [(str maddr0 "/p2p-circuit")]
                transports #js [(webSockets) (circuitRelayTransport)]]
            (-> (if (identical? false (unchecked-get o "webrtc"))
                  (js/Promise.resolve false)
                  ;; a genuine dynamic import (shadow.esm): when the package or
                  ;; its native binding is missing we must degrade to
                  ;; circuit-only, not fail the whole module load
                  (-> (dynamic-import "@libp2p/webrtc")
                      (.then (fn [m]
                               (.push transports
                                      ((unchecked-get m "webRTC")
                                       (js-obj "rtcConfiguration" (fn [] (turn/ice-get ice)))))
                               (.push listen "/webrtc")
                               true))
                      (.catch (fn [err]
                                (js/console.warn "webrtc transport unavailable, running circuit-only"
                                                 (when (some? err) (unchecked-get err "message")))
                                false))))
                (.then
                 (fn [webrtc-enabled]
                   (js-await [node (createLibp2p
                                    (js-obj "addresses" (js-obj "listen" listen)
                                            "transports" transports
                                            "connectionEncrypters" #js [(noise)]
                                            "streamMuxers" #js [(yamux)]
                                            "peerDiscovery" #js [(pubsubPeerDiscovery
                                                                  (js-obj "interval" 3000))]
                                            "services" (js-obj "identify" (identify)
                                                                "pubsub" (gossipsub))))]
                     (let [nn (NetNode. node relay webrtc-enabled)]
                       ;; never dial our own sibling nodes
                       (.add (env/self-peer-ids) (.toString (.-peerId ^js node)))
                       (js-await [_ (redial-relay nn)]
                         nn))))))))))))

(unchecked-set NetNode "create" (fn [relay ice opts] (create relay ice opts)))

(let [proto (.-prototype NetNode)]
  (unchecked-set proto "relayConnected" (fn [] (this-as self (relay-connected? self))))
  (unchecked-set proto "redialRelay" (fn [] (this-as self (redial-relay self)))))

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/peer-kit.git