update client & server send fn's to allow sending even non-map data
i personally think this was an important change as the default encoding of maps to be sent as json is somewhat lossy (keywords converted to strings, would never be able to get this 100% perfect using json alone without a more complex encoding solution). encoding to edn is the perfect solution to this. since browserchannel pretty much requires the final sent data to be json encoded, we just serialize the clojure data being sent to an edn string within a root json object. this does ultimately mean we have to do a little bit of pre-parsing of sent and received maps to encode/decode properly and make it completely transparent to application code, but i think it's an acceptable tradeoff.
This commit is contained in:
parent
b86d048e64
commit
6674d171c3
|
@ -9,7 +9,7 @@
|
|||
(enable-console-print!)
|
||||
|
||||
(defn say [text]
|
||||
(browserchannel/send-map {:msg text}))
|
||||
(browserchannel/send-data {:msg text}))
|
||||
|
||||
(defn toggle-element [elem]
|
||||
(dom/toggle-attr! elem :disabled (not (dom/attr elem :disabled))))
|
||||
|
@ -37,10 +37,9 @@
|
|||
|
||||
:on-receive
|
||||
(fn [data]
|
||||
(let [msg (get data "msg")]
|
||||
(dom/append! (by-id "room")
|
||||
(-> (dom/create-element "div")
|
||||
(dom/set-text! (str "MSG::" msg))))))})
|
||||
(dom/append! (by-id "room")
|
||||
(-> (dom/create-element "div")
|
||||
(dom/set-text! (str "MSG::" (:msg data))))))})
|
||||
|
||||
(defn ^:export run []
|
||||
(browserchannel/init! event-handlers))
|
|
@ -16,17 +16,17 @@
|
|||
{:on-open
|
||||
(fn [session-id request]
|
||||
(println "client" session-id "connected")
|
||||
(browserchannel/send-map-to-all {"msg" (str "client " session-id " connected")}))
|
||||
(browserchannel/send-data-to-all {:msg (str "client " session-id " connected")}))
|
||||
|
||||
:on-close
|
||||
(fn [session-id request reason]
|
||||
(println "client" session-id "disconnected. reason: " reason)
|
||||
(browserchannel/send-map-to-all {"msg" (str "client " session-id " disconnected. reason: " reason)}))
|
||||
(browserchannel/send-data-to-all {:msg (str "client " session-id " disconnected. reason: " reason)}))
|
||||
|
||||
:on-receive
|
||||
(fn [session-id request m]
|
||||
(println "client" session-id "sent" m)
|
||||
(browserchannel/send-map-to-all m))})
|
||||
(browserchannel/send-data-to-all m))})
|
||||
|
||||
(def app-routes
|
||||
(routes
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
(ns net.thegeez.browserchannel.client
|
||||
(:require
|
||||
[cljs.reader :as reader]
|
||||
[dommy.core :refer-macros [sel1]]
|
||||
goog.net.BrowserChannel
|
||||
goog.net.BrowserChannel.Handler
|
||||
|
@ -33,15 +34,32 @@
|
|||
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 encode-map
|
||||
[data]
|
||||
(doto (js-obj)
|
||||
(aset "__edn" (pr-str data))))
|
||||
|
||||
(defn- array-of-queued-map->clj
|
||||
(defn decode-map
|
||||
[m]
|
||||
(let [m (js->clj m)]
|
||||
(if (contains? m "__edn")
|
||||
(reader/read-string (str (get m "__edn")))
|
||||
m)))
|
||||
|
||||
(defn decode-queued-map
|
||||
[queued-map]
|
||||
(let [m (js->clj queued-map)
|
||||
data (get m "map")]
|
||||
(merge
|
||||
{:context (get m "context")
|
||||
:map-id (get m "mapId")}
|
||||
(if (contains? data "__edn")
|
||||
{:data (reader/read-string (str (get data "__edn")))}
|
||||
{:map data}))))
|
||||
|
||||
(defn decode-queued-map-array
|
||||
[queued-map-array]
|
||||
(mapv queued-map->clj queued-map-array))
|
||||
(mapv decode-queued-map queued-map-array))
|
||||
|
||||
(defn channel-state []
|
||||
(.getState channel))
|
||||
|
@ -56,9 +74,10 @@
|
|||
(.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 send-data
|
||||
[data & [{:keys [on-success]}]]
|
||||
(if data
|
||||
(.sendMap channel (encode-map data) {:on-success on-success})))
|
||||
|
||||
(defn connect!
|
||||
[& [{:keys [base] :as options}]]
|
||||
|
@ -89,16 +108,16 @@
|
|||
(set! (.-channelClosed handler)
|
||||
(fn [ch pending undelivered]
|
||||
(if on-close
|
||||
(on-close (array-of-queued-map->clj pending)
|
||||
(array-of-queued-map->clj undelivered)))))
|
||||
(on-close (decode-queued-map-array pending)
|
||||
(decode-queued-map-array undelivered)))))
|
||||
(set! (.-channelHandleArray handler)
|
||||
(fn [ch m]
|
||||
(if on-receive
|
||||
(on-receive (js->clj m)))))
|
||||
(on-receive (decode-map m)))))
|
||||
(set! (.-channelSuccess handler)
|
||||
(fn [ch delivered]
|
||||
(if on-sent
|
||||
(on-sent (array-of-queued-map->clj delivered)))
|
||||
(on-sent (decode-queued-map-array delivered)))
|
||||
(doseq [m delivered]
|
||||
(let [{:keys [on-success] :as context} (aget m "context")]
|
||||
(if on-success
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
(:import
|
||||
[java.util.concurrent ScheduledExecutorService Executors TimeUnit Callable ScheduledFuture])
|
||||
(:require
|
||||
[ring.middleware.params :as params]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.data.json :as json]
|
||||
[ring.middleware.params :as params]
|
||||
[net.thegeez.browserchannel.async-adapter :as async-adapter]))
|
||||
|
||||
;; @todo: out of order acks and maps - AKH the maps at least is taken care of.
|
||||
|
@ -139,6 +140,16 @@
|
|||
[p]
|
||||
(str "[" (first p) "," (second p) "]"))
|
||||
|
||||
(defn- decode-map
|
||||
[m]
|
||||
(if (contains? m "__edn")
|
||||
(edn/read-string (get m "__edn"))
|
||||
m))
|
||||
|
||||
(defn- encode-map
|
||||
[data]
|
||||
{"__edn" (pr-str data)})
|
||||
|
||||
;;;;;; end of utils
|
||||
|
||||
|
||||
|
@ -583,25 +594,27 @@
|
|||
;; convience function to send data to a session
|
||||
;; the data will be queued until there is a backchannel to send it
|
||||
;; over
|
||||
(defn- send-string
|
||||
[session-id string]
|
||||
(defn- send-map
|
||||
[session-id m]
|
||||
(when-let [session-agent (get @sessions session-id)]
|
||||
(send-off session-agent #(-> %
|
||||
(queue-string string)
|
||||
flush-buffer))))
|
||||
(let [string (json/write-str m)]
|
||||
(send-off session-agent #(-> %
|
||||
(queue-string string)
|
||||
flush-buffer)))))
|
||||
|
||||
(defn send-map
|
||||
"sends a map to the client identified by session-id over the backchannel.
|
||||
(defn send-data
|
||||
"sends data to the client identified by session-id over the backchannel.
|
||||
if there is currently no available backchannel for this client, the data
|
||||
is queued until one is available."
|
||||
[session-id m]
|
||||
(send-string session-id (json/write-str m)))
|
||||
[session-id data]
|
||||
(if data
|
||||
(send-map session-id (encode-map data))))
|
||||
|
||||
(defn send-map-to-all
|
||||
"sends a map to all currently connected clients over their backchannels."
|
||||
[m]
|
||||
(defn send-data-to-all
|
||||
"sends data to all currently connected clients over their backchannels."
|
||||
[data]
|
||||
(doseq [[session-id _] @sessions]
|
||||
(send-map session-id m)))
|
||||
(send-data session-id data)))
|
||||
|
||||
(defn connected?
|
||||
"returns true if a client with the given session-id is currently connected."
|
||||
|
@ -714,8 +727,9 @@
|
|||
;; backchannelPresent = 0 for false, 1 for true
|
||||
;; send as json for XHR and IE
|
||||
(do
|
||||
(doseq [map maps]
|
||||
(notify-listeners (:id @session-agent) req :map map))
|
||||
(doseq [m maps]
|
||||
(let [decoded (decode-map m)]
|
||||
(notify-listeners (:id @session-agent) req :map decoded)))
|
||||
(let [status (session-status @session-agent)]
|
||||
{:status 200
|
||||
:headers (:headers options)
|
||||
|
|
Reference in a new issue