add clojurescript client-side browserchannel api

This commit is contained in:
Gered 2016-05-08 14:34:22 -04:00
parent 161e7c3999
commit a4b2e14897
3 changed files with 174 additions and 49 deletions

View file

@ -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))

View file

@ -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"]]}})

View file

@ -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)))