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!)
|
(enable-console-print!)
|
||||||
|
|
||||||
(defn say [text]
|
(defn say [text]
|
||||||
(browserchannel/send-map {:msg text}))
|
(browserchannel/send-data {:msg text}))
|
||||||
|
|
||||||
(defn toggle-element [elem]
|
(defn toggle-element [elem]
|
||||||
(dom/toggle-attr! elem :disabled (not (dom/attr elem :disabled))))
|
(dom/toggle-attr! elem :disabled (not (dom/attr elem :disabled))))
|
||||||
|
@ -37,10 +37,9 @@
|
||||||
|
|
||||||
:on-receive
|
:on-receive
|
||||||
(fn [data]
|
(fn [data]
|
||||||
(let [msg (get data "msg")]
|
(dom/append! (by-id "room")
|
||||||
(dom/append! (by-id "room")
|
(-> (dom/create-element "div")
|
||||||
(-> (dom/create-element "div")
|
(dom/set-text! (str "MSG::" (:msg data))))))})
|
||||||
(dom/set-text! (str "MSG::" msg))))))})
|
|
||||||
|
|
||||||
(defn ^:export run []
|
(defn ^:export run []
|
||||||
(browserchannel/init! event-handlers))
|
(browserchannel/init! event-handlers))
|
|
@ -16,17 +16,17 @@
|
||||||
{:on-open
|
{:on-open
|
||||||
(fn [session-id request]
|
(fn [session-id request]
|
||||||
(println "client" session-id "connected")
|
(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
|
:on-close
|
||||||
(fn [session-id request reason]
|
(fn [session-id request reason]
|
||||||
(println "client" session-id "disconnected. reason: " 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
|
:on-receive
|
||||||
(fn [session-id request m]
|
(fn [session-id request m]
|
||||||
(println "client" session-id "sent" m)
|
(println "client" session-id "sent" m)
|
||||||
(browserchannel/send-map-to-all m))})
|
(browserchannel/send-data-to-all m))})
|
||||||
|
|
||||||
(def app-routes
|
(def app-routes
|
||||||
(routes
|
(routes
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
(ns net.thegeez.browserchannel.client
|
(ns net.thegeez.browserchannel.client
|
||||||
(:require
|
(:require
|
||||||
|
[cljs.reader :as reader]
|
||||||
[dommy.core :refer-macros [sel1]]
|
[dommy.core :refer-macros [sel1]]
|
||||||
goog.net.BrowserChannel
|
goog.net.BrowserChannel
|
||||||
goog.net.BrowserChannel.Handler
|
goog.net.BrowserChannel.Handler
|
||||||
|
@ -33,15 +34,32 @@
|
||||||
11 :bad-response
|
11 :bad-response
|
||||||
12 :active-x-blocked})
|
12 :active-x-blocked})
|
||||||
|
|
||||||
(defn- queued-map->clj
|
(defn encode-map
|
||||||
[queued-map]
|
[data]
|
||||||
{:context (aget queued-map "context")
|
(doto (js-obj)
|
||||||
:map (js->clj (aget queued-map "map"))
|
(aset "__edn" (pr-str data))))
|
||||||
:map-id (aget queued-map "mapId")})
|
|
||||||
|
|
||||||
(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]
|
[queued-map-array]
|
||||||
(mapv queued-map->clj queued-map-array))
|
(mapv decode-queued-map queued-map-array))
|
||||||
|
|
||||||
(defn channel-state []
|
(defn channel-state []
|
||||||
(.getState channel))
|
(.getState channel))
|
||||||
|
@ -56,9 +74,10 @@
|
||||||
(.setLevel level)
|
(.setLevel level)
|
||||||
(.addHandler (or f #(js/console.log %)))))
|
(.addHandler (or f #(js/console.log %)))))
|
||||||
|
|
||||||
(defn send-map
|
(defn send-data
|
||||||
[m & [{:keys [on-success]}]]
|
[data & [{:keys [on-success]}]]
|
||||||
(.sendMap channel (clj->js m) {:on-success on-success}))
|
(if data
|
||||||
|
(.sendMap channel (encode-map data) {:on-success on-success})))
|
||||||
|
|
||||||
(defn connect!
|
(defn connect!
|
||||||
[& [{:keys [base] :as options}]]
|
[& [{:keys [base] :as options}]]
|
||||||
|
@ -89,16 +108,16 @@
|
||||||
(set! (.-channelClosed handler)
|
(set! (.-channelClosed handler)
|
||||||
(fn [ch pending undelivered]
|
(fn [ch pending undelivered]
|
||||||
(if on-close
|
(if on-close
|
||||||
(on-close (array-of-queued-map->clj pending)
|
(on-close (decode-queued-map-array pending)
|
||||||
(array-of-queued-map->clj undelivered)))))
|
(decode-queued-map-array undelivered)))))
|
||||||
(set! (.-channelHandleArray handler)
|
(set! (.-channelHandleArray handler)
|
||||||
(fn [ch m]
|
(fn [ch m]
|
||||||
(if on-receive
|
(if on-receive
|
||||||
(on-receive (js->clj m)))))
|
(on-receive (decode-map m)))))
|
||||||
(set! (.-channelSuccess handler)
|
(set! (.-channelSuccess handler)
|
||||||
(fn [ch delivered]
|
(fn [ch delivered]
|
||||||
(if on-sent
|
(if on-sent
|
||||||
(on-sent (array-of-queued-map->clj delivered)))
|
(on-sent (decode-queued-map-array delivered)))
|
||||||
(doseq [m delivered]
|
(doseq [m delivered]
|
||||||
(let [{:keys [on-success] :as context} (aget m "context")]
|
(let [{:keys [on-success] :as context} (aget m "context")]
|
||||||
(if on-success
|
(if on-success
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
(:import
|
(:import
|
||||||
[java.util.concurrent ScheduledExecutorService Executors TimeUnit Callable ScheduledFuture])
|
[java.util.concurrent ScheduledExecutorService Executors TimeUnit Callable ScheduledFuture])
|
||||||
(:require
|
(:require
|
||||||
[ring.middleware.params :as params]
|
[clojure.edn :as edn]
|
||||||
[clojure.data.json :as json]
|
[clojure.data.json :as json]
|
||||||
|
[ring.middleware.params :as params]
|
||||||
[net.thegeez.browserchannel.async-adapter :as async-adapter]))
|
[net.thegeez.browserchannel.async-adapter :as async-adapter]))
|
||||||
|
|
||||||
;; @todo: out of order acks and maps - AKH the maps at least is taken care of.
|
;; @todo: out of order acks and maps - AKH the maps at least is taken care of.
|
||||||
|
@ -139,6 +140,16 @@
|
||||||
[p]
|
[p]
|
||||||
(str "[" (first p) "," (second 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
|
;;;;;; end of utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -583,25 +594,27 @@
|
||||||
;; convience function to send data to a session
|
;; convience function to send data to a session
|
||||||
;; the data will be queued until there is a backchannel to send it
|
;; the data will be queued until there is a backchannel to send it
|
||||||
;; over
|
;; over
|
||||||
(defn- send-string
|
(defn- send-map
|
||||||
[session-id string]
|
[session-id m]
|
||||||
(when-let [session-agent (get @sessions session-id)]
|
(when-let [session-agent (get @sessions session-id)]
|
||||||
(send-off session-agent #(-> %
|
(let [string (json/write-str m)]
|
||||||
(queue-string string)
|
(send-off session-agent #(-> %
|
||||||
flush-buffer))))
|
(queue-string string)
|
||||||
|
flush-buffer)))))
|
||||||
|
|
||||||
(defn send-map
|
(defn send-data
|
||||||
"sends a map to the client identified by session-id over the backchannel.
|
"sends data to the client identified by session-id over the backchannel.
|
||||||
if there is currently no available backchannel for this client, the data
|
if there is currently no available backchannel for this client, the data
|
||||||
is queued until one is available."
|
is queued until one is available."
|
||||||
[session-id m]
|
[session-id data]
|
||||||
(send-string session-id (json/write-str m)))
|
(if data
|
||||||
|
(send-map session-id (encode-map data))))
|
||||||
|
|
||||||
(defn send-map-to-all
|
(defn send-data-to-all
|
||||||
"sends a map to all currently connected clients over their backchannels."
|
"sends data to all currently connected clients over their backchannels."
|
||||||
[m]
|
[data]
|
||||||
(doseq [[session-id _] @sessions]
|
(doseq [[session-id _] @sessions]
|
||||||
(send-map session-id m)))
|
(send-data session-id data)))
|
||||||
|
|
||||||
(defn connected?
|
(defn connected?
|
||||||
"returns true if a client with the given session-id is currently 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
|
;; backchannelPresent = 0 for false, 1 for true
|
||||||
;; send as json for XHR and IE
|
;; send as json for XHR and IE
|
||||||
(do
|
(do
|
||||||
(doseq [map maps]
|
(doseq [m maps]
|
||||||
(notify-listeners (:id @session-agent) req :map map))
|
(let [decoded (decode-map m)]
|
||||||
|
(notify-listeners (:id @session-agent) req :map decoded)))
|
||||||
(let [status (session-status @session-agent)]
|
(let [status (session-status @session-agent)]
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers (:headers options)
|
:headers (:headers options)
|
||||||
|
|
Reference in a new issue