@softarc/native-federation-orchestrator 4.0.2 → 4.1.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 (25) hide show
  1. package/README.md +9 -8
  2. package/fesm2022/@softarc/native-federation-orchestrator.mjs +238 -79
  3. package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
  4. package/fesm2022/options.mjs +79 -11
  5. package/fesm2022/options.mjs.map +3 -3
  6. package/package.json +1 -1
  7. package/quickstart.mjs +13 -13
  8. package/types/lib/1.domain/import-map/import-map.contract.d.ts +3 -1
  9. package/types/lib/1.domain/remote/remote-info.contract.d.ts +2 -0
  10. package/types/lib/1.domain/remote-entry/manifest.contract.d.ts +5 -1
  11. package/types/lib/2.app/config/host.contract.d.ts +4 -0
  12. package/types/lib/2.app/config/import-map.contract.d.ts +12 -1
  13. package/types/lib/2.app/driver-ports/dynamic-init/flow.contract.d.ts +6 -2
  14. package/types/lib/2.app/driver-ports/dynamic-init/for-getting-remote-entry.port.d.ts +2 -1
  15. package/types/lib/2.app/driver-ports/init/flow.contract.d.ts +2 -2
  16. package/types/lib/2.app/driver-ports/init/for-getting-remote-entries.port.d.ts +2 -2
  17. package/types/lib/2.app/driving-ports/for-providing-manifest.port.d.ts +4 -2
  18. package/types/lib/2.app/driving-ports/for-providing-remote-entries.port.d.ts +3 -1
  19. package/types/lib/4.config/import-map/replace-in-dom.d.ts +1 -1
  20. package/types/lib/4.config/import-map/trusted-types.d.ts +6 -0
  21. package/types/lib/4.config/import-map/use-default.d.ts +1 -1
  22. package/types/lib/4.config/import-map/use-import-shim.d.ts +1 -1
  23. package/types/lib/index.d.ts +1 -0
  24. package/types/lib/init-federation.d.ts +2 -1
  25. package/types/lib/utils/integrity.d.ts +1 -0
package/README.md CHANGED
@@ -67,7 +67,7 @@ Get up and running in under 2 minutes:
67
67
  </script>
68
68
 
69
69
  <!-- Include the orchestrator runtime -->
70
- <script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.0.2/quickstart.mjs"></script>
70
+ <script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.1.0/quickstart.mjs"></script>
71
71
  </head>
72
72
  <body>
73
73
  <!-- Use your loaded components -->
@@ -85,7 +85,7 @@ Your micro frontends are now loaded and ready to use. The runtime handles the wh
85
85
 
86
86
  ```html
87
87
  <!-- Development and quick testing -->
88
- <script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.0.2/quickstart.mjs"></script>
88
+ <script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.1.0/quickstart.mjs"></script>
89
89
  ```
90
90
 
91
91
  ## Advanced Usage
@@ -120,12 +120,13 @@ const HeaderComponent = await loadRemoteModule('team/mfe2', './Header');
120
120
 
121
121
  ## Documentation
122
122
 
123
- | Guide | Description |
124
- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
125
- | [🚀 Getting Started](https://github.com/native-federation/orchestrator/blob/main/docs/getting-started.md) | Detailed setup instructions and examples |
126
- | [🏗️ Architecture](https://github.com/native-federation/orchestrator/blob/main/docs/architecture.md) | Understanding the native federation domain |
127
- | [⚙️ Configuration](https://github.com/native-federation/orchestrator/blob/main/docs/config.md) | Complete configuration reference |
128
- | [🔄 Version Resolution](https://github.com/native-federation/orchestrator/blob/main/docs/version-resolver.md) | How dependency conflicts are resolved |
123
+ | Guide | Description |
124
+ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
125
+ | [🚀 Getting Started](https://github.com/native-federation/orchestrator/blob/main/docs/getting-started.md) | Detailed setup instructions and examples |
126
+ | [🏗️ Architecture](https://github.com/native-federation/orchestrator/blob/main/docs/architecture.md) | Understanding the native federation domain |
127
+ | [⚙️ Configuration](https://github.com/native-federation/orchestrator/blob/main/docs/config.md) | Complete configuration reference |
128
+ | [🔄 Version Resolution](https://github.com/native-federation/orchestrator/blob/main/docs/version-resolver.md) | How dependency conflicts are resolved |
129
+ | [🔒 Security & Trusted Types](https://github.com/native-federation/orchestrator/blob/main/docs/security.md) | CSP setup and the built-in Trusted Types policy |
129
130
 
130
131
  ## Example repositories
131
132
 
@@ -1278,26 +1278,35 @@ function getScope(path) {
1278
1278
 
1279
1279
  // src/lib/2.app/flows/init/get-remote-entries.ts
1280
1280
  function createGetRemoteEntries(config, ports) {
1281
- return (remotesOrManifestUrl = {}) => ports.manifestProvider.provide(remotesOrManifestUrl).catch((e) => {
1282
- config.log.error(1, "Could not fetch manifest.", e);
1283
- return Promise.reject(new NFError("Failed to fetch manifest"));
1284
- }).then(addHostRemoteEntry).then(fetchRemoteEntries).then(removeSkippedRemotes).then(checkForSSE);
1281
+ return (remotesOrManifestUrl = {}) => {
1282
+ const manifestPromise = config.manifestIntegrity ? ports.manifestProvider.provide(remotesOrManifestUrl, {
1283
+ integrity: config.manifestIntegrity
1284
+ }) : ports.manifestProvider.provide(remotesOrManifestUrl);
1285
+ return manifestPromise.catch((e) => {
1286
+ config.log.error(1, "Could not fetch manifest.", e);
1287
+ return Promise.reject(new NFError("Failed to fetch manifest"));
1288
+ }).then(addHostRemoteEntry).then(fetchRemoteEntries).then(removeSkippedRemotes).then(checkForSSE);
1289
+ };
1285
1290
  function addHostRemoteEntry(manifest) {
1286
1291
  if (!config.hostRemoteEntry) return manifest;
1287
- const { name, url, cacheTag } = config.hostRemoteEntry;
1292
+ const { name, url, cacheTag, integrity } = config.hostRemoteEntry;
1288
1293
  const urlWithCache = cacheTag ? `${url}?cacheTag=${cacheTag}` : url;
1289
1294
  return {
1290
1295
  ...manifest,
1291
- [name]: urlWithCache
1296
+ [name]: integrity ? { url: urlWithCache, integrity } : urlWithCache
1292
1297
  };
1293
1298
  }
1299
+ function normalizeEntry(descriptor) {
1300
+ return typeof descriptor === "string" ? { url: descriptor } : descriptor;
1301
+ }
1294
1302
  async function fetchRemoteEntries(manifest) {
1295
1303
  const fetchPromises = Object.entries(manifest).map(
1296
- ([remoteName, remoteEntryUrl]) => fetchRemoteEntry(remoteName, remoteEntryUrl)
1304
+ ([remoteName, descriptor]) => fetchRemoteEntry(remoteName, descriptor)
1297
1305
  );
1298
1306
  return Promise.all(fetchPromises);
1299
1307
  }
1300
- async function fetchRemoteEntry(remoteName, remoteEntryUrl) {
1308
+ async function fetchRemoteEntry(remoteName, descriptor) {
1309
+ const { url: remoteEntryUrl, integrity } = normalizeEntry(descriptor);
1301
1310
  let isOverride = false;
1302
1311
  let skip = false;
1303
1312
  ports.remoteInfoRepo.tryGet(remoteName).ifPresent((cachedRemoteInfo) => {
@@ -1311,7 +1320,7 @@ function createGetRemoteEntries(config, ports) {
1311
1320
  });
1312
1321
  if (skip) return false;
1313
1322
  try {
1314
- const remoteEntry = await ports.remoteEntryProvider.provide(remoteEntryUrl);
1323
+ const remoteEntry = integrity ? await ports.remoteEntryProvider.provide(remoteEntryUrl, { integrity }) : await ports.remoteEntryProvider.provide(remoteEntryUrl);
1315
1324
  config.log.debug(
1316
1325
  1,
1317
1326
  `Fetched '${remoteEntry.name}' from '${remoteEntry.url}', exposing: ${JSON.stringify(remoteEntry.exposes)}`
@@ -1381,13 +1390,14 @@ function createProcessRemoteEntries(config, ports) {
1381
1390
  ports.scopedExternalsRepo.remove(remoteEntry.name);
1382
1391
  ports.sharedExternalsRepo.removeFromAllScopes(remoteEntry.name);
1383
1392
  }
1384
- function addRemoteInfoToStorage({ name, url, exposes }) {
1393
+ function addRemoteInfoToStorage({ name, url, exposes, integrity }) {
1385
1394
  ports.remoteInfoRepo.addOrUpdate(name, {
1386
1395
  scopeUrl: getScope(url),
1387
1396
  exposes: Object.values(exposes ?? []).map((m) => ({
1388
1397
  moduleName: m.key,
1389
1398
  file: m.outFileName
1390
- }))
1399
+ })),
1400
+ ...integrity ? { integrity } : {}
1391
1401
  });
1392
1402
  }
1393
1403
  function addExternalsToStorage(remoteEntry) {
@@ -1552,7 +1562,7 @@ function createDetermineSharedExternals(config, ports) {
1552
1562
  }
1553
1563
  }
1554
1564
 
1555
- // node_modules/.pnpm/@softarc+native-federation@4.0.0_typescript@5.9.3/node_modules/@softarc/native-federation/src/lib/domain/core/build-notification-options.contract.js
1565
+ // node_modules/.pnpm/@softarc+native-federation@4.1.0_typescript@5.9.3/node_modules/@softarc/native-federation/src/lib/domain/core/build-notification-options.contract.js
1556
1566
  var BuildNotificationType;
1557
1567
  (function(BuildNotificationType2) {
1558
1568
  BuildNotificationType2["COMPLETED"] = "federation-rebuild-complete";
@@ -1560,7 +1570,7 @@ var BuildNotificationType;
1560
1570
  BuildNotificationType2["CANCELLED"] = "federation-rebuild-cancelled";
1561
1571
  })(BuildNotificationType || (BuildNotificationType = {}));
1562
1572
 
1563
- // node_modules/.pnpm/@softarc+native-federation@4.0.0_typescript@5.9.3/node_modules/@softarc/native-federation/src/lib/domain/core/chunk.js
1573
+ // node_modules/.pnpm/@softarc+native-federation@4.1.0_typescript@5.9.3/node_modules/@softarc/native-federation/src/lib/domain/core/chunk.js
1564
1574
  var CHUNK_PREFIX = "@nf-internal";
1565
1575
  function toChunkImport(fileName) {
1566
1576
  if (fileName.startsWith("./")) {
@@ -1594,6 +1604,9 @@ function createGenerateImportMap(config, ports) {
1594
1604
  return new NFError("Could not create ImportMap.");
1595
1605
  });
1596
1606
  addToScope(importMap, remote.scopeUrl, createScopeModules(externals, remote.scopeUrl));
1607
+ for (const version of Object.values(externals)) {
1608
+ addIntegrity(importMap, join(remote.scopeUrl, version.file), remoteName, version.file);
1609
+ }
1597
1610
  Object.values(externals).filter((e) => !!e.bundle).forEach((e) => registerBundleChunks(chunkBundles, remoteName, e.bundle));
1598
1611
  }
1599
1612
  return importMap;
@@ -1621,9 +1634,11 @@ function createGenerateImportMap(config, ports) {
1621
1634
  if (version.action === "scope") {
1622
1635
  for (const remote of version.remotes) {
1623
1636
  const remoteScope = getScope2(shareScope, remote.name, externalName);
1637
+ const url = join(remoteScope, remote.file);
1624
1638
  addToScope(importMap, remoteScope, {
1625
- [externalName]: join(remoteScope, remote.file)
1639
+ [externalName]: url
1626
1640
  });
1641
+ addIntegrity(importMap, url, remote.name, remote.file);
1627
1642
  registerBundleChunks(chunkBundles, remote.name, remote.bundle);
1628
1643
  remote.cached = true;
1629
1644
  }
@@ -1631,6 +1646,10 @@ function createGenerateImportMap(config, ports) {
1631
1646
  }
1632
1647
  const scope = getScope2(shareScope, version.remotes[0].name, externalName);
1633
1648
  let targetFileUrl = join(scope, version.remotes[0].file);
1649
+ let targetFileSource = {
1650
+ name: version.remotes[0].name,
1651
+ file: version.remotes[0].file
1652
+ };
1634
1653
  version.remotes[0].cached = true;
1635
1654
  if (version.action === "share") {
1636
1655
  registerBundleChunks(chunkBundles, version.remotes[0].name, version.remotes[0].bundle);
@@ -1643,6 +1662,10 @@ function createGenerateImportMap(config, ports) {
1643
1662
  if (!overrideScope)
1644
1663
  overrideScope = getScope2(shareScope, override.remotes[0].name, externalName);
1645
1664
  targetFileUrl = join(overrideScope, override.remotes[0].file);
1665
+ targetFileSource = {
1666
+ name: override.remotes[0].name,
1667
+ file: override.remotes[0].file
1668
+ };
1646
1669
  override.remotes[0].cached = true;
1647
1670
  version.remotes[0].cached = false;
1648
1671
  }
@@ -1651,6 +1674,7 @@ function createGenerateImportMap(config, ports) {
1651
1674
  const scope2 = getScope2(shareScope, r.name, externalName);
1652
1675
  addToScope(importMap, scope2, { [externalName]: targetFileUrl });
1653
1676
  });
1677
+ addIntegrity(importMap, targetFileUrl, targetFileSource.name, targetFileSource.file);
1654
1678
  }
1655
1679
  ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
1656
1680
  }
@@ -1691,9 +1715,11 @@ function createGenerateImportMap(config, ports) {
1691
1715
  if (version.action === "scope") {
1692
1716
  for (const remote of version.remotes) {
1693
1717
  const remoteScope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
1718
+ const url2 = join(remoteScope, remote.file);
1694
1719
  addToScope(importMap, remoteScope, {
1695
- [externalName]: join(remoteScope, remote.file)
1720
+ [externalName]: url2
1696
1721
  });
1722
+ addIntegrity(importMap, url2, remote.name, remote.file);
1697
1723
  remote.cached = true;
1698
1724
  registerBundleChunks(chunkBundles, remote.name, remote.bundle);
1699
1725
  }
@@ -1704,7 +1730,9 @@ function createGenerateImportMap(config, ports) {
1704
1730
  continue;
1705
1731
  }
1706
1732
  const scope = getScope2(GLOBAL_SCOPE, version.remotes[0].name, externalName);
1707
- addToGlobal(importMap, { [externalName]: join(scope, version.remotes[0].file) });
1733
+ const url = join(scope, version.remotes[0].file);
1734
+ addToGlobal(importMap, { [externalName]: url });
1735
+ addIntegrity(importMap, url, version.remotes[0].name, version.remotes[0].file);
1708
1736
  registerBundleChunks(chunkBundles, version.remotes[0].name, version.remotes[0].bundle);
1709
1737
  version.remotes[0].cached = true;
1710
1738
  }
@@ -1739,6 +1767,7 @@ function createGenerateImportMap(config, ports) {
1739
1767
  const moduleName = join(remoteName, exposed.moduleName);
1740
1768
  const moduleUrl = join(remote.scopeUrl, exposed.file);
1741
1769
  importMap.imports[moduleName] = moduleUrl;
1770
+ addIntegrity(importMap, moduleUrl, remoteName, exposed.file);
1742
1771
  }
1743
1772
  }
1744
1773
  function registerBundleChunks(chunkBundles, remoteName, bundleName) {
@@ -1753,7 +1782,9 @@ function createGenerateImportMap(config, ports) {
1753
1782
  const imports = Array.from(bundles).reduce((_imports, bundleName) => {
1754
1783
  ports.sharedChunksRepo.tryGet(remoteName, bundleName).ifPresent((files) => {
1755
1784
  files.forEach((file) => {
1756
- _imports[toChunkImport(file)] = join(baseUrl, file);
1785
+ const url = join(baseUrl, file);
1786
+ _imports[toChunkImport(file)] = url;
1787
+ addIntegrity(importMap, url, remoteName, file);
1757
1788
  });
1758
1789
  });
1759
1790
  return _imports;
@@ -1762,6 +1793,12 @@ function createGenerateImportMap(config, ports) {
1762
1793
  });
1763
1794
  return importMap;
1764
1795
  }
1796
+ function addIntegrity(importMap, url, remoteName, file) {
1797
+ const hash = ports.remoteInfoRepo.tryGet(remoteName).get()?.integrity?.[file];
1798
+ if (!hash) return;
1799
+ if (!importMap.integrity) importMap.integrity = {};
1800
+ importMap.integrity[url] = hash;
1801
+ }
1765
1802
  function getScope2(ctx, remoteName, externalName) {
1766
1803
  return ports.remoteInfoRepo.tryGet(remoteName).map((remote) => remote.scopeUrl).orThrow(() => {
1767
1804
  if (externalName) {
@@ -1888,31 +1925,75 @@ var createVersionCheck = () => {
1888
1925
  };
1889
1926
  };
1890
1927
 
1928
+ // src/lib/utils/integrity.ts
1929
+ var SUPPORTED_ALGORITHMS = {
1930
+ "sha256-": "SHA-256",
1931
+ "sha384-": "SHA-384",
1932
+ "sha512-": "SHA-512"
1933
+ };
1934
+ var parseIntegrity = (integrity) => {
1935
+ for (const prefix of Object.keys(SUPPORTED_ALGORITHMS)) {
1936
+ if (integrity.startsWith(prefix)) {
1937
+ return { algorithm: SUPPORTED_ALGORITHMS[prefix], expected: integrity };
1938
+ }
1939
+ }
1940
+ return null;
1941
+ };
1942
+ var toBase64 = (bytes) => {
1943
+ const view = new Uint8Array(bytes);
1944
+ let bin = "";
1945
+ for (let i = 0; i < view.length; i++) bin += String.fromCharCode(view[i]);
1946
+ return btoa(bin);
1947
+ };
1948
+ var verifyIntegrity = async (bytes, integrity) => {
1949
+ const parsed = parseIntegrity(integrity);
1950
+ if (!parsed) {
1951
+ throw new TypeError(
1952
+ `Unsupported integrity prefix in '${integrity}'. Expected sha256-, sha384-, or sha512-.`
1953
+ );
1954
+ }
1955
+ const subtle = typeof crypto !== "undefined" && crypto.subtle ? crypto.subtle : void 0;
1956
+ if (!subtle) {
1957
+ throw new Error("SubtleCrypto is not available in this environment.");
1958
+ }
1959
+ const digest = await subtle.digest(parsed.algorithm, bytes);
1960
+ const actual = integrity.substring(0, integrity.indexOf("-") + 1) + toBase64(digest);
1961
+ if (actual !== parsed.expected) {
1962
+ throw new Error(`Integrity mismatch: expected ${parsed.expected}, got ${actual}`);
1963
+ }
1964
+ };
1965
+
1891
1966
  // src/lib/3.adapters/http/manifest-provider.ts
1892
1967
  var createManifestProvider = () => {
1893
- const mapToJson = (response) => {
1968
+ const ensureOk = (response) => {
1894
1969
  if (!response.ok)
1895
1970
  return Promise.reject(new NFError(`${response.status} - ${response.statusText}`));
1896
- return response.json();
1971
+ return response;
1897
1972
  };
1898
- const formatError = (remoteEntryUrl) => (err) => {
1973
+ const formatError = (manifestUrl) => (err) => {
1899
1974
  const msg = err instanceof Error ? err.message : String(err);
1900
- throw new NFError(`Fetch of '${remoteEntryUrl}' returned ${msg}`);
1975
+ throw new NFError(`Fetch of '${manifestUrl}' returned ${msg}`);
1901
1976
  };
1902
1977
  return {
1903
- provide: async function(remotesOrManifestUrl) {
1978
+ provide: async function(remotesOrManifestUrl, opts = {}) {
1904
1979
  if (typeof remotesOrManifestUrl !== "string") return Promise.resolve(remotesOrManifestUrl);
1905
- return fetch(remotesOrManifestUrl).then(mapToJson).catch(formatError(remotesOrManifestUrl));
1980
+ const parse = async (response) => {
1981
+ if (!opts.integrity) return response.json();
1982
+ const bytes = await response.arrayBuffer();
1983
+ await verifyIntegrity(bytes, opts.integrity);
1984
+ return JSON.parse(new TextDecoder().decode(bytes));
1985
+ };
1986
+ return fetch(remotesOrManifestUrl).then(ensureOk).then(parse).catch(formatError(remotesOrManifestUrl));
1906
1987
  }
1907
1988
  };
1908
1989
  };
1909
1990
 
1910
1991
  // src/lib/3.adapters/http/remote-entry-provider.ts
1911
1992
  var createRemoteEntryProvider = () => {
1912
- const mapToJson = (response) => {
1993
+ const ensureOk = (response) => {
1913
1994
  if (!response.ok)
1914
1995
  return Promise.reject(new Error(`${response.status} - ${response.statusText}`));
1915
- return response.json();
1996
+ return response;
1916
1997
  };
1917
1998
  const fillEmptyFields = (remoteEntryUrl) => (remoteEntry) => {
1918
1999
  if (!remoteEntry.exposes) remoteEntry.exposes = [];
@@ -1925,8 +2006,14 @@ var createRemoteEntryProvider = () => {
1925
2006
  throw new NFError(`Fetch of '${remoteEntryUrl}' returned ${msg}`);
1926
2007
  };
1927
2008
  return {
1928
- provide: async function(remoteEntryUrl) {
1929
- return fetch(remoteEntryUrl).then(mapToJson).then(fillEmptyFields(remoteEntryUrl)).catch(formatError(remoteEntryUrl));
2009
+ provide: async function(remoteEntryUrl, opts = {}) {
2010
+ const parse = async (response) => {
2011
+ if (!opts.integrity) return response.json();
2012
+ const bytes = await response.arrayBuffer();
2013
+ await verifyIntegrity(bytes, opts.integrity);
2014
+ return JSON.parse(new TextDecoder().decode(bytes));
2015
+ };
2016
+ return fetch(remoteEntryUrl).then(ensureOk).then(parse).then(fillEmptyFields(remoteEntryUrl)).catch(formatError(remoteEntryUrl));
1930
2017
  }
1931
2018
  };
1932
2019
  };
@@ -2177,27 +2264,89 @@ var createDriving = (config) => {
2177
2264
  return { adapters, config };
2178
2265
  };
2179
2266
 
2267
+ // src/lib/4.config/import-map/trusted-types.ts
2268
+ var IMPORT_MAP_KEYS = /* @__PURE__ */ new Set(["imports", "scopes", "integrity"]);
2269
+ var validateImportMapJSON = (input) => {
2270
+ let parsed;
2271
+ try {
2272
+ parsed = JSON.parse(input);
2273
+ } catch {
2274
+ throw new TypeError("[nf-orchestrator] trusted-types: import map is not valid JSON");
2275
+ }
2276
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2277
+ throw new TypeError("[nf-orchestrator] trusted-types: import map must be a plain object");
2278
+ }
2279
+ for (const key of Object.keys(parsed)) {
2280
+ if (!IMPORT_MAP_KEYS.has(key)) {
2281
+ throw new TypeError(`[nf-orchestrator] trusted-types: unexpected key "${key}" in import map`);
2282
+ }
2283
+ }
2284
+ return input;
2285
+ };
2286
+ var validateScriptURL = (input) => {
2287
+ const base = typeof location !== "undefined" ? location.href : "http://localhost/";
2288
+ let url;
2289
+ try {
2290
+ url = new URL(input, base);
2291
+ } catch {
2292
+ throw new TypeError(`[nf-orchestrator] trusted-types: invalid script URL "${input}"`);
2293
+ }
2294
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
2295
+ throw new TypeError(
2296
+ `[nf-orchestrator] trusted-types: disallowed protocol "${url.protocol}" for script URL`
2297
+ );
2298
+ }
2299
+ return input;
2300
+ };
2301
+ var passThroughPolicy = {
2302
+ createScript: (input) => input,
2303
+ createScriptURL: (input) => input
2304
+ };
2305
+ var cachedPolicy = null;
2306
+ var getTrustedTypesPolicy = (name = "nfo") => {
2307
+ if (name === false) return passThroughPolicy;
2308
+ if (cachedPolicy) return cachedPolicy;
2309
+ const factory = globalThis.trustedTypes;
2310
+ if (!factory) {
2311
+ cachedPolicy = passThroughPolicy;
2312
+ return cachedPolicy;
2313
+ }
2314
+ const native = factory.createPolicy(name, {
2315
+ createScript: validateImportMapJSON,
2316
+ createScriptURL: validateScriptURL
2317
+ });
2318
+ cachedPolicy = {
2319
+ createScript: (input) => native.createScript(input),
2320
+ createScriptURL: (input) => native.createScriptURL(input)
2321
+ };
2322
+ return cachedPolicy;
2323
+ };
2324
+
2180
2325
  // src/lib/4.config/import-map/replace-in-dom.ts
2181
- var replaceInDOM = (mapType) => (importMap, opts = {}) => {
2326
+ var replaceInDOM = (mapType, trustedTypesPolicyName = "nfo") => (importMap, opts = {}) => {
2182
2327
  if (opts?.override) {
2183
2328
  document.head.querySelectorAll(`script[type="${mapType}"]`).forEach((importMap2) => importMap2.remove());
2184
2329
  }
2330
+ const policy = getTrustedTypesPolicy(trustedTypesPolicyName);
2185
2331
  document.head.appendChild(
2186
2332
  Object.assign(document.createElement("script"), {
2187
2333
  type: mapType,
2188
- innerHTML: JSON.stringify(importMap)
2334
+ text: policy.createScript(JSON.stringify(importMap))
2189
2335
  })
2190
2336
  );
2191
2337
  return Promise.resolve(importMap);
2192
2338
  };
2193
2339
 
2194
2340
  // src/lib/4.config/import-map/use-default.ts
2195
- var useDefaultImportMap = () => ({
2196
- loadModuleFn: (url) => import(
2197
- /* @vite-ignore */
2198
- url
2199
- ),
2200
- setImportMapFn: replaceInDOM("importmap"),
2341
+ var useDefaultImportMap = (trustedTypesPolicyName = "nfo") => ({
2342
+ loadModuleFn: (url) => {
2343
+ const trusted = getTrustedTypesPolicy(trustedTypesPolicyName).createScriptURL(url);
2344
+ return import(
2345
+ /* @vite-ignore */
2346
+ trusted
2347
+ );
2348
+ },
2349
+ setImportMapFn: replaceInDOM("importmap", trustedTypesPolicyName),
2201
2350
  reloadBrowserFn: () => {
2202
2351
  window.location.reload();
2203
2352
  }
@@ -2205,7 +2354,7 @@ var useDefaultImportMap = () => ({
2205
2354
 
2206
2355
  // src/lib/4.config/import-map/import-map.config.ts
2207
2356
  var createImportMapConfig = (o) => {
2208
- const fallback = useDefaultImportMap();
2357
+ const fallback = useDefaultImportMap(o.trustedTypesPolicyName);
2209
2358
  return {
2210
2359
  setImportMapFn: o.setImportMapFn ?? fallback.setImportMapFn,
2211
2360
  loadModuleFn: o.loadModuleFn ?? fallback.loadModuleFn,
@@ -2215,22 +2364,25 @@ var createImportMapConfig = (o) => {
2215
2364
 
2216
2365
  // src/lib/4.config/host/host.config.ts
2217
2366
  var createHostConfig = (override) => {
2367
+ const extras = override?.manifestIntegrity ? { manifestIntegrity: override.manifestIntegrity } : {};
2218
2368
  if (!override?.hostRemoteEntry) {
2219
- return { hostRemoteEntry: false };
2369
+ return { hostRemoteEntry: false, ...extras };
2220
2370
  }
2221
2371
  if (typeof override.hostRemoteEntry === "string") {
2222
2372
  return {
2223
2373
  hostRemoteEntry: {
2224
2374
  name: "__NF-HOST__",
2225
2375
  url: override.hostRemoteEntry
2226
- }
2376
+ },
2377
+ ...extras
2227
2378
  };
2228
2379
  }
2229
2380
  return {
2230
2381
  hostRemoteEntry: {
2231
2382
  name: "__NF-HOST__",
2232
2383
  ...override.hostRemoteEntry
2233
- }
2384
+ },
2385
+ ...extras
2234
2386
  };
2235
2387
  };
2236
2388
 
@@ -2355,15 +2507,13 @@ function createConvertToImportMap({ log }, ports) {
2355
2507
  return;
2356
2508
  }
2357
2509
  const remoteEntryScope = getScope(remoteEntry.url);
2510
+ const integrityMap = remoteEntry.integrity;
2358
2511
  const chunkBundles = /* @__PURE__ */ new Set(["mapping-or-exposed"]);
2359
2512
  remoteEntry.shared.forEach((external) => {
2360
2513
  if (!external.singleton) {
2361
- addToScopes(
2362
- remoteEntryScope,
2363
- external.packageName,
2364
- join(remoteEntryScope, external.outFileName),
2365
- importMap
2366
- );
2514
+ const url2 = join(remoteEntryScope, external.outFileName);
2515
+ addToScopes(remoteEntryScope, external.packageName, url2, importMap);
2516
+ addIntegrity(importMap, url2, integrityMap, external.outFileName);
2367
2517
  if (external?.bundle) chunkBundles.add(external?.bundle);
2368
2518
  return;
2369
2519
  }
@@ -2388,26 +2538,22 @@ function createConvertToImportMap({ log }, ports) {
2388
2538
  }
2389
2539
  if (external?.bundle) chunkBundles.add(external?.bundle);
2390
2540
  if (actions[external.packageName].action === "scope") {
2391
- addToScopes(
2392
- remoteEntryScope,
2393
- external.packageName,
2394
- join(remoteEntryScope, external.outFileName),
2395
- importMap
2396
- );
2541
+ const url2 = join(remoteEntryScope, external.outFileName);
2542
+ addToScopes(remoteEntryScope, external.packageName, url2, importMap);
2543
+ addIntegrity(importMap, url2, integrityMap, external.outFileName);
2397
2544
  return;
2398
2545
  }
2399
2546
  if (external.shareScope) {
2400
- addToScopes(
2401
- remoteEntryScope,
2402
- external.packageName,
2403
- join(remoteEntryScope, external.outFileName),
2404
- importMap
2405
- );
2547
+ const url2 = join(remoteEntryScope, external.outFileName);
2548
+ addToScopes(remoteEntryScope, external.packageName, url2, importMap);
2549
+ addIntegrity(importMap, url2, integrityMap, external.outFileName);
2406
2550
  return;
2407
2551
  }
2408
- importMap.imports[external.packageName] = join(remoteEntryScope, external.outFileName);
2552
+ const url = join(remoteEntryScope, external.outFileName);
2553
+ importMap.imports[external.packageName] = url;
2554
+ addIntegrity(importMap, url, integrityMap, external.outFileName);
2409
2555
  });
2410
- addChunkImports(importMap, remoteEntry.name, remoteEntryScope, chunkBundles);
2556
+ addChunkImports(importMap, remoteEntry, remoteEntryScope, chunkBundles);
2411
2557
  }
2412
2558
  function addToScopes(scope, packageName, url, importMap) {
2413
2559
  if (!importMap.scopes) importMap.scopes = {};
@@ -2421,34 +2567,44 @@ function createConvertToImportMap({ log }, ports) {
2421
2567
  const moduleName = join(remoteEntry.name, exposed.key);
2422
2568
  const moduleUrl = join(scope, exposed.outFileName);
2423
2569
  importMap.imports[moduleName] = moduleUrl;
2570
+ addIntegrity(importMap, moduleUrl, remoteEntry.integrity, exposed.outFileName);
2424
2571
  });
2425
2572
  }
2426
- function addChunkImports(importMap, remoteName, remoteEntryScope, chunkBundles) {
2573
+ function addChunkImports(importMap, remoteEntry, remoteEntryScope, chunkBundles) {
2427
2574
  Array.from(chunkBundles).forEach((bundleName) => {
2428
- ports.sharedChunksRepo.tryGet(remoteName, bundleName).ifPresent((files) => {
2575
+ ports.sharedChunksRepo.tryGet(remoteEntry.name, bundleName).ifPresent((files) => {
2429
2576
  files.forEach((file) => {
2430
- addToScopes(
2431
- remoteEntryScope,
2432
- toChunkImport(file),
2433
- join(remoteEntryScope, file),
2434
- importMap
2435
- );
2577
+ const url = join(remoteEntryScope, file);
2578
+ addToScopes(remoteEntryScope, toChunkImport(file), url, importMap);
2579
+ addIntegrity(importMap, url, remoteEntry.integrity, file);
2436
2580
  });
2437
2581
  });
2438
2582
  });
2439
2583
  return importMap;
2440
2584
  }
2585
+ function addIntegrity(importMap, url, integrityMap, file) {
2586
+ const hash = integrityMap?.[file];
2587
+ if (!hash) return;
2588
+ if (!importMap.integrity) importMap.integrity = {};
2589
+ importMap.integrity[url] = hash;
2590
+ }
2441
2591
  }
2442
2592
 
2443
2593
  // src/lib/2.app/flows/dynamic-init/get-remote-entry.ts
2594
+ var normalizeRemoteRef = (remote) => {
2595
+ if (!remote) return {};
2596
+ if (typeof remote === "string") return { name: remote };
2597
+ return remote;
2598
+ };
2444
2599
  function createGetRemoteEntry(config, ports) {
2445
- return async (remoteEntryUrl, remoteName) => {
2600
+ return async (remoteEntryUrl, remote) => {
2601
+ const { name: remoteName, integrity } = normalizeRemoteRef(remote);
2446
2602
  if (!!remoteName && shouldSkipCachedRemote(remoteEntryUrl, remoteName)) {
2447
2603
  config.log.debug(7, `Found remote '${remoteName}' in storage, omitting fetch.`);
2448
2604
  return Optional.empty();
2449
2605
  }
2450
2606
  try {
2451
- const remoteEntry = await ports.remoteEntryProvider.provide(remoteEntryUrl);
2607
+ const remoteEntry = integrity ? await ports.remoteEntryProvider.provide(remoteEntryUrl, { integrity }) : await ports.remoteEntryProvider.provide(remoteEntryUrl);
2452
2608
  config.log.debug(
2453
2609
  7,
2454
2610
  `[${remoteEntry.name}] Fetched from '${remoteEntry.url}', exposing: ${JSON.stringify(remoteEntry.exposes)}`
@@ -2657,7 +2813,7 @@ var createDynamicInitFlow = ({
2657
2813
  const processDynamicRemoteEntry = async (remoteEntry) => {
2658
2814
  return flow.updateCache(remoteEntry).then(flow.convertToImportMap).then(flow.commitChanges);
2659
2815
  };
2660
- const initRemoteEntry = (remoteEntryUrl, remoteName) => flow.getRemoteEntry(remoteEntryUrl, remoteName).then((entry) => entry.map(processDynamicRemoteEntry).orElse(Promise.resolve())).then(() => ({
2816
+ const initRemoteEntry = (remoteEntryUrl, remote) => flow.getRemoteEntry(remoteEntryUrl, remote).then((entry) => entry.map(processDynamicRemoteEntry).orElse(Promise.resolve())).then(() => ({
2661
2817
  config,
2662
2818
  adapters,
2663
2819
  initRemoteEntry
@@ -2689,15 +2845,18 @@ var initFederation = (remotesOrManifestUrl, options = {}) => {
2689
2845
  }),
2690
2846
  load: loadRemoteModule
2691
2847
  };
2692
- const initRemoteEntry = async (remoteEntryUrl, remoteName) => dynamicInitFlow(remoteEntryUrl, remoteName).catch((e) => {
2693
- stateDump(`[dynamic-init][${remoteName ?? remoteEntryUrl}] STATE DUMP`);
2694
- if (config.strict.strictRemoteEntry) return Promise.reject(e);
2695
- else console.warn("Failed to initialize remote entry, continuing anyway.");
2696
- return Promise.resolve();
2697
- }).then(() => ({
2698
- ...output,
2699
- initRemoteEntry
2700
- }));
2848
+ const initRemoteEntry = async (remoteEntryUrl, remote) => {
2849
+ const remoteName = typeof remote === "string" ? remote : remote?.name;
2850
+ return dynamicInitFlow(remoteEntryUrl, remote).catch((e) => {
2851
+ stateDump(`[dynamic-init][${remoteName ?? remoteEntryUrl}] STATE DUMP`);
2852
+ if (config.strict.strictRemoteEntry) return Promise.reject(e);
2853
+ else console.warn("Failed to initialize remote entry, continuing anyway.");
2854
+ return Promise.resolve();
2855
+ }).then(() => ({
2856
+ ...output,
2857
+ initRemoteEntry
2858
+ }));
2859
+ };
2701
2860
  return {
2702
2861
  ...output,
2703
2862
  initRemoteEntry