better handling of user login/logout and passing user-id's to views

when logging in/out, there is a small window where the server's session
state and the client-side state and view subscriptions will not exactly
align and some view subscription updates (because
mtgcoll.client.auth/user-profile was updated) will get treated as
unauthorized.

this change is admittedly somewhat quick/hacky, but basically the idea
is to track the sente connection state (which is true when sente is both
connected and a handshake event has been received) in a reagent atom.
if false, we hide the entire ui and show a "connecting ..." message.

not super pretty, but it's only shown to the user briefly and it solves
the problem. i'll need to revisit this at some point and wrap it up
in some library code or something somewhere to make the whole method
prettier to use in the future
This commit is contained in:
Gered 2016-07-24 15:32:03 -04:00
parent 20ab3c0693
commit 962a3624b7
5 changed files with 59 additions and 35 deletions

View file

@ -19,6 +19,10 @@
[] []
(not (nil? @user-profile))) (not (nil? @user-profile)))
(defn get-username
[]
(:username @user-profile))
(defn set-user-profile! (defn set-user-profile!
[profile] [profile]
(reset! user-profile profile)) (reset! user-profile profile))

View file

@ -25,9 +25,15 @@
(ajax/POST (->url "/login") (ajax/POST (->url "/login")
:params {:username username :password password} :params {:username username :password password}
:on-error #(reset! error "Invalid username/password.") :on-error #(reset! error "Invalid username/password.")
:on-success (fn [_] :on-success (fn [response]
(on-close) ; i'm sick and fucking tired of using cljs-ajax. it's a bloated piece
(views/reconnect!)))))) ; of shit that has always been too easy to use in a wrong way and when
; it happens, it's almost always unclear what the fuck is wrong.
; this is the last fucking project where i will be using it.
(let [user-profile (clojure.walk/keywordize-keys response)]
(on-close)
(views/reconnect!)
(auth/set-user-profile! user-profile)))))))
on-key-up (fn [e] on-key-up (fn [e]
(if (= 13 (.-keyCode e)) (if (= 13 (.-keyCode e))
(on-submit)))] (on-submit)))]

View file

@ -5,7 +5,8 @@
[webtools.reagent.bootstrap :as bs] [webtools.reagent.bootstrap :as bs]
[webtools.cljs.utils :refer [->url]] [webtools.cljs.utils :refer [->url]]
[mtgcoll.client.auth :as auth] [mtgcoll.client.auth :as auth]
[mtgcoll.client.components.auth :refer [login-form]])) [mtgcoll.client.components.auth :refer [login-form]]
[mtgcoll.client.views :as views]))
(defonce error (r/atom nil)) (defonce error (r/atom nil))
@ -25,36 +26,40 @@
(defn app-body (defn app-body
[page-component] [page-component]
(let [active-breadcrumb @active-breadcrumb] (if (views/connected?)
[:div#app-body.container (let [active-breadcrumb @active-breadcrumb]
[bs/Navbar {:inverse true} [:div#app-body.container
[bs/Navbar.Header [bs/Navbar {:inverse true}
[bs/Navbar.Brand [bs/Navbar.Header
[:a#logo {:href "#/"} [bs/Navbar.Brand
[:span [:img {:src (->url "/img/mtg_icon.png")}]] [:a#logo {:href "#/"}
"Card Collection"]]] [:span [:img {:src (->url "/img/mtg_icon.png")}]]
[bs/Navbar.Collapse "Card Collection"]]]
[bs/Nav [bs/Navbar.Collapse
[bs/NavItem {:href "#/owned" :active (= :owned active-breadcrumb)} "Owned"] [bs/Nav
[bs/NavItem {:href "#/all" :active (= :all active-breadcrumb)} "All"] [bs/NavItem {:href "#/owned" :active (= :owned active-breadcrumb)} "Owned"]
[bs/NavItem {:href "#/sets" :active (= :sets active-breadcrumb)} "Sets"] [bs/NavItem {:href "#/all" :active (= :all active-breadcrumb)} "All"]
[bs/NavItem {:href "#/stats" :active (= :stats active-breadcrumb)} "Statistics"]] [bs/NavItem {:href "#/sets" :active (= :sets active-breadcrumb)} "Sets"]
(if (auth/auth-required?) [bs/NavItem {:href "#/stats" :active (= :stats active-breadcrumb)} "Statistics"]]
[bs/Nav {:pull-right true} (if (auth/auth-required?)
(if (auth/authenticated?) [bs/Nav {:pull-right true}
[bs/NavDropdown {:title (:username @auth/user-profile)} (if (auth/authenticated?)
[bs/MenuItem {:on-click #(auth/logout!)} "Logout"]] [bs/NavDropdown {:title (:username @auth/user-profile)}
[bs/NavItem {:on-click auth/show-login-form!} "Login"])])]] [bs/MenuItem {:on-click (fn [_]
[bs/Modal (auth/logout!)
{:show (boolean @error) (views/reconnect!))} "Logout"]]
:on-hide clear-error!} [bs/NavItem {:on-click auth/show-login-form!} "Login"])])]]
[bs/Modal.Header [bs/Modal.Title "Error"]] [bs/Modal
[bs/Modal.Body {:show (boolean @error)
[:p @error]] :on-hide clear-error!}
[bs/Modal.Footer [bs/Modal.Header [bs/Modal.Title "Error"]]
[bs/Button {:on-click clear-error!} "Close"]]] [bs/Modal.Body
[login-form] [:p @error]]
page-component])) [bs/Modal.Footer
[bs/Button {:on-click clear-error!} "Close"]]]
[login-form]
page-component])
[:h1 "Connecting ..."]))
(defn page (defn page
[page-component] [page-component]

View file

@ -1,11 +1,18 @@
(ns mtgcoll.client.views (ns mtgcoll.client.views
(:require (:require
[reagent.core :as r]
[taoensso.sente :as sente] [taoensso.sente :as sente]
[views.reagent.sente.client :as vr] [views.reagent.sente.client :as vr]
[mtgcoll.client.auth :as auth])) [mtgcoll.client.auth :as auth]))
(defonce sente-socket (atom {})) (defonce sente-socket (atom {}))
(defonce connected (r/atom false))
(defn connected?
[]
(boolean @connected))
(defn chsk-exists? (defn chsk-exists?
[] []
(not (nil? (:chsk @sente-socket)))) (not (nil? (:chsk @sente-socket))))
@ -34,6 +41,7 @@
(= :chsk/handshake ev-id) (= :chsk/handshake ev-id)
(let [[_ _ handshake-data] ev-data (let [[_ _ handshake-data] ev-data
{:keys [user]} handshake-data] {:keys [user]} handshake-data]
(reset! connected true)
(auth/set-user-profile! user)) (auth/set-user-profile! user))
(= :chsk/recv id) (= :chsk/recv id)
@ -47,6 +55,7 @@
(defn reconnect! (defn reconnect!
[] []
(clear-keepalive-interval!) (clear-keepalive-interval!)
(reset! connected false)
(sente/chsk-reconnect! (:chsk @sente-socket))) (sente/chsk-reconnect! (:chsk @sente-socket)))
(defn init! (defn init!

View file

@ -12,7 +12,7 @@
(if-let [user (auth/validate-credentials username password)] (if-let [user (auth/validate-credentials username password)]
(do (do
(log/info username " logged in.") (log/info username " logged in.")
(-> (response/content "ok") (-> (response/json user)
(session/set-from-request request) (session/set-from-request request)
(session/assoc :user user))) (session/assoc :user user)))
(do (do