@mfe-orchestrator-hub/client 0.1.0 → 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 CHANGED
@@ -25,10 +25,34 @@ Call `configure()` at the very top of the host entry point, before anything impo
25
25
  ```ts
26
26
  import { configure } from "@mfe-orchestrator-hub/client"
27
27
 
28
+ configure({
29
+ backendUrl: import.meta.env.VITE_MFE_BACKEND_URL,
30
+ projectId: import.meta.env.VITE_MFE_PROJECT_ID
31
+ })
32
+ ```
33
+
34
+ `backendUrl` and `projectId` are the only required options.
35
+
36
+ ### Which environment am I?
37
+
38
+ `environment` is optional. Leave it out and the client calls the *auto* routes, where the backend
39
+ resolves the environment from the domain the request comes from — the domains you declared for each
40
+ environment in the console. One build then serves staging and production without an environment
41
+ variable to set per deployment.
42
+
43
+ ```ts
44
+ // staging.example.com → the STAGING environment, production.example.com → PROD, decided by the backend
45
+ configure({ backendUrl: "…", projectId: "…" })
46
+ ```
47
+
48
+ Pass it explicitly when the host already knows which environment it belongs to, or when one domain
49
+ has to serve several of them:
50
+
51
+ ```ts
28
52
  configure({
29
53
  backendUrl: import.meta.env.VITE_MFE_BACKEND_URL,
30
54
  projectId: import.meta.env.VITE_MFE_PROJECT_ID,
31
- environment: import.meta.env.VITE_MFE_ENVIRONMENT
55
+ environment: import.meta.env.VITE_MFE_ENVIRONMENT // ex. "DEV"
32
56
  })
33
57
  ```
34
58
 
@@ -68,13 +92,42 @@ new ModuleFederationPlugin({
68
92
  })
69
93
  ```
70
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
+
71
124
  ## API
72
125
 
73
126
  ```ts
74
127
  export interface OrchestratorConfig {
75
128
  backendUrl: string // ex. "https://console.mfe-orchestrator.dev/api"
76
129
  projectId: string
77
- environment: string // environment slug, ex. "DEV"
130
+ environment?: string // environment slug, ex. "DEV". Omitted: resolved by the backend from the domain
78
131
  userId?: string | (() => string | undefined | Promise<string | undefined>)
79
132
  }
80
133
 
@@ -136,6 +189,14 @@ GET {backendUrl}/serve/all/{projectId}/{environment}
136
189
  ?mfeSessionId=<uuid>&mfeDeviceId=<uuid>&mfeUserId=<optional>
137
190
  ```
138
191
 
192
+ and, when no `environment` is configured, the same request on the auto route, where the backend
193
+ resolves the environment from the domain the request comes from:
194
+
195
+ ```
196
+ GET {backendUrl}/serve/all/auto/{projectId}
197
+ ?mfeSessionId=<uuid>&mfeDeviceId=<uuid>&mfeUserId=<optional>
198
+ ```
199
+
139
200
  ### Why identities travel in the query string
140
201
 
141
202
  Microfrontends are loaded with a cross-site `import()`. Module scripts are fetched with a fixed
@@ -175,6 +236,49 @@ This is not a retry: nothing here loops, schedules or backs off. It only means t
175
236
  or one 502 during a console redeploy does not condemn the page to reject every `remoteUrl()` until a
176
237
  full reload. Recovering is the host app's call, by asking again.
177
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
+
178
282
  ## Not in v1
179
283
 
180
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,24 +228,37 @@ 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", environment: "\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
- const missing = ["backendUrl", "projectId", "environment"].filter((key) => !config?.[key]);
101
- if (missing.length > 0) {
102
- throw new Error(`${PREFIX} configure() is missing required option${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}.`);
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
+ );
103
249
  }
104
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
+ );
256
+ }
257
+ };
258
+ var manifestPath = (config) => {
259
+ const projectId = encodeURIComponent(config.projectId);
260
+ return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`;
261
+ };
105
262
  var manifestUrl = async (config) => {
106
263
  const { sessionId, deviceId } = identities();
107
264
  const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId });
@@ -109,13 +266,27 @@ var manifestUrl = async (config) => {
109
266
  if (userId) {
110
267
  query.set("mfeUserId", userId);
111
268
  }
112
- return `${withoutTrailingSlash(config.backendUrl)}/serve/all/${encodeURIComponent(config.projectId)}/${encodeURIComponent(config.environment)}?${query.toString()}`;
269
+ return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`;
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 "";
113
284
  };
114
285
  var fetchManifest = async (config) => {
115
286
  const url = await manifestUrl(config);
116
287
  const response = await fetch(url, { headers: { accept: "application/json" } });
117
288
  if (!response.ok) {
118
- 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)}`);
119
290
  }
120
291
  const payload = await response.json();
121
292
  return {
@@ -124,8 +295,8 @@ var fetchManifest = async (config) => {
124
295
  };
125
296
  };
126
297
  var configure = (config) => {
127
- requireConfig(config);
128
298
  const state = getState();
299
+ requireConfig(config, state.integration);
129
300
  const current = state.config;
130
301
  if (current) {
131
302
  if (!sameConfig(current, config)) {
@@ -136,12 +307,16 @@ var configure = (config) => {
136
307
  return;
137
308
  }
138
309
  state.config = { ...config };
310
+ warnAboutConfig(config);
311
+ };
312
+ var registerIntegration = (integration) => {
313
+ getState().integration = integration;
139
314
  };
140
- var manifest = () => {
315
+ var loadManifest = (caller) => {
141
316
  const state = getState();
142
317
  const config = state.config;
143
318
  if (!config) {
144
- return Promise.reject(new Error(NOT_CONFIGURED));
319
+ return Promise.reject(new Error(notConfiguredMessage(caller, state.integration)));
145
320
  }
146
321
  if (!state.manifestPromise) {
147
322
  const attempt = fetchManifest(config);
@@ -154,13 +329,15 @@ var manifest = () => {
154
329
  }
155
330
  return state.manifestPromise;
156
331
  };
332
+ var manifest = () => loadManifest("manifest()");
157
333
  var remoteUrl = async (slug) => {
158
- const { microfrontends } = await manifest();
334
+ const { microfrontends } = await loadManifest(`remoteUrl("${slug}")`);
159
335
  const found = microfrontends.find((microfrontend) => microfrontend.slug === slug);
160
336
  if (!found) {
161
337
  const available = microfrontends.map((microfrontend) => `"${microfrontend.slug}"`).join(", ");
162
- const { environment } = getState().config ?? { environment: "?" };
163
- throw new Error(`${PREFIX} unknown microfrontend slug "${slug}". Available in environment "${environment}": ${available || "none, this environment serves no microfrontend"}.`);
338
+ const environment = getState().config?.environment;
339
+ const where = environment ? `environment "${environment}"` : "the environment resolved from this domain";
340
+ throw new Error(`${PREFIX} unknown microfrontend slug "${slug}". Available in ${where}: ${available || "none, this environment serves no microfrontend"}.`);
164
341
  }
165
342
  if (!found.url) {
166
343
  throw new Error(`${PREFIX} the microfrontend "${slug}" is in the manifest but carries no url. Check its deployment in the console.`);
@@ -168,7 +345,7 @@ var remoteUrl = async (slug) => {
168
345
  return found.url;
169
346
  };
170
347
  var globalVariables = async () => {
171
- const { globalVariables: variables } = await manifest();
348
+ const { globalVariables: variables } = await loadManifest("globalVariables()");
172
349
  const result = {};
173
350
  for (const variable of variables) {
174
351
  if (variable && typeof variable.key === "string") {
@@ -189,6 +366,7 @@ exports.configure = configure;
189
366
  exports.globalVariables = globalVariables;
190
367
  exports.identities = identities;
191
368
  exports.manifest = manifest;
369
+ exports.registerIntegration = registerIntegration;
192
370
  exports.remoteUrl = remoteUrl;
193
371
  //# sourceMappingURL=index.cjs.map
194
372
  //# sourceMappingURL=index.cjs.map
@@ -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: \"…\", environment: \"…\" })`\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 const missing = ([\"backendUrl\", \"projectId\", \"environment\"] 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\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)}/serve/all/${encodeURIComponent(config.projectId)}/${encodeURIComponent(config.environment)}?${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 * 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 throw new Error(`${PREFIX} unknown microfrontend slug \"${slug}\". Available in environment \"${environment}\": ${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
  */
@@ -6,8 +19,13 @@ interface OrchestratorConfig {
6
19
  backendUrl: string;
7
20
  /** Id of the project the host belongs to. */
8
21
  projectId: string;
9
- /** Environment slug, ex. "DEV". */
10
- environment: string;
22
+ /**
23
+ * Environment slug, ex. "DEV". Optional: when it is omitted the client calls the "auto" routes
24
+ * and the backend resolves the environment from the domain the request comes from, out of the
25
+ * domains declared for each environment in the console. Pass it explicitly whenever the host
26
+ * already knows which environment it belongs to, or when one domain serves several of them.
27
+ */
28
+ environment?: string;
11
29
  /**
12
30
  * Identity of the logged in user, if the host has one. May be a plain value, a getter, or an
13
31
  * async getter resolved right before the manifest request is issued.
@@ -33,7 +51,10 @@ interface Microfrontend {
33
51
  /** Ready to use, already version pinned URL of the remote entry. Use it verbatim. */
34
52
  url: string;
35
53
  }
36
- /** The whole payload of `GET {backendUrl}/serve/all/{projectId}/{environmentSlug}`. */
54
+ /**
55
+ * The whole payload of `GET {backendUrl}/serve/all/{projectId}/{environmentSlug}`, or of
56
+ * `GET {backendUrl}/serve/all/auto/{projectId}` when no environment is configured.
57
+ */
37
58
  interface Manifest {
38
59
  globalVariables: GlobalVariable[];
39
60
  microfrontends: Microfrontend[];
@@ -47,20 +68,21 @@ interface Identities {
47
68
  /**
48
69
  * Hands the client its configuration. Call it once, synchronously, before any remote is imported.
49
70
  *
71
+ * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use
72
+ * the "auto" routes, where the backend resolves the environment from the domain of the host page.
73
+ *
50
74
  * Idempotent: calling it again with the same options is a no op, so a framework provider may safely
51
75
  * call it on every render. A second call with different options is ignored, with a warning, because
52
76
  * the manifest of this page load may already be in flight or resolved.
53
77
  */
54
78
  declare const configure: (config: OrchestratorConfig) => void;
55
79
  /**
56
- * The whole manifest of the environment, fetched once per page load and memoized as a promise, so
57
- * that concurrent callers share the single request in flight.
58
- *
59
- * A successful result is memoized for good. A failed attempt is not: the memo is dropped when the
60
- * shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry
61
- * — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the
62
- * 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.
63
83
  */
84
+ declare const registerIntegration: (integration: Integration) => void;
85
+ /** The whole manifest of the environment. See `loadManifest` for the memoization contract. */
64
86
  declare const manifest: () => Promise<Manifest>;
65
87
  /**
66
88
  * The ready to use, version pinned URL of a remote. Awaits the manifest internally.
@@ -77,4 +99,4 @@ declare const globalVariables: () => Promise<Record<string, string>>;
77
99
  */
78
100
  declare const identities: () => Identities;
79
101
 
80
- 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
  */
@@ -6,8 +19,13 @@ interface OrchestratorConfig {
6
19
  backendUrl: string;
7
20
  /** Id of the project the host belongs to. */
8
21
  projectId: string;
9
- /** Environment slug, ex. "DEV". */
10
- environment: string;
22
+ /**
23
+ * Environment slug, ex. "DEV". Optional: when it is omitted the client calls the "auto" routes
24
+ * and the backend resolves the environment from the domain the request comes from, out of the
25
+ * domains declared for each environment in the console. Pass it explicitly whenever the host
26
+ * already knows which environment it belongs to, or when one domain serves several of them.
27
+ */
28
+ environment?: string;
11
29
  /**
12
30
  * Identity of the logged in user, if the host has one. May be a plain value, a getter, or an
13
31
  * async getter resolved right before the manifest request is issued.
@@ -33,7 +51,10 @@ interface Microfrontend {
33
51
  /** Ready to use, already version pinned URL of the remote entry. Use it verbatim. */
34
52
  url: string;
35
53
  }
36
- /** The whole payload of `GET {backendUrl}/serve/all/{projectId}/{environmentSlug}`. */
54
+ /**
55
+ * The whole payload of `GET {backendUrl}/serve/all/{projectId}/{environmentSlug}`, or of
56
+ * `GET {backendUrl}/serve/all/auto/{projectId}` when no environment is configured.
57
+ */
37
58
  interface Manifest {
38
59
  globalVariables: GlobalVariable[];
39
60
  microfrontends: Microfrontend[];
@@ -47,20 +68,21 @@ interface Identities {
47
68
  /**
48
69
  * Hands the client its configuration. Call it once, synchronously, before any remote is imported.
49
70
  *
71
+ * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use
72
+ * the "auto" routes, where the backend resolves the environment from the domain of the host page.
73
+ *
50
74
  * Idempotent: calling it again with the same options is a no op, so a framework provider may safely
51
75
  * call it on every render. A second call with different options is ignored, with a warning, because
52
76
  * the manifest of this page load may already be in flight or resolved.
53
77
  */
54
78
  declare const configure: (config: OrchestratorConfig) => void;
55
79
  /**
56
- * The whole manifest of the environment, fetched once per page load and memoized as a promise, so
57
- * that concurrent callers share the single request in flight.
58
- *
59
- * A successful result is memoized for good. A failed attempt is not: the memo is dropped when the
60
- * shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry
61
- * — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the
62
- * 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.
63
83
  */
84
+ declare const registerIntegration: (integration: Integration) => void;
85
+ /** The whole manifest of the environment. See `loadManifest` for the memoization contract. */
64
86
  declare const manifest: () => Promise<Manifest>;
65
87
  /**
66
88
  * The ready to use, version pinned URL of a remote. Awaits the manifest internally.
@@ -77,4 +99,4 @@ declare const globalVariables: () => Promise<Record<string, string>>;
77
99
  */
78
100
  declare const identities: () => Identities;
79
101
 
80
- 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,24 +226,37 @@ 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", environment: "\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
- const missing = ["backendUrl", "projectId", "environment"].filter((key) => !config?.[key]);
99
- if (missing.length > 0) {
100
- throw new Error(`${PREFIX} configure() is missing required option${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}.`);
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
+ );
101
247
  }
102
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
+ );
254
+ }
255
+ };
256
+ var manifestPath = (config) => {
257
+ const projectId = encodeURIComponent(config.projectId);
258
+ return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`;
259
+ };
103
260
  var manifestUrl = async (config) => {
104
261
  const { sessionId, deviceId } = identities();
105
262
  const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId });
@@ -107,13 +264,27 @@ var manifestUrl = async (config) => {
107
264
  if (userId) {
108
265
  query.set("mfeUserId", userId);
109
266
  }
110
- return `${withoutTrailingSlash(config.backendUrl)}/serve/all/${encodeURIComponent(config.projectId)}/${encodeURIComponent(config.environment)}?${query.toString()}`;
267
+ return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`;
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 "";
111
282
  };
112
283
  var fetchManifest = async (config) => {
113
284
  const url = await manifestUrl(config);
114
285
  const response = await fetch(url, { headers: { accept: "application/json" } });
115
286
  if (!response.ok) {
116
- 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)}`);
117
288
  }
118
289
  const payload = await response.json();
119
290
  return {
@@ -122,8 +293,8 @@ var fetchManifest = async (config) => {
122
293
  };
123
294
  };
124
295
  var configure = (config) => {
125
- requireConfig(config);
126
296
  const state = getState();
297
+ requireConfig(config, state.integration);
127
298
  const current = state.config;
128
299
  if (current) {
129
300
  if (!sameConfig(current, config)) {
@@ -134,12 +305,16 @@ var configure = (config) => {
134
305
  return;
135
306
  }
136
307
  state.config = { ...config };
308
+ warnAboutConfig(config);
309
+ };
310
+ var registerIntegration = (integration) => {
311
+ getState().integration = integration;
137
312
  };
138
- var manifest = () => {
313
+ var loadManifest = (caller) => {
139
314
  const state = getState();
140
315
  const config = state.config;
141
316
  if (!config) {
142
- return Promise.reject(new Error(NOT_CONFIGURED));
317
+ return Promise.reject(new Error(notConfiguredMessage(caller, state.integration)));
143
318
  }
144
319
  if (!state.manifestPromise) {
145
320
  const attempt = fetchManifest(config);
@@ -152,13 +327,15 @@ var manifest = () => {
152
327
  }
153
328
  return state.manifestPromise;
154
329
  };
330
+ var manifest = () => loadManifest("manifest()");
155
331
  var remoteUrl = async (slug) => {
156
- const { microfrontends } = await manifest();
332
+ const { microfrontends } = await loadManifest(`remoteUrl("${slug}")`);
157
333
  const found = microfrontends.find((microfrontend) => microfrontend.slug === slug);
158
334
  if (!found) {
159
335
  const available = microfrontends.map((microfrontend) => `"${microfrontend.slug}"`).join(", ");
160
- const { environment } = getState().config ?? { environment: "?" };
161
- throw new Error(`${PREFIX} unknown microfrontend slug "${slug}". Available in environment "${environment}": ${available || "none, this environment serves no microfrontend"}.`);
336
+ const environment = getState().config?.environment;
337
+ const where = environment ? `environment "${environment}"` : "the environment resolved from this domain";
338
+ throw new Error(`${PREFIX} unknown microfrontend slug "${slug}". Available in ${where}: ${available || "none, this environment serves no microfrontend"}.`);
162
339
  }
163
340
  if (!found.url) {
164
341
  throw new Error(`${PREFIX} the microfrontend "${slug}" is in the manifest but carries no url. Check its deployment in the console.`);
@@ -166,7 +343,7 @@ var remoteUrl = async (slug) => {
166
343
  return found.url;
167
344
  };
168
345
  var globalVariables = async () => {
169
- const { globalVariables: variables } = await manifest();
346
+ const { globalVariables: variables } = await loadManifest("globalVariables()");
170
347
  const result = {};
171
348
  for (const variable of variables) {
172
349
  if (variable && typeof variable.key === "string") {
@@ -183,6 +360,6 @@ var identities = () => {
183
360
  return state.identities;
184
361
  };
185
362
 
186
- export { configure, globalVariables, identities, manifest, remoteUrl };
363
+ export { configure, globalVariables, identities, manifest, registerIntegration, remoteUrl };
187
364
  //# sourceMappingURL=index.js.map
188
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: \"…\", environment: \"…\" })`\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 const missing = ([\"backendUrl\", \"projectId\", \"environment\"] 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\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)}/serve/all/${encodeURIComponent(config.projectId)}/${encodeURIComponent(config.environment)}?${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 * 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 throw new Error(`${PREFIX} unknown microfrontend slug \"${slug}\". Available in environment \"${environment}\": ${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.0",
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",