Skip to content

Commit

Permalink
Background tabs will reconnect once they become active again
Browse files Browse the repository at this point in the history
  • Loading branch information
ggeoffrey committed Jan 26, 2024
1 parent dee3ec2 commit 54b423c
Showing 1 changed file with 32 additions and 5 deletions.
37 changes: 32 additions & 5 deletions src/hyperfiddle/electric_client.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ Returns a task producing nil or failing if the websocket was closed before end o

(comment (take 5 retry-delays))

(defn wait-for-window-to-be-visible
"Return a task completing when the current browser tab or window becomes visible
to the user, or immediately if it is already visible. Use case: detect when a
background tab becomes active again."
[]
(let [visible! (m/dfv)
visible? #(= "visible" (.-visibilityState js/document))]
(letfn [(on-visibility-change [_]
;; don't use a one-off event-listener because the visiblitichange
;; event's spec doesn't say "visible" means the page was "hidden"
;; before. "hidden" or "visible" could therefore fire more than
;; once. Spec: https://html.spec.whatwg.org/multipage/interaction.html#page-visibility
(when (visible?)
(.removeEventListener js/document "visibilitychange" on-visibility-change)
(visible! true)))]
(if (visible?)
(visible! true)
(.addEventListener js/document "visibilitychange" on-visibility-change)))
visible!))

(defn boot-with-retry [client conn]
(m/sp
(let [ws-server-url *ws-server-url*]
Expand All @@ -124,13 +144,20 @@ Returns a task producing nil or failing if the websocket was closed before end o
(m/amb x (recur))
(m/amb)))))))))]
(if-some [code (:code info)]
;; TODO wait for tab to be visible before reconnecting. No need to connect a sleeping/throttled tab.
(case code ; https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1
(1005 1006) (do (.log js/console "Connection lost.") (seq retry-delays))
(1005 1006) (do (.log js/console "Connection lost.") (m/? (wait-for-window-to-be-visible)) (seq retry-delays))
(1008) (throw (ex-info "Stale client" {:hyperfiddle.electric/type ::stale-client}))
(1013) (do (.log js/console "Server timed out, considering this client inactive.")
(seq retry-delays))
(throw (ex-info (str "Remote error - " code " " (:reason info)) {})))
(1013) ; server timeout - The WS spec defines 1011 - arbitrary server error,
; and 1015 - TLS exception. 1012, 1013, and 1014 are undefined. We
; pick 1013 for "Server closed the connection because it didn't hear of
; this client for too long".
(do (.log js/console "Server timed out, considering this client inactive.")
(m/? (wait-for-window-to-be-visible))
(seq retry-delays))
; else
(do (.log js/console (str "Remote error - " code " " (:reason info)))
(m/? (wait-for-window-to-be-visible))
(seq retry-delays)))
(do (.log js/console "Failed to connect.") delays)))]
(.log js/console (str "Next attempt in " (/ delay 1000) " seconds."))
(recur (m/? (m/sleep delay delays)))))))))
Expand Down

1 comment on commit 54b423c

@avocade
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌🏻🙏👌🚀 Let's see if this resolves all our WSODs or if there are other failure modes as well. But this seems like a great tweak, thanks a lot @ggeoffrey.

Please sign in to comment.