p2p-cljs / src / ird / p2p / turn.cljs
334 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
(ns ird.p2p.turn
  "TURN credentials from `GET /turn-credentials`, cached and renewed: five
  minutes before expiry, one in-flight fetch shared by all callers,
  failures cached for 30 seconds and degraded to an empty server list
  (direct connections still work). The result is the ICE configuration
  handed to the peer-connection factory:

    {:servers [{:urls [...] :username ... :credential ...}] :relay-only? false}"
  (:require [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]
            [ird.p2p.impl.node :as node]))

(def renew-before-ms 300000)
(def failure-retry-ms 30000)

(defn make-cache
  "OPTS: :url (nil → no TURN), :origin, :http-fn, :now-fn, :relay-only?, :timeout-ms."
  [{:keys [url origin http-fn now-fn relay-only? timeout-ms]}]
  {:url url :origin origin :http-fn (or http-fn http/fetch-http) :now-fn (or now-fn node/now-ms)
   :relay-only? (boolean relay-only?) :timeout-ms (or timeout-ms 10000)
   :cached (atom nil) :inflight (atom nil)})

(defn- fetch-config [{:keys [url origin http-fn now-fn relay-only? timeout-ms cached]}]
  (go
    (if (nil? url)
      (let [config {:servers [] :relay-only? relay-only?}]
        (reset! cached {:config config :valid-until (+ (now-fn) 3600000)})
        config)
      (let [resp (<! (http-fn {:method :get :url url
                               :headers (cond-> {"Accept" "application/json"} origin (assoc "Origin" origin))
                               :timeout-ms timeout-ms}))
            body (when (and (map? resp) (<= 200 (:status resp) 299)) (json/parse (:body resp)))]
        (if (and (map? body) (string? (:username body)) (string? (:credential body)) (vector? (:urls body)))
          (let [config {:servers [{:urls (:urls body) :username (:username body) :credential (:credential body)}]
                        :relay-only? relay-only?}
                ttl (* 1000 (max 0 (or (:ttl body) 0)))
                valid (if (> ttl renew-before-ms) (- ttl renew-before-ms) (quot ttl 2))]
            (reset! cached {:config config :valid-until (+ (now-fn) valid)})
            config)
          (let [config {:servers [] :relay-only? relay-only?}]
            (reset! cached {:config config :valid-until (+ (now-fn) failure-retry-ms)})
            config))))))

(defn configuration
  "The current ICE configuration (fetching when stale). Channel."
  [{:keys [cached inflight now-fn] :as cache}]
  (go
    (let [c @cached]
      (cond
        (and c (< (now-fn) (:valid-until c))) (:config c)
        @inflight (<! @inflight)
        :else (let [ch (async/promise-chan)]
                (reset! inflight ch)
                (let [config (<! (fetch-config cache))]
                  (a/deliver! ch config)
                  (reset! inflight nil)
                  config))))))

(defn invalidate!
  "Drop the cache so the next call fetches again (e.g. after an ICE failure)."
  [{:keys [cached]}]
  (reset! cached nil)
  nil)

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