elm-pages 3.0.0-beta.40 → 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.
- package/README.md +1 -1
- package/codegen/elm-pages-codegen.cjs +251 -285
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
- package/generator/src/RouteBuilder.elm +19 -60
- package/generator/src/SharedTemplate.elm +5 -5
- package/generator/src/compatibility-key.js +2 -2
- package/package.json +2 -2
- package/src/ApiRoute.elm +3 -31
- package/src/BackendTask.elm +18 -24
- package/src/FormData.elm +21 -1
- package/src/Head/Seo.elm +4 -4
- package/src/Internal/Request.elm +84 -4
- package/src/Pages/ConcurrentSubmission.elm +127 -0
- package/src/Pages/Form.elm +151 -40
- package/src/Pages/FormData.elm +19 -0
- package/src/Pages/Internal/NotFoundReason.elm +4 -4
- package/src/Pages/Internal/Platform/Cli.elm +30 -17
- package/src/Pages/Internal/Platform/CompatibilityKey.elm +1 -1
- package/src/Pages/Internal/Platform.elm +39 -38
- package/src/Pages/Internal/ResponseSketch.elm +2 -2
- package/src/Pages/Manifest.elm +23 -7
- package/src/Pages/Navigation.elm +85 -0
- package/src/Pages/PageUrl.elm +3 -3
- package/src/Pages/ProgramConfig.elm +13 -11
- package/src/Pages/Script.elm +64 -7
- package/src/Pages/Url.elm +3 -3
- package/src/PagesMsg.elm +9 -3
- package/src/RenderRequest.elm +7 -7
- package/src/Scaffold/Form.elm +28 -5
- package/src/Scaffold/Route.elm +82 -53
- package/src/Server/Request.elm +446 -952
- package/src/Server/Session.elm +141 -91
- package/src/Server/SetCookie.elm +71 -31
- package/src/{Path.elm → UrlPath.elm} +21 -21
- package/src/Pages/Transition.elm +0 -79
package/src/Server/Session.elm
CHANGED
|
@@ -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
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
-> (
|
|
249
|
-
->
|
|
250
|
-
->
|
|
251
|
-
withSession config toRequest
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
-> (
|
|
269
|
-
->
|
|
270
|
-
->
|
|
271
|
-
withSessionResult config
|
|
272
|
-
|
|
273
|
-
(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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,20 +356,19 @@ encodeSessionUpdate :
|
|
|
305
356
|
, secrets : BackendTask error (List String)
|
|
306
357
|
, options : Maybe SetCookie.Options
|
|
307
358
|
}
|
|
308
|
-
-> (
|
|
309
|
-
-> c
|
|
359
|
+
-> (d -> BackendTask error ( Session, Response data errorPage ))
|
|
310
360
|
-> d
|
|
311
361
|
-> BackendTask error (Response data errorPage)
|
|
312
|
-
encodeSessionUpdate config toRequest
|
|
362
|
+
encodeSessionUpdate config toRequest sessionResult =
|
|
313
363
|
sessionResult
|
|
314
|
-
|> toRequest
|
|
364
|
+
|> toRequest
|
|
315
365
|
|> BackendTask.andThen
|
|
316
366
|
(\( sessionUpdate, response ) ->
|
|
317
367
|
BackendTask.map
|
|
318
368
|
(\encoded ->
|
|
319
369
|
response
|
|
320
370
|
|> Server.Response.withSetCookieHeader
|
|
321
|
-
(SetCookie.setCookie config.name encoded (config.options |> Maybe.withDefault SetCookie.
|
|
371
|
+
(SetCookie.setCookie config.name encoded (config.options |> Maybe.withDefault SetCookie.options))
|
|
322
372
|
)
|
|
323
373
|
(sign config.secrets
|
|
324
374
|
(encodeNonExpiringPairs sessionUpdate)
|
package/src/Server/SetCookie.elm
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module Server.SetCookie exposing
|
|
2
|
-
( SetCookie
|
|
3
|
-
,
|
|
4
|
-
,
|
|
5
|
-
, withImmediateExpiration, makeVisibleToJavaScript, nonSecure,
|
|
2
|
+
( SetCookie, setCookie
|
|
3
|
+
, Options, options
|
|
4
|
+
, SameSite(..), withSameSite
|
|
5
|
+
, withImmediateExpiration, makeVisibleToJavaScript, nonSecure, withDomain, withExpiration, withMaxAge, withPath, withoutPath
|
|
6
6
|
, toString
|
|
7
7
|
)
|
|
8
8
|
|
|
@@ -20,16 +20,29 @@ You can learn more about the basics of cookies in the Web Platform in these help
|
|
|
20
20
|
- <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
|
|
21
21
|
- <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>
|
|
22
22
|
|
|
23
|
-
@docs SetCookie
|
|
23
|
+
@docs SetCookie, setCookie
|
|
24
24
|
|
|
25
|
-
@docs SameSite
|
|
26
25
|
|
|
26
|
+
## Building Options
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Usually you'll want to start by creating default `Options` with `options` and then overriding defaults using the `with...` helpers.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
import Server.SetCookie as SetCookie
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
options : SetCookie.Options
|
|
33
|
+
options =
|
|
34
|
+
SetCookie.options
|
|
35
|
+
|> SetCookie.nonSecure
|
|
36
|
+
|> SetCookie.withMaxAge 123
|
|
37
|
+
|> SetCookie.makeVisibleToJavaScript
|
|
38
|
+
|> SetCookie.withoutPath
|
|
39
|
+
|> SetCookie.setCookie "id" "a3fWa"
|
|
40
|
+
|
|
41
|
+
@docs Options, options
|
|
42
|
+
|
|
43
|
+
@docs SameSite, withSameSite
|
|
44
|
+
|
|
45
|
+
@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, withDomain, withExpiration, withMaxAge, withPath, withoutPath
|
|
33
46
|
|
|
34
47
|
|
|
35
48
|
## Internal
|
|
@@ -51,7 +64,8 @@ type alias SetCookie =
|
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
|
|
54
|
-
{-|
|
|
67
|
+
{-| The set of possible configuration options. You can configure this record directly, or use the `with...` helpers.
|
|
68
|
+
-}
|
|
55
69
|
type alias Options =
|
|
56
70
|
{ expiration : Maybe Time.Posix
|
|
57
71
|
, visibleToJavaScript : Bool
|
|
@@ -63,7 +77,15 @@ type alias Options =
|
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
|
|
66
|
-
{-| -
|
|
80
|
+
{-| Possible values for [the cookie's same-site value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value).
|
|
81
|
+
|
|
82
|
+
The default option is [`Lax`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#lax) (Lax does not send
|
|
83
|
+
cookies in cross-origin requests so it is a good default for most cases, but [`Strict`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#strict)
|
|
84
|
+
is even more restrictive).
|
|
85
|
+
|
|
86
|
+
Override the default option using [`withSameSite`](#withSameSite).
|
|
87
|
+
|
|
88
|
+
-}
|
|
67
89
|
type SameSite
|
|
68
90
|
= Strict
|
|
69
91
|
| Lax
|
|
@@ -95,24 +117,24 @@ toString builder =
|
|
|
95
117
|
else
|
|
96
118
|
""
|
|
97
119
|
|
|
98
|
-
|
|
99
|
-
|
|
120
|
+
options_ : Options
|
|
121
|
+
options_ =
|
|
100
122
|
builder.options
|
|
101
123
|
|
|
102
124
|
httpOnly : Bool
|
|
103
125
|
httpOnly =
|
|
104
|
-
not
|
|
126
|
+
not options_.visibleToJavaScript
|
|
105
127
|
in
|
|
106
128
|
builder.name
|
|
107
129
|
++ "="
|
|
108
130
|
++ Url.percentEncode builder.value
|
|
109
|
-
++ option "Expires" (
|
|
110
|
-
++ option "Max-Age" (
|
|
111
|
-
++ option "Path"
|
|
112
|
-
++ option "Domain"
|
|
113
|
-
++ option "SameSite" (
|
|
131
|
+
++ option "Expires" (options_.expiration |> Maybe.map Utc.fromTime)
|
|
132
|
+
++ option "Max-Age" (options_.maxAge |> Maybe.map String.fromInt)
|
|
133
|
+
++ option "Path" options_.path
|
|
134
|
+
++ option "Domain" options_.domain
|
|
135
|
+
++ option "SameSite" (options_.sameSite |> Maybe.map sameSiteToString)
|
|
114
136
|
++ boolOption "HttpOnly" httpOnly
|
|
115
|
-
++ boolOption "Secure"
|
|
137
|
+
++ boolOption "Secure" options_.secure
|
|
116
138
|
|
|
117
139
|
|
|
118
140
|
sameSiteToString : SameSite -> String
|
|
@@ -128,18 +150,22 @@ sameSiteToString sameSite =
|
|
|
128
150
|
"None"
|
|
129
151
|
|
|
130
152
|
|
|
131
|
-
{-| -
|
|
153
|
+
{-| Create a `SetCookie` record with the given name, value, and [`Options`](Options]. To add a `Set-Cookie` header, you can
|
|
154
|
+
pass this value with [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader). Or for more low-level
|
|
155
|
+
uses you can stringify the value manually with [`toString`](#toString).
|
|
156
|
+
-}
|
|
132
157
|
setCookie : String -> String -> Options -> SetCookie
|
|
133
|
-
setCookie name value
|
|
158
|
+
setCookie name value options_ =
|
|
134
159
|
{ name = name
|
|
135
160
|
, value = value
|
|
136
|
-
, options =
|
|
161
|
+
, options = options_
|
|
137
162
|
}
|
|
138
163
|
|
|
139
164
|
|
|
140
|
-
{-|
|
|
141
|
-
|
|
142
|
-
|
|
165
|
+
{-| Initialize the default `SetCookie` `Options`. Can be configured directly through a record update, or with `withExpiration`, etc.
|
|
166
|
+
-}
|
|
167
|
+
options : Options
|
|
168
|
+
options =
|
|
143
169
|
{ expiration = Nothing
|
|
144
170
|
, visibleToJavaScript = False
|
|
145
171
|
, maxAge = Nothing
|
|
@@ -158,7 +184,9 @@ withExpiration time builder =
|
|
|
158
184
|
}
|
|
159
185
|
|
|
160
186
|
|
|
161
|
-
{-| -
|
|
187
|
+
{-| Sets [`Expires`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#expiresdate) to `Time.millisToPosix 0`,
|
|
188
|
+
which effectively tells the browser to delete the cookie immediately (by giving it an expiration date in the past).
|
|
189
|
+
-}
|
|
162
190
|
withImmediateExpiration : Options -> Options
|
|
163
191
|
withImmediateExpiration builder =
|
|
164
192
|
{ builder
|
|
@@ -184,7 +212,8 @@ makeVisibleToJavaScript builder =
|
|
|
184
212
|
}
|
|
185
213
|
|
|
186
214
|
|
|
187
|
-
{-| -
|
|
215
|
+
{-| Sets the `Set-Cookie`'s [`Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-agenumber).
|
|
216
|
+
-}
|
|
188
217
|
withMaxAge : Int -> Options -> Options
|
|
189
218
|
withMaxAge maxAge builder =
|
|
190
219
|
{ builder
|
|
@@ -192,7 +221,11 @@ withMaxAge maxAge builder =
|
|
|
192
221
|
}
|
|
193
222
|
|
|
194
223
|
|
|
195
|
-
{-| -
|
|
224
|
+
{-| Sets the `Set-Cookie`'s [`Path`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value).
|
|
225
|
+
|
|
226
|
+
The default value is `/`, which will match any sub-directories or the root directory. See also [\`withoutPath](#withoutPath)
|
|
227
|
+
|
|
228
|
+
-}
|
|
196
229
|
withPath : String -> Options -> Options
|
|
197
230
|
withPath path builder =
|
|
198
231
|
{ builder
|
|
@@ -200,7 +233,13 @@ withPath path builder =
|
|
|
200
233
|
}
|
|
201
234
|
|
|
202
235
|
|
|
203
|
-
{-|
|
|
236
|
+
{-|
|
|
237
|
+
|
|
238
|
+
> If the server omits the Path attribute, the user agent will use the "directory" of the request-uri's path component as the default value.
|
|
239
|
+
|
|
240
|
+
Source: <https://www.rfc-editor.org/rfc/rfc6265>. See <https://stackoverflow.com/a/43336097>.
|
|
241
|
+
|
|
242
|
+
-}
|
|
204
243
|
withoutPath : Options -> Options
|
|
205
244
|
withoutPath builder =
|
|
206
245
|
{ builder
|
|
@@ -208,7 +247,8 @@ withoutPath builder =
|
|
|
208
247
|
}
|
|
209
248
|
|
|
210
249
|
|
|
211
|
-
{-| -
|
|
250
|
+
{-| Sets the `Set-Cookie`'s [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value).
|
|
251
|
+
-}
|
|
212
252
|
withDomain : String -> Options -> Options
|
|
213
253
|
withDomain domain builder =
|
|
214
254
|
{ builder
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
module
|
|
2
|
-
(
|
|
1
|
+
module UrlPath exposing
|
|
2
|
+
( UrlPath, join, fromString
|
|
3
3
|
, toAbsolute, toRelative, toSegments
|
|
4
4
|
)
|
|
5
5
|
|
|
@@ -9,27 +9,27 @@ This helper lets you combine together path parts without worrying about having t
|
|
|
9
9
|
These two examples will result in the same URL, even though the first example has trailing and leading slashes, and the
|
|
10
10
|
second does not.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|>
|
|
12
|
+
UrlPath.join [ "/blog/", "/post-1/" ]
|
|
13
|
+
|> UrlPath.toAbsolute
|
|
14
14
|
--> "/blog/post-1"
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|>
|
|
16
|
+
UrlPath.join [ "blog", "post-1" ]
|
|
17
|
+
|> UrlPath.toAbsolute
|
|
18
18
|
--> "/blog/post-1"
|
|
19
19
|
|
|
20
20
|
We can also safely join Strings that include multiple path parts, a single path part per string, or a mix of the two:
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|>
|
|
22
|
+
UrlPath.join [ "/articles/archive/", "1977", "06", "10", "post-1" ]
|
|
23
|
+
|> UrlPath.toAbsolute
|
|
24
24
|
--> "/articles/archive/1977/06/10/post-1"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
## Creating
|
|
27
|
+
## Creating UrlPaths
|
|
28
28
|
|
|
29
|
-
@docs
|
|
29
|
+
@docs UrlPath, join, fromString
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
## Turning
|
|
32
|
+
## Turning UrlPaths to String
|
|
33
33
|
|
|
34
34
|
@docs toAbsolute, toRelative, toSegments
|
|
35
35
|
|
|
@@ -40,35 +40,35 @@ import Pages.Internal.String exposing (chopEnd, chopStart)
|
|
|
40
40
|
|
|
41
41
|
{-| The path portion of the URL, normalized to ensure that path segments are joined with `/`s in the right places (no doubled up or missing slashes).
|
|
42
42
|
-}
|
|
43
|
-
type alias
|
|
43
|
+
type alias UrlPath =
|
|
44
44
|
List String
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
{-| Turn a Path to a relative URL.
|
|
48
48
|
-}
|
|
49
|
-
join :
|
|
49
|
+
join : UrlPath -> UrlPath
|
|
50
50
|
join parts =
|
|
51
51
|
parts
|
|
52
52
|
|> List.filter (\segment -> segment /= "/")
|
|
53
53
|
|> List.map normalize
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
{-| Turn a
|
|
56
|
+
{-| Turn a UrlPath to a relative URL.
|
|
57
57
|
-}
|
|
58
|
-
toRelative :
|
|
58
|
+
toRelative : UrlPath -> String
|
|
59
59
|
toRelative parts =
|
|
60
60
|
join parts
|
|
61
61
|
|> String.join "/"
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
{-| Create a
|
|
64
|
+
{-| Create a UrlPath from a path String.
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|>
|
|
66
|
+
UrlPath.fromString "blog/post-1/"
|
|
67
|
+
|> UrlPath.toAbsolute
|
|
68
68
|
|> Expect.equal "/blog/post-1"
|
|
69
69
|
|
|
70
70
|
-}
|
|
71
|
-
fromString : String ->
|
|
71
|
+
fromString : String -> UrlPath
|
|
72
72
|
fromString path =
|
|
73
73
|
path
|
|
74
74
|
|> toSegments
|
|
@@ -80,9 +80,9 @@ toSegments path =
|
|
|
80
80
|
path |> String.split "/" |> List.filter ((/=) "")
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
{-| Turn a
|
|
83
|
+
{-| Turn a UrlPath to an absolute URL (with no trailing slash).
|
|
84
84
|
-}
|
|
85
|
-
toAbsolute :
|
|
85
|
+
toAbsolute : UrlPath -> String
|
|
86
86
|
toAbsolute path =
|
|
87
87
|
"/" ++ toRelative path
|
|
88
88
|
|