From f83cc16c600b20c4498a5df8ab3bfd7f55ee507c Mon Sep 17 00:00:00 2001 From: gered Date: Mon, 16 May 2016 18:56:52 -0400 Subject: [PATCH] update READMEs --- README.md | 134 ++--- clj-browserchannel-immutant-adapter/README.md | 75 ++- clj-browserchannel-jetty-adapter/README.md | 96 +++- clj-browserchannel/README.md | 530 ++++++++++++++---- 4 files changed, 629 insertions(+), 206 deletions(-) diff --git a/README.md b/README.md index b390ffd..9517565 100644 --- a/README.md +++ b/README.md @@ -13,112 +13,52 @@ The javascript api of BrowserChannel is open-source and part of the Google Closure library. The server component is not, as is noted in the Google Closure book ("Closure: The Definitive Guide by Michael Bolin"). -[1]: http://closure-library.googlecode.com/svn-history/r144/docs/closure_goog_net_browserchannel.js.html +[1]: https://google.github.io/closure-library/api/source/closure/goog/net/browserchannel.js.src.html + +## Usage + +[clj-browserchannel][2] is the main library containing both the server- and +client-side functionality you'll use in your web apps. + +In order to use the server implementation of BrowserChannel you'll need to +use an async adapter. Currently the provided options are: + +* [clj-browserchannel-jetty-adapter][3] +* [clj-browserchannel-immutant-adapter][4] + +[2]: https://github.com/gered/clj-browserchannel/tree/master/clj-browserchannel +[3]: https://github.com/gered/clj-browserchannel/tree/master/clj-browserchannel-jetty-adapter +[4]: https://github.com/gered/clj-browserchannel/tree/master/clj-browserchannel-immutant-adapter + +You can find more information on usage of all of these components by +following any of the above links to them. ## Demo -clj-browserchannel-demo is an example chat application using a server -side implementation for BrowserChannel written in Clojure. The server -component is for BrowserChannel version 8. +The [chat-demo][2] application is an example chat application using a +client-side and server-side implementation for BrowserChannel written in +Clojure/ClojureScript. The server component is for BrowserChannel version 8. +The client component serves as a wrapper over `goog.net.BrowserChannel`. -This enables client->server and server->client communication in -ClojureScript and Closure web apps, without any javascript -dependencies other than the Google Closure [library][2]. +[2]: https://github.com/gered/clj-browserchannel/tree/master/chat-demo -[2]: https://developers.google.com/closure/library/ - -The example runs in at least: +The chat-demo web app runs in at least: * Chrome * Firefox * Internet Explorer 5.5+ (!!) * Android browser -## Jetty Async - -When there are long lasting connections between a client and a -webserver it is desirable to not have a thread per -connection. Therefore this demo runs with with an asynchronous Jetty -adapter. This adapter is compatible with Ring. - -The adapter is based on [ring-jetty-async-adapter][3] by Mark McGranaghan. - -[3]: https://github.com/mmcgrana/ring/tree/jetty-async - -An implementation on top of Netty, through [Aleph][4] is in -development. - -[4]: https://github.com/ztellman/aleph - ## Related and alternative frameworks * Websockets - Websockets solve the same problems as BrowserChannel, however BrowserChannel works on almost all existing clients. -* socket.io - [socket.io][5] provides a similar api as BrowserChannel on -top of many transport protocols, including websockets. BrowserChannel -only has two transport protocols: XHR and forever frames (for IE) in -streaming and non-streaming mode. +* socket.io - [socket.io][3] provides a similar api as BrowserChannel on + top of many transport protocols, including websockets. BrowserChannel + only has two transport protocols: XHR and forever frames (for IE) in + streaming and non-streaming mode. -[5]: http://socket.io - -## Run - ;; compile cljs - lein run -m tasks.build-dev-js - ;; compile cljs in advanced mode - lein run -m tasks.build-advanced-js - lein run -m chat-demo.core - -Open two windows at [http://localhost:8080/index.html](http://localhost:8080/index.html) (Advanced compiled) -or [http://localhost:8080/index-dev.html](http://localhost:8080/index-dev.html) and start chatting! - -## Run on Heroku -Use the Heroku Clojure [buildpack][7]. - - heroku config:add BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-clojure.git - -This project additionally -requires two build tasks to compile the ClojureScript during deployment. - -Enable [user_env_compile][6]: - - heroku labs:enable user_env_compile -a - -Add this config var: - - heroku config:add LEIN_BUILD_TASK="run -m tasks.build-dev-js, run -m tasks.build-advanced-js" - -[6]: https://devcenter.heroku.com/articles/labs-user-env-compile -[7]: https://github.com/heroku/heroku-buildpack-clojure.git - -### Note on disconnections on Heroku -I have found that Heroku does not immediately report when a connection to a client -is broken. If the client is able to reconnect this is not a problem, -as this is supported by the BrowserChannel API. However when you -unplug the internet cable the client cannot reconnect and the server -must timeout the session. Ussually this happens when trying to send the next -heartbeat to the client. On Heruko this does not report an error, even -though there is no connection to the client. So instead of the -connection timeing out on a heartbeat (after seconds/a minute) the -connection will only timeout after the connection is timed out by the -server (4 minutes by default). The Netty implementation has the same -problem on Heroku. Deployments on Amazon Web Services do not have this -problem. - -## Configuration: -See default-options in src/net/thegeez/browserchannel.clj -And the :response-timeout option in src/net/thegeez/jetty_async_adapter.clj - -### Debug / Play around -BrowserChannel has a helpful debug window. Uncomment the debug-window -and .setChannelDebug lines in cljs/bc/core.cljs to enable the logging window. - -## Todo -- Release backend as library -- Handling acknowledgements by client and callbacks on queued arrays -- Host prefixes -- Heroku disconnection -- Replace session listeners, possibly with lamina -- Explore other event based Java webservers, such as Netty and Webbit +[3]: http://socket.io ## Other BrowserChannel implementations Many thanks to these authors, their work is the only open-source @@ -131,18 +71,22 @@ in C++ by Andy Hochhaus - Has the most extensive [documentation][libevent-doc] o in Node.js/Javascript by Joseph Gentle [libevent]: http://code.google.com/p/libevent-browserchannel-server -[libevent-doc]: http://code.google.com/p/libevent-browserchannel-server/wiki/BrowserChannelProtocol +[libevent-doc]: http://web.archive.org/web/20121226064550/http://code.google.com/p/libevent-browserchannel-server/wiki/BrowserChannelProtocol [ruby]: https://github.com/dturnbull/browserchannel [node]: https://github.com/josephg/node-browserchannel ## About Written by: -Gijs Stuurman / [@thegeez][twt] / [Blog][blog] / [GitHub][github] +Gijs Stuurman / +[@thegeez](http://twitter.com/thegeez) / +[Blog](http://thegeez.github.com) / +[GitHub](https://github.com/thegeez) -[twt]: http://twitter.com/thegeez -[blog]: http://thegeez.github.com -[github]: https://github.com/thegeez +Many updates in this fork by: +Gered King / +[@geredking](http://twitter.com/geredking) / +[GitHub](https://github.com/gered) ### License diff --git a/clj-browserchannel-immutant-adapter/README.md b/clj-browserchannel-immutant-adapter/README.md index 7b0d062..457a611 100644 --- a/clj-browserchannel-immutant-adapter/README.md +++ b/clj-browserchannel-immutant-adapter/README.md @@ -3,19 +3,84 @@ Immutant async adapter for BrowserChannel. See also: [clj-browserchannel][1] + [1]:https://github.com/gered/clj-browserchannel + ## Leiningen - [gered/clj-browserchannel-immutant-adapter "0.0.1"] + [gered/clj-browserchannel-immutant-adapter "0.0.2"] + + +## Usage + +This library does not directly include Immutant as a dependency so +that it's not tied to a specific version of Immutant. You will need +to also include Immutant as a dependency directly in your project. + +[See the Immutant project for the relevant dependency line.][2] + +[2]: https://github.com/immutant/immutant + +To enable server-side BrowserChannel functionality, you simply +need to add the `wrap-immutant-async-adapter` middleware to your +Ring handler. For example: + +```clj +(ns your-app + (:require + ; ... + [net.thegeez.browserchannel.server :refer [wrap-browserchannel] + [net.thegeez.browserchannel.immutant-async-adapter :refer [wrap-immutant-async-adapter]] + [immutant.web :as immutant] + ; ... + )) + +(def event-handlers + ; ... browserchannel event handler map ... + ) + +(def your-app-routes + ; ... + ) + +(def ring-handler + (-> your-app-routes + ; other middleware + (wrap-browserchannel event-handlers) + (wrap-immutant-async-adapter))) + +(defn -main [& args] + (immutant/run + #'ring-handler + {:port 8080})) +``` + +By default, BrowserChannel async requests will timeout after 4 minutes. +This default time period is based on what Google uses currently in +their BrowserChannel usage on e.g. Gmail. If you would like to change this, +simple pass in `:response-timeout` with a new time in milliseconds to +`wrap-immutant-async-adapter`: + +```clj +(def ring-handler + (-> your-app-routes + ; other middleware + (wrap-browserchannel event-handlers) + ; 2 minute async request timeout + (wrap-immutant-async-adapter {:response-timeout (* 2 60 1000}))) +``` + +This timeout period directly controls the maximum time that a +back channel request can remain open for before it gets closed +and the client must open a new one. ## About Written by: -Gered King / [@geredking][twt] / [GitHub][github] - -[twt]: http://twitter.com/geredking -[github]: https://github.com/gered +Gered King / +[@geredking](http://twitter.com/geredking) / +[GitHub](https://github.com/gered) ### License diff --git a/clj-browserchannel-jetty-adapter/README.md b/clj-browserchannel-jetty-adapter/README.md index 14f7f02..43a41ff 100644 --- a/clj-browserchannel-jetty-adapter/README.md +++ b/clj-browserchannel-jetty-adapter/README.md @@ -1,25 +1,105 @@ # clj-browserchannel-jetty-adapter -Jetty async adapter for BrowserChannel. This now simply makes use of -whatever Jetty version that Ring's [ring-jetty-adapter][1] is using -and just adds the required async handling configuration on top of it. -[1]:https://github.com/ring-clojure/ring/tree/master/ring-jetty-adapter +Jetty async adapter for BrowserChannel. See also: [clj-browserchannel][1] + [1]:https://github.com/gered/clj-browserchannel + ## Leiningen [gered/clj-browserchannel-jetty-adapter "0.1.0"] + +## Usage + +This library does not directly include Jetty as a dependency so that +it's not tied to one specific version of Jetty (allowing you to upgrade +to newer versions easier as long as they remain compatible with this +library). You will need to include these Ring dependencies at a minimum: + +* `ring/ring-core` +* `ring/ring-servlet` +* `ring/ring-jetty-adapter` + +The top-level `ring` dependency includes all of these. See the +[Ring][2] project page for more information and the relevant +dependency lines for your `project.clj`. + +[2]: https://github.com/ring-clojure/ring + +To enable server-side BrowserChannel functionality, you should +start Jetty with the included `run-jetty` function. For example: + +```clj +(ns your-app + (:require + ; ... + [net.thegeez.browserchannel.server :refer [wrap-browserchannel] + [net.thegeez.browserchannel.jetty-async-adapter :refer [run-jetty]] + ; ... + )) + +(def event-handlers + ; ... browserchannel event handler map ... + ) + +(def your-app-routes + ; ... + ) + +(def ring-handler + (-> your-app-routes + ; other middleware + (wrap-browserchannel event-handlers))) + +(defn -main [& args] + (run-jetty + #'handler + {:join? false + :port 8080})) +``` + +The `run-jetty` function takes the exact same set of options as +`ring.adapter.jetty/run-jetty` does. See that function for more +information. + +One additional option is made available to configure the length of +time that BrowserChannel async requests will remain open before +timing out. By default this timeout is 4 minutes. This default +time period is based on what Google uses currently in their +BrowserChannel usage on e.g. Gmail. + +To change this, the option you need to pass in to `run-jetty` is +`:response-timeout` with a new time in milliseconds: + +```clj +(run-jetty + #'handler + {:join? false + :port 8080 + ; 2 minute async request timeout + :response-timeout (* 2 60 1000)}) +``` + +This timeout period directly controls the maximum time that a +back channel request can remain open for before it gets closed +and the client must open a new one. + + ## About Written by: -Gijs Stuurman / [@thegeez][twt] / [Blog][blog] / [GitHub][github] +Gijs Stuurman / +[@thegeez](http://twitter.com/thegeez) / +[Blog](http://thegeez.github.com) / +[GitHub](https://github.com/thegeez) -[twt]: http://twitter.com/thegeez -[blog]: http://thegeez.github.com -[github]: https://github.com/thegeez +Updates in this fork by: +Gered King / +[@geredking](http://twitter.com/geredking) / +[GitHub](https://github.com/gered) ### License diff --git a/clj-browserchannel/README.md b/clj-browserchannel/README.md index 0743151..a1f2296 100644 --- a/clj-browserchannel/README.md +++ b/clj-browserchannel/README.md @@ -1,146 +1,480 @@ -# clj-browserchannel-server +# clj-browserchannel Cross-browser compatible, real-time, bi-directional communication between ClojureScript and Clojure using Google Closure BrowserChannel. -See also: [clj-browserchannel][0] -[0]:https://github.com/gered/clj-browserchannel +See also: [clj-browserchannel][1] -## goog.net.BrowserChannel +[1]: https://github.com/gered/clj-browserchannel -From the Google Closure API: "A [BrowserChannel][1] simulates a -bidirectional socket over HTTP. It is the basis of the Gmail Chat IM -connections to the server." -The javascript api of BrowserChannel is open-source and part of the -Google Closure library. The server component is not, as is noted in -the Google Closure book ("Closure: The Definitive Guide by Michael Bolin"). -[1]: http://closure-library.googlecode.com/svn-history/r144/docs/closure_goog_net_browserchannel.js.html +## Leiningen -## Demo + [gered/clj-browserchannel "0.3"] -This project is a server side implementation for BrowserChannel -written in Clojure. The server component is for BrowserChannel version 8. +You will also need to include one of the async adapters as an +additional dependency. See [here][1] for more information.. -This enables client->server and server->client communication in -ClojureScript and Closure web apps, without any javascript -dependencies other than the Google Closure [library][2]. -[2]: https://developers.google.com/closure/library/ +## Basic Concepts -The browserchannel client side runs in at least: +Communication between client and server in a web app using +BrowserChannel occurs after a BrowserChannel **session** is +established. -* Chrome -* Firefox -* Internet Explorer 5.5+ (!!) -* Android browser +Just before this initial connection/handshake is done, the client +will perform up to 3 different test HTTP requests automatically +to determine the supported capabilities of the client browser and +what the network connection is like. After this testing is +complete, another HTTP request will be sent to establish the +session. -## Jetty Async +There are two "channels" used in a BrowserChannel session: -When there are long lasting connections between a client and a -webserver it is desirable to not have a thread per -connection. Therefore this demo runs with with an asynchronous Jetty -adapter. This adapter is compatible with Ring. +* **Forward Channel** - used to initiate the session and also used + to transmit messages from the client to the server. Behind the + scenes this is a quick HTTP POST request each time the client + wants to send something. Multiple messages can be batched into + a single request. +* **Backward Channel** - (or just "back channel") a long running + HTTP GET request that is used to transmit messages from the + server to the client. On IE 9 and earlier, the back channel + is implemented using "forever frames" while on every other + browser XHR streaming is used. -The adapter is based on [ring-jetty-async-adapter][3] by Mark McGranaghan. +As mentioned above, the forward channel also is used to initiate +the BrowserChannel session, so the first HTTP request after the +initial tests is a forward channel request. Upon success, the +client will automatically initiate a back channel request. -[3]: https://github.com/mmcgrana/ring/tree/jetty-async +Because the back channel is a long running HTTP request, over the +course of a long session multiple back channel requests will be +opened and closed over time. This is normal and to be expected +as connections time out, etc. The client and server will +automatically manage this. -## Netty +> Regarding terminology, when reviewing BrowserChannel code, +> you will see client-to-server messages commonly referred to as +> 'maps' and server-to-client messages as 'arrays.' +> clj-browserchannel simplifies this somewhat from the +> perspective of your application's code, as messages sent +> in either direction can be any arbitrary Clojure value that +> can be serialized to a string as EDN. -The server component can also run on top of Netty, through [Aleph][4]. +A **session ID** is used to identify a client's BrowserChannel +session. clj-browserchannel uses UUID's as session IDs. These IDs +are generated by the server. Unlike HTTP Session IDs that you may +be more familiar with, BrowserChannel session IDs are regenerated +far more frequently. Each time a reconnection occurs (a full +session reconnection, not just a back channel reconnect), a new +session ID is generated for the client. For example, each time a +user refreshes the page in their browser they will be given a new +BrowserChannel session and corresponding ID. If a network issue +occurs and forces the client to automatically reconnect (even +without a browser page refresh), a new BrowserChannel session +is still established with a different ID. -[4]: https://github.com/ztellman/aleph +BrowserChannel supports simple message receipt acknowledgements. +When the client sends the server a message (via the forward +channel), the client immediately receives acknowledgement based +on whether the HTTP POST request was successful or not (as the +server returns a standard reply on success). -## Related and alternative frameworks +For server-to-client messages, it is a little more complicated. +Each message sent along the back channel is given an "array id" +(which is included in the data written to the back channel). +When the client reads the data from the back channel it makes +note of the corresponding array ids, and on the next request +to the server (either a forward channel or back channel request) +includes an `AID` parameter with the most recent array id that +has been read. The server then uses this `AID` value to mark +sent items as acknowledged. -* Websockets - Websockets solve the same problems as BrowserChannel, - however BrowserChannel works on almost all existing clients. -* socket.io - [socket.io][5] provides a similar api as BrowserChannel on -top of many transport protocols, including websockets. BrowserChannel -only has two transport protocols: XHR and forever frames (for IE) in -streaming and non-streaming mode. +I recommend reading [this description of the BrowserChannel protocol][2] +for far more in-depth details on everything mentioned in this +section (moreso detailing the client side of the protocol, but +still very helpful information). -[5]: http://socket.io +[2]: http://web.archive.org/web/20121226064550/http://code.google.com/p/libevent-browserchannel-server/wiki/BrowserChannelProtocol -## Run - ;; compile cljs - lein run -m tasks.build-dev-js - ;; compile cljs in advanced mode - lein run -m tasks.build-advanced-js - lein run -m chat-demo.core +As well it may be useful to try out the [chat-demo][3] app +while monitoring ongoing XHR requests. In particular, pay +attention to HTTP requests over `/channel/test` and +`/channel/bind`. -Open two windows at [http://localhost:8080/index.html](http://localhost:8080/index.html) (Advanced compiled) -or [http://localhost:8080/index-dev.html](http://localhost:8080/index-dev.html) and start chatting! +[3]: https://github.com/gered/clj-browserchannel/tree/master/chat-demo -## Run on Heroku -Use the Heroku Clojure [buildpack][7]. +`/channel/test` is where all the previously mentioned test +HTTP requests will be sent to during the initial BrowserChannel +session connection process. `/channel/bind` is where all +forward and back channel HTTP requests are sent to. - heroku config:add BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-clojure.git +> Note that your browser's inspector may not show you the +> response body of back channel requests (the long running +> HTTP GET requests) until they finish. By default in +> clj-browserchannel, these will automatically timeout +> after 4 minutes. -This project additionally -requires two build tasks to compile the ClojureScript during deployment. -Enable [user_env_compile][6]: +## Usage - heroku labs:enable user_env_compile -a -Add this config var: +### Server-Side - heroku config:add LEIN_BUILD_TASK="run -m tasks.build-dev-js, run -m tasks.build-advanced-js" +An example using Immutant as the web server with the BrowserChannel +async adapter for it. This assumes you have added 3 dependencies +in your `project.clj`: -[6]: https://devcenter.heroku.com/articles/labs-user-env-compile -[7]: https://github.com/heroku/heroku-buildpack-clojure.git +* clj-browserchannel (that's _this_ library) +* [clj-browserchannel-immutant-adapter][4] +* [Immutant][5] -### Note on disconnections on Heroku -I have found that Heroku does not immediately report when a connection to a client -is broken. If the client is able to reconnect this is not a problem, -as this is supported by the BrowserChannel API. However when you -unplug the internet cable the client cannot reconnect and the server -must timeout the session. Ussually this happens when trying to send the next -heartbeat to the client. On Heruko this does not report an error, even -though there is no connection to the client. So instead of the -connection timeing out on a heartbeat (after seconds/a minute) the -connection will only timeout after the connection is timed out by the -server (4 minutes by default). The Netty implementation has the same -problem on Heroku. Deployments on Amazon Web Services do not have this -problem. +[4]: https://github.com/gered/clj-browserchannel/tree/master/clj-browserchannel-immutant-adapter +[5]: https://github.com/immutant/immutant -## Configuration: -See default-options in src/net/thegeez/browserchannel.clj -And the :response-timeout option in src/net/thegeez/jetty_async_adapter.clj +```clj +(ns your-app + (:gen-class) + (:require + ; this next line obviously not required. + [ring.middleware.defaults :refer [wrap-defaults site-defaults]] + ; ... + [net.thegeez.browserchannel.server :as browserchannel :refer [wrap-browserchannel] + [net.thegeez.browserchannel.immutant-async-adapter :refer [wrap-immutant-async-adapter]] + [immutant.web :as immutant] + ; ... + )) -## Todo -- Handling acknowledgements by client and callbacks on queued arrays -- Host prefixes -- Heroku disconnection -- Replace session listeners, possibly with lamina -- Explore other event based Java webservers, such as Netty and Webbit +(def event-handlers + {:on-open + (fn [session-id ring-request] + ; called when a new session is established for a client + ) + + :on-close + (fn [session-id ring-request reason] + ; called when an existing client session is closed + ; (for any reason) + ) + + :on-receive + (fn [session-id ring-request data] + ; called when a client has sent data to the server + )}) -## Other BrowserChannel implementations -Many thanks to these authors, their work is the only open-source -documentation on the BrowserChannel protocol. +(def your-app-routes + ; ... + ) -* [libevent-browserchannel-server][libevent] -in C++ by Andy Hochhaus - Has the most extensive [documentation][libevent-doc] on the BrowserChannel protocol -* [browserchannel][ruby] in Ruby by David Turnbull -* [node-browserchannel][node] -in Node.js/Javascript by Joseph Gentle +(def ring-handler + (-> your-app-routes + (wrap-browserchannel event-handlers) + (wrap-defaults site-defaults) + (wrap-immutant-async-adapter))) -[libevent]: http://code.google.com/p/libevent-browserchannel-server -[libevent-doc]: http://code.google.com/p/libevent-browserchannel-server/wiki/BrowserChannelProtocol -[ruby]: https://github.com/dturnbull/browserchannel -[node]: https://github.com/josephg/node-browserchannel +(defn -main [& args] + (immutant/run + #'ring-handler + {:port 8080})) +``` + +All the server-side BrowserChannel magic happens inside of the +`wrap-browserchannel` middleware. The `event-handlers` map +shown above includes all the different events you can respond to +on the server. + +Of important note, in each of these handlers, `ring-request` is +a full Ring HTTP request map like you see with any other normal +HTTP requests in a Clojure web app. You can use this to access +the user's HTTP session for example (but not to change it). + +The return value of these event handlers is ignored. + +##### Ring Middleware + +As mentioned above, `wrap-browserchannel` is what you should add +to your web app's Ring handler to make all the server-side +BrowserChannel functionality work. It takes a map of +event handlers and another optional options map as the last +argument. Options you don't pass in will have their default +value used instead. + +See [here][6] for a description of the various options you can +use. Most applications probably won't need to change any of +these. + +[6]: https://github.com/gered/clj-browserchannel/blob/master/clj-browserchannel/src/net/thegeez/browserchannel/server.clj#L838 + +##### Sending Data + +```clj +(use 'net.thegeez.browserchannel.server) + +; send any clojure data to a client identified by session-id +(send-data! session-id {:msg "Hello, world!"}) +(send-data! session-id :foobar) +(send-data! session-id [:a :b :c]) +(send-data! session-id "a string of text") + +; send something to all clients +(send-data-to-all! {:broadcast "yada yada"}) +``` + +Sending is done asynchronously. If the client does not currently +have a back channel request active, then the data will be queued +up in a buffer which will be flushed once a back channel +becomes available. + +The send functions also take optional callbacks for various +events relevant to message sending: + +* `on-sent` occurs when the data has been written to the client's + back channel. Remember that this may or may not happen immediately! + +* `on-confirm` occurs when the client acknowledges receipt of the + message. This will almost certainly have a long-ish delay before + being triggered. As such it is also at risk of not being raised + all the time (e.g. if the client disconnects before the next + client acknowledgement info can be sent to the server). + +* `on-error` occurs when there was some kind of error writing the + the message to the client's back channel or if the client's + session is terminated before the message could be written to a + back channel. + +```clj +(send-data! + session-id + {:important-data 42} + {:on-sent (fn [] + (println "message written to back channel")) + :on-confirm (fn [] + (println "receipt confirmed by client")) + :on-error (fn [] + (println "message could not be sent"))}) +``` + +The return value of these callback functions is ignored. + +##### Session Management + +Various functions are available to query the status of a client's +connection or to manipulate it. + +```clj +(use 'net.thegeez.browserchannel.server) + +; does a client have an active session? +; NOTE: may have an active session but no currently active back channel! +(connected? session-id) +=> true + +; more detailed info about a client's session +(get-status session-id) +=> {:connected? true + :has-back-channel? true + :last-acknowledged-array-id 42 + :outstanding-backchannel-bytes 0} + +(get-status a-different-session-id) +=> {:connected? false} + +; "politely" tells a client we are going to disconnect them (and to +; not attempt to reconnect), and then disconnects them +(close! session-id) + +; forcefully disconnects a client. note that the client may just +; try to reconnect right away if you use this function +(disconnect! session-id) + +``` + + +### Client-Side + +Client-side use of clj-browserchannel is very simple. + +```clj +(ns your-app.client + (:require + [net.thegeez.browserchannel.client :as browserchannel])) + +(def event-handlers + {:on-open + (fn [] + ; called when a new session is established + ) + + :on-opening + (fn [] + ; called when a new session connection is in progress + ) + + :on-close + (fn [due-to-error? pending undelivered] + ; called when the browserchannel session is closed + ; (for any reason) + ) + + :on-receive + (fn [data] + ; called when data has been received from the server + ) + + :on-sent + (fn [delivered] + ; called when data has been sent successfully to the server + ) + + :on-error + (fn [error-code] + ; called when a connection error occurs. + )}) + +(defn ^:export run [] + (browserchannel/connect! event-handlers)) +``` + +* `on-open` self-explanatory. Also called on reconnects. + +* `on-opening` called after a connection has been initiated but before + it has been established. + +* `on-close` called when the session is closed (either closed by the + client or server). `due-to-error?` is true/false depending on if + the close was caused by an error (if true, then `on-error` would + have been called just before this event). `pending` will contain + a list of any messages that were queued up to be sent and may + or may not have been received by the server. `undelivered` will + contain a list of messages that were queued up and definitely + were not received by the server. This event is also fired if + a connection attempt fails (even though `on-open` won't have been + fired in this case). + +* `on-receive` called when some data is received from the server. + +* `on-sent` called when data has been sent successfully to the server. + `delivered` is a list of the messages that were sent. + +* `on-error` called when an error occurs. See [here][7] for a list of + error codes and what they mean. In BrowserChannel, when an error + occurs, the session is always disconnected (`on-close` will always + fire immediately after this event). This is just how the + BrowserChannel implementation included in Google Closure works. + clj-browserchannel will automatically try to reconnect in the + event of an error. + +The `connect!` function also takes an additional and optional map of +options. Any options not specified will have their default values +used instead. See [here][8] for a description of the available +options and their defaults. + +[7]: https://github.com/gered/clj-browserchannel/blob/master/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs#L18 +[8]: https://github.com/gered/clj-browserchannel/blob/master/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs#L249 + +##### Sending Data + +```clj +(use 'net.thegeez.browserchannel.client) + +; basically identical to server-side sending +(send-data! {:msg "Hello, world!"}) +(send-data! :foobar) +(send-data! [:a :b :c]) +(send-data! "a string of text") +``` + +Sending is done asynchronously. + +Also a nice feature of BrowserChannel is that you can queue up +messages to be sent to the server _before_ a connection is +established. If you do this, the messages will be delivered to the +server in the same HTTP POST request that is used to establish the +new BrowserChannel session (thereby saving some extra round-trips). + +Like with server-side sending, the `send-data!` function also +can take optional callbacks for various message sending events: + +* `on-sent` occurs when the data has been successfully sent + to the server (via the forward channel). This will usually + occur pretty quickly after `send-data!` is called. + +* `on-error` called when any kind of error occurs that prevents + the message from being sent. This error will be raised just + before the main `on-error` event handler. + +```clj +(send-data! + {:important-data 42} + {:on-sent (fn [] + (println "message sent to server successfully")) + :on-error (fn [] + (println "error sending message to server"))}) +``` + +The return value of these callback functions is ignored. + +##### Session Management + +Various functions are available to query the status of the +current BrowserChannel session. + +```clj +(use 'net.thegeez.browserchannel.client) + +; is there currently an active session? +(connected?) +=> true + +; disconnects/closes any active browserchannel session +; also can be used in the on-close event handler to cancel +; any reconnection attempt if needed +(disconnect!) + +(channel-state) +=> :opened +``` + +For a list and descriptions of the different BrowserChannel +connection states (returned by `channel-state`) see [here][9]. + +[9]: https://github.com/gered/clj-browserchannel/blob/master/clj-browserchannel/src/net/thegeez/browserchannel/client.cljs#L10 + + +### Other Notes + +##### BrowserChannel Session Timeouts + +The server-side options do include a session timeout (`session-timeout-interval`) +which is the length of time of inactivity before a BrowserChannel +session is automatically closed (timed out). + +However it is important to note that this timeout period is _only_ +activated when the client does not have an active back channel. If +the client is able to consistently re-open back channels as they +close automatically over time (which is normal behaviour), then the +BrowserChannel session will never time out. + +`session-timeout-interval` is mainly intended to automatically +cleanup sessions when the client (for whatever reason) suddenly +becomes unable to re-open a back channel or the user closes the +browser or something like that. At this point the session timeout +interval will activate and eventually elapse and clean up the session. + ## About Written by: -Gijs Stuurman / [@thegeez][twt] / [Blog][blog] / [GitHub][github] +Gijs Stuurman / +[@thegeez](http://twitter.com/thegeez) / +[Blog](http://thegeez.github.com) / +[GitHub](https://github.com/thegeez) -[twt]: http://twitter.com/thegeez -[blog]: http://thegeez.github.com -[github]: https://github.com/thegeez +Many updates in this fork by: +Gered King / +[@geredking](http://twitter.com/geredking) / +[GitHub](https://github.com/gered) ### License