@softarc/native-federation-orchestrator 4.0.1 → 4.1.0
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.
- package/README.md +9 -8
- package/fesm2022/@softarc/native-federation-orchestrator.mjs +239 -80
- package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
- package/fesm2022/options.mjs +79 -11
- package/fesm2022/options.mjs.map +3 -3
- package/package.json +1 -1
- package/quickstart.mjs +13 -13
- package/types/lib/1.domain/import-map/import-map.contract.d.ts +3 -1
- package/types/lib/1.domain/remote/remote-info.contract.d.ts +2 -0
- package/types/lib/1.domain/remote-entry/manifest.contract.d.ts +5 -1
- package/types/lib/2.app/config/host.contract.d.ts +4 -0
- package/types/lib/2.app/config/import-map.contract.d.ts +12 -1
- package/types/lib/2.app/driver-ports/dynamic-init/flow.contract.d.ts +6 -2
- package/types/lib/2.app/driver-ports/dynamic-init/for-getting-remote-entry.port.d.ts +2 -1
- package/types/lib/2.app/driving-ports/for-providing-manifest.port.d.ts +3 -1
- package/types/lib/2.app/driving-ports/for-providing-remote-entries.port.d.ts +3 -1
- package/types/lib/4.config/import-map/replace-in-dom.d.ts +1 -1
- package/types/lib/4.config/import-map/trusted-types.d.ts +6 -0
- package/types/lib/4.config/import-map/use-default.d.ts +1 -1
- package/types/lib/4.config/import-map/use-import-shim.d.ts +1 -1
- 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
|
|
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
|
|
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 = {}) =>
|
|
1282
|
-
config.
|
|
1283
|
-
|
|
1284
|
-
|
|
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,
|
|
1304
|
+
([remoteName, descriptor]) => fetchRemoteEntry(remoteName, descriptor)
|
|
1297
1305
|
);
|
|
1298
1306
|
return Promise.all(fetchPromises);
|
|
1299
1307
|
}
|
|
1300
|
-
async function fetchRemoteEntry(remoteName,
|
|
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.
|
|
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.
|
|
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]:
|
|
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]:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1968
|
+
const ensureOk = (response) => {
|
|
1894
1969
|
if (!response.ok)
|
|
1895
1970
|
return Promise.reject(new NFError(`${response.status} - ${response.statusText}`));
|
|
1896
|
-
return response
|
|
1971
|
+
return response;
|
|
1897
1972
|
};
|
|
1898
|
-
const formatError = (
|
|
1973
|
+
const formatError = (manifestUrl) => (err) => {
|
|
1899
1974
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1900
|
-
throw new NFError(`Fetch of '${
|
|
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
|
-
|
|
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
|
|
1993
|
+
const ensureOk = (response) => {
|
|
1913
1994
|
if (!response.ok)
|
|
1914
1995
|
return Promise.reject(new Error(`${response.status} - ${response.statusText}`));
|
|
1915
|
-
return response
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
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
|
|
|
@@ -2322,7 +2474,7 @@ var createModeConfig = (override) => {
|
|
|
2322
2474
|
} : {
|
|
2323
2475
|
strictRemoteEntry: override.strict?.strictRemoteEntry ?? false,
|
|
2324
2476
|
strictExternalCompatibility: override.strict?.strictExternalCompatibility ?? false,
|
|
2325
|
-
strictExternalSameVersionCompatibility: override.strict?.
|
|
2477
|
+
strictExternalSameVersionCompatibility: override.strict?.strictExternalSameVersionCompatibility ?? false,
|
|
2326
2478
|
strictExternalVersion: override.strict?.strictExternalVersion ?? false,
|
|
2327
2479
|
strictImportMap: override.strict?.strictImportMap ?? false
|
|
2328
2480
|
};
|
|
@@ -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
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
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
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
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
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
2573
|
+
function addChunkImports(importMap, remoteEntry, remoteEntryScope, chunkBundles) {
|
|
2427
2574
|
Array.from(chunkBundles).forEach((bundleName) => {
|
|
2428
|
-
ports.sharedChunksRepo.tryGet(
|
|
2575
|
+
ports.sharedChunksRepo.tryGet(remoteEntry.name, bundleName).ifPresent((files) => {
|
|
2429
2576
|
files.forEach((file) => {
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
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
|