@declarion/embed 0.48.1 → 0.49.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 +5 -5
- package/dist/core.js +4 -4
- package/dist/declarion-embed.iife.js +1 -1
- package/dist/errors.d.ts +12 -6
- package/dist/server.js +26 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -211,7 +211,7 @@ switch. Use it to guard those.
|
|
|
211
211
|
When the Declarion deployment is redeployed while a screen is embedded, the
|
|
212
212
|
iframe does NOT show Declarion's own reload chrome (that would be wrong inside a
|
|
213
213
|
white-label host). Instead it asks the host to reload it via a `reload-required`
|
|
214
|
-
event (surfaced through `onError` with code `
|
|
214
|
+
event (surfaced through `onError` with code `embed.reload_required` and through
|
|
215
215
|
`onEvent`). The host decides: reload the iframe, show its own banner, or ignore.
|
|
216
216
|
The persistent iframe otherwise keeps its loaded schema across screen switches
|
|
217
217
|
and only re-validates it on a real version change or SSE session refresh.
|
|
@@ -222,12 +222,12 @@ A misconfiguration is surfaced loudly through `onError` (a typed
|
|
|
222
222
|
`EmbedError` with a stable `code`) **and** `console.error`. Untrusted
|
|
223
223
|
cross-origin `postMessage` frames are dropped silently. The SDK detects:
|
|
224
224
|
|
|
225
|
-
- `
|
|
226
|
-
- `
|
|
227
|
-
- `
|
|
225
|
+
- `embed.invalid_options` - a required option is missing or malformed.
|
|
226
|
+
- `embed.get_token_failed` - `getToken` rejected or returned a malformed result.
|
|
227
|
+
- `embed.handshake_timeout` - no handshake within the post-load window, usually a
|
|
228
228
|
`declarionOrigin` mismatch or framing denied by the Declarion CSP
|
|
229
229
|
(`DECLARION_FRAME_ANCESTORS` does not list the host origin).
|
|
230
|
-
- `
|
|
230
|
+
- `embed.reload_required` - the iframe asked the host to reload it.
|
|
231
231
|
|
|
232
232
|
A protocol version mismatch between this SDK and the Declarion deployment
|
|
233
233
|
produces a clear `console.warn` naming both versions.
|
package/dist/core.js
CHANGED
|
@@ -30,10 +30,10 @@ function r(e, t) {
|
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/errors.ts
|
|
32
32
|
var i = {
|
|
33
|
-
invalidOptions: "
|
|
34
|
-
getTokenFailed: "
|
|
35
|
-
handshakeTimeout: "
|
|
36
|
-
reloadRequired: "
|
|
33
|
+
invalidOptions: "embed.invalid_options",
|
|
34
|
+
getTokenFailed: "embed.get_token_failed",
|
|
35
|
+
handshakeTimeout: "embed.handshake_timeout",
|
|
36
|
+
reloadRequired: "embed.reload_required"
|
|
37
37
|
}, a = class e extends Error {
|
|
38
38
|
code;
|
|
39
39
|
constructor(t, n, r) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var DeclarionEmbed=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`declarion-embed`,n=1,r={ready:`ready`,setToken:`set-token`,tokenExpired:`token-expired`,reloadRequired:`reload-required`,resized:`resized`,navigated:`navigated`,navigationRequested:`navigation-requested`,dirtyChanged:`dirty-changed`,navigationFailed:`navigation-failed`,navigate:`navigate`,setTheme:`set-theme`};function i(e,t){if(e.origin!==t)return{kind:`rejected`};let n=e.data;if(typeof n!=`object`||!n)return{kind:`rejected`};let r=n;return r.source!==`declarion-embed`||typeof r.protocol!=`number`||typeof r.type!=`string`?{kind:`rejected`}:r.protocol===1?{kind:`valid`,message:r}:{kind:`protocol-mismatch`,received:r.protocol}}var a={invalidOptions:`
|
|
1
|
+
var DeclarionEmbed=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`declarion-embed`,n=1,r={ready:`ready`,setToken:`set-token`,tokenExpired:`token-expired`,reloadRequired:`reload-required`,resized:`resized`,navigated:`navigated`,navigationRequested:`navigation-requested`,dirtyChanged:`dirty-changed`,navigationFailed:`navigation-failed`,navigate:`navigate`,setTheme:`set-theme`};function i(e,t){if(e.origin!==t)return{kind:`rejected`};let n=e.data;if(typeof n!=`object`||!n)return{kind:`rejected`};let r=n;return r.source!==`declarion-embed`||typeof r.protocol!=`number`||typeof r.type!=`string`?{kind:`rejected`}:r.protocol===1?{kind:`valid`,message:r}:{kind:`protocol-mismatch`,received:r.protocol}}var a={invalidOptions:`embed.invalid_options`,getTokenFailed:`embed.get_token_failed`,handshakeTimeout:`embed.handshake_timeout`,reloadRequired:`embed.reload_required`},o=class e extends Error{code;constructor(t,n,r){super(n,r),this.name=`EmbedError`,this.code=t,Object.setPrototypeOf(this,e.prototype)}},s=`embed`,c=`parent_origin`,l=`theme`,u=`1`;function d(e){let t=new URL(e.route,e.declarionOrigin);return t.searchParams.set(s,u),t.searchParams.set(c,e.parentOrigin),t.searchParams.set(`nav`,e.navigation),e.theme&&t.searchParams.set(l,e.theme),t.toString()}function f(e,t){try{let n=new URL(t,e);return n.pathname+n.search+n.hash}catch{return t}}var p=2e4,m=`Declarion embedded screen`,h=[`allow-scripts`,`allow-same-origin`,`allow-forms`,`allow-modals`,`allow-downloads`,`allow-popups`,`allow-popups-to-escape-sandbox`],g=new Set([`allow-top-navigation`,`allow-top-navigation-by-user-activation`]);function _(e){let t=new Set(h);for(let n of e??[]){let e=n.trim();e&&!g.has(e)&&t.add(e)}return Array.from(t).join(` `)}var v=`[declarion-embed]`;function y(e){let t=e=>new o(a.invalidOptions,e);if(!e.container||typeof e.container.appendChild!=`function`)return t("`container` must be a DOM element that can receive the iframe.");if(typeof e.declarionOrigin!=`string`||e.declarionOrigin===``)return t('`declarionOrigin` is required and must be the exact origin of the Declarion deployment, e.g. "https://app.example.com".');let n;try{n=new URL(e.declarionOrigin)}catch{return t(`\`declarionOrigin\` is not a valid URL: "${e.declarionOrigin}". Pass an exact origin, e.g. "https://app.example.com".`)}return n.origin===e.declarionOrigin?typeof e.route!=`string`||e.route===``?t("`route` is required and must be a Declarion screen route."):typeof e.getToken==`function`?null:t("`getToken` is required and must be an async function returning { token, expires_at }."):t(`\`declarionOrigin\` must be exactly an origin with no path, query, or trailing slash. Got "${e.declarionOrigin}"; expected "${n.origin}".`)}function b(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.token==`string`&&t.token!==``&&typeof t.expires_at==`string`&&t.expires_at!==``}function x(e){let t=!1,n=null,s=null,c=null,l=!1,u=!1,h=null,g=(t,n)=>{e.debug&&(n===void 0?console.info(`${v} ${t}`):console.info(`${v} ${t}`,n))},x=(t,n,r)=>{let i=new o(t,n,r?{cause:r}:void 0);console.error(`${v} ${n}`,r??``),e.onError?.(i)},S=(t,r)=>{if(!n?.contentWindow)return;let i={source:`declarion-embed`,protocol:1,type:t,payload:r};g(`-> iframe ${t}`,r),n.contentWindow.postMessage(i,e.declarionOrigin)},C=async()=>{let n;try{n=await e.getToken()}catch(e){x(a.getTokenFailed,"`getToken` rejected. The host backend must mint a token via auth.create_embed_session and return { token, expires_at }.",e);return}if(!b(n)){x(a.getTokenFailed,"`getToken` resolved to a malformed value. Expected { token: string, expires_at: string }.");return}if(t)return;let i={token:n.token,expires_at:n.expires_at};S(r.setToken,i),N()},w=t=>{S(r.navigate,{route:f(e.declarionOrigin,t)})},T=()=>{if(l=!0,P(),g(`<- iframe ready`),e.onEvent?.({type:r.ready}),C(),h!==null){let e=h;h=null,w(e)}},E=()=>{g(`<- iframe token-expired`),e.onEvent?.({type:r.tokenExpired}),C()},D=t=>{g(`<- iframe reload-required`,t),e.onEvent?.({type:r.reloadRequired,payload:t}),x(a.reloadRequired,`The embedded iframe requested a reload: ${t.reason}. Reload the iframe to recover.`)},O=t=>{g(`<- iframe resized`,t),!e.height&&n&&Number.isFinite(t.height)&&t.height>0&&(n.style.height=`${t.height}px`),e.onEvent?.({type:r.resized,payload:t})},k=t=>{g(`<- iframe dirty-changed`,t),e.onEvent?.({type:r.dirtyChanged,payload:t})},A=(t,n)=>{g(`<- iframe ${t}`,n),e.onEvent?.({type:t,payload:n});let i={mode:t===r.navigated?`self`:`delegated`,route:n.route,screenCode:n.screenCode,entity:n.entity,recordId:n.recordId};e.onNavigate?.(i)},j=new Set([`not_found`,`unresolved`]),M=t=>{if(typeof t?.route!=`string`||t.route.length===0||!j.has(t.reason)){g(`<- iframe navigation-failed (dropped: invalid payload)`,t);return}g(`<- iframe navigation-failed`,t),e.onEvent?.({type:r.navigationFailed,payload:t}),e.onNavigationFailed?.(t)},N=()=>{u||(u=!0,g(`handshake complete - iframe authenticated`),e.onReady?.())},P=()=>{c!==null&&(clearTimeout(c),c=null)},F=t=>{if(!n||t.source!==n.contentWindow)return;let a=i(t,e.declarionOrigin);if(a.kind===`rejected`)return;if(a.kind===`protocol-mismatch`){console.warn(`${v} protocol version mismatch: this SDK speaks protocol 1, the iframe reported protocol ${String(a.received)}. Update @declarion/embed and the Declarion deployment to matching versions.`);return}let{message:o}=a;switch(o.type){case r.ready:T();break;case r.tokenExpired:E();break;case r.reloadRequired:D(o.payload);break;case r.resized:O(o.payload);break;case r.navigated:A(r.navigated,o.payload);break;case r.navigationRequested:A(r.navigationRequested,o.payload);break;case r.dirtyChanged:k(o.payload);break;case r.navigationFailed:M(o.payload)}},I=y(e);if(I)return console.error(`${v} ${I.message}`),e.onError?.(I),{navigate:()=>void 0,setTheme:()=>void 0,destroy:()=>void 0};let L=e.navigation??`self`,R=window.location.origin,z;try{z=d({declarionOrigin:e.declarionOrigin,route:e.route,parentOrigin:R,navigation:L,theme:e.theme})}catch(t){return x(a.invalidOptions,`Could not build the iframe URL from route "${e.route}". \`route\` must be a valid Declarion screen route.`,t),{navigate:()=>void 0,setTheme:()=>void 0,destroy:()=>void 0}}return n=document.createElement(`iframe`),n.src=z,n.title=e.title??m,n.setAttribute(`sandbox`,_(e.extraSandbox)),n.style.width=`100%`,n.style.border=`0`,n.style.display=`block`,e.height?n.style.height=e.height:n.style.height||(n.style.height=`150px`),s=F,window.addEventListener(`message`,s),e.container.appendChild(n),g(`iframe created`,{src:z}),c=setTimeout(()=>{c=null,!l&&x(a.handshakeTimeout,`No handshake from the iframe within ${p}ms. Check that \`declarionOrigin\` exactly matches the Declarion deployment origin, that the host origin is allow-listed in the deployment's DECLARION_FRAME_ANCESTORS, and that \`route\` resolves to a real screen.`)},p),{navigate(e){if(!t){if(!l){h=e;return}w(e)}},setTheme(e){t||S(r.setTheme,{theme:e})},destroy(){t||(t=!0,P(),s&&=(window.removeEventListener(`message`,s),null),n?.parentNode&&n.parentNode.removeChild(n),n=null,g(`embed destroyed`))}}}return e.EMBED_ERROR_CODES=a,e.EMBED_MESSAGE_SOURCE=t,e.EMBED_MESSAGE_TYPES=r,e.EMBED_PROTOCOL_VERSION=n,e.EmbedError=o,e.createDeclarionEmbed=x,e})({});
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Stable, machine-readable embed error codes
|
|
3
|
-
*
|
|
2
|
+
* Stable, machine-readable embed error codes, in the platform's one spelling
|
|
3
|
+
* (`<owner>.<name>`) so a reader never meets two conventions.
|
|
4
|
+
*
|
|
5
|
+
* These are the embed LIBRARY's own failure classes, raised in the host page
|
|
6
|
+
* for conditions the platform never sees - bad options, a rejected getToken, a
|
|
7
|
+
* handshake that never completed. They are not declared platform error types
|
|
8
|
+
* and no catalogue resolves a title for them; the strings here are the whole
|
|
9
|
+
* contract, and a host may branch on `error.code`.
|
|
4
10
|
*/
|
|
5
11
|
export declare const EMBED_ERROR_CODES: {
|
|
6
12
|
/**
|
|
@@ -8,12 +14,12 @@ export declare const EMBED_ERROR_CODES: {
|
|
|
8
14
|
* (`container`, `declarionOrigin`, `route`, `getToken`). Raised
|
|
9
15
|
* synchronously before the iframe is created.
|
|
10
16
|
*/
|
|
11
|
-
readonly invalidOptions: "
|
|
17
|
+
readonly invalidOptions: "embed.invalid_options";
|
|
12
18
|
/**
|
|
13
19
|
* The host's `getToken` callback rejected, threw, or resolved to a value
|
|
14
20
|
* that is not `{ token: string, expires_at: string }`.
|
|
15
21
|
*/
|
|
16
|
-
readonly getTokenFailed: "
|
|
22
|
+
readonly getTokenFailed: "embed.get_token_failed";
|
|
17
23
|
/**
|
|
18
24
|
* No `ready` frame arrived from the iframe within the post-mount timeout.
|
|
19
25
|
* The usual causes are a `declarionOrigin` mismatch (the iframe loaded a
|
|
@@ -21,12 +27,12 @@ export declare const EMBED_ERROR_CODES: {
|
|
|
21
27
|
* (the host origin is not in `DECLARION_FRAME_ANCESTORS`). A slow `getToken`
|
|
22
28
|
* does NOT cause this - the timer is cleared as soon as `ready` arrives.
|
|
23
29
|
*/
|
|
24
|
-
readonly handshakeTimeout: "
|
|
30
|
+
readonly handshakeTimeout: "embed.handshake_timeout";
|
|
25
31
|
/**
|
|
26
32
|
* The iframe asked the host to reload it (`reload-required`) - asset drift
|
|
27
33
|
* or a terminal auth failure inside the iframe.
|
|
28
34
|
*/
|
|
29
|
-
readonly reloadRequired: "
|
|
35
|
+
readonly reloadRequired: "embed.reload_required";
|
|
30
36
|
};
|
|
31
37
|
/** The union of all embed error code strings. */
|
|
32
38
|
export type EmbedErrorCode = (typeof EMBED_ERROR_CODES)[keyof typeof EMBED_ERROR_CODES];
|
package/dist/server.js
CHANGED
|
@@ -7,40 +7,45 @@ var e = class e extends Error {
|
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
9
|
function t(e) {
|
|
10
|
+
if (!e || e === "about:blank") return null;
|
|
11
|
+
let t = e.lastIndexOf("/");
|
|
12
|
+
return t >= 0 ? e.slice(t + 1) : e;
|
|
13
|
+
}
|
|
14
|
+
function n(e) {
|
|
10
15
|
if (typeof e != "object" || !e) return !1;
|
|
11
16
|
let t = e;
|
|
12
17
|
return typeof t.token == "string" && t.token !== "" && typeof t.expires_at == "string" && t.expires_at !== "";
|
|
13
18
|
}
|
|
14
|
-
async function
|
|
15
|
-
let
|
|
16
|
-
tenant_code:
|
|
17
|
-
user_email:
|
|
18
|
-
screen_code:
|
|
19
|
-
ttl_seconds:
|
|
20
|
-
},
|
|
19
|
+
async function r(r) {
|
|
20
|
+
let i = `${new URL(r.declarionOrigin).origin}/api/actions/auth.create_embed_session`, a = {
|
|
21
|
+
tenant_code: r.tenantCode,
|
|
22
|
+
user_email: r.userEmail,
|
|
23
|
+
screen_code: r.screenCode,
|
|
24
|
+
ttl_seconds: r.ttlSeconds ?? 600
|
|
25
|
+
}, o = await fetch(i, {
|
|
21
26
|
method: "POST",
|
|
22
27
|
headers: {
|
|
23
28
|
"content-type": "application/json",
|
|
24
|
-
authorization: `Bearer ${
|
|
29
|
+
authorization: `Bearer ${r.apiKey}`
|
|
25
30
|
},
|
|
26
|
-
body: JSON.stringify(
|
|
27
|
-
signal:
|
|
28
|
-
}),
|
|
31
|
+
body: JSON.stringify(a),
|
|
32
|
+
signal: r.signal
|
|
33
|
+
}), s;
|
|
29
34
|
try {
|
|
30
|
-
|
|
35
|
+
s = await o.json();
|
|
31
36
|
} catch {
|
|
32
|
-
throw new e(`auth.create_embed_session returned a non-JSON response (HTTP ${
|
|
37
|
+
throw new e(`auth.create_embed_session returned a non-JSON response (HTTP ${o.status}).`, o.status, null);
|
|
33
38
|
}
|
|
34
|
-
if (!
|
|
35
|
-
let
|
|
36
|
-
throw new e(
|
|
39
|
+
if (!o.ok) {
|
|
40
|
+
let n = s, r = t(n.type);
|
|
41
|
+
throw new e(n.title ?? `auth.create_embed_session failed with HTTP ${o.status}.`, o.status, r);
|
|
37
42
|
}
|
|
38
|
-
let
|
|
39
|
-
if (!
|
|
43
|
+
let c = s.result;
|
|
44
|
+
if (!n(c)) throw new e("auth.create_embed_session returned a malformed result: expected { token, expires_at }.", o.status, null);
|
|
40
45
|
return {
|
|
41
|
-
token:
|
|
42
|
-
expires_at:
|
|
46
|
+
token: c.token,
|
|
47
|
+
expires_at: c.expires_at
|
|
43
48
|
};
|
|
44
49
|
}
|
|
45
50
|
//#endregion
|
|
46
|
-
export { e as EmbedSessionError,
|
|
51
|
+
export { e as EmbedSessionError, r as createEmbedSession };
|