@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/README.md +62 -2
- package/dist/chunk-QVNE5FQV.js +2 -0
- package/dist/chunk-QVNE5FQV.js.map +7 -0
- package/dist/deeplink-IBFUWANV.js +104 -0
- package/dist/deeplink-IBFUWANV.js.map +1 -0
- package/dist/deeplink-QW3VRPOY.js +101 -0
- package/dist/deeplink-QW3VRPOY.js.map +1 -0
- package/dist/embed.js +1 -1
- package/dist/embed.js.map +3 -3
- package/dist/index.cjs +131 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +214 -14
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +44 -8
- package/dist/react.d.ts +44 -8
- package/dist/react.js +102 -11
- package/dist/react.js.map +1 -1
- package/examples/embed-demo/src/App.tsx +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,64 @@ 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
|
+
### Finding out which params an artifact accepts
|
|
96
|
+
|
|
97
|
+
Params only work under the names the artifact declared in its manifest, and a name it
|
|
98
|
+
never declared is ignored rather than rejected — so a typo looks exactly like a working
|
|
99
|
+
integration.
|
|
100
|
+
|
|
101
|
+
**Open the browser console.** Every embedded artifact states what it accepts as soon as it
|
|
102
|
+
loads, before you have pushed anything:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
[matterfact] Artifact "snowflake-query-showcase" accepts params:
|
|
106
|
+
ticker (enum: AAPL US, default "AAPL US"); companyLike (enum: %APPLE%, default "%APPLE%").
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Read the **default** as well as the type: it is what reveals a convention the type can't.
|
|
110
|
+
Ticker format is per-artifact — some want a bare symbol (`MU`), others Bloomberg-style
|
|
111
|
+
(`AAPL US`), and none want a ` Equity` tail.
|
|
112
|
+
|
|
113
|
+
`declares no params` means the artifact has no `params` block at all. It isn't drivable as
|
|
114
|
+
it stands and whoever owns it needs to add one — no host-side change will help.
|
|
115
|
+
|
|
116
|
+
Push a name it doesn't declare and you also get a warning naming it, so a typo says so
|
|
117
|
+
rather than failing quietly. Values are normalised on arrival, so what a reader sees may
|
|
118
|
+
differ from what you sent: an `entity` is trimmed, upper-cased and capped at 24 characters.
|
|
119
|
+
|
|
120
|
+
### An artifact inside the chat
|
|
121
|
+
|
|
64
122
|
```js
|
|
65
123
|
const host = mount({ …config });
|
|
66
124
|
|
|
@@ -69,8 +127,8 @@ host.setArtifactParams({ ticker: 'NVDA' }); // or window.matterfact.setArtifac
|
|
|
69
127
|
|
|
70
128
|
Safe to call before the widget finishes loading — it's queued and delivered on ready.
|
|
71
129
|
|
|
72
|
-
**A push is authoritative for every key it names.**
|
|
73
|
-
changing
|
|
130
|
+
**A push is authoritative for every key it names.** Unlike the `params` prop, this is raw:
|
|
131
|
+
send only the params you're actually changing.
|
|
74
132
|
|
|
75
133
|
```js
|
|
76
134
|
host.setArtifactParams({ window: '5Y' }); // ✅ changes the window
|
|
@@ -90,6 +148,8 @@ onEvent(e) {
|
|
|
90
148
|
}
|
|
91
149
|
```
|
|
92
150
|
|
|
151
|
+
### Either way
|
|
152
|
+
|
|
93
153
|
A param can also follow your page automatically: if it declares a binding and you publish
|
|
94
154
|
page context (`window.matterfact.context` or `getPageContext`), it tracks the entity you're
|
|
95
155
|
showing with no glue code. A reader's own pick takes over from there, until they choose to
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var y="{share_token}",g="mf-share-token-sentinel",S=/^[A-Za-z0-9._~-]{4,256}$/,m=r=>r.length>1&&r.endsWith("/")?r.slice(0,-1):r;function P(r){let p=r.indexOf(y);if(p<0||r.indexOf(y,p+1)>=0)return null;let t;try{t=new URL(r.replace(y,g))}catch{return null}if(t.protocol!=="https:"&&t.protocol!=="http:")return null;let c=[...t.searchParams.keys()];if(new Set(c).size!==c.length)return null;let h=m(t.pathname),s=h.split("/"),o=s.indexOf(g),a=null;for(let[u,n]of t.searchParams)n===g&&(a=u);return o>=0==(a!==null)?null:u=>{let n;try{n=new URL(u)}catch{return null}if(n.origin!==t.origin)return null;let e=m(n.pathname),i;if(o>=0){let f=e.split("/");if(f.length!==s.length)return null;for(let l=0;l<s.length;l++)if(l!==o&&f[l]!==s[l])return null;i=f[o];for(let[l,d]of t.searchParams)if(n.searchParams.get(l)!==d)return null}else{if(e!==h)return null;for(let[f,l]of t.searchParams)if(l!==g&&n.searchParams.get(f)!==l)return null;i=n.searchParams.get(a)}return i&&S.test(i)?i:null}}function k(r,p){let t=P(r);if(!t)return()=>{};let c=null,h=!1,s=()=>{if(!h)try{let e=t(location.href);e!==c&&(c=e,p(e))}catch(e){console.warn("[mf-embed] deeplink match failed",e)}},o=history.pushState,a=history.replaceState,u=function(...e){let i=o.apply(this,e);return s(),i},n=function(...e){let i=a.apply(this,e);return s(),i};return history.pushState=u,history.replaceState=n,window.addEventListener("popstate",s),s(),()=>{h=!0,window.removeEventListener("popstate",s),history.pushState===u&&(history.pushState=o),history.replaceState===n&&(history.replaceState=a)}}export{P as compileDeeplinkFormat,k as installDeeplinkWatch};
|
|
2
|
+
//# sourceMappingURL=chunk-QVNE5FQV.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/deeplink.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Share-deeplink matching for the host-page loader. Lazy chunk \u2014 loaded only\n * when the widget reports a configured deeplink_format \u2014 so the eager stub\n * budget is untouched (see build.mjs STUB_BUDGET_GZ).\n *\n * Mirrors the backend's validate_deeplink_format contract: the {share_token}\n * placeholder is a whole path segment or a whole query value; literal template\n * query params must match; extra host params (utm noise) are ignored. A repeated\n * query key is read first-wins on the HREF (URLSearchParams.get), and refused\n * outright on the TEMPLATE \u2014 a template asking for two values of one key can\n * never match.\n */\nconst PLACEHOLDER = '{share_token}';\n// URL() percent-encodes braces in paths \u2014 parse the template with a safe\n// sentinel standing in for the placeholder, then locate the sentinel.\nconst SENTINEL = 'mf-share-token-sentinel';\nconst TOKEN_RE = /^[A-Za-z0-9._~-]{4,256}$/;\n\n/** `/share/tok/` and `/share/tok` are the same route; one trailing empty segment\n * is noise, on the href and on the template alike. A second one is not. */\nconst trimSlash = (path: string) =>\n path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path;\n\n/**\n * Turn a host's deeplink template into a matcher over `location.href`, or `null`\n * if the template is one we refuse to honour (no placeholder, two of them, one\n * that isn't a whole segment/value, a non-web scheme, a repeated query key). A\n * `null` here is a dead watch, never a match-everything.\n */\nexport function compileDeeplinkFormat(\n format: string,\n): ((href: string) => string | null) | null {\n const first = format.indexOf(PLACEHOLDER);\n if (first < 0 || format.indexOf(PLACEHOLDER, first + 1) >= 0) return null;\n let f: URL;\n try {\n f = new URL(format.replace(PLACEHOLDER, SENTINEL));\n } catch {\n return null;\n }\n // Web schemes only, as the backend validator has it. A `file:`/opaque-origin\n // template would make the origin check below vacuous: every opaque origin\n // serialises to the same `\"null\"`, so any such page would match any such template.\n if (f.protocol !== 'https:' && f.protocol !== 'http:') return null;\n const keys = [...f.searchParams.keys()];\n if (new Set(keys).size !== keys.length) return null;\n const fPath = trimSlash(f.pathname);\n const segs = fPath.split('/');\n const segIdx = segs.indexOf(SENTINEL);\n let queryKey: string | null = null;\n for (const [k, v] of f.searchParams) {\n if (v === SENTINEL) queryKey = k;\n }\n // Exactly one placement, and it must be a WHOLE segment/value \u2014 a partial\n // (\"/share-{share_token}\") leaves no sentinel match in either list.\n if ((segIdx >= 0) === (queryKey !== null)) return null;\n\n return (href: string) => {\n let u: URL;\n try {\n u = new URL(href);\n } catch {\n return null;\n }\n if (u.origin !== f.origin) return null;\n const uPath = trimSlash(u.pathname);\n let token: string | null;\n if (segIdx >= 0) {\n const us = uPath.split('/');\n if (us.length !== segs.length) return null;\n for (let i = 0; i < segs.length; i++) {\n if (i !== segIdx && us[i] !== segs[i]) return null;\n }\n // Deliberately NOT decoded. TOKEN_RE admits only unreserved characters, so a\n // percent-escape is a non-token either way \u2014 while decodeURIComponent throws\n // URIError on a malformed one (`/share/100%off`), inside the host's own pushState.\n token = us[segIdx];\n for (const [k, v] of f.searchParams) {\n if (u.searchParams.get(k) !== v) return null;\n }\n } else {\n if (uPath !== fPath) return null;\n for (const [k, v] of f.searchParams) {\n if (v === SENTINEL) continue;\n if (u.searchParams.get(k) !== v) return null;\n }\n token = u.searchParams.get(queryKey!);\n }\n return token && TOKEN_RE.test(token) ? token : null;\n };\n}\n\n/**\n * Watch the host location for the template, reporting the token on every\n * transition \u2014 including the transition to `null` when the user navigates away,\n * which is how the widget learns to stop showing a shared thread. Never fires an\n * initial `null`: a page that was never a deeplink has nothing to report.\n *\n * Returns an uninstall. An unmatchable format gets a no-op one rather than a\n * watch that can never fire.\n */\nexport function installDeeplinkWatch(\n format: string,\n onChange: (token: string | null) => void,\n): () => void {\n const match = compileDeeplinkFormat(format);\n if (!match) return () => {};\n let last: string | null = null;\n let stopped = false;\n const fire = () => {\n // We run INSIDE the host's own pushState. Nothing in here may surface as an\n // exception in their router, so the whole body is guarded \u2014 and `stopped` neuters\n // this closure for good, including via a wrapper someone else's restore put back.\n if (stopped) return;\n try {\n const token = match(location.href);\n if (token !== last) {\n last = token;\n onChange(token);\n }\n } catch (err) {\n // Swallowed \u2014 a matcher bug, or a throwing consumer, is ours, not the host's \u2014\n // but surfaced, since this chunk already loaded and the byte is free here.\n console.warn('[mf-embed] deeplink match failed', err);\n }\n };\n // Wrap history like context.ts's watchNavigation does, but independently \u2014 this\n // watch must run even when page-context observation is off.\n const origPush = history.pushState;\n const origReplace = history.replaceState;\n const ourPush = function (\n this: History,\n ...args: Parameters<History['pushState']>\n ) {\n const r = origPush.apply(this, args);\n fire();\n return r;\n } as History['pushState'];\n const ourReplace = function (\n this: History,\n ...args: Parameters<History['replaceState']>\n ) {\n const r = origReplace.apply(this, args);\n fire();\n return r;\n } as History['replaceState'];\n history.pushState = ourPush;\n history.replaceState = ourReplace;\n window.addEventListener('popstate', fire);\n fire();\n return () => {\n stopped = true;\n window.removeEventListener('popstate', fire);\n // Restore only while OUR wrapper is still the outermost one. context.ts wraps on\n // first chat open, on top of us: assigning over it would drop its wrapper, and its\n // own restore would later hand ours back \u2014 which is what `stopped` is for.\n if (history.pushState === ourPush) history.pushState = origPush;\n if (history.replaceState === ourReplace) history.replaceState = origReplace;\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["PLACEHOLDER", "SENTINEL", "TOKEN_RE", "trimSlash", "path", "compileDeeplinkFormat", "format", "first", "f", "keys", "fPath", "segs", "segIdx", "queryKey", "k", "v", "href", "u", "uPath", "token", "us", "i", "installDeeplinkWatch", "onChange", "match", "last", "stopped", "fire", "err", "origPush", "origReplace", "ourPush", "args", "r", "ourReplace"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/deeplink.ts
|
|
4
|
+
var PLACEHOLDER = "{share_token}";
|
|
5
|
+
var SENTINEL = "mf-share-token-sentinel";
|
|
6
|
+
var TOKEN_RE = /^[A-Za-z0-9._~-]{4,256}$/;
|
|
7
|
+
var trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
8
|
+
function compileDeeplinkFormat(format) {
|
|
9
|
+
const first = format.indexOf(PLACEHOLDER);
|
|
10
|
+
if (first < 0 || format.indexOf(PLACEHOLDER, first + 1) >= 0) return null;
|
|
11
|
+
let f;
|
|
12
|
+
try {
|
|
13
|
+
f = new URL(format.replace(PLACEHOLDER, SENTINEL));
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (f.protocol !== "https:" && f.protocol !== "http:") return null;
|
|
18
|
+
const keys = [...f.searchParams.keys()];
|
|
19
|
+
if (new Set(keys).size !== keys.length) return null;
|
|
20
|
+
const fPath = trimSlash(f.pathname);
|
|
21
|
+
const segs = fPath.split("/");
|
|
22
|
+
const segIdx = segs.indexOf(SENTINEL);
|
|
23
|
+
let queryKey = null;
|
|
24
|
+
for (const [k, v] of f.searchParams) {
|
|
25
|
+
if (v === SENTINEL) queryKey = k;
|
|
26
|
+
}
|
|
27
|
+
if (segIdx >= 0 === (queryKey !== null)) return null;
|
|
28
|
+
return (href) => {
|
|
29
|
+
let u;
|
|
30
|
+
try {
|
|
31
|
+
u = new URL(href);
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (u.origin !== f.origin) return null;
|
|
36
|
+
const uPath = trimSlash(u.pathname);
|
|
37
|
+
let token;
|
|
38
|
+
if (segIdx >= 0) {
|
|
39
|
+
const us = uPath.split("/");
|
|
40
|
+
if (us.length !== segs.length) return null;
|
|
41
|
+
for (let i = 0; i < segs.length; i++) {
|
|
42
|
+
if (i !== segIdx && us[i] !== segs[i]) return null;
|
|
43
|
+
}
|
|
44
|
+
token = us[segIdx];
|
|
45
|
+
for (const [k, v] of f.searchParams) {
|
|
46
|
+
if (u.searchParams.get(k) !== v) return null;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
if (uPath !== fPath) return null;
|
|
50
|
+
for (const [k, v] of f.searchParams) {
|
|
51
|
+
if (v === SENTINEL) continue;
|
|
52
|
+
if (u.searchParams.get(k) !== v) return null;
|
|
53
|
+
}
|
|
54
|
+
token = u.searchParams.get(queryKey);
|
|
55
|
+
}
|
|
56
|
+
return token && TOKEN_RE.test(token) ? token : null;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function installDeeplinkWatch(format, onChange) {
|
|
60
|
+
const match = compileDeeplinkFormat(format);
|
|
61
|
+
if (!match) return () => {
|
|
62
|
+
};
|
|
63
|
+
let last = null;
|
|
64
|
+
let stopped = false;
|
|
65
|
+
const fire = () => {
|
|
66
|
+
if (stopped) return;
|
|
67
|
+
try {
|
|
68
|
+
const token = match(location.href);
|
|
69
|
+
if (token !== last) {
|
|
70
|
+
last = token;
|
|
71
|
+
onChange(token);
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.warn("[mf-embed] deeplink match failed", err);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const origPush = history.pushState;
|
|
78
|
+
const origReplace = history.replaceState;
|
|
79
|
+
const ourPush = function(...args) {
|
|
80
|
+
const r = origPush.apply(this, args);
|
|
81
|
+
fire();
|
|
82
|
+
return r;
|
|
83
|
+
};
|
|
84
|
+
const ourReplace = function(...args) {
|
|
85
|
+
const r = origReplace.apply(this, args);
|
|
86
|
+
fire();
|
|
87
|
+
return r;
|
|
88
|
+
};
|
|
89
|
+
history.pushState = ourPush;
|
|
90
|
+
history.replaceState = ourReplace;
|
|
91
|
+
window.addEventListener("popstate", fire);
|
|
92
|
+
fire();
|
|
93
|
+
return () => {
|
|
94
|
+
stopped = true;
|
|
95
|
+
window.removeEventListener("popstate", fire);
|
|
96
|
+
if (history.pushState === ourPush) history.pushState = origPush;
|
|
97
|
+
if (history.replaceState === ourReplace) history.replaceState = origReplace;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
compileDeeplinkFormat,
|
|
102
|
+
installDeeplinkWatch
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=deeplink-IBFUWANV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/deeplink.ts"],"sourcesContent":["/**\n * Share-deeplink matching for the host-page loader. Lazy chunk — loaded only\n * when the widget reports a configured deeplink_format — so the eager stub\n * budget is untouched (see build.mjs STUB_BUDGET_GZ).\n *\n * Mirrors the backend's validate_deeplink_format contract: the {share_token}\n * placeholder is a whole path segment or a whole query value; literal template\n * query params must match; extra host params (utm noise) are ignored. A repeated\n * query key is read first-wins on the HREF (URLSearchParams.get), and refused\n * outright on the TEMPLATE — a template asking for two values of one key can\n * never match.\n */\nconst PLACEHOLDER = '{share_token}';\n// URL() percent-encodes braces in paths — parse the template with a safe\n// sentinel standing in for the placeholder, then locate the sentinel.\nconst SENTINEL = 'mf-share-token-sentinel';\nconst TOKEN_RE = /^[A-Za-z0-9._~-]{4,256}$/;\n\n/** `/share/tok/` and `/share/tok` are the same route; one trailing empty segment\n * is noise, on the href and on the template alike. A second one is not. */\nconst trimSlash = (path: string) =>\n path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path;\n\n/**\n * Turn a host's deeplink template into a matcher over `location.href`, or `null`\n * if the template is one we refuse to honour (no placeholder, two of them, one\n * that isn't a whole segment/value, a non-web scheme, a repeated query key). A\n * `null` here is a dead watch, never a match-everything.\n */\nexport function compileDeeplinkFormat(\n format: string,\n): ((href: string) => string | null) | null {\n const first = format.indexOf(PLACEHOLDER);\n if (first < 0 || format.indexOf(PLACEHOLDER, first + 1) >= 0) return null;\n let f: URL;\n try {\n f = new URL(format.replace(PLACEHOLDER, SENTINEL));\n } catch {\n return null;\n }\n // Web schemes only, as the backend validator has it. A `file:`/opaque-origin\n // template would make the origin check below vacuous: every opaque origin\n // serialises to the same `\"null\"`, so any such page would match any such template.\n if (f.protocol !== 'https:' && f.protocol !== 'http:') return null;\n const keys = [...f.searchParams.keys()];\n if (new Set(keys).size !== keys.length) return null;\n const fPath = trimSlash(f.pathname);\n const segs = fPath.split('/');\n const segIdx = segs.indexOf(SENTINEL);\n let queryKey: string | null = null;\n for (const [k, v] of f.searchParams) {\n if (v === SENTINEL) queryKey = k;\n }\n // Exactly one placement, and it must be a WHOLE segment/value — a partial\n // (\"/share-{share_token}\") leaves no sentinel match in either list.\n if ((segIdx >= 0) === (queryKey !== null)) return null;\n\n return (href: string) => {\n let u: URL;\n try {\n u = new URL(href);\n } catch {\n return null;\n }\n if (u.origin !== f.origin) return null;\n const uPath = trimSlash(u.pathname);\n let token: string | null;\n if (segIdx >= 0) {\n const us = uPath.split('/');\n if (us.length !== segs.length) return null;\n for (let i = 0; i < segs.length; i++) {\n if (i !== segIdx && us[i] !== segs[i]) return null;\n }\n // Deliberately NOT decoded. TOKEN_RE admits only unreserved characters, so a\n // percent-escape is a non-token either way — while decodeURIComponent throws\n // URIError on a malformed one (`/share/100%off`), inside the host's own pushState.\n token = us[segIdx];\n for (const [k, v] of f.searchParams) {\n if (u.searchParams.get(k) !== v) return null;\n }\n } else {\n if (uPath !== fPath) return null;\n for (const [k, v] of f.searchParams) {\n if (v === SENTINEL) continue;\n if (u.searchParams.get(k) !== v) return null;\n }\n token = u.searchParams.get(queryKey!);\n }\n return token && TOKEN_RE.test(token) ? token : null;\n };\n}\n\n/**\n * Watch the host location for the template, reporting the token on every\n * transition — including the transition to `null` when the user navigates away,\n * which is how the widget learns to stop showing a shared thread. Never fires an\n * initial `null`: a page that was never a deeplink has nothing to report.\n *\n * Returns an uninstall. An unmatchable format gets a no-op one rather than a\n * watch that can never fire.\n */\nexport function installDeeplinkWatch(\n format: string,\n onChange: (token: string | null) => void,\n): () => void {\n const match = compileDeeplinkFormat(format);\n if (!match) return () => {};\n let last: string | null = null;\n let stopped = false;\n const fire = () => {\n // We run INSIDE the host's own pushState. Nothing in here may surface as an\n // exception in their router, so the whole body is guarded — and `stopped` neuters\n // this closure for good, including via a wrapper someone else's restore put back.\n if (stopped) return;\n try {\n const token = match(location.href);\n if (token !== last) {\n last = token;\n onChange(token);\n }\n } catch (err) {\n // Swallowed — a matcher bug, or a throwing consumer, is ours, not the host's —\n // but surfaced, since this chunk already loaded and the byte is free here.\n console.warn('[mf-embed] deeplink match failed', err);\n }\n };\n // Wrap history like context.ts's watchNavigation does, but independently — this\n // watch must run even when page-context observation is off.\n const origPush = history.pushState;\n const origReplace = history.replaceState;\n const ourPush = function (\n this: History,\n ...args: Parameters<History['pushState']>\n ) {\n const r = origPush.apply(this, args);\n fire();\n return r;\n } as History['pushState'];\n const ourReplace = function (\n this: History,\n ...args: Parameters<History['replaceState']>\n ) {\n const r = origReplace.apply(this, args);\n fire();\n return r;\n } as History['replaceState'];\n history.pushState = ourPush;\n history.replaceState = ourReplace;\n window.addEventListener('popstate', fire);\n fire();\n return () => {\n stopped = true;\n window.removeEventListener('popstate', fire);\n // Restore only while OUR wrapper is still the outermost one. context.ts wraps on\n // first chat open, on top of us: assigning over it would drop its wrapper, and its\n // own restore would later hand ours back — which is what `stopped` is for.\n if (history.pushState === ourPush) history.pushState = origPush;\n if (history.replaceState === ourReplace) history.replaceState = origReplace;\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// src/deeplink.ts
|
|
2
|
+
var PLACEHOLDER = "{share_token}";
|
|
3
|
+
var SENTINEL = "mf-share-token-sentinel";
|
|
4
|
+
var TOKEN_RE = /^[A-Za-z0-9._~-]{4,256}$/;
|
|
5
|
+
var trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
6
|
+
function compileDeeplinkFormat(format) {
|
|
7
|
+
const first = format.indexOf(PLACEHOLDER);
|
|
8
|
+
if (first < 0 || format.indexOf(PLACEHOLDER, first + 1) >= 0) return null;
|
|
9
|
+
let f;
|
|
10
|
+
try {
|
|
11
|
+
f = new URL(format.replace(PLACEHOLDER, SENTINEL));
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (f.protocol !== "https:" && f.protocol !== "http:") return null;
|
|
16
|
+
const keys = [...f.searchParams.keys()];
|
|
17
|
+
if (new Set(keys).size !== keys.length) return null;
|
|
18
|
+
const fPath = trimSlash(f.pathname);
|
|
19
|
+
const segs = fPath.split("/");
|
|
20
|
+
const segIdx = segs.indexOf(SENTINEL);
|
|
21
|
+
let queryKey = null;
|
|
22
|
+
for (const [k, v] of f.searchParams) {
|
|
23
|
+
if (v === SENTINEL) queryKey = k;
|
|
24
|
+
}
|
|
25
|
+
if (segIdx >= 0 === (queryKey !== null)) return null;
|
|
26
|
+
return (href) => {
|
|
27
|
+
let u;
|
|
28
|
+
try {
|
|
29
|
+
u = new URL(href);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (u.origin !== f.origin) return null;
|
|
34
|
+
const uPath = trimSlash(u.pathname);
|
|
35
|
+
let token;
|
|
36
|
+
if (segIdx >= 0) {
|
|
37
|
+
const us = uPath.split("/");
|
|
38
|
+
if (us.length !== segs.length) return null;
|
|
39
|
+
for (let i = 0; i < segs.length; i++) {
|
|
40
|
+
if (i !== segIdx && us[i] !== segs[i]) return null;
|
|
41
|
+
}
|
|
42
|
+
token = us[segIdx];
|
|
43
|
+
for (const [k, v] of f.searchParams) {
|
|
44
|
+
if (u.searchParams.get(k) !== v) return null;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
if (uPath !== fPath) return null;
|
|
48
|
+
for (const [k, v] of f.searchParams) {
|
|
49
|
+
if (v === SENTINEL) continue;
|
|
50
|
+
if (u.searchParams.get(k) !== v) return null;
|
|
51
|
+
}
|
|
52
|
+
token = u.searchParams.get(queryKey);
|
|
53
|
+
}
|
|
54
|
+
return token && TOKEN_RE.test(token) ? token : null;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function installDeeplinkWatch(format, onChange) {
|
|
58
|
+
const match = compileDeeplinkFormat(format);
|
|
59
|
+
if (!match) return () => {
|
|
60
|
+
};
|
|
61
|
+
let last = null;
|
|
62
|
+
let stopped = false;
|
|
63
|
+
const fire = () => {
|
|
64
|
+
if (stopped) return;
|
|
65
|
+
try {
|
|
66
|
+
const token = match(location.href);
|
|
67
|
+
if (token !== last) {
|
|
68
|
+
last = token;
|
|
69
|
+
onChange(token);
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.warn("[mf-embed] deeplink match failed", err);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const origPush = history.pushState;
|
|
76
|
+
const origReplace = history.replaceState;
|
|
77
|
+
const ourPush = function(...args) {
|
|
78
|
+
const r = origPush.apply(this, args);
|
|
79
|
+
fire();
|
|
80
|
+
return r;
|
|
81
|
+
};
|
|
82
|
+
const ourReplace = function(...args) {
|
|
83
|
+
const r = origReplace.apply(this, args);
|
|
84
|
+
fire();
|
|
85
|
+
return r;
|
|
86
|
+
};
|
|
87
|
+
history.pushState = ourPush;
|
|
88
|
+
history.replaceState = ourReplace;
|
|
89
|
+
window.addEventListener("popstate", fire);
|
|
90
|
+
fire();
|
|
91
|
+
return () => {
|
|
92
|
+
stopped = true;
|
|
93
|
+
window.removeEventListener("popstate", fire);
|
|
94
|
+
if (history.pushState === ourPush) history.pushState = origPush;
|
|
95
|
+
if (history.replaceState === ourReplace) history.replaceState = origReplace;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { compileDeeplinkFormat, installDeeplinkWatch };
|
|
100
|
+
//# sourceMappingURL=deeplink-QW3VRPOY.js.map
|
|
101
|
+
//# sourceMappingURL=deeplink-QW3VRPOY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/deeplink.ts"],"names":[],"mappings":"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","file":"deeplink-QW3VRPOY.js","sourcesContent":["/**\n * Share-deeplink matching for the host-page loader. Lazy chunk — loaded only\n * when the widget reports a configured deeplink_format — so the eager stub\n * budget is untouched (see build.mjs STUB_BUDGET_GZ).\n *\n * Mirrors the backend's validate_deeplink_format contract: the {share_token}\n * placeholder is a whole path segment or a whole query value; literal template\n * query params must match; extra host params (utm noise) are ignored. A repeated\n * query key is read first-wins on the HREF (URLSearchParams.get), and refused\n * outright on the TEMPLATE — a template asking for two values of one key can\n * never match.\n */\nconst PLACEHOLDER = '{share_token}';\n// URL() percent-encodes braces in paths — parse the template with a safe\n// sentinel standing in for the placeholder, then locate the sentinel.\nconst SENTINEL = 'mf-share-token-sentinel';\nconst TOKEN_RE = /^[A-Za-z0-9._~-]{4,256}$/;\n\n/** `/share/tok/` and `/share/tok` are the same route; one trailing empty segment\n * is noise, on the href and on the template alike. A second one is not. */\nconst trimSlash = (path: string) =>\n path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path;\n\n/**\n * Turn a host's deeplink template into a matcher over `location.href`, or `null`\n * if the template is one we refuse to honour (no placeholder, two of them, one\n * that isn't a whole segment/value, a non-web scheme, a repeated query key). A\n * `null` here is a dead watch, never a match-everything.\n */\nexport function compileDeeplinkFormat(\n format: string,\n): ((href: string) => string | null) | null {\n const first = format.indexOf(PLACEHOLDER);\n if (first < 0 || format.indexOf(PLACEHOLDER, first + 1) >= 0) return null;\n let f: URL;\n try {\n f = new URL(format.replace(PLACEHOLDER, SENTINEL));\n } catch {\n return null;\n }\n // Web schemes only, as the backend validator has it. A `file:`/opaque-origin\n // template would make the origin check below vacuous: every opaque origin\n // serialises to the same `\"null\"`, so any such page would match any such template.\n if (f.protocol !== 'https:' && f.protocol !== 'http:') return null;\n const keys = [...f.searchParams.keys()];\n if (new Set(keys).size !== keys.length) return null;\n const fPath = trimSlash(f.pathname);\n const segs = fPath.split('/');\n const segIdx = segs.indexOf(SENTINEL);\n let queryKey: string | null = null;\n for (const [k, v] of f.searchParams) {\n if (v === SENTINEL) queryKey = k;\n }\n // Exactly one placement, and it must be a WHOLE segment/value — a partial\n // (\"/share-{share_token}\") leaves no sentinel match in either list.\n if ((segIdx >= 0) === (queryKey !== null)) return null;\n\n return (href: string) => {\n let u: URL;\n try {\n u = new URL(href);\n } catch {\n return null;\n }\n if (u.origin !== f.origin) return null;\n const uPath = trimSlash(u.pathname);\n let token: string | null;\n if (segIdx >= 0) {\n const us = uPath.split('/');\n if (us.length !== segs.length) return null;\n for (let i = 0; i < segs.length; i++) {\n if (i !== segIdx && us[i] !== segs[i]) return null;\n }\n // Deliberately NOT decoded. TOKEN_RE admits only unreserved characters, so a\n // percent-escape is a non-token either way — while decodeURIComponent throws\n // URIError on a malformed one (`/share/100%off`), inside the host's own pushState.\n token = us[segIdx];\n for (const [k, v] of f.searchParams) {\n if (u.searchParams.get(k) !== v) return null;\n }\n } else {\n if (uPath !== fPath) return null;\n for (const [k, v] of f.searchParams) {\n if (v === SENTINEL) continue;\n if (u.searchParams.get(k) !== v) return null;\n }\n token = u.searchParams.get(queryKey!);\n }\n return token && TOKEN_RE.test(token) ? token : null;\n };\n}\n\n/**\n * Watch the host location for the template, reporting the token on every\n * transition — including the transition to `null` when the user navigates away,\n * which is how the widget learns to stop showing a shared thread. Never fires an\n * initial `null`: a page that was never a deeplink has nothing to report.\n *\n * Returns an uninstall. An unmatchable format gets a no-op one rather than a\n * watch that can never fire.\n */\nexport function installDeeplinkWatch(\n format: string,\n onChange: (token: string | null) => void,\n): () => void {\n const match = compileDeeplinkFormat(format);\n if (!match) return () => {};\n let last: string | null = null;\n let stopped = false;\n const fire = () => {\n // We run INSIDE the host's own pushState. Nothing in here may surface as an\n // exception in their router, so the whole body is guarded — and `stopped` neuters\n // this closure for good, including via a wrapper someone else's restore put back.\n if (stopped) return;\n try {\n const token = match(location.href);\n if (token !== last) {\n last = token;\n onChange(token);\n }\n } catch (err) {\n // Swallowed — a matcher bug, or a throwing consumer, is ours, not the host's —\n // but surfaced, since this chunk already loaded and the byte is free here.\n console.warn('[mf-embed] deeplink match failed', err);\n }\n };\n // Wrap history like context.ts's watchNavigation does, but independently — this\n // watch must run even when page-context observation is off.\n const origPush = history.pushState;\n const origReplace = history.replaceState;\n const ourPush = function (\n this: History,\n ...args: Parameters<History['pushState']>\n ) {\n const r = origPush.apply(this, args);\n fire();\n return r;\n } as History['pushState'];\n const ourReplace = function (\n this: History,\n ...args: Parameters<History['replaceState']>\n ) {\n const r = origReplace.apply(this, args);\n fire();\n return r;\n } as History['replaceState'];\n history.pushState = ourPush;\n history.replaceState = ourReplace;\n window.addEventListener('popstate', fire);\n fire();\n return () => {\n stopped = true;\n window.removeEventListener('popstate', fire);\n // Restore only while OUR wrapper is still the outermost one. context.ts wraps on\n // first chat open, on top of us: assigning over it would drop its wrapper, and its\n // own restore would later hand ours back — which is what `stopped` is for.\n if (history.pushState === ourPush) history.pushState = origPush;\n if (history.replaceState === ourReplace) history.replaceState = origReplace;\n };\n}\n"]}
|
package/dist/embed.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var y="mf-embed";function C(o,e){return{channel:y,protocol:3,...e?{id:e}:{},payload:o}}function M(o){return typeof o=="object"&&o!==null&&o.channel===y}var v=["tl","tr","bl","br"];function l(){return{mode:"dock",corner:"br",x:0,y:0,floatW:420,floatH:640,dockSide:"right",tabY:0,dockW:600}}function h(o,e,t){return Math.min(Math.max(o,e),t)}function R(o,e,t,r,n){return{x:o[1]==="l"?20:r-20-e,y:o[0]==="t"?20:n-20-t}}function u(o,e,t){return o.corner?R(o.corner,56,56,e,t):{x:h(o.x,20,Math.max(20,e-20-56)),y:h(o.y,20,Math.max(20,t-20-56))}}function c(o,e,t){if(o.corner)return o.corner;let r=u(o,e,t);return $(r.x+56/2,r.y+56/2,e,t)}function $(o,e,t,r){return`${e<r/2?"t":"b"}${o<t/2?"l":"r"}`}function V(o,e,t,r){let n=h(o,20,Math.max(20,t-20-56)),i=h(e,20,Math.max(20,r-20-56));for(let a of v){let d=R(a,56,56,t,r);if(Math.hypot(n-d.x,i-d.y)<=96)return{corner:a,x:n,y:i}}return{corner:null,x:n,y:i}}function x(o,e,t){return o<=64?"left":o+e>=t-64?"right":null}function S(o,e,t){return o.mode==="dock"?{x:o.dockSide==="left"?0:e-44,y:w(o,t)}:u(o,e,t)}function G(o,e,t,r){if(o.mode==="dock"){let a=e?Math.min(Math.max(o.dockW,320),Math.max(0,t-40)):44;return{x:o.dockSide==="left"?0:t-a,w:a}}let n=u(o,t,r);if(!e)return{x:n.x,w:56};let{w:i}=p(o.floatW,o.floatH,t,r,320,420);return{x:c(o,t,r)[1]==="r"?n.x+56-i:n.x,w:i}}function N(o,e,t,r,n,i){let a=V(o,e,n,i);if(a.corner)return{mode:"float",...a};let d=x(t,r,n);return d?{mode:"dock",dockSide:d,tabY:h(e,0,Math.max(0,i-132))}:{mode:"float",...a}}function p(o,e,t,r,n,i){return{w:Math.min(Math.max(o,n),Math.max(0,t-40)),h:Math.min(Math.max(e,i),Math.max(0,r-40))}}function T(o,e,t,r,n){let i=o[1]==="r"?-1:1,a=o[0]==="b"?-1:1;return{w:e+i*r,h:t+a*n}}function L(o,e,t){return o==="right"?e-t:e+t}function H(o){if(!o)return null;try{let e=JSON.parse(o);if(!e||e.mode!=="float"&&e.mode!=="dock")return null;let t=l(),r=(n,i)=>typeof n=="number"?n:i;return{mode:e.mode,corner:v.includes(e.corner)?e.corner:null,x:r(e.x,t.x),y:r(e.y,t.y),floatW:r(e.floatW,t.floatW),floatH:r(e.floatH,t.floatH),dockSide:e.dockSide==="left"?"left":"right",tabY:r(e.tabY,t.tabY),dockW:r(e.dockW,t.dockW)}}catch{return null}}function I(o){return JSON.stringify(o)}var s=o=>`${o}px`;function j(o,e,t,r,n,i,a){let d=n[1]==="r",b=n[0]==="b",K=d?i-(o+56):o,Y=b?a-(e+56):e,A=h(K,20,Math.max(20,i-20-t)),E=h(Y,20,Math.max(20,a-20-r));return{top:b?"auto":s(E),bottom:b?s(E):"auto",left:d?"auto":s(A),right:d?s(A):"auto",width:s(t),height:s(r)}}function w(o,e){let t=Math.max(0,e-132);return h(o.tabY||Math.round((e-132)/2),0,t)}function _(o,e,t){let r=o.dockSide==="left";return{top:s(w(o,t)),bottom:"auto",left:r?"0px":"auto",right:r?"auto":"0px",width:s(44),height:s(132)}}function f(o,e,t,r,n,i){let a=u(o,r,n);return j(a.x,a.y,e,t,i??c(o,r,n),r,n)}function W(o,e,t,r,n){let i=o.dockSide==="left";return{top:s(w(o,n)),bottom:"auto",left:i?"0px":"auto",right:i?"auto":"0px",width:s(e),height:s(t)}}function B(o,e,t,r){let n=p(o.floatW,o.floatH,e,t,320,420);return f(o,n.w,n.h,e,t,r)}function P(o,e,t){let r=Math.min(Math.max(o.dockW,320),Math.max(0,e-40)),n=o.dockSide==="left";return{top:"0px",bottom:"0px",height:"auto",left:n?"0px":"auto",right:n?"auto":"0px",width:s(r)}}var te="https://app.matterfact.com";function F(){try{if(new URLSearchParams(location.search).get("mfdev")==="1")return!0;try{return localStorage.getItem("mfdev")==="1"}catch{return!1}}catch{return!1}}function g(o,e){F()&&console.info("[embed auth] host: "+o,e??"")}function U(){let o=document.currentScript??document.querySelector('script[data-key][src*="embed"]'),e=o?.dataset.key;if(!e)return console.error("[matterfact] missing data-key on the embed script tag"),null;let t=o?.dataset.container,r=t?document.querySelector(t):null;t&&!r&&console.error(`[matterfact] data-container="${t}" matched nothing; falling back to the corner`);let n=o?.dataset.pageContext?.toLowerCase(),i=!0;return n==="off"||n==="false"||n==="0"?i=!1:n==="declared"?i="declared":(n==="full"||n==="on"||n==="true"||n==="1")&&(i=!0),{publishableKey:e,origin:o?.dataset.origin||te,theme:o?.dataset.theme||"auto",surface:o?.dataset.surface||"",container:r,pageContext:i}}var O="mf.embed.pos",k=class{constructor(e){this.config=e;this.iframe=null;this.shadow=null;this.queue=[];this.ready=!1;this.open=!1;this.geo=l();this.resizeBase=null;this.dockBase=600;this.dragFrom=null;this.dragAt={x:0,y:0};this.dragBox={x:0,w:0};this.dragGrowth=null;this.dragLeftBand=!1;this.context=null;this.hostEl=null;this.ac=0;this.onMessage=e=>{e.origin===this.config.origin&&e.source===this.iframe?.contentWindow&&M(e.data)&&this.handle(e.data.payload)};this.onViewportResize=()=>{this.place()};this.send=e=>{if(!this.ready){this.queue.push(e);return}this.iframe?.contentWindow?.postMessage(C(e),this.config.origin)};this.inline=!!e.container}mount(){let e=document.createElement("div");this.hostEl=e,e.id="matterfact-embed",this.inline?(e.style.cssText=["all: initial","position: relative","display: block","width: 100%","height: 100%","contain: layout style"].join(";"),this.config.container.appendChild(e)):(this.geo=this.readGeometry(),e.style.cssText=["all: initial","position: fixed","z-index: 2147483000","contain: layout style","transition: width .18s ease, height .18s ease, top .18s ease, right .18s ease, bottom .18s ease, left .18s ease"].join(";"),document.body.appendChild(e)),this.shadow=e.attachShadow({mode:"closed"});let t=document.createElement("style");t.textContent=":host{all:initial}iframe{border:0;display:block;width:100%;height:100%;background:transparent}"+(this.inline?"":':host([data-mode="float"]) iframe,:host([data-mode="dock"]) iframe{color-scheme:light dark}:host([data-mode="float"]) iframe{border-radius:12px;box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029}:host([data-mode="dock"]) iframe{box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029}:host([data-mode="dock"][data-flush="right"]) iframe{border-radius:12px 0 0 12px}:host([data-mode="dock"][data-flush="left"]) iframe{border-radius:0 12px 12px 0}'),this.shadow.appendChild(t);let r=document.createElement("iframe");r.title="matterfact assistant",r.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"),r.setAttribute("allow","microphone; clipboard-write"),r.src=`${this.config.origin}/embed/chat?k=${encodeURIComponent(this.config.publishableKey)}&o=${encodeURIComponent(location.origin)}`+(this.config.surface?`&s=${encodeURIComponent(this.config.surface)}`:"")+(F()||this.config.dev?"&dev=1":"")+(this.inline?"&inline=1":""),this.iframe=r,this.shadow.appendChild(r),window.addEventListener("message",this.onMessage),this.inline||(window.addEventListener("resize",this.onViewportResize),this.place())}__testHandle(e){this.handle(e)}emit(e){let t=globalThis.matterfact?.onEvent;if(typeof t=="function")try{t(e)}catch{}}handle(e){switch(e.type){case"widget.ready":this.ready=!0,this.send({type:"host.ready",protocol:3,origin:location.origin}),this.send({type:"host.theme",mode:this.themeMode()}),this.flush(),this.inline&&this.loadContext(),this.emit({type:"ready"});break;case"widget.setOpen":if(this.emit({type:e.open?"open":"close"}),this.inline)break;this.open=e.open,this.place(),e.open&&this.loadContext();break;case"widget.setMode":if(this.inline)break;this.geo.mode=e.mode,this.writeGeometry(this.geo),this.place();break;case"widget.setDockSide":if(this.inline)break;this.geo.dockSide=e.side,this.writeGeometry(this.geo),this.place();break;case"widget.snapCorner":if(this.inline)break;this.geo.corner=e.corner,this.writeGeometry(this.geo),this.place();break;case"widget.setLauncherRegion":{if(this.inline||this.open)break;let t=window.innerWidth,r=window.innerHeight;if(e.w<=56&&e.h<=56)this.place();else{let n=this.geo.mode==="dock"?W(this.geo,e.w,e.h,t,r):f(this.geo,e.w,e.h,t,r);this.applyBox(n,e.h>56?"menu":"pill")}break}case"widget.resize":if(this.inline)break;this.hostEl&&this.open&&this.geo.mode==="float"&&(this.hostEl.style.height=`${Math.min(e.height,Math.max(0,window.innerHeight-40))}px`);break;case"widget.resizeStart":if(this.inline)break;this.resizeBase={w:this.geo.floatW,h:this.geo.floatH},this.dockBase=this.geo.dockW,this.hostEl&&(this.hostEl.style.transition="none");break;case"widget.resizeMove":{if(this.inline||!this.resizeBase)break;if(this.geo.mode==="dock"){let t=L(this.geo.dockSide,this.dockBase,e.dx);this.geo.dockW=Math.min(Math.max(t,320),Math.max(0,window.innerWidth-40))}else{let t=T(c(this.geo,window.innerWidth,window.innerHeight),this.resizeBase.w,this.resizeBase.h,e.dx,e.dy),r=p(t.w,t.h,window.innerWidth,window.innerHeight,320,420);this.geo.floatW=r.w,this.geo.floatH=r.h}this.place();break}case"widget.resizeEnd":if(this.inline)break;this.resizeBase=null,this.hostEl&&(this.hostEl.style.transition=""),this.writeGeometry(this.geo);break;case"widget.requestContext":this.loadContext().then(t=>t.provideContext());break;case"widget.pageContextMax":this.loadContext().then(t=>{t.setPageContextMax(e.mode)});break;case"widget.requestSnapshot":this.loadContext().then(t=>t.sendSnapshot(this.send));break;case"widget.readRegion":this.loadContext().then(t=>t.sendRegion(e.ref,this.send));break;case"widget.callTool":this.loadContext().then(t=>t.callTool(e.call,this.send));break;case"widget.dragStart":if(this.inline)break;this.dragFrom=S(this.geo,window.innerWidth,window.innerHeight),this.dragAt={...this.dragFrom},this.dragLeftBand=!1,this.dragGrowth=c(this.geo,window.innerWidth,window.innerHeight),this.dragBox=G(this.geo,this.open,window.innerWidth,window.innerHeight),this.hostEl&&(this.hostEl.style.transition="none");break;case"widget.dragMove":{if(this.inline||!this.dragFrom)break;let t=window.innerWidth,r=window.innerHeight;this.dragAt={x:this.dragFrom.x+e.dx,y:this.dragFrom.y+e.dy};let n=x(this.dragBox.x+e.dx,this.dragBox.w,t);n||(this.dragLeftBand=!0),n&&(this.dragLeftBand||this.geo.mode==="dock")?(this.geo.mode="dock",this.geo.dockSide=n,this.geo.tabY=Math.max(0,this.dragAt.y)):(this.geo.mode="float",this.geo.corner=null,this.geo.x=this.dragAt.x,this.geo.y=this.dragAt.y),this.place();break}case"widget.dragEnd":{if(this.inline||!this.dragFrom)break;let t=N(this.dragAt.x,this.dragAt.y,this.dragBox.x+(this.dragAt.x-this.dragFrom.x),this.dragBox.w,window.innerWidth,window.innerHeight);this.geo.mode=t.mode,t.mode==="dock"?(this.geo.dockSide=t.dockSide,this.geo.tabY=t.tabY):(this.geo.corner=t.corner,this.geo.x=t.x,this.geo.y=t.y),this.dragFrom=null,this.dragGrowth=null,this.hostEl&&(this.hostEl.style.transition=""),this.place(),this.writeGeometry(this.geo);break}case"widget.resetPos":if(this.inline)break;this.geo=l(),this.writeGeometry(this.geo),this.place();break;case"widget.hide":if(this.inline)break;this.hostEl&&(this.hostEl.style.display="none");break;case"widget.needsAuth":this.emit({type:"auth",phase:"required"}),this.provideAuth();break;case"widget.navigate":this.emit({type:"navigate",href:e.href}),this.loadContext().then(t=>t.navigateHost(e.href));break;case"widget.chat":this.emit({type:"chat",phase:e.phase,chatId:e.chatId});break;case"widget.artifactParams":this.emit({type:"artifactParams",params:e.params,source:e.source});break}}applyBox(e,t){if(!this.hostEl||this.inline)return;let r=this.hostEl.style;r.top=e.top,r.right=e.right,r.bottom=e.bottom,r.left=e.left,r.width=e.width,r.height=e.height,this.hostEl.dataset.mode=t,t==="tab"||t==="dock"?this.hostEl.dataset.flush=this.geo.dockSide:delete this.hostEl.dataset.flush}growthNow(){return this.dragGrowth??c(this.geo,window.innerWidth,window.innerHeight)}place(){let e=window.innerWidth,t=window.innerHeight;if(this.open)this.geo.mode==="dock"?this.applyBox(P(this.geo,e,t),"dock"):this.applyBox(B(this.geo,e,t,this.growthNow()),"float");else{let r=this.geo.mode==="dock";this.applyBox(r?_(this.geo,e,t):f(this.geo,56,56,e,t,this.growthNow()),r?"tab":"launcher")}this.publishGeometry()}publishGeometry(){this.send({type:"host.geometry",mode:this.geo.mode,dockSide:this.geo.dockSide,growth:this.growthNow()})}themeMode(){return this.config.theme==="auto"?matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":this.config.theme}readGeometry(){try{return H(localStorage.getItem(O))??l()}catch{return l()}}writeGeometry(e){try{localStorage.setItem(O,I(e))}catch{}}async provideAuth(){let e=this.config.authTokenProvider??globalThis.matterfact?.getEmbedAuthToken;if(!e){g("no provider");return}if(++this.ac>5){g("storm cap");return}try{let t=await e();t&&this.send({type:"host.auth",token:t,expiresAt:0}),this.emit({type:"auth",phase:t?"granted":"failed"}),g(t?"token":"empty")}catch(t){this.emit({type:"auth",phase:"failed"}),g("threw",t)}}loadContext(){return this.context??(this.context=import("./chunk-C7DXO37G.js").then(e=>(e.start(this.send,this.config.origin,this.config.pageContext,this.config.pageContextProvider),this.config.theme!=="auto"&&this.send({type:"host.theme",mode:this.config.theme}),e))),this.context}flush(){let e=this.queue;this.queue=[];for(let t of e)this.send(t)}setArtifactParams(e){if(e==null||typeof e!="object"||Array.isArray(e)){console.error("[matterfact] setArtifactParams expects an object of param values");return}this.send({type:"host.artifactParams",params:e})}destroy(){window.removeEventListener("message",this.onMessage),window.removeEventListener("resize",this.onViewportResize),this.hostEl?.remove(),this.hostEl=null,this.iframe=null,this.shadow=null,this.ready=!1,this.context?.then(e=>e.stop())}};function z(o){let e=new k(o);return e.mount(),window.matterfact={...window.matterfact??{},setArtifactParams:t=>e.setArtifactParams(t)},e}var q=U();if(q){let o=()=>z(q);document.readyState==="loading"?document.addEventListener("DOMContentLoaded",o,{once:!0}):o()}
|
|
1
|
+
var y="mf-embed";function C(o,e){return{channel:y,protocol:4,...e?{id:e}:{},payload:o}}function M(o){return typeof o=="object"&&o!==null&&o.channel===y}var v=["tl","tr","bl","br"];function l(){return{mode:"dock",corner:"br",x:0,y:0,floatW:420,floatH:640,dockSide:"right",tabY:0,dockW:600}}function h(o,e,t){return Math.min(Math.max(o,e),t)}function R(o,e,t,r,n){return{x:o[1]==="l"?20:r-20-e,y:o[0]==="t"?20:n-20-t}}function p(o,e,t){return o.corner?R(o.corner,56,56,e,t):{x:h(o.x,20,Math.max(20,e-20-56)),y:h(o.y,20,Math.max(20,t-20-56))}}function c(o,e,t){if(o.corner)return o.corner;let r=p(o,e,t);return V(r.x+56/2,r.y+56/2,e,t)}function V(o,e,t,r){return`${e<r/2?"t":"b"}${o<t/2?"l":"r"}`}function $(o,e,t,r){let n=h(o,20,Math.max(20,t-20-56)),i=h(e,20,Math.max(20,r-20-56));for(let a of v){let d=R(a,56,56,t,r);if(Math.hypot(n-d.x,i-d.y)<=96)return{corner:a,x:n,y:i}}return{corner:null,x:n,y:i}}function x(o,e,t){return o<=64?"left":o+e>=t-64?"right":null}function S(o,e,t){return o.mode==="dock"?{x:o.dockSide==="left"?0:e-44,y:w(o,t)}:p(o,e,t)}function N(o,e,t,r){if(o.mode==="dock"){let a=e?Math.min(Math.max(o.dockW,320),Math.max(0,t-40)):44;return{x:o.dockSide==="left"?0:t-a,w:a}}let n=p(o,t,r);if(!e)return{x:n.x,w:56};let{w:i}=u(o.floatW,o.floatH,t,r,320,420);return{x:c(o,t,r)[1]==="r"?n.x+56-i:n.x,w:i}}function G(o,e,t,r,n,i){let a=$(o,e,n,i);if(a.corner)return{mode:"float",...a};let d=x(t,r,n);return d?{mode:"dock",dockSide:d,tabY:h(e,0,Math.max(0,i-132))}:{mode:"float",...a}}function u(o,e,t,r,n,i){return{w:Math.min(Math.max(o,n),Math.max(0,t-40)),h:Math.min(Math.max(e,i),Math.max(0,r-40))}}function T(o,e,t,r,n){let i=o[1]==="r"?-1:1,a=o[0]==="b"?-1:1;return{w:e+i*r,h:t+a*n}}function L(o,e,t){return o==="right"?e-t:e+t}function H(o){if(!o)return null;try{let e=JSON.parse(o);if(!e||e.mode!=="float"&&e.mode!=="dock")return null;let t=l(),r=(n,i)=>typeof n=="number"?n:i;return{mode:e.mode,corner:v.includes(e.corner)?e.corner:null,x:r(e.x,t.x),y:r(e.y,t.y),floatW:r(e.floatW,t.floatW),floatH:r(e.floatH,t.floatH),dockSide:e.dockSide==="left"?"left":"right",tabY:r(e.tabY,t.tabY),dockW:r(e.dockW,t.dockW)}}catch{return null}}function I(o){return JSON.stringify(o)}var s=o=>`${o}px`;function j(o,e,t,r,n,i,a){let d=n[1]==="r",b=n[0]==="b",K=d?i-(o+56):o,Y=b?a-(e+56):e,E=h(K,20,Math.max(20,i-20-t)),A=h(Y,20,Math.max(20,a-20-r));return{top:b?"auto":s(A),bottom:b?s(A):"auto",left:d?"auto":s(E),right:d?s(E):"auto",width:s(t),height:s(r)}}function w(o,e){let t=Math.max(0,e-132);return h(o.tabY||Math.round((e-132)/2),0,t)}function _(o,e,t){let r=o.dockSide==="left";return{top:s(w(o,t)),bottom:"auto",left:r?"0px":"auto",right:r?"auto":"0px",width:s(44),height:s(132)}}function f(o,e,t,r,n,i){let a=p(o,r,n);return j(a.x,a.y,e,t,i??c(o,r,n),r,n)}function W(o,e,t,r,n){let i=o.dockSide==="left";return{top:s(w(o,n)),bottom:"auto",left:i?"0px":"auto",right:i?"auto":"0px",width:s(e),height:s(t)}}function D(o,e,t,r){let n=u(o.floatW,o.floatH,e,t,320,420);return f(o,n.w,n.h,e,t,r)}function B(o,e,t){let r=Math.min(Math.max(o.dockW,320),Math.max(0,e-40)),n=o.dockSide==="left";return{top:"0px",bottom:"0px",height:"auto",left:n?"0px":"auto",right:n?"auto":"0px",width:s(r)}}var te="https://app.matterfact.com",oe="0.17.0";function F(){try{if(new URLSearchParams(location.search).get("mfdev")==="1")return!0;try{return localStorage.getItem("mfdev")==="1"}catch{return!1}}catch{return!1}}function g(o,e){F()&&console.info("[embed auth] host: "+o,e??"")}function U(){let o=document.currentScript??document.querySelector('script[data-key][src*="embed"]'),e=o?.dataset.key;if(!e)return console.error("[matterfact] missing data-key on the embed script tag"),null;let t=o?.dataset.container,r=t?document.querySelector(t):null;t&&!r&&console.error(`[matterfact] data-container="${t}" matched nothing; falling back to the corner`);let n=o?.dataset.pageContext?.toLowerCase(),i=!0;return n==="off"||n==="false"||n==="0"?i=!1:n==="declared"?i="declared":(n==="full"||n==="on"||n==="true"||n==="1")&&(i=!0),{publishableKey:e,origin:o?.dataset.origin||te,theme:o?.dataset.theme||"auto",surface:o?.dataset.surface||"",container:r,pageContext:i,shareDeeplinkFormat:o?.dataset.shareDeeplinkFormat}}var O="mf.embed.pos",k=class{constructor(e){this.config=e;this.iframe=null;this.shadow=null;this.queue=[];this.ready=!1;this.open=!1;this.geo=l();this.resizeBase=null;this.dockBase=600;this.dragFrom=null;this.dragAt={x:0,y:0};this.dragBox={x:0,w:0};this.dragGrowth=null;this.dragLeftBand=!1;this.context=null;this.hostEl=null;this.ac=0;this.onMessage=e=>{e.origin===this.config.origin&&e.source===this.iframe?.contentWindow&&M(e.data)&&this.handle(e.data.payload)};this.onViewportResize=()=>{this.place()};this.send=e=>{if(!this.ready){this.queue.push(e);return}this.iframe?.contentWindow?.postMessage(C(e),this.config.origin)};this.inline=!!e.container}mount(){let e=window;(e.matterfact??(e.matterfact={})).embedVersion=oe;let t=document.createElement("div");this.hostEl=t,t.id="matterfact-embed",this.inline?(t.style.cssText=["all: initial","position: relative","display: block","width: 100%","height: 100%","contain: layout style"].join(";"),this.config.container.appendChild(t)):(this.geo=this.readGeometry(),t.style.cssText=["all: initial","position: fixed","z-index: 2147483000","contain: layout style","transition: width .18s ease, height .18s ease, top .18s ease, right .18s ease, bottom .18s ease, left .18s ease"].join(";"),document.body.appendChild(t)),this.shadow=t.attachShadow({mode:"closed"});let r=document.createElement("style");r.textContent=":host{all:initial}iframe{border:0;display:block;width:100%;height:100%;background:transparent}"+(this.inline?"":':host([data-mode="float"]) iframe,:host([data-mode="dock"]) iframe{color-scheme:light dark}:host([data-mode="float"]) iframe{border-radius:12px;box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029}:host([data-mode="dock"]) iframe{box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029}:host([data-mode="dock"][data-flush="right"]) iframe{border-radius:12px 0 0 12px}:host([data-mode="dock"][data-flush="left"]) iframe{border-radius:0 12px 12px 0}'),this.shadow.appendChild(r);let n=document.createElement("iframe");n.title="matterfact assistant",n.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"),n.setAttribute("allow","microphone; clipboard-write; web-share"),n.src=`${this.config.origin}/embed/chat?k=${encodeURIComponent(this.config.publishableKey)}&o=${encodeURIComponent(location.origin)}`+(this.config.surface?`&s=${encodeURIComponent(this.config.surface)}`:"")+(F()||this.config.dev?"&dev=1":"")+(this.inline?"&inline=1":""),this.iframe=n,this.shadow.appendChild(n),window.addEventListener("message",this.onMessage),this.inline||(window.addEventListener("resize",this.onViewportResize),this.place());let i=this.config.shareDeeplinkFormat;i&&(this.send({type:"host.deeplinkFormat",format:i}),import("./chunk-QVNE5FQV.js").then(a=>{this.destroyed||(this.deeplinkStop=a.installDeeplinkWatch(i,d=>{this.send({type:"host.openShare",token:d})}))}).catch(()=>{}))}__testHandle(e){this.handle(e)}emit(e){let t=globalThis.matterfact?.onEvent;if(typeof t=="function")try{t(e)}catch{}}handle(e){switch(e.type){case"widget.ready":this.ready=!0,this.send({type:"host.ready",protocol:4,origin:location.origin}),this.send({type:"host.theme",mode:this.themeMode()}),this.flush(),this.inline&&this.loadContext(),this.emit({type:"ready"});break;case"widget.setOpen":if(this.emit({type:e.open?"open":"close"}),this.inline)break;this.open=e.open,this.place(),e.open&&this.loadContext();break;case"widget.setMode":if(this.inline)break;this.geo.mode=e.mode,this.writeGeometry(this.geo),this.place();break;case"widget.setDockSide":if(this.inline)break;this.geo.dockSide=e.side,this.writeGeometry(this.geo),this.place();break;case"widget.snapCorner":if(this.inline)break;this.geo.corner=e.corner,this.writeGeometry(this.geo),this.place();break;case"widget.setLauncherRegion":{if(this.inline||this.open)break;let t=window.innerWidth,r=window.innerHeight;if(e.w<=56&&e.h<=56)this.place();else{let n=this.geo.mode==="dock"?W(this.geo,e.w,e.h,t,r):f(this.geo,e.w,e.h,t,r);this.applyBox(n,e.h>56?"menu":"pill")}break}case"widget.resize":if(this.inline)break;this.hostEl&&this.open&&this.geo.mode==="float"&&(this.hostEl.style.height=`${Math.min(e.height,Math.max(0,window.innerHeight-40))}px`);break;case"widget.resizeStart":if(this.inline)break;this.resizeBase={w:this.geo.floatW,h:this.geo.floatH},this.dockBase=this.geo.dockW,this.hostEl&&(this.hostEl.style.transition="none");break;case"widget.resizeMove":{if(this.inline||!this.resizeBase)break;if(this.geo.mode==="dock"){let t=L(this.geo.dockSide,this.dockBase,e.dx);this.geo.dockW=Math.min(Math.max(t,320),Math.max(0,window.innerWidth-40))}else{let t=T(c(this.geo,window.innerWidth,window.innerHeight),this.resizeBase.w,this.resizeBase.h,e.dx,e.dy),r=u(t.w,t.h,window.innerWidth,window.innerHeight,320,420);this.geo.floatW=r.w,this.geo.floatH=r.h}this.place();break}case"widget.resizeEnd":if(this.inline)break;this.resizeBase=null,this.hostEl&&(this.hostEl.style.transition=""),this.writeGeometry(this.geo);break;case"widget.requestContext":this.loadContext().then(t=>t.provideContext());break;case"widget.pageContextMax":this.loadContext().then(t=>{t.setPageContextMax(e.mode)});break;case"widget.requestSnapshot":this.loadContext().then(t=>t.sendSnapshot(this.send));break;case"widget.readRegion":this.loadContext().then(t=>t.sendRegion(e.ref,this.send));break;case"widget.callTool":this.loadContext().then(t=>t.callTool(e.call,this.send));break;case"widget.dragStart":if(this.inline)break;this.dragFrom=S(this.geo,window.innerWidth,window.innerHeight),this.dragAt={...this.dragFrom},this.dragLeftBand=!1,this.dragGrowth=c(this.geo,window.innerWidth,window.innerHeight),this.dragBox=N(this.geo,this.open,window.innerWidth,window.innerHeight),this.hostEl&&(this.hostEl.style.transition="none");break;case"widget.dragMove":{if(this.inline||!this.dragFrom)break;let t=window.innerWidth,r=window.innerHeight;this.dragAt={x:this.dragFrom.x+e.dx,y:this.dragFrom.y+e.dy};let n=x(this.dragBox.x+e.dx,this.dragBox.w,t);n||(this.dragLeftBand=!0),n&&(this.dragLeftBand||this.geo.mode==="dock")?(this.geo.mode="dock",this.geo.dockSide=n,this.geo.tabY=Math.max(0,this.dragAt.y)):(this.geo.mode="float",this.geo.corner=null,this.geo.x=this.dragAt.x,this.geo.y=this.dragAt.y),this.place();break}case"widget.dragEnd":{if(this.inline||!this.dragFrom)break;let t=G(this.dragAt.x,this.dragAt.y,this.dragBox.x+(this.dragAt.x-this.dragFrom.x),this.dragBox.w,window.innerWidth,window.innerHeight);this.geo.mode=t.mode,t.mode==="dock"?(this.geo.dockSide=t.dockSide,this.geo.tabY=t.tabY):(this.geo.corner=t.corner,this.geo.x=t.x,this.geo.y=t.y),this.dragFrom=null,this.dragGrowth=null,this.hostEl&&(this.hostEl.style.transition=""),this.place(),this.writeGeometry(this.geo);break}case"widget.resetPos":if(this.inline)break;this.geo=l(),this.writeGeometry(this.geo),this.place();break;case"widget.hide":if(this.inline)break;this.hostEl&&(this.hostEl.style.display="none");break;case"widget.needsAuth":this.emit({type:"auth",phase:"required"}),this.provideAuth();break;case"widget.navigate":this.emit({type:"navigate",href:e.href}),this.loadContext().then(t=>t.navigateHost(e.href));break;case"widget.chat":this.emit({type:"chat",phase:e.phase,chatId:e.chatId});break;case"widget.artifactParams":this.emit({type:"artifactParams",params:e.params,source:e.source});break}}applyBox(e,t){if(!this.hostEl||this.inline)return;let r=this.hostEl.style;r.top=e.top,r.right=e.right,r.bottom=e.bottom,r.left=e.left,r.width=e.width,r.height=e.height,this.hostEl.dataset.mode=t,t==="tab"||t==="dock"?this.hostEl.dataset.flush=this.geo.dockSide:delete this.hostEl.dataset.flush}growthNow(){return this.dragGrowth??c(this.geo,window.innerWidth,window.innerHeight)}place(){let e=window.innerWidth,t=window.innerHeight;if(this.open)this.geo.mode==="dock"?this.applyBox(B(this.geo,e,t),"dock"):this.applyBox(D(this.geo,e,t,this.growthNow()),"float");else{let r=this.geo.mode==="dock";this.applyBox(r?_(this.geo,e,t):f(this.geo,56,56,e,t,this.growthNow()),r?"tab":"launcher")}this.publishGeometry()}publishGeometry(){this.send({type:"host.geometry",mode:this.geo.mode,dockSide:this.geo.dockSide,growth:this.growthNow()})}themeMode(){return this.config.theme==="auto"?matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":this.config.theme}readGeometry(){try{return H(localStorage.getItem(O))??l()}catch{return l()}}writeGeometry(e){try{localStorage.setItem(O,I(e))}catch{}}async provideAuth(){let e=this.config.authTokenProvider??globalThis.matterfact?.getEmbedAuthToken;if(!e){g("no provider");return}if(++this.ac>5){g("storm cap");return}try{let t=await e();t&&this.send({type:"host.auth",token:t,expiresAt:0}),this.emit({type:"auth",phase:t?"granted":"failed"}),g(t?"token":"empty")}catch(t){this.emit({type:"auth",phase:"failed"}),g("threw",t)}}loadContext(){return this.context??(this.context=import("./chunk-C7DXO37G.js").then(e=>(e.start(this.send,this.config.origin,this.config.pageContext,this.config.pageContextProvider),this.config.theme!=="auto"&&this.send({type:"host.theme",mode:this.config.theme}),e))),this.context}flush(){let e=this.queue;this.queue=[];for(let t of e)this.send(t)}setArtifactParams(e){if(e==null||typeof e!="object"||Array.isArray(e)){console.error("[matterfact] setArtifactParams expects an object of param values");return}this.send({type:"host.artifactParams",params:e})}destroy(){window.removeEventListener("message",this.onMessage),window.removeEventListener("resize",this.onViewportResize),this.hostEl?.remove(),this.hostEl=null,this.iframe=null,this.shadow=null,this.ready=!1,this.destroyed=!0,this.deeplinkStop?.(),this.deeplinkStop=null,this.context?.then(e=>e.stop())}};function z(o){let e=new k(o);return e.mount(),window.matterfact={...window.matterfact??{},setArtifactParams:t=>e.setArtifactParams(t)},e}var q=U();if(q){let o=()=>z(q);document.readyState==="loading"?document.addEventListener("DOMContentLoaded",o,{once:!0}):o()}
|
|
2
2
|
//# sourceMappingURL=embed.js.map
|