@mfe-orchestrator-hub/client 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
 
@@ -74,7 +98,7 @@ new ModuleFederationPlugin({
74
98
  export interface OrchestratorConfig {
75
99
  backendUrl: string // ex. "https://console.mfe-orchestrator.dev/api"
76
100
  projectId: string
77
- environment: string // environment slug, ex. "DEV"
101
+ environment?: string // environment slug, ex. "DEV". Omitted: resolved by the backend from the domain
78
102
  userId?: string | (() => string | undefined | Promise<string | undefined>)
79
103
  }
80
104
 
@@ -136,6 +160,14 @@ GET {backendUrl}/serve/all/{projectId}/{environment}
136
160
  ?mfeSessionId=<uuid>&mfeDeviceId=<uuid>&mfeUserId=<optional>
137
161
  ```
138
162
 
163
+ and, when no `environment` is configured, the same request on the auto route, where the backend
164
+ resolves the environment from the domain the request comes from:
165
+
166
+ ```
167
+ GET {backendUrl}/serve/all/auto/{projectId}
168
+ ?mfeSessionId=<uuid>&mfeDeviceId=<uuid>&mfeUserId=<optional>
169
+ ```
170
+
139
171
  ### Why identities travel in the query string
140
172
 
141
173
  Microfrontends are loaded with a cross-site `import()`. Module scripts are fetched with a fixed
package/dist/index.cjs CHANGED
@@ -89,7 +89,7 @@ var NOT_CONFIGURED = `${PREFIX} call configure() first. Configure the client at
89
89
 
90
90
  import { configure } from "@mfe-orchestrator-hub/client"
91
91
 
92
- configure({ backendUrl: "\u2026", projectId: "\u2026", environment: "\u2026" })`;
92
+ configure({ backendUrl: "\u2026", projectId: "\u2026" })`;
93
93
  var withoutTrailingSlash = (value) => value.replace(/\/+$/, "");
94
94
  var resolveUserId = async (userId) => {
95
95
  const value = typeof userId === "function" ? await userId() : userId;
@@ -97,11 +97,15 @@ var resolveUserId = async (userId) => {
97
97
  };
98
98
  var sameConfig = (left, right) => left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId;
99
99
  var requireConfig = (config) => {
100
- const missing = ["backendUrl", "projectId", "environment"].filter((key) => !config?.[key]);
100
+ const missing = ["backendUrl", "projectId"].filter((key) => !config?.[key]);
101
101
  if (missing.length > 0) {
102
102
  throw new Error(`${PREFIX} configure() is missing required option${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}.`);
103
103
  }
104
104
  };
105
+ var manifestPath = (config) => {
106
+ const projectId = encodeURIComponent(config.projectId);
107
+ return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`;
108
+ };
105
109
  var manifestUrl = async (config) => {
106
110
  const { sessionId, deviceId } = identities();
107
111
  const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId });
@@ -109,7 +113,7 @@ var manifestUrl = async (config) => {
109
113
  if (userId) {
110
114
  query.set("mfeUserId", userId);
111
115
  }
112
- return `${withoutTrailingSlash(config.backendUrl)}/serve/all/${encodeURIComponent(config.projectId)}/${encodeURIComponent(config.environment)}?${query.toString()}`;
116
+ return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`;
113
117
  };
114
118
  var fetchManifest = async (config) => {
115
119
  const url = await manifestUrl(config);
@@ -159,8 +163,9 @@ var remoteUrl = async (slug) => {
159
163
  const found = microfrontends.find((microfrontend) => microfrontend.slug === slug);
160
164
  if (!found) {
161
165
  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"}.`);
166
+ const environment = getState().config?.environment;
167
+ const where = environment ? `environment "${environment}"` : "the environment resolved from this domain";
168
+ throw new Error(`${PREFIX} unknown microfrontend slug "${slug}". Available in ${where}: ${available || "none, this environment serves no microfrontend"}.`);
164
169
  }
165
170
  if (!found.url) {
166
171
  throw new Error(`${PREFIX} the microfrontend "${slug}" is in the manifest but carries no url. Check its deployment in the console.`);
@@ -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/state.ts","../src/storage.ts","../src/index.ts"],"names":["created"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport interface OrchestratorState {\n config: OrchestratorConfig | null\n manifestPromise: Promise<Manifest> | null\n identities: Identities | null\n}\n\n/**\n * The state lives on `globalThis` under this key, not in a module scope variable.\n *\n * A host can end up with two copies of this package loaded at once: the app imports the ESM build\n * while something else pulls the CJS one, or the bundler config and the app resolve to different\n * chunks. Two module scopes would mean two configurations and two manifest requests, which is\n * exactly what the contract forbids. One well known global slot makes the singleton real.\n */\nexport const STATE_KEY = \"__mfe_orchestrator_client__\"\n\nconst globalSlot = globalThis as typeof globalThis & Record<typeof STATE_KEY, OrchestratorState | undefined>\n\nexport const getState = (): OrchestratorState => {\n let state = globalSlot[STATE_KEY]\n if (!state) {\n state = { config: null, manifestPromise: null, identities: null }\n globalSlot[STATE_KEY] = state\n }\n return state\n}\n","import type { Identities } from \"./types\"\n\nexport const SESSION_ID_KEY = \"mfe-orchestrator.sessionId\"\nexport const DEVICE_ID_KEY = \"mfe-orchestrator.deviceId\"\n\nconst PROBE_KEY = \"mfe-orchestrator.probe\"\n\ntype StorageKind = \"local\" | \"session\"\n\n/**\n * Ids minted while storage was unusable. They live for the lifetime of the page so that, even\n * without storage, every request of this page load carries one stable pair of ids.\n */\nconst inMemoryIds = new Map<string, string>()\n\nconst uuidFromRandomBytes = (bytes: Uint8Array): string => {\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n const hex: string[] = []\n for (const byte of bytes) {\n hex.push(byte.toString(16).padStart(2, \"0\"))\n }\n return `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex.slice(6, 8).join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10, 16).join(\"\")}`\n}\n\n/**\n * A v4 UUID. `crypto.randomUUID` is the intended path; the two fallbacks only exist so that a page\n * served over plain http, or an exotic runtime, still gets an id instead of an exception.\n */\nexport const newId = (): string => {\n const cryptoApi = globalThis.crypto\n try {\n if (typeof cryptoApi?.randomUUID === \"function\") {\n return cryptoApi.randomUUID()\n }\n if (typeof cryptoApi?.getRandomValues === \"function\") {\n return uuidFromRandomBytes(cryptoApi.getRandomValues(new Uint8Array(16)))\n }\n } catch {\n // fall through to the non cryptographic fallback\n }\n const bytes = new Uint8Array(16)\n for (let index = 0; index < bytes.length; index++) {\n bytes[index] = Math.floor(Math.random() * 256)\n }\n return uuidFromRandomBytes(bytes)\n}\n\n/**\n * The requested storage, or `null` when it is missing or unusable.\n *\n * Both the property access and the write can throw: Safari in private mode exposes the object and\n * then refuses `setItem`, some hardened browsers throw a SecurityError on the access itself. The\n * write probe is the only reliable way to tell a usable storage from a decorative one.\n */\nconst openStorage = (kind: StorageKind): Storage | null => {\n try {\n const storage = kind === \"local\" ? globalThis.localStorage : globalThis.sessionStorage\n if (!storage) {\n return null\n }\n storage.setItem(PROBE_KEY, \"1\")\n storage.removeItem(PROBE_KEY)\n return storage\n } catch {\n return null\n }\n}\n\n/**\n * The id under `key`, read from storage and created on first use. Falls back to an id kept in\n * memory when storage cannot be used, so this never throws out of the SDK.\n */\nconst persistentId = (kind: StorageKind, key: string): string => {\n const storage = openStorage(kind)\n if (storage) {\n try {\n const stored = storage.getItem(key)\n if (stored) {\n return stored\n }\n const created = newId()\n storage.setItem(key, created)\n return created\n } catch {\n // storage passed the probe and failed anyway: keep going with an in memory id\n }\n }\n const remembered = inMemoryIds.get(key)\n if (remembered) {\n return remembered\n }\n const created = newId()\n inMemoryIds.set(key, created)\n return created\n}\n\n/** Reads, and creates when needed, the session and device ids. */\nexport const readIdentities = (): Identities => ({\n sessionId: persistentId(\"session\", SESSION_ID_KEY),\n deviceId: persistentId(\"local\", DEVICE_ID_KEY)\n})\n","import { getState } from \"./state\"\nimport { readIdentities } from \"./storage\"\nimport type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport type { GlobalVariable, Identities, Manifest, Microfrontend, OrchestratorConfig } from \"./types\"\n\nconst PREFIX = \"[@mfe-orchestrator-hub/client]\"\n\nconst NOT_CONFIGURED = `${PREFIX} call configure() first. Configure the client at the very top of the host entry point, before anything imports a remote:\n\n import { configure } from \"@mfe-orchestrator-hub/client\"\n\n configure({ backendUrl: \"…\", projectId: \"…\" })`\n\nconst withoutTrailingSlash = (value: string): string => value.replace(/\\/+$/, \"\")\n\nconst resolveUserId = async (userId: OrchestratorConfig[\"userId\"]): Promise<string | undefined> => {\n const value = typeof userId === \"function\" ? await userId() : userId\n return value === undefined || value === null || value === \"\" ? undefined : String(value)\n}\n\nconst sameConfig = (left: OrchestratorConfig, right: OrchestratorConfig): boolean =>\n left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId\n\nconst requireConfig = (config: OrchestratorConfig): void => {\n // environment is deliberately absent: without it the backend resolves it from the domain.\n const missing = ([\"backendUrl\", \"projectId\"] as const).filter(key => !config?.[key])\n if (missing.length > 0) {\n throw new Error(`${PREFIX} configure() is missing required option${missing.length > 1 ? \"s\" : \"\"}: ${missing.join(\", \")}.`)\n }\n}\n\n/**\n * Two shapes, one per route family. With an explicit environment the slug is the last segment; with\n * no environment the \"auto\" segment takes its place before the project id and the backend resolves\n * the environment from the domain the request comes from.\n */\nconst manifestPath = (config: OrchestratorConfig): string => {\n const projectId = encodeURIComponent(config.projectId)\n return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`\n}\n\nconst manifestUrl = async (config: OrchestratorConfig): Promise<string> => {\n const { sessionId, deviceId } = identities()\n const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId })\n // Resolved as late as possible: the host may only know the user after its own auth round trip.\n const userId = await resolveUserId(config.userId)\n if (userId) {\n query.set(\"mfeUserId\", userId)\n }\n return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`\n}\n\nconst fetchManifest = async (config: OrchestratorConfig): Promise<Manifest> => {\n const url = await manifestUrl(config)\n // No retry in v1: whatever fetch throws travels straight to the caller.\n const response = await fetch(url, { headers: { accept: \"application/json\" } })\n if (!response.ok) {\n throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}`)\n }\n const payload = (await response.json()) as Partial<Manifest> | null\n return {\n globalVariables: payload?.globalVariables ?? [],\n microfrontends: payload?.microfrontends ?? []\n }\n}\n\n/**\n * Hands the client its configuration. Call it once, synchronously, before any remote is imported.\n *\n * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use\n * the \"auto\" routes, where the backend resolves the environment from the domain of the host page.\n *\n * Idempotent: calling it again with the same options is a no op, so a framework provider may safely\n * call it on every render. A second call with different options is ignored, with a warning, because\n * the manifest of this page load may already be in flight or resolved.\n */\nexport const configure = (config: OrchestratorConfig): void => {\n requireConfig(config)\n const state = getState()\n const current = state.config\n if (current) {\n if (!sameConfig(current, config)) {\n console.warn(\n `${PREFIX} configure() was called again with a different configuration. The first one is kept: ${JSON.stringify({ backendUrl: current.backendUrl, projectId: current.projectId, environment: current.environment })}.`\n )\n }\n return\n }\n state.config = { ...config }\n}\n\n/**\n * The whole manifest of the environment, fetched once per page load and memoized as a promise, so\n * that concurrent callers share the single request in flight.\n *\n * A successful result is memoized for good. A failed attempt is not: the memo is dropped when the\n * shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry\n * — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the\n * page to reject every `remoteUrl()` until a full reload.\n */\nexport const manifest = (): Promise<Manifest> => {\n const state = getState()\n const config = state.config\n if (!config) {\n // Not memoized: the host may still configure the client and try again.\n return Promise.reject(new Error(NOT_CONFIGURED))\n }\n if (!state.manifestPromise) {\n // Assigned before the first await inside fetchManifest, which is what makes N concurrent\n // callers collapse onto one request.\n const attempt = fetchManifest(config)\n state.manifestPromise = attempt\n // Cleared once, when the shared attempt settles, not in each caller's own catch: every\n // caller already waiting on this attempt still gets its rejection. The identity check keeps\n // a late failure from evicting a newer attempt.\n attempt.catch(() => {\n if (state.manifestPromise === attempt) {\n state.manifestPromise = null\n }\n })\n }\n return state.manifestPromise\n}\n\n/**\n * The ready to use, version pinned URL of a remote. Awaits the manifest internally.\n *\n * The returned URL is used verbatim: the host never parses the version out of it and never strips\n * the `_v/<version>/` segment, or a classic script would load its chunks off another version.\n */\nexport const remoteUrl = async (slug: string): Promise<string> => {\n const { microfrontends } = await manifest()\n const found = microfrontends.find(microfrontend => microfrontend.slug === slug)\n if (!found) {\n const available = microfrontends.map(microfrontend => `\"${microfrontend.slug}\"`).join(\", \")\n const environment = getState().config?.environment\n // With no configured environment the host cannot name the one it was served: only the backend knows.\n const where = environment ? `environment \"${environment}\"` : \"the environment resolved from this domain\"\n throw new Error(`${PREFIX} unknown microfrontend slug \"${slug}\". Available in ${where}: ${available || \"none, this environment serves no microfrontend\"}.`)\n }\n if (!found.url) {\n throw new Error(`${PREFIX} the microfrontend \"${slug}\" is in the manifest but carries no url. Check its deployment in the console.`)\n }\n return found.url\n}\n\n/** The global variables of the environment, as a plain object. */\nexport const globalVariables = async (): Promise<Record<string, string>> => {\n const { globalVariables: variables } = await manifest()\n const result: Record<string, string> = {}\n for (const variable of variables) {\n if (variable && typeof variable.key === \"string\") {\n result[variable.key] = variable.value\n }\n }\n return result\n}\n\n/**\n * The session and device ids of this page, exposed for telemetry and debugging. Created on first\n * use and stable for the lifetime of the page, whether or not storage is available.\n */\nexport const identities = (): Identities => {\n const state = getState()\n if (!state.identities) {\n state.identities = readIdentities()\n }\n return state.identities\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -6,8 +6,13 @@ interface OrchestratorConfig {
6
6
  backendUrl: string;
7
7
  /** Id of the project the host belongs to. */
8
8
  projectId: string;
9
- /** Environment slug, ex. "DEV". */
10
- environment: string;
9
+ /**
10
+ * Environment slug, ex. "DEV". Optional: when it is omitted the client calls the "auto" routes
11
+ * and the backend resolves the environment from the domain the request comes from, out of the
12
+ * domains declared for each environment in the console. Pass it explicitly whenever the host
13
+ * already knows which environment it belongs to, or when one domain serves several of them.
14
+ */
15
+ environment?: string;
11
16
  /**
12
17
  * Identity of the logged in user, if the host has one. May be a plain value, a getter, or an
13
18
  * async getter resolved right before the manifest request is issued.
@@ -33,7 +38,10 @@ interface Microfrontend {
33
38
  /** Ready to use, already version pinned URL of the remote entry. Use it verbatim. */
34
39
  url: string;
35
40
  }
36
- /** The whole payload of `GET {backendUrl}/serve/all/{projectId}/{environmentSlug}`. */
41
+ /**
42
+ * The whole payload of `GET {backendUrl}/serve/all/{projectId}/{environmentSlug}`, or of
43
+ * `GET {backendUrl}/serve/all/auto/{projectId}` when no environment is configured.
44
+ */
37
45
  interface Manifest {
38
46
  globalVariables: GlobalVariable[];
39
47
  microfrontends: Microfrontend[];
@@ -47,6 +55,9 @@ interface Identities {
47
55
  /**
48
56
  * Hands the client its configuration. Call it once, synchronously, before any remote is imported.
49
57
  *
58
+ * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use
59
+ * the "auto" routes, where the backend resolves the environment from the domain of the host page.
60
+ *
50
61
  * Idempotent: calling it again with the same options is a no op, so a framework provider may safely
51
62
  * call it on every render. A second call with different options is ignored, with a warning, because
52
63
  * the manifest of this page load may already be in flight or resolved.
package/dist/index.d.ts CHANGED
@@ -6,8 +6,13 @@ interface OrchestratorConfig {
6
6
  backendUrl: string;
7
7
  /** Id of the project the host belongs to. */
8
8
  projectId: string;
9
- /** Environment slug, ex. "DEV". */
10
- environment: string;
9
+ /**
10
+ * Environment slug, ex. "DEV". Optional: when it is omitted the client calls the "auto" routes
11
+ * and the backend resolves the environment from the domain the request comes from, out of the
12
+ * domains declared for each environment in the console. Pass it explicitly whenever the host
13
+ * already knows which environment it belongs to, or when one domain serves several of them.
14
+ */
15
+ environment?: string;
11
16
  /**
12
17
  * Identity of the logged in user, if the host has one. May be a plain value, a getter, or an
13
18
  * async getter resolved right before the manifest request is issued.
@@ -33,7 +38,10 @@ interface Microfrontend {
33
38
  /** Ready to use, already version pinned URL of the remote entry. Use it verbatim. */
34
39
  url: string;
35
40
  }
36
- /** The whole payload of `GET {backendUrl}/serve/all/{projectId}/{environmentSlug}`. */
41
+ /**
42
+ * The whole payload of `GET {backendUrl}/serve/all/{projectId}/{environmentSlug}`, or of
43
+ * `GET {backendUrl}/serve/all/auto/{projectId}` when no environment is configured.
44
+ */
37
45
  interface Manifest {
38
46
  globalVariables: GlobalVariable[];
39
47
  microfrontends: Microfrontend[];
@@ -47,6 +55,9 @@ interface Identities {
47
55
  /**
48
56
  * Hands the client its configuration. Call it once, synchronously, before any remote is imported.
49
57
  *
58
+ * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use
59
+ * the "auto" routes, where the backend resolves the environment from the domain of the host page.
60
+ *
50
61
  * Idempotent: calling it again with the same options is a no op, so a framework provider may safely
51
62
  * call it on every render. A second call with different options is ignored, with a warning, because
52
63
  * the manifest of this page load may already be in flight or resolved.
package/dist/index.js CHANGED
@@ -87,7 +87,7 @@ var NOT_CONFIGURED = `${PREFIX} call configure() first. Configure the client at
87
87
 
88
88
  import { configure } from "@mfe-orchestrator-hub/client"
89
89
 
90
- configure({ backendUrl: "\u2026", projectId: "\u2026", environment: "\u2026" })`;
90
+ configure({ backendUrl: "\u2026", projectId: "\u2026" })`;
91
91
  var withoutTrailingSlash = (value) => value.replace(/\/+$/, "");
92
92
  var resolveUserId = async (userId) => {
93
93
  const value = typeof userId === "function" ? await userId() : userId;
@@ -95,11 +95,15 @@ var resolveUserId = async (userId) => {
95
95
  };
96
96
  var sameConfig = (left, right) => left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId;
97
97
  var requireConfig = (config) => {
98
- const missing = ["backendUrl", "projectId", "environment"].filter((key) => !config?.[key]);
98
+ const missing = ["backendUrl", "projectId"].filter((key) => !config?.[key]);
99
99
  if (missing.length > 0) {
100
100
  throw new Error(`${PREFIX} configure() is missing required option${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}.`);
101
101
  }
102
102
  };
103
+ var manifestPath = (config) => {
104
+ const projectId = encodeURIComponent(config.projectId);
105
+ return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`;
106
+ };
103
107
  var manifestUrl = async (config) => {
104
108
  const { sessionId, deviceId } = identities();
105
109
  const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId });
@@ -107,7 +111,7 @@ var manifestUrl = async (config) => {
107
111
  if (userId) {
108
112
  query.set("mfeUserId", userId);
109
113
  }
110
- return `${withoutTrailingSlash(config.backendUrl)}/serve/all/${encodeURIComponent(config.projectId)}/${encodeURIComponent(config.environment)}?${query.toString()}`;
114
+ return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`;
111
115
  };
112
116
  var fetchManifest = async (config) => {
113
117
  const url = await manifestUrl(config);
@@ -157,8 +161,9 @@ var remoteUrl = async (slug) => {
157
161
  const found = microfrontends.find((microfrontend) => microfrontend.slug === slug);
158
162
  if (!found) {
159
163
  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"}.`);
164
+ const environment = getState().config?.environment;
165
+ const where = environment ? `environment "${environment}"` : "the environment resolved from this domain";
166
+ throw new Error(`${PREFIX} unknown microfrontend slug "${slug}". Available in ${where}: ${available || "none, this environment serves no microfrontend"}.`);
162
167
  }
163
168
  if (!found.url) {
164
169
  throw new Error(`${PREFIX} the microfrontend "${slug}" is in the manifest but carries no url. Check its deployment in the console.`);
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/state.ts","../src/storage.ts","../src/index.ts"],"names":["created"],"mappings":"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","file":"index.js","sourcesContent":["import type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport interface OrchestratorState {\n config: OrchestratorConfig | null\n manifestPromise: Promise<Manifest> | null\n identities: Identities | null\n}\n\n/**\n * The state lives on `globalThis` under this key, not in a module scope variable.\n *\n * A host can end up with two copies of this package loaded at once: the app imports the ESM build\n * while something else pulls the CJS one, or the bundler config and the app resolve to different\n * chunks. Two module scopes would mean two configurations and two manifest requests, which is\n * exactly what the contract forbids. One well known global slot makes the singleton real.\n */\nexport const STATE_KEY = \"__mfe_orchestrator_client__\"\n\nconst globalSlot = globalThis as typeof globalThis & Record<typeof STATE_KEY, OrchestratorState | undefined>\n\nexport const getState = (): OrchestratorState => {\n let state = globalSlot[STATE_KEY]\n if (!state) {\n state = { config: null, manifestPromise: null, identities: null }\n globalSlot[STATE_KEY] = state\n }\n return state\n}\n","import type { Identities } from \"./types\"\n\nexport const SESSION_ID_KEY = \"mfe-orchestrator.sessionId\"\nexport const DEVICE_ID_KEY = \"mfe-orchestrator.deviceId\"\n\nconst PROBE_KEY = \"mfe-orchestrator.probe\"\n\ntype StorageKind = \"local\" | \"session\"\n\n/**\n * Ids minted while storage was unusable. They live for the lifetime of the page so that, even\n * without storage, every request of this page load carries one stable pair of ids.\n */\nconst inMemoryIds = new Map<string, string>()\n\nconst uuidFromRandomBytes = (bytes: Uint8Array): string => {\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n const hex: string[] = []\n for (const byte of bytes) {\n hex.push(byte.toString(16).padStart(2, \"0\"))\n }\n return `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex.slice(6, 8).join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10, 16).join(\"\")}`\n}\n\n/**\n * A v4 UUID. `crypto.randomUUID` is the intended path; the two fallbacks only exist so that a page\n * served over plain http, or an exotic runtime, still gets an id instead of an exception.\n */\nexport const newId = (): string => {\n const cryptoApi = globalThis.crypto\n try {\n if (typeof cryptoApi?.randomUUID === \"function\") {\n return cryptoApi.randomUUID()\n }\n if (typeof cryptoApi?.getRandomValues === \"function\") {\n return uuidFromRandomBytes(cryptoApi.getRandomValues(new Uint8Array(16)))\n }\n } catch {\n // fall through to the non cryptographic fallback\n }\n const bytes = new Uint8Array(16)\n for (let index = 0; index < bytes.length; index++) {\n bytes[index] = Math.floor(Math.random() * 256)\n }\n return uuidFromRandomBytes(bytes)\n}\n\n/**\n * The requested storage, or `null` when it is missing or unusable.\n *\n * Both the property access and the write can throw: Safari in private mode exposes the object and\n * then refuses `setItem`, some hardened browsers throw a SecurityError on the access itself. The\n * write probe is the only reliable way to tell a usable storage from a decorative one.\n */\nconst openStorage = (kind: StorageKind): Storage | null => {\n try {\n const storage = kind === \"local\" ? globalThis.localStorage : globalThis.sessionStorage\n if (!storage) {\n return null\n }\n storage.setItem(PROBE_KEY, \"1\")\n storage.removeItem(PROBE_KEY)\n return storage\n } catch {\n return null\n }\n}\n\n/**\n * The id under `key`, read from storage and created on first use. Falls back to an id kept in\n * memory when storage cannot be used, so this never throws out of the SDK.\n */\nconst persistentId = (kind: StorageKind, key: string): string => {\n const storage = openStorage(kind)\n if (storage) {\n try {\n const stored = storage.getItem(key)\n if (stored) {\n return stored\n }\n const created = newId()\n storage.setItem(key, created)\n return created\n } catch {\n // storage passed the probe and failed anyway: keep going with an in memory id\n }\n }\n const remembered = inMemoryIds.get(key)\n if (remembered) {\n return remembered\n }\n const created = newId()\n inMemoryIds.set(key, created)\n return created\n}\n\n/** Reads, and creates when needed, the session and device ids. */\nexport const readIdentities = (): Identities => ({\n sessionId: persistentId(\"session\", SESSION_ID_KEY),\n deviceId: persistentId(\"local\", DEVICE_ID_KEY)\n})\n","import { getState } from \"./state\"\nimport { readIdentities } from \"./storage\"\nimport type { Identities, Manifest, OrchestratorConfig } from \"./types\"\n\nexport type { GlobalVariable, Identities, Manifest, Microfrontend, OrchestratorConfig } from \"./types\"\n\nconst PREFIX = \"[@mfe-orchestrator-hub/client]\"\n\nconst NOT_CONFIGURED = `${PREFIX} call configure() first. Configure the client at the very top of the host entry point, before anything imports a remote:\n\n import { configure } from \"@mfe-orchestrator-hub/client\"\n\n configure({ backendUrl: \"…\", projectId: \"…\" })`\n\nconst withoutTrailingSlash = (value: string): string => value.replace(/\\/+$/, \"\")\n\nconst resolveUserId = async (userId: OrchestratorConfig[\"userId\"]): Promise<string | undefined> => {\n const value = typeof userId === \"function\" ? await userId() : userId\n return value === undefined || value === null || value === \"\" ? undefined : String(value)\n}\n\nconst sameConfig = (left: OrchestratorConfig, right: OrchestratorConfig): boolean =>\n left.backendUrl === right.backendUrl && left.projectId === right.projectId && left.environment === right.environment && left.userId === right.userId\n\nconst requireConfig = (config: OrchestratorConfig): void => {\n // environment is deliberately absent: without it the backend resolves it from the domain.\n const missing = ([\"backendUrl\", \"projectId\"] as const).filter(key => !config?.[key])\n if (missing.length > 0) {\n throw new Error(`${PREFIX} configure() is missing required option${missing.length > 1 ? \"s\" : \"\"}: ${missing.join(\", \")}.`)\n }\n}\n\n/**\n * Two shapes, one per route family. With an explicit environment the slug is the last segment; with\n * no environment the \"auto\" segment takes its place before the project id and the backend resolves\n * the environment from the domain the request comes from.\n */\nconst manifestPath = (config: OrchestratorConfig): string => {\n const projectId = encodeURIComponent(config.projectId)\n return config.environment ? `/serve/all/${projectId}/${encodeURIComponent(config.environment)}` : `/serve/all/auto/${projectId}`\n}\n\nconst manifestUrl = async (config: OrchestratorConfig): Promise<string> => {\n const { sessionId, deviceId } = identities()\n const query = new URLSearchParams({ mfeSessionId: sessionId, mfeDeviceId: deviceId })\n // Resolved as late as possible: the host may only know the user after its own auth round trip.\n const userId = await resolveUserId(config.userId)\n if (userId) {\n query.set(\"mfeUserId\", userId)\n }\n return `${withoutTrailingSlash(config.backendUrl)}${manifestPath(config)}?${query.toString()}`\n}\n\nconst fetchManifest = async (config: OrchestratorConfig): Promise<Manifest> => {\n const url = await manifestUrl(config)\n // No retry in v1: whatever fetch throws travels straight to the caller.\n const response = await fetch(url, { headers: { accept: \"application/json\" } })\n if (!response.ok) {\n throw new Error(`${PREFIX} the manifest request failed with ${response.status} ${response.statusText}. GET ${url}`)\n }\n const payload = (await response.json()) as Partial<Manifest> | null\n return {\n globalVariables: payload?.globalVariables ?? [],\n microfrontends: payload?.microfrontends ?? []\n }\n}\n\n/**\n * Hands the client its configuration. Call it once, synchronously, before any remote is imported.\n *\n * `backendUrl` and `projectId` are required. `environment` is not: omitting it makes the client use\n * the \"auto\" routes, where the backend resolves the environment from the domain of the host page.\n *\n * Idempotent: calling it again with the same options is a no op, so a framework provider may safely\n * call it on every render. A second call with different options is ignored, with a warning, because\n * the manifest of this page load may already be in flight or resolved.\n */\nexport const configure = (config: OrchestratorConfig): void => {\n requireConfig(config)\n const state = getState()\n const current = state.config\n if (current) {\n if (!sameConfig(current, config)) {\n console.warn(\n `${PREFIX} configure() was called again with a different configuration. The first one is kept: ${JSON.stringify({ backendUrl: current.backendUrl, projectId: current.projectId, environment: current.environment })}.`\n )\n }\n return\n }\n state.config = { ...config }\n}\n\n/**\n * The whole manifest of the environment, fetched once per page load and memoized as a promise, so\n * that concurrent callers share the single request in flight.\n *\n * A successful result is memoized for good. A failed attempt is not: the memo is dropped when the\n * shared attempt settles as rejected, so the next caller starts a fresh request. That is not a retry\n * — nothing here loops or backs off — it only means one flaky lookup at boot does not condemn the\n * page to reject every `remoteUrl()` until a full reload.\n */\nexport const manifest = (): Promise<Manifest> => {\n const state = getState()\n const config = state.config\n if (!config) {\n // Not memoized: the host may still configure the client and try again.\n return Promise.reject(new Error(NOT_CONFIGURED))\n }\n if (!state.manifestPromise) {\n // Assigned before the first await inside fetchManifest, which is what makes N concurrent\n // callers collapse onto one request.\n const attempt = fetchManifest(config)\n state.manifestPromise = attempt\n // Cleared once, when the shared attempt settles, not in each caller's own catch: every\n // caller already waiting on this attempt still gets its rejection. The identity check keeps\n // a late failure from evicting a newer attempt.\n attempt.catch(() => {\n if (state.manifestPromise === attempt) {\n state.manifestPromise = null\n }\n })\n }\n return state.manifestPromise\n}\n\n/**\n * The ready to use, version pinned URL of a remote. Awaits the manifest internally.\n *\n * The returned URL is used verbatim: the host never parses the version out of it and never strips\n * the `_v/<version>/` segment, or a classic script would load its chunks off another version.\n */\nexport const remoteUrl = async (slug: string): Promise<string> => {\n const { microfrontends } = await manifest()\n const found = microfrontends.find(microfrontend => microfrontend.slug === slug)\n if (!found) {\n const available = microfrontends.map(microfrontend => `\"${microfrontend.slug}\"`).join(\", \")\n const environment = getState().config?.environment\n // With no configured environment the host cannot name the one it was served: only the backend knows.\n const where = environment ? `environment \"${environment}\"` : \"the environment resolved from this domain\"\n throw new Error(`${PREFIX} unknown microfrontend slug \"${slug}\". Available in ${where}: ${available || \"none, this environment serves no microfrontend\"}.`)\n }\n if (!found.url) {\n throw new Error(`${PREFIX} the microfrontend \"${slug}\" is in the manifest but carries no url. Check its deployment in the console.`)\n }\n return found.url\n}\n\n/** The global variables of the environment, as a plain object. */\nexport const globalVariables = async (): Promise<Record<string, string>> => {\n const { globalVariables: variables } = await manifest()\n const result: Record<string, string> = {}\n for (const variable of variables) {\n if (variable && typeof variable.key === \"string\") {\n result[variable.key] = variable.value\n }\n }\n return result\n}\n\n/**\n * The session and device ids of this page, exposed for telemetry and debugging. Created on first\n * use and stable for the lifetime of the page, whether or not storage is available.\n */\nexport const identities = (): Identities => {\n const state = getState()\n if (!state.identities) {\n state.identities = readIdentities()\n }\n return state.identities\n}\n"]}
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.1",
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",