;; ported-from: src/core/turn.ts @ v1.3.0
;;
;; Fetches ephemeral TURN credentials from the signaling host (coturn REST
;; scheme) and builds RTCConfiguration. Caches until 5 minutes before expiry.
(ns ardegazu.peer.core.turn
(:require [shadow.cljs.modern :refer (defclass js-await)]))
(defclass IceConfig
(constructor [this endpoint force-relay]
(unchecked-set this "_endpoint" endpoint)
;; `forceRelay = false` was a JS default parameter
(unchecked-set this "_forceRelay" (if (identical? force-relay js/undefined) false force-relay))
(unchecked-set this "_cached" nil)
(unchecked-set this "_validUntil" 0) ; refetch after this timestamp
(unchecked-set this "_inflight" nil)))
(defn- ice-fetch [self]
(-> (js/Promise.resolve nil)
(.then
(fn [_]
(js-await [res (js/fetch (unchecked-get self "_endpoint"))]
(do
(when-not (.-ok ^js res)
(throw (js/Error. (str "turn-credentials " (.-status ^js res)))))
(js-await [c (.json ^js res)]
(let [cfg (js-obj "iceServers"
#js [(js-obj "urls" (unchecked-get c "urls")
"username" (unchecked-get c "username")
"credential" (unchecked-get c "credential"))])]
(when (unchecked-get self "_forceRelay")
(unchecked-set cfg "iceTransportPolicy" "relay"))
(unchecked-set self "_cached" cfg)
;; renew 5 min before expiry
(unchecked-set self "_validUntil"
(- (+ (js/Date.now) (* (unchecked-get c "ttl") 1000)) 300000))
cfg))))))
(.catch
(fn [err]
;; Credential fetch failing shouldn't block LAN/host-candidate connections.
(js/console.warn "turn credentials unavailable, continuing without ICE servers" err)
(when-not (unchecked-get self "_cached")
(unchecked-set self "_cached"
(if (unchecked-get self "_forceRelay")
(js-obj "iceTransportPolicy" "relay")
(js-obj))))
(unchecked-set self "_validUntil" (+ (js/Date.now) 30000)) ; retry soon, don't storm
(unchecked-get self "_cached")))))
(defn ice-get [self]
(if (and (unchecked-get self "_cached")
(< (js/Date.now) (unchecked-get self "_validUntil")))
(js/Promise.resolve (unchecked-get self "_cached"))
(do
;; `#inflight ??= ...` — one in-flight fetch shared by every caller
(when-not (unchecked-get self "_inflight")
(unchecked-set self "_inflight"
(.finally (ice-fetch self)
(fn [] (unchecked-set self "_inflight" nil)))))
(unchecked-get self "_inflight"))))
(unchecked-set (.-prototype IceConfig) "get" (fn [] (this-as self (ice-get self))))