@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.d.cts CHANGED
@@ -18,7 +18,7 @@
18
18
  * shapes are right, "deeper integration" is a transport swap (postMessage ->
19
19
  * direct call), not a rewrite. So model the DOMAIN here, not the plumbing.
20
20
  */
21
- declare const PROTOCOL_VERSION = 3;
21
+ declare const PROTOCOL_VERSION = 4;
22
22
  /** Every message is namespaced so we never collide with the host page's own postMessage traffic. */
23
23
  declare const CHANNEL = "mf-embed";
24
24
  /**
@@ -346,6 +346,26 @@ type HostToWidget = {
346
346
  type: 'host.theme';
347
347
  mode: 'light' | 'dark';
348
348
  }
349
+ /**
350
+ * The share-link template the integration configured on the loader
351
+ * (`shareDeeplinkFormat`), e.g. `https://portal.acme.com/share/{share_token}`. Sent
352
+ * at mount; the widget mints "Share chat" links in this format. Absent config ⇒
353
+ * never sent, and the widget falls back to its own share route.
354
+ */
355
+ | {
356
+ type: 'host.deeplinkFormat';
357
+ format: string;
358
+ }
359
+ /**
360
+ * The host page's URL matches that same template, and this is the share token it
361
+ * carries — or `null` the moment the user navigates off it. Only the loader can see
362
+ * the host URL, so only the loader can report this; the widget opens (or closes) the
363
+ * shared thread on the back of it.
364
+ */
365
+ | {
366
+ type: 'host.openShare';
367
+ token: string | null;
368
+ }
349
369
  /**
350
370
  * Where the widget currently IS. Sent on mount and after anything that moves it
351
371
  * (drag, snap, mode change, viewport resize).
@@ -669,6 +689,17 @@ interface LoaderConfig {
669
689
  * the URL param still works untouched; `mount()` ORs the two together.
670
690
  */
671
691
  dev?: boolean;
692
+ /**
693
+ * Host-site share-link template. The widget mints "Share chat" links in this format,
694
+ * and this loader watches the host URL for a match so a visitor landing on one opens
695
+ * that conversation. Exactly one `{share_token}`, as a whole path segment or whole
696
+ * query value, e.g. `https://portal.acme.com/assistant?mf_share={share_token}`.
697
+ * `{path}` directly after the origin makes it dynamic — links carry the page the
698
+ * sharer is on and pickup fires on any page (token must be a query value; the
699
+ * current pathname then reaches the widget). Absent ⇒ no watch is installed and
700
+ * the widget keeps its own share route.
701
+ */
702
+ shareDeeplinkFormat?: string;
672
703
  }
673
704
  declare function readConfig(): LoaderConfig | null;
674
705
  declare class EmbedHost {
@@ -706,6 +737,12 @@ declare class EmbedHost {
706
737
  private dragLeftBand;
707
738
  /** Loaded on first open. Holds everything that touches the customer's DOM. */
708
739
  private context;
740
+ /** Uninstall for the share-deeplink URL watch. No initializer on purpose: an `= null`
741
+ * emits a constructor assignment into the size-budgeted stub for no behaviour. */
742
+ private deeplinkStop?;
743
+ /** Set once `destroy()` has run; falsy (never assigned) otherwise — no initializer,
744
+ * same reasoning as `deeplinkStop` above. */
745
+ private destroyed?;
709
746
  /** The host element; kept so `destroy()` can remove it (React lifecycle). */
710
747
  private hostEl;
711
748
  /** Rendering into the host's own element: they own the box, the chrome and visibility. */
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  * shapes are right, "deeper integration" is a transport swap (postMessage ->
19
19
  * direct call), not a rewrite. So model the DOMAIN here, not the plumbing.
20
20
  */
21
- declare const PROTOCOL_VERSION = 3;
21
+ declare const PROTOCOL_VERSION = 4;
22
22
  /** Every message is namespaced so we never collide with the host page's own postMessage traffic. */
23
23
  declare const CHANNEL = "mf-embed";
24
24
  /**
@@ -346,6 +346,26 @@ type HostToWidget = {
346
346
  type: 'host.theme';
347
347
  mode: 'light' | 'dark';
348
348
  }
349
+ /**
350
+ * The share-link template the integration configured on the loader
351
+ * (`shareDeeplinkFormat`), e.g. `https://portal.acme.com/share/{share_token}`. Sent
352
+ * at mount; the widget mints "Share chat" links in this format. Absent config ⇒
353
+ * never sent, and the widget falls back to its own share route.
354
+ */
355
+ | {
356
+ type: 'host.deeplinkFormat';
357
+ format: string;
358
+ }
359
+ /**
360
+ * The host page's URL matches that same template, and this is the share token it
361
+ * carries — or `null` the moment the user navigates off it. Only the loader can see
362
+ * the host URL, so only the loader can report this; the widget opens (or closes) the
363
+ * shared thread on the back of it.
364
+ */
365
+ | {
366
+ type: 'host.openShare';
367
+ token: string | null;
368
+ }
349
369
  /**
350
370
  * Where the widget currently IS. Sent on mount and after anything that moves it
351
371
  * (drag, snap, mode change, viewport resize).
@@ -669,6 +689,17 @@ interface LoaderConfig {
669
689
  * the URL param still works untouched; `mount()` ORs the two together.
670
690
  */
671
691
  dev?: boolean;
692
+ /**
693
+ * Host-site share-link template. The widget mints "Share chat" links in this format,
694
+ * and this loader watches the host URL for a match so a visitor landing on one opens
695
+ * that conversation. Exactly one `{share_token}`, as a whole path segment or whole
696
+ * query value, e.g. `https://portal.acme.com/assistant?mf_share={share_token}`.
697
+ * `{path}` directly after the origin makes it dynamic — links carry the page the
698
+ * sharer is on and pickup fires on any page (token must be a query value; the
699
+ * current pathname then reaches the widget). Absent ⇒ no watch is installed and
700
+ * the widget keeps its own share route.
701
+ */
702
+ shareDeeplinkFormat?: string;
672
703
  }
673
704
  declare function readConfig(): LoaderConfig | null;
674
705
  declare class EmbedHost {
@@ -706,6 +737,12 @@ declare class EmbedHost {
706
737
  private dragLeftBand;
707
738
  /** Loaded on first open. Holds everything that touches the customer's DOM. */
708
739
  private context;
740
+ /** Uninstall for the share-deeplink URL watch. No initializer on purpose: an `= null`
741
+ * emits a constructor assignment into the size-budgeted stub for no behaviour. */
742
+ private deeplinkStop?;
743
+ /** Set once `destroy()` has run; falsy (never assigned) otherwise — no initializer,
744
+ * same reasoning as `deeplinkStop` above. */
745
+ private destroyed?;
709
746
  /** The host element; kept so `destroy()` can remove it (React lifecycle). */
710
747
  private hostEl;
711
748
  /** Rendering into the host's own element: they own the box, the chrome and visibility. */
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/protocol.ts
2
- var PROTOCOL_VERSION = 3;
2
+ var PROTOCOL_VERSION = 4;
3
3
  var CHANNEL = "mf-embed";
4
4
  function envelope(payload, id) {
5
5
  return {
@@ -222,6 +222,7 @@ function dockBox(g, vw, _vh) {
222
222
 
223
223
  // src/loader.ts
224
224
  var DEFAULT_ORIGIN = "https://app.matterfact.com";
225
+ var EMBED_VERSION = "0.18.0";
225
226
  function devRequested() {
226
227
  try {
227
228
  if (new URLSearchParams(location.search).get("mfdev") === "1") return true;
@@ -266,7 +267,10 @@ function readConfig() {
266
267
  theme: el?.dataset.theme || "auto",
267
268
  surface: el?.dataset.surface || "",
268
269
  container,
269
- pageContext
270
+ pageContext,
271
+ // `data-share-deeplink-format="https://…/{share_token}"` — the script-tag twin of
272
+ // the React `shareDeeplinkFormat` prop.
273
+ shareDeeplinkFormat: el?.dataset.shareDeeplinkFormat
270
274
  // No `data-dev` — see the `dev` field's doc comment: the URL trigger is the
271
275
  // point for the script-tag path, so there is deliberately no script-tag knob here.
272
276
  // No `data-actions` either: the action policy lives entirely in the lazy chunk,
@@ -347,6 +351,8 @@ var EmbedHost = class {
347
351
  this.inline = !!config.container;
348
352
  }
349
353
  mount() {
354
+ const w = window;
355
+ (w.matterfact ?? (w.matterfact = {})).embedVersion = EMBED_VERSION;
350
356
  const host = document.createElement("div");
351
357
  this.hostEl = host;
352
358
  host.id = "matterfact-embed";
@@ -390,7 +396,7 @@ var EmbedHost = class {
390
396
  "sandbox",
391
397
  "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"
392
398
  );
393
- iframe.setAttribute("allow", "microphone; clipboard-write");
399
+ iframe.setAttribute("allow", "microphone; clipboard-write; web-share");
394
400
  iframe.src = `${this.config.origin}/embed/chat?k=${encodeURIComponent(
395
401
  this.config.publishableKey
396
402
  )}&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
@@ -403,6 +409,18 @@ var EmbedHost = class {
403
409
  window.addEventListener("resize", this.onViewportResize);
404
410
  this.place();
405
411
  }
412
+ const fmt = this.config.shareDeeplinkFormat;
413
+ if (fmt) {
414
+ void import('./deeplink-X7IQDBCR.js').then((m) => {
415
+ if (this.destroyed) return;
416
+ this.deeplinkStop = m.installDeeplinkWatch(
417
+ fmt,
418
+ (token) => this.send({ type: "host.openShare", token }),
419
+ (format) => this.send({ type: "host.deeplinkFormat", format })
420
+ );
421
+ }).catch(() => {
422
+ });
423
+ }
406
424
  }
407
425
  /**
408
426
  * Test seam. The shadow root is CLOSED, so a test cannot reach the iframe to forge a
@@ -825,6 +843,9 @@ var EmbedHost = class {
825
843
  this.iframe = null;
826
844
  this.shadow = null;
827
845
  this.ready = false;
846
+ this.destroyed = true;
847
+ this.deeplinkStop?.();
848
+ this.deeplinkStop = null;
828
849
  void this.context?.then((m) => m.stop());
829
850
  }
830
851
  };