add initial support for automatic client reconnections on server error
This commit is contained in:
parent
74a33200aa
commit
001d21a63d
|
@ -7,7 +7,11 @@
|
||||||
[goog.events :as events]
|
[goog.events :as events]
|
||||||
[goog.debug.Logger.Level :as log-level]))
|
[goog.debug.Logger.Level :as log-level]))
|
||||||
|
|
||||||
(defonce channel (goog.net.BrowserChannel.))
|
(defonce channel (atom (goog.net.BrowserChannel.)))
|
||||||
|
|
||||||
|
(defonce last-error (atom nil))
|
||||||
|
(defonce reconnect-attempts-counter (atom nil))
|
||||||
|
(defonce reconnect-timer (atom nil))
|
||||||
|
|
||||||
(def ^:private bch-state-enum-to-keyword
|
(def ^:private bch-state-enum-to-keyword
|
||||||
{
|
{
|
||||||
|
@ -61,7 +65,7 @@
|
||||||
(defn channel-state
|
(defn channel-state
|
||||||
"returns the current state of the browserchannel connection."
|
"returns the current state of the browserchannel connection."
|
||||||
[]
|
[]
|
||||||
(get bch-state-enum-to-keyword (.getState channel) :unknown))
|
(get bch-state-enum-to-keyword (.getState @channel) :unknown))
|
||||||
|
|
||||||
(defn connected?
|
(defn connected?
|
||||||
"returns true if the browserchannel connection is currently connected."
|
"returns true if the browserchannel connection is currently connected."
|
||||||
|
@ -72,7 +76,7 @@
|
||||||
"sets the debug log level, and optionally a log output handler function
|
"sets the debug log level, and optionally a log output handler function
|
||||||
which defaults to js/console.log"
|
which defaults to js/console.log"
|
||||||
[level & [f]]
|
[level & [f]]
|
||||||
(doto (.. channel getChannelDebug getLogger)
|
(doto (.. @channel getChannelDebug getLogger)
|
||||||
(.setLevel level)
|
(.setLevel level)
|
||||||
(.addHandler (or f #(js/console.log %)))))
|
(.addHandler (or f #(js/console.log %)))))
|
||||||
|
|
||||||
|
@ -82,7 +86,31 @@
|
||||||
on-success callback is invoked."
|
on-success callback is invoked."
|
||||||
[data & [{:keys [on-success]}]]
|
[data & [{:keys [on-success]}]]
|
||||||
(if data
|
(if data
|
||||||
(.sendMap channel (encode-map data) {:on-success on-success})))
|
(.sendMap @channel (encode-map data) {:on-success on-success})))
|
||||||
|
|
||||||
|
(defn- get-anti-forgery-token
|
||||||
|
[]
|
||||||
|
(if-let [tag (sel1 "meta[name='anti-forgery-token']")]
|
||||||
|
(.-content tag)))
|
||||||
|
|
||||||
|
(defn- apply-options!
|
||||||
|
[options]
|
||||||
|
(let [csrf-token (get-anti-forgery-token)
|
||||||
|
headers (merge
|
||||||
|
(:headers options)
|
||||||
|
(if csrf-token {"X-CSRF-Token" csrf-token}))]
|
||||||
|
(set-debug-log! (if (:verbose-logging? options) log-level/FINER log-level/OFF))
|
||||||
|
(doto @channel
|
||||||
|
(.setExtraHeaders (clj->js headers))
|
||||||
|
(.setAllowChunkedMode (boolean (:allow-chunked-mode? options)))
|
||||||
|
(.setAllowHostPrefix (boolean (:allow-host-prefix? options)))
|
||||||
|
(.setFailFast (boolean (:fail-fast? options)))
|
||||||
|
(.setForwardChannelMaxRetries (:max-forward-channel-retries options))
|
||||||
|
(.setForwardChannelRequestTimeout (:forward-channel-request-timeout options)))
|
||||||
|
;; HACK: this is relying on changing a value for a setting that google's
|
||||||
|
;; documentation lists as private. however, it is a fairly important
|
||||||
|
;; setting to be able to change, so i think it's worth the risk...
|
||||||
|
(set! goog.net.BrowserChannel/BACK_CHANNEL_MAX_RETRIES (:max-back-channel-retries options))))
|
||||||
|
|
||||||
(defn connect!
|
(defn connect!
|
||||||
"starts the browserchannel connection (initiates a connection to the server).
|
"starts the browserchannel connection (initiates a connection to the server).
|
||||||
|
@ -92,33 +120,62 @@
|
||||||
(let [state (channel-state)]
|
(let [state (channel-state)]
|
||||||
(if (or (= state :closed)
|
(if (or (= state :closed)
|
||||||
(= state :init))
|
(= state :init))
|
||||||
(.connect channel
|
(.connect @channel
|
||||||
(str base "/test")
|
(str base "/test")
|
||||||
(str base "/bind")))))
|
(str base "/bind")))))
|
||||||
|
|
||||||
(defn disconnect!
|
(defn- clear-reconnect-timer!
|
||||||
"disconnects and closes the browserchannel connection."
|
|
||||||
[]
|
[]
|
||||||
(if-not (= (channel-state) :closed)
|
(swap! reconnect-timer
|
||||||
(.disconnect channel)))
|
(fn [timer]
|
||||||
|
(if timer (js/clearTimeout timer))
|
||||||
|
nil)))
|
||||||
|
|
||||||
(defn- get-anti-forgery-token
|
(defn disconnect!
|
||||||
|
"disconnects and closes the browserchannel connection, and stops and
|
||||||
|
reconnection attempts"
|
||||||
[]
|
[]
|
||||||
(if-let [tag (sel1 "meta[name='anti-forgery-token']")]
|
(reset! last-error :ok)
|
||||||
(.-content tag)))
|
(clear-reconnect-timer!)
|
||||||
|
(if-not (= (channel-state) :closed)
|
||||||
|
(.disconnect @channel)))
|
||||||
|
|
||||||
|
(defn- reconnect!
|
||||||
|
[handler options]
|
||||||
|
(reset! channel (goog.net.BrowserChannel.))
|
||||||
|
(swap! reconnect-attempts-counter inc)
|
||||||
|
(.setHandler @channel handler)
|
||||||
|
(apply-options! options)
|
||||||
|
(connect! options))
|
||||||
|
|
||||||
(defn- ->handler
|
(defn- ->handler
|
||||||
[{:keys [on-open on-close on-receive on-sent on-error]}]
|
[{:keys [on-open on-close on-receive on-sent on-error]} options]
|
||||||
(let [handler (goog.net.BrowserChannel.Handler.)]
|
(let [handler (goog.net.BrowserChannel.Handler.)]
|
||||||
(set! (.-channelOpened handler)
|
(set! (.-channelOpened handler)
|
||||||
(fn [ch]
|
(fn [ch]
|
||||||
|
(reset! last-error nil)
|
||||||
|
(reset! reconnect-attempts-counter 0)
|
||||||
(if on-open
|
(if on-open
|
||||||
(on-open))))
|
(on-open))))
|
||||||
(set! (.-channelClosed handler)
|
(set! (.-channelClosed handler)
|
||||||
(fn [ch pending undelivered]
|
(fn [ch pending undelivered]
|
||||||
(if on-close
|
(let [due-to-error? (and @last-error
|
||||||
(on-close (decode-queued-map-array pending)
|
(not (some #{@last-error} [:stop :ok])))]
|
||||||
(decode-queued-map-array undelivered)))))
|
(when (and (:auto-reconnect? options)
|
||||||
|
due-to-error?
|
||||||
|
(< @reconnect-attempts-counter
|
||||||
|
(dec (:max-reconnect-attempts options))))
|
||||||
|
(clear-reconnect-timer!)
|
||||||
|
(js/setTimeout
|
||||||
|
#(reconnect! handler options)
|
||||||
|
(if (= @last-error :unknown-session-id)
|
||||||
|
0
|
||||||
|
(:reconnect-time options))))
|
||||||
|
(if on-close
|
||||||
|
(on-close due-to-error?
|
||||||
|
(decode-queued-map-array pending)
|
||||||
|
(decode-queued-map-array undelivered)))
|
||||||
|
(reset! last-error nil))))
|
||||||
(set! (.-channelHandleArray handler)
|
(set! (.-channelHandleArray handler)
|
||||||
(fn [ch m]
|
(fn [ch m]
|
||||||
(if on-receive
|
(if on-receive
|
||||||
|
@ -135,23 +192,12 @@
|
||||||
(on-success))))))
|
(on-success))))))
|
||||||
(set! (.-channelError handler)
|
(set! (.-channelError handler)
|
||||||
(fn [ch error-code]
|
(fn [ch error-code]
|
||||||
(if on-error
|
(let [error-code (get bch-error-enum-to-keyword error-code :unknown)]
|
||||||
(on-error (get bch-error-enum-to-keyword error-code :unknown)))))
|
(reset! last-error error-code)
|
||||||
|
(if on-error
|
||||||
|
(on-error error-code)))))
|
||||||
handler))
|
handler))
|
||||||
|
|
||||||
(defn- apply-options!
|
|
||||||
[options]
|
|
||||||
(set-debug-log! (if (:verbose-logging? options) log-level/FINER log-level/OFF))
|
|
||||||
(.setAllowChunkedMode channel (boolean (:allow-chunked-mode? options)))
|
|
||||||
(.setAllowHostPrefix channel (boolean (:allow-host-prefix? options)))
|
|
||||||
(.setFailFast channel (boolean (:fail-fast? options)))
|
|
||||||
(.setForwardChannelMaxRetries channel (:max-forward-channel-retries options))
|
|
||||||
(.setForwardChannelRequestTimeout channel (:forward-channel-request-timeout options))
|
|
||||||
;; HACK: this is relying on changing a value for a setting that google's
|
|
||||||
;; documentation lists as private. however, it is a fairly important
|
|
||||||
;; setting to be able to change, so i think it's worth the risk...
|
|
||||||
(set! goog.net.BrowserChannel/BACK_CHANNEL_MAX_RETRIES (:max-back-channel-retries options)))
|
|
||||||
|
|
||||||
(def default-options
|
(def default-options
|
||||||
"default options that will be applied by init! unless
|
"default options that will be applied by init! unless
|
||||||
overridden."
|
overridden."
|
||||||
|
@ -184,6 +230,18 @@
|
||||||
|
|
||||||
;; whether to enable somewhat verbose debug logging
|
;; whether to enable somewhat verbose debug logging
|
||||||
:verbose-logging? false
|
:verbose-logging? false
|
||||||
|
|
||||||
|
;; whether to automatically reconnect in the event the session
|
||||||
|
;; connection is lost due to some error. if the server requests
|
||||||
|
;; that we disconnect, an automatic reconnect will not occur
|
||||||
|
:auto-reconnect? true
|
||||||
|
|
||||||
|
;; time after an error resulting in a disconnect before we try to
|
||||||
|
;; reconnect again (milliseconds)
|
||||||
|
:reconnect-time (* 3 1000)
|
||||||
|
|
||||||
|
; sets the max number of reconnection attempts
|
||||||
|
:max-reconnect-attempts 3
|
||||||
})
|
})
|
||||||
|
|
||||||
(defn init!
|
(defn init!
|
||||||
|
@ -193,7 +251,7 @@
|
||||||
handler should be a map of event handler functions:
|
handler should be a map of event handler functions:
|
||||||
|
|
||||||
{:on-open (fn [] ...)
|
{:on-open (fn [] ...)
|
||||||
:on-close (fn [pending undelivered] ...)
|
:on-close (fn [due-to-error? pending undelivered] ...)
|
||||||
:on-receive (fn [data] ...)
|
:on-receive (fn [data] ...)
|
||||||
:on-sent (fn [delivered] ...)
|
:on-sent (fn [delivered] ...)
|
||||||
:on-error (fn [error-code] ...)
|
:on-error (fn [error-code] ...)
|
||||||
|
@ -204,15 +262,13 @@
|
||||||
:on-sent is called when data has been successfully sent to
|
:on-sent is called when data has been successfully sent to
|
||||||
the server ('delivered' is a list of what was sent).
|
the server ('delivered' is a list of what was sent).
|
||||||
:on-error is only invoked once just before the connection is
|
:on-error is only invoked once just before the connection is
|
||||||
closed, and only if there was an error."
|
closed, and only if there was an error.
|
||||||
|
|
||||||
|
for other supported options, see
|
||||||
|
net.thegeez.browserchannel.client/default-options"
|
||||||
[handler & [options]]
|
[handler & [options]]
|
||||||
(let [options (merge default-options options)]
|
(let [options (merge default-options options)]
|
||||||
(events/listen js/window "unload" #(disconnect!))
|
(events/listen js/window "unload" #(disconnect!))
|
||||||
(.setHandler channel (->handler handler))
|
(.setHandler @channel (->handler handler options))
|
||||||
(apply-options! options)
|
(apply-options! options)
|
||||||
(let [csrf-token (get-anti-forgery-token)
|
|
||||||
headers (merge
|
|
||||||
(:headers options)
|
|
||||||
(if csrf-token {"X-CSRF-Token" csrf-token}))]
|
|
||||||
(.setExtraHeaders channel (clj->js headers)))
|
|
||||||
(connect! options)))
|
(connect! options)))
|
Reference in a new issue