@sveltejs/kit 3.0.0-next.2 → 3.0.0-next.21

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 (195) hide show
  1. package/package.json +62 -36
  2. package/src/cli.js +15 -12
  3. package/src/constants.js +6 -1
  4. package/src/core/adapt/builder.js +66 -72
  5. package/src/core/adapt/index.js +17 -6
  6. package/src/core/config/index.js +134 -80
  7. package/src/core/config/options.js +295 -273
  8. package/src/core/config/types.d.ts +1 -1
  9. package/src/core/env.js +146 -16
  10. package/src/core/generate_manifest/index.js +18 -15
  11. package/src/core/postbuild/analyse.js +8 -20
  12. package/src/core/postbuild/crawl.js +22 -6
  13. package/src/core/postbuild/entities.js +8 -2
  14. package/src/core/postbuild/fallback.js +4 -2
  15. package/src/core/postbuild/prerender.js +222 -70
  16. package/src/core/postbuild/queue.js +2 -1
  17. package/src/core/sync/create_manifest_data/conflict.js +1 -1
  18. package/src/core/sync/create_manifest_data/index.js +82 -62
  19. package/src/core/sync/sync.js +40 -31
  20. package/src/core/sync/ts.js +1 -1
  21. package/src/core/sync/utils.js +22 -2
  22. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +107 -58
  23. package/src/core/sync/write_client_manifest.js +14 -29
  24. package/src/core/sync/write_env.js +7 -5
  25. package/src/core/sync/write_server.js +37 -45
  26. package/src/core/sync/write_tsconfig/index.js +274 -0
  27. package/src/core/sync/write_tsconfig/test-app/package.json +7 -0
  28. package/src/core/sync/write_tsconfig/utils.js +77 -0
  29. package/src/core/sync/write_tsconfig/validate.js +128 -0
  30. package/src/core/sync/write_types/index.js +127 -123
  31. package/src/core/utils.js +30 -5
  32. package/src/exports/env/index.js +77 -0
  33. package/src/exports/env/public.d.ts +55 -0
  34. package/src/exports/hooks/index.js +3 -9
  35. package/src/exports/hooks/public.d.ts +195 -0
  36. package/src/exports/hooks/sequence.js +9 -6
  37. package/src/exports/index.js +52 -29
  38. package/src/exports/internal/client.js +5 -0
  39. package/src/exports/internal/env.js +8 -5
  40. package/src/exports/internal/index.js +1 -90
  41. package/src/exports/internal/{event.js → server/event.js} +2 -3
  42. package/src/exports/internal/server/index.js +37 -0
  43. package/src/exports/internal/server/telemetry.js +95 -0
  44. package/src/exports/internal/shared.js +90 -0
  45. package/src/exports/node/index.js +64 -22
  46. package/src/exports/params/index.js +70 -0
  47. package/src/exports/params/public.d.ts +63 -0
  48. package/src/exports/public.d.ts +50 -1733
  49. package/src/exports/remote/index.js +11 -0
  50. package/src/exports/remote/public.d.ts +519 -0
  51. package/src/exports/url.js +86 -0
  52. package/src/exports/vite/build/build_server.js +54 -65
  53. package/src/exports/vite/build/remote.js +24 -19
  54. package/src/exports/vite/build/utils.js +0 -8
  55. package/src/exports/vite/dev/index.js +218 -146
  56. package/src/exports/vite/index.js +1590 -858
  57. package/src/exports/vite/module_ids.js +2 -2
  58. package/src/exports/vite/preview/index.js +40 -32
  59. package/src/exports/vite/public.d.ts +588 -0
  60. package/src/exports/vite/utils.js +84 -46
  61. package/src/pathname.js +55 -0
  62. package/src/runner.js +15 -0
  63. package/src/runtime/app/env/internal.js +4 -4
  64. package/src/runtime/app/env/types.d.ts +1 -1
  65. package/src/runtime/app/environment/index.js +3 -3
  66. package/src/runtime/app/{forms.js → forms/index.js} +73 -42
  67. package/src/runtime/app/forms/public.d.ts +2 -0
  68. package/src/runtime/app/forms/types.d.ts +56 -0
  69. package/src/runtime/app/internal/transport.js +53 -0
  70. package/src/runtime/app/manifest/index.js +1 -0
  71. package/src/runtime/app/{navigation.js → navigation/index.js} +2 -1
  72. package/src/runtime/app/navigation/public.d.ts +237 -0
  73. package/src/runtime/app/paths/client.js +37 -37
  74. package/src/runtime/app/paths/index.js +1 -1
  75. package/src/runtime/app/paths/internal/client.js +34 -2
  76. package/src/runtime/app/paths/internal/server.js +6 -23
  77. package/src/runtime/app/paths/internal.d.ts +3 -0
  78. package/src/runtime/app/paths/public.d.ts +1 -29
  79. package/src/runtime/app/paths/server.js +36 -17
  80. package/src/runtime/app/paths/types.d.ts +11 -19
  81. package/src/runtime/app/server/index.js +2 -2
  82. package/src/runtime/app/server/public.d.ts +201 -0
  83. package/src/runtime/app/server/remote/command.js +13 -11
  84. package/src/runtime/app/server/remote/form.js +61 -39
  85. package/src/runtime/app/server/remote/prerender.js +45 -45
  86. package/src/runtime/app/server/remote/query.js +106 -108
  87. package/src/runtime/app/server/remote/requested.js +27 -19
  88. package/src/runtime/app/server/remote/shared.js +76 -76
  89. package/src/runtime/app/service-worker/index.js +24 -0
  90. package/src/runtime/app/state/client.js +4 -2
  91. package/src/runtime/app/state/index.js +7 -5
  92. package/src/runtime/app/state/public.d.ts +72 -0
  93. package/src/runtime/app/state/server.js +3 -0
  94. package/src/runtime/app/stores.js +15 -78
  95. package/src/runtime/client/bundle.js +1 -1
  96. package/src/runtime/client/client-entry.js +3 -0
  97. package/src/runtime/client/client.js +1336 -648
  98. package/src/runtime/client/constants.js +3 -6
  99. package/src/runtime/client/entry.js +24 -3
  100. package/src/runtime/client/fetcher.js +25 -25
  101. package/src/runtime/client/ndjson.js +1 -1
  102. package/src/runtime/client/parse.js +1 -1
  103. package/src/runtime/client/payload.js +17 -0
  104. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  105. package/src/runtime/client/remote-functions/command.svelte.js +37 -61
  106. package/src/runtime/client/remote-functions/form.svelte.js +267 -178
  107. package/src/runtime/client/remote-functions/prerender.svelte.js +32 -11
  108. package/src/runtime/client/remote-functions/query/index.js +10 -17
  109. package/src/runtime/client/remote-functions/query/instance.svelte.js +64 -18
  110. package/src/runtime/client/remote-functions/query/proxy.js +5 -5
  111. package/src/runtime/client/remote-functions/query-batch.svelte.js +61 -70
  112. package/src/runtime/client/remote-functions/query-live/index.js +1 -1
  113. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +63 -21
  114. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -12
  115. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  116. package/src/runtime/client/remote-functions/shared.svelte.js +90 -65
  117. package/src/runtime/client/snapshots.js +147 -0
  118. package/src/runtime/client/state.svelte.js +94 -55
  119. package/src/runtime/client/stream.js +3 -2
  120. package/src/runtime/client/types.d.ts +13 -9
  121. package/src/runtime/client/utils.js +22 -110
  122. package/src/runtime/components/root.svelte +56 -0
  123. package/src/runtime/env/dynamic/private.js +7 -0
  124. package/src/runtime/env/dynamic/public.js +7 -0
  125. package/src/runtime/env/static/private.js +6 -0
  126. package/src/runtime/env/static/public.js +6 -0
  127. package/src/runtime/form-utils.js +172 -61
  128. package/src/runtime/pathname.js +20 -32
  129. package/src/runtime/props.svelte.js +72 -0
  130. package/src/runtime/server/constants.js +0 -3
  131. package/src/runtime/server/cookie.js +72 -55
  132. package/src/runtime/server/csrf.js +65 -0
  133. package/src/runtime/server/data/index.js +49 -53
  134. package/src/runtime/server/dev.js +22 -0
  135. package/src/runtime/server/endpoint.js +6 -7
  136. package/src/runtime/server/env_module.js +0 -5
  137. package/src/runtime/server/errors.js +160 -0
  138. package/src/runtime/server/fetch.js +32 -39
  139. package/src/runtime/server/index.js +90 -64
  140. package/src/runtime/server/internal.js +71 -0
  141. package/src/runtime/server/page/actions.js +85 -64
  142. package/src/runtime/server/page/crypto.js +2 -2
  143. package/src/runtime/server/page/csp.js +88 -104
  144. package/src/runtime/server/page/data_serializer.js +24 -25
  145. package/src/runtime/server/page/index.js +36 -53
  146. package/src/runtime/server/page/load_data.js +50 -57
  147. package/src/runtime/server/page/render.js +173 -239
  148. package/src/runtime/server/page/respond_with_error.js +17 -31
  149. package/src/runtime/server/page/serialize_data.js +2 -13
  150. package/src/runtime/server/page/server_routing.js +85 -26
  151. package/src/runtime/server/remote-functions.js +640 -0
  152. package/src/runtime/server/respond.js +190 -131
  153. package/src/runtime/server/sourcemaps.js +183 -0
  154. package/src/runtime/server/state.js +53 -0
  155. package/src/runtime/server/utils.js +13 -155
  156. package/src/runtime/shared.js +20 -40
  157. package/src/runtime/utils.js +3 -0
  158. package/src/types/ambient-private.d.ts +11 -1
  159. package/src/types/ambient.d.ts +87 -36
  160. package/src/types/global-private.d.ts +25 -25
  161. package/src/types/internal.d.ts +163 -157
  162. package/src/types/private.d.ts +41 -1
  163. package/src/utils/error.js +28 -4
  164. package/src/utils/escape.js +9 -25
  165. package/src/utils/features.js +1 -1
  166. package/src/utils/filesystem.js +1 -23
  167. package/src/utils/fork.js +7 -2
  168. package/src/utils/hash.js +21 -0
  169. package/src/utils/http.js +8 -7
  170. package/src/utils/import.js +9 -2
  171. package/src/utils/imports.js +83 -0
  172. package/src/utils/mime.js +9 -0
  173. package/src/utils/page_nodes.js +6 -7
  174. package/src/utils/params.js +67 -0
  175. package/src/utils/regex.js +9 -0
  176. package/src/utils/routing.js +145 -73
  177. package/src/utils/shared-iterator.js +5 -0
  178. package/src/utils/streaming.js +14 -4
  179. package/src/utils/url.js +33 -2
  180. package/src/version.js +1 -1
  181. package/types/index.d.ts +2616 -2433
  182. package/types/index.d.ts.map +120 -106
  183. package/src/core/sync/write_ambient.js +0 -18
  184. package/src/core/sync/write_root.js +0 -148
  185. package/src/core/sync/write_tsconfig.js +0 -250
  186. package/src/exports/internal/server.js +0 -22
  187. package/src/exports/node/polyfills.js +0 -30
  188. package/src/runtime/server/app.js +0 -9
  189. package/src/runtime/server/remote.js +0 -457
  190. package/src/runtime/shared-server.js +0 -7
  191. package/src/runtime/telemetry/otel.js +0 -21
  192. package/src/runtime/telemetry/record_span.js +0 -65
  193. package/src/types/synthetic/$lib.md +0 -5
  194. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
  195. /package/src/{runtime/telemetry/noop.js → telemetry.js} +0 -0
@@ -1,14 +1,16 @@
1
- /** @import { RequestEvent, ActionResult, Actions } from '@sveltejs/kit' */
2
- /** @import { SSROptions, SSRNode, ServerNode, ServerHooks } from 'types' */
3
- import * as devalue from 'devalue';
1
+ /** @import { Actions } from '@sveltejs/kit' */
2
+ /** @import { RequestEvent } from '$app/server' */
3
+ /** @import { ActionResult } from '$app/forms' */
4
+ /** @import { SSROptions, SSRNode, ServerNode } from 'types' */
4
5
  import { DEV } from 'esm-env';
5
6
  import { json } from '@sveltejs/kit';
6
7
  import { HttpError, Redirect, ActionFailure, SvelteKitError } from '@sveltejs/kit/internal';
7
- import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server';
8
- import { get_status, normalize_error } from '../../../utils/error.js';
8
+ import { with_request_store, merge_tracing, record_span } from '@sveltejs/kit/internal/server';
9
+ import { normalize_error } from '../../../utils/error.js';
9
10
  import { is_form_content_type, negotiate } from '../../../utils/http.js';
10
- import { create_replacer, handle_error_and_jsonify } from '../utils.js';
11
- import { record_span } from '../../telemetry/record_span.js';
11
+ import { with_version_header } from '../utils.js';
12
+ import { handle_error_and_jsonify } from '../errors.js';
13
+ import { stringify, uneval } from '#app/internal/transport';
12
14
 
13
15
  /** @param {RequestEvent} event */
14
16
  export function is_action_json_request(event) {
@@ -22,12 +24,13 @@ export function is_action_json_request(event) {
22
24
 
23
25
  /**
24
26
  * @param {RequestEvent} event
25
- * @param {import('types').RequestState} event_state
27
+ * @param {import('types').RequestState} state
26
28
  * @param {SSROptions} options
27
29
  * @param {SSRNode['server'] | undefined} server
28
30
  */
29
- export async function handle_action_json_request(event, event_state, options, server) {
31
+ export async function handle_action_json_request(event, state, options, server) {
30
32
  const actions = server?.actions;
33
+ const location = get_action_location(event.url);
31
34
 
32
35
  if (!actions) {
33
36
  const no_actions_error = new SvelteKitError(
@@ -36,13 +39,16 @@ export async function handle_action_json_request(event, event_state, options, se
36
39
  `POST method not allowed. No form actions exist for ${DEV ? `the page at ${event.route.id}` : 'this page'}`
37
40
  );
38
41
 
42
+ const error = await handle_error_and_jsonify(event, state, options, no_actions_error);
43
+
39
44
  return action_json(
40
45
  {
41
46
  type: 'error',
42
- error: await handle_error_and_jsonify(event, event_state, options, no_actions_error)
47
+ error,
48
+ location
43
49
  },
44
50
  {
45
- status: no_actions_error.status,
51
+ status: error.status,
46
52
  headers: {
47
53
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
48
54
  // "The server must generate an Allow header field in a 405 status code response"
@@ -55,35 +61,40 @@ export async function handle_action_json_request(event, event_state, options, se
55
61
  check_named_default_separate(actions);
56
62
 
57
63
  try {
58
- const data = await call_action(event, event_state, actions);
64
+ const data = await call_action(event, state, actions);
59
65
 
60
66
  if (DEV) {
61
67
  validate_action_return(data);
62
68
  }
63
69
 
64
70
  if (data instanceof ActionFailure) {
71
+ return action_json(
72
+ {
73
+ type: 'failure',
74
+ status: data.status,
75
+ location,
76
+ // @ts-expect-error we assign a string to what is supposed to be an object. That's ok
77
+ // because we don't use the object outside, and this way we have better code navigation
78
+ // through knowing where the related interface is used.
79
+ data: try_serialize(data.data, stringify, /** @type {string} */ (event.route.id))
80
+ },
81
+ {
82
+ status: data.status
83
+ }
84
+ );
85
+ } else if (data) {
65
86
  return action_json({
66
- type: 'failure',
67
- status: data.status,
68
- // @ts-expect-error we assign a string to what is supposed to be an object. That's ok
69
- // because we don't use the object outside, and this way we have better code navigation
70
- // through knowing where the related interface is used.
71
- data: stringify_action_response(
72
- data.data,
73
- /** @type {string} */ (event.route.id),
74
- options.hooks.transport
75
- )
87
+ type: 'success',
88
+ status: 200,
89
+ location,
90
+ // @ts-expect-error see comment above
91
+ data: try_serialize(data, stringify, /** @type {string} */ (event.route.id))
76
92
  });
77
93
  } else {
78
94
  return action_json({
79
95
  type: 'success',
80
- status: data ? 200 : 204,
81
- // @ts-expect-error see comment above
82
- data: stringify_action_response(
83
- data,
84
- /** @type {string} */ (event.route.id),
85
- options.hooks.transport
86
- )
96
+ status: 204,
97
+ location
87
98
  });
88
99
  }
89
100
  } catch (e) {
@@ -93,23 +104,42 @@ export async function handle_action_json_request(event, event_state, options, se
93
104
  return action_json_redirect(err);
94
105
  }
95
106
 
107
+ const transformed = await handle_error_and_jsonify(
108
+ event,
109
+ state,
110
+ options,
111
+ check_incorrect_fail_use(err)
112
+ );
113
+
96
114
  return action_json(
97
115
  {
98
116
  type: 'error',
99
- error: await handle_error_and_jsonify(
100
- event,
101
- event_state,
102
- options,
103
- check_incorrect_fail_use(err)
104
- )
117
+ error: transformed,
118
+ location
105
119
  },
106
120
  {
107
- status: get_status(err)
121
+ status: transformed.status
108
122
  }
109
123
  );
110
124
  }
111
125
  }
112
126
 
127
+ /**
128
+ * @param {URL} url
129
+ */
130
+ export function get_action_location(url) {
131
+ const location = new URL(url);
132
+
133
+ for (const key of location.searchParams.keys()) {
134
+ if (key.startsWith('/')) {
135
+ location.searchParams.delete(key);
136
+ break;
137
+ }
138
+ }
139
+
140
+ return location.pathname + location.search;
141
+ }
142
+
113
143
  /**
114
144
  * @param {HttpError | Error} error
115
145
  */
@@ -135,7 +165,7 @@ export function action_json_redirect(redirect) {
135
165
  * @param {ResponseInit} [init]
136
166
  */
137
167
  function action_json(data, init) {
138
- return json(data, init);
168
+ return with_version_header(json(data, init));
139
169
  }
140
170
 
141
171
  /**
@@ -147,12 +177,13 @@ export function is_action_request(event) {
147
177
 
148
178
  /**
149
179
  * @param {RequestEvent} event
150
- * @param {import('types').RequestState} event_state
180
+ * @param {import('types').RequestState} state
151
181
  * @param {SSRNode['server'] | undefined} server
152
182
  * @returns {Promise<ActionResult>}
153
183
  */
154
- export async function handle_action_request(event, event_state, server) {
184
+ export async function handle_action_request(event, state, server) {
155
185
  const actions = server?.actions;
186
+ const location = get_action_location(event.url);
156
187
 
157
188
  if (!actions) {
158
189
  // TODO should this be a different error altogether?
@@ -163,6 +194,8 @@ export async function handle_action_request(event, event_state, server) {
163
194
  });
164
195
  return {
165
196
  type: 'error',
197
+ location,
198
+ // We're lying a bit with the types here; this will be transformed into a proper App.Error object later
166
199
  error: new SvelteKitError(
167
200
  405,
168
201
  'Method Not Allowed',
@@ -174,7 +207,7 @@ export async function handle_action_request(event, event_state, server) {
174
207
  check_named_default_separate(actions);
175
208
 
176
209
  try {
177
- const data = await call_action(event, event_state, actions);
210
+ const data = await call_action(event, state, actions);
178
211
 
179
212
  if (DEV) {
180
213
  validate_action_return(data);
@@ -184,12 +217,14 @@ export async function handle_action_request(event, event_state, server) {
184
217
  return {
185
218
  type: 'failure',
186
219
  status: data.status,
220
+ location,
187
221
  data: data.data
188
222
  };
189
223
  } else {
190
224
  return {
191
225
  type: 'success',
192
226
  status: 200,
227
+ location,
193
228
  // @ts-expect-error this will be removed upon serialization, so `undefined` is the same as omission
194
229
  data
195
230
  };
@@ -207,6 +242,8 @@ export async function handle_action_request(event, event_state, server) {
207
242
 
208
243
  return {
209
244
  type: 'error',
245
+ location,
246
+ // @ts-expect-error We're lying a bit with the types here; this will be transformed into a proper App.Error object later
210
247
  error: check_incorrect_fail_use(err)
211
248
  };
212
249
  }
@@ -225,11 +262,11 @@ function check_named_default_separate(actions) {
225
262
 
226
263
  /**
227
264
  * @param {RequestEvent} event
228
- * @param {import('types').RequestState} event_state
265
+ * @param {import('types').RequestState} state
229
266
  * @param {NonNullable<ServerNode['actions']>} actions
230
267
  * @throws {Redirect | HttpError | SvelteKitError | Error}
231
268
  */
232
- async function call_action(event, event_state, actions) {
269
+ async function call_action(event, state, actions) {
233
270
  const url = new URL(event.request.url);
234
271
 
235
272
  let name = 'default';
@@ -243,11 +280,12 @@ async function call_action(event, event_state, actions) {
243
280
  }
244
281
  }
245
282
 
246
- const action = actions[name];
247
- if (!action) {
283
+ if (!Object.hasOwn(actions, name)) {
248
284
  throw new SvelteKitError(404, 'Not Found', `No action with name '${name}' found`);
249
285
  }
250
286
 
287
+ const action = actions[name];
288
+
251
289
  if (!is_form_content_type(event.request)) {
252
290
  throw new SvelteKitError(
253
291
  415,
@@ -267,7 +305,7 @@ async function call_action(event, event_state, actions) {
267
305
  fn: async (current) => {
268
306
  const traced_event = merge_tracing(event, current);
269
307
 
270
- const result = await with_request_store({ event: traced_event, state: event_state }, () =>
308
+ const result = await with_request_store({ event: traced_event, state }, () =>
271
309
  action(traced_event)
272
310
  );
273
311
 
@@ -298,26 +336,9 @@ function validate_action_return(data) {
298
336
  * Try to `devalue.uneval` the data object, and if it fails, return a proper Error with context
299
337
  * @param {any} data
300
338
  * @param {string} route_id
301
- * @param {ServerHooks['transport']} transport
302
339
  */
303
- export function uneval_action_response(data, route_id, transport) {
304
- const replacer = create_replacer(transport);
305
-
306
- return try_serialize(data, (value) => devalue.uneval(value, replacer), route_id);
307
- }
308
-
309
- /**
310
- * Try to `devalue.stringify` the data object, and if it fails, return a proper Error with context
311
- * @param {any} data
312
- * @param {string} route_id
313
- * @param {ServerHooks['transport']} transport
314
- */
315
- function stringify_action_response(data, route_id, transport) {
316
- const encoders = Object.fromEntries(
317
- Object.entries(transport).map(([key, value]) => [key, value.encode])
318
- );
319
-
320
- return try_serialize(data, (value) => devalue.stringify(value, encoders), route_id);
340
+ export function uneval_action_response(data, route_id) {
341
+ return try_serialize(data, uneval, route_id);
321
342
  }
322
343
 
323
344
  /**
@@ -1,4 +1,4 @@
1
- import { text_encoder } from '../../utils.js';
1
+ import { base64_encode, text_encoder } from '../../utils.js';
2
2
 
3
3
  /**
4
4
  * SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
@@ -102,7 +102,7 @@ export function sha256(data) {
102
102
  const bytes = new Uint8Array(out.buffer);
103
103
  reverse_endianness(bytes);
104
104
 
105
- return btoa(String.fromCharCode(...bytes));
105
+ return base64_encode(bytes);
106
106
  }
107
107
 
108
108
  /** The SHA-256 initialization vector */
@@ -1,11 +1,12 @@
1
1
  import { escape_html } from '../../../utils/escape.js';
2
+ import { base64_encode } from '../../utils.js';
2
3
  import { sha256 } from './crypto.js';
3
4
 
4
5
  const array = new Uint8Array(16);
5
6
 
6
7
  function generate_nonce() {
7
8
  crypto.getRandomValues(array);
8
- return btoa(String.fromCharCode(...array));
9
+ return base64_encode(array);
9
10
  }
10
11
 
11
12
  const quoted = new Set([
@@ -53,19 +54,19 @@ class BaseProvider {
53
54
  #directives;
54
55
 
55
56
  /** @type {Set<import('types').Csp.Source>} */
56
- #script_src;
57
+ #script_src = new Set();
57
58
 
58
59
  /** @type {Set<import('types').Csp.Source>} */
59
- #script_src_elem;
60
+ #script_src_elem = new Set();
60
61
 
61
62
  /** @type {Set<import('types').Csp.Source>} */
62
- #style_src;
63
+ #style_src = new Set();
63
64
 
64
65
  /** @type {Set<import('types').Csp.Source>} */
65
- #style_src_attr;
66
+ #style_src_attr = new Set();
66
67
 
67
68
  /** @type {Set<import('types').Csp.Source>} */
68
- #style_src_elem;
69
+ #style_src_elem = new Set();
69
70
 
70
71
  /** @type {boolean} */
71
72
  script_needs_nonce;
@@ -90,12 +91,6 @@ class BaseProvider {
90
91
 
91
92
  const d = this.#directives;
92
93
 
93
- this.#script_src = new Set();
94
- this.#script_src_elem = new Set();
95
- this.#style_src = new Set();
96
- this.#style_src_attr = new Set();
97
- this.#style_src_elem = new Set();
98
-
99
94
  const effective_script_src = d['script-src'] || d['default-src'];
100
95
  const script_src_elem = d['script-src-elem'];
101
96
  const effective_style_src = d['style-src'] || d['default-src'];
@@ -117,32 +112,25 @@ class BaseProvider {
117
112
 
118
113
  // ...and add unsafe-inline so we can inject <style> elements
119
114
  // Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list, so we remove those during dev when injecting unsafe-inline
120
- if (effective_style_src && !effective_style_src.includes('unsafe-inline')) {
121
- d['style-src'] = [
122
- ...effective_style_src.filter(
123
- (value) => !(value.startsWith('sha256-') || value.startsWith('nonce-'))
124
- ),
125
- 'unsafe-inline'
126
- ];
127
- }
128
-
129
- if (style_src_attr && !style_src_attr.includes('unsafe-inline')) {
130
- d['style-src-attr'] = [
131
- ...style_src_attr.filter(
132
- (value) => !(value.startsWith('sha256-') || value.startsWith('nonce-'))
133
- ),
134
- 'unsafe-inline'
135
- ];
136
- }
137
-
138
- if (style_src_elem && !style_src_elem.includes('unsafe-inline')) {
139
- d['style-src-elem'] = [
140
- ...style_src_elem.filter(
141
- (value) => !(value.startsWith('sha256-') || value.startsWith('nonce-'))
142
- ),
143
- 'unsafe-inline'
144
- ];
145
- }
115
+ /**
116
+ * @template {'style-src' | 'style-src-attr' | 'style-src-elem'} K
117
+ * @param {K} key
118
+ * @param {import('types').CspDirectives[K]} directive
119
+ */
120
+ const add_unsafe_inline = (key, directive) => {
121
+ if (directive && !directive.includes('unsafe-inline')) {
122
+ d[key] = /** @type {import('types').CspDirectives[K]} */ ([
123
+ ...directive.filter(
124
+ (value) => !(value.startsWith('sha256-') || value.startsWith('nonce-'))
125
+ ),
126
+ 'unsafe-inline'
127
+ ]);
128
+ }
129
+ };
130
+
131
+ add_unsafe_inline('style-src', effective_style_src);
132
+ add_unsafe_inline('style-src-attr', style_src_attr);
133
+ add_unsafe_inline('style-src-elem', style_src_elem);
146
134
  }
147
135
 
148
136
  /** @param {(import('types').Csp.Source | import('types').Csp.ActionSource)[] | undefined} directive */
@@ -175,13 +163,16 @@ class BaseProvider {
175
163
  this.#nonce = nonce;
176
164
  }
177
165
 
178
- /** @param {string} content */
179
- add_script(content) {
180
- if (!this.#script_needs_csp) return;
181
-
182
- /** @type {`nonce-${string}` | `sha256-${string}`} */
183
- const source = this.#use_hashes ? `sha256-${sha256(content)}` : `nonce-${this.#nonce}`;
166
+ /**
167
+ * @param {string} content
168
+ * @returns {`nonce-${string}` | `sha256-${string}`}
169
+ */
170
+ #get_source(content) {
171
+ return this.#use_hashes ? `sha256-${sha256(content)}` : `nonce-${this.#nonce}`;
172
+ }
184
173
 
174
+ /** @param {`nonce-${string}` | `sha256-${string}`} source */
175
+ #add_script_source(source) {
185
176
  if (this.#script_src_needs_csp) {
186
177
  this.#script_src.add(source);
187
178
  }
@@ -191,15 +182,17 @@ class BaseProvider {
191
182
  }
192
183
  }
193
184
 
185
+ /** @param {string} content */
186
+ add_script(content) {
187
+ if (!this.#script_needs_csp) return;
188
+
189
+ this.#add_script_source(this.#get_source(content));
190
+ }
191
+
194
192
  /** @param {`sha256-${string}`[]} hashes */
195
193
  add_script_hashes(hashes) {
196
194
  for (const hash of hashes) {
197
- if (this.#script_src_needs_csp) {
198
- this.#script_src.add(hash);
199
- }
200
- if (this.#script_src_elem_needs_csp) {
201
- this.#script_src_elem.add(hash);
202
- }
195
+ this.#add_script_source(hash);
203
196
  }
204
197
  }
205
198
 
@@ -207,8 +200,7 @@ class BaseProvider {
207
200
  add_style(content) {
208
201
  if (!this.#style_needs_csp) return;
209
202
 
210
- /** @type {`nonce-${string}` | `sha256-${string}`} */
211
- const source = this.#use_hashes ? `sha256-${sha256(content)}` : `nonce-${this.#nonce}`;
203
+ const source = this.#get_source(content);
212
204
 
213
205
  if (this.#style_src_needs_csp) {
214
206
  this.#style_src.add(source);
@@ -251,40 +243,34 @@ class BaseProvider {
251
243
 
252
244
  const directives = { ...this.#directives };
253
245
 
254
- if (this.#style_src.size > 0) {
255
- directives['style-src'] = [
256
- ...(directives['style-src'] || directives['default-src'] || []),
257
- ...this.#style_src
258
- ];
259
- }
260
-
261
- if (this.#style_src_attr.size > 0) {
262
- directives['style-src-attr'] = [
263
- ...(directives['style-src-attr'] || []),
264
- ...this.#style_src_attr
265
- ];
266
- }
267
-
268
- if (this.#style_src_elem.size > 0) {
269
- directives['style-src-elem'] = [
270
- ...(directives['style-src-elem'] || []),
271
- ...this.#style_src_elem
272
- ];
273
- }
274
-
275
- if (this.#script_src.size > 0) {
276
- directives['script-src'] = [
277
- ...(directives['script-src'] || directives['default-src'] || []),
278
- ...this.#script_src
279
- ];
280
- }
281
-
282
- if (this.#script_src_elem.size > 0) {
283
- directives['script-src-elem'] = [
284
- ...(directives['script-src-elem'] || []),
285
- ...this.#script_src_elem
286
- ];
287
- }
246
+ /**
247
+ * @template {'style-src' | 'style-src-attr' | 'style-src-elem' | 'script-src' | 'script-src-elem'} K
248
+ * @param {K} key
249
+ * @param {Set<import('types').Csp.Source>} sources
250
+ * @param {import('types').CspDirectives[K]} [base]
251
+ */
252
+ const merge_sources = (key, sources, base) => {
253
+ if (sources.size > 0) {
254
+ directives[key] = /** @type {import('types').CspDirectives[K]} */ ([
255
+ ...(base || []),
256
+ ...sources
257
+ ]);
258
+ }
259
+ };
260
+
261
+ merge_sources(
262
+ 'style-src',
263
+ this.#style_src,
264
+ directives['style-src'] || directives['default-src']
265
+ );
266
+ merge_sources('style-src-attr', this.#style_src_attr, directives['style-src-attr']);
267
+ merge_sources('style-src-elem', this.#style_src_elem, directives['style-src-elem']);
268
+ merge_sources(
269
+ 'script-src',
270
+ this.#script_src,
271
+ directives['script-src'] || directives['default-src']
272
+ );
273
+ merge_sources('script-src-elem', this.#script_src_elem, directives['script-src-elem']);
288
274
 
289
275
  for (const key in directives) {
290
276
  if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) {
@@ -300,13 +286,11 @@ class BaseProvider {
300
286
 
301
287
  const directive = [key];
302
288
  if (Array.isArray(value)) {
303
- value.forEach((value) => {
304
- if (quoted.has(value) || crypto_pattern.test(value)) {
305
- directive.push(`'${value}'`);
306
- } else {
307
- directive.push(value);
308
- }
309
- });
289
+ for (const source of value) {
290
+ directive.push(
291
+ quoted.has(source) || crypto_pattern.test(source) ? `'${source}'` : source
292
+ );
293
+ }
310
294
  }
311
295
 
312
296
  header.push(directive.join(' '));
@@ -337,17 +321,17 @@ class CspReportOnlyProvider extends BaseProvider {
337
321
  constructor(use_hashes, directives, nonce) {
338
322
  super(use_hashes, directives, nonce);
339
323
 
340
- if (Object.values(directives).filter((v) => !!v).length > 0) {
341
- // If we're generating content-security-policy-report-only,
342
- // if there are any directives, we need a report-uri or report-to (or both)
343
- // else it's just an expensive noop.
344
- const has_report_to = directives['report-to']?.length ?? 0 > 0;
345
- const has_report_uri = directives['report-uri']?.length ?? 0 > 0;
346
- if (!has_report_to && !has_report_uri) {
347
- throw Error(
348
- '`content-security-policy-report-only` must be specified with either the `report-to` or `report-uri` directives, or both'
349
- );
350
- }
324
+ // If we're generating content-security-policy-report-only,
325
+ // if there are any directives, we need a report-uri or report-to (or both)
326
+ // else it's just an expensive noop.
327
+ if (
328
+ Object.values(directives).some((v) => !!v) &&
329
+ !directives['report-to']?.length &&
330
+ !directives['report-uri']?.length
331
+ ) {
332
+ throw Error(
333
+ '`content-security-policy-report-only` must be specified with either the `report-to` or `report-uri` directives, or both'
334
+ );
351
335
  }
352
336
  }
353
337
  }