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:
Gered 2016-05-09 12:45:17 -04:00
parent b86d048e64
commit 6674d171c3
4 changed files with 70 additions and 38 deletions

View file

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

View file

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

View file

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

View file

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