Skip to content

Commit

Permalink
Connect to wish-server and listen for push events, as appropriate (#92
Browse files Browse the repository at this point in the history
)

* Add methods for collection the watch session API auth map

Refs #53

* Import `http` from old branch; add `(create-session)`

* Import updates to cljs-worker that fix CORS requests to push-server

* Add events and fx to create and connect to sessions

* Automatically create push sessions as the user navigates

* Add some framework for dispatching push events

* Reload sheets in response to "CHANGED" events

Refs #53

It's happening!

* Handle fatal server disconnects with exponential backoff retry

Hopefully this will only come into play on local dev, but it's good to
handle anyway, just in case.

* Create shared throttle-with-set implementation

Prep for `create-watches` routine, which we also want to throttle

* Respond to `need-watch` events

* Wait much longer between push/check when offline

But check immediately when we come back online
  • Loading branch information
dhleong committed Nov 1, 2018
1 parent 1603047 commit 0a4f6ba
Show file tree
Hide file tree
Showing 16 changed files with 502 additions and 37 deletions.
2 changes: 2 additions & 0 deletions dev/cljs/wish/config.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
(def server-root "")

(def gdrive-client-id "661182319990-1uerkr0pue6k60a83atj2f58md95fb1b.apps.googleusercontent.com")

(def push-server "http://localhost:4321")
2 changes: 2 additions & 0 deletions prod/cljs/wish/config.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
(def server-root "/wish")

(def gdrive-client-id "661182319990-3aa8akj9fh8eva9lf7bt02621q2i18s6.apps.googleusercontent.com")

(def push-server "https://wish-server.now.sh")
23 changes: 16 additions & 7 deletions src/cljs-worker/wish/worker/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
; external resources:
"https://fonts.googleapis.com/icon?family=Material+Icons"])

(def push-server-url (url/url config/push-server))


; ======= utils ===========================================

Expand Down Expand Up @@ -118,7 +120,14 @@
; don't cache gapi; it's simpler to just use local-storage
; and handle caching sheets and data sources ourselves
(contains? #{"apis.google.com"}
(:host url))))
(:host url))

; also, don't interfere with requests to the push-server.
; This is generally only a problem for local dev
(and (= (:port push-server-url)
(:port url))
(= (:host push-server-url)
(:host url)))))

(defn wish-asset? [url]
(contains? #{"localhost"
Expand Down Expand Up @@ -245,12 +254,6 @@
(let [request (.-request ev)
url (-> request .-url url/url)]
(cond
(shell-root? url)
(fetch-shell-path shell-root)

(shell-asset? url)
(fetch-shell-asset url)

(never-cache? url)
(do
(log "Never cache: " url)
Expand All @@ -259,6 +262,12 @@
(log/warn "Unable to fetch " url ": " e)
(js/Response. nil #js {:status 503})))))

(shell-root? url)
(fetch-shell-path shell-root)

(shell-asset? url)
(fetch-shell-asset url)

:else
(fetch-with-cache request))))

Expand Down
93 changes: 86 additions & 7 deletions src/cljs/wish/events.cljs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(ns wish.events
(:require-macros [wish.util.log :refer [log]])
(:require-macros [wish.util.log :as log :refer [log]])
(:require [clojure.string :as str]
[re-frame.core :refer [dispatch reg-event-db reg-event-fx
path
Expand All @@ -9,6 +9,7 @@
[wish.db :as db]
[wish.fx :as fx]
[wish.inventory :as inv]
[wish.push :as push]
[wish.providers :as providers]
[wish.sheets.util :refer [update-uses update-sheet update-sheet-path]]
[wish.subs-util :refer [active-sheet-id]]
Expand All @@ -26,7 +27,8 @@
[trim-v]
(fn-traced [{:keys [db]} page-spec]
{:db (assoc db :page page-spec)
:dispatch [::update-keymap page-spec]}))
:dispatch-n [[::update-keymap page-spec]
[:push/check]]}))

(reg-event-db
:set-device
Expand Down Expand Up @@ -77,7 +79,21 @@
(cond-> {:db (assoc db :online? online?)}

; if we're coming back online, trigger init!
online? (assoc :providers/init! :!))))
online? (assoc :providers/init! :!

; also, go ahead and check if we should
; init a push connection
:dispatch [:push/check])

; going offline? go ahead and boost the push retry delay,
; if we had any. since we immediately trigger a check when
; we come back online anyway, this seems like a good way to
; ensure ; our desperate pleas for attention don't ruin battery
; life too much
(not online?) (update-in [:db ::push/retry-delay]
(fn [old-delay]
(when old-delay
(* 4 old-delay)))))))

(reg-event-fx
:set-latest-update
Expand Down Expand Up @@ -208,6 +224,7 @@
; delete the sheet source to trigger a reload
[:db :sheet-sources sheet-id] nil)))


; ======= sheet-related ====================================

(defn- reset-sheet-err
Expand Down Expand Up @@ -624,7 +641,7 @@
(reg-event-fx
:toggle-equipped
[trim-v]
(fn [cofx [item]]
(fn-traced [cofx [item]]
(update-sheet-path cofx [:equipped]
(fn [equipped inst-id]
(if (get equipped inst-id)
Expand All @@ -638,13 +655,13 @@
(reg-event-db
::db/put-pending-save
[trim-v]
(fn [db [sheet-id]]
(fn-traced [db [sheet-id]]
(update db ::db/pending-saves conj sheet-id)))

(reg-event-db
::db/mark-save-processing
[trim-v]
(fn [db [sheet-id]]
(fn-traced [db [sheet-id]]
(-> db
(update ::db/pending-saves disj sheet-id)
(update ::db/save-errors disj sheet-id)
Expand All @@ -653,10 +670,72 @@
(reg-event-db
::db/finish-save
[trim-v]
(fn [db [sheet-id err]]
(fn-traced [db [sheet-id err]]
(-> db
(update ::db/pending-saves disj sheet-id)
(update ::db/processing-saves disj sheet-id)
(update ::db/save-errors (if err
conj
disj) sheet-id))))


; ======= Push notifications ==============================

; NOTE: we immediately trigger :push/check on coming back online,
; so it should be fine to have a very long sleep when offline
(def ^:private push-retry-delay-offline 120000)
(def ^:private push-retry-delay-online 15000)

(reg-event-fx
:push/check
[(inject-cofx ::inject/sub ^:ignore-dispose [:interested-push-ids])]
(fn-traced [{ids :interested-push-ids}]
(if (seq ids)
{:push/ensure ids}
{:push/disconnect :!})))

(reg-event-fx
:push/retry-later
(fn-traced [{:keys [db]} _]
; retry connect (via :push/check) with exponential backoff:
(let [online? (:online? db)
last-delay (::push/retry-delay db)
max-delay (if online?
push-retry-delay-online
push-retry-delay-offline)
new-delay (if last-delay
(min max-delay
(* 2 last-delay))

; base of 2s delay
2000)]
(log "Retry push connection after " new-delay)
{:db (assoc db ::push/retry-delay new-delay)
:dispatch-later [{:ms new-delay :dispatch [:push/check]}]})))

(reg-event-fx
::push/session-created
[trim-v (inject-cofx ::inject/sub ^:ignore-dispose [:interested-push-ids])]
(fn-traced [{current-ids :interested-push-ids :keys [db]}
[interested-ids session-id]]
(let [session-id (when (= current-ids interested-ids)
session-id)]
(when-not session-id
(log "Drop un-interesting sesssion; was " interested-ids "; now " current-ids))

; always clear the retry-delay on success to reset the backoff
{:push/connect session-id
:db (dissoc db ::push/retry-delay)})))

(reg-event-fx
:reload-changed!
[trim-v (inject-cofx ::inject/sub [:active-sheet-id])]
(fn [{:keys [active-sheet-id]} [changed-ids]]
(when (> (count changed-ids) 1)
; TODO:
(log/todo "Support reloading data sources as well as sheets"))

(when (contains? changed-ids active-sheet-id)
; trigger sheet data reload
{:load-sheet! active-sheet-id})))

44 changes: 44 additions & 0 deletions src/cljs/wish/fx.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[alandipert.storage-atom :refer [local-storage]]
[wish.db :as db]
[wish.sources :as sources]
[wish.push :as push]
[wish.providers :as providers :refer [load-sheet! save-sheet!]]
[wish.sheets :as sheets]
[wish.util :refer [>evt]]))
Expand Down Expand Up @@ -147,3 +148,46 @@
(when-let [controller js/navigator.serviceWorker.controller]
; let the serviceWorker know we're listening
(.postMessage controller (str [:ready]))))))


; ======= Push notifications ==============================

(defonce ^:private current-event-source (atom nil))
(defonce ^:private current-session-ids (atom nil))

(reg-fx
:push/disconnect
(fn [_]
(reset! current-session-ids nil)
(swap! current-event-source
(fn [old-source]
(when old-source
(log "Disconnect from push session")
(.close old-source))
nil))))

(reg-fx
:push/connect
(fn [session-id]
; NOTE: session-id may be nil if we lost interest in the
; session between requesting the create and it being created
(when session-id
(log "Connect to push session " session-id)
(swap! current-event-source
(fn [old-source]
(when old-source
(.close old-source))
(push/connect session-id))))))

(reg-fx
:push/ensure
(fn [interested-ids]
(swap! current-session-ids
(fn [current]
(when (or (not= current interested-ids)
(= :closed (push/ready-state @current-event-source)))
(log "Create push session for " interested-ids " (was: " current ")")
(push/create-session interested-ids))

; always swap in the new interested-ids set
interested-ids))))
15 changes: 15 additions & 0 deletions src/cljs/wish/providers.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,18 @@
(throw (js/Error. (str "No provider instance for " sheet-id
"(" provider-id " / " pro-sheet-id ")"))))))

(defn watch-auth-map
"Given a collection of sheet IDs, generate an appropate
auth-map for use with the wish-server watch session API"
[sheet-ids]
(->> sheet-ids
(map (comp first unpack-id))
(into #{})
(reduce
(fn [m provider-id]
(if-let [auth (some-> provider-id
(provider-key :inst)
(provider/watch-auth))]
(assoc m provider-id auth)
m))
{})))
6 changes: 5 additions & 1 deletion src/cljs/wish/providers/caching.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,11 @@
(swap! dirty?-storage disj file-id))

; return the result as-is
result))))
result)))

(watch-auth [this]
; delegate
(provider/watch-auth base)))

(defn with-caching [base-provider]
(let [cache-id (keyword (str (name (provider/id base-provider))
Expand Down
7 changes: 6 additions & 1 deletion src/cljs/wish/providers/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@
The original `data` *may* be provided, which you can use to
update `:name`, for example, but it may also be omitted.
`data-str` is in the same string format that should be
returned by `load-raw`."))
returned by `load-raw`.")

(watch-auth
[this]
"Generate the auth data needed to watch changes to files provided
by this provider, or nil if that's not supported by this provider"))

(defn signed-out-err?
"Check if the given error was caused by not being signed into the provider"
Expand Down
16 changes: 12 additions & 4 deletions src/cljs/wish/providers/gdrive.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,15 @@
(.-currentUser)
(.get)))

(defn- auth-response []
(some-> (current-user)
(.getAuthResponse)))

(defn- access-token
"When logged in, get the current user's access token"
[]
(-> (current-user)
(.getAuthResponse)
(.-access_token)))
(some-> (auth-response)
(.-access_token)))

(defn- update-signin-status!
[signed-in?]
Expand Down Expand Up @@ -519,7 +522,12 @@
data-str))

; not ready? don't try
(to-chan [[(js/Error. "No network; unable to save sheet") nil]]))))
(to-chan [[(js/Error. "No network; unable to save sheet") nil]])))

(watch-auth [this]
(when-let [resp (auth-response)]
{:id_token (.-id_token resp)
:access_token (access-token)})))

(defn create-provider []
(->GDriveProvider))
Loading

0 comments on commit 0a4f6ba

Please sign in to comment.