@mfe-orchestrator-hub/client 0.1.1 → 0.1.2
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 +72 -0
- package/dist/index.cjs +190 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -8
- package/dist/index.d.ts +19 -8
- package/dist/index.js +190 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,35 @@ new ModuleFederationPlugin({
|
|
|
92
92
|
})
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
### Where `backendUrl` and `projectId` come from
|
|
96
|
+
|
|
97
|
+
The remotes above resolve themselves through this client, so the bundle has to carry the two
|
|
98
|
+
required options. The console writes them into the bundler config itself rather than into a `.env`:
|
|
99
|
+
the config is the file it commits to your repository, so a fresh clone builds a bundle that already
|
|
100
|
+
knows which console to ask and which project to ask about, with no variable left to set.
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// vite.config.js
|
|
104
|
+
define: {
|
|
105
|
+
"import.meta.env.VITE_MFE_BACKEND_URL": JSON.stringify("https://console.mfe-orchestrator.dev/api"),
|
|
106
|
+
"import.meta.env.VITE_MFE_PROJECT_ID": JSON.stringify("6f1b2c3d4e5f6a7b8c9d0e1f")
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
// webpack.config.js
|
|
112
|
+
new webpack.DefinePlugin({
|
|
113
|
+
"process.env.MFE_BACKEND_URL": JSON.stringify("https://console.mfe-orchestrator.dev/api"),
|
|
114
|
+
"process.env.MFE_PROJECT_ID": JSON.stringify("6f1b2c3d4e5f6a7b8c9d0e1f"),
|
|
115
|
+
// Verbatim text: the bare `undefined` is what an unset environment looks like, and the backend
|
|
116
|
+
// then resolves it from the domain. `process` does not exist in a browser, so leaving the key
|
|
117
|
+
// out would throw instead of reading as undefined.
|
|
118
|
+
"process.env.MFE_ENVIRONMENT": "undefined"
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`configure()` keeps reading the same names either way — a `.env` still works if you prefer one.
|
|
123
|
+
|
|
95
124
|
## API
|
|
96
125
|
|
|
97
126
|
```ts
|
|
@@ -207,6 +236,49 @@ This is not a retry: nothing here loops, schedules or backs off. It only means t
|
|
|
207
236
|
or one 502 during a console redeploy does not condemn the page to reject every `remoteUrl()` until a
|
|
208
237
|
full reload. Recovering is the host app's call, by asking again.
|
|
209
238
|
|
|
239
|
+
## When the configuration is wrong
|
|
240
|
+
|
|
241
|
+
`backendUrl` and `projectId` almost always come from build time variables, so the usual failure is
|
|
242
|
+
not a wrong value but a value that never reached the bundle. The client says so out loud instead of
|
|
243
|
+
firing a request at `undefined`:
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
[@mfe-orchestrator-hub/client] configure() cannot start the client: projectId is not usable.
|
|
247
|
+
|
|
248
|
+
• projectId is missing.
|
|
249
|
+
→ It is the id of the project in the console: open the project and copy it from its page, ex. "6f1b2c3d4e5f6a7b8c9d0e1f".
|
|
250
|
+
→ Nothing arrived here at all, which is what a value that never reached the bundle looks like: check VITE_MFE_PROJECT_ID (Vite) or MFE_PROJECT_ID (webpack) in the build that produced this bundle. The generated configs carry it in the "define" block of vite.config, or in the DefinePlugin of webpack.config, so look there first; declared only in a .env file the build never reads, it looks exactly like this.
|
|
251
|
+
|
|
252
|
+
Configure the client like this:
|
|
253
|
+
|
|
254
|
+
import { configure } from "@mfe-orchestrator-hub/client"
|
|
255
|
+
|
|
256
|
+
configure({
|
|
257
|
+
backendUrl: "https://console.mfe-orchestrator.dev/api",
|
|
258
|
+
projectId: "6f1b2c3d4e5f6a7b8c9d0e1f"
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
Call it once, at the very top of the host entry point, before anything imports a remote.
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`configure()` throws, before anything is stored, when an option is:
|
|
265
|
+
|
|
266
|
+
| What arrived | What the message says |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| `undefined`, `null`, `""`, `" "` | missing or empty, plus the environment variable to check |
|
|
269
|
+
| `projectID`, `backend_url`, `apiUrl`… | the key the client ignores, and how the option is spelled |
|
|
270
|
+
| `"undefined"`, `"…"`, `"<your-project-id>"` | a placeholder that was never filled in |
|
|
271
|
+
| a number, an object, an array | the type it received, instead of a string |
|
|
272
|
+
| `"console.test/api"` | not a URL: the two forms that do work, with and without protocol |
|
|
273
|
+
| `"localhost:3000/api"` | a bare `host:port` is read as a scheme, write `http://localhost:3000/api` |
|
|
274
|
+
| `" p1 "` as `projectId` | padded with whitespace, which would be percent encoded into the URL |
|
|
275
|
+
|
|
276
|
+
Reading anything before `configure()` ran rejects with the same guidance, naming the call that was
|
|
277
|
+
made too early. A `404` on the manifest route says which two things the backend could not find, the
|
|
278
|
+
project id or the environment, since it answers the same status for both. Every message shows the
|
|
279
|
+
snippet of the package the host actually uses: `<OrchestratorProvider>` for React,
|
|
280
|
+
`createOrchestrator()` for Vue, `provideOrchestrator()` for Angular.
|
|
281
|
+
|
|
210
282
|
## Not in v1
|
|
211
283
|
|
|
212
284
|
No automatic retries, no offline caching, no service worker, and nothing that reads or writes
|
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,156 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var PREFIX = "[@mfe-orchestrator-hub/client]";
|
|
5
|
+
var EXAMPLE_BACKEND_URL = "https://console.mfe-orchestrator.dev/api";
|
|
6
|
+
var EXAMPLE_PROJECT_ID = "6f1b2c3d4e5f6a7b8c9d0e1f";
|
|
7
|
+
var SNIPPETS = {
|
|
8
|
+
core: `import { configure } from "@mfe-orchestrator-hub/client"
|
|
9
|
+
|
|
10
|
+
configure({
|
|
11
|
+
backendUrl: "${EXAMPLE_BACKEND_URL}",
|
|
12
|
+
projectId: "${EXAMPLE_PROJECT_ID}"
|
|
13
|
+
})`,
|
|
14
|
+
react: `import { OrchestratorProvider } from "@mfe-orchestrator-hub/client-react"
|
|
15
|
+
|
|
16
|
+
<OrchestratorProvider config={{ backendUrl: "${EXAMPLE_BACKEND_URL}", projectId: "${EXAMPLE_PROJECT_ID}" }}>
|
|
17
|
+
<App />
|
|
18
|
+
</OrchestratorProvider>`,
|
|
19
|
+
vue: `import { createOrchestrator } from "@mfe-orchestrator-hub/client-vue"
|
|
20
|
+
|
|
21
|
+
app.use(createOrchestrator({ backendUrl: "${EXAMPLE_BACKEND_URL}", projectId: "${EXAMPLE_PROJECT_ID}" }))`,
|
|
22
|
+
angular: `import { provideOrchestrator } from "@mfe-orchestrator-hub/client-angular"
|
|
23
|
+
|
|
24
|
+
bootstrapApplication(AppComponent, {
|
|
25
|
+
providers: [provideOrchestrator({ backendUrl: "${EXAMPLE_BACKEND_URL}", projectId: "${EXAMPLE_PROJECT_ID}" })]
|
|
26
|
+
})`
|
|
27
|
+
};
|
|
28
|
+
var WHERE = {
|
|
29
|
+
core: "Call it once, at the very top of the host entry point, before anything imports a remote.",
|
|
30
|
+
react: "Render it above every component that resolves a remote. Better still, call configure() at the very top of the entry point: a bundler may import a remote before React mounts.",
|
|
31
|
+
vue: "Install the plugin before mounting the app. Better still, call configure() at the very top of the entry point: a bundler may import a remote before the app is created.",
|
|
32
|
+
angular: "Pass it to bootstrapApplication(). Better still, call configure() at the very top of main.ts: a lazily loaded remote must find the client already configured."
|
|
33
|
+
};
|
|
34
|
+
var WHAT_IT_IS = {
|
|
35
|
+
backendUrl: `It is the base URL of the console API, protocol included, ex. "${EXAMPLE_BACKEND_URL}". A path on the same origin as the host, ex. "/api", works too.`,
|
|
36
|
+
projectId: `It is the id of the project in the console: open the project and copy it from its page, ex. "${EXAMPLE_PROJECT_ID}".`
|
|
37
|
+
};
|
|
38
|
+
var BUILD_VARIABLE = {
|
|
39
|
+
backendUrl: { vite: "VITE_MFE_BACKEND_URL", webpack: "MFE_BACKEND_URL" },
|
|
40
|
+
projectId: { vite: "VITE_MFE_PROJECT_ID", webpack: "MFE_PROJECT_ID" }
|
|
41
|
+
};
|
|
42
|
+
var missingEnvHint = (option) => `Nothing arrived here at all, which is what a value that never reached the bundle looks like: check ${BUILD_VARIABLE[option].vite} (Vite) or ${BUILD_VARIABLE[option].webpack} (webpack) in the build that produced this bundle. The generated configs carry it in the "define" block of vite.config, or in the DefinePlugin of webpack.config, so look there first; declared only in a .env file the build never reads, it looks exactly like this.`;
|
|
43
|
+
var PLACEHOLDER = /^(…|\.{3}|undefined|null|nan|<[^>]*>|\{\{[^}]*\}\}|\$\{[^}]*\}|your[-_ ]?[a-z]*|x{3,}|change[-_ ]?me|todo|tbd)$/i;
|
|
44
|
+
var quoted = (value) => JSON.stringify(value.length > 120 ? `${value.slice(0, 120)}\u2026` : value);
|
|
45
|
+
var described = (value) => {
|
|
46
|
+
if (value === null) {
|
|
47
|
+
return "null";
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return "an array";
|
|
51
|
+
}
|
|
52
|
+
const type = typeof value;
|
|
53
|
+
return type === "object" || type === "function" ? `a ${type}` : `a ${type} (${String(value)})`;
|
|
54
|
+
};
|
|
55
|
+
var normalized = (key) => key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
56
|
+
var ALIASES = {
|
|
57
|
+
backendUrl: ["backendurl", "backendbaseurl", "apiurl", "baseurl", "consoleurl", "url", "backend"],
|
|
58
|
+
projectId: ["projectid", "project", "idproject", "projectuuid", "projectkey"]
|
|
59
|
+
};
|
|
60
|
+
var confusableKey = (option, config) => {
|
|
61
|
+
if (typeof config !== "object" || config === null) {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
const candidates = ALIASES[option];
|
|
65
|
+
return Object.keys(config).find((key) => key !== option && candidates.includes(normalized(key)));
|
|
66
|
+
};
|
|
67
|
+
var problem = (option, what, ...hints) => ({
|
|
68
|
+
option,
|
|
69
|
+
what,
|
|
70
|
+
hints: [WHAT_IT_IS[option], ...hints].filter((hint) => Boolean(hint))
|
|
71
|
+
});
|
|
72
|
+
var diagnoseBackendUrl = (value) => {
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
if (trimmed.startsWith("/")) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = new URL(trimmed);
|
|
80
|
+
} catch {
|
|
81
|
+
return problem(
|
|
82
|
+
"backendUrl",
|
|
83
|
+
`is ${quoted(value)}, which is not a URL`,
|
|
84
|
+
trimmed.includes(".") || trimmed.includes(":") ? `It looks like a host without a protocol: write "https://${trimmed}" instead, or "/${trimmed.replace(/^\/+/, "")}" if the console API answers on the same origin as this page.` : void 0
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
88
|
+
return problem(
|
|
89
|
+
"backendUrl",
|
|
90
|
+
`is ${quoted(value)}, whose "${parsed.protocol.replace(":", "")}" scheme is neither http nor https`,
|
|
91
|
+
'A bare "host:port" is read as a scheme: write "http://localhost:3000/api", not "localhost:3000/api".'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
var diagnose = (option, config) => {
|
|
97
|
+
const value = config?.[option];
|
|
98
|
+
const typo = confusableKey(option, config);
|
|
99
|
+
const typoHint = typo ? `The configuration carries "${typo}", which the client ignores. The option is spelled "${option}".` : void 0;
|
|
100
|
+
if (value === void 0 || value === null) {
|
|
101
|
+
return problem(option, value === null ? "is null" : "is missing", typoHint, typo ? void 0 : missingEnvHint(option));
|
|
102
|
+
}
|
|
103
|
+
if (typeof value !== "string") {
|
|
104
|
+
return problem(option, `is ${described(value)}, not a string`, typoHint);
|
|
105
|
+
}
|
|
106
|
+
if (value.trim() === "") {
|
|
107
|
+
return problem(option, value === "" ? "is an empty string" : "holds nothing but whitespace", typoHint, missingEnvHint(option));
|
|
108
|
+
}
|
|
109
|
+
if (PLACEHOLDER.test(value.trim())) {
|
|
110
|
+
const looksLikeAnUnsetVariable = /^(undefined|null|nan)$/i.test(value.trim());
|
|
111
|
+
return problem(option, `is still the placeholder ${quoted(value)}`, typoHint, looksLikeAnUnsetVariable ? missingEnvHint(option) : void 0);
|
|
112
|
+
}
|
|
113
|
+
if (option === "projectId" && value.trim() !== value) {
|
|
114
|
+
return problem(
|
|
115
|
+
"projectId",
|
|
116
|
+
`is ${quoted(value)}, padded with whitespace`,
|
|
117
|
+
"Trim the value: it is percent encoded into the manifest URL, so the spaces would reach the backend as part of the id."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return option === "backendUrl" ? diagnoseBackendUrl(value) : null;
|
|
121
|
+
};
|
|
122
|
+
var diagnoseConfig = (config) => ["backendUrl", "projectId"].map((option) => diagnose(option, config)).filter((found) => found !== null);
|
|
123
|
+
var bulleted = (problems) => problems.map(({ option, what, hints }) => [` \u2022 ${option} ${what}.`, ...hints.map((hint) => ` \u2192 ${hint}`)].join("\n")).join("\n");
|
|
124
|
+
var indented = (block) => block.split("\n").map((line) => line ? ` ${line}` : line).join("\n");
|
|
125
|
+
var howToConfigure = (integration) => `Configure the client like this:
|
|
126
|
+
|
|
127
|
+
${indented(SNIPPETS[integration])}
|
|
128
|
+
|
|
129
|
+
${WHERE[integration]}`;
|
|
130
|
+
var listed = (options) => options.length > 1 ? `${options.slice(0, -1).join(", ")} and ${options.at(-1)}` : String(options[0]);
|
|
131
|
+
var invalidConfigMessage = (problems, integration) => {
|
|
132
|
+
const options = problems.map(({ option }) => option);
|
|
133
|
+
const headline = `${PREFIX} configure() cannot start the client: ${listed(options)} ${options.length > 1 ? "are" : "is"} not usable.`;
|
|
134
|
+
return `${headline}
|
|
135
|
+
|
|
136
|
+
${bulleted(problems)}
|
|
137
|
+
|
|
138
|
+
${howToConfigure(integration)}`;
|
|
139
|
+
};
|
|
140
|
+
var noConfigMessage = (config, integration) => `${PREFIX} configure() was called with ${config === void 0 ? "no argument" : described(config)} instead of a configuration object. It needs at least backendUrl and projectId.
|
|
141
|
+
|
|
142
|
+
${howToConfigure(integration)}`;
|
|
143
|
+
var notConfiguredMessage = (caller, integration) => `${PREFIX} the client is not configured: ${caller} was called before configure() ever ran, so neither backendUrl nor projectId is known and no manifest can be requested.
|
|
144
|
+
|
|
145
|
+
${howToConfigure(integration)}`;
|
|
146
|
+
|
|
3
147
|
// src/state.ts
|
|
4
148
|
var STATE_KEY = "__mfe_orchestrator_client__";
|
|
5
149
|
var globalSlot = globalThis;
|
|
6
150
|
var getState = () => {
|
|
7
151
|
let state = globalSlot[STATE_KEY];
|
|
8
152
|
if (!state) {
|
|
9
|
-
state = { config: null, manifestPromise: null, identities: null };
|
|
153
|
+
state = { config: null, manifestPromise: null, identities: null, integration: "core" };
|
|
10
154
|
globalSlot[STATE_KEY] = state;
|
|
11
155
|
}
|
|
12
156
|
return state;
|
|
@@ -84,22 +228,31 @@ var readIdentities = () => ({
|
|
|
84
228
|
});
|
|
85
229
|
|
|
86
230
|
// src/index.ts
|
|
87
|
-
var PREFIX = "[@mfe-orchestrator-hub/client]";
|
|
88
|
-
var NOT_CONFIGURED = `${PREFIX} call configure() first. Configure the client at the very top of the host entry point, before anything imports a remote:
|
|
89
|
-
|
|
90
|
-
import { configure } from "@mfe-orchestrator-hub/client"
|
|
91
|
-
|
|
92
|
-
configure({ backendUrl: "\u2026", projectId: "\u2026" })`;
|
|
93
231
|
var withoutTrailingSlash = (value) => value.replace(/\/+$/, "");
|
|
94
232
|
var resolveUserId = async (userId) => {
|
|
95
233
|
const value = typeof userId === "function" ? await userId() : userId;
|
|
96
234
|
return value === void 0 || value === null || value === "" ? void 0 : String(value);
|
|
97
235
|
};
|
|
98
236
|
var sameConfig = (left, right) => left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId;
|
|
99
|
-
var requireConfig = (config) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
237
|
+
var requireConfig = (config, integration) => {
|
|
238
|
+
if (typeof config !== "object" || config === null) {
|
|
239
|
+
throw new Error(noConfigMessage(config, integration));
|
|
240
|
+
}
|
|
241
|
+
const problems = diagnoseConfig(config);
|
|
242
|
+
if (problems.length > 0) {
|
|
243
|
+
throw new Error(invalidConfigMessage(problems, integration));
|
|
244
|
+
}
|
|
245
|
+
if (config.environment !== void 0 && config.environment !== null && typeof config.environment !== "string") {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`${PREFIX} configure() received environment as a ${typeof config.environment}, not a string. Pass the environment slug as it appears in the console, ex. "DEV", or leave it out to let the backend resolve it from the domain of this page.`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
var warnAboutConfig = (config) => {
|
|
252
|
+
if (typeof config.environment === "string" && config.environment.trim() === "") {
|
|
253
|
+
console.warn(
|
|
254
|
+
`${PREFIX} configure() received an empty environment, so the client falls back to the auto routes and the backend resolves the environment from the domain of this page. Remove the option if that is what you want, or check the variable that feeds it.`
|
|
255
|
+
);
|
|
103
256
|
}
|
|
104
257
|
};
|
|
105
258
|
var manifestPath = (config) => {
|
|
@@ -115,11 +268,25 @@ var manifestUrl = async (config) => {
|
|
|
115
268
|
}
|
|
116
269
|
return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`;
|
|
117
270
|
};
|
|
271
|
+
var statusHint = (status, config) => {
|
|
272
|
+
if (status === 404) {
|
|
273
|
+
const environment = config.environment ? `the environment "${config.environment}" does not exist in it` : "no environment of that project declares the domain this page is served on, which is what the auto route resolves on";
|
|
274
|
+
return `
|
|
275
|
+
|
|
276
|
+
A 404 on this route means the backend did not find what the URL names: either the project id "${config.projectId}" is not a project of this console, or ${environment}. Check both in the console, and check that backendUrl points at the console API of the same instance.`;
|
|
277
|
+
}
|
|
278
|
+
if (status === 401 || status === 403) {
|
|
279
|
+
return `
|
|
280
|
+
|
|
281
|
+
The console refused the request. Check that the project id "${config.projectId}" belongs to the instance backendUrl points at, and that this domain is allowed to read its manifest.`;
|
|
282
|
+
}
|
|
283
|
+
return "";
|
|
284
|
+
};
|
|
118
285
|
var fetchManifest = async (config) => {
|
|
119
286
|
const url = await manifestUrl(config);
|
|
120
287
|
const response = await fetch(url, { headers: { accept: "application/json" } });
|
|
121
288
|
if (!response.ok) {
|
|
122
|
-
throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}`);
|
|
289
|
+
throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}${statusHint(response.status, config)}`);
|
|
123
290
|
}
|
|
124
291
|
const payload = await response.json();
|
|
125
292
|
return {
|
|
@@ -128,8 +295,8 @@ var fetchManifest = async (config) => {
|
|
|
128
295
|
};
|
|
129
296
|
};
|
|
130
297
|
var configure = (config) => {
|
|
131
|
-
requireConfig(config);
|
|
132
298
|
const state = getState();
|
|
299
|
+
requireConfig(config, state.integration);
|
|
133
300
|
const current = state.config;
|
|
134
301
|
if (current) {
|
|
135
302
|
if (!sameConfig(current, config)) {
|
|
@@ -140,12 +307,16 @@ var configure = (config) => {
|
|
|
140
307
|
return;
|
|
141
308
|
}
|
|
142
309
|
state.config = { ...config };
|
|
310
|
+
warnAboutConfig(config);
|
|
311
|
+
};
|
|
312
|
+
var registerIntegration = (integration) => {
|
|
313
|
+
getState().integration = integration;
|
|
143
314
|
};
|
|
144
|
-
var
|
|
315
|
+
var loadManifest = (caller) => {
|
|
145
316
|
const state = getState();
|
|
146
317
|
const config = state.config;
|
|
147
318
|
if (!config) {
|
|
148
|
-
return Promise.reject(new Error(
|
|
319
|
+
return Promise.reject(new Error(notConfiguredMessage(caller, state.integration)));
|
|
149
320
|
}
|
|
150
321
|
if (!state.manifestPromise) {
|
|
151
322
|
const attempt = fetchManifest(config);
|
|
@@ -158,8 +329,9 @@ var manifest = () => {
|
|
|
158
329
|
}
|
|
159
330
|
return state.manifestPromise;
|
|
160
331
|
};
|
|
332
|
+
var manifest = () => loadManifest("manifest()");
|
|
161
333
|
var remoteUrl = async (slug) => {
|
|
162
|
-
const { microfrontends } = await
|
|
334
|
+
const { microfrontends } = await loadManifest(`remoteUrl("${slug}")`);
|
|
163
335
|
const found = microfrontends.find((microfrontend) => microfrontend.slug === slug);
|
|
164
336
|
if (!found) {
|
|
165
337
|
const available = microfrontends.map((microfrontend) => `"${microfrontend.slug}"`).join(", ");
|
|
@@ -173,7 +345,7 @@ var remoteUrl = async (slug) => {
|
|
|
173
345
|
return found.url;
|
|
174
346
|
};
|
|
175
347
|
var globalVariables = async () => {
|
|
176
|
-
const { globalVariables: variables } = await
|
|
348
|
+
const { globalVariables: variables } = await loadManifest("globalVariables()");
|
|
177
349
|
const result = {};
|
|
178
350
|
for (const variable of variables) {
|
|
179
351
|
if (variable && typeof variable.key === "string") {
|
|
@@ -194,6 +366,7 @@ exports.configure = configure;
|
|
|
194
366
|
exports.globalVariables = globalVariables;
|
|
195
367
|
exports.identities = identities;
|
|
196
368
|
exports.manifest = manifest;
|
|
369
|
+
exports.registerIntegration = registerIntegration;
|
|
197
370
|
exports.remoteUrl = remoteUrl;
|
|
198
371
|
//# sourceMappingURL=index.cjs.map
|
|
199
372
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/state.ts","../src/storage.ts","../src/index.ts"],"names":["created"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport interface OrchestratorState {\n config: OrchestratorConfig | null\n manifestPromise: Promise<Manifest> | null\n identities: Identities | null\n}\n\n/**\n * The state lives on `globalThis` under this key, not in a module scope variable.\n *\n * A host can end up with two copies of this package loaded at once: the app imports the ESM build\n * while something else pulls the CJS one, or the bundler config and the app resolve to different\n * chunks. Two module scopes would mean two configurations and two manifest requests, which is\n * exactly what the contract forbids. One well known global slot makes the singleton real.\n */\nexport const STATE_KEY = \"__mfe_orchestrator_client__\"\n\nconst globalSlot = globalThis as typeof globalThis & Record<typeof STATE_KEY, OrchestratorState | undefined>\n\nexport const getState = (): OrchestratorState => {\n let state = globalSlot[STATE_KEY]\n if (!state) {\n state = { config: null, manifestPromise: null, identities: null }\n globalSlot[STATE_KEY] = state\n }\n return state\n}\n","import type { Identities } from \"./types\"\n\nexport const SESSION_ID_KEY = \"mfe-orchestrator.sessionId\"\nexport const DEVICE_ID_KEY = \"mfe-orchestrator.deviceId\"\n\nconst PROBE_KEY = \"mfe-orchestrator.probe\"\n\ntype StorageKind = \"local\" | \"session\"\n\n/**\n * Ids minted while storage was unusable. They live for the lifetime of the page so that, even\n * without storage, every request of this page load carries one stable pair of ids.\n */\nconst inMemoryIds = new Map<string, string>()\n\nconst uuidFromRandomBytes = (bytes: Uint8Array): string => {\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n const hex: string[] = []\n for (const byte of bytes) {\n hex.push(byte.toString(16).padStart(2, \"0\"))\n }\n return `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex.slice(6, 8).join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10, 16).join(\"\")}`\n}\n\n/**\n * A v4 UUID. `crypto.randomUUID` is the intended path; the two fallbacks only exist so that a page\n * served over plain http, or an exotic runtime, still gets an id instead of an exception.\n */\nexport const newId = (): string => {\n const cryptoApi = globalThis.crypto\n try {\n if (typeof cryptoApi?.randomUUID === \"function\") {\n return cryptoApi.randomUUID()\n }\n if (typeof cryptoApi?.getRandomValues === \"function\") {\n return uuidFromRandomBytes(cryptoApi.getRandomValues(new Uint8Array(16)))\n }\n } catch {\n // fall through to the non cryptographic fallback\n }\n const bytes = new Uint8Array(16)\n for (let index = 0; index < bytes.length; index++) {\n bytes[index] = Math.floor(Math.random() * 256)\n }\n return uuidFromRandomBytes(bytes)\n}\n\n/**\n * The requested storage, or `null` when it is missing or unusable.\n *\n * Both the property access and the write can throw: Safari in private mode exposes the object and\n * then refuses `setItem`, some hardened browsers throw a SecurityError on the access itself. The\n * write probe is the only reliable way to tell a usable storage from a decorative one.\n */\nconst openStorage = (kind: StorageKind): Storage | null => {\n try {\n const storage = kind === \"local\" ? globalThis.localStorage : globalThis.sessionStorage\n if (!storage) {\n return null\n }\n storage.setItem(PROBE_KEY, \"1\")\n storage.removeItem(PROBE_KEY)\n return storage\n } catch {\n return null\n }\n}\n\n/**\n * The id under `key`, read from storage and created on first use. Falls back to an id kept in\n * memory when storage cannot be used, so this never throws out of the SDK.\n */\nconst persistentId = (kind: StorageKind, key: string): string => {\n const storage = openStorage(kind)\n if (storage) {\n try {\n const stored = storage.getItem(key)\n if (stored) {\n return stored\n }\n const created = newId()\n storage.setItem(key, created)\n return created\n } catch {\n // storage passed the probe and failed anyway: keep going with an in memory id\n }\n }\n const remembered = inMemoryIds.get(key)\n if (remembered) {\n return remembered\n }\n const created = newId()\n inMemoryIds.set(key, created)\n return created\n}\n\n/** Reads, and creates when needed, the session and device ids. */\nexport const readIdentities = (): Identities => ({\n sessionId: persistentId(\"session\", SESSION_ID_KEY),\n deviceId: persistentId(\"local\", DEVICE_ID_KEY)\n})\n","import { getState } from \"./state\"\nimport { readIdentities } from \"./storage\"\nimport type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport type { GlobalVariable, Identities, Manifest, Microfrontend, OrchestratorConfig } from \"./types\"\n\nconst PREFIX = \"[@mfe-orchestrator-hub/client]\"\n\nconst NOT_CONFIGURED = `${PREFIX} call configure() first. Configure the client at the very top of the host entry point, before anything imports a remote:\n\n import { configure } from \"@mfe-orchestrator-hub/client\"\n\n configure({ backendUrl: \"…\", projectId: \"…\" })`\n\nconst withoutTrailingSlash = (value: string): string => value.replace(/\\/+$/, \"\")\n\nconst resolveUserId = async (userId: OrchestratorConfig[\"userId\"]): Promise<string | undefined> => {\n const value = typeof userId === \"function\" ? await userId() : userId\n return value === undefined || value === null || value === \"\" ? undefined : String(value)\n}\n\nconst sameConfig = (left: OrchestratorConfig, right: OrchestratorConfig): boolean =>\n left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId\n\nconst requireConfig = (config: OrchestratorConfig): void => {\n // environment is deliberately absent: without it the backend resolves it from the domain.\n const missing = ([\"backendUrl\", \"projectId\"] as const).filter(key => !config?.[key])\n if (missing.length > 0) {\n throw new Error(`${PREFIX} configure() is missing required option${missing.length > 1 ? \"s\" : \"\"}: ${missing.join(\", \")}.`)\n }\n}\n\n/**\n * Two shapes, one per route family. With an explicit environment the slug is the last segment; with\n * no environment the \"auto\" segment takes its place before the project id and the backend resolves\n * the environment from the domain the request comes from.\n */\nconst manifestPath = (config: OrchestratorConfig): string => {\n const projectId = encodeURIComponent(config.projectId)\n return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`\n}\n\nconst manifestUrl = async (config: OrchestratorConfig): Promise<string> => {\n const { sessionId, deviceId } = identities()\n const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId })\n // Resolved as late as possible: the host may only know the user after its own auth round trip.\n const userId = await resolveUserId(config.userId)\n if (userId) {\n query.set(\"mfeUserId\", userId)\n }\n return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`\n}\n\nconst fetchManifest = async (config: OrchestratorConfig): Promise<Manifest> => {\n const url = await manifestUrl(config)\n // No retry in v1: whatever fetch throws travels straight to the caller.\n const response = await fetch(url, { headers: { accept: \"application/json\" } })\n if (!response.ok) {\n throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}`)\n }\n const payload = (await response.json()) as Partial<Manifest> | null\n return {\n globalVariables: payload?.globalVariables ?? [],\n microfrontends: payload?.microfrontends ?? []\n }\n}\n\n/**\n * Hands the client its configuration. Call it once, synchronously, before any remote is imported.\n *\n * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use\n * the \"auto\" routes, where the backend resolves the environment from the domain of the host page.\n *\n * Idempotent: calling it again with the same options is a no op, so a framework provider may safely\n * call it on every render. A second call with different options is ignored, with a warning, because\n * the manifest of this page load may already be in flight or resolved.\n */\nexport const configure = (config: OrchestratorConfig): void => {\n requireConfig(config)\n const state = getState()\n const current = state.config\n if (current) {\n if (!sameConfig(current, config)) {\n console.warn(\n `${PREFIX} configure() was called again with a different configuration. The first one is kept: ${JSON.stringify({ backendUrl: current.backendUrl, projectId: current.projectId, environment: current.environment })}.`\n )\n }\n return\n }\n state.config = { ...config }\n}\n\n/**\n * The whole manifest of the environment, fetched once per page load and memoized as a promise, so\n * that concurrent callers share the single request in flight.\n *\n * A successful result is memoized for good. A failed attempt is not: the memo is dropped when the\n * shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry\n * — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the\n * page to reject every `remoteUrl()` until a full reload.\n */\nexport const manifest = (): Promise<Manifest> => {\n const state = getState()\n const config = state.config\n if (!config) {\n // Not memoized: the host may still configure the client and try again.\n return Promise.reject(new Error(NOT_CONFIGURED))\n }\n if (!state.manifestPromise) {\n // Assigned before the first await inside fetchManifest, which is what makes N concurrent\n // callers collapse onto one request.\n const attempt = fetchManifest(config)\n state.manifestPromise = attempt\n // Cleared once, when the shared attempt settles, not in each caller's own catch: every\n // caller already waiting on this attempt still gets its rejection. The identity check keeps\n // a late failure from evicting a newer attempt.\n attempt.catch(() => {\n if (state.manifestPromise === attempt) {\n state.manifestPromise = null\n }\n })\n }\n return state.manifestPromise\n}\n\n/**\n * The ready to use, version pinned URL of a remote. Awaits the manifest internally.\n *\n * The returned URL is used verbatim: the host never parses the version out of it and never strips\n * the `_v/<version>/` segment, or a classic script would load its chunks off another version.\n */\nexport const remoteUrl = async (slug: string): Promise<string> => {\n const { microfrontends } = await manifest()\n const found = microfrontends.find(microfrontend => microfrontend.slug === slug)\n if (!found) {\n const available = microfrontends.map(microfrontend => `\"${microfrontend.slug}\"`).join(\", \")\n const environment = getState().config?.environment\n // With no configured environment the host cannot name the one it was served: only the backend knows.\n const where = environment ? `environment \"${environment}\"` : \"the environment resolved from this domain\"\n throw new Error(`${PREFIX} unknown microfrontend slug \"${slug}\". Available in ${where}: ${available || \"none, this environment serves no microfrontend\"}.`)\n }\n if (!found.url) {\n throw new Error(`${PREFIX} the microfrontend \"${slug}\" is in the manifest but carries no url. Check its deployment in the console.`)\n }\n return found.url\n}\n\n/** The global variables of the environment, as a plain object. */\nexport const globalVariables = async (): Promise<Record<string, string>> => {\n const { globalVariables: variables } = await manifest()\n const result: Record<string, string> = {}\n for (const variable of variables) {\n if (variable && typeof variable.key === \"string\") {\n result[variable.key] = variable.value\n }\n }\n return result\n}\n\n/**\n * The session and device ids of this page, exposed for telemetry and debugging. Created on first\n * use and stable for the lifetime of the page, whether or not storage is available.\n */\nexport const identities = (): Identities => {\n const state = getState()\n if (!state.identities) {\n state.identities = readIdentities()\n }\n return state.identities\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/state.ts","../src/storage.ts","../src/index.ts"],"names":["created"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Everything the client says when its configuration is wrong or absent.\n *\n * The two required options almost always arrive from build time environment variables, so the\n * failure the host sees is not \"a value is invalid\" but \"nothing reached the bundle\". Messages here\n * are written for that moment: they name the option, show what actually arrived, say where the real\n * value comes from, and print the exact call to write, in the flavour of the package the host uses.\n */\n\nexport const PREFIX = \"[@mfe-orchestrator-hub/client]\"\n\n/** Which package the host went through. Only decides which snippet an error prints. */\nexport type Integration = \"core\" | \"react\" | \"vue\" | \"angular\"\n\n/** The two options without which nothing can be requested. */\nexport type RequiredOption = \"backendUrl\" | \"projectId\"\n\nconst EXAMPLE_BACKEND_URL = \"https://console.mfe-orchestrator.dev/api\"\nconst EXAMPLE_PROJECT_ID = \"6f1b2c3d4e5f6a7b8c9d0e1f\"\n\nconst SNIPPETS: Record<Integration, string> = {\n core: `import { configure } from \"@mfe-orchestrator-hub/client\"\n\nconfigure({\n backendUrl: \"${EXAMPLE_BACKEND_URL}\",\n projectId: \"${EXAMPLE_PROJECT_ID}\"\n})`,\n react: `import { OrchestratorProvider } from \"@mfe-orchestrator-hub/client-react\"\n\n<OrchestratorProvider config={{ backendUrl: \"${EXAMPLE_BACKEND_URL}\", projectId: \"${EXAMPLE_PROJECT_ID}\" }}>\n <App />\n</OrchestratorProvider>`,\n vue: `import { createOrchestrator } from \"@mfe-orchestrator-hub/client-vue\"\n\napp.use(createOrchestrator({ backendUrl: \"${EXAMPLE_BACKEND_URL}\", projectId: \"${EXAMPLE_PROJECT_ID}\" }))`,\n angular: `import { provideOrchestrator } from \"@mfe-orchestrator-hub/client-angular\"\n\nbootstrapApplication(AppComponent, {\n providers: [provideOrchestrator({ backendUrl: \"${EXAMPLE_BACKEND_URL}\", projectId: \"${EXAMPLE_PROJECT_ID}\" })]\n})`\n}\n\nconst WHERE: Record<Integration, string> = {\n core: \"Call it once, at the very top of the host entry point, before anything imports a remote.\",\n react: \"Render it above every component that resolves a remote. Better still, call configure() at the very top of the entry point: a bundler may import a remote before React mounts.\",\n vue: \"Install the plugin before mounting the app. Better still, call configure() at the very top of the entry point: a bundler may import a remote before the app is created.\",\n angular: \"Pass it to bootstrapApplication(). Better still, call configure() at the very top of main.ts: a lazily loaded remote must find the client already configured.\"\n}\n\nconst WHAT_IT_IS: Record<RequiredOption, string> = {\n backendUrl: `It is the base URL of the console API, protocol included, ex. \"${EXAMPLE_BACKEND_URL}\". A path on the same origin as the host, ex. \"/api\", works too.`,\n projectId: `It is the id of the project in the console: open the project and copy it from its page, ex. \"${EXAMPLE_PROJECT_ID}\".`\n}\n\n/** The name the value travels under from the bundler config into the bundle, per toolchain. */\nconst BUILD_VARIABLE: Record<RequiredOption, { vite: string; webpack: string }> = {\n backendUrl: { vite: \"VITE_MFE_BACKEND_URL\", webpack: \"MFE_BACKEND_URL\" },\n projectId: { vite: \"VITE_MFE_PROJECT_ID\", webpack: \"MFE_PROJECT_ID\" }\n}\n\n/**\n * What an option that was never substituted into the bundle looks like.\n *\n * The bundler config is named before the .env file on purpose: it is where the console writes both\n * values when it generates the config, so it is where they are missing from when they are missing.\n */\nconst missingEnvHint = (option: RequiredOption): string =>\n `Nothing arrived here at all, which is what a value that never reached the bundle looks like: check ${BUILD_VARIABLE[option].vite} (Vite) or ${BUILD_VARIABLE[option].webpack} (webpack) in the build that produced this bundle. The generated configs carry it in the \"define\" block of vite.config, or in the DefinePlugin of webpack.config, so look there first; declared only in a .env file the build never reads, it looks exactly like this.`\n\n/** Values that mean \"the template was never filled in\". */\nconst PLACEHOLDER = /^(…|\\.{3}|undefined|null|nan|<[^>]*>|\\{\\{[^}]*\\}\\}|\\$\\{[^}]*\\}|your[-_ ]?[a-z]*|x{3,}|change[-_ ]?me|todo|tbd)$/i\n\n/** Long values are cut: an error message is not the place to print a whole JWT. */\nconst quoted = (value: string): string => JSON.stringify(value.length > 120 ? `${value.slice(0, 120)}…` : value)\n\nconst described = (value: unknown): string => {\n if (value === null) {\n return \"null\"\n }\n if (Array.isArray(value)) {\n return \"an array\"\n }\n const type = typeof value\n return type === \"object\" || type === \"function\" ? `a ${type}` : `a ${type} (${String(value)})`\n}\n\n/** Same key modulo case and separators: projectID, project_id, PROJECT-ID all collapse onto projectid. */\nconst normalized = (key: string): string => key.toLowerCase().replace(/[^a-z0-9]/g, \"\")\n\nconst ALIASES: Record<RequiredOption, string[]> = {\n backendUrl: [\"backendurl\", \"backendbaseurl\", \"apiurl\", \"baseurl\", \"consoleurl\", \"url\", \"backend\"],\n projectId: [\"projectid\", \"project\", \"idproject\", \"projectuuid\", \"projectkey\"]\n}\n\n/**\n * The key the host actually wrote, when it looks like a misspelling of the one that is missing.\n * `projectID` is by far the most common: it is silently ignored, so without this the message would\n * claim the option is absent while the developer stares at a line that clearly sets it.\n */\nconst confusableKey = (option: RequiredOption, config: unknown): string | undefined => {\n if (typeof config !== \"object\" || config === null) {\n return undefined\n }\n const candidates = ALIASES[option]\n return Object.keys(config).find(key => key !== option && candidates.includes(normalized(key)))\n}\n\n/** One thing that is wrong with one option, already spelled out. */\nexport interface ConfigProblem {\n option: RequiredOption\n /** Predicate completing \"<option> …\", ex. `is missing`. */\n what: string\n /** Extra lines, printed under the option, that say how to fix this particular case. */\n hints: string[]\n}\n\nconst problem = (option: RequiredOption, what: string, ...hints: (string | undefined)[]): ConfigProblem => ({\n option,\n what,\n hints: [WHAT_IT_IS[option], ...hints].filter((hint): hint is string => Boolean(hint))\n})\n\n/**\n * A backendUrl the client can actually build a request on: an absolute http(s) URL, or a path on the\n * host's own origin, which `fetch` resolves against the page.\n */\nconst diagnoseBackendUrl = (value: string): ConfigProblem | null => {\n const trimmed = value.trim()\n if (trimmed.startsWith(\"/\")) {\n return null\n }\n let parsed: URL\n try {\n parsed = new URL(trimmed)\n } catch {\n return problem(\n \"backendUrl\",\n `is ${quoted(value)}, which is not a URL`,\n trimmed.includes(\".\") || trimmed.includes(\":\")\n ? `It looks like a host without a protocol: write \"https://${trimmed}\" instead, or \"/${trimmed.replace(/^\\/+/, \"\")}\" if the console API answers on the same origin as this page.`\n : undefined\n )\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return problem(\n \"backendUrl\",\n `is ${quoted(value)}, whose \"${parsed.protocol.replace(\":\", \"\")}\" scheme is neither http nor https`,\n 'A bare \"host:port\" is read as a scheme: write \"http://localhost:3000/api\", not \"localhost:3000/api\".'\n )\n }\n return null\n}\n\n/** Everything wrong with one required option, or null when it is usable. */\nconst diagnose = (option: RequiredOption, config: unknown): ConfigProblem | null => {\n const value = (config as Record<string, unknown> | null | undefined)?.[option]\n const typo = confusableKey(option, config)\n const typoHint = typo ? `The configuration carries \"${typo}\", which the client ignores. The option is spelled \"${option}\".` : undefined\n\n if (value === undefined || value === null) {\n return problem(option, value === null ? \"is null\" : \"is missing\", typoHint, typo ? undefined : missingEnvHint(option))\n }\n if (typeof value !== \"string\") {\n return problem(option, `is ${described(value)}, not a string`, typoHint)\n }\n if (value.trim() === \"\") {\n return problem(option, value === \"\" ? \"is an empty string\" : \"holds nothing but whitespace\", typoHint, missingEnvHint(option))\n }\n if (PLACEHOLDER.test(value.trim())) {\n const looksLikeAnUnsetVariable = /^(undefined|null|nan)$/i.test(value.trim())\n return problem(option, `is still the placeholder ${quoted(value)}`, typoHint, looksLikeAnUnsetVariable ? missingEnvHint(option) : undefined)\n }\n if (option === \"projectId\" && value.trim() !== value) {\n // Percent encoded into the path, so the padding would travel to the backend as %20.\n return problem(\n \"projectId\",\n `is ${quoted(value)}, padded with whitespace`,\n \"Trim the value: it is percent encoded into the manifest URL, so the spaces would reach the backend as part of the id.\"\n )\n }\n return option === \"backendUrl\" ? diagnoseBackendUrl(value) : null\n}\n\n/** Every problem of a configuration, in the order the options are declared. Empty means usable. */\nexport const diagnoseConfig = (config: unknown): ConfigProblem[] =>\n ([\"backendUrl\", \"projectId\"] as const).map(option => diagnose(option, config)).filter((found): found is ConfigProblem => found !== null)\n\nconst bulleted = (problems: ConfigProblem[]): string => problems.map(({ option, what, hints }) => [` • ${option} ${what}.`, ...hints.map(hint => ` → ${hint}`)].join(\"\\n\")).join(\"\\n\")\n\nconst indented = (block: string): string =>\n block\n .split(\"\\n\")\n .map(line => (line ? ` ${line}` : line))\n .join(\"\\n\")\n\nconst howToConfigure = (integration: Integration): string => `Configure the client like this:\\n\\n${indented(SNIPPETS[integration])}\\n\\n${WHERE[integration]}`\n\nconst listed = (options: RequiredOption[]): string => (options.length > 1 ? `${options.slice(0, -1).join(\", \")} and ${options.at(-1)}` : String(options[0]))\n\n/** The message thrown by `configure()` when what it received cannot be used. */\nexport const invalidConfigMessage = (problems: ConfigProblem[], integration: Integration): string => {\n const options = problems.map(({ option }) => option)\n const headline = `${PREFIX} configure() cannot start the client: ${listed(options)} ${options.length > 1 ? \"are\" : \"is\"} not usable.`\n return `${headline}\\n\\n${bulleted(problems)}\\n\\n${howToConfigure(integration)}`\n}\n\n/** The message thrown by `configure()` when it did not even receive an object. */\nexport const noConfigMessage = (config: unknown, integration: Integration): string =>\n `${PREFIX} configure() was called with ${config === undefined ? \"no argument\" : described(config)} instead of a configuration object. It needs at least backendUrl and projectId.\\n\\n${howToConfigure(integration)}`\n\n/** The message every read rejects with while the client has no configuration at all. */\nexport const notConfiguredMessage = (caller: string, integration: Integration): string =>\n `${PREFIX} the client is not configured: ${caller} was called before configure() ever ran, so neither backendUrl nor projectId is known and no manifest can be requested.\\n\\n${howToConfigure(integration)}`\n","import type { Integration } from \"./errors\"\nimport type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport interface OrchestratorState {\n config: OrchestratorConfig | null\n manifestPromise: Promise<Manifest> | null\n identities: Identities | null\n /** Set by the framework adapters as they load, so an error prints the snippet of their own API. */\n integration: Integration\n}\n\n/**\n * The state lives on `globalThis` under this key, not in a module scope variable.\n *\n * A host can end up with two copies of this package loaded at once: the app imports the ESM build\n * while something else pulls the CJS one, or the bundler config and the app resolve to different\n * chunks. Two module scopes would mean two configurations and two manifest requests, which is\n * exactly what the contract forbids. One well known global slot makes the singleton real.\n */\nexport const STATE_KEY = \"__mfe_orchestrator_client__\"\n\nconst globalSlot = globalThis as typeof globalThis & Record<typeof STATE_KEY, OrchestratorState | undefined>\n\nexport const getState = (): OrchestratorState => {\n let state = globalSlot[STATE_KEY]\n if (!state) {\n state = { config: null, manifestPromise: null, identities: null, integration: \"core\" }\n globalSlot[STATE_KEY] = state\n }\n return state\n}\n","import type { Identities } from \"./types\"\n\nexport const SESSION_ID_KEY = \"mfe-orchestrator.sessionId\"\nexport const DEVICE_ID_KEY = \"mfe-orchestrator.deviceId\"\n\nconst PROBE_KEY = \"mfe-orchestrator.probe\"\n\ntype StorageKind = \"local\" | \"session\"\n\n/**\n * Ids minted while storage was unusable. They live for the lifetime of the page so that, even\n * without storage, every request of this page load carries one stable pair of ids.\n */\nconst inMemoryIds = new Map<string, string>()\n\nconst uuidFromRandomBytes = (bytes: Uint8Array): string => {\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n const hex: string[] = []\n for (const byte of bytes) {\n hex.push(byte.toString(16).padStart(2, \"0\"))\n }\n return `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex.slice(6, 8).join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10, 16).join(\"\")}`\n}\n\n/**\n * A v4 UUID. `crypto.randomUUID` is the intended path; the two fallbacks only exist so that a page\n * served over plain http, or an exotic runtime, still gets an id instead of an exception.\n */\nexport const newId = (): string => {\n const cryptoApi = globalThis.crypto\n try {\n if (typeof cryptoApi?.randomUUID === \"function\") {\n return cryptoApi.randomUUID()\n }\n if (typeof cryptoApi?.getRandomValues === \"function\") {\n return uuidFromRandomBytes(cryptoApi.getRandomValues(new Uint8Array(16)))\n }\n } catch {\n // fall through to the non cryptographic fallback\n }\n const bytes = new Uint8Array(16)\n for (let index = 0; index < bytes.length; index++) {\n bytes[index] = Math.floor(Math.random() * 256)\n }\n return uuidFromRandomBytes(bytes)\n}\n\n/**\n * The requested storage, or `null` when it is missing or unusable.\n *\n * Both the property access and the write can throw: Safari in private mode exposes the object and\n * then refuses `setItem`, some hardened browsers throw a SecurityError on the access itself. The\n * write probe is the only reliable way to tell a usable storage from a decorative one.\n */\nconst openStorage = (kind: StorageKind): Storage | null => {\n try {\n const storage = kind === \"local\" ? globalThis.localStorage : globalThis.sessionStorage\n if (!storage) {\n return null\n }\n storage.setItem(PROBE_KEY, \"1\")\n storage.removeItem(PROBE_KEY)\n return storage\n } catch {\n return null\n }\n}\n\n/**\n * The id under `key`, read from storage and created on first use. Falls back to an id kept in\n * memory when storage cannot be used, so this never throws out of the SDK.\n */\nconst persistentId = (kind: StorageKind, key: string): string => {\n const storage = openStorage(kind)\n if (storage) {\n try {\n const stored = storage.getItem(key)\n if (stored) {\n return stored\n }\n const created = newId()\n storage.setItem(key, created)\n return created\n } catch {\n // storage passed the probe and failed anyway: keep going with an in memory id\n }\n }\n const remembered = inMemoryIds.get(key)\n if (remembered) {\n return remembered\n }\n const created = newId()\n inMemoryIds.set(key, created)\n return created\n}\n\n/** Reads, and creates when needed, the session and device ids. */\nexport const readIdentities = (): Identities => ({\n sessionId: persistentId(\"session\", SESSION_ID_KEY),\n deviceId: persistentId(\"local\", DEVICE_ID_KEY)\n})\n","import { diagnoseConfig, type Integration, invalidConfigMessage, noConfigMessage, notConfiguredMessage, PREFIX } from \"./errors\"\nimport { getState } from \"./state\"\nimport { readIdentities } from \"./storage\"\nimport type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport type { ConfigProblem, Integration } from \"./errors\"\nexport type { GlobalVariable, Identities, Manifest, Microfrontend, OrchestratorConfig } from \"./types\"\n\nconst withoutTrailingSlash = (value: string): string => value.replace(/\\/+$/, \"\")\n\nconst resolveUserId = async (userId: OrchestratorConfig[\"userId\"]): Promise<string | undefined> => {\n const value = typeof userId === \"function\" ? await userId() : userId\n return value === undefined || value === null || value === \"\" ? undefined : String(value)\n}\n\nconst sameConfig = (left: OrchestratorConfig, right: OrchestratorConfig): boolean =>\n left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId\n\n/**\n * Rejects a configuration the client could not use, with a message that names the option, shows what\n * actually arrived and prints the call to write. environment is deliberately not required: without\n * it the backend resolves it from the domain. It is still checked for type, because a non string\n * there would silently end up percent encoded into the path.\n */\nconst requireConfig = (config: OrchestratorConfig, integration: Integration): void => {\n if (typeof config !== \"object\" || config === null) {\n throw new Error(noConfigMessage(config, integration))\n }\n const problems = diagnoseConfig(config)\n if (problems.length > 0) {\n throw new Error(invalidConfigMessage(problems, integration))\n }\n if (config.environment !== undefined && config.environment !== null && typeof config.environment !== \"string\") {\n throw new Error(\n `${PREFIX} configure() received environment as a ${typeof config.environment}, not a string. Pass the environment slug as it appears in the console, ex. \"DEV\", or leave it out to let the backend resolve it from the domain of this page.`\n )\n }\n}\n\n/** Notices about a stored configuration that works but probably does not do what the host meant. */\nconst warnAboutConfig = (config: OrchestratorConfig): void => {\n if (typeof config.environment === \"string\" && config.environment.trim() === \"\") {\n console.warn(\n `${PREFIX} configure() received an empty environment, so the client falls back to the auto routes and the backend resolves the environment from the domain of this page. Remove the option if that is what you want, or check the variable that feeds it.`\n )\n }\n}\n\n/**\n * Two shapes, one per route family. With an explicit environment the slug is the last segment; with\n * no environment the \"auto\" segment takes its place before the project id and the backend resolves\n * the environment from the domain the request comes from.\n */\nconst manifestPath = (config: OrchestratorConfig): string => {\n const projectId = encodeURIComponent(config.projectId)\n return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`\n}\n\nconst manifestUrl = async (config: OrchestratorConfig): Promise<string> => {\n const { sessionId, deviceId } = identities()\n const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId })\n // Resolved as late as possible: the host may only know the user after its own auth round trip.\n const userId = await resolveUserId(config.userId)\n if (userId) {\n query.set(\"mfeUserId\", userId)\n }\n return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`\n}\n\n/**\n * What a status usually means on this route. The backend answers the same 404 for an unknown project\n * and for an unknown environment, and the two are indistinguishable from here, so both are named.\n */\nconst statusHint = (status: number, config: OrchestratorConfig): string => {\n if (status === 404) {\n const environment = config.environment\n ? `the environment \"${config.environment}\" does not exist in it`\n : \"no environment of that project declares the domain this page is served on, which is what the auto route resolves on\"\n return `\\n\\nA 404 on this route means the backend did not find what the URL names: either the project id \"${config.projectId}\" is not a project of this console, or ${environment}. Check both in the console, and check that backendUrl points at the console API of the same instance.`\n }\n if (status === 401 || status === 403) {\n return `\\n\\nThe console refused the request. Check that the project id \"${config.projectId}\" belongs to the instance backendUrl points at, and that this domain is allowed to read its manifest.`\n }\n return \"\"\n}\n\nconst fetchManifest = async (config: OrchestratorConfig): Promise<Manifest> => {\n const url = await manifestUrl(config)\n // No retry in v1: whatever fetch throws travels straight to the caller.\n const response = await fetch(url, { headers: { accept: \"application/json\" } })\n if (!response.ok) {\n throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}${statusHint(response.status, config)}`)\n }\n const payload = (await response.json()) as Partial<Manifest> | null\n return {\n globalVariables: payload?.globalVariables ?? [],\n microfrontends: payload?.microfrontends ?? []\n }\n}\n\n/**\n * Hands the client its configuration. Call it once, synchronously, before any remote is imported.\n *\n * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use\n * the \"auto\" routes, where the backend resolves the environment from the domain of the host page.\n *\n * Idempotent: calling it again with the same options is a no op, so a framework provider may safely\n * call it on every render. A second call with different options is ignored, with a warning, because\n * the manifest of this page load may already be in flight or resolved.\n */\nexport const configure = (config: OrchestratorConfig): void => {\n const state = getState()\n requireConfig(config, state.integration)\n const current = state.config\n if (current) {\n if (!sameConfig(current, config)) {\n console.warn(\n `${PREFIX} configure() was called again with a different configuration. The first one is kept: ${JSON.stringify({ backendUrl: current.backendUrl, projectId: current.projectId, environment: current.environment })}.`\n )\n }\n return\n }\n state.config = { ...config }\n warnAboutConfig(config)\n}\n\n/**\n * @internal Lets a framework adapter say which package the host is really using, so a configuration\n * error prints the snippet of that API instead of the bare `configure()` one. Idempotent, and never\n * needed by an application: the adapters call it as they load.\n */\nexport const registerIntegration = (integration: Integration): void => {\n getState().integration = integration\n}\n\n/**\n * The whole manifest of the environment, fetched once per page load and memoized as a promise, so\n * that concurrent callers share the single request in flight.\n *\n * A successful result is memoized for good. A failed attempt is not: the memo is dropped when the\n * shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry\n * — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the\n * page to reject every `remoteUrl()` until a full reload.\n */\nconst loadManifest = (caller: string): Promise<Manifest> => {\n const state = getState()\n const config = state.config\n if (!config) {\n // Not memoized: the host may still configure the client and try again.\n return Promise.reject(new Error(notConfiguredMessage(caller, state.integration)))\n }\n if (!state.manifestPromise) {\n // Assigned before the first await inside fetchManifest, which is what makes N concurrent\n // callers collapse onto one request.\n const attempt = fetchManifest(config)\n state.manifestPromise = attempt\n // Cleared once, when the shared attempt settles, not in each caller's own catch: every\n // caller already waiting on this attempt still gets its rejection. The identity check keeps\n // a late failure from evicting a newer attempt.\n attempt.catch(() => {\n if (state.manifestPromise === attempt) {\n state.manifestPromise = null\n }\n })\n }\n return state.manifestPromise\n}\n\n/** The whole manifest of the environment. See `loadManifest` for the memoization contract. */\nexport const manifest = (): Promise<Manifest> => loadManifest(\"manifest()\")\n\n/**\n * The ready to use, version pinned URL of a remote. Awaits the manifest internally.\n *\n * The returned URL is used verbatim: the host never parses the version out of it and never strips\n * the `_v/<version>/` segment, or a classic script would load its chunks off another version.\n */\nexport const remoteUrl = async (slug: string): Promise<string> => {\n const { microfrontends } = await loadManifest(`remoteUrl(\"${slug}\")`)\n const found = microfrontends.find(microfrontend => microfrontend.slug === slug)\n if (!found) {\n const available = microfrontends.map(microfrontend => `\"${microfrontend.slug}\"`).join(\", \")\n const environment = getState().config?.environment\n // With no configured environment the host cannot name the one it was served: only the backend knows.\n const where = environment ? `environment \"${environment}\"` : \"the environment resolved from this domain\"\n throw new Error(`${PREFIX} unknown microfrontend slug \"${slug}\". Available in ${where}: ${available || \"none, this environment serves no microfrontend\"}.`)\n }\n if (!found.url) {\n throw new Error(`${PREFIX} the microfrontend \"${slug}\" is in the manifest but carries no url. Check its deployment in the console.`)\n }\n return found.url\n}\n\n/** The global variables of the environment, as a plain object. */\nexport const globalVariables = async (): Promise<Record<string, string>> => {\n const { globalVariables: variables } = await loadManifest(\"globalVariables()\")\n const result: Record<string, string> = {}\n for (const variable of variables) {\n if (variable && typeof variable.key === \"string\") {\n result[variable.key] = variable.value\n }\n }\n return result\n}\n\n/**\n * The session and device ids of this page, exposed for telemetry and debugging. Created on first\n * use and stable for the lifetime of the page, whether or not storage is available.\n */\nexport const identities = (): Identities => {\n const state = getState()\n if (!state.identities) {\n state.identities = readIdentities()\n }\n return state.identities\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/** Which package the host went through. Only decides which snippet an error prints. */
|
|
2
|
+
type Integration = "core" | "react" | "vue" | "angular";
|
|
3
|
+
/** The two options without which nothing can be requested. */
|
|
4
|
+
type RequiredOption = "backendUrl" | "projectId";
|
|
5
|
+
/** One thing that is wrong with one option, already spelled out. */
|
|
6
|
+
interface ConfigProblem {
|
|
7
|
+
option: RequiredOption;
|
|
8
|
+
/** Predicate completing "<option> …", ex. `is missing`. */
|
|
9
|
+
what: string;
|
|
10
|
+
/** Extra lines, printed under the option, that say how to fix this particular case. */
|
|
11
|
+
hints: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
/**
|
|
2
15
|
* Configuration of the orchestrator client. Handed over once, at the very top of the host entry point.
|
|
3
16
|
*/
|
|
@@ -64,14 +77,12 @@ interface Identities {
|
|
|
64
77
|
*/
|
|
65
78
|
declare const configure: (config: OrchestratorConfig) => void;
|
|
66
79
|
/**
|
|
67
|
-
*
|
|
68
|
-
* that
|
|
69
|
-
*
|
|
70
|
-
* A successful result is memoized for good. A failed attempt is not: the memo is dropped when the
|
|
71
|
-
* shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry
|
|
72
|
-
* — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the
|
|
73
|
-
* page to reject every `remoteUrl()` until a full reload.
|
|
80
|
+
* @internal Lets a framework adapter say which package the host is really using, so a configuration
|
|
81
|
+
* error prints the snippet of that API instead of the bare `configure()` one. Idempotent, and never
|
|
82
|
+
* needed by an application: the adapters call it as they load.
|
|
74
83
|
*/
|
|
84
|
+
declare const registerIntegration: (integration: Integration) => void;
|
|
85
|
+
/** The whole manifest of the environment. See `loadManifest` for the memoization contract. */
|
|
75
86
|
declare const manifest: () => Promise<Manifest>;
|
|
76
87
|
/**
|
|
77
88
|
* The ready to use, version pinned URL of a remote. Awaits the manifest internally.
|
|
@@ -88,4 +99,4 @@ declare const globalVariables: () => Promise<Record<string, string>>;
|
|
|
88
99
|
*/
|
|
89
100
|
declare const identities: () => Identities;
|
|
90
101
|
|
|
91
|
-
export { type GlobalVariable, type Identities, type Manifest, type Microfrontend, type OrchestratorConfig, configure, globalVariables, identities, manifest, remoteUrl };
|
|
102
|
+
export { type ConfigProblem, type GlobalVariable, type Identities, type Integration, type Manifest, type Microfrontend, type OrchestratorConfig, configure, globalVariables, identities, manifest, registerIntegration, remoteUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/** Which package the host went through. Only decides which snippet an error prints. */
|
|
2
|
+
type Integration = "core" | "react" | "vue" | "angular";
|
|
3
|
+
/** The two options without which nothing can be requested. */
|
|
4
|
+
type RequiredOption = "backendUrl" | "projectId";
|
|
5
|
+
/** One thing that is wrong with one option, already spelled out. */
|
|
6
|
+
interface ConfigProblem {
|
|
7
|
+
option: RequiredOption;
|
|
8
|
+
/** Predicate completing "<option> …", ex. `is missing`. */
|
|
9
|
+
what: string;
|
|
10
|
+
/** Extra lines, printed under the option, that say how to fix this particular case. */
|
|
11
|
+
hints: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
/**
|
|
2
15
|
* Configuration of the orchestrator client. Handed over once, at the very top of the host entry point.
|
|
3
16
|
*/
|
|
@@ -64,14 +77,12 @@ interface Identities {
|
|
|
64
77
|
*/
|
|
65
78
|
declare const configure: (config: OrchestratorConfig) => void;
|
|
66
79
|
/**
|
|
67
|
-
*
|
|
68
|
-
* that
|
|
69
|
-
*
|
|
70
|
-
* A successful result is memoized for good. A failed attempt is not: the memo is dropped when the
|
|
71
|
-
* shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry
|
|
72
|
-
* — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the
|
|
73
|
-
* page to reject every `remoteUrl()` until a full reload.
|
|
80
|
+
* @internal Lets a framework adapter say which package the host is really using, so a configuration
|
|
81
|
+
* error prints the snippet of that API instead of the bare `configure()` one. Idempotent, and never
|
|
82
|
+
* needed by an application: the adapters call it as they load.
|
|
74
83
|
*/
|
|
84
|
+
declare const registerIntegration: (integration: Integration) => void;
|
|
85
|
+
/** The whole manifest of the environment. See `loadManifest` for the memoization contract. */
|
|
75
86
|
declare const manifest: () => Promise<Manifest>;
|
|
76
87
|
/**
|
|
77
88
|
* The ready to use, version pinned URL of a remote. Awaits the manifest internally.
|
|
@@ -88,4 +99,4 @@ declare const globalVariables: () => Promise<Record<string, string>>;
|
|
|
88
99
|
*/
|
|
89
100
|
declare const identities: () => Identities;
|
|
90
101
|
|
|
91
|
-
export { type GlobalVariable, type Identities, type Manifest, type Microfrontend, type OrchestratorConfig, configure, globalVariables, identities, manifest, remoteUrl };
|
|
102
|
+
export { type ConfigProblem, type GlobalVariable, type Identities, type Integration, type Manifest, type Microfrontend, type OrchestratorConfig, configure, globalVariables, identities, manifest, registerIntegration, remoteUrl };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,154 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var PREFIX = "[@mfe-orchestrator-hub/client]";
|
|
3
|
+
var EXAMPLE_BACKEND_URL = "https://console.mfe-orchestrator.dev/api";
|
|
4
|
+
var EXAMPLE_PROJECT_ID = "6f1b2c3d4e5f6a7b8c9d0e1f";
|
|
5
|
+
var SNIPPETS = {
|
|
6
|
+
core: `import { configure } from "@mfe-orchestrator-hub/client"
|
|
7
|
+
|
|
8
|
+
configure({
|
|
9
|
+
backendUrl: "${EXAMPLE_BACKEND_URL}",
|
|
10
|
+
projectId: "${EXAMPLE_PROJECT_ID}"
|
|
11
|
+
})`,
|
|
12
|
+
react: `import { OrchestratorProvider } from "@mfe-orchestrator-hub/client-react"
|
|
13
|
+
|
|
14
|
+
<OrchestratorProvider config={{ backendUrl: "${EXAMPLE_BACKEND_URL}", projectId: "${EXAMPLE_PROJECT_ID}" }}>
|
|
15
|
+
<App />
|
|
16
|
+
</OrchestratorProvider>`,
|
|
17
|
+
vue: `import { createOrchestrator } from "@mfe-orchestrator-hub/client-vue"
|
|
18
|
+
|
|
19
|
+
app.use(createOrchestrator({ backendUrl: "${EXAMPLE_BACKEND_URL}", projectId: "${EXAMPLE_PROJECT_ID}" }))`,
|
|
20
|
+
angular: `import { provideOrchestrator } from "@mfe-orchestrator-hub/client-angular"
|
|
21
|
+
|
|
22
|
+
bootstrapApplication(AppComponent, {
|
|
23
|
+
providers: [provideOrchestrator({ backendUrl: "${EXAMPLE_BACKEND_URL}", projectId: "${EXAMPLE_PROJECT_ID}" })]
|
|
24
|
+
})`
|
|
25
|
+
};
|
|
26
|
+
var WHERE = {
|
|
27
|
+
core: "Call it once, at the very top of the host entry point, before anything imports a remote.",
|
|
28
|
+
react: "Render it above every component that resolves a remote. Better still, call configure() at the very top of the entry point: a bundler may import a remote before React mounts.",
|
|
29
|
+
vue: "Install the plugin before mounting the app. Better still, call configure() at the very top of the entry point: a bundler may import a remote before the app is created.",
|
|
30
|
+
angular: "Pass it to bootstrapApplication(). Better still, call configure() at the very top of main.ts: a lazily loaded remote must find the client already configured."
|
|
31
|
+
};
|
|
32
|
+
var WHAT_IT_IS = {
|
|
33
|
+
backendUrl: `It is the base URL of the console API, protocol included, ex. "${EXAMPLE_BACKEND_URL}". A path on the same origin as the host, ex. "/api", works too.`,
|
|
34
|
+
projectId: `It is the id of the project in the console: open the project and copy it from its page, ex. "${EXAMPLE_PROJECT_ID}".`
|
|
35
|
+
};
|
|
36
|
+
var BUILD_VARIABLE = {
|
|
37
|
+
backendUrl: { vite: "VITE_MFE_BACKEND_URL", webpack: "MFE_BACKEND_URL" },
|
|
38
|
+
projectId: { vite: "VITE_MFE_PROJECT_ID", webpack: "MFE_PROJECT_ID" }
|
|
39
|
+
};
|
|
40
|
+
var missingEnvHint = (option) => `Nothing arrived here at all, which is what a value that never reached the bundle looks like: check ${BUILD_VARIABLE[option].vite} (Vite) or ${BUILD_VARIABLE[option].webpack} (webpack) in the build that produced this bundle. The generated configs carry it in the "define" block of vite.config, or in the DefinePlugin of webpack.config, so look there first; declared only in a .env file the build never reads, it looks exactly like this.`;
|
|
41
|
+
var PLACEHOLDER = /^(…|\.{3}|undefined|null|nan|<[^>]*>|\{\{[^}]*\}\}|\$\{[^}]*\}|your[-_ ]?[a-z]*|x{3,}|change[-_ ]?me|todo|tbd)$/i;
|
|
42
|
+
var quoted = (value) => JSON.stringify(value.length > 120 ? `${value.slice(0, 120)}\u2026` : value);
|
|
43
|
+
var described = (value) => {
|
|
44
|
+
if (value === null) {
|
|
45
|
+
return "null";
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return "an array";
|
|
49
|
+
}
|
|
50
|
+
const type = typeof value;
|
|
51
|
+
return type === "object" || type === "function" ? `a ${type}` : `a ${type} (${String(value)})`;
|
|
52
|
+
};
|
|
53
|
+
var normalized = (key) => key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
54
|
+
var ALIASES = {
|
|
55
|
+
backendUrl: ["backendurl", "backendbaseurl", "apiurl", "baseurl", "consoleurl", "url", "backend"],
|
|
56
|
+
projectId: ["projectid", "project", "idproject", "projectuuid", "projectkey"]
|
|
57
|
+
};
|
|
58
|
+
var confusableKey = (option, config) => {
|
|
59
|
+
if (typeof config !== "object" || config === null) {
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
const candidates = ALIASES[option];
|
|
63
|
+
return Object.keys(config).find((key) => key !== option && candidates.includes(normalized(key)));
|
|
64
|
+
};
|
|
65
|
+
var problem = (option, what, ...hints) => ({
|
|
66
|
+
option,
|
|
67
|
+
what,
|
|
68
|
+
hints: [WHAT_IT_IS[option], ...hints].filter((hint) => Boolean(hint))
|
|
69
|
+
});
|
|
70
|
+
var diagnoseBackendUrl = (value) => {
|
|
71
|
+
const trimmed = value.trim();
|
|
72
|
+
if (trimmed.startsWith("/")) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = new URL(trimmed);
|
|
78
|
+
} catch {
|
|
79
|
+
return problem(
|
|
80
|
+
"backendUrl",
|
|
81
|
+
`is ${quoted(value)}, which is not a URL`,
|
|
82
|
+
trimmed.includes(".") || trimmed.includes(":") ? `It looks like a host without a protocol: write "https://${trimmed}" instead, or "/${trimmed.replace(/^\/+/, "")}" if the console API answers on the same origin as this page.` : void 0
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
86
|
+
return problem(
|
|
87
|
+
"backendUrl",
|
|
88
|
+
`is ${quoted(value)}, whose "${parsed.protocol.replace(":", "")}" scheme is neither http nor https`,
|
|
89
|
+
'A bare "host:port" is read as a scheme: write "http://localhost:3000/api", not "localhost:3000/api".'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
};
|
|
94
|
+
var diagnose = (option, config) => {
|
|
95
|
+
const value = config?.[option];
|
|
96
|
+
const typo = confusableKey(option, config);
|
|
97
|
+
const typoHint = typo ? `The configuration carries "${typo}", which the client ignores. The option is spelled "${option}".` : void 0;
|
|
98
|
+
if (value === void 0 || value === null) {
|
|
99
|
+
return problem(option, value === null ? "is null" : "is missing", typoHint, typo ? void 0 : missingEnvHint(option));
|
|
100
|
+
}
|
|
101
|
+
if (typeof value !== "string") {
|
|
102
|
+
return problem(option, `is ${described(value)}, not a string`, typoHint);
|
|
103
|
+
}
|
|
104
|
+
if (value.trim() === "") {
|
|
105
|
+
return problem(option, value === "" ? "is an empty string" : "holds nothing but whitespace", typoHint, missingEnvHint(option));
|
|
106
|
+
}
|
|
107
|
+
if (PLACEHOLDER.test(value.trim())) {
|
|
108
|
+
const looksLikeAnUnsetVariable = /^(undefined|null|nan)$/i.test(value.trim());
|
|
109
|
+
return problem(option, `is still the placeholder ${quoted(value)}`, typoHint, looksLikeAnUnsetVariable ? missingEnvHint(option) : void 0);
|
|
110
|
+
}
|
|
111
|
+
if (option === "projectId" && value.trim() !== value) {
|
|
112
|
+
return problem(
|
|
113
|
+
"projectId",
|
|
114
|
+
`is ${quoted(value)}, padded with whitespace`,
|
|
115
|
+
"Trim the value: it is percent encoded into the manifest URL, so the spaces would reach the backend as part of the id."
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return option === "backendUrl" ? diagnoseBackendUrl(value) : null;
|
|
119
|
+
};
|
|
120
|
+
var diagnoseConfig = (config) => ["backendUrl", "projectId"].map((option) => diagnose(option, config)).filter((found) => found !== null);
|
|
121
|
+
var bulleted = (problems) => problems.map(({ option, what, hints }) => [` \u2022 ${option} ${what}.`, ...hints.map((hint) => ` \u2192 ${hint}`)].join("\n")).join("\n");
|
|
122
|
+
var indented = (block) => block.split("\n").map((line) => line ? ` ${line}` : line).join("\n");
|
|
123
|
+
var howToConfigure = (integration) => `Configure the client like this:
|
|
124
|
+
|
|
125
|
+
${indented(SNIPPETS[integration])}
|
|
126
|
+
|
|
127
|
+
${WHERE[integration]}`;
|
|
128
|
+
var listed = (options) => options.length > 1 ? `${options.slice(0, -1).join(", ")} and ${options.at(-1)}` : String(options[0]);
|
|
129
|
+
var invalidConfigMessage = (problems, integration) => {
|
|
130
|
+
const options = problems.map(({ option }) => option);
|
|
131
|
+
const headline = `${PREFIX} configure() cannot start the client: ${listed(options)} ${options.length > 1 ? "are" : "is"} not usable.`;
|
|
132
|
+
return `${headline}
|
|
133
|
+
|
|
134
|
+
${bulleted(problems)}
|
|
135
|
+
|
|
136
|
+
${howToConfigure(integration)}`;
|
|
137
|
+
};
|
|
138
|
+
var noConfigMessage = (config, integration) => `${PREFIX} configure() was called with ${config === void 0 ? "no argument" : described(config)} instead of a configuration object. It needs at least backendUrl and projectId.
|
|
139
|
+
|
|
140
|
+
${howToConfigure(integration)}`;
|
|
141
|
+
var notConfiguredMessage = (caller, integration) => `${PREFIX} the client is not configured: ${caller} was called before configure() ever ran, so neither backendUrl nor projectId is known and no manifest can be requested.
|
|
142
|
+
|
|
143
|
+
${howToConfigure(integration)}`;
|
|
144
|
+
|
|
1
145
|
// src/state.ts
|
|
2
146
|
var STATE_KEY = "__mfe_orchestrator_client__";
|
|
3
147
|
var globalSlot = globalThis;
|
|
4
148
|
var getState = () => {
|
|
5
149
|
let state = globalSlot[STATE_KEY];
|
|
6
150
|
if (!state) {
|
|
7
|
-
state = { config: null, manifestPromise: null, identities: null };
|
|
151
|
+
state = { config: null, manifestPromise: null, identities: null, integration: "core" };
|
|
8
152
|
globalSlot[STATE_KEY] = state;
|
|
9
153
|
}
|
|
10
154
|
return state;
|
|
@@ -82,22 +226,31 @@ var readIdentities = () => ({
|
|
|
82
226
|
});
|
|
83
227
|
|
|
84
228
|
// src/index.ts
|
|
85
|
-
var PREFIX = "[@mfe-orchestrator-hub/client]";
|
|
86
|
-
var NOT_CONFIGURED = `${PREFIX} call configure() first. Configure the client at the very top of the host entry point, before anything imports a remote:
|
|
87
|
-
|
|
88
|
-
import { configure } from "@mfe-orchestrator-hub/client"
|
|
89
|
-
|
|
90
|
-
configure({ backendUrl: "\u2026", projectId: "\u2026" })`;
|
|
91
229
|
var withoutTrailingSlash = (value) => value.replace(/\/+$/, "");
|
|
92
230
|
var resolveUserId = async (userId) => {
|
|
93
231
|
const value = typeof userId === "function" ? await userId() : userId;
|
|
94
232
|
return value === void 0 || value === null || value === "" ? void 0 : String(value);
|
|
95
233
|
};
|
|
96
234
|
var sameConfig = (left, right) => left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId;
|
|
97
|
-
var requireConfig = (config) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
235
|
+
var requireConfig = (config, integration) => {
|
|
236
|
+
if (typeof config !== "object" || config === null) {
|
|
237
|
+
throw new Error(noConfigMessage(config, integration));
|
|
238
|
+
}
|
|
239
|
+
const problems = diagnoseConfig(config);
|
|
240
|
+
if (problems.length > 0) {
|
|
241
|
+
throw new Error(invalidConfigMessage(problems, integration));
|
|
242
|
+
}
|
|
243
|
+
if (config.environment !== void 0 && config.environment !== null && typeof config.environment !== "string") {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`${PREFIX} configure() received environment as a ${typeof config.environment}, not a string. Pass the environment slug as it appears in the console, ex. "DEV", or leave it out to let the backend resolve it from the domain of this page.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var warnAboutConfig = (config) => {
|
|
250
|
+
if (typeof config.environment === "string" && config.environment.trim() === "") {
|
|
251
|
+
console.warn(
|
|
252
|
+
`${PREFIX} configure() received an empty environment, so the client falls back to the auto routes and the backend resolves the environment from the domain of this page. Remove the option if that is what you want, or check the variable that feeds it.`
|
|
253
|
+
);
|
|
101
254
|
}
|
|
102
255
|
};
|
|
103
256
|
var manifestPath = (config) => {
|
|
@@ -113,11 +266,25 @@ var manifestUrl = async (config) => {
|
|
|
113
266
|
}
|
|
114
267
|
return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`;
|
|
115
268
|
};
|
|
269
|
+
var statusHint = (status, config) => {
|
|
270
|
+
if (status === 404) {
|
|
271
|
+
const environment = config.environment ? `the environment "${config.environment}" does not exist in it` : "no environment of that project declares the domain this page is served on, which is what the auto route resolves on";
|
|
272
|
+
return `
|
|
273
|
+
|
|
274
|
+
A 404 on this route means the backend did not find what the URL names: either the project id "${config.projectId}" is not a project of this console, or ${environment}. Check both in the console, and check that backendUrl points at the console API of the same instance.`;
|
|
275
|
+
}
|
|
276
|
+
if (status === 401 || status === 403) {
|
|
277
|
+
return `
|
|
278
|
+
|
|
279
|
+
The console refused the request. Check that the project id "${config.projectId}" belongs to the instance backendUrl points at, and that this domain is allowed to read its manifest.`;
|
|
280
|
+
}
|
|
281
|
+
return "";
|
|
282
|
+
};
|
|
116
283
|
var fetchManifest = async (config) => {
|
|
117
284
|
const url = await manifestUrl(config);
|
|
118
285
|
const response = await fetch(url, { headers: { accept: "application/json" } });
|
|
119
286
|
if (!response.ok) {
|
|
120
|
-
throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}`);
|
|
287
|
+
throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}${statusHint(response.status, config)}`);
|
|
121
288
|
}
|
|
122
289
|
const payload = await response.json();
|
|
123
290
|
return {
|
|
@@ -126,8 +293,8 @@ var fetchManifest = async (config) => {
|
|
|
126
293
|
};
|
|
127
294
|
};
|
|
128
295
|
var configure = (config) => {
|
|
129
|
-
requireConfig(config);
|
|
130
296
|
const state = getState();
|
|
297
|
+
requireConfig(config, state.integration);
|
|
131
298
|
const current = state.config;
|
|
132
299
|
if (current) {
|
|
133
300
|
if (!sameConfig(current, config)) {
|
|
@@ -138,12 +305,16 @@ var configure = (config) => {
|
|
|
138
305
|
return;
|
|
139
306
|
}
|
|
140
307
|
state.config = { ...config };
|
|
308
|
+
warnAboutConfig(config);
|
|
309
|
+
};
|
|
310
|
+
var registerIntegration = (integration) => {
|
|
311
|
+
getState().integration = integration;
|
|
141
312
|
};
|
|
142
|
-
var
|
|
313
|
+
var loadManifest = (caller) => {
|
|
143
314
|
const state = getState();
|
|
144
315
|
const config = state.config;
|
|
145
316
|
if (!config) {
|
|
146
|
-
return Promise.reject(new Error(
|
|
317
|
+
return Promise.reject(new Error(notConfiguredMessage(caller, state.integration)));
|
|
147
318
|
}
|
|
148
319
|
if (!state.manifestPromise) {
|
|
149
320
|
const attempt = fetchManifest(config);
|
|
@@ -156,8 +327,9 @@ var manifest = () => {
|
|
|
156
327
|
}
|
|
157
328
|
return state.manifestPromise;
|
|
158
329
|
};
|
|
330
|
+
var manifest = () => loadManifest("manifest()");
|
|
159
331
|
var remoteUrl = async (slug) => {
|
|
160
|
-
const { microfrontends } = await
|
|
332
|
+
const { microfrontends } = await loadManifest(`remoteUrl("${slug}")`);
|
|
161
333
|
const found = microfrontends.find((microfrontend) => microfrontend.slug === slug);
|
|
162
334
|
if (!found) {
|
|
163
335
|
const available = microfrontends.map((microfrontend) => `"${microfrontend.slug}"`).join(", ");
|
|
@@ -171,7 +343,7 @@ var remoteUrl = async (slug) => {
|
|
|
171
343
|
return found.url;
|
|
172
344
|
};
|
|
173
345
|
var globalVariables = async () => {
|
|
174
|
-
const { globalVariables: variables } = await
|
|
346
|
+
const { globalVariables: variables } = await loadManifest("globalVariables()");
|
|
175
347
|
const result = {};
|
|
176
348
|
for (const variable of variables) {
|
|
177
349
|
if (variable && typeof variable.key === "string") {
|
|
@@ -188,6 +360,6 @@ var identities = () => {
|
|
|
188
360
|
return state.identities;
|
|
189
361
|
};
|
|
190
362
|
|
|
191
|
-
export { configure, globalVariables, identities, manifest, remoteUrl };
|
|
363
|
+
export { configure, globalVariables, identities, manifest, registerIntegration, remoteUrl };
|
|
192
364
|
//# sourceMappingURL=index.js.map
|
|
193
365
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/state.ts","../src/storage.ts","../src/index.ts"],"names":["created"],"mappings":"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","file":"index.js","sourcesContent":["import type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport interface OrchestratorState {\n config: OrchestratorConfig | null\n manifestPromise: Promise<Manifest> | null\n identities: Identities | null\n}\n\n/**\n * The state lives on `globalThis` under this key, not in a module scope variable.\n *\n * A host can end up with two copies of this package loaded at once: the app imports the ESM build\n * while something else pulls the CJS one, or the bundler config and the app resolve to different\n * chunks. Two module scopes would mean two configurations and two manifest requests, which is\n * exactly what the contract forbids. One well known global slot makes the singleton real.\n */\nexport const STATE_KEY = \"__mfe_orchestrator_client__\"\n\nconst globalSlot = globalThis as typeof globalThis & Record<typeof STATE_KEY, OrchestratorState | undefined>\n\nexport const getState = (): OrchestratorState => {\n let state = globalSlot[STATE_KEY]\n if (!state) {\n state = { config: null, manifestPromise: null, identities: null }\n globalSlot[STATE_KEY] = state\n }\n return state\n}\n","import type { Identities } from \"./types\"\n\nexport const SESSION_ID_KEY = \"mfe-orchestrator.sessionId\"\nexport const DEVICE_ID_KEY = \"mfe-orchestrator.deviceId\"\n\nconst PROBE_KEY = \"mfe-orchestrator.probe\"\n\ntype StorageKind = \"local\" | \"session\"\n\n/**\n * Ids minted while storage was unusable. They live for the lifetime of the page so that, even\n * without storage, every request of this page load carries one stable pair of ids.\n */\nconst inMemoryIds = new Map<string, string>()\n\nconst uuidFromRandomBytes = (bytes: Uint8Array): string => {\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n const hex: string[] = []\n for (const byte of bytes) {\n hex.push(byte.toString(16).padStart(2, \"0\"))\n }\n return `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex.slice(6, 8).join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10, 16).join(\"\")}`\n}\n\n/**\n * A v4 UUID. `crypto.randomUUID` is the intended path; the two fallbacks only exist so that a page\n * served over plain http, or an exotic runtime, still gets an id instead of an exception.\n */\nexport const newId = (): string => {\n const cryptoApi = globalThis.crypto\n try {\n if (typeof cryptoApi?.randomUUID === \"function\") {\n return cryptoApi.randomUUID()\n }\n if (typeof cryptoApi?.getRandomValues === \"function\") {\n return uuidFromRandomBytes(cryptoApi.getRandomValues(new Uint8Array(16)))\n }\n } catch {\n // fall through to the non cryptographic fallback\n }\n const bytes = new Uint8Array(16)\n for (let index = 0; index < bytes.length; index++) {\n bytes[index] = Math.floor(Math.random() * 256)\n }\n return uuidFromRandomBytes(bytes)\n}\n\n/**\n * The requested storage, or `null` when it is missing or unusable.\n *\n * Both the property access and the write can throw: Safari in private mode exposes the object and\n * then refuses `setItem`, some hardened browsers throw a SecurityError on the access itself. The\n * write probe is the only reliable way to tell a usable storage from a decorative one.\n */\nconst openStorage = (kind: StorageKind): Storage | null => {\n try {\n const storage = kind === \"local\" ? globalThis.localStorage : globalThis.sessionStorage\n if (!storage) {\n return null\n }\n storage.setItem(PROBE_KEY, \"1\")\n storage.removeItem(PROBE_KEY)\n return storage\n } catch {\n return null\n }\n}\n\n/**\n * The id under `key`, read from storage and created on first use. Falls back to an id kept in\n * memory when storage cannot be used, so this never throws out of the SDK.\n */\nconst persistentId = (kind: StorageKind, key: string): string => {\n const storage = openStorage(kind)\n if (storage) {\n try {\n const stored = storage.getItem(key)\n if (stored) {\n return stored\n }\n const created = newId()\n storage.setItem(key, created)\n return created\n } catch {\n // storage passed the probe and failed anyway: keep going with an in memory id\n }\n }\n const remembered = inMemoryIds.get(key)\n if (remembered) {\n return remembered\n }\n const created = newId()\n inMemoryIds.set(key, created)\n return created\n}\n\n/** Reads, and creates when needed, the session and device ids. */\nexport const readIdentities = (): Identities => ({\n sessionId: persistentId(\"session\", SESSION_ID_KEY),\n deviceId: persistentId(\"local\", DEVICE_ID_KEY)\n})\n","import { getState } from \"./state\"\nimport { readIdentities } from \"./storage\"\nimport type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport type { GlobalVariable, Identities, Manifest, Microfrontend, OrchestratorConfig } from \"./types\"\n\nconst PREFIX = \"[@mfe-orchestrator-hub/client]\"\n\nconst NOT_CONFIGURED = `${PREFIX} call configure() first. Configure the client at the very top of the host entry point, before anything imports a remote:\n\n import { configure } from \"@mfe-orchestrator-hub/client\"\n\n configure({ backendUrl: \"…\", projectId: \"…\" })`\n\nconst withoutTrailingSlash = (value: string): string => value.replace(/\\/+$/, \"\")\n\nconst resolveUserId = async (userId: OrchestratorConfig[\"userId\"]): Promise<string | undefined> => {\n const value = typeof userId === \"function\" ? await userId() : userId\n return value === undefined || value === null || value === \"\" ? undefined : String(value)\n}\n\nconst sameConfig = (left: OrchestratorConfig, right: OrchestratorConfig): boolean =>\n left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId\n\nconst requireConfig = (config: OrchestratorConfig): void => {\n // environment is deliberately absent: without it the backend resolves it from the domain.\n const missing = ([\"backendUrl\", \"projectId\"] as const).filter(key => !config?.[key])\n if (missing.length > 0) {\n throw new Error(`${PREFIX} configure() is missing required option${missing.length > 1 ? \"s\" : \"\"}: ${missing.join(\", \")}.`)\n }\n}\n\n/**\n * Two shapes, one per route family. With an explicit environment the slug is the last segment; with\n * no environment the \"auto\" segment takes its place before the project id and the backend resolves\n * the environment from the domain the request comes from.\n */\nconst manifestPath = (config: OrchestratorConfig): string => {\n const projectId = encodeURIComponent(config.projectId)\n return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`\n}\n\nconst manifestUrl = async (config: OrchestratorConfig): Promise<string> => {\n const { sessionId, deviceId } = identities()\n const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId })\n // Resolved as late as possible: the host may only know the user after its own auth round trip.\n const userId = await resolveUserId(config.userId)\n if (userId) {\n query.set(\"mfeUserId\", userId)\n }\n return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`\n}\n\nconst fetchManifest = async (config: OrchestratorConfig): Promise<Manifest> => {\n const url = await manifestUrl(config)\n // No retry in v1: whatever fetch throws travels straight to the caller.\n const response = await fetch(url, { headers: { accept: \"application/json\" } })\n if (!response.ok) {\n throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}`)\n }\n const payload = (await response.json()) as Partial<Manifest> | null\n return {\n globalVariables: payload?.globalVariables ?? [],\n microfrontends: payload?.microfrontends ?? []\n }\n}\n\n/**\n * Hands the client its configuration. Call it once, synchronously, before any remote is imported.\n *\n * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use\n * the \"auto\" routes, where the backend resolves the environment from the domain of the host page.\n *\n * Idempotent: calling it again with the same options is a no op, so a framework provider may safely\n * call it on every render. A second call with different options is ignored, with a warning, because\n * the manifest of this page load may already be in flight or resolved.\n */\nexport const configure = (config: OrchestratorConfig): void => {\n requireConfig(config)\n const state = getState()\n const current = state.config\n if (current) {\n if (!sameConfig(current, config)) {\n console.warn(\n `${PREFIX} configure() was called again with a different configuration. The first one is kept: ${JSON.stringify({ backendUrl: current.backendUrl, projectId: current.projectId, environment: current.environment })}.`\n )\n }\n return\n }\n state.config = { ...config }\n}\n\n/**\n * The whole manifest of the environment, fetched once per page load and memoized as a promise, so\n * that concurrent callers share the single request in flight.\n *\n * A successful result is memoized for good. A failed attempt is not: the memo is dropped when the\n * shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry\n * — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the\n * page to reject every `remoteUrl()` until a full reload.\n */\nexport const manifest = (): Promise<Manifest> => {\n const state = getState()\n const config = state.config\n if (!config) {\n // Not memoized: the host may still configure the client and try again.\n return Promise.reject(new Error(NOT_CONFIGURED))\n }\n if (!state.manifestPromise) {\n // Assigned before the first await inside fetchManifest, which is what makes N concurrent\n // callers collapse onto one request.\n const attempt = fetchManifest(config)\n state.manifestPromise = attempt\n // Cleared once, when the shared attempt settles, not in each caller's own catch: every\n // caller already waiting on this attempt still gets its rejection. The identity check keeps\n // a late failure from evicting a newer attempt.\n attempt.catch(() => {\n if (state.manifestPromise === attempt) {\n state.manifestPromise = null\n }\n })\n }\n return state.manifestPromise\n}\n\n/**\n * The ready to use, version pinned URL of a remote. Awaits the manifest internally.\n *\n * The returned URL is used verbatim: the host never parses the version out of it and never strips\n * the `_v/<version>/` segment, or a classic script would load its chunks off another version.\n */\nexport const remoteUrl = async (slug: string): Promise<string> => {\n const { microfrontends } = await manifest()\n const found = microfrontends.find(microfrontend => microfrontend.slug === slug)\n if (!found) {\n const available = microfrontends.map(microfrontend => `\"${microfrontend.slug}\"`).join(\", \")\n const environment = getState().config?.environment\n // With no configured environment the host cannot name the one it was served: only the backend knows.\n const where = environment ? `environment \"${environment}\"` : \"the environment resolved from this domain\"\n throw new Error(`${PREFIX} unknown microfrontend slug \"${slug}\". Available in ${where}: ${available || \"none, this environment serves no microfrontend\"}.`)\n }\n if (!found.url) {\n throw new Error(`${PREFIX} the microfrontend \"${slug}\" is in the manifest but carries no url. Check its deployment in the console.`)\n }\n return found.url\n}\n\n/** The global variables of the environment, as a plain object. */\nexport const globalVariables = async (): Promise<Record<string, string>> => {\n const { globalVariables: variables } = await manifest()\n const result: Record<string, string> = {}\n for (const variable of variables) {\n if (variable && typeof variable.key === \"string\") {\n result[variable.key] = variable.value\n }\n }\n return result\n}\n\n/**\n * The session and device ids of this page, exposed for telemetry and debugging. Created on first\n * use and stable for the lifetime of the page, whether or not storage is available.\n */\nexport const identities = (): Identities => {\n const state = getState()\n if (!state.identities) {\n state.identities = readIdentities()\n }\n return state.identities\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/state.ts","../src/storage.ts","../src/index.ts"],"names":["created"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Everything the client says when its configuration is wrong or absent.\n *\n * The two required options almost always arrive from build time environment variables, so the\n * failure the host sees is not \"a value is invalid\" but \"nothing reached the bundle\". Messages here\n * are written for that moment: they name the option, show what actually arrived, say where the real\n * value comes from, and print the exact call to write, in the flavour of the package the host uses.\n */\n\nexport const PREFIX = \"[@mfe-orchestrator-hub/client]\"\n\n/** Which package the host went through. Only decides which snippet an error prints. */\nexport type Integration = \"core\" | \"react\" | \"vue\" | \"angular\"\n\n/** The two options without which nothing can be requested. */\nexport type RequiredOption = \"backendUrl\" | \"projectId\"\n\nconst EXAMPLE_BACKEND_URL = \"https://console.mfe-orchestrator.dev/api\"\nconst EXAMPLE_PROJECT_ID = \"6f1b2c3d4e5f6a7b8c9d0e1f\"\n\nconst SNIPPETS: Record<Integration, string> = {\n core: `import { configure } from \"@mfe-orchestrator-hub/client\"\n\nconfigure({\n backendUrl: \"${EXAMPLE_BACKEND_URL}\",\n projectId: \"${EXAMPLE_PROJECT_ID}\"\n})`,\n react: `import { OrchestratorProvider } from \"@mfe-orchestrator-hub/client-react\"\n\n<OrchestratorProvider config={{ backendUrl: \"${EXAMPLE_BACKEND_URL}\", projectId: \"${EXAMPLE_PROJECT_ID}\" }}>\n <App />\n</OrchestratorProvider>`,\n vue: `import { createOrchestrator } from \"@mfe-orchestrator-hub/client-vue\"\n\napp.use(createOrchestrator({ backendUrl: \"${EXAMPLE_BACKEND_URL}\", projectId: \"${EXAMPLE_PROJECT_ID}\" }))`,\n angular: `import { provideOrchestrator } from \"@mfe-orchestrator-hub/client-angular\"\n\nbootstrapApplication(AppComponent, {\n providers: [provideOrchestrator({ backendUrl: \"${EXAMPLE_BACKEND_URL}\", projectId: \"${EXAMPLE_PROJECT_ID}\" })]\n})`\n}\n\nconst WHERE: Record<Integration, string> = {\n core: \"Call it once, at the very top of the host entry point, before anything imports a remote.\",\n react: \"Render it above every component that resolves a remote. Better still, call configure() at the very top of the entry point: a bundler may import a remote before React mounts.\",\n vue: \"Install the plugin before mounting the app. Better still, call configure() at the very top of the entry point: a bundler may import a remote before the app is created.\",\n angular: \"Pass it to bootstrapApplication(). Better still, call configure() at the very top of main.ts: a lazily loaded remote must find the client already configured.\"\n}\n\nconst WHAT_IT_IS: Record<RequiredOption, string> = {\n backendUrl: `It is the base URL of the console API, protocol included, ex. \"${EXAMPLE_BACKEND_URL}\". A path on the same origin as the host, ex. \"/api\", works too.`,\n projectId: `It is the id of the project in the console: open the project and copy it from its page, ex. \"${EXAMPLE_PROJECT_ID}\".`\n}\n\n/** The name the value travels under from the bundler config into the bundle, per toolchain. */\nconst BUILD_VARIABLE: Record<RequiredOption, { vite: string; webpack: string }> = {\n backendUrl: { vite: \"VITE_MFE_BACKEND_URL\", webpack: \"MFE_BACKEND_URL\" },\n projectId: { vite: \"VITE_MFE_PROJECT_ID\", webpack: \"MFE_PROJECT_ID\" }\n}\n\n/**\n * What an option that was never substituted into the bundle looks like.\n *\n * The bundler config is named before the .env file on purpose: it is where the console writes both\n * values when it generates the config, so it is where they are missing from when they are missing.\n */\nconst missingEnvHint = (option: RequiredOption): string =>\n `Nothing arrived here at all, which is what a value that never reached the bundle looks like: check ${BUILD_VARIABLE[option].vite} (Vite) or ${BUILD_VARIABLE[option].webpack} (webpack) in the build that produced this bundle. The generated configs carry it in the \"define\" block of vite.config, or in the DefinePlugin of webpack.config, so look there first; declared only in a .env file the build never reads, it looks exactly like this.`\n\n/** Values that mean \"the template was never filled in\". */\nconst PLACEHOLDER = /^(…|\\.{3}|undefined|null|nan|<[^>]*>|\\{\\{[^}]*\\}\\}|\\$\\{[^}]*\\}|your[-_ ]?[a-z]*|x{3,}|change[-_ ]?me|todo|tbd)$/i\n\n/** Long values are cut: an error message is not the place to print a whole JWT. */\nconst quoted = (value: string): string => JSON.stringify(value.length > 120 ? `${value.slice(0, 120)}…` : value)\n\nconst described = (value: unknown): string => {\n if (value === null) {\n return \"null\"\n }\n if (Array.isArray(value)) {\n return \"an array\"\n }\n const type = typeof value\n return type === \"object\" || type === \"function\" ? `a ${type}` : `a ${type} (${String(value)})`\n}\n\n/** Same key modulo case and separators: projectID, project_id, PROJECT-ID all collapse onto projectid. */\nconst normalized = (key: string): string => key.toLowerCase().replace(/[^a-z0-9]/g, \"\")\n\nconst ALIASES: Record<RequiredOption, string[]> = {\n backendUrl: [\"backendurl\", \"backendbaseurl\", \"apiurl\", \"baseurl\", \"consoleurl\", \"url\", \"backend\"],\n projectId: [\"projectid\", \"project\", \"idproject\", \"projectuuid\", \"projectkey\"]\n}\n\n/**\n * The key the host actually wrote, when it looks like a misspelling of the one that is missing.\n * `projectID` is by far the most common: it is silently ignored, so without this the message would\n * claim the option is absent while the developer stares at a line that clearly sets it.\n */\nconst confusableKey = (option: RequiredOption, config: unknown): string | undefined => {\n if (typeof config !== \"object\" || config === null) {\n return undefined\n }\n const candidates = ALIASES[option]\n return Object.keys(config).find(key => key !== option && candidates.includes(normalized(key)))\n}\n\n/** One thing that is wrong with one option, already spelled out. */\nexport interface ConfigProblem {\n option: RequiredOption\n /** Predicate completing \"<option> …\", ex. `is missing`. */\n what: string\n /** Extra lines, printed under the option, that say how to fix this particular case. */\n hints: string[]\n}\n\nconst problem = (option: RequiredOption, what: string, ...hints: (string | undefined)[]): ConfigProblem => ({\n option,\n what,\n hints: [WHAT_IT_IS[option], ...hints].filter((hint): hint is string => Boolean(hint))\n})\n\n/**\n * A backendUrl the client can actually build a request on: an absolute http(s) URL, or a path on the\n * host's own origin, which `fetch` resolves against the page.\n */\nconst diagnoseBackendUrl = (value: string): ConfigProblem | null => {\n const trimmed = value.trim()\n if (trimmed.startsWith(\"/\")) {\n return null\n }\n let parsed: URL\n try {\n parsed = new URL(trimmed)\n } catch {\n return problem(\n \"backendUrl\",\n `is ${quoted(value)}, which is not a URL`,\n trimmed.includes(\".\") || trimmed.includes(\":\")\n ? `It looks like a host without a protocol: write \"https://${trimmed}\" instead, or \"/${trimmed.replace(/^\\/+/, \"\")}\" if the console API answers on the same origin as this page.`\n : undefined\n )\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return problem(\n \"backendUrl\",\n `is ${quoted(value)}, whose \"${parsed.protocol.replace(\":\", \"\")}\" scheme is neither http nor https`,\n 'A bare \"host:port\" is read as a scheme: write \"http://localhost:3000/api\", not \"localhost:3000/api\".'\n )\n }\n return null\n}\n\n/** Everything wrong with one required option, or null when it is usable. */\nconst diagnose = (option: RequiredOption, config: unknown): ConfigProblem | null => {\n const value = (config as Record<string, unknown> | null | undefined)?.[option]\n const typo = confusableKey(option, config)\n const typoHint = typo ? `The configuration carries \"${typo}\", which the client ignores. The option is spelled \"${option}\".` : undefined\n\n if (value === undefined || value === null) {\n return problem(option, value === null ? \"is null\" : \"is missing\", typoHint, typo ? undefined : missingEnvHint(option))\n }\n if (typeof value !== \"string\") {\n return problem(option, `is ${described(value)}, not a string`, typoHint)\n }\n if (value.trim() === \"\") {\n return problem(option, value === \"\" ? \"is an empty string\" : \"holds nothing but whitespace\", typoHint, missingEnvHint(option))\n }\n if (PLACEHOLDER.test(value.trim())) {\n const looksLikeAnUnsetVariable = /^(undefined|null|nan)$/i.test(value.trim())\n return problem(option, `is still the placeholder ${quoted(value)}`, typoHint, looksLikeAnUnsetVariable ? missingEnvHint(option) : undefined)\n }\n if (option === \"projectId\" && value.trim() !== value) {\n // Percent encoded into the path, so the padding would travel to the backend as %20.\n return problem(\n \"projectId\",\n `is ${quoted(value)}, padded with whitespace`,\n \"Trim the value: it is percent encoded into the manifest URL, so the spaces would reach the backend as part of the id.\"\n )\n }\n return option === \"backendUrl\" ? diagnoseBackendUrl(value) : null\n}\n\n/** Every problem of a configuration, in the order the options are declared. Empty means usable. */\nexport const diagnoseConfig = (config: unknown): ConfigProblem[] =>\n ([\"backendUrl\", \"projectId\"] as const).map(option => diagnose(option, config)).filter((found): found is ConfigProblem => found !== null)\n\nconst bulleted = (problems: ConfigProblem[]): string => problems.map(({ option, what, hints }) => [` • ${option} ${what}.`, ...hints.map(hint => ` → ${hint}`)].join(\"\\n\")).join(\"\\n\")\n\nconst indented = (block: string): string =>\n block\n .split(\"\\n\")\n .map(line => (line ? ` ${line}` : line))\n .join(\"\\n\")\n\nconst howToConfigure = (integration: Integration): string => `Configure the client like this:\\n\\n${indented(SNIPPETS[integration])}\\n\\n${WHERE[integration]}`\n\nconst listed = (options: RequiredOption[]): string => (options.length > 1 ? `${options.slice(0, -1).join(\", \")} and ${options.at(-1)}` : String(options[0]))\n\n/** The message thrown by `configure()` when what it received cannot be used. */\nexport const invalidConfigMessage = (problems: ConfigProblem[], integration: Integration): string => {\n const options = problems.map(({ option }) => option)\n const headline = `${PREFIX} configure() cannot start the client: ${listed(options)} ${options.length > 1 ? \"are\" : \"is\"} not usable.`\n return `${headline}\\n\\n${bulleted(problems)}\\n\\n${howToConfigure(integration)}`\n}\n\n/** The message thrown by `configure()` when it did not even receive an object. */\nexport const noConfigMessage = (config: unknown, integration: Integration): string =>\n `${PREFIX} configure() was called with ${config === undefined ? \"no argument\" : described(config)} instead of a configuration object. It needs at least backendUrl and projectId.\\n\\n${howToConfigure(integration)}`\n\n/** The message every read rejects with while the client has no configuration at all. */\nexport const notConfiguredMessage = (caller: string, integration: Integration): string =>\n `${PREFIX} the client is not configured: ${caller} was called before configure() ever ran, so neither backendUrl nor projectId is known and no manifest can be requested.\\n\\n${howToConfigure(integration)}`\n","import type { Integration } from \"./errors\"\nimport type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport interface OrchestratorState {\n config: OrchestratorConfig | null\n manifestPromise: Promise<Manifest> | null\n identities: Identities | null\n /** Set by the framework adapters as they load, so an error prints the snippet of their own API. */\n integration: Integration\n}\n\n/**\n * The state lives on `globalThis` under this key, not in a module scope variable.\n *\n * A host can end up with two copies of this package loaded at once: the app imports the ESM build\n * while something else pulls the CJS one, or the bundler config and the app resolve to different\n * chunks. Two module scopes would mean two configurations and two manifest requests, which is\n * exactly what the contract forbids. One well known global slot makes the singleton real.\n */\nexport const STATE_KEY = \"__mfe_orchestrator_client__\"\n\nconst globalSlot = globalThis as typeof globalThis & Record<typeof STATE_KEY, OrchestratorState | undefined>\n\nexport const getState = (): OrchestratorState => {\n let state = globalSlot[STATE_KEY]\n if (!state) {\n state = { config: null, manifestPromise: null, identities: null, integration: \"core\" }\n globalSlot[STATE_KEY] = state\n }\n return state\n}\n","import type { Identities } from \"./types\"\n\nexport const SESSION_ID_KEY = \"mfe-orchestrator.sessionId\"\nexport const DEVICE_ID_KEY = \"mfe-orchestrator.deviceId\"\n\nconst PROBE_KEY = \"mfe-orchestrator.probe\"\n\ntype StorageKind = \"local\" | \"session\"\n\n/**\n * Ids minted while storage was unusable. They live for the lifetime of the page so that, even\n * without storage, every request of this page load carries one stable pair of ids.\n */\nconst inMemoryIds = new Map<string, string>()\n\nconst uuidFromRandomBytes = (bytes: Uint8Array): string => {\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n const hex: string[] = []\n for (const byte of bytes) {\n hex.push(byte.toString(16).padStart(2, \"0\"))\n }\n return `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex.slice(6, 8).join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10, 16).join(\"\")}`\n}\n\n/**\n * A v4 UUID. `crypto.randomUUID` is the intended path; the two fallbacks only exist so that a page\n * served over plain http, or an exotic runtime, still gets an id instead of an exception.\n */\nexport const newId = (): string => {\n const cryptoApi = globalThis.crypto\n try {\n if (typeof cryptoApi?.randomUUID === \"function\") {\n return cryptoApi.randomUUID()\n }\n if (typeof cryptoApi?.getRandomValues === \"function\") {\n return uuidFromRandomBytes(cryptoApi.getRandomValues(new Uint8Array(16)))\n }\n } catch {\n // fall through to the non cryptographic fallback\n }\n const bytes = new Uint8Array(16)\n for (let index = 0; index < bytes.length; index++) {\n bytes[index] = Math.floor(Math.random() * 256)\n }\n return uuidFromRandomBytes(bytes)\n}\n\n/**\n * The requested storage, or `null` when it is missing or unusable.\n *\n * Both the property access and the write can throw: Safari in private mode exposes the object and\n * then refuses `setItem`, some hardened browsers throw a SecurityError on the access itself. The\n * write probe is the only reliable way to tell a usable storage from a decorative one.\n */\nconst openStorage = (kind: StorageKind): Storage | null => {\n try {\n const storage = kind === \"local\" ? globalThis.localStorage : globalThis.sessionStorage\n if (!storage) {\n return null\n }\n storage.setItem(PROBE_KEY, \"1\")\n storage.removeItem(PROBE_KEY)\n return storage\n } catch {\n return null\n }\n}\n\n/**\n * The id under `key`, read from storage and created on first use. Falls back to an id kept in\n * memory when storage cannot be used, so this never throws out of the SDK.\n */\nconst persistentId = (kind: StorageKind, key: string): string => {\n const storage = openStorage(kind)\n if (storage) {\n try {\n const stored = storage.getItem(key)\n if (stored) {\n return stored\n }\n const created = newId()\n storage.setItem(key, created)\n return created\n } catch {\n // storage passed the probe and failed anyway: keep going with an in memory id\n }\n }\n const remembered = inMemoryIds.get(key)\n if (remembered) {\n return remembered\n }\n const created = newId()\n inMemoryIds.set(key, created)\n return created\n}\n\n/** Reads, and creates when needed, the session and device ids. */\nexport const readIdentities = (): Identities => ({\n sessionId: persistentId(\"session\", SESSION_ID_KEY),\n deviceId: persistentId(\"local\", DEVICE_ID_KEY)\n})\n","import { diagnoseConfig, type Integration, invalidConfigMessage, noConfigMessage, notConfiguredMessage, PREFIX } from \"./errors\"\nimport { getState } from \"./state\"\nimport { readIdentities } from \"./storage\"\nimport type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport type { ConfigProblem, Integration } from \"./errors\"\nexport type { GlobalVariable, Identities, Manifest, Microfrontend, OrchestratorConfig } from \"./types\"\n\nconst withoutTrailingSlash = (value: string): string => value.replace(/\\/+$/, \"\")\n\nconst resolveUserId = async (userId: OrchestratorConfig[\"userId\"]): Promise<string | undefined> => {\n const value = typeof userId === \"function\" ? await userId() : userId\n return value === undefined || value === null || value === \"\" ? undefined : String(value)\n}\n\nconst sameConfig = (left: OrchestratorConfig, right: OrchestratorConfig): boolean =>\n left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId\n\n/**\n * Rejects a configuration the client could not use, with a message that names the option, shows what\n * actually arrived and prints the call to write. environment is deliberately not required: without\n * it the backend resolves it from the domain. It is still checked for type, because a non string\n * there would silently end up percent encoded into the path.\n */\nconst requireConfig = (config: OrchestratorConfig, integration: Integration): void => {\n if (typeof config !== \"object\" || config === null) {\n throw new Error(noConfigMessage(config, integration))\n }\n const problems = diagnoseConfig(config)\n if (problems.length > 0) {\n throw new Error(invalidConfigMessage(problems, integration))\n }\n if (config.environment !== undefined && config.environment !== null && typeof config.environment !== \"string\") {\n throw new Error(\n `${PREFIX} configure() received environment as a ${typeof config.environment}, not a string. Pass the environment slug as it appears in the console, ex. \"DEV\", or leave it out to let the backend resolve it from the domain of this page.`\n )\n }\n}\n\n/** Notices about a stored configuration that works but probably does not do what the host meant. */\nconst warnAboutConfig = (config: OrchestratorConfig): void => {\n if (typeof config.environment === \"string\" && config.environment.trim() === \"\") {\n console.warn(\n `${PREFIX} configure() received an empty environment, so the client falls back to the auto routes and the backend resolves the environment from the domain of this page. Remove the option if that is what you want, or check the variable that feeds it.`\n )\n }\n}\n\n/**\n * Two shapes, one per route family. With an explicit environment the slug is the last segment; with\n * no environment the \"auto\" segment takes its place before the project id and the backend resolves\n * the environment from the domain the request comes from.\n */\nconst manifestPath = (config: OrchestratorConfig): string => {\n const projectId = encodeURIComponent(config.projectId)\n return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`\n}\n\nconst manifestUrl = async (config: OrchestratorConfig): Promise<string> => {\n const { sessionId, deviceId } = identities()\n const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId })\n // Resolved as late as possible: the host may only know the user after its own auth round trip.\n const userId = await resolveUserId(config.userId)\n if (userId) {\n query.set(\"mfeUserId\", userId)\n }\n return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`\n}\n\n/**\n * What a status usually means on this route. The backend answers the same 404 for an unknown project\n * and for an unknown environment, and the two are indistinguishable from here, so both are named.\n */\nconst statusHint = (status: number, config: OrchestratorConfig): string => {\n if (status === 404) {\n const environment = config.environment\n ? `the environment \"${config.environment}\" does not exist in it`\n : \"no environment of that project declares the domain this page is served on, which is what the auto route resolves on\"\n return `\\n\\nA 404 on this route means the backend did not find what the URL names: either the project id \"${config.projectId}\" is not a project of this console, or ${environment}. Check both in the console, and check that backendUrl points at the console API of the same instance.`\n }\n if (status === 401 || status === 403) {\n return `\\n\\nThe console refused the request. Check that the project id \"${config.projectId}\" belongs to the instance backendUrl points at, and that this domain is allowed to read its manifest.`\n }\n return \"\"\n}\n\nconst fetchManifest = async (config: OrchestratorConfig): Promise<Manifest> => {\n const url = await manifestUrl(config)\n // No retry in v1: whatever fetch throws travels straight to the caller.\n const response = await fetch(url, { headers: { accept: \"application/json\" } })\n if (!response.ok) {\n throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}${statusHint(response.status, config)}`)\n }\n const payload = (await response.json()) as Partial<Manifest> | null\n return {\n globalVariables: payload?.globalVariables ?? [],\n microfrontends: payload?.microfrontends ?? []\n }\n}\n\n/**\n * Hands the client its configuration. Call it once, synchronously, before any remote is imported.\n *\n * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use\n * the \"auto\" routes, where the backend resolves the environment from the domain of the host page.\n *\n * Idempotent: calling it again with the same options is a no op, so a framework provider may safely\n * call it on every render. A second call with different options is ignored, with a warning, because\n * the manifest of this page load may already be in flight or resolved.\n */\nexport const configure = (config: OrchestratorConfig): void => {\n const state = getState()\n requireConfig(config, state.integration)\n const current = state.config\n if (current) {\n if (!sameConfig(current, config)) {\n console.warn(\n `${PREFIX} configure() was called again with a different configuration. The first one is kept: ${JSON.stringify({ backendUrl: current.backendUrl, projectId: current.projectId, environment: current.environment })}.`\n )\n }\n return\n }\n state.config = { ...config }\n warnAboutConfig(config)\n}\n\n/**\n * @internal Lets a framework adapter say which package the host is really using, so a configuration\n * error prints the snippet of that API instead of the bare `configure()` one. Idempotent, and never\n * needed by an application: the adapters call it as they load.\n */\nexport const registerIntegration = (integration: Integration): void => {\n getState().integration = integration\n}\n\n/**\n * The whole manifest of the environment, fetched once per page load and memoized as a promise, so\n * that concurrent callers share the single request in flight.\n *\n * A successful result is memoized for good. A failed attempt is not: the memo is dropped when the\n * shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry\n * — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the\n * page to reject every `remoteUrl()` until a full reload.\n */\nconst loadManifest = (caller: string): Promise<Manifest> => {\n const state = getState()\n const config = state.config\n if (!config) {\n // Not memoized: the host may still configure the client and try again.\n return Promise.reject(new Error(notConfiguredMessage(caller, state.integration)))\n }\n if (!state.manifestPromise) {\n // Assigned before the first await inside fetchManifest, which is what makes N concurrent\n // callers collapse onto one request.\n const attempt = fetchManifest(config)\n state.manifestPromise = attempt\n // Cleared once, when the shared attempt settles, not in each caller's own catch: every\n // caller already waiting on this attempt still gets its rejection. The identity check keeps\n // a late failure from evicting a newer attempt.\n attempt.catch(() => {\n if (state.manifestPromise === attempt) {\n state.manifestPromise = null\n }\n })\n }\n return state.manifestPromise\n}\n\n/** The whole manifest of the environment. See `loadManifest` for the memoization contract. */\nexport const manifest = (): Promise<Manifest> => loadManifest(\"manifest()\")\n\n/**\n * The ready to use, version pinned URL of a remote. Awaits the manifest internally.\n *\n * The returned URL is used verbatim: the host never parses the version out of it and never strips\n * the `_v/<version>/` segment, or a classic script would load its chunks off another version.\n */\nexport const remoteUrl = async (slug: string): Promise<string> => {\n const { microfrontends } = await loadManifest(`remoteUrl(\"${slug}\")`)\n const found = microfrontends.find(microfrontend => microfrontend.slug === slug)\n if (!found) {\n const available = microfrontends.map(microfrontend => `\"${microfrontend.slug}\"`).join(\", \")\n const environment = getState().config?.environment\n // With no configured environment the host cannot name the one it was served: only the backend knows.\n const where = environment ? `environment \"${environment}\"` : \"the environment resolved from this domain\"\n throw new Error(`${PREFIX} unknown microfrontend slug \"${slug}\". Available in ${where}: ${available || \"none, this environment serves no microfrontend\"}.`)\n }\n if (!found.url) {\n throw new Error(`${PREFIX} the microfrontend \"${slug}\" is in the manifest but carries no url. Check its deployment in the console.`)\n }\n return found.url\n}\n\n/** The global variables of the environment, as a plain object. */\nexport const globalVariables = async (): Promise<Record<string, string>> => {\n const { globalVariables: variables } = await loadManifest(\"globalVariables()\")\n const result: Record<string, string> = {}\n for (const variable of variables) {\n if (variable && typeof variable.key === \"string\") {\n result[variable.key] = variable.value\n }\n }\n return result\n}\n\n/**\n * The session and device ids of this page, exposed for telemetry and debugging. Created on first\n * use and stable for the lifetime of the page, whether or not storage is available.\n */\nexport const identities = (): Identities => {\n const state = getState()\n if (!state.identities) {\n state.identities = readIdentities()\n }\n return state.identities\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mfe-orchestrator-hub/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Framework agnostic client for MFE Orchestrator: resolves version pinned microfrontend URLs from the console manifest",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Lorenzo De Francesco",
|