@matterfact/embed 0.15.0 → 0.17.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/dist/react.cjs CHANGED
@@ -22,6 +22,115 @@ var __copyProps = (to, from, except, desc) => {
22
22
  };
23
23
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
24
 
25
+ // src/deeplink.ts
26
+ var deeplink_exports = {};
27
+ __export(deeplink_exports, {
28
+ compileDeeplinkFormat: () => compileDeeplinkFormat,
29
+ installDeeplinkWatch: () => installDeeplinkWatch
30
+ });
31
+ function compileDeeplinkFormat(format) {
32
+ const first = format.indexOf(PLACEHOLDER);
33
+ if (first < 0 || format.indexOf(PLACEHOLDER, first + 1) >= 0) return null;
34
+ let f;
35
+ try {
36
+ f = new URL(format.replace(PLACEHOLDER, SENTINEL));
37
+ } catch {
38
+ return null;
39
+ }
40
+ if (f.protocol !== "https:" && f.protocol !== "http:") return null;
41
+ const keys = [...f.searchParams.keys()];
42
+ if (new Set(keys).size !== keys.length) return null;
43
+ const fPath = trimSlash(f.pathname);
44
+ const segs = fPath.split("/");
45
+ const segIdx = segs.indexOf(SENTINEL);
46
+ let queryKey = null;
47
+ for (const [k, v] of f.searchParams) {
48
+ if (v === SENTINEL) queryKey = k;
49
+ }
50
+ if (segIdx >= 0 === (queryKey !== null)) return null;
51
+ return (href) => {
52
+ let u;
53
+ try {
54
+ u = new URL(href);
55
+ } catch {
56
+ return null;
57
+ }
58
+ if (u.origin !== f.origin) return null;
59
+ const uPath = trimSlash(u.pathname);
60
+ let token;
61
+ if (segIdx >= 0) {
62
+ const us = uPath.split("/");
63
+ if (us.length !== segs.length) return null;
64
+ for (let i = 0; i < segs.length; i++) {
65
+ if (i !== segIdx && us[i] !== segs[i]) return null;
66
+ }
67
+ token = us[segIdx];
68
+ for (const [k, v] of f.searchParams) {
69
+ if (u.searchParams.get(k) !== v) return null;
70
+ }
71
+ } else {
72
+ if (uPath !== fPath) return null;
73
+ for (const [k, v] of f.searchParams) {
74
+ if (v === SENTINEL) continue;
75
+ if (u.searchParams.get(k) !== v) return null;
76
+ }
77
+ token = u.searchParams.get(queryKey);
78
+ }
79
+ return token && TOKEN_RE.test(token) ? token : null;
80
+ };
81
+ }
82
+ function installDeeplinkWatch(format, onChange2) {
83
+ const match = compileDeeplinkFormat(format);
84
+ if (!match) return () => {
85
+ };
86
+ let last = null;
87
+ let stopped = false;
88
+ const fire = () => {
89
+ if (stopped) return;
90
+ try {
91
+ const token = match(location.href);
92
+ if (token !== last) {
93
+ last = token;
94
+ onChange2(token);
95
+ }
96
+ } catch (err) {
97
+ console.warn("[mf-embed] deeplink match failed", err);
98
+ }
99
+ };
100
+ const origPush = history.pushState;
101
+ const origReplace = history.replaceState;
102
+ const ourPush = function(...args) {
103
+ const r = origPush.apply(this, args);
104
+ fire();
105
+ return r;
106
+ };
107
+ const ourReplace = function(...args) {
108
+ const r = origReplace.apply(this, args);
109
+ fire();
110
+ return r;
111
+ };
112
+ history.pushState = ourPush;
113
+ history.replaceState = ourReplace;
114
+ window.addEventListener("popstate", fire);
115
+ fire();
116
+ return () => {
117
+ stopped = true;
118
+ window.removeEventListener("popstate", fire);
119
+ if (history.pushState === ourPush) history.pushState = origPush;
120
+ if (history.replaceState === ourReplace) history.replaceState = origReplace;
121
+ };
122
+ }
123
+ var PLACEHOLDER, SENTINEL, TOKEN_RE, trimSlash;
124
+ var init_deeplink = __esm({
125
+ "src/deeplink.ts"() {
126
+ "use strict";
127
+ PLACEHOLDER = "{share_token}";
128
+ SENTINEL = "mf-share-token-sentinel";
129
+ TOKEN_RE = /^[A-Za-z0-9._~-]{4,256}$/;
130
+ trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
131
+ }
132
+ });
133
+
25
134
  // src/pageContextMode.ts
26
135
  function parsePageContextMode(value) {
27
136
  if (typeof value === "boolean") return value ? "full" : "off";
@@ -1362,8 +1471,8 @@ async function readFocus() {
1362
1471
  }
1363
1472
  const active = document.activeElement;
1364
1473
  if (active && active !== document.body && !isPrivate(active) && !active.closest("[data-mf-private]")) {
1365
- const { snapshot: snapshot2 } = await loadSnapshotModule();
1366
- void snapshot2;
1474
+ const { snapshot: snapshot3 } = await loadSnapshotModule();
1475
+ void snapshot3;
1367
1476
  focus.focused = {
1368
1477
  label: redact(describeControl(active)),
1369
1478
  role: active.getAttribute("role") || active.tagName.toLowerCase()
@@ -1406,9 +1515,9 @@ async function sendSnapshot(emit) {
1406
1515
  emit({ type: "host.focus", focus: lastFocus });
1407
1516
  return;
1408
1517
  }
1409
- const { snapshot: snapshot2 } = await loadSnapshotModule();
1518
+ const { snapshot: snapshot3 } = await loadSnapshotModule();
1410
1519
  if (!allowsAuto(effectivePageContextMode())) return;
1411
- const { yaml, truncated, visibleRefs } = snapshot2();
1520
+ const { yaml, truncated, visibleRefs } = snapshot3();
1412
1521
  emit({
1413
1522
  type: "host.snapshot",
1414
1523
  snapshot: { yaml: redact(yaml), seq: ++snapshotSeq, truncated }
@@ -1618,7 +1727,7 @@ module.exports = __toCommonJS(react_exports);
1618
1727
  var import_react2 = require("react");
1619
1728
 
1620
1729
  // src/protocol.ts
1621
- var PROTOCOL_VERSION = 3;
1730
+ var PROTOCOL_VERSION = 4;
1622
1731
  var CHANNEL = "mf-embed";
1623
1732
  function envelope(payload, id) {
1624
1733
  return {
@@ -1840,6 +1949,7 @@ function dockBox(g, vw, _vh) {
1840
1949
  }
1841
1950
 
1842
1951
  // src/loader.ts
1952
+ var EMBED_VERSION = "0.17.0";
1843
1953
  function devRequested() {
1844
1954
  try {
1845
1955
  if (new URLSearchParams(location.search).get("mfdev") === "1") return true;
@@ -1927,6 +2037,8 @@ var EmbedHost = class {
1927
2037
  this.inline = !!config.container;
1928
2038
  }
1929
2039
  mount() {
2040
+ const w = window;
2041
+ (w.matterfact ?? (w.matterfact = {})).embedVersion = EMBED_VERSION;
1930
2042
  const host = document.createElement("div");
1931
2043
  this.hostEl = host;
1932
2044
  host.id = "matterfact-embed";
@@ -1970,7 +2082,7 @@ var EmbedHost = class {
1970
2082
  "sandbox",
1971
2083
  "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"
1972
2084
  );
1973
- iframe.setAttribute("allow", "microphone; clipboard-write");
2085
+ iframe.setAttribute("allow", "microphone; clipboard-write; web-share");
1974
2086
  iframe.src = `${this.config.origin}/embed/chat?k=${encodeURIComponent(
1975
2087
  this.config.publishableKey
1976
2088
  )}&o=${encodeURIComponent(location.origin)}` + (this.config.surface ? `&s=${encodeURIComponent(this.config.surface)}` : "") + // Either trigger works: the URL param (no redeploy needed) OR the config's
@@ -1983,6 +2095,17 @@ var EmbedHost = class {
1983
2095
  window.addEventListener("resize", this.onViewportResize);
1984
2096
  this.place();
1985
2097
  }
2098
+ const fmt = this.config.shareDeeplinkFormat;
2099
+ if (fmt) {
2100
+ this.send({ type: "host.deeplinkFormat", format: fmt });
2101
+ void Promise.resolve().then(() => (init_deeplink(), deeplink_exports)).then((m) => {
2102
+ if (this.destroyed) return;
2103
+ this.deeplinkStop = m.installDeeplinkWatch(fmt, (token) => {
2104
+ this.send({ type: "host.openShare", token });
2105
+ });
2106
+ }).catch(() => {
2107
+ });
2108
+ }
1986
2109
  }
1987
2110
  /**
1988
2111
  * Test seam. The shadow root is CLOSED, so a test cannot reach the iframe to forge a
@@ -2406,6 +2529,9 @@ var EmbedHost = class {
2406
2529
  this.iframe = null;
2407
2530
  this.shadow = null;
2408
2531
  this.ready = false;
2532
+ this.destroyed = true;
2533
+ this.deeplinkStop?.();
2534
+ this.deeplinkStop = null;
2409
2535
  void this.context?.then((m) => m.stop());
2410
2536
  }
2411
2537
  };
@@ -2522,6 +2648,7 @@ function MatterfactAgent({
2522
2648
  publishableKey: publishableKeyProp,
2523
2649
  widgetOrigin: widgetOriginProp,
2524
2650
  surface = "",
2651
+ shareDeeplinkFormat,
2525
2652
  theme: themeProp,
2526
2653
  getAuthToken: getAuthTokenProp,
2527
2654
  getPageContext,
@@ -2583,7 +2710,8 @@ function MatterfactAgent({
2583
2710
  pageContextProvider: async () => await pageContextRef.current?.() ?? null,
2584
2711
  container: inline ? slot.current : null,
2585
2712
  pageContext,
2586
- dev
2713
+ dev,
2714
+ shareDeeplinkFormat
2587
2715
  };
2588
2716
  let host = null;
2589
2717
  try {
@@ -2603,6 +2731,7 @@ function MatterfactAgent({
2603
2731
  publishableKey,
2604
2732
  widgetOrigin2,
2605
2733
  surface,
2734
+ shareDeeplinkFormat,
2606
2735
  theme,
2607
2736
  inline,
2608
2737
  pageContext,
@@ -2624,6 +2753,13 @@ function MatterfactAgent({
2624
2753
  );
2625
2754
  }
2626
2755
  var DEFAULT_ARTIFACT_ORIGIN = "https://app.matterfact.com";
2756
+ function snapshot2(params) {
2757
+ const out = /* @__PURE__ */ new Map();
2758
+ for (const [key, value] of Object.entries(params ?? {})) {
2759
+ out.set(key, JSON.stringify(value ?? null));
2760
+ }
2761
+ return out;
2762
+ }
2627
2763
  function MatterfactArtifact({
2628
2764
  name: nameProp,
2629
2765
  slug,
@@ -2632,7 +2768,9 @@ function MatterfactArtifact({
2632
2768
  widgetOrigin: widgetOriginProp,
2633
2769
  theme: themeProp,
2634
2770
  className: className2,
2635
- style
2771
+ style,
2772
+ params,
2773
+ onParamsChange
2636
2774
  }) {
2637
2775
  const ctx = useMatterfactConfig();
2638
2776
  const artifactName = nameProp ?? slug;
@@ -2643,12 +2781,72 @@ function MatterfactArtifact({
2643
2781
  }
2644
2782
  const origin = widgetOriginProp ?? ctx?.widgetOrigin ?? DEFAULT_ARTIFACT_ORIGIN;
2645
2783
  const theme = themeProp ?? ctx?.theme ?? "auto";
2784
+ const frame = (0, import_react2.useRef)(null);
2785
+ const changeRef = (0, import_react2.useRef)(onParamsChange);
2786
+ changeRef.current = onParamsChange;
2787
+ const paramsRef = (0, import_react2.useRef)(params);
2788
+ paramsRef.current = params;
2789
+ const posted = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2790
+ const post = (0, import_react2.useCallback)(
2791
+ (values) => {
2792
+ if (Object.keys(values).length === 0) return;
2793
+ frame.current?.contentWindow?.postMessage(
2794
+ { type: "host.artifactParams", params: values },
2795
+ origin
2796
+ );
2797
+ },
2798
+ [origin]
2799
+ );
2800
+ const postAll = (0, import_react2.useCallback)(() => {
2801
+ posted.current = snapshot2(paramsRef.current);
2802
+ post({ ...paramsRef.current });
2803
+ }, [post]);
2804
+ const onLoad = (0, import_react2.useCallback)(() => postAll(), [postAll]);
2805
+ (0, import_react2.useEffect)(() => {
2806
+ if (typeof window === "undefined") return;
2807
+ const onMessage = (event) => {
2808
+ if (!frame.current || event.source !== frame.current.contentWindow) return;
2809
+ const data = event.data;
2810
+ if (!data || typeof data !== "object") return;
2811
+ if (data.type === "widget.artifactReady") {
2812
+ postAll();
2813
+ return;
2814
+ }
2815
+ if (data.type === "widget.artifactParams" && data.params) {
2816
+ const source = data.source === "context" ? "context" : "user";
2817
+ try {
2818
+ changeRef.current?.(data.params, source);
2819
+ } catch {
2820
+ }
2821
+ }
2822
+ };
2823
+ window.addEventListener("message", onMessage);
2824
+ return () => window.removeEventListener("message", onMessage);
2825
+ }, [postAll]);
2826
+ const paramsKey = JSON.stringify(params ?? null);
2827
+ const settled = (0, import_react2.useRef)(false);
2828
+ (0, import_react2.useEffect)(() => {
2829
+ const next = snapshot2(paramsRef.current);
2830
+ if (!settled.current) {
2831
+ settled.current = true;
2832
+ posted.current = next;
2833
+ return;
2834
+ }
2835
+ const changed = {};
2836
+ for (const [key, json] of next) {
2837
+ if (posted.current.get(key) !== json) {
2838
+ changed[key] = paramsRef.current[key];
2839
+ }
2840
+ }
2841
+ posted.current = next;
2842
+ post(changed);
2843
+ }, [paramsKey, post]);
2646
2844
  let src;
2647
- if (token) {
2648
- src = `${origin}/embed/artifacts/${encodeURIComponent(artifactName)}?owner=${encodeURIComponent(owner)}&t=${encodeURIComponent(token)}&theme=${theme}`;
2649
- } else if (ctx) {
2845
+ if (ctx) {
2650
2846
  const hostOrigin = typeof window === "undefined" ? "" : window.location.origin;
2651
2847
  src = `${origin}/embed/artifacts/${encodeURIComponent(artifactName)}?owner=${encodeURIComponent(owner)}&k=${encodeURIComponent(ctx.publishableKey)}&o=${encodeURIComponent(hostOrigin)}&theme=${theme}`;
2848
+ } else if (token) {
2849
+ src = `${origin}/embed/artifacts/${encodeURIComponent(artifactName)}?owner=${encodeURIComponent(owner)}&t=${encodeURIComponent(token)}&theme=${theme}`;
2652
2850
  } else {
2653
2851
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2654
2852
  "div",
@@ -2670,7 +2868,9 @@ function MatterfactArtifact({
2670
2868
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2671
2869
  "iframe",
2672
2870
  {
2871
+ ref: frame,
2673
2872
  src,
2873
+ onLoad,
2674
2874
  title: `matterfact artifact ${artifactName}`,
2675
2875
  className: className2,
2676
2876
  style: { width: "100%", height: 600, border: 0, ...style },
@@ -2683,10 +2883,10 @@ function diffDocParams(params, sent) {
2683
2883
  const changed = {};
2684
2884
  for (const [key, value] of Object.entries(params)) {
2685
2885
  if (value === void 0) continue;
2686
- const snapshot2 = JSON.stringify(value);
2687
- if (sent.get(key) === snapshot2) continue;
2886
+ const snapshot3 = JSON.stringify(value);
2887
+ if (sent.get(key) === snapshot3) continue;
2688
2888
  changed[key] = value;
2689
- sent.set(key, snapshot2);
2889
+ sent.set(key, snapshot3);
2690
2890
  }
2691
2891
  return changed;
2692
2892
  }