@aglyn/aglyn 1.0.0-beta.213 → 1.0.0-beta.214

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
  3. package/src/lib/app-utils/docs-help.generated.js +1 -0
  4. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  5. package/src/lib/app-utils/docs-index.generated.js +3 -3
  6. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  7. package/src/lib/app-utils/enabled-plugins-context.d.ts +8 -4
  8. package/src/lib/app-utils/enabled-plugins-context.js +9 -4
  9. package/src/lib/app-utils/enabled-plugins-context.js.map +1 -1
  10. package/src/lib/app-utils/host-naming.d.ts +2 -29
  11. package/src/lib/app-utils/host-naming.js +5 -28
  12. package/src/lib/app-utils/host-naming.js.map +1 -1
  13. package/src/lib/app-utils/media-ref.js +1 -1
  14. package/src/lib/app-utils/media-ref.js.map +1 -1
  15. package/src/lib/app-utils/plugin-bundle-checks.js +193 -15
  16. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  17. package/src/lib/app-utils/realm-elements-context.d.ts +17 -0
  18. package/src/lib/app-utils/realm-elements-context.js +35 -0
  19. package/src/lib/app-utils/realm-elements-context.js.map +1 -0
  20. package/src/lib/app-utils/tenant-apex.d.ts +45 -0
  21. package/src/lib/app-utils/tenant-apex.js +48 -0
  22. package/src/lib/app-utils/tenant-apex.js.map +1 -0
  23. package/src/lib/app-utils/wire-nodes.d.ts +64 -0
  24. package/src/lib/app-utils/wire-nodes.js +112 -0
  25. package/src/lib/app-utils/wire-nodes.js.map +1 -0
  26. package/src/lib/plugin-manager/plugin-contributions.d.ts +8 -3
  27. package/src/lib/plugin-manager/plugin-contributions.js +5 -3
  28. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  29. package/src/lib/plugin-manager/realm-plugins.d.ts +70 -0
  30. package/src/lib/plugin-manager/realm-plugins.js +143 -29
  31. package/src/lib/plugin-manager/realm-plugins.js.map +1 -1
@@ -47,6 +47,102 @@ import { capturePluginStyles } from "./plugin-styles.js";
47
47
  */ export function isCompatibleHostAbi(hostAbi) {
48
48
  return hostAbi === undefined || hostAbi === PLUGIN_HOST_ABI_VERSION;
49
49
  }
50
+ export function snapshotRealmRegistry(registry) {
51
+ return {
52
+ factories: _extends({}, registry.factories),
53
+ schemas: _extends({}, registry.schemas),
54
+ presets: _extends({}, registry.presets)
55
+ };
56
+ }
57
+ /** The keys whose value differs between two maps, including added and removed. */ function changedKeys(before, after) {
58
+ const keys = new Set([
59
+ ...Object.keys(before),
60
+ ...Object.keys(after)
61
+ ]);
62
+ return [
63
+ ...keys
64
+ ].filter((key)=>before[key] !== after[key]);
65
+ }
66
+ /**
67
+ * What a realm bundle registered that it may not have (AGL-3390).
68
+ *
69
+ * The registry is process-global: on a server it holds every site's
70
+ * elements. A bundle is therefore held to three rules:
71
+ *
72
+ * - It replaces and removes nothing that was registered before it ran.
73
+ * - A component id containing a `.` is a marketplace namespace
74
+ * (`<identity>.<role>`), and a bundle may use only its own.
75
+ * - A bundle published with an identity registers every component in that
76
+ * namespace, and every component and preset names that identity as its
77
+ * plugin, which is what the renderer's per-site gate reads.
78
+ *
79
+ * Empty when the bundle kept to them.
80
+ */ export function realmRegistrationProblems(before, after, install) {
81
+ const problems = [];
82
+ const identity = install.identity;
83
+ const components = new Set([
84
+ ...changedKeys(before.factories, after.factories),
85
+ ...changedKeys(before.schemas, after.schemas)
86
+ ]);
87
+ for (const id of components){
88
+ var _after_schemas_id;
89
+ const existed = id in before.factories || id in before.schemas;
90
+ const exists = id in after.factories || id in after.schemas;
91
+ if (existed) {
92
+ problems.push(`${exists ? 'replaces' : 'removes'} component ${id}`);
93
+ continue;
94
+ }
95
+ const inOwnNamespace = Boolean(identity) && id.startsWith(`${identity}.`);
96
+ if (id.includes('.') && !inOwnNamespace) {
97
+ problems.push(`registers ${id} outside its namespace`);
98
+ } else if (identity && !inOwnNamespace) {
99
+ problems.push(`registers ${id}, which is not ${identity}.<role>`);
100
+ } else if (identity && ((_after_schemas_id = after.schemas[id]) == null ? void 0 : _after_schemas_id.pluginId) !== identity) {
101
+ problems.push(`registers ${id} under another plugin id`);
102
+ }
103
+ }
104
+ for (const id of changedKeys(before.presets, after.presets)){
105
+ var _after_presets_id;
106
+ if (id in before.presets) {
107
+ problems.push(`${id in after.presets ? 'replaces' : 'removes'} preset ${id}`);
108
+ } else if (identity && ((_after_presets_id = after.presets[id]) == null ? void 0 : _after_presets_id.pluginId) !== identity) {
109
+ problems.push(`registers preset ${id} under another plugin id`);
110
+ }
111
+ }
112
+ return problems;
113
+ }
114
+ /** Puts the registry back the way `before` had it, for every key that moved. */ function restoreRealmRegistry(registry, before) {
115
+ const components = new Set([
116
+ ...changedKeys(before.factories, registry.factories),
117
+ ...changedKeys(before.schemas, registry.schemas)
118
+ ]);
119
+ for (const id of components){
120
+ const factory = before.factories[id];
121
+ const schema = before.schemas[id];
122
+ if (factory && schema) {
123
+ registry.registerComponent(factory, schema);
124
+ } else {
125
+ registry.unregisterComponent(id);
126
+ }
127
+ }
128
+ for (const id of changedKeys(before.presets, registry.presets)){
129
+ const preset = before.presets[id];
130
+ if (preset) registry.registerPreset(preset);
131
+ else registry.unregisterPreset(id);
132
+ }
133
+ }
134
+ /** Imports verified bytes in a browser, through a blob URL. */ async function importFromBlob(bytes) {
135
+ const blobUrl = URL.createObjectURL(new Blob([
136
+ bytes
137
+ ], {
138
+ type: 'text/javascript'
139
+ }));
140
+ try {
141
+ return await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ blobUrl);
142
+ } finally{
143
+ URL.revokeObjectURL(blobUrl);
144
+ }
145
+ }
50
146
  const decodeBase64 = (value)=>{
51
147
  if (typeof atob === 'function') {
52
148
  return Uint8Array.from(atob(value), (char)=>char.charCodeAt(0));
@@ -126,6 +222,12 @@ export async function sha256Hex(bytes) {
126
222
  }, host);
127
223
  }
128
224
  const loaded = new Map();
225
+ /** The tail of the queue {@link runExclusively} runs work on. */ let registering = Promise.resolve();
226
+ /** Runs `work` after every earlier call's work has settled. */ function runExclusively(work) {
227
+ const run = registering.then(()=>work(), ()=>work());
228
+ registering = run.catch(()=>undefined);
229
+ return run;
230
+ }
129
231
  /**
130
232
  * Fetches, verifies, and executes realm bundles in the app realm. Each
131
233
  * bundle default-exports (or exports) `register(host)`; failures are
@@ -143,13 +245,49 @@ const loaded = new Map();
143
245
  console.error(`realm plugins: refusing to load ${realmInstalls.length} bundle(s) — ` + 'NEXT_PUBLIC_PLUGIN_TRUST_PUBLIC_KEY is not configured, so signatures ' + 'cannot be verified');
144
246
  return;
145
247
  }
248
+ const runBundle = async (bytes, install)=>{
249
+ const registry = options.registry;
250
+ const before = registry ? snapshotRealmRegistry(registry) : null;
251
+ // AGL-2486: module evaluation and `register()` both run inside
252
+ // the style capture, because a bundler's `import './x.css'`
253
+ // compiles to a `document.head.appendChild(<style>)` at module
254
+ // EVAL time — before `register` is ever called. Anything the
255
+ // bundle injects in that window is mirrored into the besigner
256
+ // canvas's closed shadow root, which a document-level rule
257
+ // otherwise cannot reach at all (measured: no effect on the
258
+ // canvas, wins outright on the published page). See
259
+ // `plugin-styles.tsx`.
260
+ await capturePluginStyles(install.listingId, async ()=>{
261
+ var _options_importModule, _mod_register;
262
+ var _mod_default;
263
+ const mod = await ((_options_importModule = options.importModule) != null ? _options_importModule : importFromBlob)(bytes);
264
+ const register = (_mod_register = mod.register) != null ? _mod_register : (_mod_default = mod.default) == null ? void 0 : _mod_default.register;
265
+ if (typeof register !== 'function') {
266
+ throw new Error('bundle exports no register(host)');
267
+ }
268
+ register(host);
269
+ });
270
+ // Held to its namespace (AGL-3390). A bundle that broke a rule is
271
+ // undone whole, not in part: half a plugin is harder to reason
272
+ // about than none, and the published page already treats a
273
+ // plugin that failed to load as absent.
274
+ if (registry && before) {
275
+ const problems = realmRegistrationProblems(before, snapshotRealmRegistry(registry), install);
276
+ if (problems.length) {
277
+ restoreRealmRegistry(registry, before);
278
+ throw new Error(`refused: ${problems.join('; ')}`);
279
+ }
280
+ }
281
+ };
146
282
  await Promise.all(realmInstalls.map((install)=>{
147
283
  const key = `${install.listingId}@${install.version}`;
148
284
  let promise = loaded.get(key);
149
285
  if (!promise) {
150
286
  promise = (async ()=>{
151
287
  const url = `${options.artifactsBase.replace(/\/+$/, '')}/${pluginArtifactPath(install.listingId, install.version, install.sha256)}`;
152
- const response = await fetch(url);
288
+ const response = await fetch(url, options.fetchTimeoutMs ? {
289
+ signal: AbortSignal.timeout(options.fetchTimeoutMs)
290
+ } : undefined);
153
291
  if (!response.ok) throw new Error(`fetch ${response.status}`);
154
292
  if (!isCompatibleHostAbi(install.hostAbi)) {
155
293
  throw new Error(`built for host ABI ${install.hostAbi}, host is ${PLUGIN_HOST_ABI_VERSION}`);
@@ -160,34 +298,10 @@ const loaded = new Map();
160
298
  const bytes = await response.arrayBuffer();
161
299
  const verdict = await verifyRealmBundle(bytes, install, options.publicKeyBase64);
162
300
  if (verdict.ok === false) throw new Error(verdict.reason);
163
- const blobUrl = URL.createObjectURL(new Blob([
164
- bytes
165
- ], {
166
- type: 'text/javascript'
167
- }));
168
- try {
169
- // AGL-2486: module evaluation and `register()` both run inside
170
- // the style capture, because a bundler's `import './x.css'`
171
- // compiles to a `document.head.appendChild(<style>)` at module
172
- // EVAL time — before `register` is ever called. Anything the
173
- // bundle injects in that window is mirrored into the besigner
174
- // canvas's closed shadow root, which a document-level rule
175
- // otherwise cannot reach at all (measured: no effect on the
176
- // canvas, wins outright on the published page). See
177
- // `plugin-styles.tsx`.
178
- await capturePluginStyles(install.listingId, async ()=>{
179
- var _mod_register;
180
- var _mod_default;
181
- const mod = await import(/* webpackIgnore: true */ blobUrl);
182
- const register = (_mod_register = mod.register) != null ? _mod_register : (_mod_default = mod.default) == null ? void 0 : _mod_default.register;
183
- if (typeof register !== 'function') {
184
- throw new Error('bundle exports no register(host)');
185
- }
186
- register(host);
187
- });
188
- } finally{
189
- URL.revokeObjectURL(blobUrl);
190
- }
301
+ // One bundle runs at a time: the check below reads what changed in
302
+ // the registry while THIS bundle ran, and another bundle's
303
+ // registrations landing in the same window would be blamed on it.
304
+ await runExclusively(()=>runBundle(bytes, install));
191
305
  })().catch((error)=>{
192
306
  loaded.delete(key);
193
307
  console.error(`realm plugin ${key} failed to load:`, error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/realm-plugins.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginArtifactPath } from '../app-utils/plugin-artifact-path'\nimport type { PluginContributions } from './plugin-contributions'\nimport { capturePluginStyles } from './plugin-styles'\n\n/**\n * Trusted-realm remote plugins (AGL-420). Marketplace bundles normally run\n * inside the cross-origin sandboxed PluginFrame; listings a staff member\n * has REVIEWED AND SIGNED (`trust: 'realm'`) may instead load into the app\n * realm and register real components/runtimes — first-party-grade\n * extensions installed from the marketplace.\n *\n * Trust chain, all of which must hold before a byte executes:\n * 1. The install doc pins {version, sha256}; the fetched bundle's SHA-256\n * must match (content-addressing — a swapped artifact can't load).\n * 2. The version doc carries an Ed25519 `signature` over the sha256 hex,\n * made by the platform signing key (staff publish flow). When a public\n * key is configured, verification is MANDATORY and fails closed —\n * including on runtimes without WebCrypto Ed25519 support.\n * 3. Bundles import nothing: the host injects its React/registry\n * singletons through `globalThis.__AGLYN_PLUGIN_HOST__` (see\n * {@link setRealmPluginHost}), so there is exactly one registry/React\n * instance — the same invariant that keeps first-party canvases from\n * rendering blank.\n *\n * Everything here is isomorphic (WebCrypto only, no node imports) so the\n * client barrel stays browser-safe; the server-side loader for API\n * handler bundles lives in realm-server.ts (node-only, /server entry).\n */\n\nexport const PLUGIN_HOST_ABI_VERSION = 1\n\n/** The pinned install shape the loaders consume (installs + version doc). */\nexport interface RealmPluginInstall {\n listingId: string\n version: string\n sha256: string\n /** Staff-granted tier; anything but 'realm' never realm-loads. */\n trust?: string\n /** Ed25519 signature (base64) over the sha256 hex string. */\n signature?: string\n /** Host ABI the bundle targets (AGL-429); mismatches never load. */\n hostAbi?: number\n /**\n * The pinned version's manifest id (AGL-3116): the `pluginId` its own\n * elements carry, which is how a page shows it uses an undeclared plugin.\n */\n pluginId?: string\n /**\n * What the pinned version declares it contributes (AGL-3116); absent when\n * it declares nothing, and then the default in `plugin-contributions.ts`\n * decides where it loads.\n */\n contributes?: PluginContributions\n}\n\n/**\n * ABI gate (AGL-429): a declared `hostAbi` must equal the running host's\n * generation; absent = pre-compat bundle, allowed (with a loader warning)\n * so existing installs keep working across the introduction of the field.\n */\nexport function isCompatibleHostAbi(hostAbi: number | undefined): boolean {\n return hostAbi === undefined || hostAbi === PLUGIN_HOST_ABI_VERSION\n}\n\nexport interface LoadRealmPluginsOptions {\n /** Base URL of the content-addressed artifacts origin. */\n artifactsBase: string\n /**\n * Base64 raw Ed25519 public key. When set, unsigned or badly-signed\n * bundles are rejected; when unset, signature checking is skipped and\n * the Firestore trust flag + sha pinning are the whole gate (dev mode).\n */\n publicKeyBase64?: string\n}\n\nconst decodeBase64 = (value: string): Uint8Array => {\n if (typeof atob === 'function') {\n return Uint8Array.from(atob(value), (char) => char.charCodeAt(0))\n }\n // Node without atob (never in practice on the pinned runtime, kept for\n // safety). Reached through `globalThis` rather than as a bare global: a\n // free `Buffer` identifier makes a browser bundler inject its 22 KB\n // polyfill for a branch no browser can enter.\n return new Uint8Array(globalThis.Buffer.from(value, 'base64'))\n}\n\nexport async function sha256Hex(bytes: ArrayBuffer): Promise<string> {\n const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Verifies the platform Ed25519 signature over a sha256 hex string.\n * Fails CLOSED: any import/verify error (including runtimes without\n * WebCrypto Ed25519) returns false.\n */\nexport async function verifyRealmSignature(\n shaHex: string,\n signatureBase64: string,\n publicKeyBase64: string,\n): Promise<boolean> {\n try {\n const key = await globalThis.crypto.subtle.importKey(\n 'raw',\n decodeBase64(publicKeyBase64) as unknown as ArrayBuffer,\n { name: 'Ed25519' },\n false,\n ['verify'],\n )\n return await globalThis.crypto.subtle.verify(\n { name: 'Ed25519' },\n key,\n decodeBase64(signatureBase64) as unknown as ArrayBuffer,\n new TextEncoder().encode(shaHex),\n )\n } catch {\n return false\n }\n}\n\n/**\n * Verifies fetched bundle bytes against a pinned install: content hash\n * always; platform signature when a public key is configured. Returns the\n * reason on failure so callers can log precisely.\n */\nexport async function verifyRealmBundle(\n bytes: ArrayBuffer,\n install: RealmPluginInstall,\n publicKeyBase64?: string,\n): Promise<{ ok: true } | { ok: false; reason: string }> {\n const digest = await sha256Hex(bytes)\n if (digest !== install.sha256) {\n return { ok: false, reason: `sha256 mismatch (got ${digest.slice(0, 12)}…)` }\n }\n if (publicKeyBase64) {\n if (!install.signature) return { ok: false, reason: 'missing signature' }\n const valid = await verifyRealmSignature(\n install.sha256,\n install.signature,\n publicKeyBase64,\n )\n if (!valid) return { ok: false, reason: 'invalid signature' }\n }\n return { ok: true }\n}\n\n// ---------------------------------------------------------------------------\n// Host ABI + client loader (browser only past this point).\n// ---------------------------------------------------------------------------\n\n/**\n * `PLUGIN_HOST_GLOBAL` (`app-utils/plugin-host-abi.ts`), spelled out: this\n * module ships on every published page and the ABI module's key lists would\n * ride along for one string. `realm-plugins.spec.ts` holds the two equal.\n */\nconst HOST_GLOBAL = '__AGLYN_PLUGIN_HOST__'\n\n/**\n * Publishes the host ABI realm bundles build against. The APP composes it\n * (React/jsxRuntime/MUI must come from the app bundle so singletons hold) —\n * core only owns the slot, the version stamp and the key list\n * (`PLUGIN_HOST_ABI_KEYS`).\n */\nexport function setRealmPluginHost(host: Record<string, unknown>): void {\n ;(globalThis as Record<string, unknown>)[HOST_GLOBAL] = {\n version: PLUGIN_HOST_ABI_VERSION,\n ...host,\n }\n}\n\nconst loaded = new Map<string, Promise<void>>()\n\n/**\n * Fetches, verifies, and executes realm bundles in the app realm. Each\n * bundle default-exports (or exports) `register(host)`; failures are\n * logged and skipped — a broken marketplace plugin must never take the\n * console or a site down. Idempotent per listing@version.\n */\nexport async function loadRealmPlugins(\n installs: readonly RealmPluginInstall[],\n options: LoadRealmPluginsOptions,\n): Promise<void> {\n const host = (globalThis as Record<string, unknown>)[HOST_GLOBAL]\n const realmInstalls = installs.filter((install) => install.trust === 'realm')\n // Fail closed (AGL-507): without the trust public key we cannot verify\n // bundle signatures, so refuse to load any realm plugin rather than fall\n // back to sha256-only — mirroring the mandatory server-side key check\n // (realm-server.ts). Silently degrading here contradicted the documented\n // \"verification is MANDATORY and fails closed\" contract.\n if (realmInstalls.length > 0 && !options.publicKeyBase64) {\n console.error(\n `realm plugins: refusing to load ${realmInstalls.length} bundle(s) — ` +\n 'NEXT_PUBLIC_PLUGIN_TRUST_PUBLIC_KEY is not configured, so signatures ' +\n 'cannot be verified',\n )\n return\n }\n await Promise.all(\n realmInstalls.map((install) => {\n const key = `${install.listingId}@${install.version}`\n let promise = loaded.get(key)\n if (!promise) {\n promise = (async () => {\n const url = `${options.artifactsBase.replace(/\\/+$/, '')}/${pluginArtifactPath(\n install.listingId,\n install.version,\n install.sha256,\n )}`\n const response = await fetch(url)\n if (!response.ok) throw new Error(`fetch ${response.status}`)\n if (!isCompatibleHostAbi(install.hostAbi)) {\n throw new Error(\n `built for host ABI ${install.hostAbi}, host is ${PLUGIN_HOST_ABI_VERSION}`,\n )\n }\n if (install.hostAbi === undefined) {\n console.warn(\n `realm plugin ${key}: no hostAbi declared (pre-AGL-429 bundle)`,\n )\n }\n const bytes = await response.arrayBuffer()\n const verdict = await verifyRealmBundle(\n bytes,\n install,\n options.publicKeyBase64,\n )\n if (verdict.ok === false) throw new Error(verdict.reason)\n const blobUrl = URL.createObjectURL(\n new Blob([bytes], { type: 'text/javascript' }),\n )\n try {\n // AGL-2486: module evaluation and `register()` both run inside\n // the style capture, because a bundler's `import './x.css'`\n // compiles to a `document.head.appendChild(<style>)` at module\n // EVAL time — before `register` is ever called. Anything the\n // bundle injects in that window is mirrored into the besigner\n // canvas's closed shadow root, which a document-level rule\n // otherwise cannot reach at all (measured: no effect on the\n // canvas, wins outright on the published page). See\n // `plugin-styles.tsx`.\n await capturePluginStyles(install.listingId, async () => {\n const mod = (await import(\n /* webpackIgnore: true */ blobUrl\n )) as {\n register?: (host: unknown) => void\n default?: { register?: (host: unknown) => void }\n }\n const register = mod.register ?? mod.default?.register\n if (typeof register !== 'function') {\n throw new Error('bundle exports no register(host)')\n }\n register(host)\n })\n } finally {\n URL.revokeObjectURL(blobUrl)\n }\n })().catch((error) => {\n loaded.delete(key)\n console.error(`realm plugin ${key} failed to load:`, error)\n })\n loaded.set(key, promise)\n }\n return promise\n }),\n )\n}\n"],"names":["pluginArtifactPath","capturePluginStyles","PLUGIN_HOST_ABI_VERSION","isCompatibleHostAbi","hostAbi","undefined","decodeBase64","value","atob","Uint8Array","from","char","charCodeAt","globalThis","Buffer","sha256Hex","bytes","digest","crypto","subtle","Array","map","byte","toString","padStart","join","verifyRealmSignature","shaHex","signatureBase64","publicKeyBase64","key","importKey","name","verify","TextEncoder","encode","verifyRealmBundle","install","sha256","ok","reason","slice","signature","valid","HOST_GLOBAL","setRealmPluginHost","host","version","loaded","Map","loadRealmPlugins","installs","options","realmInstalls","filter","trust","length","console","error","Promise","all","listingId","promise","get","url","artifactsBase","replace","response","fetch","Error","status","warn","arrayBuffer","verdict","blobUrl","URL","createObjectURL","Blob","type","mod","register","default","revokeObjectURL","catch","delete","set"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/realm-plugins.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginArtifactPath } from '../app-utils/plugin-artifact-path'\nimport type { PluginContributions } from './plugin-contributions'\nimport { capturePluginStyles } from './plugin-styles'\n\n/**\n * Trusted-realm remote plugins (AGL-420). Marketplace bundles normally run\n * inside the cross-origin sandboxed PluginFrame; listings a staff member\n * has REVIEWED AND SIGNED (`trust: 'realm'`) may instead load into the app\n * realm and register real components/runtimes — first-party-grade\n * extensions installed from the marketplace.\n *\n * Trust chain, all of which must hold before a byte executes:\n * 1. The install doc pins {version, sha256}; the fetched bundle's SHA-256\n * must match (content-addressing — a swapped artifact can't load).\n * 2. The version doc carries an Ed25519 `signature` over the sha256 hex,\n * made by the platform signing key (staff publish flow). When a public\n * key is configured, verification is MANDATORY and fails closed —\n * including on runtimes without WebCrypto Ed25519 support.\n * 3. Bundles import nothing: the host injects its React/registry\n * singletons through `globalThis.__AGLYN_PLUGIN_HOST__` (see\n * {@link setRealmPluginHost}), so there is exactly one registry/React\n * instance — the same invariant that keeps first-party canvases from\n * rendering blank.\n *\n * Everything here is isomorphic (WebCrypto only, no node imports) so the\n * client barrel stays browser-safe; the server-side loader for API\n * handler bundles lives in realm-server.ts (node-only, /server entry).\n */\n\nexport const PLUGIN_HOST_ABI_VERSION = 1\n\n/** The pinned install shape the loaders consume (installs + version doc). */\nexport interface RealmPluginInstall {\n listingId: string\n version: string\n sha256: string\n /** Staff-granted tier; anything but 'realm' never realm-loads. */\n trust?: string\n /** Ed25519 signature (base64) over the sha256 hex string. */\n signature?: string\n /** Host ABI the bundle targets (AGL-429); mismatches never load. */\n hostAbi?: number\n /**\n * The pinned version's manifest id (AGL-3116): the `pluginId` its own\n * elements carry, which is how a page shows it uses an undeclared plugin.\n */\n pluginId?: string\n /**\n * The namespace the pinned version was published under (AGL-3390),\n * `<publisher handle>.<manifest id>`: the `pluginId` its components carry\n * and the prefix of their ids. Absent on a version published before\n * identities existed.\n */\n identity?: string\n /**\n * What the pinned version declares it contributes (AGL-3116); absent when\n * it declares nothing, and then the default in `plugin-contributions.ts`\n * decides where it loads.\n */\n contributes?: PluginContributions\n}\n\n/**\n * ABI gate (AGL-429): a declared `hostAbi` must equal the running host's\n * generation; absent = pre-compat bundle, allowed (with a loader warning)\n * so existing installs keep working across the introduction of the field.\n */\nexport function isCompatibleHostAbi(hostAbi: number | undefined): boolean {\n return hostAbi === undefined || hostAbi === PLUGIN_HOST_ABI_VERSION\n}\n\n/** What a realm bundle's module exports. */\nexport interface RealmBundleModule {\n register?: (host: unknown) => void\n default?: { register?: (host: unknown) => void }\n}\n\n/**\n * The slice of the component registry a realm load is held to (AGL-3390):\n * the app passes core's `components`, whose maps these are.\n */\nexport interface RealmRegistry {\n factories: Record<string, unknown>\n schemas: Record<string, { pluginId?: string } | undefined>\n presets: Record<string, { pluginId?: string } | undefined>\n registerComponent(factory: never, schema: never): void\n unregisterComponent(id: string): void\n registerPreset(preset: never): void\n unregisterPreset(id: string): void\n}\n\nexport interface LoadRealmPluginsOptions {\n /** Base URL of the content-addressed artifacts origin. */\n artifactsBase: string\n /**\n * Base64 raw Ed25519 public key. When set, unsigned or badly-signed\n * bundles are rejected; when unset, signature checking is skipped and\n * the Firestore trust flag + sha pinning are the whole gate (dev mode).\n */\n publicKeyBase64?: string\n /**\n * The registry every bundle's registrations are checked against\n * (AGL-3390). See {@link realmRegistrationProblems}.\n */\n registry?: RealmRegistry\n /**\n * Turns verified bytes into a module. The browser's default imports a blob\n * URL; a server, which cannot import one, passes a `data:` URL importer.\n */\n importModule?: (bytes: ArrayBuffer) => Promise<RealmBundleModule>\n /** Aborts an artifact fetch that has not answered, in milliseconds. */\n fetchTimeoutMs?: number\n}\n\n/** A copy of the registry's three maps, taken before a bundle runs. */\nexport interface RealmRegistrySnapshot {\n factories: Record<string, unknown>\n schemas: Record<string, { pluginId?: string } | undefined>\n presets: Record<string, { pluginId?: string } | undefined>\n}\n\nexport function snapshotRealmRegistry(\n registry: RealmRegistrySnapshot,\n): RealmRegistrySnapshot {\n return {\n factories: { ...registry.factories },\n schemas: { ...registry.schemas },\n presets: { ...registry.presets },\n }\n}\n\n/** The keys whose value differs between two maps, including added and removed. */\nfunction changedKeys(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n): string[] {\n const keys = new Set([...Object.keys(before), ...Object.keys(after)])\n return [...keys].filter((key) => before[key] !== after[key])\n}\n\n/**\n * What a realm bundle registered that it may not have (AGL-3390).\n *\n * The registry is process-global: on a server it holds every site's\n * elements. A bundle is therefore held to three rules:\n *\n * - It replaces and removes nothing that was registered before it ran.\n * - A component id containing a `.` is a marketplace namespace\n * (`<identity>.<role>`), and a bundle may use only its own.\n * - A bundle published with an identity registers every component in that\n * namespace, and every component and preset names that identity as its\n * plugin, which is what the renderer's per-site gate reads.\n *\n * Empty when the bundle kept to them.\n */\nexport function realmRegistrationProblems(\n before: RealmRegistrySnapshot,\n after: RealmRegistrySnapshot,\n install: Pick<RealmPluginInstall, 'identity'>,\n): string[] {\n const problems: string[] = []\n const identity = install.identity\n const components = new Set([\n ...changedKeys(before.factories, after.factories),\n ...changedKeys(before.schemas, after.schemas),\n ])\n for (const id of components) {\n const existed = id in before.factories || id in before.schemas\n const exists = id in after.factories || id in after.schemas\n if (existed) {\n problems.push(`${exists ? 'replaces' : 'removes'} component ${id}`)\n continue\n }\n const inOwnNamespace = Boolean(identity) && id.startsWith(`${identity}.`)\n if (id.includes('.') && !inOwnNamespace) {\n problems.push(`registers ${id} outside its namespace`)\n } else if (identity && !inOwnNamespace) {\n problems.push(`registers ${id}, which is not ${identity}.<role>`)\n } else if (identity && after.schemas[id]?.pluginId !== identity) {\n problems.push(`registers ${id} under another plugin id`)\n }\n }\n for (const id of changedKeys(before.presets, after.presets)) {\n if (id in before.presets) {\n problems.push(`${id in after.presets ? 'replaces' : 'removes'} preset ${id}`)\n } else if (identity && after.presets[id]?.pluginId !== identity) {\n problems.push(`registers preset ${id} under another plugin id`)\n }\n }\n return problems\n}\n\n/** Puts the registry back the way `before` had it, for every key that moved. */\nfunction restoreRealmRegistry(\n registry: RealmRegistry,\n before: RealmRegistrySnapshot,\n): void {\n const components = new Set([\n ...changedKeys(before.factories, registry.factories),\n ...changedKeys(before.schemas, registry.schemas),\n ])\n for (const id of components) {\n const factory = before.factories[id]\n const schema = before.schemas[id]\n if (factory && schema) {\n registry.registerComponent(factory as never, schema as never)\n } else {\n registry.unregisterComponent(id)\n }\n }\n for (const id of changedKeys(before.presets, registry.presets)) {\n const preset = before.presets[id]\n if (preset) registry.registerPreset(preset as never)\n else registry.unregisterPreset(id)\n }\n}\n\n/** Imports verified bytes in a browser, through a blob URL. */\nasync function importFromBlob(bytes: ArrayBuffer): Promise<RealmBundleModule> {\n const blobUrl = URL.createObjectURL(\n new Blob([bytes], { type: 'text/javascript' }),\n )\n try {\n return (await import(\n /* webpackIgnore: true */ /* turbopackIgnore: true */ blobUrl\n )) as RealmBundleModule\n } finally {\n URL.revokeObjectURL(blobUrl)\n }\n}\n\nconst decodeBase64 = (value: string): Uint8Array => {\n if (typeof atob === 'function') {\n return Uint8Array.from(atob(value), (char) => char.charCodeAt(0))\n }\n // Node without atob (never in practice on the pinned runtime, kept for\n // safety). Reached through `globalThis` rather than as a bare global: a\n // free `Buffer` identifier makes a browser bundler inject its 22 KB\n // polyfill for a branch no browser can enter.\n return new Uint8Array(globalThis.Buffer.from(value, 'base64'))\n}\n\nexport async function sha256Hex(bytes: ArrayBuffer): Promise<string> {\n const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Verifies the platform Ed25519 signature over a sha256 hex string.\n * Fails CLOSED: any import/verify error (including runtimes without\n * WebCrypto Ed25519) returns false.\n */\nexport async function verifyRealmSignature(\n shaHex: string,\n signatureBase64: string,\n publicKeyBase64: string,\n): Promise<boolean> {\n try {\n const key = await globalThis.crypto.subtle.importKey(\n 'raw',\n decodeBase64(publicKeyBase64) as unknown as ArrayBuffer,\n { name: 'Ed25519' },\n false,\n ['verify'],\n )\n return await globalThis.crypto.subtle.verify(\n { name: 'Ed25519' },\n key,\n decodeBase64(signatureBase64) as unknown as ArrayBuffer,\n new TextEncoder().encode(shaHex),\n )\n } catch {\n return false\n }\n}\n\n/**\n * Verifies fetched bundle bytes against a pinned install: content hash\n * always; platform signature when a public key is configured. Returns the\n * reason on failure so callers can log precisely.\n */\nexport async function verifyRealmBundle(\n bytes: ArrayBuffer,\n install: RealmPluginInstall,\n publicKeyBase64?: string,\n): Promise<{ ok: true } | { ok: false; reason: string }> {\n const digest = await sha256Hex(bytes)\n if (digest !== install.sha256) {\n return { ok: false, reason: `sha256 mismatch (got ${digest.slice(0, 12)}…)` }\n }\n if (publicKeyBase64) {\n if (!install.signature) return { ok: false, reason: 'missing signature' }\n const valid = await verifyRealmSignature(\n install.sha256,\n install.signature,\n publicKeyBase64,\n )\n if (!valid) return { ok: false, reason: 'invalid signature' }\n }\n return { ok: true }\n}\n\n// ---------------------------------------------------------------------------\n// Host ABI + client loader (browser only past this point).\n// ---------------------------------------------------------------------------\n\n/**\n * `PLUGIN_HOST_GLOBAL` (`app-utils/plugin-host-abi.ts`), spelled out: this\n * module ships on every published page and the ABI module's key lists would\n * ride along for one string. `realm-plugins.spec.ts` holds the two equal.\n */\nconst HOST_GLOBAL = '__AGLYN_PLUGIN_HOST__'\n\n/**\n * Publishes the host ABI realm bundles build against. The APP composes it\n * (React/jsxRuntime/MUI must come from the app bundle so singletons hold) —\n * core only owns the slot, the version stamp and the key list\n * (`PLUGIN_HOST_ABI_KEYS`).\n */\nexport function setRealmPluginHost(host: Record<string, unknown>): void {\n ;(globalThis as Record<string, unknown>)[HOST_GLOBAL] = {\n version: PLUGIN_HOST_ABI_VERSION,\n ...host,\n }\n}\n\nconst loaded = new Map<string, Promise<void>>()\n\n/** The tail of the queue {@link runExclusively} runs work on. */\nlet registering: Promise<unknown> = Promise.resolve()\n\n/** Runs `work` after every earlier call's work has settled. */\nfunction runExclusively<T>(work: () => Promise<T>): Promise<T> {\n const run = registering.then(\n (): Promise<T> => work(),\n (): Promise<T> => work(),\n )\n registering = run.catch((): undefined => undefined)\n return run\n}\n\n/**\n * Fetches, verifies, and executes realm bundles in the app realm. Each\n * bundle default-exports (or exports) `register(host)`; failures are\n * logged and skipped — a broken marketplace plugin must never take the\n * console or a site down. Idempotent per listing@version.\n */\nexport async function loadRealmPlugins(\n installs: readonly RealmPluginInstall[],\n options: LoadRealmPluginsOptions,\n): Promise<void> {\n const host = (globalThis as Record<string, unknown>)[HOST_GLOBAL]\n const realmInstalls = installs.filter((install) => install.trust === 'realm')\n // Fail closed (AGL-507): without the trust public key we cannot verify\n // bundle signatures, so refuse to load any realm plugin rather than fall\n // back to sha256-only — mirroring the mandatory server-side key check\n // (realm-server.ts). Silently degrading here contradicted the documented\n // \"verification is MANDATORY and fails closed\" contract.\n if (realmInstalls.length > 0 && !options.publicKeyBase64) {\n console.error(\n `realm plugins: refusing to load ${realmInstalls.length} bundle(s) — ` +\n 'NEXT_PUBLIC_PLUGIN_TRUST_PUBLIC_KEY is not configured, so signatures ' +\n 'cannot be verified',\n )\n return\n }\n const runBundle = async (\n bytes: ArrayBuffer,\n install: RealmPluginInstall,\n ): Promise<void> => {\n const registry = options.registry\n const before = registry ? snapshotRealmRegistry(registry) : null\n // AGL-2486: module evaluation and `register()` both run inside\n // the style capture, because a bundler's `import './x.css'`\n // compiles to a `document.head.appendChild(<style>)` at module\n // EVAL time — before `register` is ever called. Anything the\n // bundle injects in that window is mirrored into the besigner\n // canvas's closed shadow root, which a document-level rule\n // otherwise cannot reach at all (measured: no effect on the\n // canvas, wins outright on the published page). See\n // `plugin-styles.tsx`.\n await capturePluginStyles(install.listingId, async () => {\n const mod = await (options.importModule ?? importFromBlob)(bytes)\n const register = mod.register ?? mod.default?.register\n if (typeof register !== 'function') {\n throw new Error('bundle exports no register(host)')\n }\n register(host)\n })\n // Held to its namespace (AGL-3390). A bundle that broke a rule is\n // undone whole, not in part: half a plugin is harder to reason\n // about than none, and the published page already treats a\n // plugin that failed to load as absent.\n if (registry && before) {\n const problems = realmRegistrationProblems(\n before,\n snapshotRealmRegistry(registry),\n install,\n )\n if (problems.length) {\n restoreRealmRegistry(registry, before)\n throw new Error(`refused: ${problems.join('; ')}`)\n }\n }\n }\n\n await Promise.all(\n realmInstalls.map((install) => {\n const key = `${install.listingId}@${install.version}`\n let promise = loaded.get(key)\n if (!promise) {\n promise = (async () => {\n const url = `${options.artifactsBase.replace(/\\/+$/, '')}/${pluginArtifactPath(\n install.listingId,\n install.version,\n install.sha256,\n )}`\n const response = await fetch(\n url,\n options.fetchTimeoutMs\n ? { signal: AbortSignal.timeout(options.fetchTimeoutMs) }\n : undefined,\n )\n if (!response.ok) throw new Error(`fetch ${response.status}`)\n if (!isCompatibleHostAbi(install.hostAbi)) {\n throw new Error(\n `built for host ABI ${install.hostAbi}, host is ${PLUGIN_HOST_ABI_VERSION}`,\n )\n }\n if (install.hostAbi === undefined) {\n console.warn(\n `realm plugin ${key}: no hostAbi declared (pre-AGL-429 bundle)`,\n )\n }\n const bytes = await response.arrayBuffer()\n const verdict = await verifyRealmBundle(\n bytes,\n install,\n options.publicKeyBase64,\n )\n if (verdict.ok === false) throw new Error(verdict.reason)\n // One bundle runs at a time: the check below reads what changed in\n // the registry while THIS bundle ran, and another bundle's\n // registrations landing in the same window would be blamed on it.\n await runExclusively(() => runBundle(bytes, install))\n })().catch((error) => {\n loaded.delete(key)\n console.error(`realm plugin ${key} failed to load:`, error)\n })\n loaded.set(key, promise)\n }\n return promise\n }),\n )\n}\n"],"names":["pluginArtifactPath","capturePluginStyles","PLUGIN_HOST_ABI_VERSION","isCompatibleHostAbi","hostAbi","undefined","snapshotRealmRegistry","registry","factories","schemas","presets","changedKeys","before","after","keys","Set","Object","filter","key","realmRegistrationProblems","install","problems","identity","components","id","existed","exists","push","inOwnNamespace","Boolean","startsWith","includes","pluginId","restoreRealmRegistry","factory","schema","registerComponent","unregisterComponent","preset","registerPreset","unregisterPreset","importFromBlob","bytes","blobUrl","URL","createObjectURL","Blob","type","revokeObjectURL","decodeBase64","value","atob","Uint8Array","from","char","charCodeAt","globalThis","Buffer","sha256Hex","digest","crypto","subtle","Array","map","byte","toString","padStart","join","verifyRealmSignature","shaHex","signatureBase64","publicKeyBase64","importKey","name","verify","TextEncoder","encode","verifyRealmBundle","sha256","ok","reason","slice","signature","valid","HOST_GLOBAL","setRealmPluginHost","host","version","loaded","Map","registering","Promise","resolve","runExclusively","work","run","then","catch","loadRealmPlugins","installs","options","realmInstalls","trust","length","console","error","runBundle","listingId","mod","importModule","register","default","Error","all","promise","get","url","artifactsBase","replace","response","fetch","fetchTimeoutMs","signal","AbortSignal","timeout","status","warn","arrayBuffer","verdict","delete","set"],"mappings":"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"}