elm-pages 3.0.0-beta.41 → 3.0.0-beta.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,43 +4,48 @@ module Server.Session exposing
4
4
  , Session, empty, get, insert, remove, update, withFlash
5
5
  )
6
6
 
7
- {-| You can manage server state with HTTP cookies using this Server.Session API. Server-rendered pages define a `Server.Request.Parser`
8
- to choose which requests to respond to and how to extract structured data from the incoming request.
7
+ {-| You can manage server state with HTTP cookies using this Server.Session API. Server-rendered routes have a `Server.Request.Request`
8
+ argument that lets you inspect the incoming HTTP request, and return a response using the `Server.Response.Response` type.
9
9
 
10
+ This API provides a higher-level abstraction for extracting data from the HTTP request, and setting data in the HTTP response.
11
+ It manages the session through key-value data stored in cookies, and lets you [`insert`](#insert), [`update`](#update), and [`remove`](#remove)
12
+ values from the Session. It also provides an abstraction for flash session values through [`withFlash`](#withFlash).
10
13
 
11
- ## Using Sessions in a Request.Parser
14
+
15
+ ## Using Sessions
12
16
 
13
17
  Using these functions, you can store and read session data in cookies to maintain state between requests.
14
- For example, TODO:
15
-
16
- action : RouteParams -> Request.Parser (BackendTask (Response ActionData ErrorPage))
17
- action routeParams =
18
- MySession.withSession
19
- (Request.formDataWithServerValidation (form |> Form.initCombinedServer identity))
20
- (\nameResultData session ->
21
- nameResultData
22
- |> BackendTask.map
23
- (\nameResult ->
24
- case nameResult of
25
- Err errors ->
26
- ( session
27
- |> Result.withDefault Nothing
28
- |> Maybe.withDefault Session.empty
29
- , Response.render
30
- { errors = errors
31
- }
32
- )
33
18
 
34
- Ok ( _, name ) ->
35
- ( session
36
- |> Result.withDefault Nothing
37
- |> Maybe.withDefault Session.empty
38
- |> Session.insert "name" name
39
- |> Session.withFlash "message" ("Welcome " ++ name ++ "!")
40
- , Route.redirectTo Route.Greet
41
- )
42
- )
43
- )
19
+ import Server.Session as Session
20
+
21
+ secrets : BackendTask FatalError (List String)
22
+ secrets =
23
+ Env.expect "SESSION_SECRET"
24
+ |> BackendTask.allowFatal
25
+ |> BackendTask.map List.singleton
26
+
27
+ type alias Data =
28
+ { darkMode : Bool }
29
+
30
+ data : RouteParams -> Request -> BackendTask (Response Data ErrorPage)
31
+ data routeParams request =
32
+ request
33
+ |> Session.withSession
34
+ { name = "mysession"
35
+ , secrets = secrets
36
+ , options = Nothing
37
+ }
38
+ (\session ->
39
+ let
40
+ darkMode : Bool
41
+ darkMode =
42
+ (session |> Session.get "mode" |> Maybe.withDefault "light")
43
+ == "dark"
44
+ in
45
+ ( session
46
+ , { darkMode = darkMode }
47
+ )
48
+ )
44
49
 
45
50
  The elm-pages framework will manage signing these cookies using the `secrets : BackendTask (List String)` you pass in.
46
51
  That means that the values you set in your session will be directly visible to anyone who has access to the cookie
@@ -113,12 +118,17 @@ import BackendTask.Internal.Request
113
118
  import Dict exposing (Dict)
114
119
  import Json.Decode
115
120
  import Json.Encode
116
- import Server.Request
121
+ import Server.Request exposing (Request)
117
122
  import Server.Response exposing (Response)
118
123
  import Server.SetCookie as SetCookie
119
124
 
120
125
 
121
- {-| -}
126
+ {-| Represents a Session with key-value Strings.
127
+
128
+ Use with `withSession` to read in the `Session`, and encode any changes you make to the `Session` back through cookie storage
129
+ via the outgoing HTTP response.
130
+
131
+ -}
122
132
  type Session
123
133
  = Session (Dict String Value)
124
134
 
@@ -130,7 +140,12 @@ type Value
130
140
  | NewFlash String
131
141
 
132
142
 
133
- {-| -}
143
+ {-| Flash session values are values that are only available for the next request.
144
+
145
+ session
146
+ |> Session.withFlash "message" "Your payment was successful!"
147
+
148
+ -}
134
149
  withFlash : String -> String -> Session -> Session
135
150
  withFlash key value (Session session) =
136
151
  session
@@ -138,7 +153,12 @@ withFlash key value (Session session) =
138
153
  |> Session
139
154
 
140
155
 
141
- {-| -}
156
+ {-| Insert a value under the given key in the `Session`.
157
+
158
+ session
159
+ |> Session.insert "mode" "dark"
160
+
161
+ -}
142
162
  insert : String -> String -> Session -> Session
143
163
  insert key value (Session session) =
144
164
  session
@@ -146,7 +166,15 @@ insert key value (Session session) =
146
166
  |> Session
147
167
 
148
168
 
149
- {-| -}
169
+ {-| Retrieve a String value from the session for the given key (or `Nothing` if the key is not present).
170
+
171
+ (session
172
+ |> Session.get "mode"
173
+ |> Maybe.withDefault "light"
174
+ )
175
+ == "dark"
176
+
177
+ -}
150
178
  get : String -> Session -> Maybe String
151
179
  get key (Session session) =
152
180
  session
@@ -168,7 +196,25 @@ unwrap value =
168
196
  string
169
197
 
170
198
 
171
- {-| -}
199
+ {-| Update the `Session`, given a `Maybe String` of the current value for the given key, and returning a `Maybe String`.
200
+
201
+ If you return `Nothing`, the key-value pair will be removed from the `Session` (or left out if it didn't exist in the first place).
202
+
203
+ session
204
+ |> Session.update "mode"
205
+ (\mode ->
206
+ case mode of
207
+ Just "dark" ->
208
+ Just "light"
209
+
210
+ Just "light" ->
211
+ Just "dark"
212
+
213
+ Nothing ->
214
+ Just "dark"
215
+ )
216
+
217
+ -}
172
218
  update : String -> (Maybe String -> Maybe String) -> Session -> Session
173
219
  update key updateFn (Session session) =
174
220
  session
@@ -192,7 +238,8 @@ update key updateFn (Session session) =
192
238
  |> Session
193
239
 
194
240
 
195
- {-| -}
241
+ {-| Remove a key from the `Session`.
242
+ -}
196
243
  remove : String -> Session -> Session
197
244
  remove key (Session session) =
198
245
  session
@@ -200,13 +247,15 @@ remove key (Session session) =
200
247
  |> Session
201
248
 
202
249
 
203
- {-| -}
250
+ {-| An empty `Session` with no key-value pairs.
251
+ -}
204
252
  empty : Session
205
253
  empty =
206
254
  Session Dict.empty
207
255
 
208
256
 
209
- {-| -}
257
+ {-| [`withSessionResult`](#withSessionResult) will return a `Result` with this type if it can't load a session.
258
+ -}
210
259
  type NotLoadedReason
211
260
  = NoSessionCookie
212
261
  | InvalidSessionCookie
@@ -239,65 +288,67 @@ flashPrefix =
239
288
  "__flash__"
240
289
 
241
290
 
242
- {-| -}
291
+ {-| The main function for using sessions. If you need more fine-grained control over cases where a session can't be loaded, see
292
+ [`withSessionResult`](#withSessionResult).
293
+ -}
243
294
  withSession :
244
295
  { name : String
245
296
  , secrets : BackendTask error (List String)
246
297
  , options : Maybe SetCookie.Options
247
298
  }
248
- -> (request -> Session -> BackendTask error ( Session, Response data errorPage ))
249
- -> Server.Request.Parser request
250
- -> Server.Request.Parser (BackendTask error (Response data errorPage))
251
- withSession config toRequest userRequest =
252
- withSessionResult config
253
- (\request session ->
254
- toRequest request
255
- (session
299
+ -> (Session -> BackendTask error ( Session, Response data errorPage ))
300
+ -> Request
301
+ -> BackendTask error (Response data errorPage)
302
+ withSession config toRequest request_ =
303
+ request_
304
+ |> withSessionResult config
305
+ (\session ->
306
+ session
256
307
  |> Result.withDefault empty
257
- )
258
- )
259
- userRequest
308
+ |> toRequest
309
+ )
260
310
 
261
311
 
262
- {-| -}
312
+ {-| Same as `withSession`, but gives you an `Err` with the reason why the Session couldn't be loaded instead of
313
+ using `Session.empty` as a default in the cases where there is an error loading the session.
314
+
315
+ A session won't load if there is no session, or if it cannot be unsigned with your secrets. This could be because the cookie was tampered with
316
+ or otherwise corrupted, or because the cookie was signed with a secret that is no longer in the rotation.
317
+
318
+ -}
263
319
  withSessionResult :
264
320
  { name : String
265
321
  , secrets : BackendTask error (List String)
266
322
  , options : Maybe SetCookie.Options
267
323
  }
268
- -> (request -> Result NotLoadedReason Session -> BackendTask error ( Session, Response data errorPage ))
269
- -> Server.Request.Parser request
270
- -> Server.Request.Parser (BackendTask error (Response data errorPage))
271
- withSessionResult config toRequest userRequest =
272
- Server.Request.map2
273
- (\maybeSessionCookie userRequestData ->
274
- let
275
- unsigned : BackendTask error (Result NotLoadedReason Session)
276
- unsigned =
277
- case maybeSessionCookie of
278
- Just sessionCookie ->
279
- sessionCookie
280
- |> unsignCookie config
281
- |> BackendTask.map
282
- (\unsignResult ->
283
- case unsignResult of
284
- Ok decoded ->
285
- Ok decoded
286
-
287
- Err () ->
288
- Err InvalidSessionCookie
289
- )
324
+ -> (Result NotLoadedReason Session -> BackendTask error ( Session, Response data errorPage ))
325
+ -> Request
326
+ -> BackendTask error (Response data errorPage)
327
+ withSessionResult config toTask request =
328
+ let
329
+ unsigned : BackendTask error (Result NotLoadedReason Session)
330
+ unsigned =
331
+ case Server.Request.cookie config.name request of
332
+ Just sessionCookie ->
333
+ sessionCookie
334
+ |> unsignCookie config
335
+ |> BackendTask.map
336
+ (\unsignResult ->
337
+ case unsignResult of
338
+ Ok decoded ->
339
+ Ok decoded
340
+
341
+ Err () ->
342
+ Err InvalidSessionCookie
343
+ )
290
344
 
291
- Nothing ->
292
- Err NoSessionCookie
293
- |> BackendTask.succeed
294
- in
295
- unsigned
296
- |> BackendTask.andThen
297
- (encodeSessionUpdate config toRequest userRequestData)
298
- )
299
- (Server.Request.cookie config.name)
300
- userRequest
345
+ Nothing ->
346
+ Err NoSessionCookie
347
+ |> BackendTask.succeed
348
+ in
349
+ unsigned
350
+ |> BackendTask.andThen
351
+ (encodeSessionUpdate config toTask)
301
352
 
302
353
 
303
354
  encodeSessionUpdate :
@@ -305,13 +356,12 @@ encodeSessionUpdate :
305
356
  , secrets : BackendTask error (List String)
306
357
  , options : Maybe SetCookie.Options
307
358
  }
308
- -> (c -> d -> BackendTask error ( Session, Response data errorPage ))
309
- -> c
359
+ -> (d -> BackendTask error ( Session, Response data errorPage ))
310
360
  -> d
311
361
  -> BackendTask error (Response data errorPage)
312
- encodeSessionUpdate config toRequest userRequestData sessionResult =
362
+ encodeSessionUpdate config toRequest sessionResult =
313
363
  sessionResult
314
- |> toRequest userRequestData
364
+ |> toRequest
315
365
  |> BackendTask.andThen
316
366
  (\( sessionUpdate, response ) ->
317
367
  BackendTask.map