p2p-cljs / src / ird / p2p / impl / ws.cljs
319 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
67
68
(ns ird.p2p.impl.ws
  "One WebSocket over the `ws` npm package. `open` is the default
  :socket-fn of ird.p2p.transport.ws: it connects, sends the Origin header
  when configured, and delivers a socket map

    {:in     channel of {:type :text :data <string>} / {:type :binary :data <b64>};
             closes when the socket closes
     :send!  (fn [text]) → nil, or an error value when the socket is gone
     :close! (fn [code reason]) → nil}

  or an error: :ird.p2p/upgrade-rejected with the HTTP :status when the
  server refused the upgrade (403 origin, 404 unknown host, 429 too many
  connections), :ird.p2p/transport-error otherwise.

  Internal interop namespace."
  (:require [cljs.core.async :as async]
            [ird.p2p.async :as a]
            [ird.p2p.errors :as err]
            [ird.p2p.impl.bytes :as bytes]
            [ird.p2p.impl.node :as node]))

(def ^:private ws-module (delay (js/require "ws")))

(defn open
  "Connect to URL. OPTS: :origin (Origin header), :timeout-ms (handshake
  deadline, default 15000). Channel of a socket map or an error value."
  [{:keys [url origin timeout-ms]}]
  (let [out (async/promise-chan)
        {:keys [put! close!] in :out} (a/unbounded-chan)
        WebSocket (.-WebSocket @ws-module)
        headers (js-obj)
        _ (when origin (aset headers "Origin" origin))
        sock (try (WebSocket. url #js {:headers headers
                                       :handshakeTimeout (or timeout-ms 15000)
                                       :perMessageDeflate false})
                  (catch :default e e))
        settled (atom false)
        settle! (fn [v] (when (compare-and-set! settled false true) (async/put! out v)))]
    (if (node/error? sock)
      (do (async/put! out (err/transport-error (str "WebSocket failed: " (node/error-message sock)) {:url url}))
          out)
      (do
        (.on sock "unexpected-response"
             (fn [_req res]
               (let [status (.-statusCode res)]
                 (.resume res)
                 (settle! (err/upgrade-rejected status))
                 (.terminate sock))))
        (.on sock "error"
             (fn [e]
               (settle! (err/transport-error (str "WebSocket error: " (node/error-message e)) {:url url}))))
        (.on sock "close" (fn [_code _reason] (close!)))
        (.on sock "message"
             (fn [data binary?]
               (if binary?
                 (put! {:type :binary :data (bytes/buffer->b64 data)})
                 (put! {:type :text :data (.toString data "utf8")}))))
        (.on sock "open"
             (fn []
               (settle! {:in in
                         :send! (fn [text]
                                  (try (.send sock text) nil
                                       (catch :default e
                                         (err/transport-error (str "WebSocket send failed: " (node/error-message e)) {}))))
                         :close! (fn [code reason]
                                   (try (.close sock (or code 1000) (or reason "")) (catch :default _ nil))
                                   nil)})))
        out))))

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