@matterfact/embed 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +122 -0
  2. package/dist/{chunk-6EM7T2JV.js → chunk-AANODHBV.js} +217 -15
  3. package/dist/chunk-AANODHBV.js.map +1 -0
  4. package/dist/{chunk-URGQBG4I.js → chunk-CTZEDOH7.js} +218 -17
  5. package/dist/chunk-CTZEDOH7.js.map +1 -0
  6. package/dist/{chunk-BKKXHSYU.js → chunk-JO3GWFMJ.js} +2 -2
  7. package/dist/chunk-SKJFF7RD.js +2 -0
  8. package/dist/chunk-Y7I25VHL.js +3 -0
  9. package/dist/chunk-Y7I25VHL.js.map +7 -0
  10. package/dist/context-FR7VFENN.js +3 -0
  11. package/dist/{context-ACFBWIFH.js.map → context-FR7VFENN.js.map} +1 -1
  12. package/dist/{context-U2HJJN2S.js → context-IK5MECUW.js} +4 -2
  13. package/dist/embed.js +1 -1
  14. package/dist/embed.js.map +3 -3
  15. package/dist/index.cjs +247 -13
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +88 -1
  18. package/dist/index.d.ts +88 -1
  19. package/dist/index.js +25 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/react.cjs +302 -15
  22. package/dist/react.cjs.map +1 -1
  23. package/dist/react.d.cts +174 -2
  24. package/dist/react.d.ts +174 -2
  25. package/dist/react.js +79 -3
  26. package/dist/react.js.map +1 -1
  27. package/dist/{snapshot-4GXT6PKZ.js → snapshot-2V5SDSH2.js} +2 -2
  28. package/dist/{snapshot-Y75SCGCM.js → snapshot-Y5BF2UJR.js} +3 -3
  29. package/dist/{snapshot-Y75SCGCM.js.map → snapshot-Y5BF2UJR.js.map} +1 -1
  30. package/examples/embed-demo/README.md +22 -0
  31. package/examples/embed-demo/src/App.tsx +105 -2
  32. package/examples/embed-demo/src/styles.css +29 -0
  33. package/package.json +1 -1
  34. package/dist/chunk-6EM7T2JV.js.map +0 -1
  35. package/dist/chunk-FEXG4LQJ.js +0 -3
  36. package/dist/chunk-FEXG4LQJ.js.map +0 -7
  37. package/dist/chunk-NWNMS34P.js +0 -2
  38. package/dist/chunk-URGQBG4I.js.map +0 -1
  39. package/dist/context-ACFBWIFH.js +0 -3
  40. /package/dist/{chunk-BKKXHSYU.js.map → chunk-JO3GWFMJ.js.map} +0 -0
  41. /package/dist/{chunk-NWNMS34P.js.map → chunk-SKJFF7RD.js.map} +0 -0
  42. /package/dist/{context-U2HJJN2S.js.map → context-IK5MECUW.js.map} +0 -0
  43. /package/dist/{snapshot-4GXT6PKZ.js.map → snapshot-2V5SDSH2.js.map} +0 -0
package/dist/react.cjs CHANGED
@@ -417,6 +417,165 @@ var init_hoist_runtime = __esm({
417
417
  }
418
418
  });
419
419
 
420
+ // src/adapters/registry.ts
421
+ function mfGlobal(win) {
422
+ return win?.matterfact ?? {};
423
+ }
424
+ function toolClass(name) {
425
+ if (name.startsWith("hoist.")) return "hoist";
426
+ if (name.startsWith("mf.")) return "mf";
427
+ return "app";
428
+ }
429
+ function emitEvent(win, e) {
430
+ const cb = mfGlobal(win).onEvent;
431
+ if (typeof cb !== "function") return;
432
+ try {
433
+ cb(e);
434
+ } catch {
435
+ }
436
+ }
437
+ function emitToolEvent(win, e) {
438
+ emitEvent(win, { type: "tool", ...e });
439
+ const cb = mfGlobal(win).onToolEvent;
440
+ if (typeof cb !== "function") return;
441
+ try {
442
+ cb(e);
443
+ } catch {
444
+ }
445
+ }
446
+ function mfTools(win) {
447
+ const { document: document2, artifact } = mfGlobal(win).resolve ?? {};
448
+ const out = [];
449
+ if (typeof document2 === "function") {
450
+ out.push({
451
+ name: "mf.resolveDocument",
452
+ description: 'Resolve a matterfact document (dossier, report, briefing) that is NOT on the current page to a concrete id. Call it with the document type and a key (e.g. a ticker) when the site map shows the document lives on another route. Returns {id, label, href}. Cite the result inline as <MFRef kind="document" id="<id>" label="<label>" /> \u2014 it renders as a chip that opens the document in the side panel; href is its "open on page" deeplink.',
453
+ inputSchema: {
454
+ type: "object",
455
+ properties: {
456
+ doctype: { type: "string" },
457
+ key: { type: "string" }
458
+ },
459
+ required: ["doctype", "key"]
460
+ },
461
+ readOnly: true,
462
+ confirm: "auto"
463
+ });
464
+ }
465
+ if (typeof artifact === "function") {
466
+ out.push({
467
+ name: "mf.resolveArtifact",
468
+ description: 'Resolve a matterfact artifact (an interactive board) that is NOT co-embedded on the current page to a read grant. Call it with the artifact slug. Returns {id, owner, token, label}; the grant is applied for you. Cite the result inline as <MFRef kind="artifact" id="<id>" label="<label>" /> \u2014 it renders as a chip that opens the artifact in the side panel.',
469
+ inputSchema: {
470
+ type: "object",
471
+ properties: { slug: { type: "string" } },
472
+ required: ["slug"]
473
+ },
474
+ readOnly: true,
475
+ confirm: "auto"
476
+ });
477
+ }
478
+ return out;
479
+ }
480
+ function appName(name) {
481
+ return name.startsWith("app.") ? name : `app.${name}`;
482
+ }
483
+ function appTools(win) {
484
+ const defs = mfGlobal(win).tools;
485
+ if (!Array.isArray(defs)) return [];
486
+ const out = [];
487
+ for (const d of defs) {
488
+ const raw = (d?.name ?? "").trim();
489
+ if (!raw || typeof d.handler !== "function") continue;
490
+ const name = appName(raw);
491
+ if (!APP_NAME_RE.test(name)) continue;
492
+ out.push({
493
+ name,
494
+ description: String(d.description ?? `Run the host tool ${name}.`),
495
+ inputSchema: d.inputSchema ?? { type: "object", properties: {} },
496
+ readOnly: false,
497
+ confirm: d.confirm === "auto" ? "auto" : "required"
498
+ });
499
+ }
500
+ return out;
501
+ }
502
+ function advertise(win = typeof window !== "undefined" ? window : void 0) {
503
+ const merged = [];
504
+ const seen = /* @__PURE__ */ new Set();
505
+ const push = (tools) => {
506
+ for (const t of tools) {
507
+ if (seen.has(t.name)) continue;
508
+ seen.add(t.name);
509
+ merged.push(t);
510
+ }
511
+ };
512
+ try {
513
+ push(advertiseTools(win));
514
+ } catch {
515
+ }
516
+ try {
517
+ push(mfTools(win));
518
+ push(appTools(win));
519
+ } catch {
520
+ }
521
+ return merged.slice(0, MAX_TOOLS);
522
+ }
523
+ function timeout(ms) {
524
+ return new Promise(
525
+ (_, reject) => setTimeout(() => reject(new Error("host tool timed out")), ms)
526
+ );
527
+ }
528
+ function redactResult(value) {
529
+ if (value === void 0 || value === null) return value;
530
+ if (typeof value === "string") return redact(value);
531
+ try {
532
+ return JSON.parse(redact(JSON.stringify(value)));
533
+ } catch {
534
+ return redact(String(value));
535
+ }
536
+ }
537
+ async function execute(call, win = typeof window !== "undefined" ? window : void 0) {
538
+ const cls = toolClass(call.name);
539
+ try {
540
+ if (cls === "hoist") {
541
+ return executeHostTool(call, win);
542
+ }
543
+ const handler = resolveHandler(win, call.name);
544
+ if (!handler) return { ok: false, error: "unknown tool" };
545
+ const result = await Promise.race([
546
+ Promise.resolve(handler(call.args ?? {})),
547
+ timeout(HOST_HANDLER_TIMEOUT_MS)
548
+ ]);
549
+ return { ok: true, result: redactResult(result) };
550
+ } catch (e) {
551
+ return {
552
+ ok: false,
553
+ error: redact(e instanceof Error ? e.message : "host tool failed")
554
+ };
555
+ }
556
+ }
557
+ function resolveHandler(win, name) {
558
+ const mf = mfGlobal(win);
559
+ if (name === "mf.resolveDocument") return mf.resolve?.document;
560
+ if (name === "mf.resolveArtifact") return mf.resolve?.artifact;
561
+ if (name.startsWith("app.")) {
562
+ const def = (mf.tools ?? []).find((d) => appName((d?.name ?? "").trim()) === name);
563
+ return def?.handler;
564
+ }
565
+ return void 0;
566
+ }
567
+ var MAX_TOOLS, HOST_HANDLER_TIMEOUT_MS, APP_NAME_RE;
568
+ var init_registry = __esm({
569
+ "src/adapters/registry.ts"() {
570
+ "use strict";
571
+ init_redact();
572
+ init_hoist_runtime();
573
+ MAX_TOOLS = 16;
574
+ HOST_HANDLER_TIMEOUT_MS = 3e4;
575
+ APP_NAME_RE = /^[A-Za-z0-9._-]+$/;
576
+ }
577
+ });
578
+
420
579
  // src/dom/a11y.ts
421
580
  function createStyleCache() {
422
581
  const cache = /* @__PURE__ */ new WeakMap();
@@ -900,6 +1059,7 @@ __export(context_exports, {
900
1059
  isPrivate: () => isPrivate,
901
1060
  navigateHost: () => navigateHost,
902
1061
  provideContext: () => provideContext,
1062
+ readDeclaredGrants: () => readDeclaredGrants,
903
1063
  readPageContext: () => readPageContext,
904
1064
  redact: () => redact,
905
1065
  sendRegion: () => sendRegion,
@@ -1021,12 +1181,31 @@ function grantsFromIframeSrcs(srcs, origin) {
1021
1181
  }
1022
1182
  return out;
1023
1183
  }
1184
+ function readDeclaredGrants() {
1185
+ const declared = window.matterfact?.artifacts;
1186
+ if (!Array.isArray(declared)) return [];
1187
+ const out = [];
1188
+ for (const a of declared) {
1189
+ const id = (a?.slug || "").trim();
1190
+ const owner = (a?.owner || "").trim();
1191
+ const token = (a?.token || "").trim();
1192
+ if (!id || !owner || !token) continue;
1193
+ if (out.some((g) => g.id === id)) continue;
1194
+ out.push({ id, owner, token });
1195
+ }
1196
+ return out;
1197
+ }
1024
1198
  function readArtifactGrants() {
1025
1199
  if (!widgetOrigin) return [];
1026
1200
  const srcs = Array.from(document.querySelectorAll("iframe")).map(
1027
1201
  (f) => f.getAttribute("src") || ""
1028
1202
  );
1029
- return grantsFromIframeSrcs(srcs, widgetOrigin);
1203
+ const scanned = grantsFromIframeSrcs(srcs, widgetOrigin);
1204
+ const out = [...scanned];
1205
+ for (const g of readDeclaredGrants()) {
1206
+ if (!out.some((s) => s.id === g.id)) out.push(g);
1207
+ }
1208
+ return out;
1030
1209
  }
1031
1210
  function publishArtifactGrants() {
1032
1211
  send?.({ type: "host.artifactGrants", grants: readArtifactGrants() });
@@ -1115,10 +1294,7 @@ function watchNavigation() {
1115
1294
  void publishContext();
1116
1295
  publishArtifactGrants();
1117
1296
  publishSitemap();
1118
- send?.({
1119
- type: "host.tools",
1120
- tools: advertiseTools(typeof window !== "undefined" ? window : void 0)
1121
- });
1297
+ publishTools();
1122
1298
  };
1123
1299
  navFire = fire;
1124
1300
  for (const name of ["pushState", "replaceState"]) {
@@ -1223,10 +1399,24 @@ async function callTool(call, emit) {
1223
1399
  });
1224
1400
  return;
1225
1401
  }
1226
- const r = executeHostTool(
1227
- call,
1228
- typeof window !== "undefined" ? window : void 0
1229
- );
1402
+ const win = typeof window !== "undefined" ? window : void 0;
1403
+ const cls = toolClass(call.name);
1404
+ const startedAt = Date.now();
1405
+ emitToolEvent(win, {
1406
+ phase: "call",
1407
+ name: call.name,
1408
+ toolClass: cls,
1409
+ args: call.args
1410
+ });
1411
+ const r = await execute(call, win);
1412
+ emitToolEvent(win, {
1413
+ phase: "result",
1414
+ name: call.name,
1415
+ toolClass: cls,
1416
+ ok: r.ok,
1417
+ error: r.error,
1418
+ ms: Date.now() - startedAt
1419
+ });
1230
1420
  emit({
1231
1421
  type: "host.toolResult",
1232
1422
  callId: call.callId,
@@ -1304,10 +1494,30 @@ function start(emit, origin, pageContext = true, provider) {
1304
1494
  const theme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1305
1495
  emit({ type: "host.theme", mode: theme });
1306
1496
  if (pageContextOn) {
1307
- const tools = advertiseTools(
1308
- typeof window !== "undefined" ? window : void 0
1309
- );
1310
- if (tools.length) emit({ type: "host.tools", tools });
1497
+ const w = typeof window !== "undefined" ? window : void 0;
1498
+ const tools = advertise(w);
1499
+ if (tools.length) {
1500
+ emit({ type: "host.tools", tools });
1501
+ for (const t of tools) {
1502
+ emitToolEvent(w, {
1503
+ phase: "advertise",
1504
+ name: t.name,
1505
+ toolClass: toolClass(t.name)
1506
+ });
1507
+ }
1508
+ }
1509
+ }
1510
+ }
1511
+ function publishTools() {
1512
+ const win = typeof window !== "undefined" ? window : void 0;
1513
+ const tools = advertise(win);
1514
+ send?.({ type: "host.tools", tools });
1515
+ for (const t of tools) {
1516
+ emitToolEvent(win, {
1517
+ phase: "advertise",
1518
+ name: t.name,
1519
+ toolClass: toolClass(t.name)
1520
+ });
1311
1521
  }
1312
1522
  }
1313
1523
  var widgetOrigin, MAX_ACTIVITY, send, activitySeq, activity, lastUrl, pageContextOn, contextProvider, navFire, origPushState, origReplaceState, snapshotModule, FORM_CONTROLS, ACTIONABLE, lastFocus, focusTimer, snapshotSeq;
@@ -1317,6 +1527,7 @@ var init_context = __esm({
1317
1527
  init_redact();
1318
1528
  init_hoist();
1319
1529
  init_hoist_runtime();
1530
+ init_registry();
1320
1531
  init_redact();
1321
1532
  widgetOrigin = "";
1322
1533
  MAX_ACTIVITY = 40;
@@ -1713,6 +1924,21 @@ var EmbedHost = class {
1713
1924
  __testHandle(msg) {
1714
1925
  this.handle(msg);
1715
1926
  }
1927
+ /**
1928
+ * The eager stub's own emit-and-swallow for the host's `onEvent` telemetry hook.
1929
+ * Deliberately does NOT import the registry's `emitEvent`: pulling the lazy ./context
1930
+ * chunk (where the registry lives) into the size-budgeted stub would blow it, so this
1931
+ * ~5-line duplication across the bundle boundary is intentional. Host telemetry is
1932
+ * host code — a throw here must never break the widget.
1933
+ */
1934
+ emit(e) {
1935
+ const cb = globalThis.matterfact?.onEvent;
1936
+ if (typeof cb !== "function") return;
1937
+ try {
1938
+ cb(e);
1939
+ } catch {
1940
+ }
1941
+ }
1716
1942
  handle(msg) {
1717
1943
  switch (msg.type) {
1718
1944
  case "widget.ready":
@@ -1725,8 +1951,10 @@ var EmbedHost = class {
1725
1951
  this.send({ type: "host.theme", mode: this.themeMode() });
1726
1952
  this.flush();
1727
1953
  if (this.inline) void this.loadContext();
1954
+ this.emit({ type: "ready" });
1728
1955
  break;
1729
1956
  case "widget.setOpen":
1957
+ this.emit({ type: msg.open ? "open" : "close" });
1730
1958
  if (this.inline) break;
1731
1959
  this.open = msg.open;
1732
1960
  this.place();
@@ -1915,11 +2143,16 @@ var EmbedHost = class {
1915
2143
  if (this.hostEl) this.hostEl.style.display = "none";
1916
2144
  break;
1917
2145
  case "widget.needsAuth":
2146
+ this.emit({ type: "auth", phase: "required" });
1918
2147
  void this.provideAuth();
1919
2148
  break;
1920
2149
  case "widget.navigate":
2150
+ this.emit({ type: "navigate", href: msg.href });
1921
2151
  void this.loadContext().then((m) => m.navigateHost(msg.href));
1922
2152
  break;
2153
+ case "widget.chat":
2154
+ this.emit({ type: "chat", phase: msg.phase, chatId: msg.chatId });
2155
+ break;
1923
2156
  }
1924
2157
  }
1925
2158
  /**
@@ -2032,7 +2265,9 @@ var EmbedHost = class {
2032
2265
  try {
2033
2266
  const token = await provider();
2034
2267
  if (token) this.send({ type: "host.auth", token, expiresAt: 0 });
2268
+ this.emit({ type: "auth", phase: token ? "granted" : "failed" });
2035
2269
  } catch {
2270
+ this.emit({ type: "auth", phase: "failed" });
2036
2271
  }
2037
2272
  }
2038
2273
  loadContext() {
@@ -2089,6 +2324,37 @@ function writeSitemapGlobal(sitemap) {
2089
2324
  const w = window;
2090
2325
  (w.matterfact ?? (w.matterfact = {})).sitemap = sitemap;
2091
2326
  }
2327
+ function writeToolGlobals(artifacts, resolve, tools, onToolEvent, onEvent) {
2328
+ if (typeof window === "undefined") return;
2329
+ const mf = window.matterfact ?? (window.matterfact = {});
2330
+ mf.artifacts = artifacts ?? [];
2331
+ mf.resolve = resolve ?? {};
2332
+ mf.tools = tools ?? [];
2333
+ mf.onToolEvent = onToolEvent;
2334
+ mf.onEvent = onEvent;
2335
+ }
2336
+ function toolDescriptorKey(artifacts, resolve, tools) {
2337
+ return JSON.stringify({
2338
+ // Artifact identity is data (slug/owner/label), not a function.
2339
+ artifacts: (artifacts ?? []).map((a) => ({
2340
+ slug: a.slug,
2341
+ owner: a.owner,
2342
+ label: a.label
2343
+ })),
2344
+ // For resolvers, only WHICH contracts are present changes the advertised set.
2345
+ resolve: {
2346
+ document: typeof resolve?.document === "function",
2347
+ artifact: typeof resolve?.artifact === "function"
2348
+ },
2349
+ // For host tools, the advertised descriptor — never the handler.
2350
+ tools: (tools ?? []).map((t) => ({
2351
+ name: t.name,
2352
+ description: t.description,
2353
+ inputSchema: t.inputSchema,
2354
+ confirm: t.confirm
2355
+ }))
2356
+ });
2357
+ }
2092
2358
  function MatterfactAgent({
2093
2359
  publishableKey,
2094
2360
  widgetOrigin: widgetOrigin2,
@@ -2102,7 +2368,12 @@ function MatterfactAgent({
2102
2368
  pageContext,
2103
2369
  dev,
2104
2370
  actions,
2105
- sitemap
2371
+ sitemap,
2372
+ artifacts,
2373
+ resolve,
2374
+ tools,
2375
+ onToolEvent,
2376
+ onEvent
2106
2377
  }) {
2107
2378
  const authRef = (0, import_react.useRef)(getAuthToken);
2108
2379
  authRef.current = getAuthToken;
@@ -2116,10 +2387,15 @@ function MatterfactAgent({
2116
2387
  (0, import_react.useEffect)(() => {
2117
2388
  writeSitemapGlobal(sitemap);
2118
2389
  }, [sitemapKey]);
2390
+ (0, import_react.useEffect)(() => {
2391
+ writeToolGlobals(artifacts, resolve, tools, onToolEvent, onEvent);
2392
+ });
2393
+ const toolsKey = toolDescriptorKey(artifacts, resolve, tools);
2119
2394
  (0, import_react.useEffect)(() => {
2120
2395
  if (typeof window === "undefined") return;
2121
2396
  if (inline && !slot.current) return;
2122
2397
  writeActionsGlobal(actionsRef.current);
2398
+ writeToolGlobals(artifacts, resolve, tools, onToolEvent, onEvent);
2123
2399
  const config = {
2124
2400
  publishableKey,
2125
2401
  origin: widgetOrigin2 || DEFAULT_ORIGIN,
@@ -2140,6 +2416,13 @@ function MatterfactAgent({
2140
2416
  host = mount(config);
2141
2417
  } catch (e) {
2142
2418
  console.error("[matterfact] failed to mount the embed widget", e);
2419
+ try {
2420
+ window.matterfact?.onEvent?.({
2421
+ type: "error",
2422
+ message: e instanceof Error ? e.message : String(e)
2423
+ });
2424
+ } catch {
2425
+ }
2143
2426
  }
2144
2427
  return () => host?.destroy();
2145
2428
  }, [
@@ -2150,7 +2433,11 @@ function MatterfactAgent({
2150
2433
  inline,
2151
2434
  pageContext,
2152
2435
  dev,
2153
- actionsKey
2436
+ actionsKey,
2437
+ // A change to the advertised tool SHAPE remounts so start() re-advertises under it;
2438
+ // a change to only a handler body or callback identity does not (see toolDescriptorKey).
2439
+ toolsKey
2440
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2154
2441
  ]);
2155
2442
  if (!inline) return null;
2156
2443
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(