peer-kit / src / ardegazu / peer / social / agent.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
;; ported-from: src/social/agent.ts @ v1.3.0
;;
;; The suite's social layer for a headless peer: social-kit's SocialAgent runs
;; unmodified under node/env's browser-global shims (a bot is a tab that is
;; always visible), riding any libp2p pubsub — a game room's node or the bare
;; suite node below. This wrapper assembles the pieces: friend store, agent, and
;; the bot's own friend link.
;;
;; VENDOR RETIREMENT — what this switch inherits. The old src/vendor/social
;; copy was a hand-maintained TypeScript translation, and it had drifted:
;;   * `fsync` envelopes had NO self-origin guard. Inbox room ids are publicly
;;     derivable from an identity pub, so any stranger could seal a validly
;;     signed `fsync` into a peer's inbox and rewrite its friend list and block
;;     tombstones. The real kit checks `from === myPub` in both the agent's
;;     envelope router AND applyFriendEnvelope. Fixed by switching.
;;   * self-sync had no change gate: the vendored copy published AND deposited
;;     on every call. The real kit hashes the payload (soc.ts zeroed), stores
;;     it, and skips the wire unless the payload changed or the last deposit is
;;     older than 20 h (the deposit re-arms the drop's 7-day mailbox TTL). A
;;     long-running bot now deposits far less.
;;   * `profileSync` / `appState` hooks and `syncNow()` did not exist in the
;;     copy at all. They are forwarded/exposed below.
;; Nothing was lost in the other direction: every export of the vendored copy
;; has a same-named counterpart upstream.
(ns ardegazu.peer.social.agent
  (:require ["node:events" :refer (EventEmitter)]
            [ardegazu.id.xkey :as xk]
            [ardegazu.social.friends :as friends]
            [ardegazu.social.presence :as presence]
            [ardegazu.peer.apps :as apps]
            [ardegazu.peer.core.bootstrap :as bootstrap]
            [ardegazu.peer.core.turn :as turn]
            [ardegazu.peer.core.node :as netnode]
            [ardegazu.peer.node.env :as env]
            [shadow.cljs.modern :refer (defclass js-await)]))

(defn suite-friend-link
  "A suite identity's shareable friend link — no agent, no network."
  [suite]
  (-> (js/Promise.resolve nil)
      (.then
       (fn [_]
         (let [identity (unchecked-get suite "identity")]
           (js-await [x (xk/derive-suite-x-key-pair (unchecked-get suite "seed"))]
             (js-await [xs (xk/sign-x-cert identity xk/SUITE-SALT (unchecked-get x "pubB64"))]
               (friends/build-friend-link
                "https://ardegazu.ro/"
                (js-obj "pub" (unchecked-get identity "publicKeyB64")
                        "x" (unchecked-get x "pubB64")
                        "xs" xs
                        "name" (unchecked-get (unchecked-get suite "profile") "name"))))))))))

(defclass Social
  (extends EventEmitter)
  (constructor [this agent store suite node]
    (super)
    (unchecked-set this "agent" agent)
    (unchecked-set this "store" store)
    (unchecked-set this "suite" suite)
    (unchecked-set this "_node" node)))

(let [proto (.-prototype Social)]
  ;; The bare suite node (null when riding an app node's pubsub).
  (js/Object.defineProperty proto "node"
                            #js {:get (fn [] (this-as self (unchecked-get self "_node")))
                                 :configurable true})

  ;; My shareable friend link (paste into the hub to add this bot).
  (unchecked-set proto "friendLink"
                 (fn [] (this-as self (suite-friend-link (unchecked-get self "suite")))))

  (unchecked-set
   proto "stop"
   (fn []
     (this-as self
       (.stop ^js (unchecked-get self "agent"))
       (let [n (unchecked-get self "_node")]
         (if ^boolean (js* "!!(~{})" n)
           (.stop ^js (unchecked-get n "node"))
           (js/Promise.resolve js/undefined)))))))

(defn start
  "opts = #js {:suite :app? :ns? :storageFile? :origin? :pubsub? :autoAcceptFriends?
               :joinLink? :brains? :profileSync? :appState? :webrtc?}"
  [opts]
  (-> (js/Promise.resolve nil)
      (.then
       (fn [_]
         (let [origin (unchecked-get opts "origin")]
           (env/install-origin (if ^boolean (js* "~{} == null" origin) apps/DEFAULT-ORIGIN origin)))
         (env/install-browser-globals (unchecked-get opts "storageFile"))
         (let [ns (let [n (unchecked-get opts "ns")]
                    (if ^boolean (js* "~{} == null" n) "peer" n))
               store (friends/FriendStore. ns)
               suite (unchecked-get opts "suite")
               given-pubsub (let [p (unchecked-get opts "pubsub")]
                              (if ^boolean (js* "~{} == null" p) nil p))]
           (-> (if (some? given-pubsub)
                 (js/Promise.resolve #js [nil given-pubsub])
                 (-> (bootstrap/fetch-relay-info (unchecked-get apps/SIGNAL "descriptorUrl")
                                                 (unchecked-get apps/SIGNAL "relayFallback"))
                     (.then
                      (fn [relay]
                        ;; `webrtc` passes through to the node factory — a
                        ;; headless resident can run circuit-only (undefined
                        ;; keeps the browser-parity default of true)
                        (js-await [node (netnode/create
                                         relay
                                         (turn/IceConfig. (unchecked-get apps/SIGNAL "turnUrl")
                                                          js/undefined)
                                         (js-obj "webrtc" (unchecked-get opts "webrtc")))]
                          (let [^js n (unchecked-get node "node")]
                            ;; gossipsub only meshes between connected peers and
                            ;; the relay doesn't forward arbitrary topics — dial
                            ;; whoever discovery surfaces (except this process's
                            ;; own sibling nodes)
                            (.addEventListener
                             n "peer:discovery"
                             (fn [e]
                               (let [pid (unchecked-get (.-detail ^js e) "id")]
                                 (when-not (.has (env/self-peer-ids) (.toString ^js pid))
                                   (.catch (.dial n pid
                                                  (js-obj "signal" (js/AbortSignal.timeout 15000)))
                                           (fn [_] nil))))
                               js/undefined))
                            #js [node (unchecked-get (unchecked-get n "services") "pubsub")]))))))
               (.then
                (fn [pair]
                  (let [node (aget pair 0)
                        pubsub (aget pair 1)
                        holder (volatile! nil)
                        emit (fn [& args]
                               (let [s @holder]
                                 (when (some? s) (.apply (.-emit ^js s) s (into-array args))))
                               js/undefined)
                        agent-holder (volatile! nil)
                        agent (presence/SocialAgent.
                               (js-obj
                                "ns" ns
                                "app" (let [a (unchecked-get opts "app")]
                                        (if ^boolean (js* "~{} == null" a) "ardegazu.ro" a))
                                "identity" (unchecked-get suite "identity")
                                "seed" (unchecked-get suite "seed")
                                "myName" (fn []
                                           (let [n (unchecked-get (unchecked-get suite "profile")
                                                                  "name")]
                                             (if ^boolean (js* "!!(~{})" n) n "peer")))
                                "store" store
                                "pubsub" pubsub
                                "joinLink" (unchecked-get opts "joinLink")
                                ;; forwarded to social-kit 2.1.0+: the brains
                                ;; thunk (re-read every presence beacon) and the
                                ;; 1.2.0 self-sync hooks the vendored copy lacked
                                "brains" (unchecked-get opts "brains")
                                "profileSync" (unchecked-get opts "profileSync")
                                "appState" (unchecked-get opts "appState")
                                "events"
                                (js-obj
                                 "presence" (fn [pub info] (emit "presence" pub info))
                                 "invite" (fn [inv] (emit "invite" inv))
                                 "request" (fn [f]
                                             (emit "request" f)
                                             (when-not (identical?
                                                        false
                                                        (unchecked-get opts "autoAcceptFriends"))
                                               (.acceptRequest ^js @agent-holder
                                                               (unchecked-get f "pub")))
                                             js/undefined)
                                 "accepted" (fn [f] (emit "accepted" f)))))]
                    (vreset! agent-holder agent)
                    (let [social (Social. agent store suite node)]
                      (vreset! holder social)
                      (js-await [_ (.start ^js agent)]
                        social)))))))))))

(unchecked-set Social "start" (fn [opts] (start opts)))

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