@matterfact/embed 0.16.0 → 0.18.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/index.cjs CHANGED
@@ -12,6 +12,190 @@ var __export = (target, all) => {
12
12
  __defProp(target, name, { get: all[name], enumerable: true });
13
13
  };
14
14
 
15
+ // src/deeplink.ts
16
+ var deeplink_exports = {};
17
+ __export(deeplink_exports, {
18
+ compileDeeplink: () => compileDeeplink,
19
+ compileDeeplinkFormat: () => compileDeeplinkFormat,
20
+ installDeeplinkWatch: () => installDeeplinkWatch
21
+ });
22
+ function compileDeeplinkFormat(format) {
23
+ return compileDeeplink(format)?.match ?? null;
24
+ }
25
+ function compileDeeplink(format) {
26
+ if (format.includes(PATH_TOKEN)) return compileDynamic(format);
27
+ const match = compileStatic(format);
28
+ return match ? { match, mintFormat: () => format, dynamic: false } : null;
29
+ }
30
+ function compileDynamic(format) {
31
+ const pi = format.indexOf(PATH_TOKEN);
32
+ if (format.indexOf(PATH_TOKEN, pi + 1) >= 0) return null;
33
+ let prefix = format.slice(0, pi);
34
+ if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
35
+ const suffix = format.slice(pi + PATH_TOKEN.length);
36
+ let po;
37
+ try {
38
+ po = new URL(prefix);
39
+ } catch {
40
+ return null;
41
+ }
42
+ if (po.protocol !== "https:" && po.protocol !== "http:") return null;
43
+ if (po.origin !== prefix) return null;
44
+ if (!suffix.startsWith("?")) return null;
45
+ const c1 = suffix.indexOf(PLACEHOLDER);
46
+ if (c1 < 0 || suffix.indexOf(PLACEHOLDER, c1 + 1) >= 0) return null;
47
+ let probe;
48
+ try {
49
+ probe = new URL(prefix + "/__mfp__" + suffix.replace(PLACEHOLDER, SENTINEL));
50
+ } catch {
51
+ return null;
52
+ }
53
+ const keys = [...probe.searchParams.keys()];
54
+ if (new Set(keys).size !== keys.length) return null;
55
+ let queryKey = null;
56
+ for (const [k, v] of probe.searchParams) {
57
+ if (v === SENTINEL) queryKey = k;
58
+ }
59
+ if (!queryKey) return null;
60
+ const match = (href) => {
61
+ let u;
62
+ try {
63
+ u = new URL(href);
64
+ } catch {
65
+ return null;
66
+ }
67
+ if (u.origin !== prefix) return null;
68
+ for (const [k, v] of probe.searchParams) {
69
+ if (v === SENTINEL) continue;
70
+ if (u.searchParams.get(k) !== v) return null;
71
+ }
72
+ const token = u.searchParams.get(queryKey);
73
+ return token && TOKEN_RE.test(token) ? token : null;
74
+ };
75
+ return {
76
+ match,
77
+ mintFormat: (pathname) => prefix + pathname + suffix,
78
+ dynamic: true
79
+ };
80
+ }
81
+ function compileStatic(format) {
82
+ const first = format.indexOf(PLACEHOLDER);
83
+ if (first < 0 || format.indexOf(PLACEHOLDER, first + 1) >= 0) return null;
84
+ let f;
85
+ try {
86
+ f = new URL(format.replace(PLACEHOLDER, SENTINEL));
87
+ } catch {
88
+ return null;
89
+ }
90
+ if (f.protocol !== "https:" && f.protocol !== "http:") return null;
91
+ const keys = [...f.searchParams.keys()];
92
+ if (new Set(keys).size !== keys.length) return null;
93
+ const fPath = trimSlash(f.pathname);
94
+ const segs = fPath.split("/");
95
+ const segIdx = segs.indexOf(SENTINEL);
96
+ let queryKey = null;
97
+ for (const [k, v] of f.searchParams) {
98
+ if (v === SENTINEL) queryKey = k;
99
+ }
100
+ if (segIdx >= 0 === (queryKey !== null)) return null;
101
+ return (href) => {
102
+ let u;
103
+ try {
104
+ u = new URL(href);
105
+ } catch {
106
+ return null;
107
+ }
108
+ if (u.origin !== f.origin) return null;
109
+ const uPath = trimSlash(u.pathname);
110
+ let token;
111
+ if (segIdx >= 0) {
112
+ const us = uPath.split("/");
113
+ if (us.length !== segs.length) return null;
114
+ for (let i = 0; i < segs.length; i++) {
115
+ if (i !== segIdx && us[i] !== segs[i]) return null;
116
+ }
117
+ token = us[segIdx];
118
+ for (const [k, v] of f.searchParams) {
119
+ if (u.searchParams.get(k) !== v) return null;
120
+ }
121
+ } else {
122
+ if (uPath !== fPath) return null;
123
+ for (const [k, v] of f.searchParams) {
124
+ if (v === SENTINEL) continue;
125
+ if (u.searchParams.get(k) !== v) return null;
126
+ }
127
+ token = u.searchParams.get(queryKey);
128
+ }
129
+ return token && TOKEN_RE.test(token) ? token : null;
130
+ };
131
+ }
132
+ function installDeeplinkWatch(format, onChange2, onFormat) {
133
+ const c = compileDeeplink(format);
134
+ if (!c) return () => {
135
+ };
136
+ let last = null;
137
+ let lastTokenPath = null;
138
+ let lastMintPath = null;
139
+ let stopped = false;
140
+ const emitFormat = () => {
141
+ if (!onFormat) return;
142
+ const p = location.pathname;
143
+ if (lastMintPath === null || c.dynamic && p !== lastMintPath) {
144
+ lastMintPath = p;
145
+ onFormat(c.mintFormat(p));
146
+ }
147
+ };
148
+ const fire = () => {
149
+ if (stopped) return;
150
+ try {
151
+ emitFormat();
152
+ const token = c.match(location.href);
153
+ if (token === null && last !== null && trimSlash(location.pathname) === lastTokenPath) {
154
+ return;
155
+ }
156
+ if (token !== last) {
157
+ last = token;
158
+ if (token !== null) lastTokenPath = trimSlash(location.pathname);
159
+ onChange2(token);
160
+ }
161
+ } catch (err) {
162
+ console.warn("[mf-embed] deeplink match failed", err);
163
+ }
164
+ };
165
+ const origPush = history.pushState;
166
+ const origReplace = history.replaceState;
167
+ const ourPush = function(...args) {
168
+ const r = origPush.apply(this, args);
169
+ fire();
170
+ return r;
171
+ };
172
+ const ourReplace = function(...args) {
173
+ const r = origReplace.apply(this, args);
174
+ fire();
175
+ return r;
176
+ };
177
+ history.pushState = ourPush;
178
+ history.replaceState = ourReplace;
179
+ window.addEventListener("popstate", fire);
180
+ fire();
181
+ return () => {
182
+ stopped = true;
183
+ window.removeEventListener("popstate", fire);
184
+ if (history.pushState === ourPush) history.pushState = origPush;
185
+ if (history.replaceState === ourReplace) history.replaceState = origReplace;
186
+ };
187
+ }
188
+ var PLACEHOLDER, PATH_TOKEN, SENTINEL, TOKEN_RE, trimSlash;
189
+ var init_deeplink = __esm({
190
+ "src/deeplink.ts"() {
191
+ PLACEHOLDER = "{share_token}";
192
+ PATH_TOKEN = "{path}";
193
+ SENTINEL = "mf-share-token-sentinel";
194
+ TOKEN_RE = /^[A-Za-z0-9._~-]{4,256}$/;
195
+ trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
196
+ }
197
+ });
198
+
15
199
  // src/pageContextMode.ts
16
200
  function parsePageContextMode(value) {
17
201
  if (typeof value === "boolean") return value ? "full" : "off";
@@ -1583,7 +1767,7 @@ var init_context = __esm({
1583
1767
  });
1584
1768
 
1585
1769
  // src/protocol.ts
1586
- var PROTOCOL_VERSION = 3;
1770
+ var PROTOCOL_VERSION = 4;
1587
1771
  var CHANNEL = "mf-embed";
1588
1772
  function envelope(payload, id) {
1589
1773
  return {
@@ -1806,6 +1990,7 @@ function dockBox(g, vw, _vh) {
1806
1990
 
1807
1991
  // src/loader.ts
1808
1992
  var DEFAULT_ORIGIN = "https://app.matterfact.com";
1993
+ var EMBED_VERSION = "0.18.0";
1809
1994
  function devRequested() {
1810
1995
  try {
1811
1996
  if (new URLSearchParams(location.search).get("mfdev") === "1") return true;
@@ -1850,7 +2035,10 @@ function readConfig() {
1850
2035
  theme: el?.dataset.theme || "auto",
1851
2036
  surface: el?.dataset.surface || "",
1852
2037
  container,
1853
- pageContext
2038
+ pageContext,
2039
+ // `data-share-deeplink-format="https://…/{share_token}"` — the script-tag twin of
2040
+ // the React `shareDeeplinkFormat` prop.
2041
+ shareDeeplinkFormat: el?.dataset.shareDeeplinkFormat
1854
2042
  // No `data-dev` — see the `dev` field's doc comment: the URL trigger is the
1855
2043
  // point for the script-tag path, so there is deliberately no script-tag knob here.
1856
2044
  // No `data-actions` either: the action policy lives entirely in the lazy chunk,
@@ -1931,6 +2119,8 @@ var EmbedHost = class {
1931
2119
  this.inline = !!config.container;
1932
2120
  }
1933
2121
  mount() {
2122
+ const w = window;
2123
+ (w.matterfact ?? (w.matterfact = {})).embedVersion = EMBED_VERSION;
1934
2124
  const host = document.createElement("div");
1935
2125
  this.hostEl = host;
1936
2126
  host.id = "matterfact-embed";
@@ -1974,7 +2164,7 @@ var EmbedHost = class {
1974
2164
  "sandbox",
1975
2165
  "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"
1976
2166
  );
1977
- iframe.setAttribute("allow", "microphone; clipboard-write");
2167
+ iframe.setAttribute("allow", "microphone; clipboard-write; web-share");
1978
2168
  iframe.src = `${this.config.origin}/embed/chat?k=${encodeURIComponent(
1979
2169
  this.config.publishableKey
1980
2170
  )}&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
@@ -1987,6 +2177,18 @@ var EmbedHost = class {
1987
2177
  window.addEventListener("resize", this.onViewportResize);
1988
2178
  this.place();
1989
2179
  }
2180
+ const fmt = this.config.shareDeeplinkFormat;
2181
+ if (fmt) {
2182
+ void Promise.resolve().then(() => (init_deeplink(), deeplink_exports)).then((m) => {
2183
+ if (this.destroyed) return;
2184
+ this.deeplinkStop = m.installDeeplinkWatch(
2185
+ fmt,
2186
+ (token) => this.send({ type: "host.openShare", token }),
2187
+ (format) => this.send({ type: "host.deeplinkFormat", format })
2188
+ );
2189
+ }).catch(() => {
2190
+ });
2191
+ }
1990
2192
  }
1991
2193
  /**
1992
2194
  * Test seam. The shadow root is CLOSED, so a test cannot reach the iframe to forge a
@@ -2409,6 +2611,9 @@ var EmbedHost = class {
2409
2611
  this.iframe = null;
2410
2612
  this.shadow = null;
2411
2613
  this.ready = false;
2614
+ this.destroyed = true;
2615
+ this.deeplinkStop?.();
2616
+ this.deeplinkStop = null;
2412
2617
  void this.context?.then((m) => m.stop());
2413
2618
  }
2414
2619
  };