onasty/web/src/Shared.elm (view raw)
Smirnov Oleksandr
Smirnov Oleksandr
ss2316544@gmail.com refactor(web): make use of types for handling auth (#137)..., 11 months ago
ss2316544@gmail.com refactor(web): make use of types for handling auth (#137)..., 11 months ago
| 1 | module Shared exposing |
| 2 | ( Flags, decoder |
| 3 | , Model, Msg |
| 4 | , init, update, subscriptions |
| 5 | ) |
| 6 | |
| 7 | {-| |
| 8 | |
| 9 | @docs Flags, decoder |
| 10 | @docs Model, Msg |
| 11 | @docs init, update, subscriptions |
| 12 | |
| 13 | -} |
| 14 | |
| 15 | import Api.Auth |
| 16 | import Auth.User |
| 17 | import Data.Credentials exposing (Credentials) |
| 18 | import Dict |
| 19 | import Effect exposing (Effect) |
| 20 | import Json.Decode |
| 21 | import JwtUtil |
| 22 | import Route exposing (Route) |
| 23 | import Route.Path |
| 24 | import Shared.Model |
| 25 | import Shared.Msg |
| 26 | import Task |
| 27 | import Time |
| 28 | |
| 29 | |
| 30 | |
| 31 | -- FLAGS |
| 32 | |
| 33 | |
| 34 | type alias Flags = |
| 35 | { accessToken : Maybe String |
| 36 | , refreshToken : Maybe String |
| 37 | } |
| 38 | |
| 39 | |
| 40 | decoder : Json.Decode.Decoder Flags |
| 41 | decoder = |
| 42 | Json.Decode.map2 Flags |
| 43 | (Json.Decode.field "access_token" (Json.Decode.maybe Json.Decode.string)) |
| 44 | (Json.Decode.field "refresh_token" (Json.Decode.maybe Json.Decode.string)) |
| 45 | |
| 46 | |
| 47 | |
| 48 | -- INIT |
| 49 | |
| 50 | |
| 51 | type alias Model = |
| 52 | Shared.Model.Model |
| 53 | |
| 54 | |
| 55 | init : Result Json.Decode.Error Flags -> Route () -> ( Model, Effect Msg ) |
| 56 | init flagsResult _ = |
| 57 | let |
| 58 | flags : Flags |
| 59 | flags = |
| 60 | flagsResult |> Result.withDefault { accessToken = Nothing, refreshToken = Nothing } |
| 61 | |
| 62 | maybeCredentials : Maybe Credentials |
| 63 | maybeCredentials = |
| 64 | Maybe.map2 |
| 65 | (\access refresh -> { accessToken = access, refreshToken = refresh }) |
| 66 | flags.accessToken |
| 67 | flags.refreshToken |
| 68 | |
| 69 | user : Auth.User.SignInStatus |
| 70 | user = |
| 71 | case maybeCredentials of |
| 72 | Just credentials -> |
| 73 | Auth.User.SignedIn credentials |
| 74 | |
| 75 | Nothing -> |
| 76 | Auth.User.NotSignedIn |
| 77 | |
| 78 | initModel : Model |
| 79 | initModel = |
| 80 | { user = user |
| 81 | , timeZone = Time.utc |
| 82 | } |
| 83 | in |
| 84 | ( initModel |
| 85 | , Effect.batch |
| 86 | [ Time.now |> Task.perform Shared.Msg.CheckTokenExpiration |> Effect.sendCmd |
| 87 | , Time.here |> Task.perform Shared.Msg.GotZone |> Effect.sendCmd |
| 88 | ] |
| 89 | ) |
| 90 | |
| 91 | |
| 92 | |
| 93 | -- UPDATE |
| 94 | |
| 95 | |
| 96 | type alias Msg = |
| 97 | Shared.Msg.Msg |
| 98 | |
| 99 | |
| 100 | update : Route () -> Msg -> Model -> ( Model, Effect Msg ) |
| 101 | update _ msg model = |
| 102 | case msg of |
| 103 | Shared.Msg.GotZone timeZone -> |
| 104 | ( { model | timeZone = timeZone }, Effect.none ) |
| 105 | |
| 106 | Shared.Msg.Logout -> |
| 107 | ( { model | user = Auth.User.NotSignedIn }, Effect.clearUser ) |
| 108 | |
| 109 | Shared.Msg.SignedIn credentials -> |
| 110 | ( { model | user = Auth.User.SignedIn credentials } |
| 111 | , Effect.batch |
| 112 | [ Effect.pushRoute |
| 113 | { path = Route.Path.Home_ |
| 114 | , query = Dict.empty |
| 115 | , hash = Nothing |
| 116 | } |
| 117 | , Effect.saveUser credentials.accessToken credentials.refreshToken |
| 118 | ] |
| 119 | ) |
| 120 | |
| 121 | Shared.Msg.CheckTokenExpiration now -> |
| 122 | case model.user of |
| 123 | Auth.User.SignedIn credentials -> |
| 124 | if JwtUtil.isExpired now credentials.accessToken then |
| 125 | ( model, Effect.refreshTokens ) |
| 126 | |
| 127 | else |
| 128 | ( model, Effect.none ) |
| 129 | |
| 130 | _ -> |
| 131 | ( model, Effect.none ) |
| 132 | |
| 133 | Shared.Msg.TriggerTokenRefresh -> |
| 134 | case model.user of |
| 135 | Auth.User.SignedIn credentials -> |
| 136 | ( { model | user = Auth.User.RefreshingTokens } |
| 137 | , Api.Auth.refreshToken |
| 138 | { onResponse = Shared.Msg.ApiRefreshTokensResponded |
| 139 | , refreshToken = credentials.refreshToken |
| 140 | } |
| 141 | ) |
| 142 | |
| 143 | _ -> |
| 144 | ( model, Effect.none ) |
| 145 | |
| 146 | Shared.Msg.ApiRefreshTokensResponded (Ok credentials) -> |
| 147 | ( { model | user = Auth.User.SignedIn credentials } |
| 148 | , Effect.saveUser credentials.accessToken credentials.refreshToken |
| 149 | ) |
| 150 | |
| 151 | Shared.Msg.ApiRefreshTokensResponded (Err _) -> |
| 152 | ( { model | user = Auth.User.NotSignedIn }, Effect.clearUser ) |
| 153 | |
| 154 | |
| 155 | |
| 156 | -- SUBSCRIPTIONS |
| 157 | |
| 158 | |
| 159 | subscriptions : Route () -> Model -> Sub Msg |
| 160 | subscriptions _ _ = |
| 161 | Time.every (30 * 1000) Shared.Msg.CheckTokenExpiration |