@jsenv/core 25.0.1 → 25.2.1

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 (63) hide show
  1. package/dist/browser_runtime/browser_runtime_91c5a3b8.js.map +2 -2
  2. package/dist/build_manifest.js +4 -4
  3. package/dist/compile_proxy/asset-manifest.json +2 -2
  4. package/dist/compile_proxy/{compile_proxy_e3b0c442_809f35f7.js.map → compile_proxy.html__inline__20_809f35f7.js.map} +0 -0
  5. package/dist/compile_proxy/{compile_proxy_7ad5faa6.html → compile_proxy_8dfaee51.html} +3 -4
  6. package/dist/redirector/asset-manifest.json +2 -2
  7. package/dist/redirector/{redirector_e3b0c442_e391410e.js.map → redirector.html__inline__15_e391410e.js.map} +0 -0
  8. package/dist/redirector/{redirector_eb92e8a7.html → redirector_3e9a97b9.html} +3 -4
  9. package/dist/toolbar/asset-manifest.json +1 -1
  10. package/dist/toolbar/{toolbar_f7b8a263.html → toolbar_361afb84.html} +2 -3
  11. package/dist/toolbar_injector/asset-manifest.json +2 -2
  12. package/dist/toolbar_injector/{toolbar_injector_49e4756e.js → toolbar_injector_fac1e995.js} +2 -2
  13. package/dist/toolbar_injector/{toolbar_injector_49e4756e.js.map → toolbar_injector_fac1e995.js.map} +2 -2
  14. package/package.json +9 -10
  15. package/readme.md +60 -51
  16. package/src/buildProject.js +21 -13
  17. package/src/commonJsToJavaScriptModule.js +8 -7
  18. package/src/dev_server.js +2 -0
  19. package/src/execute.js +2 -0
  20. package/src/executeTestPlan.js +14 -0
  21. package/src/internal/building/buildUsingRollup.js +4 -2
  22. package/src/internal/building/build_stats.js +3 -0
  23. package/src/internal/building/build_url_generator.js +153 -0
  24. package/src/internal/building/css/parseCssRessource.js +32 -26
  25. package/src/internal/building/html/parseHtmlRessource.js +109 -91
  26. package/src/internal/building/js/parseJsRessource.js +5 -13
  27. package/src/internal/building/parseRessource.js +3 -0
  28. package/src/internal/building/ressource_builder.js +72 -64
  29. package/src/internal/building/ressource_builder_util.js +17 -5
  30. package/src/internal/building/rollup_plugin_jsenv.js +262 -189
  31. package/src/internal/building/url_fetcher.js +16 -7
  32. package/src/internal/building/url_loader.js +1 -5
  33. package/src/internal/building/url_versioning.js +0 -173
  34. package/src/internal/compiling/babel_plugin_import_metadata.js +7 -11
  35. package/src/internal/compiling/babel_plugin_proxy_external_imports.js +31 -0
  36. package/src/internal/compiling/compile-directory/compile-asset.js +8 -4
  37. package/src/internal/compiling/compile-directory/getOrGenerateCompiledFile.js +43 -8
  38. package/src/internal/compiling/compile-directory/updateMeta.js +4 -8
  39. package/src/internal/compiling/compile-directory/validateCache.js +1 -2
  40. package/src/internal/compiling/compileFile.js +22 -10
  41. package/src/internal/compiling/compileHtml.js +15 -28
  42. package/src/internal/compiling/createCompiledFileService.js +22 -24
  43. package/src/internal/compiling/html_source_file_service.js +18 -19
  44. package/src/internal/compiling/js-compilation-service/babelHelper.js +10 -13
  45. package/src/internal/compiling/js-compilation-service/babel_plugin_babel_helpers_as_jsenv_imports.js +4 -2
  46. package/src/internal/compiling/js-compilation-service/jsenvTransform.js +16 -7
  47. package/src/internal/compiling/js-compilation-service/transformJs.js +9 -5
  48. package/src/internal/compiling/jsenvCompilerForHtml.js +536 -262
  49. package/src/internal/compiling/jsenvCompilerForJavaScript.js +15 -11
  50. package/src/internal/compiling/startCompileServer.js +83 -20
  51. package/src/internal/compiling/transformResultToCompilationResult.js +47 -25
  52. package/src/internal/executing/executePlan.js +2 -0
  53. package/src/internal/fetchUrl.js +3 -2
  54. package/src/internal/integrity/integrity_algorithms.js +26 -0
  55. package/src/internal/integrity/integrity_parsing.js +50 -0
  56. package/src/internal/integrity/integrity_update.js +23 -0
  57. package/src/internal/integrity/integrity_validation.js +49 -0
  58. package/src/internal/jsenvCoreDirectoryUrl.js +2 -0
  59. package/src/internal/jsenv_remote_directory.js +156 -0
  60. package/src/internal/origin_directory_converter.js +62 -0
  61. package/src/internal/response_validation.js +11 -24
  62. package/src/internal/sourceMappingURLUtils.js +10 -0
  63. package/src/internal/url_conversion.js +1 -0
@@ -1,12 +1,13 @@
1
+ import { generateSourcemapUrl } from "@jsenv/core/src/internal/sourceMappingURLUtils.js"
2
+
1
3
  import { transformJs } from "./js-compilation-service/transformJs.js"
2
4
  import { transformResultToCompilationResult } from "./transformResultToCompilationResult.js"
3
5
 
4
6
  export const compileJavascript = async ({
5
- code,
6
- map,
7
+ projectDirectoryUrl,
8
+ jsenvRemoteDirectory,
7
9
  url,
8
10
  compiledUrl,
9
- projectDirectoryUrl,
10
11
 
11
12
  babelPluginMap,
12
13
  workerUrls,
@@ -16,6 +17,8 @@ export const compileJavascript = async ({
16
17
  topLevelAwait,
17
18
  prependSystemJs,
18
19
 
20
+ code,
21
+ map,
19
22
  sourcemapExcludeSources,
20
23
  sourcemapMethod,
21
24
  }) => {
@@ -23,38 +26,39 @@ export const compileJavascript = async ({
23
26
  prependSystemJs =
24
27
  workerUrls.includes(url) || serviceWorkerUrls.includes(url)
25
28
  }
26
-
27
29
  const transformResult = await transformJs({
28
- code,
29
- map,
30
+ projectDirectoryUrl,
31
+ jsenvRemoteDirectory,
30
32
  url,
31
33
  compiledUrl,
32
- projectDirectoryUrl,
33
34
 
34
35
  babelPluginMap,
35
36
  moduleOutFormat,
36
37
  importMetaFormat,
37
38
  topLevelAwait,
38
39
  prependSystemJs,
39
- })
40
40
 
41
+ code,
42
+ map,
43
+ })
41
44
  return transformResultToCompilationResult(
42
45
  {
43
46
  contentType: "application/javascript",
47
+ metadata: transformResult.metadata,
44
48
  code: transformResult.code,
45
49
  map: transformResult.map,
46
- metadata: transformResult.metadata,
47
50
  },
48
51
  {
49
52
  projectDirectoryUrl,
50
- originalFileContent: code,
53
+ jsenvRemoteDirectory,
51
54
  originalFileUrl: url,
52
55
  compiledFileUrl: compiledUrl,
53
56
  // sourcemap are not inside the asset folder because
54
57
  // of https://github.com/microsoft/vscode-chrome-debug-core/issues/544
55
- sourcemapFileUrl: `${compiledUrl}.map`,
58
+ sourcemapFileUrl: generateSourcemapUrl(compiledUrl),
56
59
  sourcemapExcludeSources,
57
60
  sourcemapMethod,
61
+ originalFileContent: code,
58
62
  },
59
63
  )
60
64
  }
@@ -36,14 +36,19 @@ import {
36
36
  } from "@jsenv/core/dist/build_manifest.js"
37
37
  import { generateGroupMap } from "@jsenv/core/src/internal/generateGroupMap/generateGroupMap.js"
38
38
  import { isBrowserPartOfSupportedRuntimes } from "@jsenv/core/src/internal/generateGroupMap/runtime_support.js"
39
- import { loadBabelPluginMapFromFile } from "./load_babel_plugin_map_from_file.js"
40
- import { extractSyntaxBabelPluginMap } from "./babel_plugins.js"
39
+
40
+ import { createJsenvRemoteDirectory } from "../jsenv_remote_directory.js"
41
41
  import {
42
42
  sourcemapMainFileInfo,
43
43
  sourcemapMappingFileInfo,
44
44
  } from "../jsenvInternalFiles.js"
45
- import { jsenvCoreDirectoryUrl } from "../jsenvCoreDirectoryUrl.js"
46
45
  import { babelPluginReplaceExpressions } from "../babel_plugin_replace_expressions.js"
46
+ import {
47
+ jsenvCoreDirectoryUrl,
48
+ jsenvDistDirectoryUrl,
49
+ } from "../jsenvCoreDirectoryUrl.js"
50
+ import { loadBabelPluginMapFromFile } from "./load_babel_plugin_map_from_file.js"
51
+ import { extractSyntaxBabelPluginMap } from "./babel_plugins.js"
47
52
  import { babelPluginGlobalThisAsJsenvImport } from "./babel_plugin_global_this_as_jsenv_import.js"
48
53
  import { babelPluginNewStylesheetAsJsenvImport } from "./babel_plugin_new_stylesheet_as_jsenv_import.js"
49
54
  import { babelPluginImportAssertions } from "./babel_plugin_import_assertions.js"
@@ -95,6 +100,7 @@ export const startCompileServer = async ({
95
100
  babelPluginMap,
96
101
  babelConfigFileUrl,
97
102
  customCompilers = {},
103
+ preservedUrls,
98
104
  workers = [],
99
105
  serviceWorkers = [],
100
106
  importMapInWebWorkers = false,
@@ -143,6 +149,30 @@ export const startCompileServer = async ({
143
149
  )
144
150
  const logger = createLogger({ logLevel })
145
151
 
152
+ preservedUrls = {
153
+ // Authorize jsenv to modify any file url
154
+ // because the goal is to build the files into chunks
155
+ "file://": false,
156
+ // Preserves http and https urls
157
+ // because if code specifiy a CDN url it's usually because code wants
158
+ // to keep the url intact and keep HTTP request to CDN (both in dev and prod)
159
+ "http://": true,
160
+ "https://": true,
161
+ /*
162
+ * It's possible to selectively overrides the behaviour above:
163
+ * 1. The CDN file needs to be transformed to be executable in dev, build or both
164
+ * preservedUrls: {"https://cdn.skypack.dev/preact@10.6.4": false}
165
+ * 2. No strong need to preserve the CDN dependency
166
+ * 3. Prevent concatenation of a file during build
167
+ * preservedUrls: {"./file.js": false}
168
+ */
169
+ ...preservedUrls,
170
+ }
171
+ const jsenvRemoteDirectory = createJsenvRemoteDirectory({
172
+ projectDirectoryUrl,
173
+ jsenvDirectoryRelativeUrl,
174
+ preservedUrls,
175
+ })
146
176
  const workerUrls = workers.map((worker) =>
147
177
  resolveUrl(worker, projectDirectoryUrl),
148
178
  )
@@ -287,11 +317,18 @@ export const startCompileServer = async ({
287
317
  projectDirectoryUrl,
288
318
  jsenvDirectoryRelativeUrl,
289
319
  outDirectoryRelativeUrl,
320
+ jsenvRemoteDirectory,
290
321
  importDefaultExtension,
322
+
323
+ preservedUrls,
324
+ workers,
325
+ serviceWorkers,
291
326
  compileServerGroupMap,
327
+ babelPluginMap,
328
+ replaceProcessEnvNodeEnv,
329
+ processEnvNodeEnv,
292
330
  env,
293
331
  inlineImportMapIntoHTML,
294
- babelPluginMap,
295
332
  customCompilers,
296
333
  jsenvToolbarInjection,
297
334
  sourcemapMethod,
@@ -325,7 +362,9 @@ export const startCompileServer = async ({
325
362
  logger,
326
363
 
327
364
  projectDirectoryUrl,
365
+ jsenvDirectoryRelativeUrl,
328
366
  outDirectoryRelativeUrl,
367
+ jsenvRemoteDirectory,
329
368
 
330
369
  importDefaultExtension,
331
370
 
@@ -366,6 +405,7 @@ export const startCompileServer = async ({
366
405
  : {}),
367
406
  "service:source file": createSourceFileService({
368
407
  projectDirectoryUrl,
408
+ jsenvRemoteDirectory,
369
409
  projectFileRequestedCallback,
370
410
  projectFileCacheStrategy,
371
411
  }),
@@ -421,6 +461,7 @@ export const startCompileServer = async ({
421
461
  ...compileServer,
422
462
  compileServerGroupMap,
423
463
  babelPluginMap,
464
+ preservedUrls,
424
465
  projectFileRequestedCallback,
425
466
  }
426
467
  }
@@ -936,30 +977,45 @@ const createSourceFileService = ({
936
977
  projectDirectoryUrl,
937
978
  projectFileRequestedCallback,
938
979
  projectFileCacheStrategy,
980
+ jsenvRemoteDirectory,
939
981
  }) => {
940
982
  return async (request) => {
941
983
  const relativeUrl = request.pathname.slice(1)
942
984
  projectFileRequestedCallback(relativeUrl, request)
943
-
944
985
  const fileUrl = new URL(request.ressource.slice(1), projectDirectoryUrl)
945
986
  .href
946
987
  const fileIsInsideJsenvDistDirectory = urlIsInsideOf(
947
988
  fileUrl,
948
- new URL("./dist/", jsenvCoreDirectoryUrl),
989
+ jsenvDistDirectoryUrl,
949
990
  )
950
-
951
- const responsePromise = fetchFileSystem(fileUrl, {
952
- headers: request.headers,
953
- etagEnabled: projectFileCacheStrategy === "etag",
954
- mtimeEnabled: projectFileCacheStrategy === "mtime",
955
- ...(fileIsInsideJsenvDistDirectory
956
- ? {
957
- cacheControl: `private,max-age=${60 * 60 * 24 * 30},immutable`,
958
- }
959
- : {}),
960
- })
961
-
962
- return responsePromise
991
+ const fromFileSystem = () =>
992
+ fetchFileSystem(fileUrl, {
993
+ headers: request.headers,
994
+ etagEnabled: projectFileCacheStrategy === "etag",
995
+ mtimeEnabled: projectFileCacheStrategy === "mtime",
996
+ ...(fileIsInsideJsenvDistDirectory
997
+ ? {
998
+ cacheControl: `private,max-age=${60 * 60 * 24 * 30},immutable`,
999
+ }
1000
+ : {}),
1001
+ })
1002
+ const filesystemResponse = await fromFileSystem()
1003
+ if (
1004
+ filesystemResponse.status === 404 &&
1005
+ jsenvRemoteDirectory.isFileUrlForRemoteUrl(fileUrl)
1006
+ ) {
1007
+ try {
1008
+ await jsenvRemoteDirectory.loadFileUrlFromRemote(fileUrl, request)
1009
+ // re-fetch filesystem instead to ensure response headers are correct
1010
+ return fromFileSystem()
1011
+ } catch (e) {
1012
+ if (e && e.asResponse) {
1013
+ return e.asResponse()
1014
+ }
1015
+ throw e
1016
+ }
1017
+ }
1018
+ return filesystemResponse
963
1019
  }
964
1020
  }
965
1021
 
@@ -968,6 +1024,10 @@ const createCompileServerMetaFileInfo = ({
968
1024
  jsenvDirectoryRelativeUrl,
969
1025
  outDirectoryRelativeUrl,
970
1026
  importDefaultExtension,
1027
+
1028
+ preservedUrls,
1029
+ workers,
1030
+ serviceWorkers,
971
1031
  compileServerGroupMap,
972
1032
  babelPluginMap,
973
1033
  replaceProcessEnvNodeEnv,
@@ -1013,6 +1073,9 @@ const createCompileServerMetaFileInfo = ({
1013
1073
  outDirectoryRelativeUrl,
1014
1074
  importDefaultExtension,
1015
1075
 
1076
+ preservedUrls,
1077
+ workers,
1078
+ serviceWorkers,
1016
1079
  babelPluginMap: babelPluginMapAsData(babelPluginMap),
1017
1080
  compileServerGroupMap,
1018
1081
  customCompilerPatterns,
@@ -1026,7 +1089,7 @@ const createCompileServerMetaFileInfo = ({
1026
1089
  sourcemapMappingFileRelativeUrl,
1027
1090
  errorStackRemapping: true,
1028
1091
 
1029
- // used to consider the logic generating files may have changed
1092
+ // used to consider logic generating files may have changed
1030
1093
  jsenvCorePackageVersion,
1031
1094
 
1032
1095
  // impact only HTML files
@@ -3,6 +3,7 @@ import {
3
3
  urlToRelativeUrl,
4
4
  readFile,
5
5
  ensureWindowsDriveLetter,
6
+ urlIsInsideOf,
6
7
  } from "@jsenv/filesystem"
7
8
 
8
9
  import {
@@ -15,16 +16,16 @@ import {
15
16
  setCssSourceMappingUrl,
16
17
  sourcemapToBase64Url,
17
18
  } from "../sourceMappingURLUtils.js"
18
- import { generateCompiledFileAssetUrl } from "./compile-directory/compile-asset.js"
19
+ import { generateCompilationAssetUrl } from "./compile-directory/compile-asset.js"
19
20
  import { testFilePresence } from "./compile-directory/fs-optimized-for-cache.js"
20
21
 
21
22
  const isWindows = process.platform === "win32"
22
23
 
23
24
  export const transformResultToCompilationResult = async (
24
- { contentType, code, map, metadata = {} },
25
+ { contentType, metadata = {}, code, map },
25
26
  {
26
27
  projectDirectoryUrl,
27
- originalFileContent,
28
+ jsenvRemoteDirectory,
28
29
  originalFileUrl,
29
30
  compiledFileUrl,
30
31
  sourcemapFileUrl,
@@ -33,9 +34,13 @@ export const transformResultToCompilationResult = async (
33
34
  // it also means client have to fetch source from server (additional http request)
34
35
  // some client ignore sourcesContent property such as vscode-chrome-debugger
35
36
  // Because it's the most complex scenario and we want to ensure client is always able
36
- // to find source from the sourcemap, we remove map.sourcesContent by default to test this.
37
- sourcemapExcludeSources = true,
37
+ // to find source from the sourcemap, it's a good idea
38
+ // to exclude sourcesContent from sourcemap.
39
+ // However some ressource are abstract and it means additional http request for the browser.
40
+ // For these reasons it's simpler to keep source content in sourcemap.
41
+ sourcemapExcludeSources = false,
38
42
  sourcemapMethod = "comment", // "comment", "inline"
43
+ originalFileContent,
39
44
  },
40
45
  ) => {
41
46
  if (typeof contentType !== "string") {
@@ -71,6 +76,14 @@ export const transformResultToCompilationResult = async (
71
76
  const sourcesContent = []
72
77
  const assets = []
73
78
  const assetsContent = []
79
+ const addSource = ({ url, content }) => {
80
+ sources.push(url)
81
+ sourcesContent.push(content)
82
+ }
83
+ const addAsset = ({ url, content }) => {
84
+ assets.push(url)
85
+ assetsContent.push(content)
86
+ }
74
87
 
75
88
  let output = code
76
89
  if (sourcemapEnabled && map) {
@@ -78,8 +91,10 @@ export const transformResultToCompilationResult = async (
78
91
  // may happen in some cases where babel returns a wrong sourcemap
79
92
  // there is at least one case where it happens
80
93
  // a file with only import './whatever.js' inside
81
- sources.push(originalFileUrl)
82
- sourcesContent.push(originalFileContent)
94
+ addSource({
95
+ url: originalFileUrl,
96
+ content: originalFileContent,
97
+ })
83
98
  } else {
84
99
  map.sources.forEach((source, index) => {
85
100
  const sourceFileUrl = resolveSourceFile({
@@ -90,19 +105,25 @@ export const transformResultToCompilationResult = async (
90
105
  projectDirectoryUrl,
91
106
  })
92
107
  if (sourceFileUrl) {
93
- map.sources[index] = urlToRelativeUrl(sourceFileUrl, sourcemapFileUrl)
108
+ // In case the file comes from a remote url
109
+ // we prefer to consider remote url as the real source for this code
110
+ map.sources[index] =
111
+ jsenvRemoteDirectory &&
112
+ jsenvRemoteDirectory.isFileUrlForRemoteUrl(sourceFileUrl)
113
+ ? jsenvRemoteDirectory.remoteUrlFromFileUrl(sourceFileUrl)
114
+ : urlToRelativeUrl(sourceFileUrl, sourcemapFileUrl)
94
115
  sources[index] = sourceFileUrl
95
116
  }
96
117
  })
97
-
98
118
  if (sources.length === 0) {
99
119
  // happens when sourcemap is generated by webpack and looks like
100
120
  // webpack://Package./src/file.js
101
121
  // in that case we'll don't know how to find the source file
102
- sources.push(originalFileUrl)
103
- sourcesContent.push(originalFileContent)
122
+ addSource({
123
+ url: originalFileUrl,
124
+ content: originalFileContent,
125
+ })
104
126
  }
105
-
106
127
  await Promise.all(
107
128
  sources.map(async (sourceUrl, index) => {
108
129
  const contentFromSourcemap = map.sourcesContent
@@ -121,7 +142,6 @@ export const transformResultToCompilationResult = async (
121
142
  if (sourcemapExcludeSources) {
122
143
  delete map.sourcesContent
123
144
  }
124
-
125
145
  // we don't need sourceRoot because our path are relative or absolute to the current location
126
146
  // we could comment this line because it is not set by babel because not passed during transform
127
147
  delete map.sourceRoot
@@ -139,22 +159,28 @@ export const transformResultToCompilationResult = async (
139
159
  compiledFileUrl,
140
160
  )
141
161
  output = setSourceMappingUrl(output, sourcemapFileRelativePathForModule)
142
- assets.push(sourcemapFileUrl)
143
- assetsContent.push(stringifyMap(map))
162
+ addAsset({
163
+ url: sourcemapFileUrl,
164
+ content: stringifyMap(map),
165
+ })
144
166
  }
145
167
  } else {
146
- sources.push(originalFileUrl)
147
- sourcesContent.push(originalFileContent)
168
+ addSource({
169
+ url: originalFileUrl,
170
+ content: originalFileContent,
171
+ })
148
172
  }
149
173
 
150
174
  const { coverage } = metadata
151
175
  if (coverage) {
152
- const coverageAssetFileUrl = generateCompiledFileAssetUrl(
176
+ const coverageAssetFileUrl = generateCompilationAssetUrl(
153
177
  compiledFileUrl,
154
178
  "coverage.json",
155
179
  )
156
- assets.push(coverageAssetFileUrl)
157
- assetsContent.push(stringifyCoverage(coverage))
180
+ addAsset({
181
+ url: coverageAssetFileUrl,
182
+ content: stringifyCoverage(coverage),
183
+ })
158
184
  }
159
185
 
160
186
  const { dependencies = [] } = metadata
@@ -179,18 +205,15 @@ const resolveSourceFile = ({
179
205
  projectDirectoryUrl,
180
206
  }) => {
181
207
  const sourceFileUrl = resolveSourceUrl({ source, sourcemapFileUrl })
182
-
183
- if (!sourceFileUrl.startsWith(projectDirectoryUrl)) {
208
+ if (!urlIsInsideOf(sourceFileUrl, projectDirectoryUrl)) {
184
209
  // do not track dependency outside project
185
210
  // it means cache stays valid for those external sources
186
211
  return null
187
212
  }
188
-
189
213
  const fileFound = testFilePresence(sourceFileUrl)
190
214
  if (fileFound) {
191
215
  return sourceFileUrl
192
216
  }
193
-
194
217
  // prefer original source file
195
218
  const relativeUrl = urlToRelativeUrl(sourceFileUrl, compiledFileUrl)
196
219
  const originalSourceUrl = resolveUrl(relativeUrl, originalFileUrl)
@@ -211,7 +234,6 @@ const resolveSourceUrl = ({ source, sourcemapFileUrl }) => {
211
234
  )
212
235
  return ensureWindowsDriveLetter(url, sourcemapFileUrl)
213
236
  }
214
-
215
237
  return resolveUrl(source, sourcemapFileUrl)
216
238
  }
217
239
 
@@ -51,6 +51,7 @@ export const executePlan = async (
51
51
  compileServerCanWriteOnFilesystem,
52
52
  babelPluginMap,
53
53
  babelConfigFileUrl,
54
+ preservedUrls,
54
55
  workers,
55
56
  serviceWorkers,
56
57
  importMapInWebWorkers,
@@ -126,6 +127,7 @@ export const executePlan = async (
126
127
  keepProcessAlive: true, // to be sure it stays alive
127
128
  babelPluginMap,
128
129
  babelConfigFileUrl,
130
+ preservedUrls,
129
131
  workers,
130
132
  serviceWorkers,
131
133
  importMapInWebWorkers,
@@ -13,9 +13,9 @@ export const fetchUrl = async (
13
13
  ignoreHttpsError,
14
14
  ...rest,
15
15
  })
16
-
17
- return {
16
+ const responseObject = {
18
17
  url: response.url,
18
+ type: "default",
19
19
  status: response.status,
20
20
  statusText: response.statusText,
21
21
  headers: headersToObject(response.headers),
@@ -24,4 +24,5 @@ export const fetchUrl = async (
24
24
  blob: response.blob.bind(response),
25
25
  arrayBuffer: response.arrayBuffer.bind(response),
26
26
  }
27
+ return responseObject
27
28
  }
@@ -0,0 +1,26 @@
1
+ import crypto from "node:crypto"
2
+
3
+ export const isSupportedAlgorithm = (algo) => {
4
+ return SUPPORTED_ALGORITHMS.includes(algo)
5
+ }
6
+
7
+ // https://www.w3.org/TR/SRI/#priority
8
+ export const getPrioritizedHashFunction = (firstAlgo, secondAlgo) => {
9
+ const firstIndex = SUPPORTED_ALGORITHMS.indexOf(firstAlgo)
10
+ const secondIndex = SUPPORTED_ALGORITHMS.indexOf(secondAlgo)
11
+ if (firstIndex === secondIndex) {
12
+ return ""
13
+ }
14
+ if (firstIndex < secondIndex) {
15
+ return secondAlgo
16
+ }
17
+ return firstAlgo
18
+ }
19
+
20
+ export const applyAlgoToRepresentationData = (algo, data) => {
21
+ const base64Value = crypto.createHash(algo).update(data).digest("base64")
22
+ return base64Value
23
+ }
24
+
25
+ // keep this ordered by collision resistance as it is also used by "getPrioritizedHashFunction"
26
+ const SUPPORTED_ALGORITHMS = ["sha256", "sha384", "sha512"]
@@ -0,0 +1,50 @@
1
+ import { isSupportedAlgorithm } from "./integrity_algorithms.js"
2
+
3
+ // see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
4
+ export const parseIntegrity = (string) => {
5
+ const integrityMetadata = {}
6
+ string
7
+ .trim()
8
+ .split(/\s+/)
9
+ .forEach((token) => {
10
+ const { isValid, algo, base64Value, optionExpression } =
11
+ parseAsHashWithOptions(token)
12
+ if (!isValid) {
13
+ return
14
+ }
15
+ if (!isSupportedAlgorithm(algo)) {
16
+ return
17
+ }
18
+ const metadataList = integrityMetadata[algo]
19
+ const metadata = { base64Value, optionExpression }
20
+ integrityMetadata[algo] = metadataList
21
+ ? [...metadataList, metadata]
22
+ : [metadata]
23
+ })
24
+ return integrityMetadata
25
+ }
26
+
27
+ // see https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute
28
+ const parseAsHashWithOptions = (token) => {
29
+ const dashIndex = token.indexOf("-")
30
+ if (dashIndex === -1) {
31
+ return { isValid: false }
32
+ }
33
+ const beforeDash = token.slice(0, dashIndex)
34
+ const afterDash = token.slice(dashIndex + 1)
35
+ const questionIndex = afterDash.indexOf("?")
36
+ const algo = beforeDash
37
+ if (questionIndex === -1) {
38
+ const base64Value = afterDash
39
+ const isValid = BASE64_REGEX.test(afterDash)
40
+ return { isValid, algo, base64Value }
41
+ }
42
+ const base64Value = afterDash.slice(0, questionIndex)
43
+ const optionExpression = afterDash.slice(questionIndex + 1)
44
+ const isValid =
45
+ BASE64_REGEX.test(afterDash) && VCHAR_REGEX.test(optionExpression)
46
+ return { isValid, algo, base64Value, optionExpression }
47
+ }
48
+
49
+ const BASE64_REGEX = /^[A-Za-z0-9+\/=+]+$/
50
+ const VCHAR_REGEX = /^[\x21-\x7E]+$/
@@ -0,0 +1,23 @@
1
+ import { parseIntegrity } from "./integrity_parsing.js"
2
+ import {
3
+ getPrioritizedHashFunction,
4
+ applyAlgoToRepresentationData,
5
+ } from "./integrity_algorithms.js"
6
+
7
+ export const updateIntegrity = (integrity, representationData) => {
8
+ const integrityMetadata = parseIntegrity(integrity)
9
+ const algos = Object.keys(integrityMetadata)
10
+ if (algos.length === 0) {
11
+ return ""
12
+ }
13
+ let strongestAlgo = algos[0]
14
+ algos.slice(1).forEach((algoCandidate) => {
15
+ strongestAlgo =
16
+ getPrioritizedHashFunction(strongestAlgo, algoCandidate) || strongestAlgo
17
+ })
18
+ const base64Value = applyAlgoToRepresentationData(
19
+ strongestAlgo,
20
+ representationData,
21
+ )
22
+ return `${strongestAlgo}-${base64Value}`
23
+ }
@@ -0,0 +1,49 @@
1
+ import { parseIntegrity } from "./integrity_parsing.js"
2
+ import {
3
+ getPrioritizedHashFunction,
4
+ applyAlgoToRepresentationData,
5
+ } from "./integrity_algorithms.js"
6
+
7
+ // https://www.w3.org/TR/SRI/#does-response-match-metadatalist
8
+ export const validateResponseIntegrity = (
9
+ { url, type, dataRepresentation },
10
+ integrity,
11
+ ) => {
12
+ if (!isResponseEligibleForIntegrityValidation({ type })) {
13
+ return false
14
+ }
15
+ const integrityMetadata = parseIntegrity(integrity)
16
+ const algos = Object.keys(integrityMetadata)
17
+ if (algos.length === 0) {
18
+ return true
19
+ }
20
+ let strongestAlgo = algos[0]
21
+ algos.slice(1).forEach((algoCandidate) => {
22
+ strongestAlgo =
23
+ getPrioritizedHashFunction(strongestAlgo, algoCandidate) || strongestAlgo
24
+ })
25
+ const metadataList = integrityMetadata[strongestAlgo]
26
+ const actualBase64Value = applyAlgoToRepresentationData(
27
+ strongestAlgo,
28
+ dataRepresentation,
29
+ )
30
+ const acceptedBase64Values = metadataList.map(
31
+ (metadata) => metadata.base64Value,
32
+ )
33
+ const someIsMatching = acceptedBase64Values.includes(actualBase64Value)
34
+ if (someIsMatching) {
35
+ return true
36
+ }
37
+ const error = new Error(
38
+ `Integrity validation failed for ressource "${url}". The integrity found for this ressource is "${strongestAlgo}-${actualBase64Value}"`,
39
+ )
40
+ error.code = "EINTEGRITY"
41
+ error.algorithm = strongestAlgo
42
+ error.found = actualBase64Value
43
+ throw error
44
+ }
45
+
46
+ // https://www.w3.org/TR/SRI/#is-response-eligible-for-integrity-validation
47
+ const isResponseEligibleForIntegrityValidation = (response) => {
48
+ return ["basic", "cors", "default"].includes(response.type)
49
+ }
@@ -17,4 +17,6 @@ if (typeof __filename === "string") {
17
17
  )
18
18
  }
19
19
 
20
+ export const jsenvDistDirectoryUrl = new URL("./dist/", jsenvCoreDirectoryUrl)
21
+
20
22
  export { jsenvCoreDirectoryUrl }