;; ported-from: src/lib/turn.ts @ v1.0.0
;;
;; Fetches ephemeral TURN credentials from the signaling host (coturn REST
;; scheme) and builds RTCConfiguration. Caches until 5 minutes before expiry.
(ns ardegazu.rooms.lib.turn
(:require [ardegazu.rooms.js :as j]
[shadow.cljs.modern :refer (defclass js-await)]))
(defclass IceConfig
(constructor [this endpoint force-relay]
(unchecked-set this "_endpoint" endpoint)
;; TS default parameter: `forceRelay = false`
(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- do-fetch [self]
(-> (js/Promise.resolve nil)
(.then
(fn [_]
(js-await [res (js/fetch (unchecked-get self "_endpoint"))]
(if-not (j/truthy? (unchecked-get res "ok"))
(throw (js/Error. (str "turn-credentials " (unchecked-get res "status"))))
(js-await [c (.json ^js res)]
(do
(unchecked-set
self "_cached"
(let [out (j/ordered
"iceServers" #js [(j/ordered "urls" (unchecked-get c "urls")
"username" (unchecked-get c "username")
"credential" (unchecked-get c "credential"))])]
(when (j/truthy? (unchecked-get self "_forceRelay"))
(unchecked-set out "iceTransportPolicy" "relay"))
out))
;; renew 5 min before expiry
(unchecked-set self "_validUntil"
(- (+ (js/Date.now) (* (unchecked-get c "ttl") 1000)) 300000))
js/undefined))))))
(.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 (j/truthy? (unchecked-get self "_cached"))
(unchecked-set self "_cached"
(if (j/truthy? (unchecked-get self "_forceRelay"))
(j/ordered "iceTransportPolicy" "relay")
(j/ordered))))
;; retry soon, but don't storm
(unchecked-set self "_validUntil" (+ (js/Date.now) 30000))
js/undefined))
(.then (fn [_] (unchecked-get self "_cached")))))
(defn get-config [self]
(if (and (j/truthy? (unchecked-get self "_cached"))
(< (js/Date.now) (unchecked-get self "_validUntil")))
(js/Promise.resolve (unchecked-get self "_cached"))
(do
;; TS `this.#inflight ??= this.#fetch().finally(() => (this.#inflight = null))`
(when-not (j/truthy? (unchecked-get self "_inflight"))
(unchecked-set self "_inflight"
(.finally (do-fetch self)
(fn [] (unchecked-set self "_inflight" nil)))))
(unchecked-get self "_inflight"))))
(unchecked-set (.-prototype IceConfig) "get" (fn [] (this-as self (get-config self))))