re-work app-specified callbacks into a 'middleware' system
This commit is contained in:
parent
ada511f5d2
commit
0a1ee1df25
156
README.md
156
README.md
|
@ -3,165 +3,13 @@
|
||||||
Helper utilities and Ring middleware for using [BrowserChannel](http://thegeez.net/2012/04/03/why_browserchannel.html)
|
Helper utilities and Ring middleware for using [BrowserChannel](http://thegeez.net/2012/04/03/why_browserchannel.html)
|
||||||
as a real-time client-server messaging protocol in your Clojure/ClojureScript web apps.
|
as a real-time client-server messaging protocol in your Clojure/ClojureScript web apps.
|
||||||
|
|
||||||
**Note: This library is currently "beta status." As such some of this setup may be a bit overly complex
|
**Currently in early development. Documentation and examples to come!**
|
||||||
and the documentation a bit rough.**
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Dependencies
|
TODO: write better and more complete usage instructions + examples
|
||||||
|
|
||||||
None of the current versions of Clojure BrowserChannel libraries we need (including this library at the moment)
|
|
||||||
are available on Clojars.
|
|
||||||
|
|
||||||
You will need to install **clj-browserchannel-server** and **clj-browserchannel-jetty-adapter** manually via
|
|
||||||
`lein install`. This library depends on the versions of these libraries
|
|
||||||
[located here](https://github.com/gered/clj-browserchannel) currently.
|
|
||||||
|
|
||||||
Then you will need to locally install this library via `lein install` as well.
|
|
||||||
|
|
||||||
### `project.clj`
|
|
||||||
|
|
||||||
Add these to your dependencies.
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
[net.thegeez/clj-browserchannel-jetty-adapter "0.0.5"]
|
|
||||||
[clj-browserchannel-messaging "0.0.1"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Message Format
|
|
||||||
|
|
||||||
This library wraps messages sent/received by client and server in a lightly structured format:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
{:topic <<keyword describing the contents of the message>>
|
|
||||||
:body <<any clojure data structure or value>>}
|
|
||||||
```
|
|
||||||
|
|
||||||
The topic is kind of like a message type or category. Similar types of messages communicating the same types of
|
|
||||||
information should share the same message topic.
|
|
||||||
|
|
||||||
## Server-side Setup
|
|
||||||
|
|
||||||
### Jetty Async
|
|
||||||
|
|
||||||
Both clj-browserchannel and this library _require_ use of an async HTTP server. The
|
|
||||||
**clj-browserchannel-jetty-adapter** library you installed previously contains the Jetty Async adapter that can be
|
|
||||||
used by **clj-browserchannel-server**.
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(ns yourapp
|
|
||||||
(:gen-class)
|
|
||||||
(:require [net.thegeez.jetty-async-adapter :refer [run-jetty-async]]))
|
|
||||||
|
|
||||||
(defn -main [& args]
|
|
||||||
(run-jetty-async handler {:port 8080 :join? false}))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ring Middleware
|
|
||||||
|
|
||||||
You need to add the `clj-browserchannel-messaging.server/wrap-browserchannel` middleware to your Ring app handler.
|
|
||||||
|
|
||||||
Note that currently, this library does not play nice with Ring's `wrap-anti-forgery` middleware. If you are using
|
|
||||||
lib-noir or ring-defaults, then this middleware is enabled by default when using the `site-defaults` settings
|
|
||||||
from ring-defaults. You will need to disable this by setting, e.g.:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(assoc-in site-defaults [:security :anti-forgery] false)
|
|
||||||
```
|
|
||||||
|
|
||||||
Otherwise you will get 403 access denied responses when sending BrowserChannel messages from client to server. This
|
|
||||||
will be addressed properly in this library in a future release.
|
|
||||||
|
|
||||||
See the doc comments for `clj-browserchannel-messaging.server/wrap-browserchannel` for more detailed descriptions
|
|
||||||
of the options you can pass to this middleware. Example usage:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(wrap-browserchannel
|
|
||||||
{:on-open (fn [browserchannel-session-id request]
|
|
||||||
(println "browserchannel session opened with client:" browserchannel-session-id))
|
|
||||||
:on-close (fn [browserchannel-session-id request reason]
|
|
||||||
(println "browserchannel session closed for client:" browserchannel-session-id ", reason:" reason))
|
|
||||||
:on-receive (fn [browserchannel-session-id request message]
|
|
||||||
(println "received message from client" browserchannel-session-id ":" message))})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sending and Receiving Messages
|
|
||||||
|
|
||||||
The `:on-receive` handler passed to `wrap-browserchannel` will be invoked when any message is received from any
|
|
||||||
BrowserChannel client. It's basically your main event handler.
|
|
||||||
|
|
||||||
However, you can also use `clj-browserchannel-messaging.server/message-handler` anywhere in your application code
|
|
||||||
to listen for specific types of messages and provide a separate handler function to run when they are received.
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(message-handler
|
|
||||||
:foobar
|
|
||||||
(fn [msg]
|
|
||||||
(println "received :foobar message:" msg)))
|
|
||||||
```
|
|
||||||
|
|
||||||
To send a message to a client, you must have the "BrowserChannel session id" associated with that client. This is
|
|
||||||
first generated and passed to the `:on-open` event and becomes invalid after `:on-close`. All messages received
|
|
||||||
from the client will automatically have the BrowserChannel session id of the sending client included in the message
|
|
||||||
under the `:browserchannel-session-id` key.
|
|
||||||
|
|
||||||
Use the `clj-browserchannel-messaging.server/send` function to send a message to a client. If the message could not
|
|
||||||
be sent for any reason, a nil value is returned.
|
|
||||||
|
|
||||||
#### BrowserChannel Sessions
|
|
||||||
|
|
||||||
Just a quick note about BrowserChannel Sessions. They are essentially tied to the length of time that a user has
|
|
||||||
a single page of the web app open in their browser, so obviously it goes without saying that BrowserChannel should be
|
|
||||||
used by Single Page Apps or other applications that keep the user on a single page for a lengthy time and have heavy
|
|
||||||
client-side scripting driving the UI.
|
|
||||||
|
|
||||||
When the page is first loaded, the BrowserChannel setup occurs and a session id is generated (`:on-open`). The user
|
|
||||||
then continues using the app in their browser and if they leave the page or close their browser, the BrowserChannel
|
|
||||||
connection and session is closed (`:on-close`). If the user refreshes the page with their browser, the existing
|
|
||||||
session is closed and a new one is created when the page reloads.
|
|
||||||
|
|
||||||
Also, BrowserChannel sessions can expire after a period of inactivity (the `:on-close` reason will be "Timed out").
|
|
||||||
|
|
||||||
## Client-side Setup
|
|
||||||
|
|
||||||
### Page Load
|
|
||||||
|
|
||||||
When the page loads, in your ClojureScript code you should call `clj-browserchannel-messaging.client/init!`. This
|
|
||||||
function takes some options, the most important of which are callbacks (similar in idea to the callbacks you specify
|
|
||||||
to the `wrap-browserchannel` middleware on the server-side).
|
|
||||||
|
|
||||||
See the doc comments for `clj-browserchannel-messaging.client/init!` for more detailed descriptions of the options
|
|
||||||
available. Example usage:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(init!
|
|
||||||
{:on-open (fn []
|
|
||||||
(println "on-open"))
|
|
||||||
:on-send (fn [msg]
|
|
||||||
(println "sending" msg))
|
|
||||||
:on-receive (fn [msg]
|
|
||||||
(println "receiving" msg))
|
|
||||||
:on-close (fn [pending undelivered]
|
|
||||||
(println "closed" pending undelivered))
|
|
||||||
:on-error (fn [error]
|
|
||||||
(println "error:" error))})
|
|
||||||
```
|
|
||||||
|
|
||||||
On the client-side, you'll probably care most about `:on-receive` and possibly `:on-close` and `:on-error` to help
|
|
||||||
gracefully deal with connection loss / server timeouts.
|
|
||||||
|
|
||||||
### Sending and Receiving Messages
|
|
||||||
|
|
||||||
Note that, unlike on the server, the client does not deal with any "BrowserChannel session ids." That is because
|
|
||||||
it only sends and receives messages to/from the server, not directly to other clients.
|
|
||||||
|
|
||||||
Like on the server, the `:on-receive` handler will be invoked when any message is received from the server.
|
|
||||||
|
|
||||||
You can also use `clj-browserchannel-messaging.client/message-handler` which works in exactly the same manner as the
|
|
||||||
server-side version mentioned above.
|
|
||||||
|
|
||||||
To send a message to the server, use the `clj-browserchannel-messaging.client/send` function. If the message could
|
|
||||||
not be sent for any reason, a nil value is returned.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject clj-browserchannel-messaging "0.0.1"
|
(defproject clj-browserchannel-messaging "0.0.2"
|
||||||
:description "Tools for quickly using BrowserChannel for bi-directional client-server messaging."
|
:description "Tools for quickly using BrowserChannel for bi-directional client-server messaging."
|
||||||
:url "https://github.com/gered/clj-browserchannel-messaging"
|
:url "https://github.com/gered/clj-browserchannel-messaging"
|
||||||
:license {:name "MIT License"
|
:license {:name "MIT License"
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
:dependencies [[org.clojure/clojure "1.6.0"]
|
:dependencies [[org.clojure/clojure "1.6.0"]
|
||||||
[org.clojure/clojurescript "0.0-2371" :scope "provided"]
|
[org.clojure/clojurescript "0.0-2371" :scope "provided"]
|
||||||
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
|
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
|
||||||
[net.thegeez/clj-browserchannel-server "0.0.9"]]
|
[net.thegeez/clj-browserchannel-server "0.1.0"]]
|
||||||
|
|
||||||
:source-paths ["src/clj"]
|
:source-paths ["src/clj"]
|
||||||
:resource-paths ["src/cljs"])
|
:resource-paths ["src/cljs"])
|
||||||
|
|
|
@ -4,15 +4,25 @@
|
||||||
[clojure.core.async :refer [chan pub sub <! put! go-loop]]
|
[clojure.core.async :refer [chan pub sub <! put! go-loop]]
|
||||||
[net.thegeez.browserchannel :as browserchannel]))
|
[net.thegeez.browserchannel :as browserchannel]))
|
||||||
|
|
||||||
|
(defonce ^:private handler-middleware (atom nil))
|
||||||
|
|
||||||
(defonce incoming-messages (chan))
|
(defonce incoming-messages (chan))
|
||||||
(defonce incoming-messages-pub (pub incoming-messages :topic))
|
(defonce incoming-messages-pub (pub incoming-messages :topic))
|
||||||
|
|
||||||
|
(defn- run-middleware [middleware final-handler & args]
|
||||||
|
(let [wrap (fn [handler [f & more]]
|
||||||
|
(if f
|
||||||
|
(recur (f handler) more)
|
||||||
|
handler))
|
||||||
|
handler (wrap final-handler middleware)]
|
||||||
|
(apply handler args)))
|
||||||
|
|
||||||
(defn encode-message
|
(defn encode-message
|
||||||
"encodes a message made up of a topic and body into a format that can be sent
|
"encodes a message made up of a topic and body into a format that can be sent
|
||||||
via browserchannel to a client. topic should be a keyword, while body can be
|
via browserchannel to a client. topic should be a keyword, while body can be
|
||||||
anything. returns nil if the message could not be encoded."
|
any Clojure data structure. returns nil if the message could not be encoded."
|
||||||
[topic body]
|
[{:keys [topic body] :as msg}]
|
||||||
(if-let [topic (name topic)]
|
(if-let [topic (if topic (name topic))]
|
||||||
{"topic" topic
|
{"topic" topic
|
||||||
"body" (pr-str body)}))
|
"body" (pr-str body)}))
|
||||||
|
|
||||||
|
@ -31,8 +41,14 @@
|
||||||
browserchannel session id. topic should be a keyword, while body can be
|
browserchannel session id. topic should be a keyword, while body can be
|
||||||
anything. returns nil if the message was not sent."
|
anything. returns nil if the message was not sent."
|
||||||
[browserchannel-session-id topic body]
|
[browserchannel-session-id topic body]
|
||||||
(if-let [encoded (encode-message topic body)]
|
(let [msg {:topic topic
|
||||||
(browserchannel/send-map browserchannel-session-id encoded)))
|
:body body}]
|
||||||
|
(run-middleware
|
||||||
|
(:on-send @handler-middleware)
|
||||||
|
(fn [browserchannel-session-id msg]
|
||||||
|
(if-let [encoded (encode-message msg)]
|
||||||
|
(browserchannel/send-map browserchannel-session-id encoded)))
|
||||||
|
browserchannel-session-id msg)))
|
||||||
|
|
||||||
(defn message-handler
|
(defn message-handler
|
||||||
"listens for incoming browserchannel messages with the specified topic.
|
"listens for incoming browserchannel messages with the specified topic.
|
||||||
|
@ -49,21 +65,37 @@
|
||||||
(handler msg)
|
(handler msg)
|
||||||
(recur)))))
|
(recur)))))
|
||||||
|
|
||||||
(defn- handle-session [browserchannel-session-id req {:keys [on-open on-close on-receive]}]
|
(defn- handle-session [browserchannel-session-id req]
|
||||||
(if on-open (on-open browserchannel-session-id req))
|
(run-middleware
|
||||||
|
(:on-open @handler-middleware)
|
||||||
|
(fn [browserchannel-session-id request]
|
||||||
|
; no-op
|
||||||
|
)
|
||||||
|
browserchannel-session-id req)
|
||||||
|
|
||||||
(browserchannel/add-listener
|
(browserchannel/add-listener
|
||||||
browserchannel-session-id
|
browserchannel-session-id
|
||||||
:close
|
:close
|
||||||
(fn [request reason]
|
(fn [request reason]
|
||||||
(if on-close (on-close browserchannel-session-id request reason))))
|
(run-middleware
|
||||||
|
(:on-close @handler-middleware)
|
||||||
|
(fn [browserchannel-session-id request reason]
|
||||||
|
; no-op
|
||||||
|
)
|
||||||
|
browserchannel-session-id request reason)))
|
||||||
|
|
||||||
(browserchannel/add-listener
|
(browserchannel/add-listener
|
||||||
browserchannel-session-id
|
browserchannel-session-id
|
||||||
:map
|
:map
|
||||||
(fn [request m]
|
(fn [request m]
|
||||||
(if-let [decoded (decode-message m)]
|
(if-let [decoded (decode-message m)]
|
||||||
(let [msg (assoc decoded :browserchannel-session-id browserchannel-session-id)]
|
(let [msg (assoc decoded :browserchannel-session-id browserchannel-session-id)]
|
||||||
(if on-receive (on-receive browserchannel-session-id request msg))
|
(run-middleware
|
||||||
(put! incoming-messages msg))))))
|
(:on-receive @handler-middleware)
|
||||||
|
(fn [browserchannel-session-id request msg]
|
||||||
|
(if msg
|
||||||
|
(put! incoming-messages msg)))
|
||||||
|
browserchannel-session-id request msg))))))
|
||||||
|
|
||||||
(defn wrap-browserchannel
|
(defn wrap-browserchannel
|
||||||
"Middleware to handle server-side browserchannel session and message
|
"Middleware to handle server-side browserchannel session and message
|
||||||
|
@ -80,6 +112,47 @@
|
||||||
In addition, you can pass event handler functions. Note that the return
|
In addition, you can pass event handler functions. Note that the return
|
||||||
value for all of these handlers is not used.
|
value for all of these handlers is not used.
|
||||||
|
|
||||||
|
"
|
||||||
|
[handler & [opts]]
|
||||||
|
(-> handler
|
||||||
|
(browserchannel/wrap-browserchannel
|
||||||
|
(assoc
|
||||||
|
opts
|
||||||
|
:base (or (:base opts) "/browserchannel")
|
||||||
|
:on-session
|
||||||
|
(fn [browserchannel-session-id request]
|
||||||
|
(handle-session browserchannel-session-id request))))))
|
||||||
|
|
||||||
|
(defn- get-handlers [middleware k]
|
||||||
|
(->> middleware (map k) (remove nil?) (doall)))
|
||||||
|
|
||||||
|
(defn init!
|
||||||
|
"Sets up browserchannel for server-side use. This function should be called
|
||||||
|
once during application startup.
|
||||||
|
|
||||||
|
:middleware - a vector of middleware maps.
|
||||||
|
|
||||||
|
Middleware is optional. If specificed, each middleware is provided as a
|
||||||
|
'middleware map'. This is a map where functions are specified for one
|
||||||
|
or more of :on-open, :on-close, :on-send, :on-receive. A middleware map
|
||||||
|
need not provide a function for any events it is not doing any processing
|
||||||
|
for.
|
||||||
|
|
||||||
|
Each middleware function looks like a Ring middleware function. They
|
||||||
|
are passed a handler and should return a function which performs the
|
||||||
|
actual middleware processing and calls handler to continue on down
|
||||||
|
the chain of middleware. e.g.
|
||||||
|
|
||||||
|
{:on-send (fn [handler]
|
||||||
|
(fn [session-id request {:keys [topic body] :as msg]
|
||||||
|
; do something here with the message to be sent
|
||||||
|
(handler session-id request msg)))}
|
||||||
|
|
||||||
|
Remember that middleware is run in the reverse order that they appear
|
||||||
|
in the vector you pass in.
|
||||||
|
|
||||||
|
Middleware function descriptions:
|
||||||
|
|
||||||
:on-open
|
:on-open
|
||||||
Occurs when a new browserchannel session has been established. Receives 2
|
Occurs when a new browserchannel session has been established. Receives 2
|
||||||
arguments: the browserchannel session id and the request map (for the
|
arguments: the browserchannel session id and the request map (for the
|
||||||
|
@ -88,11 +161,15 @@
|
||||||
|
|
||||||
:on-receive
|
:on-receive
|
||||||
Occurs when a new message is received from a client. Receives 3 arguments:
|
Occurs when a new message is received from a client. Receives 3 arguments:
|
||||||
the browsercannel session id, the request map (for the client request that
|
the browserchannel session id, the request map (for the client request that
|
||||||
the message was sent with), and the actual decoded message as arguments.
|
the message was sent with), and the actual decoded message as arguments.
|
||||||
the browserchannel session id of the client that sent the message is
|
the browserchannel session id of the client that sent the message is
|
||||||
automatically added to the message under :browserchannel-session-id.
|
automatically added to the message under :browserchannel-session-id.
|
||||||
|
|
||||||
|
:on-send
|
||||||
|
Occurs when a message is to be sent to a client. Receives 2 arguments:
|
||||||
|
the browserchannel session id and the actual message to be sent.
|
||||||
|
|
||||||
:on-close
|
:on-close
|
||||||
Occurs when the browserchannel session is closed. Receives 3 arguments: the
|
Occurs when the browserchannel session is closed. Receives 3 arguments: the
|
||||||
browserchannel session id, the request map (for the request sent by the
|
browserchannel session id, the request map (for the request sent by the
|
||||||
|
@ -101,14 +178,10 @@
|
||||||
initiated directly by the client. The request argument will be nil if the
|
initiated directly by the client. The request argument will be nil if the
|
||||||
session is being closed as part of some server-side operation (e.g.
|
session is being closed as part of some server-side operation (e.g.
|
||||||
browserchannel session timeout)."
|
browserchannel session timeout)."
|
||||||
[handler & [opts]]
|
[& {:keys [middleware]}]
|
||||||
(-> handler
|
(reset!
|
||||||
(browserchannel/wrap-browserchannel
|
handler-middleware
|
||||||
(assoc
|
{:on-open (get-handlers middleware :on-open)
|
||||||
opts
|
:on-close (get-handlers middleware :on-close)
|
||||||
:base (or (:base opts) "/browserchannel")
|
:on-receive (get-handlers middleware :on-receive)
|
||||||
:on-session
|
:on-send (get-handlers middleware :on-send)}))
|
||||||
(fn [browserchannel-session-id request]
|
|
||||||
(handle-session
|
|
||||||
browserchannel-session-id request
|
|
||||||
(select-keys opts [:on-open :on-close :on-receive])))))))
|
|
|
@ -6,12 +6,22 @@
|
||||||
goog.net.BrowserChannel
|
goog.net.BrowserChannel
|
||||||
[goog.events :as events]))
|
[goog.events :as events]))
|
||||||
|
|
||||||
|
(defonce ^:private handler-middleware (atom nil))
|
||||||
|
|
||||||
(defonce browser-channel (goog.net.BrowserChannel.))
|
(defonce browser-channel (goog.net.BrowserChannel.))
|
||||||
|
|
||||||
(defonce incoming-messages (chan))
|
(defonce incoming-messages (chan))
|
||||||
(defonce incoming-messages-pub (pub incoming-messages :topic))
|
(defonce incoming-messages-pub (pub incoming-messages :topic))
|
||||||
(defonce outgoing-messages (chan))
|
(defonce outgoing-messages (chan))
|
||||||
|
|
||||||
|
(defn- run-middleware [middleware final-handler & args]
|
||||||
|
(let [wrap (fn [handler [f & more]]
|
||||||
|
(if f
|
||||||
|
(recur (f handler) more)
|
||||||
|
handler))
|
||||||
|
handler (wrap final-handler middleware)]
|
||||||
|
(apply handler args)))
|
||||||
|
|
||||||
(defn encode-message
|
(defn encode-message
|
||||||
"encodes a message composed of a topic and body into a format that can be
|
"encodes a message composed of a topic and body into a format that can be
|
||||||
sent via browserchannel. topic should be a keyword while body can be
|
sent via browserchannel. topic should be a keyword while body can be
|
||||||
|
@ -50,18 +60,25 @@
|
||||||
(handler msg)
|
(handler msg)
|
||||||
(recur)))))
|
(recur)))))
|
||||||
|
|
||||||
(defn- handle-outgoing [channel on-send]
|
(defn- handle-outgoing [channel]
|
||||||
(go-loop []
|
(go-loop []
|
||||||
(when-let [msg (<! outgoing-messages)]
|
(when-let [msg (<! outgoing-messages)]
|
||||||
(when-let [encoded (encode-message msg)]
|
(run-middleware
|
||||||
(if on-send (on-send msg))
|
(:on-send @handler-middleware)
|
||||||
(.sendMap channel encoded))
|
(fn [msg]
|
||||||
|
(if-let [encoded (encode-message msg)]
|
||||||
|
(.sendMap channel encoded)))
|
||||||
|
msg)
|
||||||
(recur))))
|
(recur))))
|
||||||
|
|
||||||
(defn- handle-incoming [channel msg on-receive]
|
(defn- handle-incoming [channel msg]
|
||||||
(when-let [decoded (decode-message msg)]
|
(when-let [decoded (decode-message msg)]
|
||||||
(if on-receive (on-receive decoded))
|
(run-middleware
|
||||||
(put! incoming-messages decoded)))
|
(:on-receive @handler-middleware)
|
||||||
|
(fn [msg]
|
||||||
|
(if msg
|
||||||
|
(put! incoming-messages msg)))
|
||||||
|
decoded)))
|
||||||
|
|
||||||
; see: http://docs.closure-library.googlecode.com/git/local_closure_goog_net_browserchannel.js.source.html#line521
|
; see: http://docs.closure-library.googlecode.com/git/local_closure_goog_net_browserchannel.js.source.html#line521
|
||||||
(def bch-error-enum-to-keyword
|
(def bch-error-enum-to-keyword
|
||||||
|
@ -81,37 +98,78 @@
|
||||||
(or (get bch-error-enum-to-keyword error-code)
|
(or (get bch-error-enum-to-keyword error-code)
|
||||||
:unknown))
|
:unknown))
|
||||||
|
|
||||||
(defn- handler [{:keys [on-open on-send on-receive on-close on-error]}]
|
(defn- ->handler []
|
||||||
(let [h (goog.net.BrowserChannel.Handler.)]
|
(let [h (goog.net.BrowserChannel.Handler.)]
|
||||||
(set! (.-channelOpened h)
|
(set! (.-channelOpened h)
|
||||||
(fn [channel]
|
(fn [channel]
|
||||||
(if on-open (on-open))
|
(run-middleware (:on-open @handler-middleware) (fn []))
|
||||||
(handle-outgoing channel on-send)))
|
(handle-outgoing channel)))
|
||||||
(set! (.-channelHandleArray h)
|
(set! (.-channelHandleArray h)
|
||||||
(fn [channel msg]
|
(fn [channel msg]
|
||||||
(handle-incoming channel msg on-receive)))
|
(handle-incoming channel msg)))
|
||||||
(set! (.-channelClosed h)
|
(set! (.-channelClosed h)
|
||||||
(fn [channel pending undelivered]
|
(fn [channel pending undelivered]
|
||||||
(if on-close (on-close pending undelivered))))
|
(run-middleware
|
||||||
|
(:on-close @handler-middleware)
|
||||||
|
(fn [pending undelivered]
|
||||||
|
; no-op
|
||||||
|
)
|
||||||
|
pending undelivered)))
|
||||||
(set! (.-channelError h)
|
(set! (.-channelError h)
|
||||||
(fn [channel error]
|
(fn [channel error]
|
||||||
(if on-error (on-error (bch-error-enum->keyword error)))))
|
(run-middleware
|
||||||
|
(:on-error @handler-middleware)
|
||||||
|
(fn [error]
|
||||||
|
; no-op
|
||||||
|
)
|
||||||
|
(bch-error-enum->keyword error))))
|
||||||
h))
|
h))
|
||||||
|
|
||||||
(defn- set-debug-logger! [level]
|
(defn- set-debug-logger! [level]
|
||||||
(if-let [logger (-> browser-channel .getChannelDebug .getLogger)]
|
(if-let [logger (-> browser-channel .getChannelDebug .getLogger)]
|
||||||
(.setLevel logger level)))
|
(.setLevel logger level)))
|
||||||
|
|
||||||
|
(defn- get-handlers [middleware k]
|
||||||
|
(->> middleware (map k) (remove nil?) (doall)))
|
||||||
|
|
||||||
|
(defn- register-middleware! [middleware]
|
||||||
|
(reset!
|
||||||
|
handler-middleware
|
||||||
|
{:on-open (get-handlers middleware :on-open)
|
||||||
|
:on-close (get-handlers middleware :on-close)
|
||||||
|
:on-error (get-handlers middleware :on-error)
|
||||||
|
:on-receive (get-handlers middleware :on-receive)
|
||||||
|
:on-send (get-handlers middleware :on-send)}))
|
||||||
|
|
||||||
(defn init!
|
(defn init!
|
||||||
"sets up browserchannel for use, creating a handler with the specified
|
"Sets up browserchannel for use, creating a handler with the specified
|
||||||
properties. this function should be called once on page load.
|
properties. this function should be called once on page load.
|
||||||
|
|
||||||
properties:
|
|
||||||
|
|
||||||
:base - the base URL on which the server's browserchannel routes are
|
:base - the base URL on which the server's browserchannel routes are
|
||||||
located at. default is '/browserchannel'
|
located at. default if not specified is '/browserchannel'
|
||||||
|
|
||||||
callbacks:
|
:middleware - a vector of middleware maps.
|
||||||
|
|
||||||
|
Middleware is optional. If specificed, each middleware is provided as a
|
||||||
|
'middleware map'. This is a map where functions are specified for one
|
||||||
|
or more of :on-open, :on-close, :on-error, :on-send, :on-receive. A
|
||||||
|
middleware map need not provide a function for any events it is
|
||||||
|
not doing any processing for.
|
||||||
|
|
||||||
|
Each middleware function looks like a Ring middleware function. They
|
||||||
|
are passed a handler and should return a function which performs the
|
||||||
|
actual middleware processing and calls handler to continue on down
|
||||||
|
the chain of middleware. e.g.
|
||||||
|
|
||||||
|
{:on-send (fn [handler]
|
||||||
|
(fn [{:keys [topic body] :as msg]
|
||||||
|
; do something here with the message to be sent
|
||||||
|
(handler msg)))}
|
||||||
|
|
||||||
|
Remember that middleware is run in the reverse order that they appear
|
||||||
|
in the vector you pass in.
|
||||||
|
|
||||||
|
Middleware function descriptions:
|
||||||
|
|
||||||
:on-open
|
:on-open
|
||||||
occurs when a browserchannel session with the server is established
|
occurs when a browserchannel session with the server is established
|
||||||
|
@ -131,9 +189,7 @@
|
||||||
|
|
||||||
:on-send
|
:on-send
|
||||||
raised whenever a message is sent via the send function. receives 1
|
raised whenever a message is sent via the send function. receives 1
|
||||||
argument: the message that is to be sent. this is probably only useful for
|
argument: the message that is to be sent.
|
||||||
debugging/logging purposes. note that this event is only raised for messages
|
|
||||||
which can be encoded by encode-message
|
|
||||||
|
|
||||||
:on-receive
|
:on-receive
|
||||||
occurs whenever a browserchannel message is received from the server.
|
occurs whenever a browserchannel message is received from the server.
|
||||||
|
@ -141,15 +197,16 @@
|
||||||
only raised for messages which can be decoded by decode-message. also note
|
only raised for messages which can be decoded by decode-message. also note
|
||||||
that this event is raised for all messages received, regardless of any
|
that this event is raised for all messages received, regardless of any
|
||||||
listeners created via message-handler."
|
listeners created via message-handler."
|
||||||
[& [{:keys [base] :as opts}]]
|
[& {:keys [base middleware]}]
|
||||||
(let [base (or base "/browserchannel")]
|
(let [base (or base "/browserchannel")]
|
||||||
|
(register-middleware! middleware)
|
||||||
(events/listen
|
(events/listen
|
||||||
js/window "unload"
|
js/window "unload"
|
||||||
(fn []
|
(fn []
|
||||||
(.disconnect browser-channel)
|
(.disconnect browser-channel)
|
||||||
(events/removeAll)))
|
(events/removeAll)))
|
||||||
(set-debug-logger! goog.debug.Logger.Level.OFF)
|
(set-debug-logger! goog.debug.Logger.Level.OFF)
|
||||||
(.setHandler browser-channel (handler opts))
|
(.setHandler browser-channel (->handler))
|
||||||
(.connect browser-channel
|
(.connect browser-channel
|
||||||
(str base "/test")
|
(str base "/test")
|
||||||
(str base "/bind"))))
|
(str base "/bind"))))
|
||||||
|
|
Reference in a new issue