add clojurescript client-side browserchannel api
This commit is contained in:
parent
161e7c3999
commit
a4b2e14897
|
@ -1,70 +1,53 @@
|
|||
(ns chat-demo.client
|
||||
(:require
|
||||
[net.thegeez.browserchannel.client :as browserchannel]
|
||||
[dommy.core :as dom :refer-macros [by-id]]
|
||||
goog.net.BrowserChannel
|
||||
goog.events.KeyHandler
|
||||
[goog.events.KeyCodes :as key-codes]
|
||||
[goog.events :as events]))
|
||||
|
||||
(defonce channel (goog.net.BrowserChannel.))
|
||||
(enable-console-print!)
|
||||
|
||||
(defn say [text]
|
||||
(.sendMap channel (clj->js {:msg text})))
|
||||
(browserchannel/send-map {:msg text}))
|
||||
|
||||
(defn toggle-element [elem]
|
||||
(if (dom/attr elem :disabled)
|
||||
(dom/remove-attr! elem :disabled)
|
||||
(dom/set-attr! elem :disabled)))
|
||||
|
||||
(defn enable-chat []
|
||||
(let [msg-input (by-id "msg-input")
|
||||
send-button (by-id "send-button")
|
||||
send-message (fn [e]
|
||||
(say (dom/value msg-input))
|
||||
(dom/set-value! msg-input ""))]
|
||||
(toggle-element msg-input)
|
||||
(toggle-element send-button)
|
||||
(events/listen
|
||||
(goog.events.KeyHandler. msg-input)
|
||||
"key"
|
||||
(fn [e]
|
||||
(when (= (.-keyCode e) key-codes/ENTER)
|
||||
(send-message e))))
|
||||
(events/listen
|
||||
send-button
|
||||
"click"
|
||||
send-message)))
|
||||
(def handler
|
||||
{:on-open
|
||||
(fn []
|
||||
(let [msg-input (by-id "msg-input")
|
||||
send-button (by-id "send-button")
|
||||
send-message (fn [e]
|
||||
(say (dom/value msg-input))
|
||||
(dom/set-value! msg-input ""))]
|
||||
(toggle-element msg-input)
|
||||
(toggle-element send-button)
|
||||
(events/listen
|
||||
(goog.events.KeyHandler. msg-input)
|
||||
"key"
|
||||
(fn [e]
|
||||
(when (= (.-keyCode e) key-codes/ENTER)
|
||||
(send-message e))))
|
||||
(events/listen
|
||||
send-button
|
||||
"click"
|
||||
send-message)))
|
||||
|
||||
(defn handler []
|
||||
(let [h (goog.net.BrowserChannel.Handler.)]
|
||||
(set! (.-channelOpened h)
|
||||
(fn [channel]
|
||||
(enable-chat)))
|
||||
(set! (.-channelHandleArray h)
|
||||
(fn [channel data]
|
||||
(let [data (js->clj data)
|
||||
msg (get data "msg")]
|
||||
(dom/append! (by-id "room")
|
||||
(-> (dom/create-element "div")
|
||||
(dom/set-text! (str "MSG::" msg)))))))
|
||||
h))
|
||||
:on-receive
|
||||
(fn [data]
|
||||
(let [msg (get data "msg")]
|
||||
(dom/append! (by-id "room")
|
||||
(-> (dom/create-element "div")
|
||||
(dom/set-text! (str "MSG::" msg))))))})
|
||||
|
||||
(defn ^:export run []
|
||||
(events/listen
|
||||
js/window "unload"
|
||||
(fn []
|
||||
(.disconnect channel)
|
||||
(events/removeAll)))
|
||||
|
||||
; disable logging
|
||||
(doto (.. channel getChannelDebug getLogger)
|
||||
(.setLevel goog.debug.Logger.Level.OFF))
|
||||
|
||||
; or if you would like to see a ton of browserchannel logging output, uncomment this
|
||||
#_(doto (.. channel getChannelDebug getLogger)
|
||||
(.setLevel goog.debug.Logger.Level.FINER)
|
||||
(.addHandler #(js/console.log %)))
|
||||
|
||||
(doto channel
|
||||
(.setHandler (handler))
|
||||
(.connect "/channel/test" "/channel/bind")))
|
||||
(browserchannel/init! handler))
|
|
@ -1,7 +1,9 @@
|
|||
(defproject gered/clj-browserchannel-server "0.2.2"
|
||||
:description "BrowserChannel server implementation in Clojure"
|
||||
:dependencies [[ring/ring-core "1.4.0"]
|
||||
[org.clojure/data.json "0.2.6"]]
|
||||
[org.clojure/data.json "0.2.6"]
|
||||
[prismatic/dommy "1.1.0"]]
|
||||
:profiles {:provided
|
||||
{:dependencies
|
||||
[[org.clojure/clojure "1.8.0"]]}})
|
||||
[[org.clojure/clojure "1.8.0"]
|
||||
[org.clojure/clojurescript "1.8.51"]]}})
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
(ns net.thegeez.browserchannel.client
|
||||
(:require
|
||||
[dommy.core :refer-macros [sel1]]
|
||||
goog.net.BrowserChannel
|
||||
goog.net.BrowserChannel.Handler
|
||||
[goog.net.BrowserChannel.State :as bc-state]
|
||||
[goog.events :as events]
|
||||
[goog.debug.Logger.Level :as log-level]))
|
||||
|
||||
(defonce channel (goog.net.BrowserChannel.))
|
||||
|
||||
(def default-options
|
||||
{:base "/channel"
|
||||
:allow-chunked-mode? true
|
||||
:allow-host-prefix? true
|
||||
:fail-fast? false
|
||||
:max-back-channel-retries 3
|
||||
:max-forward-channel-retries 2
|
||||
:forward-channel-request-timeout (* 20 1000)
|
||||
:verbose-logging? false})
|
||||
|
||||
; see: https://google.github.io/closure-library/api/source/closure/goog/net/browserchannel.js.src.html#l521
|
||||
(def ^:private bch-error-enum-to-keyword
|
||||
{0 :ok
|
||||
2 :request-failed
|
||||
4 :logged-out
|
||||
5 :no-data
|
||||
6 :unknown-session-id
|
||||
7 :stop
|
||||
8 :network
|
||||
9 :blocked
|
||||
10 :bad-data
|
||||
11 :bad-response
|
||||
12 :active-x-blocked})
|
||||
|
||||
(defn- queued-map->clj
|
||||
[queued-map]
|
||||
{:context (aget queued-map "context")
|
||||
:map (js->clj (aget queued-map "map"))
|
||||
:map-id (aget queued-map "mapId")})
|
||||
|
||||
(defn- array-of-queued-map->clj
|
||||
[queued-map-array]
|
||||
(mapv queued-map->clj queued-map-array))
|
||||
|
||||
(defn channel-state []
|
||||
(.getState channel))
|
||||
|
||||
(defn connected?
|
||||
[]
|
||||
(= (channel-state) bc-state/OPENED))
|
||||
|
||||
(defn set-debug-log!
|
||||
[level & [f]]
|
||||
(doto (.. channel getChannelDebug getLogger)
|
||||
(.setLevel level)
|
||||
(.addHandler (or f #(js/console.log %)))))
|
||||
|
||||
(defn send-map
|
||||
[m & [{:keys [on-success]}]]
|
||||
(.sendMap channel (clj->js m) {:on-success on-success}))
|
||||
|
||||
(defn connect!
|
||||
[& [{:keys [base] :as options}]]
|
||||
(let [state (channel-state)]
|
||||
(if (or (= state bc-state/CLOSED)
|
||||
(= state bc-state/INIT))
|
||||
(.connect channel
|
||||
(str base "/test")
|
||||
(str base "/bind")))))
|
||||
|
||||
(defn disconnect!
|
||||
[]
|
||||
(if-not (= (channel-state) bc-state/CLOSED)
|
||||
(.disconnect channel)))
|
||||
|
||||
(defn- get-anti-forgery-token
|
||||
[]
|
||||
(if-let [tag (sel1 "meta[name='anti-forgery-token']")]
|
||||
(.-content tag)))
|
||||
|
||||
(defn ->handler
|
||||
[{:keys [on-open on-close on-receive on-sent on-error]}]
|
||||
(let [handler (goog.net.BrowserChannel.Handler.)]
|
||||
(set! (.-channelOpened handler)
|
||||
(fn [ch]
|
||||
(if on-open
|
||||
(on-open))))
|
||||
(set! (.-channelClosed handler)
|
||||
(fn [ch pending undelivered]
|
||||
(if on-close
|
||||
(on-close (array-of-queued-map->clj pending)
|
||||
(array-of-queued-map->clj undelivered)))))
|
||||
(set! (.-channelHandleArray handler)
|
||||
(fn [ch m]
|
||||
(if on-receive
|
||||
(on-receive (js->clj m)))))
|
||||
(set! (.-channelSuccess handler)
|
||||
(fn [ch delivered]
|
||||
(if on-sent
|
||||
(on-sent (array-of-queued-map->clj delivered)))
|
||||
(doseq [m delivered]
|
||||
(let [{:keys [on-success] :as context} (aget m "context")]
|
||||
(if on-success
|
||||
(on-success))))))
|
||||
(set! (.-channelError handler)
|
||||
(fn [ch error-code]
|
||||
(if on-error
|
||||
(on-error (get bch-error-enum-to-keyword error-code :unknown)))))
|
||||
handler))
|
||||
|
||||
(defn- apply-options!
|
||||
[options]
|
||||
(set-debug-log! (if (:verbose-logging? options) log-level/FINER log-level/OFF))
|
||||
(.setAllowChunkedMode channel (boolean (:allow-chunked-mode? options)))
|
||||
(.setAllowHostPrefix channel (boolean (:allow-host-prefix? options)))
|
||||
(.setFailFast channel (boolean (:fail-fast? options)))
|
||||
(.setForwardChannelMaxRetries channel (:max-forward-channel-retries options))
|
||||
(.setForwardChannelRequestTimeout channel (:forward-channel-request-timeout options))
|
||||
;; HACK: this is relying on changing a value for a setting that google's
|
||||
;; documentation lists as private. however, it is a fairly important
|
||||
;; setting to be able to change, so i think it's worth the risk...
|
||||
(set! goog.net.BrowserChannel/BACK_CHANNEL_MAX_RETRIES (:max-back-channel-retries options)))
|
||||
|
||||
(defn init!
|
||||
[handler & [options]]
|
||||
(let [options (merge default-options options)]
|
||||
(events/listen
|
||||
js/window "unload"
|
||||
(fn []
|
||||
(disconnect!)))
|
||||
|
||||
(.setHandler channel (->handler handler))
|
||||
(apply-options! options)
|
||||
(let [csrf-token (get-anti-forgery-token)
|
||||
headers (merge
|
||||
(:headers options)
|
||||
(if csrf-token {"X-CSRF-Token" csrf-token}))]
|
||||
(.setExtraHeaders channel (clj->js headers)))
|
||||
(connect! options)))
|
Reference in a new issue