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)))))
|