@matterfact/embed 0.13.0 → 0.16.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.
package/README.md CHANGED
@@ -61,6 +61,39 @@ about the data behind it — no wiring, and the token never reaches the model.
61
61
  An artifact can declare typed params — a company, a time window, a peer set. Readers change
62
62
  them in a control bar on the artifact itself; your page can drive them too.
63
63
 
64
+ There are two artifacts you might be driving, and they're **different iframes** — one
65
+ doesn't reach the other:
66
+
67
+ | The artifact… | Drive it with |
68
+ |---|---|
69
+ | on your page, via `<MatterfactArtifact>` | the `params` prop |
70
+ | inside the chat widget | `host.setArtifactParams()` |
71
+
72
+ ### An artifact on your page
73
+
74
+ ```tsx
75
+ <MatterfactArtifact
76
+ slug="tsla-liquidity"
77
+ owner="you@firm.com"
78
+ token="…"
79
+ params={{ ticker, window: '5Y' }}
80
+ onParamsChange={(params, source) => setTicker(params.ticker)}
81
+ />
82
+ ```
83
+
84
+ Pass your whole param state — the component works out what actually changed and pushes only
85
+ that. Safe to set before the artifact has loaded; it's delivered as soon as the frame is
86
+ ready. Changing a param never reloads the artifact.
87
+
88
+ Dropping a key stops driving it and leaves its current value alone; there is no "unset".
89
+ Re-adding a key you'd dropped re-asserts it, which is how you force a value back after a
90
+ reader has changed it.
91
+
92
+ `onParamsChange` fires for reader- and page-context-driven changes, never for your own
93
+ pushes — so feeding it straight back into `params` can't loop.
94
+
95
+ ### An artifact inside the chat
96
+
64
97
  ```js
65
98
  const host = mount({ …config });
66
99
 
@@ -69,8 +102,8 @@ host.setArtifactParams({ ticker: 'NVDA' }); // or window.matterfact.setArtifac
69
102
 
70
103
  Safe to call before the widget finishes loading — it's queued and delivered on ready.
71
104
 
72
- **A push is authoritative for every key it names.** Send only the params you're actually
73
- changing:
105
+ **A push is authoritative for every key it names.** Unlike the `params` prop, this is raw:
106
+ send only the params you're actually changing.
74
107
 
75
108
  ```js
76
109
  host.setArtifactParams({ window: '5Y' }); // ✅ changes the window
@@ -90,6 +123,8 @@ onEvent(e) {
90
123
  }
91
124
  ```
92
125
 
126
+ ### Either way
127
+
93
128
  A param can also follow your page automatically: if it declares a binding and you publish
94
129
  page context (`window.matterfact.context` or `getPageContext`), it tracks the entity you're
95
130
  showing with no glue code. A reader's own pick takes over from there, until they choose to
package/dist/react.cjs CHANGED
@@ -1362,8 +1362,8 @@ async function readFocus() {
1362
1362
  }
1363
1363
  const active = document.activeElement;
1364
1364
  if (active && active !== document.body && !isPrivate(active) && !active.closest("[data-mf-private]")) {
1365
- const { snapshot: snapshot2 } = await loadSnapshotModule();
1366
- void snapshot2;
1365
+ const { snapshot: snapshot3 } = await loadSnapshotModule();
1366
+ void snapshot3;
1367
1367
  focus.focused = {
1368
1368
  label: redact(describeControl(active)),
1369
1369
  role: active.getAttribute("role") || active.tagName.toLowerCase()
@@ -1406,9 +1406,9 @@ async function sendSnapshot(emit) {
1406
1406
  emit({ type: "host.focus", focus: lastFocus });
1407
1407
  return;
1408
1408
  }
1409
- const { snapshot: snapshot2 } = await loadSnapshotModule();
1409
+ const { snapshot: snapshot3 } = await loadSnapshotModule();
1410
1410
  if (!allowsAuto(effectivePageContextMode())) return;
1411
- const { yaml, truncated, visibleRefs } = snapshot2();
1411
+ const { yaml, truncated, visibleRefs } = snapshot3();
1412
1412
  emit({
1413
1413
  type: "host.snapshot",
1414
1414
  snapshot: { yaml: redact(yaml), seq: ++snapshotSeq, truncated }
@@ -1610,10 +1610,12 @@ var init_context = __esm({
1610
1610
  var react_exports = {};
1611
1611
  __export(react_exports, {
1612
1612
  MatterfactAgent: () => MatterfactAgent,
1613
- MatterfactArtifact: () => MatterfactArtifact
1613
+ MatterfactArtifact: () => MatterfactArtifact,
1614
+ MatterfactAuthProvider: () => MatterfactAuthProvider,
1615
+ MatterfactDoc: () => MatterfactDoc
1614
1616
  });
1615
1617
  module.exports = __toCommonJS(react_exports);
1616
- var import_react = require("react");
1618
+ var import_react2 = require("react");
1617
1619
 
1618
1620
  // src/protocol.ts
1619
1621
  var PROTOCOL_VERSION = 3;
@@ -2417,9 +2419,63 @@ function mount(config) {
2417
2419
  return host;
2418
2420
  }
2419
2421
 
2420
- // src/react.tsx
2422
+ // src/react-context.tsx
2423
+ var import_react = require("react");
2421
2424
  var import_jsx_runtime = require("react/jsx-runtime");
2422
2425
  var DEFAULT_ORIGIN = "https://app.matterfact.com";
2426
+ var MatterfactAuthContext = (0, import_react.createContext)(null);
2427
+ function useMatterfactConfig() {
2428
+ return (0, import_react.useContext)(MatterfactAuthContext);
2429
+ }
2430
+ function MatterfactAuthProvider({
2431
+ publishableKey,
2432
+ widgetOrigin: widgetOrigin2,
2433
+ getAuthToken,
2434
+ theme,
2435
+ onEvent,
2436
+ children
2437
+ }) {
2438
+ const resolvedWidgetOrigin = widgetOrigin2 || DEFAULT_ORIGIN;
2439
+ const value = (0, import_react.useMemo)(
2440
+ () => ({
2441
+ publishableKey,
2442
+ widgetOrigin: resolvedWidgetOrigin,
2443
+ getAuthToken,
2444
+ theme: theme || "auto",
2445
+ onEvent
2446
+ }),
2447
+ [publishableKey, resolvedWidgetOrigin, getAuthToken, theme, onEvent]
2448
+ );
2449
+ (0, import_react.useEffect)(() => {
2450
+ if (typeof window === "undefined" || !getAuthToken) return;
2451
+ let inflight = null;
2452
+ const onMessage = (e) => {
2453
+ if (e.origin !== resolvedWidgetOrigin) return;
2454
+ if (!e.data || e.data.type !== "mf.hostAuth.request") return;
2455
+ const source = e.source;
2456
+ if (!source) return;
2457
+ try {
2458
+ inflight = inflight ?? Promise.resolve(getAuthToken()).catch(() => null);
2459
+ } catch {
2460
+ return;
2461
+ }
2462
+ const p = inflight;
2463
+ void p.then((token) => {
2464
+ if (p === inflight) inflight = null;
2465
+ if (typeof token === "string" && token) {
2466
+ source.postMessage({ type: "mf.hostAuth.grant", token }, resolvedWidgetOrigin);
2467
+ }
2468
+ });
2469
+ };
2470
+ window.addEventListener("message", onMessage);
2471
+ return () => window.removeEventListener("message", onMessage);
2472
+ }, [getAuthToken, resolvedWidgetOrigin]);
2473
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MatterfactAuthContext.Provider, { value, children });
2474
+ }
2475
+
2476
+ // src/react.tsx
2477
+ var import_jsx_runtime2 = require("react/jsx-runtime");
2478
+ var DEFAULT_ORIGIN2 = "https://app.matterfact.com";
2423
2479
  function writeActionsGlobal(actions) {
2424
2480
  if (typeof window === "undefined") return;
2425
2481
  const w = window;
@@ -2463,11 +2519,11 @@ function toolDescriptorKey(artifacts, resolve, tools) {
2463
2519
  });
2464
2520
  }
2465
2521
  function MatterfactAgent({
2466
- publishableKey,
2467
- widgetOrigin: widgetOrigin2,
2522
+ publishableKey: publishableKeyProp,
2523
+ widgetOrigin: widgetOriginProp,
2468
2524
  surface = "",
2469
- theme = "auto",
2470
- getAuthToken,
2525
+ theme: themeProp,
2526
+ getAuthToken: getAuthTokenProp,
2471
2527
  getPageContext,
2472
2528
  inline = false,
2473
2529
  className: className2,
@@ -2482,30 +2538,41 @@ function MatterfactAgent({
2482
2538
  onToolEvent,
2483
2539
  onEvent
2484
2540
  }) {
2485
- const authRef = (0, import_react.useRef)(getAuthToken);
2541
+ const ctx = useMatterfactConfig();
2542
+ const publishableKey = publishableKeyProp ?? ctx?.publishableKey;
2543
+ if (!publishableKey) {
2544
+ throw new Error(
2545
+ "MatterfactAgent requires publishableKey (as a prop or via MatterfactAuthProvider)"
2546
+ );
2547
+ }
2548
+ const widgetOrigin2 = widgetOriginProp ?? ctx?.widgetOrigin ?? DEFAULT_ORIGIN2;
2549
+ const getAuthToken = getAuthTokenProp ?? ctx?.getAuthToken;
2550
+ const theme = themeProp ?? ctx?.theme ?? "auto";
2551
+ const authRef = (0, import_react2.useRef)(getAuthToken);
2486
2552
  authRef.current = getAuthToken;
2487
- const pageContextRef = (0, import_react.useRef)(getPageContext);
2553
+ const pageContextRef = (0, import_react2.useRef)(getPageContext);
2488
2554
  pageContextRef.current = getPageContext;
2489
- const actionsRef = (0, import_react.useRef)(actions);
2555
+ const actionsRef = (0, import_react2.useRef)(actions);
2490
2556
  actionsRef.current = actions;
2491
2557
  const actionsKey = JSON.stringify(actions ?? null);
2492
- const slot = (0, import_react.useRef)(null);
2558
+ const slot = (0, import_react2.useRef)(null);
2493
2559
  const sitemapKey = JSON.stringify(sitemap ?? null);
2494
- (0, import_react.useEffect)(() => {
2560
+ (0, import_react2.useEffect)(() => {
2495
2561
  writeSitemapGlobal(sitemap);
2496
2562
  }, [sitemapKey]);
2497
- (0, import_react.useEffect)(() => {
2563
+ (0, import_react2.useEffect)(() => {
2498
2564
  writeToolGlobals(artifacts, resolve, tools, onToolEvent, onEvent);
2499
2565
  });
2500
2566
  const toolsKey = toolDescriptorKey(artifacts, resolve, tools);
2501
- (0, import_react.useEffect)(() => {
2567
+ (0, import_react2.useEffect)(() => {
2502
2568
  if (typeof window === "undefined") return;
2503
2569
  if (inline && !slot.current) return;
2504
2570
  writeActionsGlobal(actionsRef.current);
2505
2571
  writeToolGlobals(artifacts, resolve, tools, onToolEvent, onEvent);
2506
2572
  const config = {
2507
2573
  publishableKey,
2508
- origin: widgetOrigin2 || DEFAULT_ORIGIN,
2574
+ // Already fully resolved above (prop ?? context ?? DEFAULT_ORIGIN).
2575
+ origin: widgetOrigin2,
2509
2576
  theme,
2510
2577
  surface,
2511
2578
  // Always present; a null return (no getAuthToken supplied) makes the core fall
@@ -2547,7 +2614,7 @@ function MatterfactAgent({
2547
2614
  // eslint-disable-next-line react-hooks/exhaustive-deps
2548
2615
  ]);
2549
2616
  if (!inline) return null;
2550
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2617
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2551
2618
  "div",
2552
2619
  {
2553
2620
  ref: slot,
@@ -2557,31 +2624,211 @@ function MatterfactAgent({
2557
2624
  );
2558
2625
  }
2559
2626
  var DEFAULT_ARTIFACT_ORIGIN = "https://app.matterfact.com";
2627
+ function snapshot2(params) {
2628
+ const out = /* @__PURE__ */ new Map();
2629
+ for (const [key, value] of Object.entries(params ?? {})) {
2630
+ out.set(key, JSON.stringify(value ?? null));
2631
+ }
2632
+ return out;
2633
+ }
2560
2634
  function MatterfactArtifact({
2635
+ name: nameProp,
2561
2636
  slug,
2562
2637
  owner,
2563
2638
  token,
2564
- widgetOrigin: widgetOrigin2,
2565
- theme = "auto",
2639
+ widgetOrigin: widgetOriginProp,
2640
+ theme: themeProp,
2566
2641
  className: className2,
2567
- style
2642
+ style,
2643
+ params,
2644
+ onParamsChange
2568
2645
  }) {
2569
- const origin = widgetOrigin2 || DEFAULT_ARTIFACT_ORIGIN;
2570
- const src = `${origin}/embed/artifacts/${encodeURIComponent(slug)}?owner=${encodeURIComponent(owner)}&t=${encodeURIComponent(token)}&theme=${theme}`;
2571
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2646
+ const ctx = useMatterfactConfig();
2647
+ const artifactName = nameProp ?? slug;
2648
+ if (!artifactName) {
2649
+ throw new Error(
2650
+ "MatterfactArtifact requires name (or its deprecated alias, slug)"
2651
+ );
2652
+ }
2653
+ const origin = widgetOriginProp ?? ctx?.widgetOrigin ?? DEFAULT_ARTIFACT_ORIGIN;
2654
+ const theme = themeProp ?? ctx?.theme ?? "auto";
2655
+ const frame = (0, import_react2.useRef)(null);
2656
+ const changeRef = (0, import_react2.useRef)(onParamsChange);
2657
+ changeRef.current = onParamsChange;
2658
+ const paramsRef = (0, import_react2.useRef)(params);
2659
+ paramsRef.current = params;
2660
+ const posted = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2661
+ const post = (0, import_react2.useCallback)(
2662
+ (values) => {
2663
+ if (Object.keys(values).length === 0) return;
2664
+ frame.current?.contentWindow?.postMessage(
2665
+ { type: "host.artifactParams", params: values },
2666
+ origin
2667
+ );
2668
+ },
2669
+ [origin]
2670
+ );
2671
+ const postAll = (0, import_react2.useCallback)(() => {
2672
+ posted.current = snapshot2(paramsRef.current);
2673
+ post({ ...paramsRef.current });
2674
+ }, [post]);
2675
+ const onLoad = (0, import_react2.useCallback)(() => postAll(), [postAll]);
2676
+ (0, import_react2.useEffect)(() => {
2677
+ if (typeof window === "undefined") return;
2678
+ const onMessage = (event) => {
2679
+ if (!frame.current || event.source !== frame.current.contentWindow) return;
2680
+ const data = event.data;
2681
+ if (!data || typeof data !== "object") return;
2682
+ if (data.type === "widget.artifactReady") {
2683
+ postAll();
2684
+ return;
2685
+ }
2686
+ if (data.type === "widget.artifactParams" && data.params) {
2687
+ const source = data.source === "context" ? "context" : "user";
2688
+ try {
2689
+ changeRef.current?.(data.params, source);
2690
+ } catch {
2691
+ }
2692
+ }
2693
+ };
2694
+ window.addEventListener("message", onMessage);
2695
+ return () => window.removeEventListener("message", onMessage);
2696
+ }, [postAll]);
2697
+ const paramsKey = JSON.stringify(params ?? null);
2698
+ const settled = (0, import_react2.useRef)(false);
2699
+ (0, import_react2.useEffect)(() => {
2700
+ const next = snapshot2(paramsRef.current);
2701
+ if (!settled.current) {
2702
+ settled.current = true;
2703
+ posted.current = next;
2704
+ return;
2705
+ }
2706
+ const changed = {};
2707
+ for (const [key, json] of next) {
2708
+ if (posted.current.get(key) !== json) {
2709
+ changed[key] = paramsRef.current[key];
2710
+ }
2711
+ }
2712
+ posted.current = next;
2713
+ post(changed);
2714
+ }, [paramsKey, post]);
2715
+ let src;
2716
+ if (token) {
2717
+ src = `${origin}/embed/artifacts/${encodeURIComponent(artifactName)}?owner=${encodeURIComponent(owner)}&t=${encodeURIComponent(token)}&theme=${theme}`;
2718
+ } else if (ctx) {
2719
+ const hostOrigin = typeof window === "undefined" ? "" : window.location.origin;
2720
+ src = `${origin}/embed/artifacts/${encodeURIComponent(artifactName)}?owner=${encodeURIComponent(owner)}&k=${encodeURIComponent(ctx.publishableKey)}&o=${encodeURIComponent(hostOrigin)}&theme=${theme}`;
2721
+ } else {
2722
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2723
+ "div",
2724
+ {
2725
+ className: className2,
2726
+ style: {
2727
+ width: "100%",
2728
+ height: 600,
2729
+ display: "flex",
2730
+ alignItems: "center",
2731
+ justifyContent: "center",
2732
+ textAlign: "center",
2733
+ ...style
2734
+ },
2735
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "This embedded artifact link is incomplete." })
2736
+ }
2737
+ );
2738
+ }
2739
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2572
2740
  "iframe",
2573
2741
  {
2742
+ ref: frame,
2574
2743
  src,
2575
- title: `matterfact artifact ${slug}`,
2744
+ onLoad,
2745
+ title: `matterfact artifact ${artifactName}`,
2576
2746
  className: className2,
2577
2747
  style: { width: "100%", height: 600, border: 0, ...style },
2578
2748
  sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"
2579
2749
  }
2580
2750
  );
2581
2751
  }
2752
+ var DEFAULT_DOC_ORIGIN = "https://app.matterfact.com";
2753
+ function diffDocParams(params, sent) {
2754
+ const changed = {};
2755
+ for (const [key, value] of Object.entries(params)) {
2756
+ if (value === void 0) continue;
2757
+ const snapshot3 = JSON.stringify(value);
2758
+ if (sent.get(key) === snapshot3) continue;
2759
+ changed[key] = value;
2760
+ sent.set(key, snapshot3);
2761
+ }
2762
+ return changed;
2763
+ }
2764
+ function MatterfactDoc({
2765
+ docKey,
2766
+ ticker = "",
2767
+ version = "latest",
2768
+ theme: themeProp,
2769
+ params,
2770
+ className: className2,
2771
+ style
2772
+ }) {
2773
+ const ctx = useMatterfactConfig();
2774
+ const iframeRef = (0, import_react2.useRef)(null);
2775
+ const sentParamsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2776
+ const origin = ctx?.widgetOrigin ?? DEFAULT_DOC_ORIGIN;
2777
+ const paramsKey = JSON.stringify(params ?? null);
2778
+ const pushParams = (force) => {
2779
+ if (!params) return;
2780
+ const win = iframeRef.current?.contentWindow;
2781
+ if (!win) return;
2782
+ if (force) sentParamsRef.current.clear();
2783
+ const changed = diffDocParams(params, sentParamsRef.current);
2784
+ if (Object.keys(changed).length === 0) return;
2785
+ win.postMessage({ type: "host.docParams", params: changed }, origin);
2786
+ };
2787
+ (0, import_react2.useEffect)(() => {
2788
+ pushParams(false);
2789
+ }, [paramsKey]);
2790
+ if (!docKey) {
2791
+ throw new Error("MatterfactDoc requires docKey");
2792
+ }
2793
+ if (!ctx || !ctx.publishableKey) {
2794
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2795
+ "div",
2796
+ {
2797
+ className: className2,
2798
+ style: {
2799
+ width: "100%",
2800
+ height: 600,
2801
+ display: "flex",
2802
+ alignItems: "center",
2803
+ justifyContent: "center",
2804
+ textAlign: "center",
2805
+ ...style
2806
+ },
2807
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "This embedded document link is incomplete." })
2808
+ }
2809
+ );
2810
+ }
2811
+ const theme = themeProp ?? ctx.theme ?? "auto";
2812
+ const hostOrigin = typeof window === "undefined" ? "" : window.location.origin;
2813
+ const src = `${origin}/embed/document?k=${encodeURIComponent(ctx.publishableKey)}&o=${encodeURIComponent(hostOrigin)}&key=${encodeURIComponent(docKey)}&ticker=${encodeURIComponent(ticker)}&version=${encodeURIComponent(version)}&theme=${theme}`;
2814
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2815
+ "iframe",
2816
+ {
2817
+ ref: iframeRef,
2818
+ src,
2819
+ title: `matterfact document ${docKey}`,
2820
+ className: className2,
2821
+ style: { width: "100%", height: 600, border: 0, ...style },
2822
+ sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads",
2823
+ onLoad: () => pushParams(true)
2824
+ }
2825
+ );
2826
+ }
2582
2827
  // Annotate the CommonJS export names for ESM import in node:
2583
2828
  0 && (module.exports = {
2584
2829
  MatterfactAgent,
2585
- MatterfactArtifact
2830
+ MatterfactArtifact,
2831
+ MatterfactAuthProvider,
2832
+ MatterfactDoc
2586
2833
  });
2587
2834
  //# sourceMappingURL=react.cjs.map