all repos

onasty @ 327757c9371a333d3436f0a204677742a04236cf

a one-time notes service

onasty/web/src/Shared.elm(view raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
module Shared exposing
    ( Flags, decoder
    , Model, Msg
    , init, update, subscriptions
    )

{-|

@docs Flags, decoder
@docs Model, Msg
@docs init, update, subscriptions

-}

import Api.Auth
import Data.Credentials exposing (Credentials)
import Dict
import Effect exposing (Effect)
import Json.Decode
import JwtUtil
import Route exposing (Route)
import Route.Path
import Shared.Model
import Shared.Msg
import Task
import Time



-- FLAGS


type alias Flags =
    { accessToken : Maybe String
    , refreshToken : Maybe String
    }


decoder : Json.Decode.Decoder Flags
decoder =
    Json.Decode.map2 Flags
        (Json.Decode.field "access_token" (Json.Decode.maybe Json.Decode.string))
        (Json.Decode.field "refresh_token" (Json.Decode.maybe Json.Decode.string))



-- INIT


type alias Model =
    Shared.Model.Model


init : Result Json.Decode.Error Flags -> Route () -> ( Model, Effect Msg )
init flagsResult _ =
    let
        flags : Flags
        flags =
            flagsResult |> Result.withDefault { accessToken = Nothing, refreshToken = Nothing }

        maybeCredentials : Maybe Credentials
        maybeCredentials =
            Maybe.map2
                (\access refresh -> { accessToken = access, refreshToken = refresh })
                flags.accessToken
                flags.refreshToken

        initModel : Model
        initModel =
            { credentials = maybeCredentials
            , timeZone = Time.utc
            , isRefreshingTokens = False
            }
    in
    ( initModel
    , Effect.batch
        [ Time.now |> Task.perform Shared.Msg.CheckTokenExpiration |> Effect.sendCmd
        , Time.here |> Task.perform Shared.Msg.GotZone |> Effect.sendCmd
        ]
    )



-- UPDATE


type alias Msg =
    Shared.Msg.Msg


update : Route () -> Msg -> Model -> ( Model, Effect Msg )
update _ msg model =
    case msg of
        Shared.Msg.GotZone timeZone ->
            ( { model | timeZone = timeZone }, Effect.none )

        Shared.Msg.Logout ->
            ( { model | credentials = Nothing }, Effect.clearUser )

        Shared.Msg.SignedIn credentials ->
            ( { model | credentials = Just credentials }
            , Effect.batch
                [ Effect.pushRoute
                    { path = Route.Path.Home_
                    , query = Dict.empty
                    , hash = Nothing
                    }
                , Effect.saveUser credentials.accessToken credentials.refreshToken
                ]
            )

        Shared.Msg.CheckTokenExpiration now ->
            case model.credentials of
                Just credentials ->
                    if JwtUtil.isExpired now credentials.accessToken then
                        ( model, Effect.refreshTokens )

                    else
                        ( model, Effect.none )

                Nothing ->
                    ( model, Effect.none )

        Shared.Msg.TriggerTokenRefresh ->
            case model.credentials of
                Just credentials ->
                    ( { model | isRefreshingTokens = True }
                    , Api.Auth.refreshToken
                        { onResponse = Shared.Msg.ApiRefreshTokensResponded
                        , refreshToken = credentials.refreshToken
                        }
                    )

                Nothing ->
                    ( model, Effect.none )

        Shared.Msg.ApiRefreshTokensResponded (Ok credentials) ->
            ( { model | isRefreshingTokens = False, credentials = Just credentials }
            , Effect.saveUser credentials.accessToken credentials.refreshToken
            )

        Shared.Msg.ApiRefreshTokensResponded (Err _) ->
            ( { model | isRefreshingTokens = False }, Effect.clearUser )



-- SUBSCRIPTIONS


subscriptions : Route () -> Model -> Sub Msg
subscriptions _ _ =
    Time.every (30 * 1000) Shared.Msg.CheckTokenExpiration