@sveltejs/kit 3.0.0-next.2 → 3.0.0-next.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 (192) hide show
  1. package/package.json +58 -36
  2. package/src/cli.js +14 -11
  3. package/src/constants.js +6 -1
  4. package/src/core/adapt/builder.js +54 -60
  5. package/src/core/adapt/index.js +2 -5
  6. package/src/core/config/index.js +141 -76
  7. package/src/core/config/options.js +260 -260
  8. package/src/core/config/types.d.ts +1 -1
  9. package/src/core/env.js +143 -13
  10. package/src/core/generate_manifest/index.js +18 -15
  11. package/src/core/postbuild/analyse.js +7 -19
  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 +193 -69
  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 +72 -52
  19. package/src/core/sync/sync.js +35 -26
  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} +105 -56
  23. package/src/core/sync/write_client_manifest.js +13 -28
  24. package/src/core/sync/write_env.js +7 -5
  25. package/src/core/sync/write_server.js +25 -33
  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 +119 -116
  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 +194 -0
  36. package/src/exports/hooks/sequence.js +9 -6
  37. package/src/exports/index.js +53 -20
  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} +1 -2
  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 +219 -1118
  49. package/src/exports/url.js +86 -0
  50. package/src/exports/vite/build/build_server.js +53 -64
  51. package/src/exports/vite/build/remote.js +24 -19
  52. package/src/exports/vite/build/utils.js +0 -8
  53. package/src/exports/vite/dev/index.js +199 -125
  54. package/src/exports/vite/index.js +1514 -833
  55. package/src/exports/vite/module_ids.js +2 -2
  56. package/src/exports/vite/preview/index.js +32 -24
  57. package/src/exports/vite/utils.js +83 -45
  58. package/src/pathname.js +55 -0
  59. package/src/runner.js +15 -0
  60. package/src/runtime/app/env/internal.js +4 -4
  61. package/src/runtime/app/env/types.d.ts +1 -1
  62. package/src/runtime/app/environment/index.js +3 -3
  63. package/src/runtime/app/{forms.js → forms/index.js} +73 -42
  64. package/src/runtime/app/forms/public.d.ts +2 -0
  65. package/src/runtime/app/forms/types.d.ts +56 -0
  66. package/src/runtime/app/internal/transport.js +53 -0
  67. package/src/runtime/app/manifest/index.js +1 -0
  68. package/src/runtime/app/{navigation.js → navigation/index.js} +2 -1
  69. package/src/runtime/app/navigation/public.d.ts +237 -0
  70. package/src/runtime/app/paths/client.js +37 -37
  71. package/src/runtime/app/paths/index.js +1 -1
  72. package/src/runtime/app/paths/internal/client.js +34 -2
  73. package/src/runtime/app/paths/internal/server.js +6 -23
  74. package/src/runtime/app/paths/internal.d.ts +3 -0
  75. package/src/runtime/app/paths/public.d.ts +1 -29
  76. package/src/runtime/app/paths/server.js +36 -17
  77. package/src/runtime/app/paths/types.d.ts +11 -19
  78. package/src/runtime/app/server/index.js +2 -2
  79. package/src/runtime/app/server/public.d.ts +519 -0
  80. package/src/runtime/app/server/remote/command.js +13 -11
  81. package/src/runtime/app/server/remote/form.js +61 -39
  82. package/src/runtime/app/server/remote/prerender.js +45 -45
  83. package/src/runtime/app/server/remote/query.js +106 -108
  84. package/src/runtime/app/server/remote/requested.js +27 -19
  85. package/src/runtime/app/server/remote/shared.js +75 -75
  86. package/src/runtime/app/service-worker/index.js +24 -0
  87. package/src/runtime/app/state/client.js +4 -2
  88. package/src/runtime/app/state/index.js +7 -5
  89. package/src/runtime/app/state/public.d.ts +72 -0
  90. package/src/runtime/app/state/server.js +3 -0
  91. package/src/runtime/app/stores.js +15 -78
  92. package/src/runtime/client/bundle.js +1 -1
  93. package/src/runtime/client/client-entry.js +3 -0
  94. package/src/runtime/client/client.js +1336 -648
  95. package/src/runtime/client/constants.js +3 -6
  96. package/src/runtime/client/entry.js +24 -3
  97. package/src/runtime/client/fetcher.js +25 -25
  98. package/src/runtime/client/ndjson.js +1 -1
  99. package/src/runtime/client/parse.js +1 -1
  100. package/src/runtime/client/payload.js +17 -0
  101. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  102. package/src/runtime/client/remote-functions/command.svelte.js +37 -61
  103. package/src/runtime/client/remote-functions/form.svelte.js +267 -178
  104. package/src/runtime/client/remote-functions/prerender.svelte.js +32 -11
  105. package/src/runtime/client/remote-functions/query/index.js +10 -17
  106. package/src/runtime/client/remote-functions/query/instance.svelte.js +64 -18
  107. package/src/runtime/client/remote-functions/query/proxy.js +5 -5
  108. package/src/runtime/client/remote-functions/query-batch.svelte.js +61 -70
  109. package/src/runtime/client/remote-functions/query-live/index.js +1 -1
  110. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +63 -21
  111. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -12
  112. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  113. package/src/runtime/client/remote-functions/shared.svelte.js +90 -65
  114. package/src/runtime/client/snapshots.js +147 -0
  115. package/src/runtime/client/state.svelte.js +94 -55
  116. package/src/runtime/client/stream.js +3 -2
  117. package/src/runtime/client/types.d.ts +13 -9
  118. package/src/runtime/client/utils.js +22 -110
  119. package/src/runtime/components/root.svelte +56 -0
  120. package/src/runtime/env/dynamic/private.js +7 -0
  121. package/src/runtime/env/dynamic/public.js +7 -0
  122. package/src/runtime/env/static/private.js +6 -0
  123. package/src/runtime/env/static/public.js +6 -0
  124. package/src/runtime/form-utils.js +172 -61
  125. package/src/runtime/pathname.js +20 -32
  126. package/src/runtime/props.svelte.js +72 -0
  127. package/src/runtime/server/constants.js +0 -3
  128. package/src/runtime/server/cookie.js +70 -53
  129. package/src/runtime/server/csrf.js +65 -0
  130. package/src/runtime/server/data/index.js +48 -52
  131. package/src/runtime/server/dev.js +22 -0
  132. package/src/runtime/server/endpoint.js +3 -4
  133. package/src/runtime/server/env_module.js +0 -5
  134. package/src/runtime/server/errors.js +160 -0
  135. package/src/runtime/server/fetch.js +31 -38
  136. package/src/runtime/server/index.js +90 -64
  137. package/src/runtime/server/internal.js +71 -0
  138. package/src/runtime/server/page/actions.js +84 -64
  139. package/src/runtime/server/page/crypto.js +2 -2
  140. package/src/runtime/server/page/csp.js +88 -104
  141. package/src/runtime/server/page/data_serializer.js +22 -23
  142. package/src/runtime/server/page/index.js +35 -53
  143. package/src/runtime/server/page/load_data.js +47 -54
  144. package/src/runtime/server/page/render.js +172 -238
  145. package/src/runtime/server/page/respond_with_error.js +16 -30
  146. package/src/runtime/server/page/serialize_data.js +2 -13
  147. package/src/runtime/server/page/server_routing.js +85 -26
  148. package/src/runtime/server/remote-functions.js +639 -0
  149. package/src/runtime/server/respond.js +188 -129
  150. package/src/runtime/server/sourcemaps.js +183 -0
  151. package/src/runtime/server/state.js +53 -0
  152. package/src/runtime/server/utils.js +12 -154
  153. package/src/runtime/shared.js +20 -40
  154. package/src/runtime/utils.js +3 -0
  155. package/src/types/ambient-private.d.ts +11 -1
  156. package/src/types/ambient.d.ts +87 -36
  157. package/src/types/global-private.d.ts +25 -25
  158. package/src/types/internal.d.ts +160 -150
  159. package/src/types/private.d.ts +41 -1
  160. package/src/utils/error.js +28 -4
  161. package/src/utils/escape.js +9 -25
  162. package/src/utils/features.js +1 -1
  163. package/src/utils/filesystem.js +1 -23
  164. package/src/utils/fork.js +7 -2
  165. package/src/utils/hash.js +21 -0
  166. package/src/utils/http.js +8 -7
  167. package/src/utils/import.js +9 -2
  168. package/src/utils/imports.js +83 -0
  169. package/src/utils/mime.js +9 -0
  170. package/src/utils/page_nodes.js +6 -7
  171. package/src/utils/params.js +67 -0
  172. package/src/utils/regex.js +9 -0
  173. package/src/utils/routing.js +145 -73
  174. package/src/utils/shared-iterator.js +5 -0
  175. package/src/utils/streaming.js +14 -4
  176. package/src/utils/url.js +33 -2
  177. package/src/version.js +1 -1
  178. package/types/index.d.ts +2291 -2135
  179. package/types/index.d.ts.map +116 -108
  180. package/src/core/sync/write_ambient.js +0 -18
  181. package/src/core/sync/write_root.js +0 -148
  182. package/src/core/sync/write_tsconfig.js +0 -250
  183. package/src/exports/internal/server.js +0 -22
  184. package/src/exports/node/polyfills.js +0 -30
  185. package/src/runtime/server/app.js +0 -9
  186. package/src/runtime/server/remote.js +0 -457
  187. package/src/runtime/shared-server.js +0 -7
  188. package/src/runtime/telemetry/otel.js +0 -21
  189. package/src/runtime/telemetry/record_span.js +0 -65
  190. package/src/types/synthetic/$lib.md +0 -5
  191. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
  192. /package/src/{runtime/telemetry/noop.js → telemetry.js} +0 -0
@@ -1,14 +1,15 @@
1
- /** @import { RequestEvent, ActionResult, Actions } from '@sveltejs/kit' */
2
- /** @import { SSROptions, SSRNode, ServerNode, ServerHooks } from 'types' */
3
- import * as devalue from 'devalue';
1
+ /** @import { RequestEvent, Actions } from '@sveltejs/kit' */
2
+ /** @import { ActionResult } from '$app/forms' */
3
+ /** @import { SSROptions, SSRNode, ServerNode } from 'types' */
4
4
  import { DEV } from 'esm-env';
5
5
  import { json } from '@sveltejs/kit';
6
6
  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';
7
+ import { with_request_store, merge_tracing, record_span } from '@sveltejs/kit/internal/server';
8
+ import { normalize_error } from '../../../utils/error.js';
9
9
  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';
10
+ import { with_version_header } from '../utils.js';
11
+ import { handle_error_and_jsonify } from '../errors.js';
12
+ import { stringify, uneval } from '#app/internal/transport';
12
13
 
13
14
  /** @param {RequestEvent} event */
14
15
  export function is_action_json_request(event) {
@@ -22,12 +23,13 @@ export function is_action_json_request(event) {
22
23
 
23
24
  /**
24
25
  * @param {RequestEvent} event
25
- * @param {import('types').RequestState} event_state
26
+ * @param {import('types').RequestState} state
26
27
  * @param {SSROptions} options
27
28
  * @param {SSRNode['server'] | undefined} server
28
29
  */
29
- export async function handle_action_json_request(event, event_state, options, server) {
30
+ export async function handle_action_json_request(event, state, options, server) {
30
31
  const actions = server?.actions;
32
+ const location = get_action_location(event.url);
31
33
 
32
34
  if (!actions) {
33
35
  const no_actions_error = new SvelteKitError(
@@ -36,13 +38,16 @@ export async function handle_action_json_request(event, event_state, options, se
36
38
  `POST method not allowed. No form actions exist for ${DEV ? `the page at ${event.route.id}` : 'this page'}`
37
39
  );
38
40
 
41
+ const error = await handle_error_and_jsonify(event, state, options, no_actions_error);
42
+
39
43
  return action_json(
40
44
  {
41
45
  type: 'error',
42
- error: await handle_error_and_jsonify(event, event_state, options, no_actions_error)
46
+ error,
47
+ location
43
48
  },
44
49
  {
45
- status: no_actions_error.status,
50
+ status: error.status,
46
51
  headers: {
47
52
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
48
53
  // "The server must generate an Allow header field in a 405 status code response"
@@ -55,35 +60,40 @@ export async function handle_action_json_request(event, event_state, options, se
55
60
  check_named_default_separate(actions);
56
61
 
57
62
  try {
58
- const data = await call_action(event, event_state, actions);
63
+ const data = await call_action(event, state, actions);
59
64
 
60
65
  if (DEV) {
61
66
  validate_action_return(data);
62
67
  }
63
68
 
64
69
  if (data instanceof ActionFailure) {
70
+ return action_json(
71
+ {
72
+ type: 'failure',
73
+ status: data.status,
74
+ location,
75
+ // @ts-expect-error we assign a string to what is supposed to be an object. That's ok
76
+ // because we don't use the object outside, and this way we have better code navigation
77
+ // through knowing where the related interface is used.
78
+ data: try_serialize(data.data, stringify, /** @type {string} */ (event.route.id))
79
+ },
80
+ {
81
+ status: data.status
82
+ }
83
+ );
84
+ } else if (data) {
65
85
  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
- )
86
+ type: 'success',
87
+ status: 200,
88
+ location,
89
+ // @ts-expect-error see comment above
90
+ data: try_serialize(data, stringify, /** @type {string} */ (event.route.id))
76
91
  });
77
92
  } else {
78
93
  return action_json({
79
94
  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
- )
95
+ status: 204,
96
+ location
87
97
  });
88
98
  }
89
99
  } catch (e) {
@@ -93,23 +103,42 @@ export async function handle_action_json_request(event, event_state, options, se
93
103
  return action_json_redirect(err);
94
104
  }
95
105
 
106
+ const transformed = await handle_error_and_jsonify(
107
+ event,
108
+ state,
109
+ options,
110
+ check_incorrect_fail_use(err)
111
+ );
112
+
96
113
  return action_json(
97
114
  {
98
115
  type: 'error',
99
- error: await handle_error_and_jsonify(
100
- event,
101
- event_state,
102
- options,
103
- check_incorrect_fail_use(err)
104
- )
116
+ error: transformed,
117
+ location
105
118
  },
106
119
  {
107
- status: get_status(err)
120
+ status: transformed.status
108
121
  }
109
122
  );
110
123
  }
111
124
  }
112
125
 
126
+ /**
127
+ * @param {URL} url
128
+ */
129
+ export function get_action_location(url) {
130
+ const location = new URL(url);
131
+
132
+ for (const key of location.searchParams.keys()) {
133
+ if (key.startsWith('/')) {
134
+ location.searchParams.delete(key);
135
+ break;
136
+ }
137
+ }
138
+
139
+ return location.pathname + location.search;
140
+ }
141
+
113
142
  /**
114
143
  * @param {HttpError | Error} error
115
144
  */
@@ -135,7 +164,7 @@ export function action_json_redirect(redirect) {
135
164
  * @param {ResponseInit} [init]
136
165
  */
137
166
  function action_json(data, init) {
138
- return json(data, init);
167
+ return with_version_header(json(data, init));
139
168
  }
140
169
 
141
170
  /**
@@ -147,12 +176,13 @@ export function is_action_request(event) {
147
176
 
148
177
  /**
149
178
  * @param {RequestEvent} event
150
- * @param {import('types').RequestState} event_state
179
+ * @param {import('types').RequestState} state
151
180
  * @param {SSRNode['server'] | undefined} server
152
181
  * @returns {Promise<ActionResult>}
153
182
  */
154
- export async function handle_action_request(event, event_state, server) {
183
+ export async function handle_action_request(event, state, server) {
155
184
  const actions = server?.actions;
185
+ const location = get_action_location(event.url);
156
186
 
157
187
  if (!actions) {
158
188
  // TODO should this be a different error altogether?
@@ -163,6 +193,8 @@ export async function handle_action_request(event, event_state, server) {
163
193
  });
164
194
  return {
165
195
  type: 'error',
196
+ location,
197
+ // We're lying a bit with the types here; this will be transformed into a proper App.Error object later
166
198
  error: new SvelteKitError(
167
199
  405,
168
200
  'Method Not Allowed',
@@ -174,7 +206,7 @@ export async function handle_action_request(event, event_state, server) {
174
206
  check_named_default_separate(actions);
175
207
 
176
208
  try {
177
- const data = await call_action(event, event_state, actions);
209
+ const data = await call_action(event, state, actions);
178
210
 
179
211
  if (DEV) {
180
212
  validate_action_return(data);
@@ -184,12 +216,14 @@ export async function handle_action_request(event, event_state, server) {
184
216
  return {
185
217
  type: 'failure',
186
218
  status: data.status,
219
+ location,
187
220
  data: data.data
188
221
  };
189
222
  } else {
190
223
  return {
191
224
  type: 'success',
192
225
  status: 200,
226
+ location,
193
227
  // @ts-expect-error this will be removed upon serialization, so `undefined` is the same as omission
194
228
  data
195
229
  };
@@ -207,6 +241,8 @@ export async function handle_action_request(event, event_state, server) {
207
241
 
208
242
  return {
209
243
  type: 'error',
244
+ location,
245
+ // @ts-expect-error We're lying a bit with the types here; this will be transformed into a proper App.Error object later
210
246
  error: check_incorrect_fail_use(err)
211
247
  };
212
248
  }
@@ -225,11 +261,11 @@ function check_named_default_separate(actions) {
225
261
 
226
262
  /**
227
263
  * @param {RequestEvent} event
228
- * @param {import('types').RequestState} event_state
264
+ * @param {import('types').RequestState} state
229
265
  * @param {NonNullable<ServerNode['actions']>} actions
230
266
  * @throws {Redirect | HttpError | SvelteKitError | Error}
231
267
  */
232
- async function call_action(event, event_state, actions) {
268
+ async function call_action(event, state, actions) {
233
269
  const url = new URL(event.request.url);
234
270
 
235
271
  let name = 'default';
@@ -243,11 +279,12 @@ async function call_action(event, event_state, actions) {
243
279
  }
244
280
  }
245
281
 
246
- const action = actions[name];
247
- if (!action) {
282
+ if (!Object.hasOwn(actions, name)) {
248
283
  throw new SvelteKitError(404, 'Not Found', `No action with name '${name}' found`);
249
284
  }
250
285
 
286
+ const action = actions[name];
287
+
251
288
  if (!is_form_content_type(event.request)) {
252
289
  throw new SvelteKitError(
253
290
  415,
@@ -267,7 +304,7 @@ async function call_action(event, event_state, actions) {
267
304
  fn: async (current) => {
268
305
  const traced_event = merge_tracing(event, current);
269
306
 
270
- const result = await with_request_store({ event: traced_event, state: event_state }, () =>
307
+ const result = await with_request_store({ event: traced_event, state }, () =>
271
308
  action(traced_event)
272
309
  );
273
310
 
@@ -298,26 +335,9 @@ function validate_action_return(data) {
298
335
  * Try to `devalue.uneval` the data object, and if it fails, return a proper Error with context
299
336
  * @param {any} data
300
337
  * @param {string} route_id
301
- * @param {ServerHooks['transport']} transport
302
338
  */
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);
339
+ export function uneval_action_response(data, route_id) {
340
+ return try_serialize(data, uneval, route_id);
321
341
  }
322
342
 
323
343
  /**
@@ -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
  }