elm-pages 3.0.0-beta.2 → 3.0.0-beta.20

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.
Files changed (109) hide show
  1. package/README.md +10 -1
  2. package/codegen/{elm-pages-codegen.js → elm-pages-codegen.cjs} +2420 -1592
  3. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
  4. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
  5. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
  6. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  7. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  8. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  9. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -1
  10. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1326 -121
  11. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +15215 -13007
  12. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
  13. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
  14. package/generator/dead-code-review/elm.json +8 -6
  15. package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +189 -17
  16. package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +255 -21
  17. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  18. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  19. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  20. package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -1
  21. package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1326 -121
  22. package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +14620 -12636
  23. package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
  24. package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
  25. package/generator/review/elm.json +8 -8
  26. package/generator/src/RouteBuilder.elm +66 -52
  27. package/generator/src/SharedTemplate.elm +3 -2
  28. package/generator/src/SiteConfig.elm +3 -2
  29. package/generator/src/basepath-middleware.js +3 -3
  30. package/generator/src/build.js +122 -86
  31. package/generator/src/cli.js +247 -51
  32. package/generator/src/codegen.js +29 -27
  33. package/generator/src/compatibility-key.js +1 -0
  34. package/generator/src/compile-elm.js +20 -22
  35. package/generator/src/config.js +39 -0
  36. package/generator/src/copy-dir.js +2 -2
  37. package/generator/src/dev-server.js +119 -110
  38. package/generator/src/dir-helpers.js +9 -26
  39. package/generator/src/elm-codegen.js +5 -4
  40. package/generator/src/elm-file-constants.js +2 -3
  41. package/generator/src/error-formatter.js +12 -11
  42. package/generator/src/file-helpers.js +3 -4
  43. package/generator/src/generate-template-module-connector.js +14 -21
  44. package/generator/src/init.js +8 -7
  45. package/generator/src/pre-render-html.js +41 -28
  46. package/generator/src/render-test.js +109 -0
  47. package/generator/src/render-worker.js +26 -28
  48. package/generator/src/render.js +322 -142
  49. package/generator/src/request-cache.js +200 -162
  50. package/generator/src/rewrite-client-elm-json.js +5 -5
  51. package/generator/src/rewrite-elm-json.js +7 -7
  52. package/generator/src/route-codegen-helpers.js +16 -31
  53. package/generator/src/seo-renderer.js +12 -7
  54. package/generator/src/vite-utils.js +77 -0
  55. package/generator/static-code/hmr.js +16 -2
  56. package/generator/template/app/Api.elm +3 -3
  57. package/generator/template/app/Route/Index.elm +3 -3
  58. package/generator/template/app/Shared.elm +3 -3
  59. package/generator/template/app/Site.elm +9 -4
  60. package/package.json +21 -21
  61. package/src/ApiRoute.elm +199 -61
  62. package/src/BackendTask/Custom.elm +214 -0
  63. package/src/BackendTask/Env.elm +90 -0
  64. package/src/{DataSource → BackendTask}/File.elm +128 -43
  65. package/src/{DataSource → BackendTask}/Glob.elm +136 -125
  66. package/src/BackendTask/Http.elm +673 -0
  67. package/src/{DataSource → BackendTask}/Internal/Glob.elm +1 -1
  68. package/src/BackendTask/Internal/Request.elm +28 -0
  69. package/src/BackendTask/Random.elm +79 -0
  70. package/src/BackendTask/Time.elm +47 -0
  71. package/src/BackendTask.elm +537 -0
  72. package/src/FatalError.elm +89 -0
  73. package/src/Form/Field.elm +1 -1
  74. package/src/Form.elm +72 -92
  75. package/src/Head/Seo.elm +4 -4
  76. package/src/Head.elm +237 -7
  77. package/src/HtmlPrinter.elm +7 -3
  78. package/src/Internal/ApiRoute.elm +7 -5
  79. package/src/PageServerResponse.elm +6 -1
  80. package/src/Pages/Generate.elm +775 -132
  81. package/src/Pages/GeneratorProgramConfig.elm +15 -0
  82. package/src/Pages/Internal/FatalError.elm +5 -0
  83. package/src/Pages/Internal/Form.elm +21 -1
  84. package/src/Pages/Internal/Platform/Cli.elm +479 -747
  85. package/src/Pages/Internal/Platform/Cli.elm.bak +1276 -0
  86. package/src/Pages/Internal/Platform/CompatibilityKey.elm +6 -0
  87. package/src/Pages/Internal/Platform/Effect.elm +1 -2
  88. package/src/Pages/Internal/Platform/GeneratorApplication.elm +373 -0
  89. package/src/Pages/Internal/Platform/StaticResponses.elm +73 -270
  90. package/src/Pages/Internal/Platform/ToJsPayload.elm +4 -7
  91. package/src/Pages/Internal/Platform.elm +54 -53
  92. package/src/Pages/Internal/Script.elm +17 -0
  93. package/src/Pages/Internal/StaticHttpBody.elm +35 -1
  94. package/src/Pages/Manifest.elm +29 -4
  95. package/src/Pages/ProgramConfig.elm +12 -8
  96. package/src/Pages/Script.elm +109 -0
  97. package/src/Pages/SiteConfig.elm +3 -2
  98. package/src/Pages/StaticHttp/Request.elm +2 -2
  99. package/src/Pages/StaticHttpRequest.elm +23 -98
  100. package/src/RequestsAndPending.elm +8 -19
  101. package/src/Result/Extra.elm +21 -0
  102. package/src/Server/Request.elm +43 -34
  103. package/src/Server/Session.elm +166 -100
  104. package/src/Server/SetCookie.elm +89 -31
  105. package/src/DataSource/Env.elm +0 -38
  106. package/src/DataSource/Http.elm +0 -446
  107. package/src/DataSource/Internal/Request.elm +0 -20
  108. package/src/DataSource/Port.elm +0 -90
  109. package/src/DataSource.elm +0 -538
@@ -1,19 +1,119 @@
1
- module Server.Session exposing (Decoder, NotLoadedReason(..), Session(..), Value(..), clearFlashCookies, empty, expectSession, flashPrefix, get, insert, remove, setValues, succeed, unwrap, update, withFlash, withSession)
1
+ module Server.Session exposing
2
+ ( withSession
3
+ , NotLoadedReason(..)
4
+ , Session, empty, get, insert, remove, update, withFlash
5
+ )
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.
9
+
10
+
11
+ ## Using Sessions in a Request.Parser
12
+
13
+ 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
+
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
+ )
44
+
45
+ The elm-pages framework will manage signing these cookies using the `secrets : BackendTask (List String)` you pass in.
46
+ That means that the values you set in your session will be directly visible to anyone who has access to the cookie
47
+ (so don't directly store sensitive data in your session). Since the session cookie is signed using the secret you provide,
48
+ the cookie will be invalidated if it is tampered with because it won't match when elm-pages verifies that it has been
49
+ signed with your secrets. Of course you need to provide secure secrets and treat your secrets with care.
50
+
51
+
52
+ ### Rotating Secrets
53
+
54
+ The first String in `secrets : BackendTask (List String)` will be used to sign sessions, while the remaining String's will
55
+ still be used to attempt to "unsign" the cookies. So if you have a single secret:
56
+
57
+ Session.withSession
58
+ { name = "mysession"
59
+ , secrets =
60
+ BackendTask.map List.singleton
61
+ (Env.expect "SESSION_SECRET2022-09-01")
62
+ , options = cookieOptions
63
+ }
64
+
65
+ Then you add a second secret
2
66
 
3
- {-|
67
+ Session.withSession
68
+ { name = "mysession"
69
+ , secrets =
70
+ BackendTask.map2
71
+ (\newSecret oldSecret -> [ newSecret, oldSecret ])
72
+ (Env.expect "SESSION_SECRET2022-12-01")
73
+ (Env.expect "SESSION_SECRET2022-09-01")
74
+ , options = cookieOptions
75
+ }
4
76
 
5
- @docs Decoder, NotLoadedReason, Session, Value, clearFlashCookies, empty, expectSession, flashPrefix, get, insert, remove, setValues, succeed, unwrap, update, withFlash, withSession
77
+ The new secret (`2022-12-01`) will be used to sign all requests. This API always re-signs using the newest secret in the list
78
+ whenever a new request comes in (even if the Session key-value pairs are unchanged), so these cookies get "refreshed" with the latest
79
+ signing secret when a new request comes in.
80
+
81
+ However, incoming requests with a cookie signed using the old secret (`2022-09-01`) will still successfully be unsigned
82
+ because they are still in the rotation (and then subsequently "refreshed" and signed using the new secret).
83
+
84
+ This allows you to rotate your session secrets (for security purposes). When a secret goes out of the rotation,
85
+ it will invalidate all cookies signed with that. For example, if we remove our old secret from the rotation:
86
+
87
+ Session.withSession
88
+ { name = "mysession"
89
+ , secrets =
90
+ BackendTask.map List.singleton
91
+ (Env.expect "SESSION_SECRET2022-12-01")
92
+ , options = cookieOptions
93
+ }
94
+
95
+ And then a user makes a request but had a session signed with our old secret (`2022-09-01`), the session will be invalid
96
+ (so `withSession` would parse the session for that request as `Nothing`). It's standard for cookies to have an expiration date,
97
+ so there's nothing wrong with an old session expiring (and the browser will eventually delete old cookies), just be aware of that when rotating secrets.
98
+
99
+ @docs withSession
100
+
101
+ @docs NotLoadedReason
102
+
103
+
104
+ ## Creating and Updating Sessions
105
+
106
+ @docs Session, empty, get, insert, remove, update, withFlash
6
107
 
7
108
  -}
8
109
 
9
- import DataSource exposing (DataSource)
10
- import DataSource.Http
11
- import DataSource.Internal.Request
110
+ import BackendTask exposing (BackendTask)
111
+ import BackendTask.Http
112
+ import BackendTask.Internal.Request
12
113
  import Dict exposing (Dict)
13
- import Dict.Extra
14
114
  import Json.Decode
15
115
  import Json.Encode
16
- import Server.Request as Request exposing (Parser)
116
+ import Server.Request
17
117
  import Server.Response exposing (Response)
18
118
  import Server.SetCookie as SetCookie
19
119
 
@@ -23,11 +123,6 @@ type Session
23
123
  = Session (Dict String Value)
24
124
 
25
125
 
26
- {-| -}
27
- type alias Decoder decoded =
28
- Json.Decode.Decoder decoded
29
-
30
-
31
126
  {-| -}
32
127
  type Value
33
128
  = Persistent String
@@ -113,20 +208,13 @@ empty =
113
208
 
114
209
  {-| -}
115
210
  type NotLoadedReason
116
- = NoCookies
117
- | MissingHeaders
118
-
119
-
120
- {-| -}
121
- succeed : constructor -> Decoder constructor
122
- succeed constructor =
123
- constructor
124
- |> Json.Decode.succeed
211
+ = NoSessionCookie
212
+ | InvalidSessionCookie
125
213
 
126
214
 
127
215
  {-| -}
128
- setValues : Session -> Json.Encode.Value
129
- setValues (Session session) =
216
+ encodeNonExpiringPairs : Session -> Json.Encode.Value
217
+ encodeNonExpiringPairs (Session session) =
130
218
  session
131
219
  |> Dict.toList
132
220
  |> List.filterMap
@@ -151,100 +239,78 @@ flashPrefix =
151
239
  "__flash__"
152
240
 
153
241
 
154
- {-| -}
155
- clearFlashCookies : Dict String String -> Dict String String
156
- clearFlashCookies dict =
157
- Dict.Extra.removeWhen
158
- (\key _ ->
159
- key |> String.startsWith flashPrefix
160
- )
161
- dict
162
-
163
-
164
- {-| -}
165
- expectSession :
166
- { name : String
167
- , secrets : DataSource (List String)
168
- , sameSite : String
169
- }
170
- -> Parser request
171
- -> (request -> Result () Session -> DataSource ( Session, Response data errorPage ))
172
- -> Parser (DataSource (Response data errorPage))
173
- expectSession config userRequest toRequest =
174
- Request.map2
175
- (\sessionCookie userRequestData ->
176
- sessionCookie
177
- |> decryptCookie config
178
- |> DataSource.andThen
179
- (encodeSessionUpdate config toRequest userRequestData)
180
- )
181
- (Request.expectCookie config.name)
182
- userRequest
183
-
184
-
185
242
  {-| -}
186
243
  withSession :
187
244
  { name : String
188
- , secrets : DataSource (List String)
189
- , sameSite : String
245
+ , secrets : BackendTask error (List String)
246
+ , options : SetCookie.Options
190
247
  }
191
- -> Parser request
192
- -> (request -> Result () (Maybe Session) -> DataSource ( Session, Response data errorPage ))
193
- -> Parser (DataSource (Response data errorPage))
194
- withSession config userRequest toRequest =
195
- Request.map2
248
+ -> (request -> Result NotLoadedReason 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
+ Server.Request.map2
196
253
  (\maybeSessionCookie userRequestData ->
197
254
  let
198
- decrypted : DataSource (Result () (Maybe Session))
199
- decrypted =
255
+ unsigned : BackendTask error (Result NotLoadedReason Session)
256
+ unsigned =
200
257
  case maybeSessionCookie of
201
258
  Just sessionCookie ->
202
259
  sessionCookie
203
- |> decryptCookie config
204
- |> DataSource.map (Result.map Just)
260
+ |> unsignCookie config
261
+ |> BackendTask.map
262
+ (\unsignResult ->
263
+ case unsignResult of
264
+ Ok decoded ->
265
+ Ok decoded
266
+
267
+ Err () ->
268
+ Err InvalidSessionCookie
269
+ )
205
270
 
206
271
  Nothing ->
207
- Ok Nothing
208
- |> DataSource.succeed
272
+ Err NoSessionCookie
273
+ |> BackendTask.succeed
209
274
  in
210
- decrypted
211
- |> DataSource.andThen
275
+ unsigned
276
+ |> BackendTask.andThen
212
277
  (encodeSessionUpdate config toRequest userRequestData)
213
278
  )
214
- (Request.cookie config.name)
279
+ (Server.Request.cookie config.name)
215
280
  userRequest
216
281
 
217
282
 
218
- encodeSessionUpdate : { a | name : String, secrets : DataSource (List String) } -> (c -> d -> DataSource ( Session, Response data errorPage )) -> c -> d -> DataSource (Response data errorPage)
283
+ encodeSessionUpdate :
284
+ { name : String
285
+ , secrets : BackendTask error (List String)
286
+ , options : SetCookie.Options
287
+ }
288
+ -> (c -> d -> BackendTask error ( Session, Response data errorPage ))
289
+ -> c
290
+ -> d
291
+ -> BackendTask error (Response data errorPage)
219
292
  encodeSessionUpdate config toRequest userRequestData sessionResult =
220
293
  sessionResult
221
294
  |> toRequest userRequestData
222
- |> DataSource.andThen
295
+ |> BackendTask.andThen
223
296
  (\( sessionUpdate, response ) ->
224
- DataSource.map
297
+ BackendTask.map
225
298
  (\encoded ->
226
299
  response
227
300
  |> Server.Response.withSetCookieHeader
228
- (SetCookie.setCookie config.name encoded
229
- |> SetCookie.httpOnly
230
- |> SetCookie.withPath "/"
231
- -- TODO set expiration time
232
- -- TODO do I need to encrypt the session expiration as part of it
233
- -- TODO should I update the expiration time every time?
234
- --|> SetCookie.withExpiration (Time.millisToPosix 100000000000)
235
- )
301
+ (SetCookie.setCookie config.name encoded config.options)
236
302
  )
237
- (encrypt config.secrets
238
- (setValues sessionUpdate)
303
+ (sign config.secrets
304
+ (encodeNonExpiringPairs sessionUpdate)
239
305
  )
240
306
  )
241
307
 
242
308
 
243
- decryptCookie : { a | secrets : DataSource (List String) } -> String -> DataSource (Result () Session)
244
- decryptCookie config sessionCookie =
309
+ unsignCookie : { a | secrets : BackendTask error (List String) } -> String -> BackendTask error (Result () Session)
310
+ unsignCookie config sessionCookie =
245
311
  sessionCookie
246
- |> decrypt config.secrets (Json.Decode.dict Json.Decode.string)
247
- |> DataSource.map
312
+ |> unsign config.secrets (Json.Decode.dict Json.Decode.string)
313
+ |> BackendTask.map
248
314
  (Result.map
249
315
  (\dict ->
250
316
  dict
@@ -265,15 +331,15 @@ decryptCookie config sessionCookie =
265
331
  )
266
332
 
267
333
 
268
- encrypt : DataSource (List String) -> Json.Encode.Value -> DataSource String
269
- encrypt getSecrets input =
334
+ sign : BackendTask error (List String) -> Json.Encode.Value -> BackendTask error String
335
+ sign getSecrets input =
270
336
  getSecrets
271
- |> DataSource.andThen
337
+ |> BackendTask.andThen
272
338
  (\secrets ->
273
- DataSource.Internal.Request.request
339
+ BackendTask.Internal.Request.request
274
340
  { name = "encrypt"
275
341
  , body =
276
- DataSource.Http.jsonBody
342
+ BackendTask.Http.jsonBody
277
343
  (Json.Encode.object
278
344
  [ ( "values", input )
279
345
  , ( "secret"
@@ -287,21 +353,21 @@ encrypt getSecrets input =
287
353
  ]
288
354
  )
289
355
  , expect =
290
- DataSource.Http.expectJson
356
+ BackendTask.Http.expectJson
291
357
  Json.Decode.string
292
358
  }
293
359
  )
294
360
 
295
361
 
296
- decrypt : DataSource (List String) -> Json.Decode.Decoder a -> String -> DataSource (Result () a)
297
- decrypt getSecrets decoder input =
362
+ unsign : BackendTask error (List String) -> Json.Decode.Decoder a -> String -> BackendTask error (Result () a)
363
+ unsign getSecrets decoder input =
298
364
  getSecrets
299
- |> DataSource.andThen
365
+ |> BackendTask.andThen
300
366
  (\secrets ->
301
- DataSource.Internal.Request.request
367
+ BackendTask.Internal.Request.request
302
368
  { name = "decrypt"
303
369
  , body =
304
- DataSource.Http.jsonBody
370
+ BackendTask.Http.jsonBody
305
371
  (Json.Encode.object
306
372
  [ ( "input", Json.Encode.string input )
307
373
  , ( "secrets", Json.Encode.list Json.Encode.string secrets )
@@ -311,6 +377,6 @@ decrypt getSecrets decoder input =
311
377
  decoder
312
378
  |> Json.Decode.nullable
313
379
  |> Json.Decode.map (Result.fromMaybe ())
314
- |> DataSource.Http.expectJson
380
+ |> BackendTask.Http.expectJson
315
381
  }
316
382
  )
@@ -1,15 +1,38 @@
1
1
  module Server.SetCookie exposing
2
- ( SetCookie, SameSite(..)
3
- , withImmediateExpiration, httpOnly, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
2
+ ( SetCookie
3
+ , SameSite(..)
4
+ , Options, initOptions
5
+ , withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
4
6
  , toString
5
7
  )
6
8
 
7
- {-| <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
9
+ {-| Server-rendered pages in your `elm-pages` can set cookies. `elm-pages` provides two high-level ways to work with cookies:
8
10
 
9
- <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>
11
+ - [`Server.Session.withSession`](Server-Session#withSession)
12
+ - [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader)
10
13
 
11
- @docs SetCookie, SameSite
12
- @docs withImmediateExpiration, httpOnly, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
14
+ [`Server.Session.withSession`](Server-Session#withSession) provides a high-level way to manage key-value pairs of data using cookie storage,
15
+ whereas `Server.Response.withSetCookieHeader` gives a more low-level tool for setting cookies. It's often best to use the
16
+ most high-level tool that will fit your use case.
17
+
18
+ You can learn more about the basics of cookies in the Web Platform in these helpful MDN documentation pages:
19
+
20
+ - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
21
+ - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>
22
+
23
+ @docs SetCookie
24
+
25
+ @docs SameSite
26
+
27
+
28
+ ## Options
29
+
30
+ @docs Options, initOptions
31
+
32
+ @docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
33
+
34
+
35
+ ## Internal
13
36
 
14
37
  @docs toString
15
38
 
@@ -24,8 +47,14 @@ import Utc
24
47
  type alias SetCookie =
25
48
  { name : String
26
49
  , value : String
27
- , expiration : Maybe Time.Posix
28
- , httpOnly : Bool
50
+ , options : Options
51
+ }
52
+
53
+
54
+ {-| -}
55
+ type alias Options =
56
+ { expiration : Maybe Time.Posix
57
+ , visibleToJavaScript : Bool
29
58
  , maxAge : Maybe Int
30
59
  , path : Maybe String
31
60
  , domain : Maybe String
@@ -41,7 +70,11 @@ type SameSite
41
70
  | None
42
71
 
43
72
 
44
- {-| -}
73
+ {-| Usually you'll want to use [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader) instead.
74
+
75
+ This is a low-level helper that's there in case you want it but most users will never need this.
76
+
77
+ -}
45
78
  toString : SetCookie -> String
46
79
  toString builder =
47
80
  let
@@ -61,17 +94,25 @@ toString builder =
61
94
 
62
95
  else
63
96
  ""
97
+
98
+ options : Options
99
+ options =
100
+ builder.options
101
+
102
+ httpOnly : Bool
103
+ httpOnly =
104
+ not options.visibleToJavaScript
64
105
  in
65
106
  builder.name
66
107
  ++ "="
67
108
  ++ Url.percentEncode builder.value
68
- ++ option "Expires" (builder.expiration |> Maybe.map Utc.fromTime)
69
- ++ option "Max-Age" (builder.maxAge |> Maybe.map String.fromInt)
70
- ++ option "Path" builder.path
71
- ++ option "Domain" builder.domain
72
- ++ option "SameSite" (builder.sameSite |> Maybe.map sameSiteToString)
73
- ++ boolOption "HttpOnly" builder.httpOnly
74
- ++ boolOption "Secure" builder.secure
109
+ ++ option "Expires" (options.expiration |> Maybe.map Utc.fromTime)
110
+ ++ option "Max-Age" (options.maxAge |> Maybe.map String.fromInt)
111
+ ++ option "Path" options.path
112
+ ++ option "Domain" options.domain
113
+ ++ option "SameSite" (options.sameSite |> Maybe.map sameSiteToString)
114
+ ++ boolOption "HttpOnly" httpOnly
115
+ ++ boolOption "Secure" options.secure
75
116
 
76
117
 
77
118
  sameSiteToString : SameSite -> String
@@ -88,12 +129,19 @@ sameSiteToString sameSite =
88
129
 
89
130
 
90
131
  {-| -}
91
- setCookie : String -> String -> SetCookie
92
- setCookie name value =
132
+ setCookie : String -> String -> Options -> SetCookie
133
+ setCookie name value options =
93
134
  { name = name
94
135
  , value = value
95
- , expiration = Nothing
96
- , httpOnly = False
136
+ , options = options
137
+ }
138
+
139
+
140
+ {-| -}
141
+ initOptions : Options
142
+ initOptions =
143
+ { expiration = Nothing
144
+ , visibleToJavaScript = False
97
145
  , maxAge = Nothing
98
146
  , path = Nothing
99
147
  , domain = Nothing
@@ -103,7 +151,7 @@ setCookie name value =
103
151
 
104
152
 
105
153
  {-| -}
106
- withExpiration : Time.Posix -> SetCookie -> SetCookie
154
+ withExpiration : Time.Posix -> Options -> Options
107
155
  withExpiration time builder =
108
156
  { builder
109
157
  | expiration = Just time
@@ -111,23 +159,33 @@ withExpiration time builder =
111
159
 
112
160
 
113
161
  {-| -}
114
- withImmediateExpiration : SetCookie -> SetCookie
162
+ withImmediateExpiration : Options -> Options
115
163
  withImmediateExpiration builder =
116
164
  { builder
117
165
  | expiration = Just (Time.millisToPosix 0)
118
166
  }
119
167
 
120
168
 
121
- {-| -}
122
- httpOnly : SetCookie -> SetCookie
123
- httpOnly builder =
169
+ {-| The default option in this API is for HttpOnly cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly>.
170
+
171
+ Cookies can be exposed so you can read them from JavaScript using `Document.cookie`. When this is intended and understood
172
+ then there's nothing unsafe about that (for example, if you are setting a `darkMode` cookie and what to access that
173
+ dynamically). In this API you opt into exposing a cookie you set to JavaScript to ensure cookies aren't exposed to JS unintentionally.
174
+
175
+ In general if you can accomplish your goal using HttpOnly cookies (i.e. not using `makeVisibleToJavaScript`) then
176
+ it's a good practice. With server-rendered `elm-pages` applications you can often manage your session state by pulling
177
+ in session data from cookies in a `BackendTask` (which is resolved server-side before it ever reaches the browser).
178
+
179
+ -}
180
+ makeVisibleToJavaScript : Options -> Options
181
+ makeVisibleToJavaScript builder =
124
182
  { builder
125
- | httpOnly = True
183
+ | visibleToJavaScript = True
126
184
  }
127
185
 
128
186
 
129
187
  {-| -}
130
- withMaxAge : Int -> SetCookie -> SetCookie
188
+ withMaxAge : Int -> Options -> Options
131
189
  withMaxAge maxAge builder =
132
190
  { builder
133
191
  | maxAge = Just maxAge
@@ -135,7 +193,7 @@ withMaxAge maxAge builder =
135
193
 
136
194
 
137
195
  {-| -}
138
- withPath : String -> SetCookie -> SetCookie
196
+ withPath : String -> Options -> Options
139
197
  withPath path builder =
140
198
  { builder
141
199
  | path = Just path
@@ -143,7 +201,7 @@ withPath path builder =
143
201
 
144
202
 
145
203
  {-| -}
146
- withDomain : String -> SetCookie -> SetCookie
204
+ withDomain : String -> Options -> Options
147
205
  withDomain domain builder =
148
206
  { builder
149
207
  | domain = Just domain
@@ -153,7 +211,7 @@ withDomain domain builder =
153
211
  {-| Secure (only sent over https, or localhost on http) is the default. This overrides that and
154
212
  removes the `Secure` attribute from the cookie.
155
213
  -}
156
- nonSecure : SetCookie -> SetCookie
214
+ nonSecure : Options -> Options
157
215
  nonSecure builder =
158
216
  { builder
159
217
  | secure = False
@@ -162,7 +220,7 @@ nonSecure builder =
162
220
 
163
221
  {-| The default SameSite policy is Lax if one is not explicitly set. See the SameSite section in <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes>.
164
222
  -}
165
- withSameSite : SameSite -> SetCookie -> SetCookie
223
+ withSameSite : SameSite -> Options -> Options
166
224
  withSameSite sameSite builder =
167
225
  { builder
168
226
  | sameSite = Just sameSite
@@ -1,38 +0,0 @@
1
- module DataSource.Env exposing (get, expect)
2
-
3
- {-|
4
-
5
- @docs get, expect
6
-
7
- -}
8
-
9
- import DataSource exposing (DataSource)
10
- import DataSource.Http
11
- import DataSource.Internal.Request
12
- import Json.Decode as Decode
13
- import Json.Encode as Encode
14
-
15
-
16
- {-| -}
17
- get : String -> DataSource (Maybe String)
18
- get envVariableName =
19
- DataSource.Internal.Request.request
20
- { name = "env"
21
- , body = DataSource.Http.jsonBody (Encode.string envVariableName)
22
- , expect =
23
- DataSource.Http.expectJson
24
- (Decode.nullable Decode.string)
25
- }
26
-
27
-
28
- {-| -}
29
- expect : String -> DataSource String
30
- expect envVariableName =
31
- envVariableName
32
- |> get
33
- |> DataSource.andThen
34
- (\maybeValue ->
35
- maybeValue
36
- |> Result.fromMaybe ("DataSource.Env.expect was expecting a variable `" ++ envVariableName ++ "` but couldn't find a variable with that name.")
37
- |> DataSource.fromResult
38
- )