@kadanza/extension-sdk 0.0.14 → 0.1.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 CHANGED
@@ -56,6 +56,7 @@ const sdk: IExtensionSDK = createExtensionSDK();
56
56
 
57
57
  // Handshake flow
58
58
  const { authToken, extensionDetails, designTokens } = await sdk.connect();
59
+ // Fields are null when that host surface omitted them.
59
60
  ```
60
61
 
61
62
  Create and use the SDK once per app. Later `createExtensionSDK()` calls return the same instance.
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){if(e.startsWith(`/`))return e;let t=new URL(e);return t.hostname=`api.${t.hostname}`,t.origin}function t(t,n){if(!n.startsWith(`/`)||n.startsWith(`//`))throw Error(`API endpoint must be a root-relative path starting with a single "/".`);return`${e(t).replace(/\/+$/,``)}${n}`}async function n(e,n,r={}){let i=new Headers(r.headers);i.has(`Content-Type`)||i.set(`Content-Type`,`application/json`),i.set(`Authorization`,`Bearer ${n.authTokenJwt}`),i.set(`X-Tenant`,n.tenantDomain);let a=await fetch(t(n.baseUrl,e),{...r,headers:i});if(!a.ok){let e=a.statusText?` ${a.statusText}`:``;throw Error(`API call failed: ${a.status}${e}.`)}return await a.json()}var r={handshakeInit:`HANDSHAKE_INIT`,handshakeAck:`HANDSHAKE_ACK`,requestAuthTokenRefresh:`REQUEST_TOKEN_REFRESH`,authTokenRefresh:`TOKEN_REFRESH`,loadPageSettings:`LOAD_PAGE_SETTINGS`,updatePageSettings:`UPDATE_PAGE_SETTINGS`,pageSettingsUpdated:`PAGE_SETTINGS_UPDATED`,navigationChange:`NAVIGATION_CHANGE`};function i(e,t,n){if(!window.parent||window.parent===window)throw Error(`Extension is not running inside a parent frame.`);let r=n===void 0?{type:e}:{type:e,payload:n};window.parent.postMessage(r,t)}function a(e,t){let n=n=>{n.origin===e&&(!n.data||typeof n.data!=`object`||!(`type`in n.data)||t(n))};return window.addEventListener(`message`,n),()=>window.removeEventListener(`message`,n)}var o=new Set([`localhost`,`127.0.0.1`,`[::1]`]),s=class extends Error{constructor(e){super(e),this.name=`InvalidOriginError`}};function c(e){if(!e)throw new s(`Missing "tenantUrl" search parameter required to establish parent origin.`);let t;try{t=new URL(e)}catch{throw new s(`Invalid tenantUrl: "${e}".`)}let n=o.has(t.hostname),r=t.protocol===`https:`,i=t.protocol===`http:`&&n;if(!r&&!i)throw new s(`tenantUrl must use https (or http on localhost). Received: "${t.protocol}//${t.host}".`);return t.origin}function l(e=window.location.search){return new URLSearchParams(e).get(`tenantUrl`)}var u=1e4,d=12e4,f=3e4,p=2147483647,m=class{#e=null;#t=null;#n=null;#r=!1;#i=!1;#a=null;#o=null;#s=null;#c=null;#l=!1;#u=d;#d=null;#f=!1;#p=null;#m=null;#h=null;#g=null;#_=new Set;#v=new Set;get isConnected(){return this.#r}async connect(e={}){if(this.#w(),this.#r&&this.#a&&this.#o&&this.#s)return this.#E(e),this.#A(),{authToken:this.#a,extensionDetails:this.#o,designTokens:this.#s,pageSettings:this.#c};if(this.#m)return this.#m;this.#E(e);let t=l();this.#e=c(t),this.#t=t,this.#n?.(),this.#n=a(this.#e,e=>this.#y(e));let n=e.timeoutMs??u,o=new Promise((e,t)=>{let r=setTimeout(()=>{this.#p=null,this.#m=null,t(Error(`Handshake timed out after ${n}ms.`))},n);this.#p={resolve:e,reject:t,timer:r}});this.#m=o;try{i(r.handshakeInit,this.#e)}catch(e){throw this.#N(this.#p),this.#p=null,this.#m=null,this.#n?.(),this.#n=null,this.#e=null,this.#t=null,this.#k(),e}return o}destroy(){this.#i=!0,this.#r=!1,this.#k(),this.#l=!1,this.#f=!1,this.#n?.(),this.#n=null,this.#e=null,this.#t=null,this.#F(this.#p,Error(`SDK destroyed.`)),this.#F(this.#h,Error(`SDK destroyed.`)),this.#F(this.#g,Error(`SDK destroyed.`)),this.#p=null,this.#m=null,this.#h=null,this.#g=null,this.#_.clear(),this.#v.clear(),this.#a=null,this.#o=null,this.#s=null,this.#c=null}getAuthToken(){return this.#a}getExtensionDetails(){return this.#o}getDesignTokens(){return this.#s}getPageSettings(){return this.#c}getAllowedOrigin(){return this.#e}getTenantUrl(){return this.#t}getApiUrl(){return this.#o?e(this.#o.baseUrl):null}async apiCall(e,t={}){return this.#T(),n(e,{baseUrl:this.#o.baseUrl,tenantDomain:this.#o.tenantDomain,authTokenJwt:this.#a.jwt},t)}async emitRequestAuthTokenRefresh(e={}){if(this.#T(),this.#h)throw Error(`Auth token refresh already in progress.`);let t=e.timeoutMs??u,n=this.#e,a=new Promise((e,n)=>{let r=setTimeout(()=>{this.#h=null,n(Error(`Auth token refresh timed out after ${t}ms.`))},t);this.#h={resolve:e,reject:n,timer:r}});return i(r.requestAuthTokenRefresh,n),a}async emitUpdatePageSettings(e,t={}){if(this.#T(),this.#g)throw Error(`Page settings update already in progress.`);let n=t.timeoutMs??u,a=this.#e,o=new Promise((e,t)=>{let r=setTimeout(()=>{this.#g=null,t(Error(`Page settings update timed out after ${n}ms.`))},n);this.#g={resolve:e,reject:t,timer:r}});return i(r.updatePageSettings,a,e),o}onLoadPageSettings(e){return this.#_.add(e),()=>{this.#_.delete(e)}}onAuthTokenRefresh(e){return this.#v.add(e),()=>{this.#v.delete(e)}}emitNavigationChange(e){this.#T(),i(r.navigationChange,this.#e,e)}#y(e){let{type:t,payload:n}=e.data;switch(t){case r.handshakeAck:this.#b(n);break;case r.authTokenRefresh:this.#x(n);break;case r.loadPageSettings:this.#S(n);break;case r.pageSettingsUpdated:this.#C(n);break;default:break}}#b(e){if(!e?.authToken||!e.extensionDetails||!e.designTokens){this.#F(this.#p,Error(`Invalid HANDSHAKE_ACK payload.`)),this.#p=null,this.#m=null;return}this.#a=e.authToken,this.#o=e.extensionDetails,this.#s=e.designTokens,this.#c=e.pageSettings??null,this.#r=!0;let t=this.#p;this.#p=null,this.#m=null,this.#P(t,{authToken:this.#a,extensionDetails:this.#o,designTokens:this.#s,pageSettings:this.#c}),this.#f=!1,this.#A()}#x(e){if(!e?.authToken){this.#F(this.#h,Error(`Invalid TOKEN_REFRESH payload.`)),this.#h=null;return}this.#a=e.authToken;let t=this.#h;this.#h=null,this.#P(t,this.#a);for(let e of this.#v)e(this.#a);this.#f=!1,this.#A()}#S(e){let t=e??{};this.#c=t;for(let e of this.#_)e(t)}#C(e){if(!e||typeof e.success!=`boolean`){this.#F(this.#g,Error(`Invalid PAGE_SETTINGS_UPDATED payload.`)),this.#g=null;return}let t=this.#g;this.#g=null,this.#P(t,e)}#w(){if(this.#i)throw Error(`SDK has been destroyed.`)}#T(){if(this.#w(),!this.#r||!this.#e)throw Error(`SDK is not connected. Call connect() first.`)}#E(e){if(e.authTokenBufferMs!==void 0&&(!Number.isFinite(e.authTokenBufferMs)||e.authTokenBufferMs<0))throw Error(`authTokenBufferMs must be a finite non-negative number.`);this.#l=e.authTokenAutoRefresh===!0,this.#u=e.authTokenBufferMs??d,this.#f=!1,this.#k()}#D(e){let t=Number(e.expires);return Number.isFinite(t)?t*1e3:null}#O(e){let t=this.#D(e);return t===null?null:Math.max(0,t-this.#u-Date.now())}#k(){this.#d!==null&&(clearTimeout(this.#d),this.#d=null)}#A(){if(this.#k(),!this.#l||!this.#r||this.#i||!this.#a)return;let e=this.#O(this.#a);if(e===null)return;let t=Math.min(e,p);this.#d=setTimeout(()=>{this.#d=null,this.#j()},t)}async#j(){if(!this.#l||!this.#r||this.#i||!this.#a)return;let e=this.#a,t=this.#O(e);if(t!==null){if(t>0){this.#A();return}if(this.#h){this.#M(e);return}try{await this.emitRequestAuthTokenRefresh()}catch{this.#M(e)}}}#M(e){if(!this.#l||!this.#r||this.#i||this.#a?.jwt!==e.jwt||this.#f)return;let t=this.#D(e);if(t===null)return;let n=t-Date.now();if(n<=0)return;this.#f=!0,this.#k();let r=Math.min(f,n);this.#d=setTimeout(()=>{this.#d=null,this.#j()},r)}#N(e){e&&clearTimeout(e.timer)}#P(e,t){e&&(clearTimeout(e.timer),e.resolve(t))}#F(e,t){e&&(clearTimeout(e.timer),e.reject(t))}};function h(e,t,n,r){let i=r===void 0?{type:n}:{type:n,payload:r};e.postMessage(i,t)}function g(e,t){let n=n=>{n.origin===e&&(!n.data||typeof n.data!=`object`||!(`type`in n.data)||t(n))};return window.addEventListener(`message`,n),()=>window.removeEventListener(`message`,n)}var _=class{#e;#t=null;#n=!1;#r=!1;constructor(e){this.#e=e}start(){this.#r||this.#n||(this.#t=g(this.#e.origin,e=>{this.#i(e)}),this.#n=!0)}destroy(){this.#r=!0,this.#n=!1,this.#t?.(),this.#t=null}emitLoadPageSettings(e){this.#u(),this.#l(r.loadPageSettings,e)}async#i(e){if(this.#r)return;let{type:t,payload:n}=e.data;switch(t){case r.handshakeInit:await this.#a();break;case r.requestAuthTokenRefresh:await this.#o();break;case r.updatePageSettings:await this.#s(n);break;default:break}}async#a(){try{let e=await this.#e.resolveHandshakePayload();if(this.#r)return;this.#l(r.handshakeAck,e)}catch{}}async#o(){try{let e=await this.#e.resolveAuthToken();if(this.#r)return;this.#l(r.authTokenRefresh,{authToken:e})}catch{}}async#s(e){let t=e?.settings;if(!t||typeof t!=`object`){this.#c(!1);return}let n=this.#e.onUpdatePageSettings;if(!n){this.#c(!1);return}try{let e=await n(t);if(this.#r)return;this.#c(!!e)}catch{if(this.#r)return;this.#c(!1)}}#c(e){let t={success:e};this.#l(r.pageSettingsUpdated,t)}#l(e,t){let n=this.#e.getContentWindow();n&&h(n,this.#e.origin,e,t)}#u(){if(this.#r)throw Error(`ExtensionSDKHost has been destroyed.`)}};function v(e,t){if(typeof e!=`string`)return!1;try{let n=new URL(e),r=n.protocol===`https:`,i=!t||t(n.origin);return r&&i}catch{return!1}}function y(e,t=window.location.origin){if(typeof e!=`string`)return null;try{let n=new URL(e);return typeof t==`string`&&n.searchParams.set(`tenantUrl`,t),n}catch{return null}}var b=null,x=!1,S=class extends m{destroy(){super.destroy(),b===this&&(b=null,x=!1)}},C=()=>b?(x||=(console.warn(`[@kadanza/extension-sdk] createExtensionSDK() was called more than once. The SDK is a singleton — reuse the same instance for the whole app.`),!0),b):(b=new S,b);exports.CONNECTION_EVENTS=r,exports.ExtensionSDK=m,exports.ExtensionSDKHost=_,exports.InvalidOriginError=s,exports.createExtensionSDK=C,exports.enrichExtensionUrl=y,exports.isValidExtensionUrl=v,exports.postToChild=h,exports.readTenantUrlFromLocation=l,exports.resolveAllowedOrigin=c,exports.subscribeToChildMessages=g;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){if(e.startsWith(`/`))return e;let t=new URL(e);return t.hostname=`api.${t.hostname}`,t.origin}function t(t,n){if(!n.startsWith(`/`)||n.startsWith(`//`))throw Error(`API endpoint must be a root-relative path starting with a single "/".`);return`${e(t).replace(/\/+$/,``)}${n}`}async function n(e,n,r={}){let i=new Headers(r.headers);i.has(`Content-Type`)||i.set(`Content-Type`,`application/json`),i.set(`Authorization`,`Bearer ${n.authTokenJwt}`),i.set(`X-Tenant`,n.tenantDomain);let a=await fetch(t(n.baseUrl,e),{...r,headers:i});if(!a.ok){let e=a.statusText?` ${a.statusText}`:``;throw Error(`API call failed: ${a.status}${e}.`)}return await a.json()}var r={handshakeInit:`HANDSHAKE_INIT`,handshakeAck:`HANDSHAKE_ACK`,requestAuthTokenRefresh:`REQUEST_TOKEN_REFRESH`,authTokenRefresh:`TOKEN_REFRESH`,loadPageSettings:`LOAD_PAGE_SETTINGS`,updatePageSettings:`UPDATE_PAGE_SETTINGS`,pageSettingsUpdated:`PAGE_SETTINGS_UPDATED`,navigationChange:`NAVIGATION_CHANGE`};function i(e,t,n){if(!window.parent||window.parent===window)throw Error(`Extension is not running inside a parent frame.`);let r=n===void 0?{type:e}:{type:e,payload:n};window.parent.postMessage(r,t)}function a(e,t){let n=n=>{n.origin===e&&(!n.data||typeof n.data!=`object`||!(`type`in n.data)||t(n))};return window.addEventListener(`message`,n),()=>window.removeEventListener(`message`,n)}var o=new Set([`localhost`,`127.0.0.1`,`[::1]`]),s=class extends Error{constructor(e){super(e),this.name=`InvalidOriginError`}};function c(e){if(!e)throw new s(`Missing "tenantUrl" search parameter required to establish parent origin.`);let t;try{t=new URL(e)}catch{throw new s(`Invalid tenantUrl: "${e}".`)}let n=o.has(t.hostname),r=t.protocol===`https:`,i=t.protocol===`http:`&&n;if(!r&&!i)throw new s(`tenantUrl must use https (or http on localhost). Received: "${t.protocol}//${t.host}".`);return t.origin}function l(e=window.location.search){return new URLSearchParams(e).get(`tenantUrl`)}function u(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var d=1e4,f=12e4,p=3e4,m=2147483647,h=class{#e=null;#t=null;#n=null;#r=!1;#i=!1;#a=null;#o=null;#s=null;#c=null;#l=!1;#u=f;#d=null;#f=!1;#p=null;#m=null;#h=null;#g=null;#_=new Set;#v=new Set;get isConnected(){return this.#r}async connect(e={}){if(this.#T(),this.#r)return this.#D(e),this.#j(),this.#b();if(this.#m)return this.#m;this.#D(e);let t=l();this.#e=c(t),this.#t=t,this.#n?.(),this.#n=a(this.#e,e=>this.#y(e));let n=e.timeoutMs??d,o=new Promise((e,t)=>{let r=setTimeout(()=>{this.#p=null,this.#m=null,t(Error(`Handshake timed out after ${n}ms.`))},n);this.#p={resolve:e,reject:t,timer:r}});this.#m=o;try{i(r.handshakeInit,this.#e)}catch(e){throw this.#P(this.#p),this.#p=null,this.#m=null,this.#n?.(),this.#n=null,this.#e=null,this.#t=null,this.#A(),e}return o}destroy(){this.#i=!0,this.#r=!1,this.#A(),this.#l=!1,this.#f=!1,this.#n?.(),this.#n=null,this.#e=null,this.#t=null,this.#I(this.#p,Error(`SDK destroyed.`)),this.#I(this.#h,Error(`SDK destroyed.`)),this.#I(this.#g,Error(`SDK destroyed.`)),this.#p=null,this.#m=null,this.#h=null,this.#g=null,this.#_.clear(),this.#v.clear(),this.#a=null,this.#o=null,this.#s=null,this.#c=null}getAuthToken(){return this.#a}getExtensionDetails(){return this.#o}getDesignTokens(){return this.#s}getPageSettings(){return this.#c}getAllowedOrigin(){return this.#e}getTenantUrl(){return this.#t}getApiUrl(){let t=this.#o?.baseUrl;if(!t)return null;try{return e(t)}catch{return null}}async apiCall(e,t={}){this.#E();let r=this.#o?.baseUrl,i=this.#o?.tenantDomain,a=this.#a?.jwt;if(!r||!i||!a)throw Error(`API calls require handshake authToken and extensionDetails (baseUrl, tenantDomain).`);return n(e,{baseUrl:r,tenantDomain:i,authTokenJwt:a},t)}async emitRequestAuthTokenRefresh(e={}){if(this.#E(),this.#h)throw Error(`Auth token refresh already in progress.`);let t=e.timeoutMs??d,n=this.#e,a=new Promise((e,n)=>{let r=setTimeout(()=>{this.#h=null,n(Error(`Auth token refresh timed out after ${t}ms.`))},t);this.#h={resolve:e,reject:n,timer:r}});return i(r.requestAuthTokenRefresh,n),a}async emitUpdatePageSettings(e,t={}){if(this.#E(),this.#g)throw Error(`Page settings update already in progress.`);let n=t.timeoutMs??d,a=this.#e,o=new Promise((e,t)=>{let r=setTimeout(()=>{this.#g=null,t(Error(`Page settings update timed out after ${n}ms.`))},n);this.#g={resolve:e,reject:t,timer:r}});return i(r.updatePageSettings,a,e),o}onLoadPageSettings(e){return this.#_.add(e),()=>{this.#_.delete(e)}}onAuthTokenRefresh(e){return this.#v.add(e),()=>{this.#v.delete(e)}}emitNavigationChange(e){this.#E(),i(r.navigationChange,this.#e,e)}#y(e){let{type:t,payload:n}=e.data;switch(t){case r.handshakeAck:this.#x(n);break;case r.authTokenRefresh:this.#S(n);break;case r.loadPageSettings:this.#C(n);break;case r.pageSettingsUpdated:this.#w(n);break;default:break}}#b(){return{authToken:this.#a,extensionDetails:this.#o,designTokens:this.#s,pageSettings:this.#c}}#x(e){this.#a=u(e?.authToken)?e?.authToken:null,this.#o=u(e?.extensionDetails)?e?.extensionDetails:null,this.#s=u(e?.designTokens)?e?.designTokens:null,this.#c=u(e?.pageSettings)?e?.pageSettings:null,this.#r=!0;let t=this.#p;this.#p=null,this.#m=null,this.#F(t,this.#b()),this.#f=!1,this.#j()}#S(e){if(!e?.authToken){this.#I(this.#h,Error(`Invalid TOKEN_REFRESH payload.`)),this.#h=null;return}this.#a=e.authToken;let t=this.#h;this.#h=null,this.#F(t,this.#a);for(let e of this.#v)e(this.#a);this.#f=!1,this.#j()}#C(e){let t=e??{};this.#c=t;for(let e of this.#_)e(t)}#w(e){if(!e||typeof e.success!=`boolean`){this.#I(this.#g,Error(`Invalid PAGE_SETTINGS_UPDATED payload.`)),this.#g=null;return}let t=this.#g;this.#g=null,this.#F(t,e)}#T(){if(this.#i)throw Error(`SDK has been destroyed.`)}#E(){if(this.#T(),!this.#r||!this.#e)throw Error(`SDK is not connected. Call connect() first.`)}#D(e){if(e.authTokenBufferMs!==void 0&&(!Number.isFinite(e.authTokenBufferMs)||e.authTokenBufferMs<0))throw Error(`authTokenBufferMs must be a finite non-negative number.`);this.#l=e.authTokenAutoRefresh===!0,this.#u=e.authTokenBufferMs??f,this.#f=!1,this.#A()}#O(e){let t=Number(e.expires);return Number.isFinite(t)?t*1e3:null}#k(e){let t=this.#O(e);return t===null?null:Math.max(0,t-this.#u-Date.now())}#A(){this.#d!==null&&(clearTimeout(this.#d),this.#d=null)}#j(){if(this.#A(),!this.#l||!this.#r||this.#i||!this.#a)return;let e=this.#k(this.#a);if(e===null)return;let t=Math.min(e,m);this.#d=setTimeout(()=>{this.#d=null,this.#M()},t)}async#M(){if(!this.#l||!this.#r||this.#i||!this.#a)return;let e=this.#a,t=this.#k(e);if(t!==null){if(t>0){this.#j();return}if(this.#h){this.#N(e);return}try{await this.emitRequestAuthTokenRefresh()}catch{this.#N(e)}}}#N(e){if(!this.#l||!this.#r||this.#i||this.#a?.jwt!==e.jwt||this.#f)return;let t=this.#O(e);if(t===null)return;let n=t-Date.now();if(n<=0)return;this.#f=!0,this.#A();let r=Math.min(p,n);this.#d=setTimeout(()=>{this.#d=null,this.#M()},r)}#P(e){e&&clearTimeout(e.timer)}#F(e,t){e&&(clearTimeout(e.timer),e.resolve(t))}#I(e,t){e&&(clearTimeout(e.timer),e.reject(t))}};function g(e,t,n,r){let i=r===void 0?{type:n}:{type:n,payload:r};e.postMessage(i,t)}function _(e,t){let n=n=>{n.origin===e&&(!n.data||typeof n.data!=`object`||!(`type`in n.data)||t(n))};return window.addEventListener(`message`,n),()=>window.removeEventListener(`message`,n)}var v=class{#e;#t=null;#n=!1;#r=!1;constructor(e){this.#e=e}start(){this.#r||this.#n||(this.#t=_(this.#e.origin,e=>{this.#i(e)}),this.#n=!0)}destroy(){this.#r=!0,this.#n=!1,this.#t?.(),this.#t=null}emitLoadPageSettings(e){this.#u(),this.#l(r.loadPageSettings,e)}async#i(e){if(this.#r)return;let{type:t,payload:n}=e.data;switch(t){case r.handshakeInit:await this.#a();break;case r.requestAuthTokenRefresh:await this.#o();break;case r.updatePageSettings:await this.#s(n);break;default:break}}async#a(){try{let e=await this.#e.resolveHandshakePayload();if(this.#r)return;this.#l(r.handshakeAck,e)}catch{}}async#o(){try{let e=await this.#e.resolveAuthToken();if(this.#r)return;this.#l(r.authTokenRefresh,{authToken:e})}catch{}}async#s(e){let t=e?.settings;if(!t||typeof t!=`object`){this.#c(!1);return}let n=this.#e.onUpdatePageSettings;if(!n){this.#c(!1);return}try{let e=await n(t);if(this.#r)return;this.#c(!!e)}catch{if(this.#r)return;this.#c(!1)}}#c(e){let t={success:e};this.#l(r.pageSettingsUpdated,t)}#l(e,t){let n=this.#e.getContentWindow();n&&g(n,this.#e.origin,e,t)}#u(){if(this.#r)throw Error(`ExtensionSDKHost has been destroyed.`)}};function y(e,t){if(typeof e!=`string`)return!1;try{let n=new URL(e),r=n.protocol===`https:`,i=!t||t(n.origin);return r&&i}catch{return!1}}function b(e,t=window.location.origin){if(typeof e!=`string`)return null;try{let n=new URL(e);return typeof t==`string`&&n.searchParams.set(`tenantUrl`,t),n}catch{return null}}var x=null,S=!1,C=class extends h{destroy(){super.destroy(),x===this&&(x=null,S=!1)}},w=()=>x?(S||=(console.warn(`[@kadanza/extension-sdk] createExtensionSDK() was called more than once. The SDK is a singleton — reuse the same instance for the whole app.`),!0),x):(x=new C,x);exports.CONNECTION_EVENTS=r,exports.ExtensionSDK=h,exports.ExtensionSDKHost=v,exports.InvalidOriginError=s,exports.createExtensionSDK=w,exports.enrichExtensionUrl=b,exports.isValidExtensionUrl=y,exports.postToChild=g,exports.readTenantUrlFromLocation=l,exports.resolveAllowedOrigin=c,exports.subscribeToChildMessages=_;
2
2
  //# sourceMappingURL=extension-sdk.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension-sdk.cjs","names":["#connected","#assertNotDestroyed","#authToken","#extensionDetails","#designTokens","#configureAuthTokenAutoRefresh","#scheduleProactiveAuthTokenRefresh","#pageSettings","#handshakePromise","#allowedOrigin","#tenantUrl","#unsubscribe","#onMessage","#pendingHandshake","#clearPending","#clearProactiveAuthTokenRefreshTimer","#destroyed","#authTokenAutoRefresh","#authTokenRefreshRetryUsed","#rejectPending","#pendingAuthTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#authTokenRefreshHandlers","#assertConnected","#handleHandshakeAck","#handleAuthTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending","#authTokenBufferMs","#parseExpiresAtMs","#authTokenRefreshTimer","#getProactiveRefreshDelayMs","#runProactiveAuthTokenRefresh","#handleProactiveAuthTokenRefreshFailure","#options","#destroyed","#started","#unsubscribe","#onMessage","#assertNotDestroyed","#post","#handleHandshakeInit","#handleAuthTokenRefresh","#handleUpdatePageSettings","#postPageSettingsUpdated"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/hostMessaging.ts","../src/ExtensionSDKHost.ts","../src/hostUrl.ts","../src/index.ts"],"sourcesContent":["interface ApiRequestContext {\n baseUrl: string;\n tenantDomain: string;\n authTokenJwt: string;\n}\n\n/** Derives the Platform API origin from handshake `baseUrl`. */\nexport function deriveApiUrl(baseUrl: string): string {\n if (baseUrl.startsWith(\"/\")) {\n return baseUrl;\n }\n\n const url = new URL(baseUrl);\n url.hostname = `api.${url.hostname}`;\n return url.origin;\n}\n\nfunction resolveApiEndpoint(baseUrl: string, endpoint: string): string {\n if (!endpoint.startsWith(\"/\") || endpoint.startsWith(\"//\")) {\n throw new Error(\n 'API endpoint must be a root-relative path starting with a single \"/\".',\n );\n }\n\n return `${deriveApiUrl(baseUrl).replace(/\\/+$/, \"\")}${endpoint}`;\n}\n\nexport async function callApi<T>(\n endpoint: string,\n context: ApiRequestContext,\n options: RequestInit = {},\n): Promise<T> {\n const headers = new Headers(options.headers);\n\n if (!headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n headers.set(\"Authorization\", `Bearer ${context.authTokenJwt}`);\n headers.set(\"X-Tenant\", context.tenantDomain);\n\n const response = await fetch(resolveApiEndpoint(context.baseUrl, endpoint), {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n const statusText = response.statusText ? ` ${response.statusText}` : \"\";\n throw new Error(`API call failed: ${response.status}${statusText}.`);\n }\n\n return (await response.json()) as T;\n}\n","/**\n * Wire event type strings for parent/child `postMessage` communication.\n *\n * Keys are SDK-facing names; values are the strings sent on the wire.\n * Comments mark message direction relative to the extension (child).\n */\nexport const CONNECTION_EVENTS = {\n /** Child → parent: start handshake. */\n handshakeInit: \"HANDSHAKE_INIT\",\n /** Parent → child: handshake success with auth and context. */\n handshakeAck: \"HANDSHAKE_ACK\",\n /** Child → parent: request a new auth token. */\n requestAuthTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n /** Parent → child: new auth token (reply or push). */\n authTokenRefresh: \"TOKEN_REFRESH\",\n /** Parent → child: open/load page settings UI with values. */\n loadPageSettings: \"LOAD_PAGE_SETTINGS\",\n /** Child → parent: persist updated page settings. */\n updatePageSettings: \"UPDATE_PAGE_SETTINGS\",\n /** Parent → child: result of a page settings update. */\n pageSettingsUpdated: \"PAGE_SETTINGS_UPDATED\",\n /** Child → parent: extension route changed. */\n navigationChange: \"NAVIGATION_CHANGE\",\n} as const;\n\n/** Union of all {@link CONNECTION_EVENTS} wire string values. */\nexport type ConnectionEvent =\n (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS];\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type MessageHandler = (event: MessageEvent<ExtensionMessage>) => void;\n\nexport function postToParent(\n type: ConnectionEvent,\n allowedOrigin: string,\n payload?: unknown,\n): void {\n if (!window.parent || window.parent === window) {\n throw new Error(\"Extension is not running inside a parent frame.\");\n }\n\n const message: ExtensionMessage = payload === undefined ? { type } : { type, payload };\n window.parent.postMessage(message, allowedOrigin);\n}\n\nexport function subscribeToParentMessages(\n allowedOrigin: string,\n handler: MessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== allowedOrigin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","const LOCALHOST_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */\nexport class InvalidOriginError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidOriginError\";\n }\n}\n\n/**\n * Parses and validates a tenant URL into a postMessage-safe origin.\n * Allows https always; http only for localhost / 127.0.0.1 / [::1].\n *\n * @param tenantUrl - Absolute URL from the `tenantUrl` search param.\n * @returns Origin string suitable for `postMessage` targetOrigin.\n * @throws {InvalidOriginError} When missing, unparsable, or disallowed scheme/host.\n */\nexport function resolveAllowedOrigin(tenantUrl: string | null): string {\n if (!tenantUrl) {\n throw new InvalidOriginError(\n 'Missing \"tenantUrl\" search parameter required to establish parent origin.',\n );\n }\n\n let url: URL;\n try {\n url = new URL(tenantUrl);\n } catch {\n throw new InvalidOriginError(`Invalid tenantUrl: \"${tenantUrl}\".`);\n }\n\n const isLocalhost = LOCALHOST_HOSTS.has(url.hostname);\n const isHttps = url.protocol === \"https:\";\n const isLocalHttp = url.protocol === \"http:\" && isLocalhost;\n\n if (!isHttps && !isLocalHttp) {\n throw new InvalidOriginError(\n `tenantUrl must use https (or http on localhost). Received: \"${url.protocol}//${url.host}\".`,\n );\n }\n\n return url.origin;\n}\n\n/**\n * Reads the parent-supplied `tenantUrl` from a URL search string.\n *\n * The parent must pass `tenantUrl` on the iframe `src` so\n * {@link resolveAllowedOrigin} can whitelist the parent origin.\n *\n * @param search - Query string (defaults to `window.location.search`).\n */\nexport function readTenantUrlFromLocation(\n search: string = window.location.search,\n): string | null {\n return new URLSearchParams(search).get(\"tenantUrl\");\n}\n","import { callApi, deriveApiUrl } from \"./api\";\nimport { CONNECTION_EVENTS } from \"./events\";\nimport { postToParent, subscribeToParentMessages } from \"./messaging\";\nimport { readTenantUrlFromLocation, resolveAllowedOrigin } from \"./origin\";\nimport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_AUTH_TOKEN_BUFFER_MS = 120_000;\nconst AUTO_REFRESH_RETRY_DELAY_MS = 30_000;\n/** Largest delay `setTimeout` can reliably use in browsers. */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\n/**\n * Public contract for talking to the Kadanza parent frame over `postMessage`.\n *\n * Requires a `tenantUrl` search param on the extension URL so\n * {@link connect} can validate the parent origin.\n */\nexport interface IExtensionSDK {\n /**\n * Establishes the parent connection and waits for `HANDSHAKE_ACK`.\n *\n * Safe to call repeatedly (e.g. from a React `useEffect` with deps):\n * - Already connected: returns the cached handshake and re-applies\n * `authTokenAutoRefresh` / `authTokenBufferMs` without another init.\n * `timeoutMs` is ignored after connect.\n * - Handshake in progress: returns the same in-flight promise. Options from\n * the first connecting call apply until the handshake completes.\n *\n * Opt-in proactive refresh can be enabled with\n * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).\n *\n * @param options - Handshake timeout and optional auth-token auto-refresh.\n * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.\n * @throws When not embedded in a parent frame, destroyed, or the handshake\n * times out / returns an invalid payload.\n */\n connect(options?: ConnectOptions): Promise<HandshakePayload>;\n\n /**\n * Tears down listeners, proactive refresh timers, and cached state.\n * Rejects any in-flight requests. The instance cannot be reused after destroy.\n * When created via {@link createExtensionSDK}, clears the singleton so a\n * later create call can return a fresh instance.\n */\n destroy(): void;\n\n /** Whether a successful handshake has completed and not been destroyed. */\n readonly isConnected: boolean;\n\n /** Last auth token from handshake or refresh; `null` until connected. */\n getAuthToken(): AuthToken | null;\n\n /** Extension context from handshake; `null` until connected. */\n getExtensionDetails(): ExtensionDetails | null;\n\n /** Design tokens from handshake; `null` until connected. */\n getDesignTokens(): DesignTokens | null;\n\n /**\n * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;\n * `null` until set.\n */\n getPageSettings(): PageSettings | null;\n\n /**\n * Validated parent `postMessage` origin from `tenantUrl`;\n * `null` until `connect` resolves it.\n */\n getAllowedOrigin(): string | null;\n\n /**\n * Raw `tenantUrl` search-param value used for the allowed origin;\n * `null` until `connect` reads it.\n */\n getTenantUrl(): string | null;\n\n /**\n * Platform API origin derived from handshake `baseUrl`;\n * `null` until connected.\n */\n getApiUrl(): string | null;\n\n /**\n * Calls the Kadanza Platform API with the current connection credentials.\n *\n * The API origin is derived from the handshake `baseUrl`. Authorization and\n * tenant headers are managed by the SDK and cannot be overridden.\n *\n * @typeParam T - Expected JSON response body.\n * @param endpoint - Root-relative API path.\n * @param options - Standard fetch options.\n * @throws When not connected, the endpoint is invalid, the request fails,\n * or the response is not successful JSON.\n */\n apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;\n\n /**\n * Asks the parent for a new auth token and waits for `TOKEN_REFRESH`.\n *\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, a refresh is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitRequestAuthTokenRefresh(\n options?: RequestOptions,\n ): Promise<AuthToken>;\n\n /**\n * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.\n *\n * @param payload - Update payload containing the settings object.\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, an update is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options?: RequestOptions,\n ): Promise<PageSettingsUpdatedPayload>;\n\n /**\n * Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.\n *\n * @returns Unsubscribe function.\n */\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;\n\n /**\n * Registers a handler for auth-token updates (requested or parent-pushed).\n *\n * @returns Unsubscribe function.\n */\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;\n\n /**\n * Notifies the parent that the extension's route changed.\n *\n * Wire: `NAVIGATION_CHANGE` (fire-and-forget).\n *\n * @param payload - Navigation change payload (`path` within the extension).\n * @throws When not connected or destroyed.\n */\n emitNavigationChange(payload: NavigationChangePayload): void;\n}\n\ntype Pending<T> = {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */\nexport class ExtensionSDK implements IExtensionSDK {\n #allowedOrigin: string | null = null;\n #tenantUrl: string | null = null;\n #unsubscribe: (() => void) | null = null;\n #connected = false;\n #destroyed = false;\n\n #authToken: AuthToken | null = null;\n #extensionDetails: ExtensionDetails | null = null;\n #designTokens: DesignTokens | null = null;\n #pageSettings: PageSettings | null = null;\n\n #authTokenAutoRefresh = false;\n #authTokenBufferMs = DEFAULT_AUTH_TOKEN_BUFFER_MS;\n #authTokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n #authTokenRefreshRetryUsed = false;\n\n #pendingHandshake: Pending<HandshakePayload> | null = null;\n #handshakePromise: Promise<HandshakePayload> | null = null;\n #pendingAuthTokenRefresh: Pending<AuthToken> | null = null;\n #pendingPageSettingsUpdate: Pending<PageSettingsUpdatedPayload> | null = null;\n\n #loadPageSettingsHandlers = new Set<(settings: PageSettings) => void>();\n #authTokenRefreshHandlers = new Set<(authToken: AuthToken) => void>();\n\n get isConnected(): boolean {\n return this.#connected;\n }\n\n async connect(options: ConnectOptions = {}): Promise<HandshakePayload> {\n this.#assertNotDestroyed();\n\n if (this.#connected && this.#authToken && this.#extensionDetails && this.#designTokens) {\n this.#configureAuthTokenAutoRefresh(options);\n this.#scheduleProactiveAuthTokenRefresh();\n return {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n };\n }\n\n if (this.#handshakePromise) {\n return this.#handshakePromise;\n }\n\n this.#configureAuthTokenAutoRefresh(options);\n\n const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(tenantUrl);\n this.#tenantUrl = tenantUrl;\n\n this.#unsubscribe?.();\n this.#unsubscribe = subscribeToParentMessages(\n this.#allowedOrigin,\n (event) => this.#onMessage(event),\n );\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const handshakePromise = new Promise<HandshakePayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n this.#handshakePromise = handshakePromise;\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#tenantUrl = null;\n this.#clearProactiveAuthTokenRefreshTimer();\n throw error;\n }\n\n return handshakePromise;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#connected = false;\n\n this.#clearProactiveAuthTokenRefreshTimer();\n this.#authTokenAutoRefresh = false;\n this.#authTokenRefreshRetryUsed = false;\n\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#tenantUrl = null;\n\n this.#rejectPending(this.#pendingHandshake, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingAuthTokenRefresh, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingPageSettingsUpdate, new Error(\"SDK destroyed.\"));\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#pendingAuthTokenRefresh = null;\n this.#pendingPageSettingsUpdate = null;\n\n this.#loadPageSettingsHandlers.clear();\n this.#authTokenRefreshHandlers.clear();\n\n this.#authToken = null;\n this.#extensionDetails = null;\n this.#designTokens = null;\n this.#pageSettings = null;\n }\n\n getAuthToken(): AuthToken | null {\n return this.#authToken;\n }\n\n getExtensionDetails(): ExtensionDetails | null {\n return this.#extensionDetails;\n }\n\n getDesignTokens(): DesignTokens | null {\n return this.#designTokens;\n }\n\n getPageSettings(): PageSettings | null {\n return this.#pageSettings;\n }\n\n getAllowedOrigin(): string | null {\n return this.#allowedOrigin;\n }\n\n getTenantUrl(): string | null {\n return this.#tenantUrl;\n }\n\n getApiUrl(): string | null {\n if (!this.#extensionDetails) {\n return null;\n }\n\n return deriveApiUrl(this.#extensionDetails.baseUrl);\n }\n\n async apiCall<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n this.#assertConnected();\n\n return callApi<T>(\n endpoint,\n {\n baseUrl: this.#extensionDetails!.baseUrl,\n tenantDomain: this.#extensionDetails!.tenantDomain,\n authTokenJwt: this.#authToken!.jwt,\n },\n options,\n );\n }\n\n async emitRequestAuthTokenRefresh(\n options: RequestOptions = {},\n ): Promise<AuthToken> {\n this.#assertConnected();\n\n if (this.#pendingAuthTokenRefresh) {\n throw new Error(\"Auth token refresh already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<AuthToken>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingAuthTokenRefresh = null;\n reject(new Error(`Auth token refresh timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingAuthTokenRefresh = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.requestAuthTokenRefresh, allowedOrigin);\n return promise;\n }\n\n async emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options: RequestOptions = {},\n ): Promise<PageSettingsUpdatedPayload> {\n this.#assertConnected();\n\n if (this.#pendingPageSettingsUpdate) {\n throw new Error(\"Page settings update already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<PageSettingsUpdatedPayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingPageSettingsUpdate = null;\n reject(new Error(`Page settings update timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingPageSettingsUpdate = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.updatePageSettings, allowedOrigin, payload);\n return promise;\n }\n\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void {\n this.#loadPageSettingsHandlers.add(handler);\n return () => {\n this.#loadPageSettingsHandlers.delete(handler);\n };\n }\n\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void {\n this.#authTokenRefreshHandlers.add(handler);\n return () => {\n this.#authTokenRefreshHandlers.delete(handler);\n };\n }\n\n emitNavigationChange(payload: NavigationChangePayload): void {\n this.#assertConnected();\n postToParent(\n CONNECTION_EVENTS.navigationChange,\n this.#allowedOrigin!,\n payload,\n );\n }\n\n #onMessage(event: MessageEvent<ExtensionMessage>): void {\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeAck: {\n this.#handleHandshakeAck(payload as HandshakePayload | undefined);\n break;\n }\n case CONNECTION_EVENTS.authTokenRefresh: {\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n }\n case CONNECTION_EVENTS.loadPageSettings: {\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n }\n case CONNECTION_EVENTS.pageSettingsUpdated: {\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n }\n default: {\n break;\n }\n }\n }\n\n #handleHandshakeAck(payload: HandshakePayload | undefined): void {\n if (!payload?.authToken || !payload.extensionDetails || !payload.designTokens) {\n this.#rejectPending(\n this.#pendingHandshake,\n new Error(\"Invalid HANDSHAKE_ACK payload.\"),\n );\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n this.#extensionDetails = payload.extensionDetails;\n this.#designTokens = payload.designTokens;\n this.#pageSettings = payload.pageSettings ?? null;\n this.#connected = true;\n\n const pending = this.#pendingHandshake;\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#resolvePending(pending, {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n });\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleAuthTokenRefresh(payload: AuthTokenRefreshPayload | undefined): void {\n if (!payload?.authToken) {\n this.#rejectPending(\n this.#pendingAuthTokenRefresh,\n new Error(\"Invalid TOKEN_REFRESH payload.\"),\n );\n this.#pendingAuthTokenRefresh = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n\n const pending = this.#pendingAuthTokenRefresh;\n this.#pendingAuthTokenRefresh = null;\n this.#resolvePending(pending, this.#authToken);\n\n for (const handler of this.#authTokenRefreshHandlers) {\n handler(this.#authToken);\n }\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleLoadPageSettings(payload: PageSettings | undefined): void {\n const settings = payload ?? {};\n this.#pageSettings = settings;\n\n for (const handler of this.#loadPageSettingsHandlers) {\n handler(settings);\n }\n }\n\n #handlePageSettingsUpdated(\n payload: PageSettingsUpdatedPayload | undefined,\n ): void {\n if (!payload || typeof payload.success !== \"boolean\") {\n this.#rejectPending(\n this.#pendingPageSettingsUpdate,\n new Error(\"Invalid PAGE_SETTINGS_UPDATED payload.\"),\n );\n this.#pendingPageSettingsUpdate = null;\n return;\n }\n\n const pending = this.#pendingPageSettingsUpdate;\n this.#pendingPageSettingsUpdate = null;\n this.#resolvePending(pending, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"SDK has been destroyed.\");\n }\n }\n\n #assertConnected(): void {\n this.#assertNotDestroyed();\n if (!this.#connected || !this.#allowedOrigin) {\n throw new Error(\"SDK is not connected. Call connect() first.\");\n }\n }\n\n #configureAuthTokenAutoRefresh(options: ConnectOptions): void {\n if (options.authTokenBufferMs !== undefined) {\n if (\n !Number.isFinite(options.authTokenBufferMs) ||\n options.authTokenBufferMs < 0\n ) {\n throw new Error(\n \"authTokenBufferMs must be a finite non-negative number.\",\n );\n }\n }\n\n this.#authTokenAutoRefresh = options.authTokenAutoRefresh === true;\n this.#authTokenBufferMs =\n options.authTokenBufferMs ?? DEFAULT_AUTH_TOKEN_BUFFER_MS;\n this.#authTokenRefreshRetryUsed = false;\n this.#clearProactiveAuthTokenRefreshTimer();\n }\n\n #parseExpiresAtMs(authToken: AuthToken): number | null {\n const expiresSec = Number(authToken.expires);\n if (!Number.isFinite(expiresSec)) {\n return null;\n }\n return expiresSec * 1000;\n }\n\n #getProactiveRefreshDelayMs(authToken: AuthToken): number | null {\n const expiresAtMs = this.#parseExpiresAtMs(authToken);\n if (expiresAtMs === null) {\n return null;\n }\n return Math.max(0, expiresAtMs - this.#authTokenBufferMs - Date.now());\n }\n\n #clearProactiveAuthTokenRefreshTimer(): void {\n if (this.#authTokenRefreshTimer !== null) {\n clearTimeout(this.#authTokenRefreshTimer);\n this.#authTokenRefreshTimer = null;\n }\n }\n\n #scheduleProactiveAuthTokenRefresh(): void {\n this.#clearProactiveAuthTokenRefreshTimer();\n\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const delayMs = this.#getProactiveRefreshDelayMs(this.#authToken);\n if (delayMs === null) {\n return;\n }\n\n const clampedDelay = Math.min(delayMs, MAX_TIMEOUT_MS);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, clampedDelay);\n }\n\n async #runProactiveAuthTokenRefresh(): Promise<void> {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const authTokenAtRequest = this.#authToken;\n const delayMs = this.#getProactiveRefreshDelayMs(authTokenAtRequest);\n if (delayMs === null) {\n return;\n }\n\n if (delayMs > 0) {\n this.#scheduleProactiveAuthTokenRefresh();\n return;\n }\n\n if (this.#pendingAuthTokenRefresh) {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n return;\n }\n\n try {\n await this.emitRequestAuthTokenRefresh();\n } catch {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n }\n }\n\n #handleProactiveAuthTokenRefreshFailure(authTokenAtRequest: AuthToken): void {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n this.#authToken?.jwt !== authTokenAtRequest.jwt\n ) {\n return;\n }\n\n if (this.#authTokenRefreshRetryUsed) {\n return;\n }\n\n const expiresAtMs = this.#parseExpiresAtMs(authTokenAtRequest);\n if (expiresAtMs === null) {\n return;\n }\n\n const remainingMs = expiresAtMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n this.#authTokenRefreshRetryUsed = true;\n this.#clearProactiveAuthTokenRefreshTimer();\n\n const retryDelay = Math.min(AUTO_REFRESH_RETRY_DELAY_MS, remainingMs);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, retryDelay);\n }\n\n #clearPending(pending: { timer: ReturnType<typeof setTimeout> } | null): void {\n if (pending) {\n clearTimeout(pending.timer);\n }\n }\n\n #resolvePending<T>(pending: Pending<T> | null, value: T): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.resolve(value);\n }\n\n #rejectPending(\n pending: { timer: ReturnType<typeof setTimeout>; reject: (reason?: unknown) => void } | null,\n reason: unknown,\n ): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.reject(reason);\n }\n}\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type HostMessageHandler = (\n event: MessageEvent<ExtensionMessage>,\n) => void;\n\n/**\n * Posts a typed extension message to an embedded child frame.\n *\n * @param contentWindow - The iframe's `contentWindow`.\n * @param origin - Target origin for `postMessage` (extension origin).\n * @param type - Wire event type from {@link CONNECTION_EVENTS}.\n * @param payload - Optional message payload.\n */\nexport function postToChild(\n contentWindow: Window,\n origin: string,\n type: ConnectionEvent,\n payload?: unknown,\n): void {\n const message: ExtensionMessage =\n payload === undefined ? { type } : { type, payload };\n contentWindow.postMessage(message, origin);\n}\n\n/**\n * Subscribes to `message` events from a child frame at `origin`.\n *\n * Ignores events from other origins and payloads without a `type` field.\n *\n * @returns Unsubscribe function.\n */\nexport function subscribeToChildMessages(\n origin: string,\n handler: HostMessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== origin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","import { CONNECTION_EVENTS } from \"./events\";\nimport { postToChild, subscribeToChildMessages } from \"./hostMessaging\";\nimport type {\n AuthToken,\n ExtensionMessage,\n HandshakePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\n/**\n * Options for {@link ExtensionSDKHost}.\n *\n * Platform code supplies token minting and page-settings persistence via\n * callbacks; the host owns only the postMessage protocol.\n */\nexport interface ExtensionSDKHostOptions {\n /** Returns the iframe `contentWindow`, or `null` if not ready. */\n getContentWindow: () => Window | null;\n /** Extension origin used as `postMessage` targetOrigin / source filter. */\n origin: string;\n /**\n * Builds the handshake payload (including auth token) when the child sends\n * `HANDSHAKE_INIT`.\n */\n resolveHandshakePayload: () => HandshakePayload | Promise<HandshakePayload>;\n /**\n * Resolves a fresh auth token when the child sends `REQUEST_TOKEN_REFRESH`.\n */\n resolveAuthToken: () => AuthToken | Promise<AuthToken>;\n /**\n * Persists page settings from `UPDATE_PAGE_SETTINGS`.\n * Return `true` on success; the host emits `PAGE_SETTINGS_UPDATED`.\n */\n onUpdatePageSettings?: (\n settings: PageSettings,\n ) => boolean | Promise<boolean>;\n}\n\n/**\n * Parent-frame counterpart to {@link IExtensionSDK}.\n *\n * Listens for child messages and replies with the shared wire protocol.\n * Create one instance per iframe.\n */\nexport interface IExtensionSDKHost {\n /**\n * Attaches the child message listener. Safe to call once; no-ops if already\n * started or destroyed.\n */\n start(): void;\n\n /**\n * Tears down the listener. The instance cannot be restarted after destroy.\n */\n destroy(): void;\n\n /**\n * Asks the child to open page settings UI with the given values.\n *\n * Wire: `LOAD_PAGE_SETTINGS` (fire-and-forget).\n */\n emitLoadPageSettings(settings: PageSettings | null): void;\n}\n\n/** Default {@link IExtensionSDKHost} implementation. */\nexport class ExtensionSDKHost implements IExtensionSDKHost {\n #options: ExtensionSDKHostOptions;\n #unsubscribe: (() => void) | null = null;\n #started = false;\n #destroyed = false;\n\n constructor(options: ExtensionSDKHostOptions) {\n this.#options = options;\n }\n\n start(): void {\n if (this.#destroyed || this.#started) {\n return;\n }\n\n this.#unsubscribe = subscribeToChildMessages(\n this.#options.origin,\n (event) => {\n void this.#onMessage(event);\n },\n );\n this.#started = true;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#started = false;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n }\n\n emitLoadPageSettings(settings: PageSettings | null): void {\n this.#assertNotDestroyed();\n this.#post(CONNECTION_EVENTS.loadPageSettings, settings);\n }\n\n async #onMessage(event: MessageEvent<ExtensionMessage>): Promise<void> {\n if (this.#destroyed) {\n return;\n }\n\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeInit: {\n await this.#handleHandshakeInit();\n break;\n }\n case CONNECTION_EVENTS.requestAuthTokenRefresh: {\n await this.#handleAuthTokenRefresh();\n break;\n }\n case CONNECTION_EVENTS.updatePageSettings: {\n await this.#handleUpdatePageSettings(\n payload as UpdatePageSettingsPayload | undefined,\n );\n break;\n }\n default: {\n break;\n }\n }\n }\n\n async #handleHandshakeInit(): Promise<void> {\n try {\n const handshakePayload = await this.#options.resolveHandshakePayload();\n if (this.#destroyed) {\n return;\n }\n this.#post(CONNECTION_EVENTS.handshakeAck, handshakePayload);\n } catch {\n // Platform owns error UI / retry; do not reply with a partial ACK.\n }\n }\n\n async #handleAuthTokenRefresh(): Promise<void> {\n try {\n const authToken = await this.#options.resolveAuthToken();\n if (this.#destroyed) {\n return;\n }\n this.#post(CONNECTION_EVENTS.authTokenRefresh, { authToken });\n } catch {\n // Platform owns error UI / retry; do not reply with an invalid token.\n }\n }\n\n async #handleUpdatePageSettings(\n payload: UpdatePageSettingsPayload | undefined,\n ): Promise<void> {\n const settings = payload?.settings;\n if (!settings || typeof settings !== \"object\") {\n this.#postPageSettingsUpdated(false);\n return;\n }\n\n const handler = this.#options.onUpdatePageSettings;\n if (!handler) {\n this.#postPageSettingsUpdated(false);\n return;\n }\n\n try {\n const success = await handler(settings);\n if (this.#destroyed) {\n return;\n }\n this.#postPageSettingsUpdated(Boolean(success));\n } catch {\n if (this.#destroyed) {\n return;\n }\n this.#postPageSettingsUpdated(false);\n }\n }\n\n #postPageSettingsUpdated(success: boolean): void {\n const payload: PageSettingsUpdatedPayload = { success };\n this.#post(CONNECTION_EVENTS.pageSettingsUpdated, payload);\n }\n\n #post(type: (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS], payload?: unknown): void {\n const contentWindow = this.#options.getContentWindow();\n if (!contentWindow) {\n return;\n }\n postToChild(contentWindow, this.#options.origin, type, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"ExtensionSDKHost has been destroyed.\");\n }\n }\n}\n","/**\n * Returns whether `urlRaw` is a valid HTTPS extension URL.\n *\n * When `checkOrigin` is provided, the URL's origin must also pass that predicate\n * (e.g. hostname ends with `.kadanza.app`).\n */\nexport function isValidExtensionUrl(\n urlRaw: string | null | undefined,\n checkOrigin?: (origin: string) => boolean,\n): boolean {\n if (typeof urlRaw !== \"string\") {\n return false;\n }\n\n try {\n const url = new URL(urlRaw);\n const allowedProtocol = url.protocol === \"https:\";\n const allowedOrigin = checkOrigin ? checkOrigin(url.origin) : true;\n return allowedProtocol && allowedOrigin;\n } catch {\n return false;\n }\n}\n\n/**\n * Clones `urlRaw` and sets the `tenantUrl` search param (parent origin).\n *\n * @param urlRaw - Absolute extension URL string.\n * @param tenantUrl - Parent origin to embed (defaults to `window.location.origin`).\n * @returns Enriched `URL`, or `null` when `urlRaw` is missing or unparsable.\n */\nexport function enrichExtensionUrl(\n urlRaw: string | null | undefined,\n tenantUrl: string = window.location.origin,\n): URL | null {\n if (typeof urlRaw !== \"string\") {\n return null;\n }\n\n try {\n const enrichedUrl = new URL(urlRaw);\n\n if (typeof tenantUrl === \"string\") {\n enrichedUrl.searchParams.set(\"tenantUrl\", tenantUrl);\n }\n\n return enrichedUrl;\n } catch {\n return null;\n }\n}\n","import { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport {\n ExtensionSDKHost,\n type ExtensionSDKHostOptions,\n type IExtensionSDKHost,\n} from \"./ExtensionSDKHost\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\nexport {\n postToChild,\n subscribeToChildMessages,\n type HostMessageHandler,\n} from \"./hostMessaging\";\nexport { enrichExtensionUrl, isValidExtensionUrl } from \"./hostUrl\";\nexport {\n InvalidOriginError,\n readTenantUrlFromLocation,\n resolveAllowedOrigin,\n} from \"./origin\";\nexport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nlet instance: IExtensionSDK | null = null;\nlet hasWarnedDuplicateCreate = false;\n\n/** Clears the factory singleton when the managed instance is destroyed. */\nclass ManagedExtensionSDK extends ExtensionSDK {\n destroy(): void {\n super.destroy();\n if (instance === this) {\n instance = null;\n hasWarnedDuplicateCreate = false;\n }\n }\n}\n\n/**\n * Returns the shared {@link IExtensionSDK} for the current extension frame.\n *\n * Creates the instance on first call. Later calls return the same instance and\n * log a console warning — use the SDK once per app. After {@link IExtensionSDK.destroy},\n * the next call creates a fresh instance.\n */\nexport const createExtensionSDK = (): IExtensionSDK => {\n if (instance) {\n if (!hasWarnedDuplicateCreate) {\n console.warn(\n `[${__PACKAGE_NAME__}] createExtensionSDK() was called more than once. ` +\n \"The SDK is a singleton — reuse the same instance for the whole app.\",\n );\n hasWarnedDuplicateCreate = true;\n }\n return instance;\n }\n\n instance = new ManagedExtensionSDK();\n return instance;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension-sdk.cjs","names":["#connected","#assertNotDestroyed","#configureAuthTokenAutoRefresh","#scheduleProactiveAuthTokenRefresh","#toHandshakePayload","#handshakePromise","#allowedOrigin","#tenantUrl","#unsubscribe","#onMessage","#pendingHandshake","#clearPending","#clearProactiveAuthTokenRefreshTimer","#destroyed","#authTokenAutoRefresh","#authTokenRefreshRetryUsed","#rejectPending","#pendingAuthTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#authTokenRefreshHandlers","#authToken","#extensionDetails","#designTokens","#pageSettings","#assertConnected","#handleHandshakeAck","#handleAuthTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending","#authTokenBufferMs","#parseExpiresAtMs","#authTokenRefreshTimer","#getProactiveRefreshDelayMs","#runProactiveAuthTokenRefresh","#handleProactiveAuthTokenRefreshFailure","#options","#destroyed","#started","#unsubscribe","#onMessage","#assertNotDestroyed","#post","#handleHandshakeInit","#handleAuthTokenRefresh","#handleUpdatePageSettings","#postPageSettingsUpdated"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/hostMessaging.ts","../src/ExtensionSDKHost.ts","../src/hostUrl.ts","../src/index.ts"],"sourcesContent":["interface ApiRequestContext {\n baseUrl: string;\n tenantDomain: string;\n authTokenJwt: string;\n}\n\n/** Derives the Platform API origin from handshake `baseUrl`. */\nexport function deriveApiUrl(baseUrl: string): string {\n if (baseUrl.startsWith(\"/\")) {\n return baseUrl;\n }\n\n const url = new URL(baseUrl);\n url.hostname = `api.${url.hostname}`;\n return url.origin;\n}\n\nfunction resolveApiEndpoint(baseUrl: string, endpoint: string): string {\n if (!endpoint.startsWith(\"/\") || endpoint.startsWith(\"//\")) {\n throw new Error(\n 'API endpoint must be a root-relative path starting with a single \"/\".',\n );\n }\n\n return `${deriveApiUrl(baseUrl).replace(/\\/+$/, \"\")}${endpoint}`;\n}\n\nexport async function callApi<T>(\n endpoint: string,\n context: ApiRequestContext,\n options: RequestInit = {},\n): Promise<T> {\n const headers = new Headers(options.headers);\n\n if (!headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n headers.set(\"Authorization\", `Bearer ${context.authTokenJwt}`);\n headers.set(\"X-Tenant\", context.tenantDomain);\n\n const response = await fetch(resolveApiEndpoint(context.baseUrl, endpoint), {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n const statusText = response.statusText ? ` ${response.statusText}` : \"\";\n throw new Error(`API call failed: ${response.status}${statusText}.`);\n }\n\n return (await response.json()) as T;\n}\n","/**\n * Wire event type strings for parent/child `postMessage` communication.\n *\n * Keys are SDK-facing names; values are the strings sent on the wire.\n * Comments mark message direction relative to the extension (child).\n */\nexport const CONNECTION_EVENTS = {\n /** Child → parent: start handshake. */\n handshakeInit: \"HANDSHAKE_INIT\",\n /** Parent → child: handshake success with auth and context. */\n handshakeAck: \"HANDSHAKE_ACK\",\n /** Child → parent: request a new auth token. */\n requestAuthTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n /** Parent → child: new auth token (reply or push). */\n authTokenRefresh: \"TOKEN_REFRESH\",\n /** Parent → child: open/load page settings UI with values. */\n loadPageSettings: \"LOAD_PAGE_SETTINGS\",\n /** Child → parent: persist updated page settings. */\n updatePageSettings: \"UPDATE_PAGE_SETTINGS\",\n /** Parent → child: result of a page settings update. */\n pageSettingsUpdated: \"PAGE_SETTINGS_UPDATED\",\n /** Child → parent: extension route changed. */\n navigationChange: \"NAVIGATION_CHANGE\",\n} as const;\n\n/** Union of all {@link CONNECTION_EVENTS} wire string values. */\nexport type ConnectionEvent =\n (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS];\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type MessageHandler = (event: MessageEvent<ExtensionMessage>) => void;\n\nexport function postToParent(\n type: ConnectionEvent,\n allowedOrigin: string,\n payload?: unknown,\n): void {\n if (!window.parent || window.parent === window) {\n throw new Error(\"Extension is not running inside a parent frame.\");\n }\n\n const message: ExtensionMessage = payload === undefined ? { type } : { type, payload };\n window.parent.postMessage(message, allowedOrigin);\n}\n\nexport function subscribeToParentMessages(\n allowedOrigin: string,\n handler: MessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== allowedOrigin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","const LOCALHOST_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */\nexport class InvalidOriginError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidOriginError\";\n }\n}\n\n/**\n * Parses and validates a tenant URL into a postMessage-safe origin.\n * Allows https always; http only for localhost / 127.0.0.1 / [::1].\n *\n * @param tenantUrl - Absolute URL from the `tenantUrl` search param.\n * @returns Origin string suitable for `postMessage` targetOrigin.\n * @throws {InvalidOriginError} When missing, unparsable, or disallowed scheme/host.\n */\nexport function resolveAllowedOrigin(tenantUrl: string | null): string {\n if (!tenantUrl) {\n throw new InvalidOriginError(\n 'Missing \"tenantUrl\" search parameter required to establish parent origin.',\n );\n }\n\n let url: URL;\n try {\n url = new URL(tenantUrl);\n } catch {\n throw new InvalidOriginError(`Invalid tenantUrl: \"${tenantUrl}\".`);\n }\n\n const isLocalhost = LOCALHOST_HOSTS.has(url.hostname);\n const isHttps = url.protocol === \"https:\";\n const isLocalHttp = url.protocol === \"http:\" && isLocalhost;\n\n if (!isHttps && !isLocalHttp) {\n throw new InvalidOriginError(\n `tenantUrl must use https (or http on localhost). Received: \"${url.protocol}//${url.host}\".`,\n );\n }\n\n return url.origin;\n}\n\n/**\n * Reads the parent-supplied `tenantUrl` from a URL search string.\n *\n * The parent must pass `tenantUrl` on the iframe `src` so\n * {@link resolveAllowedOrigin} can whitelist the parent origin.\n *\n * @param search - Query string (defaults to `window.location.search`).\n */\nexport function readTenantUrlFromLocation(\n search: string = window.location.search,\n): string | null {\n return new URLSearchParams(search).get(\"tenantUrl\");\n}\n","import { callApi, deriveApiUrl } from \"./api\";\nimport { CONNECTION_EVENTS } from \"./events\";\nimport { postToParent, subscribeToParentMessages } from \"./messaging\";\nimport { readTenantUrlFromLocation, resolveAllowedOrigin } from \"./origin\";\nimport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_AUTH_TOKEN_BUFFER_MS = 120_000;\nconst AUTO_REFRESH_RETRY_DELAY_MS = 30_000;\n/** Largest delay `setTimeout` can reliably use in browsers. */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\n/**\n * Public contract for talking to the Kadanza parent frame over `postMessage`.\n *\n * Requires a `tenantUrl` search param on the extension URL so\n * {@link connect} can validate the parent origin.\n */\nexport interface IExtensionSDK {\n /**\n * Establishes the parent connection and waits for `HANDSHAKE_ACK`.\n *\n * Safe to call repeatedly (e.g. from a React `useEffect` with deps):\n * - Already connected: returns the cached handshake and re-applies\n * `authTokenAutoRefresh` / `authTokenBufferMs` without another init.\n * `timeoutMs` is ignored after connect.\n * - Handshake in progress: returns the same in-flight promise. Options from\n * the first connecting call apply until the handshake completes.\n *\n * Opt-in proactive refresh can be enabled with\n * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).\n *\n * Handshake completes when the parent sends `HANDSHAKE_ACK`. Context fields\n * (`authToken`, `extensionDetails`, `designTokens`, `pageSettings`) are\n * optional — omitted values are `null`. Use them only when the current host\n * surface provides them (e.g. `spaceId` / `pageId` on Experience Pages,\n * `designTokens` when a tenant is in context).\n *\n * @param options - Handshake timeout and optional auth-token auto-refresh.\n * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.\n * @throws When not embedded in a parent frame, destroyed, or the handshake\n * times out.\n */\n connect(options?: ConnectOptions): Promise<HandshakePayload>;\n\n /**\n * Tears down listeners, proactive refresh timers, and cached state.\n * Rejects any in-flight requests. The instance cannot be reused after destroy.\n * When created via {@link createExtensionSDK}, clears the singleton so a\n * later create call can return a fresh instance.\n */\n destroy(): void;\n\n /** Whether a successful handshake has completed and not been destroyed. */\n readonly isConnected: boolean;\n\n /**\n * Last auth token from handshake or refresh.\n * `null` until connected, or when the host omitted it.\n */\n getAuthToken(): AuthToken | null;\n\n /**\n * Extension context from handshake.\n * `null` until connected, or when the host omitted it.\n */\n getExtensionDetails(): ExtensionDetails | null;\n\n /**\n * Tenant branding from handshake.\n * `null` until connected, or when the host omitted it.\n */\n getDesignTokens(): DesignTokens | null;\n\n /**\n * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;\n * `null` until set.\n */\n getPageSettings(): PageSettings | null;\n\n /**\n * Validated parent `postMessage` origin from `tenantUrl`;\n * `null` until `connect` resolves it.\n */\n getAllowedOrigin(): string | null;\n\n /**\n * Raw `tenantUrl` search-param value used for the allowed origin;\n * `null` until `connect` reads it.\n */\n getTenantUrl(): string | null;\n\n /**\n * Platform API origin derived from handshake `baseUrl`.\n * `null` until connected, or when `extensionDetails.baseUrl` was omitted.\n */\n getApiUrl(): string | null;\n\n /**\n * Calls the Kadanza Platform API with the current connection credentials.\n *\n * The API origin is derived from the handshake `baseUrl`. Authorization and\n * tenant headers are managed by the SDK and cannot be overridden.\n *\n * @typeParam T - Expected JSON response body.\n * @param endpoint - Root-relative API path.\n * @param options - Standard fetch options.\n * @throws When not connected, handshake did not include `authToken` and\n * `extensionDetails` (`baseUrl`, `tenantDomain`), the endpoint is invalid,\n * the request fails, or the response is not successful JSON.\n */\n apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;\n\n /**\n * Asks the parent for a new auth token and waits for `TOKEN_REFRESH`.\n *\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, a refresh is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitRequestAuthTokenRefresh(\n options?: RequestOptions,\n ): Promise<AuthToken>;\n\n /**\n * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.\n *\n * @param payload - Update payload containing the settings object.\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, an update is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options?: RequestOptions,\n ): Promise<PageSettingsUpdatedPayload>;\n\n /**\n * Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.\n *\n * @returns Unsubscribe function.\n */\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;\n\n /**\n * Registers a handler for auth-token updates (requested or parent-pushed).\n *\n * @returns Unsubscribe function.\n */\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;\n\n /**\n * Notifies the parent that the extension's route changed.\n *\n * Wire: `NAVIGATION_CHANGE` (fire-and-forget).\n *\n * @param payload - Navigation change payload (`path` within the extension).\n * @throws When not connected or destroyed.\n */\n emitNavigationChange(payload: NavigationChangePayload): void;\n}\n\ntype Pending<T> = {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */\nexport class ExtensionSDK implements IExtensionSDK {\n #allowedOrigin: string | null = null;\n #tenantUrl: string | null = null;\n #unsubscribe: (() => void) | null = null;\n #connected = false;\n #destroyed = false;\n\n #authToken: AuthToken | null = null;\n #extensionDetails: ExtensionDetails | null = null;\n #designTokens: DesignTokens | null = null;\n #pageSettings: PageSettings | null = null;\n\n #authTokenAutoRefresh = false;\n #authTokenBufferMs = DEFAULT_AUTH_TOKEN_BUFFER_MS;\n #authTokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n #authTokenRefreshRetryUsed = false;\n\n #pendingHandshake: Pending<HandshakePayload> | null = null;\n #handshakePromise: Promise<HandshakePayload> | null = null;\n #pendingAuthTokenRefresh: Pending<AuthToken> | null = null;\n #pendingPageSettingsUpdate: Pending<PageSettingsUpdatedPayload> | null = null;\n\n #loadPageSettingsHandlers = new Set<(settings: PageSettings) => void>();\n #authTokenRefreshHandlers = new Set<(authToken: AuthToken) => void>();\n\n get isConnected(): boolean {\n return this.#connected;\n }\n\n async connect(options: ConnectOptions = {}): Promise<HandshakePayload> {\n this.#assertNotDestroyed();\n\n if (this.#connected) {\n this.#configureAuthTokenAutoRefresh(options);\n this.#scheduleProactiveAuthTokenRefresh();\n return this.#toHandshakePayload();\n }\n\n if (this.#handshakePromise) {\n return this.#handshakePromise;\n }\n\n this.#configureAuthTokenAutoRefresh(options);\n\n const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(tenantUrl);\n this.#tenantUrl = tenantUrl;\n\n this.#unsubscribe?.();\n this.#unsubscribe = subscribeToParentMessages(\n this.#allowedOrigin,\n (event) => this.#onMessage(event),\n );\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const handshakePromise = new Promise<HandshakePayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n this.#handshakePromise = handshakePromise;\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#tenantUrl = null;\n this.#clearProactiveAuthTokenRefreshTimer();\n throw error;\n }\n\n return handshakePromise;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#connected = false;\n\n this.#clearProactiveAuthTokenRefreshTimer();\n this.#authTokenAutoRefresh = false;\n this.#authTokenRefreshRetryUsed = false;\n\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#tenantUrl = null;\n\n this.#rejectPending(this.#pendingHandshake, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingAuthTokenRefresh, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingPageSettingsUpdate, new Error(\"SDK destroyed.\"));\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#pendingAuthTokenRefresh = null;\n this.#pendingPageSettingsUpdate = null;\n\n this.#loadPageSettingsHandlers.clear();\n this.#authTokenRefreshHandlers.clear();\n\n this.#authToken = null;\n this.#extensionDetails = null;\n this.#designTokens = null;\n this.#pageSettings = null;\n }\n\n getAuthToken(): AuthToken | null {\n return this.#authToken;\n }\n\n getExtensionDetails(): ExtensionDetails | null {\n return this.#extensionDetails;\n }\n\n getDesignTokens(): DesignTokens | null {\n return this.#designTokens;\n }\n\n getPageSettings(): PageSettings | null {\n return this.#pageSettings;\n }\n\n getAllowedOrigin(): string | null {\n return this.#allowedOrigin;\n }\n\n getTenantUrl(): string | null {\n return this.#tenantUrl;\n }\n\n getApiUrl(): string | null {\n const baseUrl = this.#extensionDetails?.baseUrl;\n if (!baseUrl) {\n return null;\n }\n\n try {\n return deriveApiUrl(baseUrl);\n } catch {\n return null;\n }\n }\n\n async apiCall<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n this.#assertConnected();\n\n const baseUrl = this.#extensionDetails?.baseUrl;\n const tenantDomain = this.#extensionDetails?.tenantDomain;\n const authTokenJwt = this.#authToken?.jwt;\n\n if (!baseUrl || !tenantDomain || !authTokenJwt) {\n throw new Error(\n \"API calls require handshake authToken and extensionDetails (baseUrl, tenantDomain).\",\n );\n }\n\n return callApi<T>(\n endpoint,\n {\n baseUrl,\n tenantDomain,\n authTokenJwt,\n },\n options,\n );\n }\n\n async emitRequestAuthTokenRefresh(\n options: RequestOptions = {},\n ): Promise<AuthToken> {\n this.#assertConnected();\n\n if (this.#pendingAuthTokenRefresh) {\n throw new Error(\"Auth token refresh already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<AuthToken>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingAuthTokenRefresh = null;\n reject(new Error(`Auth token refresh timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingAuthTokenRefresh = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.requestAuthTokenRefresh, allowedOrigin);\n return promise;\n }\n\n async emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options: RequestOptions = {},\n ): Promise<PageSettingsUpdatedPayload> {\n this.#assertConnected();\n\n if (this.#pendingPageSettingsUpdate) {\n throw new Error(\"Page settings update already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<PageSettingsUpdatedPayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingPageSettingsUpdate = null;\n reject(new Error(`Page settings update timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingPageSettingsUpdate = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.updatePageSettings, allowedOrigin, payload);\n return promise;\n }\n\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void {\n this.#loadPageSettingsHandlers.add(handler);\n return () => {\n this.#loadPageSettingsHandlers.delete(handler);\n };\n }\n\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void {\n this.#authTokenRefreshHandlers.add(handler);\n return () => {\n this.#authTokenRefreshHandlers.delete(handler);\n };\n }\n\n emitNavigationChange(payload: NavigationChangePayload): void {\n this.#assertConnected();\n postToParent(\n CONNECTION_EVENTS.navigationChange,\n this.#allowedOrigin!,\n payload,\n );\n }\n\n #onMessage(event: MessageEvent<ExtensionMessage>): void {\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeAck: {\n this.#handleHandshakeAck(payload as HandshakePayload | undefined);\n break;\n }\n case CONNECTION_EVENTS.authTokenRefresh: {\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n }\n case CONNECTION_EVENTS.loadPageSettings: {\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n }\n case CONNECTION_EVENTS.pageSettingsUpdated: {\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n }\n default: {\n break;\n }\n }\n }\n\n #toHandshakePayload(): HandshakePayload {\n return {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n };\n }\n\n #handleHandshakeAck(payload: HandshakePayload | undefined): void {\n this.#authToken = isPlainObject(payload?.authToken)\n ? (payload?.authToken as AuthToken)\n : null;\n this.#extensionDetails = isPlainObject(payload?.extensionDetails)\n ? (payload?.extensionDetails as ExtensionDetails)\n : null;\n this.#designTokens = isPlainObject(payload?.designTokens)\n ? (payload?.designTokens as DesignTokens)\n : null;\n this.#pageSettings = isPlainObject(payload?.pageSettings)\n ? (payload?.pageSettings as PageSettings)\n : null;\n this.#connected = true;\n\n const pending = this.#pendingHandshake;\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#resolvePending(pending, this.#toHandshakePayload());\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleAuthTokenRefresh(payload: AuthTokenRefreshPayload | undefined): void {\n if (!payload?.authToken) {\n this.#rejectPending(\n this.#pendingAuthTokenRefresh,\n new Error(\"Invalid TOKEN_REFRESH payload.\"),\n );\n this.#pendingAuthTokenRefresh = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n\n const pending = this.#pendingAuthTokenRefresh;\n this.#pendingAuthTokenRefresh = null;\n this.#resolvePending(pending, this.#authToken);\n\n for (const handler of this.#authTokenRefreshHandlers) {\n handler(this.#authToken);\n }\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleLoadPageSettings(payload: PageSettings | undefined): void {\n const settings = payload ?? {};\n this.#pageSettings = settings;\n\n for (const handler of this.#loadPageSettingsHandlers) {\n handler(settings);\n }\n }\n\n #handlePageSettingsUpdated(\n payload: PageSettingsUpdatedPayload | undefined,\n ): void {\n if (!payload || typeof payload.success !== \"boolean\") {\n this.#rejectPending(\n this.#pendingPageSettingsUpdate,\n new Error(\"Invalid PAGE_SETTINGS_UPDATED payload.\"),\n );\n this.#pendingPageSettingsUpdate = null;\n return;\n }\n\n const pending = this.#pendingPageSettingsUpdate;\n this.#pendingPageSettingsUpdate = null;\n this.#resolvePending(pending, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"SDK has been destroyed.\");\n }\n }\n\n #assertConnected(): void {\n this.#assertNotDestroyed();\n if (!this.#connected || !this.#allowedOrigin) {\n throw new Error(\"SDK is not connected. Call connect() first.\");\n }\n }\n\n #configureAuthTokenAutoRefresh(options: ConnectOptions): void {\n if (options.authTokenBufferMs !== undefined) {\n if (\n !Number.isFinite(options.authTokenBufferMs) ||\n options.authTokenBufferMs < 0\n ) {\n throw new Error(\n \"authTokenBufferMs must be a finite non-negative number.\",\n );\n }\n }\n\n this.#authTokenAutoRefresh = options.authTokenAutoRefresh === true;\n this.#authTokenBufferMs =\n options.authTokenBufferMs ?? DEFAULT_AUTH_TOKEN_BUFFER_MS;\n this.#authTokenRefreshRetryUsed = false;\n this.#clearProactiveAuthTokenRefreshTimer();\n }\n\n #parseExpiresAtMs(authToken: AuthToken): number | null {\n const expiresSec = Number(authToken.expires);\n if (!Number.isFinite(expiresSec)) {\n return null;\n }\n return expiresSec * 1000;\n }\n\n #getProactiveRefreshDelayMs(authToken: AuthToken): number | null {\n const expiresAtMs = this.#parseExpiresAtMs(authToken);\n if (expiresAtMs === null) {\n return null;\n }\n return Math.max(0, expiresAtMs - this.#authTokenBufferMs - Date.now());\n }\n\n #clearProactiveAuthTokenRefreshTimer(): void {\n if (this.#authTokenRefreshTimer !== null) {\n clearTimeout(this.#authTokenRefreshTimer);\n this.#authTokenRefreshTimer = null;\n }\n }\n\n #scheduleProactiveAuthTokenRefresh(): void {\n this.#clearProactiveAuthTokenRefreshTimer();\n\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const delayMs = this.#getProactiveRefreshDelayMs(this.#authToken);\n if (delayMs === null) {\n return;\n }\n\n const clampedDelay = Math.min(delayMs, MAX_TIMEOUT_MS);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, clampedDelay);\n }\n\n async #runProactiveAuthTokenRefresh(): Promise<void> {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const authTokenAtRequest = this.#authToken;\n const delayMs = this.#getProactiveRefreshDelayMs(authTokenAtRequest);\n if (delayMs === null) {\n return;\n }\n\n if (delayMs > 0) {\n this.#scheduleProactiveAuthTokenRefresh();\n return;\n }\n\n if (this.#pendingAuthTokenRefresh) {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n return;\n }\n\n try {\n await this.emitRequestAuthTokenRefresh();\n } catch {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n }\n }\n\n #handleProactiveAuthTokenRefreshFailure(authTokenAtRequest: AuthToken): void {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n this.#authToken?.jwt !== authTokenAtRequest.jwt\n ) {\n return;\n }\n\n if (this.#authTokenRefreshRetryUsed) {\n return;\n }\n\n const expiresAtMs = this.#parseExpiresAtMs(authTokenAtRequest);\n if (expiresAtMs === null) {\n return;\n }\n\n const remainingMs = expiresAtMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n this.#authTokenRefreshRetryUsed = true;\n this.#clearProactiveAuthTokenRefreshTimer();\n\n const retryDelay = Math.min(AUTO_REFRESH_RETRY_DELAY_MS, remainingMs);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, retryDelay);\n }\n\n #clearPending(pending: { timer: ReturnType<typeof setTimeout> } | null): void {\n if (pending) {\n clearTimeout(pending.timer);\n }\n }\n\n #resolvePending<T>(pending: Pending<T> | null, value: T): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.resolve(value);\n }\n\n #rejectPending(\n pending: { timer: ReturnType<typeof setTimeout>; reject: (reason?: unknown) => void } | null,\n reason: unknown,\n ): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.reject(reason);\n }\n}\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type HostMessageHandler = (\n event: MessageEvent<ExtensionMessage>,\n) => void;\n\n/**\n * Posts a typed extension message to an embedded child frame.\n *\n * @param contentWindow - The iframe's `contentWindow`.\n * @param origin - Target origin for `postMessage` (extension origin).\n * @param type - Wire event type from {@link CONNECTION_EVENTS}.\n * @param payload - Optional message payload.\n */\nexport function postToChild(\n contentWindow: Window,\n origin: string,\n type: ConnectionEvent,\n payload?: unknown,\n): void {\n const message: ExtensionMessage =\n payload === undefined ? { type } : { type, payload };\n contentWindow.postMessage(message, origin);\n}\n\n/**\n * Subscribes to `message` events from a child frame at `origin`.\n *\n * Ignores events from other origins and payloads without a `type` field.\n *\n * @returns Unsubscribe function.\n */\nexport function subscribeToChildMessages(\n origin: string,\n handler: HostMessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== origin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","import { CONNECTION_EVENTS } from \"./events\";\nimport { postToChild, subscribeToChildMessages } from \"./hostMessaging\";\nimport type {\n AuthToken,\n ExtensionMessage,\n HandshakePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\n/**\n * Options for {@link ExtensionSDKHost}.\n *\n * Platform code supplies token minting and page-settings persistence via\n * callbacks; the host owns only the postMessage protocol.\n */\nexport interface ExtensionSDKHostOptions {\n /** Returns the iframe `contentWindow`, or `null` if not ready. */\n getContentWindow: () => Window | null;\n /** Extension origin used as `postMessage` targetOrigin / source filter. */\n origin: string;\n /**\n * Builds the handshake payload when the child sends `HANDSHAKE_INIT`.\n * Context fields are optional; send `designTokens` whenever a tenant is in\n * context. `spaceId` / `pageId` apply to Experience Pages only.\n */\n resolveHandshakePayload: () =>\n Partial<HandshakePayload> | Promise<Partial<HandshakePayload>>;\n /**\n * Resolves a fresh auth token when the child sends `REQUEST_TOKEN_REFRESH`.\n */\n resolveAuthToken: () => AuthToken | Promise<AuthToken>;\n /**\n * Persists page settings from `UPDATE_PAGE_SETTINGS`.\n * Return `true` on success; the host emits `PAGE_SETTINGS_UPDATED`.\n */\n onUpdatePageSettings?: (\n settings: PageSettings,\n ) => boolean | Promise<boolean>;\n}\n\n/**\n * Parent-frame counterpart to {@link IExtensionSDK}.\n *\n * Listens for child messages and replies with the shared wire protocol.\n * Create one instance per iframe.\n */\nexport interface IExtensionSDKHost {\n /**\n * Attaches the child message listener. Safe to call once; no-ops if already\n * started or destroyed.\n */\n start(): void;\n\n /**\n * Tears down the listener. The instance cannot be restarted after destroy.\n */\n destroy(): void;\n\n /**\n * Asks the child to open page settings UI with the given values.\n *\n * Wire: `LOAD_PAGE_SETTINGS` (fire-and-forget).\n */\n emitLoadPageSettings(settings: PageSettings | null): void;\n}\n\n/** Default {@link IExtensionSDKHost} implementation. */\nexport class ExtensionSDKHost implements IExtensionSDKHost {\n #options: ExtensionSDKHostOptions;\n #unsubscribe: (() => void) | null = null;\n #started = false;\n #destroyed = false;\n\n constructor(options: ExtensionSDKHostOptions) {\n this.#options = options;\n }\n\n start(): void {\n if (this.#destroyed || this.#started) {\n return;\n }\n\n this.#unsubscribe = subscribeToChildMessages(\n this.#options.origin,\n (event) => {\n void this.#onMessage(event);\n },\n );\n this.#started = true;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#started = false;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n }\n\n emitLoadPageSettings(settings: PageSettings | null): void {\n this.#assertNotDestroyed();\n this.#post(CONNECTION_EVENTS.loadPageSettings, settings);\n }\n\n async #onMessage(event: MessageEvent<ExtensionMessage>): Promise<void> {\n if (this.#destroyed) {\n return;\n }\n\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeInit: {\n await this.#handleHandshakeInit();\n break;\n }\n case CONNECTION_EVENTS.requestAuthTokenRefresh: {\n await this.#handleAuthTokenRefresh();\n break;\n }\n case CONNECTION_EVENTS.updatePageSettings: {\n await this.#handleUpdatePageSettings(\n payload as UpdatePageSettingsPayload | undefined,\n );\n break;\n }\n default: {\n break;\n }\n }\n }\n\n async #handleHandshakeInit(): Promise<void> {\n try {\n const handshakePayload = await this.#options.resolveHandshakePayload();\n if (this.#destroyed) {\n return;\n }\n this.#post(CONNECTION_EVENTS.handshakeAck, handshakePayload);\n } catch {\n // Platform owns error UI / retry; do not reply with a partial ACK.\n }\n }\n\n async #handleAuthTokenRefresh(): Promise<void> {\n try {\n const authToken = await this.#options.resolveAuthToken();\n if (this.#destroyed) {\n return;\n }\n this.#post(CONNECTION_EVENTS.authTokenRefresh, { authToken });\n } catch {\n // Platform owns error UI / retry; do not reply with an invalid token.\n }\n }\n\n async #handleUpdatePageSettings(\n payload: UpdatePageSettingsPayload | undefined,\n ): Promise<void> {\n const settings = payload?.settings;\n if (!settings || typeof settings !== \"object\") {\n this.#postPageSettingsUpdated(false);\n return;\n }\n\n const handler = this.#options.onUpdatePageSettings;\n if (!handler) {\n this.#postPageSettingsUpdated(false);\n return;\n }\n\n try {\n const success = await handler(settings);\n if (this.#destroyed) {\n return;\n }\n this.#postPageSettingsUpdated(Boolean(success));\n } catch {\n if (this.#destroyed) {\n return;\n }\n this.#postPageSettingsUpdated(false);\n }\n }\n\n #postPageSettingsUpdated(success: boolean): void {\n const payload: PageSettingsUpdatedPayload = { success };\n this.#post(CONNECTION_EVENTS.pageSettingsUpdated, payload);\n }\n\n #post(type: (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS], payload?: unknown): void {\n const contentWindow = this.#options.getContentWindow();\n if (!contentWindow) {\n return;\n }\n postToChild(contentWindow, this.#options.origin, type, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"ExtensionSDKHost has been destroyed.\");\n }\n }\n}\n","/**\n * Returns whether `urlRaw` is a valid HTTPS extension URL.\n *\n * When `checkOrigin` is provided, the URL's origin must also pass that predicate\n * (e.g. hostname ends with `.kadanza.app`).\n */\nexport function isValidExtensionUrl(\n urlRaw: string | null | undefined,\n checkOrigin?: (origin: string) => boolean,\n): boolean {\n if (typeof urlRaw !== \"string\") {\n return false;\n }\n\n try {\n const url = new URL(urlRaw);\n const allowedProtocol = url.protocol === \"https:\";\n const allowedOrigin = checkOrigin ? checkOrigin(url.origin) : true;\n return allowedProtocol && allowedOrigin;\n } catch {\n return false;\n }\n}\n\n/**\n * Clones `urlRaw` and sets the `tenantUrl` search param (parent origin).\n *\n * @param urlRaw - Absolute extension URL string.\n * @param tenantUrl - Parent origin to embed (defaults to `window.location.origin`).\n * @returns Enriched `URL`, or `null` when `urlRaw` is missing or unparsable.\n */\nexport function enrichExtensionUrl(\n urlRaw: string | null | undefined,\n tenantUrl: string = window.location.origin,\n): URL | null {\n if (typeof urlRaw !== \"string\") {\n return null;\n }\n\n try {\n const enrichedUrl = new URL(urlRaw);\n\n if (typeof tenantUrl === \"string\") {\n enrichedUrl.searchParams.set(\"tenantUrl\", tenantUrl);\n }\n\n return enrichedUrl;\n } catch {\n return null;\n }\n}\n","import { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport {\n ExtensionSDKHost,\n type ExtensionSDKHostOptions,\n type IExtensionSDKHost,\n} from \"./ExtensionSDKHost\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\nexport {\n postToChild,\n subscribeToChildMessages,\n type HostMessageHandler,\n} from \"./hostMessaging\";\nexport { enrichExtensionUrl, isValidExtensionUrl } from \"./hostUrl\";\nexport {\n InvalidOriginError,\n readTenantUrlFromLocation,\n resolveAllowedOrigin,\n} from \"./origin\";\nexport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nlet instance: IExtensionSDK | null = null;\nlet hasWarnedDuplicateCreate = false;\n\n/** Clears the factory singleton when the managed instance is destroyed. */\nclass ManagedExtensionSDK extends ExtensionSDK {\n destroy(): void {\n super.destroy();\n if (instance === this) {\n instance = null;\n hasWarnedDuplicateCreate = false;\n }\n }\n}\n\n/**\n * Returns the shared {@link IExtensionSDK} for the current extension frame.\n *\n * Creates the instance on first call. Later calls return the same instance and\n * log a console warning — use the SDK once per app. After {@link IExtensionSDK.destroy},\n * the next call creates a fresh instance.\n */\nexport const createExtensionSDK = (): IExtensionSDK => {\n if (instance) {\n if (!hasWarnedDuplicateCreate) {\n console.warn(\n `[${__PACKAGE_NAME__}] createExtensionSDK() was called more than once. ` +\n \"The SDK is a singleton — reuse the same instance for the whole app.\",\n );\n hasWarnedDuplicateCreate = true;\n }\n return instance;\n }\n\n instance = new ManagedExtensionSDK();\n return instance;\n};\n"],"mappings":"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"}
@@ -77,7 +77,10 @@ function l(e = window.location.search) {
77
77
  }
78
78
  //#endregion
79
79
  //#region src/ExtensionSDK.ts
80
- var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
80
+ function u(e) {
81
+ return typeof e == "object" && !!e && !Array.isArray(e);
82
+ }
83
+ var d = 1e4, f = 12e4, p = 3e4, m = 2147483647, h = class {
81
84
  #e = null;
82
85
  #t = null;
83
86
  #n = null;
@@ -88,7 +91,7 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
88
91
  #s = null;
89
92
  #c = null;
90
93
  #l = !1;
91
- #u = d;
94
+ #u = f;
92
95
  #d = null;
93
96
  #f = !1;
94
97
  #p = null;
@@ -101,17 +104,12 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
101
104
  return this.#r;
102
105
  }
103
106
  async connect(e = {}) {
104
- if (this.#w(), this.#r && this.#a && this.#o && this.#s) return this.#E(e), this.#A(), {
105
- authToken: this.#a,
106
- extensionDetails: this.#o,
107
- designTokens: this.#s,
108
- pageSettings: this.#c
109
- };
107
+ if (this.#T(), this.#r) return this.#D(e), this.#j(), this.#b();
110
108
  if (this.#m) return this.#m;
111
- this.#E(e);
109
+ this.#D(e);
112
110
  let t = l();
113
111
  this.#e = c(t), this.#t = t, this.#n?.(), this.#n = a(this.#e, (e) => this.#y(e));
114
- let n = e.timeoutMs ?? u, o = new Promise((e, t) => {
112
+ let n = e.timeoutMs ?? d, o = new Promise((e, t) => {
115
113
  let r = setTimeout(() => {
116
114
  this.#p = null, this.#m = null, t(/* @__PURE__ */ Error(`Handshake timed out after ${n}ms.`));
117
115
  }, n);
@@ -125,12 +123,12 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
125
123
  try {
126
124
  i(r.handshakeInit, this.#e);
127
125
  } catch (e) {
128
- throw this.#N(this.#p), this.#p = null, this.#m = null, this.#n?.(), this.#n = null, this.#e = null, this.#t = null, this.#k(), e;
126
+ throw this.#P(this.#p), this.#p = null, this.#m = null, this.#n?.(), this.#n = null, this.#e = null, this.#t = null, this.#A(), e;
129
127
  }
130
128
  return o;
131
129
  }
132
130
  destroy() {
133
- this.#i = !0, this.#r = !1, this.#k(), this.#l = !1, this.#f = !1, this.#n?.(), this.#n = null, this.#e = null, this.#t = null, this.#F(this.#p, /* @__PURE__ */ Error("SDK destroyed.")), this.#F(this.#h, /* @__PURE__ */ Error("SDK destroyed.")), this.#F(this.#g, /* @__PURE__ */ Error("SDK destroyed.")), this.#p = null, this.#m = null, this.#h = null, this.#g = null, this.#_.clear(), this.#v.clear(), this.#a = null, this.#o = null, this.#s = null, this.#c = null;
131
+ this.#i = !0, this.#r = !1, this.#A(), this.#l = !1, this.#f = !1, this.#n?.(), this.#n = null, this.#e = null, this.#t = null, this.#I(this.#p, /* @__PURE__ */ Error("SDK destroyed.")), this.#I(this.#h, /* @__PURE__ */ Error("SDK destroyed.")), this.#I(this.#g, /* @__PURE__ */ Error("SDK destroyed.")), this.#p = null, this.#m = null, this.#h = null, this.#g = null, this.#_.clear(), this.#v.clear(), this.#a = null, this.#o = null, this.#s = null, this.#c = null;
134
132
  }
135
133
  getAuthToken() {
136
134
  return this.#a;
@@ -151,18 +149,27 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
151
149
  return this.#t;
152
150
  }
153
151
  getApiUrl() {
154
- return this.#o ? e(this.#o.baseUrl) : null;
152
+ let t = this.#o?.baseUrl;
153
+ if (!t) return null;
154
+ try {
155
+ return e(t);
156
+ } catch {
157
+ return null;
158
+ }
155
159
  }
156
160
  async apiCall(e, t = {}) {
157
- return this.#T(), n(e, {
158
- baseUrl: this.#o.baseUrl,
159
- tenantDomain: this.#o.tenantDomain,
160
- authTokenJwt: this.#a.jwt
161
+ this.#E();
162
+ let r = this.#o?.baseUrl, i = this.#o?.tenantDomain, a = this.#a?.jwt;
163
+ if (!r || !i || !a) throw Error("API calls require handshake authToken and extensionDetails (baseUrl, tenantDomain).");
164
+ return n(e, {
165
+ baseUrl: r,
166
+ tenantDomain: i,
167
+ authTokenJwt: a
161
168
  }, t);
162
169
  }
163
170
  async emitRequestAuthTokenRefresh(e = {}) {
164
- if (this.#T(), this.#h) throw Error("Auth token refresh already in progress.");
165
- let t = e.timeoutMs ?? u, n = this.#e, a = new Promise((e, n) => {
171
+ if (this.#E(), this.#h) throw Error("Auth token refresh already in progress.");
172
+ let t = e.timeoutMs ?? d, n = this.#e, a = new Promise((e, n) => {
166
173
  let r = setTimeout(() => {
167
174
  this.#h = null, n(/* @__PURE__ */ Error(`Auth token refresh timed out after ${t}ms.`));
168
175
  }, t);
@@ -175,8 +182,8 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
175
182
  return i(r.requestAuthTokenRefresh, n), a;
176
183
  }
177
184
  async emitUpdatePageSettings(e, t = {}) {
178
- if (this.#T(), this.#g) throw Error("Page settings update already in progress.");
179
- let n = t.timeoutMs ?? u, a = this.#e, o = new Promise((e, t) => {
185
+ if (this.#E(), this.#g) throw Error("Page settings update already in progress.");
186
+ let n = t.timeoutMs ?? d, a = this.#e, o = new Promise((e, t) => {
180
187
  let r = setTimeout(() => {
181
188
  this.#g = null, t(/* @__PURE__ */ Error(`Page settings update timed out after ${n}ms.`));
182
189
  }, n);
@@ -199,145 +206,144 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
199
206
  };
200
207
  }
201
208
  emitNavigationChange(e) {
202
- this.#T(), i(r.navigationChange, this.#e, e);
209
+ this.#E(), i(r.navigationChange, this.#e, e);
203
210
  }
204
211
  #y(e) {
205
212
  let { type: t, payload: n } = e.data;
206
213
  switch (t) {
207
214
  case r.handshakeAck:
208
- this.#b(n);
215
+ this.#x(n);
209
216
  break;
210
217
  case r.authTokenRefresh:
211
- this.#x(n);
218
+ this.#S(n);
212
219
  break;
213
220
  case r.loadPageSettings:
214
- this.#S(n);
221
+ this.#C(n);
215
222
  break;
216
223
  case r.pageSettingsUpdated:
217
- this.#C(n);
224
+ this.#w(n);
218
225
  break;
219
226
  default: break;
220
227
  }
221
228
  }
222
- #b(e) {
223
- if (!e?.authToken || !e.extensionDetails || !e.designTokens) {
224
- this.#F(this.#p, /* @__PURE__ */ Error("Invalid HANDSHAKE_ACK payload.")), this.#p = null, this.#m = null;
225
- return;
226
- }
227
- this.#a = e.authToken, this.#o = e.extensionDetails, this.#s = e.designTokens, this.#c = e.pageSettings ?? null, this.#r = !0;
228
- let t = this.#p;
229
- this.#p = null, this.#m = null, this.#P(t, {
229
+ #b() {
230
+ return {
230
231
  authToken: this.#a,
231
232
  extensionDetails: this.#o,
232
233
  designTokens: this.#s,
233
234
  pageSettings: this.#c
234
- }), this.#f = !1, this.#A();
235
+ };
235
236
  }
236
237
  #x(e) {
238
+ this.#a = u(e?.authToken) ? e?.authToken : null, this.#o = u(e?.extensionDetails) ? e?.extensionDetails : null, this.#s = u(e?.designTokens) ? e?.designTokens : null, this.#c = u(e?.pageSettings) ? e?.pageSettings : null, this.#r = !0;
239
+ let t = this.#p;
240
+ this.#p = null, this.#m = null, this.#F(t, this.#b()), this.#f = !1, this.#j();
241
+ }
242
+ #S(e) {
237
243
  if (!e?.authToken) {
238
- this.#F(this.#h, /* @__PURE__ */ Error("Invalid TOKEN_REFRESH payload.")), this.#h = null;
244
+ this.#I(this.#h, /* @__PURE__ */ Error("Invalid TOKEN_REFRESH payload.")), this.#h = null;
239
245
  return;
240
246
  }
241
247
  this.#a = e.authToken;
242
248
  let t = this.#h;
243
- this.#h = null, this.#P(t, this.#a);
249
+ this.#h = null, this.#F(t, this.#a);
244
250
  for (let e of this.#v) e(this.#a);
245
- this.#f = !1, this.#A();
251
+ this.#f = !1, this.#j();
246
252
  }
247
- #S(e) {
253
+ #C(e) {
248
254
  let t = e ?? {};
249
255
  this.#c = t;
250
256
  for (let e of this.#_) e(t);
251
257
  }
252
- #C(e) {
258
+ #w(e) {
253
259
  if (!e || typeof e.success != "boolean") {
254
- this.#F(this.#g, /* @__PURE__ */ Error("Invalid PAGE_SETTINGS_UPDATED payload.")), this.#g = null;
260
+ this.#I(this.#g, /* @__PURE__ */ Error("Invalid PAGE_SETTINGS_UPDATED payload.")), this.#g = null;
255
261
  return;
256
262
  }
257
263
  let t = this.#g;
258
- this.#g = null, this.#P(t, e);
264
+ this.#g = null, this.#F(t, e);
259
265
  }
260
- #w() {
266
+ #T() {
261
267
  if (this.#i) throw Error("SDK has been destroyed.");
262
268
  }
263
- #T() {
264
- if (this.#w(), !this.#r || !this.#e) throw Error("SDK is not connected. Call connect() first.");
269
+ #E() {
270
+ if (this.#T(), !this.#r || !this.#e) throw Error("SDK is not connected. Call connect() first.");
265
271
  }
266
- #E(e) {
272
+ #D(e) {
267
273
  if (e.authTokenBufferMs !== void 0 && (!Number.isFinite(e.authTokenBufferMs) || e.authTokenBufferMs < 0)) throw Error("authTokenBufferMs must be a finite non-negative number.");
268
- this.#l = e.authTokenAutoRefresh === !0, this.#u = e.authTokenBufferMs ?? d, this.#f = !1, this.#k();
274
+ this.#l = e.authTokenAutoRefresh === !0, this.#u = e.authTokenBufferMs ?? f, this.#f = !1, this.#A();
269
275
  }
270
- #D(e) {
276
+ #O(e) {
271
277
  let t = Number(e.expires);
272
278
  return Number.isFinite(t) ? t * 1e3 : null;
273
279
  }
274
- #O(e) {
275
- let t = this.#D(e);
280
+ #k(e) {
281
+ let t = this.#O(e);
276
282
  return t === null ? null : Math.max(0, t - this.#u - Date.now());
277
283
  }
278
- #k() {
284
+ #A() {
279
285
  this.#d !== null && (clearTimeout(this.#d), this.#d = null);
280
286
  }
281
- #A() {
282
- if (this.#k(), !this.#l || !this.#r || this.#i || !this.#a) return;
283
- let e = this.#O(this.#a);
287
+ #j() {
288
+ if (this.#A(), !this.#l || !this.#r || this.#i || !this.#a) return;
289
+ let e = this.#k(this.#a);
284
290
  if (e === null) return;
285
- let t = Math.min(e, p);
291
+ let t = Math.min(e, m);
286
292
  this.#d = setTimeout(() => {
287
- this.#d = null, this.#j();
293
+ this.#d = null, this.#M();
288
294
  }, t);
289
295
  }
290
- async #j() {
296
+ async #M() {
291
297
  if (!this.#l || !this.#r || this.#i || !this.#a) return;
292
- let e = this.#a, t = this.#O(e);
298
+ let e = this.#a, t = this.#k(e);
293
299
  if (t !== null) {
294
300
  if (t > 0) {
295
- this.#A();
301
+ this.#j();
296
302
  return;
297
303
  }
298
304
  if (this.#h) {
299
- this.#M(e);
305
+ this.#N(e);
300
306
  return;
301
307
  }
302
308
  try {
303
309
  await this.emitRequestAuthTokenRefresh();
304
310
  } catch {
305
- this.#M(e);
311
+ this.#N(e);
306
312
  }
307
313
  }
308
314
  }
309
- #M(e) {
315
+ #N(e) {
310
316
  if (!this.#l || !this.#r || this.#i || this.#a?.jwt !== e.jwt || this.#f) return;
311
- let t = this.#D(e);
317
+ let t = this.#O(e);
312
318
  if (t === null) return;
313
319
  let n = t - Date.now();
314
320
  if (n <= 0) return;
315
- this.#f = !0, this.#k();
316
- let r = Math.min(f, n);
321
+ this.#f = !0, this.#A();
322
+ let r = Math.min(p, n);
317
323
  this.#d = setTimeout(() => {
318
- this.#d = null, this.#j();
324
+ this.#d = null, this.#M();
319
325
  }, r);
320
326
  }
321
- #N(e) {
327
+ #P(e) {
322
328
  e && clearTimeout(e.timer);
323
329
  }
324
- #P(e, t) {
330
+ #F(e, t) {
325
331
  e && (clearTimeout(e.timer), e.resolve(t));
326
332
  }
327
- #F(e, t) {
333
+ #I(e, t) {
328
334
  e && (clearTimeout(e.timer), e.reject(t));
329
335
  }
330
336
  };
331
337
  //#endregion
332
338
  //#region src/hostMessaging.ts
333
- function h(e, t, n, r) {
339
+ function g(e, t, n, r) {
334
340
  let i = r === void 0 ? { type: n } : {
335
341
  type: n,
336
342
  payload: r
337
343
  };
338
344
  e.postMessage(i, t);
339
345
  }
340
- function g(e, t) {
346
+ function _(e, t) {
341
347
  let n = (n) => {
342
348
  n.origin === e && (!n.data || typeof n.data != "object" || !("type" in n.data) || t(n));
343
349
  };
@@ -345,7 +351,7 @@ function g(e, t) {
345
351
  }
346
352
  //#endregion
347
353
  //#region src/ExtensionSDKHost.ts
348
- var _ = class {
354
+ var v = class {
349
355
  #e;
350
356
  #t = null;
351
357
  #n = !1;
@@ -354,7 +360,7 @@ var _ = class {
354
360
  this.#e = e;
355
361
  }
356
362
  start() {
357
- this.#r || this.#n || (this.#t = g(this.#e.origin, (e) => {
363
+ this.#r || this.#n || (this.#t = _(this.#e.origin, (e) => {
358
364
  this.#i(e);
359
365
  }), this.#n = !0);
360
366
  }
@@ -420,7 +426,7 @@ var _ = class {
420
426
  }
421
427
  #l(e, t) {
422
428
  let n = this.#e.getContentWindow();
423
- n && h(n, this.#e.origin, e, t);
429
+ n && g(n, this.#e.origin, e, t);
424
430
  }
425
431
  #u() {
426
432
  if (this.#r) throw Error("ExtensionSDKHost has been destroyed.");
@@ -428,7 +434,7 @@ var _ = class {
428
434
  };
429
435
  //#endregion
430
436
  //#region src/hostUrl.ts
431
- function v(e, t) {
437
+ function y(e, t) {
432
438
  if (typeof e != "string") return !1;
433
439
  try {
434
440
  let n = new URL(e), r = n.protocol === "https:", i = !t || t(n.origin);
@@ -437,7 +443,7 @@ function v(e, t) {
437
443
  return !1;
438
444
  }
439
445
  }
440
- function y(e, t = window.location.origin) {
446
+ function b(e, t = window.location.origin) {
441
447
  if (typeof e != "string") return null;
442
448
  try {
443
449
  let n = new URL(e);
@@ -448,12 +454,12 @@ function y(e, t = window.location.origin) {
448
454
  }
449
455
  //#endregion
450
456
  //#region src/index.ts
451
- var b = null, x = !1, S = class extends m {
457
+ var x = null, S = !1, C = class extends h {
452
458
  destroy() {
453
- super.destroy(), b === this && (b = null, x = !1);
459
+ super.destroy(), x === this && (x = null, S = !1);
454
460
  }
455
- }, C = () => b ? (x ||= (console.warn("[@kadanza/extension-sdk] createExtensionSDK() was called more than once. The SDK is a singleton — reuse the same instance for the whole app."), !0), b) : (b = new S(), b);
461
+ }, w = () => x ? (S ||= (console.warn("[@kadanza/extension-sdk] createExtensionSDK() was called more than once. The SDK is a singleton — reuse the same instance for the whole app."), !0), x) : (x = new C(), x);
456
462
  //#endregion
457
- export { r as CONNECTION_EVENTS, m as ExtensionSDK, _ as ExtensionSDKHost, s as InvalidOriginError, C as createExtensionSDK, y as enrichExtensionUrl, v as isValidExtensionUrl, h as postToChild, l as readTenantUrlFromLocation, c as resolveAllowedOrigin, g as subscribeToChildMessages };
463
+ export { r as CONNECTION_EVENTS, h as ExtensionSDK, v as ExtensionSDKHost, s as InvalidOriginError, w as createExtensionSDK, b as enrichExtensionUrl, y as isValidExtensionUrl, g as postToChild, l as readTenantUrlFromLocation, c as resolveAllowedOrigin, _ as subscribeToChildMessages };
458
464
 
459
465
  //# sourceMappingURL=extension-sdk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension-sdk.js","names":["#connected","#assertNotDestroyed","#authToken","#extensionDetails","#designTokens","#configureAuthTokenAutoRefresh","#scheduleProactiveAuthTokenRefresh","#pageSettings","#handshakePromise","#allowedOrigin","#tenantUrl","#unsubscribe","#onMessage","#pendingHandshake","#clearPending","#clearProactiveAuthTokenRefreshTimer","#destroyed","#authTokenAutoRefresh","#authTokenRefreshRetryUsed","#rejectPending","#pendingAuthTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#authTokenRefreshHandlers","#assertConnected","#handleHandshakeAck","#handleAuthTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending","#authTokenBufferMs","#parseExpiresAtMs","#authTokenRefreshTimer","#getProactiveRefreshDelayMs","#runProactiveAuthTokenRefresh","#handleProactiveAuthTokenRefreshFailure","#options","#destroyed","#started","#unsubscribe","#onMessage","#assertNotDestroyed","#post","#handleHandshakeInit","#handleAuthTokenRefresh","#handleUpdatePageSettings","#postPageSettingsUpdated"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/hostMessaging.ts","../src/ExtensionSDKHost.ts","../src/hostUrl.ts","../src/index.ts"],"sourcesContent":["interface ApiRequestContext {\n baseUrl: string;\n tenantDomain: string;\n authTokenJwt: string;\n}\n\n/** Derives the Platform API origin from handshake `baseUrl`. */\nexport function deriveApiUrl(baseUrl: string): string {\n if (baseUrl.startsWith(\"/\")) {\n return baseUrl;\n }\n\n const url = new URL(baseUrl);\n url.hostname = `api.${url.hostname}`;\n return url.origin;\n}\n\nfunction resolveApiEndpoint(baseUrl: string, endpoint: string): string {\n if (!endpoint.startsWith(\"/\") || endpoint.startsWith(\"//\")) {\n throw new Error(\n 'API endpoint must be a root-relative path starting with a single \"/\".',\n );\n }\n\n return `${deriveApiUrl(baseUrl).replace(/\\/+$/, \"\")}${endpoint}`;\n}\n\nexport async function callApi<T>(\n endpoint: string,\n context: ApiRequestContext,\n options: RequestInit = {},\n): Promise<T> {\n const headers = new Headers(options.headers);\n\n if (!headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n headers.set(\"Authorization\", `Bearer ${context.authTokenJwt}`);\n headers.set(\"X-Tenant\", context.tenantDomain);\n\n const response = await fetch(resolveApiEndpoint(context.baseUrl, endpoint), {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n const statusText = response.statusText ? ` ${response.statusText}` : \"\";\n throw new Error(`API call failed: ${response.status}${statusText}.`);\n }\n\n return (await response.json()) as T;\n}\n","/**\n * Wire event type strings for parent/child `postMessage` communication.\n *\n * Keys are SDK-facing names; values are the strings sent on the wire.\n * Comments mark message direction relative to the extension (child).\n */\nexport const CONNECTION_EVENTS = {\n /** Child → parent: start handshake. */\n handshakeInit: \"HANDSHAKE_INIT\",\n /** Parent → child: handshake success with auth and context. */\n handshakeAck: \"HANDSHAKE_ACK\",\n /** Child → parent: request a new auth token. */\n requestAuthTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n /** Parent → child: new auth token (reply or push). */\n authTokenRefresh: \"TOKEN_REFRESH\",\n /** Parent → child: open/load page settings UI with values. */\n loadPageSettings: \"LOAD_PAGE_SETTINGS\",\n /** Child → parent: persist updated page settings. */\n updatePageSettings: \"UPDATE_PAGE_SETTINGS\",\n /** Parent → child: result of a page settings update. */\n pageSettingsUpdated: \"PAGE_SETTINGS_UPDATED\",\n /** Child → parent: extension route changed. */\n navigationChange: \"NAVIGATION_CHANGE\",\n} as const;\n\n/** Union of all {@link CONNECTION_EVENTS} wire string values. */\nexport type ConnectionEvent =\n (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS];\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type MessageHandler = (event: MessageEvent<ExtensionMessage>) => void;\n\nexport function postToParent(\n type: ConnectionEvent,\n allowedOrigin: string,\n payload?: unknown,\n): void {\n if (!window.parent || window.parent === window) {\n throw new Error(\"Extension is not running inside a parent frame.\");\n }\n\n const message: ExtensionMessage = payload === undefined ? { type } : { type, payload };\n window.parent.postMessage(message, allowedOrigin);\n}\n\nexport function subscribeToParentMessages(\n allowedOrigin: string,\n handler: MessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== allowedOrigin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","const LOCALHOST_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */\nexport class InvalidOriginError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidOriginError\";\n }\n}\n\n/**\n * Parses and validates a tenant URL into a postMessage-safe origin.\n * Allows https always; http only for localhost / 127.0.0.1 / [::1].\n *\n * @param tenantUrl - Absolute URL from the `tenantUrl` search param.\n * @returns Origin string suitable for `postMessage` targetOrigin.\n * @throws {InvalidOriginError} When missing, unparsable, or disallowed scheme/host.\n */\nexport function resolveAllowedOrigin(tenantUrl: string | null): string {\n if (!tenantUrl) {\n throw new InvalidOriginError(\n 'Missing \"tenantUrl\" search parameter required to establish parent origin.',\n );\n }\n\n let url: URL;\n try {\n url = new URL(tenantUrl);\n } catch {\n throw new InvalidOriginError(`Invalid tenantUrl: \"${tenantUrl}\".`);\n }\n\n const isLocalhost = LOCALHOST_HOSTS.has(url.hostname);\n const isHttps = url.protocol === \"https:\";\n const isLocalHttp = url.protocol === \"http:\" && isLocalhost;\n\n if (!isHttps && !isLocalHttp) {\n throw new InvalidOriginError(\n `tenantUrl must use https (or http on localhost). Received: \"${url.protocol}//${url.host}\".`,\n );\n }\n\n return url.origin;\n}\n\n/**\n * Reads the parent-supplied `tenantUrl` from a URL search string.\n *\n * The parent must pass `tenantUrl` on the iframe `src` so\n * {@link resolveAllowedOrigin} can whitelist the parent origin.\n *\n * @param search - Query string (defaults to `window.location.search`).\n */\nexport function readTenantUrlFromLocation(\n search: string = window.location.search,\n): string | null {\n return new URLSearchParams(search).get(\"tenantUrl\");\n}\n","import { callApi, deriveApiUrl } from \"./api\";\nimport { CONNECTION_EVENTS } from \"./events\";\nimport { postToParent, subscribeToParentMessages } from \"./messaging\";\nimport { readTenantUrlFromLocation, resolveAllowedOrigin } from \"./origin\";\nimport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_AUTH_TOKEN_BUFFER_MS = 120_000;\nconst AUTO_REFRESH_RETRY_DELAY_MS = 30_000;\n/** Largest delay `setTimeout` can reliably use in browsers. */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\n/**\n * Public contract for talking to the Kadanza parent frame over `postMessage`.\n *\n * Requires a `tenantUrl` search param on the extension URL so\n * {@link connect} can validate the parent origin.\n */\nexport interface IExtensionSDK {\n /**\n * Establishes the parent connection and waits for `HANDSHAKE_ACK`.\n *\n * Safe to call repeatedly (e.g. from a React `useEffect` with deps):\n * - Already connected: returns the cached handshake and re-applies\n * `authTokenAutoRefresh` / `authTokenBufferMs` without another init.\n * `timeoutMs` is ignored after connect.\n * - Handshake in progress: returns the same in-flight promise. Options from\n * the first connecting call apply until the handshake completes.\n *\n * Opt-in proactive refresh can be enabled with\n * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).\n *\n * @param options - Handshake timeout and optional auth-token auto-refresh.\n * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.\n * @throws When not embedded in a parent frame, destroyed, or the handshake\n * times out / returns an invalid payload.\n */\n connect(options?: ConnectOptions): Promise<HandshakePayload>;\n\n /**\n * Tears down listeners, proactive refresh timers, and cached state.\n * Rejects any in-flight requests. The instance cannot be reused after destroy.\n * When created via {@link createExtensionSDK}, clears the singleton so a\n * later create call can return a fresh instance.\n */\n destroy(): void;\n\n /** Whether a successful handshake has completed and not been destroyed. */\n readonly isConnected: boolean;\n\n /** Last auth token from handshake or refresh; `null` until connected. */\n getAuthToken(): AuthToken | null;\n\n /** Extension context from handshake; `null` until connected. */\n getExtensionDetails(): ExtensionDetails | null;\n\n /** Design tokens from handshake; `null` until connected. */\n getDesignTokens(): DesignTokens | null;\n\n /**\n * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;\n * `null` until set.\n */\n getPageSettings(): PageSettings | null;\n\n /**\n * Validated parent `postMessage` origin from `tenantUrl`;\n * `null` until `connect` resolves it.\n */\n getAllowedOrigin(): string | null;\n\n /**\n * Raw `tenantUrl` search-param value used for the allowed origin;\n * `null` until `connect` reads it.\n */\n getTenantUrl(): string | null;\n\n /**\n * Platform API origin derived from handshake `baseUrl`;\n * `null` until connected.\n */\n getApiUrl(): string | null;\n\n /**\n * Calls the Kadanza Platform API with the current connection credentials.\n *\n * The API origin is derived from the handshake `baseUrl`. Authorization and\n * tenant headers are managed by the SDK and cannot be overridden.\n *\n * @typeParam T - Expected JSON response body.\n * @param endpoint - Root-relative API path.\n * @param options - Standard fetch options.\n * @throws When not connected, the endpoint is invalid, the request fails,\n * or the response is not successful JSON.\n */\n apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;\n\n /**\n * Asks the parent for a new auth token and waits for `TOKEN_REFRESH`.\n *\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, a refresh is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitRequestAuthTokenRefresh(\n options?: RequestOptions,\n ): Promise<AuthToken>;\n\n /**\n * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.\n *\n * @param payload - Update payload containing the settings object.\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, an update is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options?: RequestOptions,\n ): Promise<PageSettingsUpdatedPayload>;\n\n /**\n * Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.\n *\n * @returns Unsubscribe function.\n */\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;\n\n /**\n * Registers a handler for auth-token updates (requested or parent-pushed).\n *\n * @returns Unsubscribe function.\n */\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;\n\n /**\n * Notifies the parent that the extension's route changed.\n *\n * Wire: `NAVIGATION_CHANGE` (fire-and-forget).\n *\n * @param payload - Navigation change payload (`path` within the extension).\n * @throws When not connected or destroyed.\n */\n emitNavigationChange(payload: NavigationChangePayload): void;\n}\n\ntype Pending<T> = {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */\nexport class ExtensionSDK implements IExtensionSDK {\n #allowedOrigin: string | null = null;\n #tenantUrl: string | null = null;\n #unsubscribe: (() => void) | null = null;\n #connected = false;\n #destroyed = false;\n\n #authToken: AuthToken | null = null;\n #extensionDetails: ExtensionDetails | null = null;\n #designTokens: DesignTokens | null = null;\n #pageSettings: PageSettings | null = null;\n\n #authTokenAutoRefresh = false;\n #authTokenBufferMs = DEFAULT_AUTH_TOKEN_BUFFER_MS;\n #authTokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n #authTokenRefreshRetryUsed = false;\n\n #pendingHandshake: Pending<HandshakePayload> | null = null;\n #handshakePromise: Promise<HandshakePayload> | null = null;\n #pendingAuthTokenRefresh: Pending<AuthToken> | null = null;\n #pendingPageSettingsUpdate: Pending<PageSettingsUpdatedPayload> | null = null;\n\n #loadPageSettingsHandlers = new Set<(settings: PageSettings) => void>();\n #authTokenRefreshHandlers = new Set<(authToken: AuthToken) => void>();\n\n get isConnected(): boolean {\n return this.#connected;\n }\n\n async connect(options: ConnectOptions = {}): Promise<HandshakePayload> {\n this.#assertNotDestroyed();\n\n if (this.#connected && this.#authToken && this.#extensionDetails && this.#designTokens) {\n this.#configureAuthTokenAutoRefresh(options);\n this.#scheduleProactiveAuthTokenRefresh();\n return {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n };\n }\n\n if (this.#handshakePromise) {\n return this.#handshakePromise;\n }\n\n this.#configureAuthTokenAutoRefresh(options);\n\n const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(tenantUrl);\n this.#tenantUrl = tenantUrl;\n\n this.#unsubscribe?.();\n this.#unsubscribe = subscribeToParentMessages(\n this.#allowedOrigin,\n (event) => this.#onMessage(event),\n );\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const handshakePromise = new Promise<HandshakePayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n this.#handshakePromise = handshakePromise;\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#tenantUrl = null;\n this.#clearProactiveAuthTokenRefreshTimer();\n throw error;\n }\n\n return handshakePromise;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#connected = false;\n\n this.#clearProactiveAuthTokenRefreshTimer();\n this.#authTokenAutoRefresh = false;\n this.#authTokenRefreshRetryUsed = false;\n\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#tenantUrl = null;\n\n this.#rejectPending(this.#pendingHandshake, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingAuthTokenRefresh, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingPageSettingsUpdate, new Error(\"SDK destroyed.\"));\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#pendingAuthTokenRefresh = null;\n this.#pendingPageSettingsUpdate = null;\n\n this.#loadPageSettingsHandlers.clear();\n this.#authTokenRefreshHandlers.clear();\n\n this.#authToken = null;\n this.#extensionDetails = null;\n this.#designTokens = null;\n this.#pageSettings = null;\n }\n\n getAuthToken(): AuthToken | null {\n return this.#authToken;\n }\n\n getExtensionDetails(): ExtensionDetails | null {\n return this.#extensionDetails;\n }\n\n getDesignTokens(): DesignTokens | null {\n return this.#designTokens;\n }\n\n getPageSettings(): PageSettings | null {\n return this.#pageSettings;\n }\n\n getAllowedOrigin(): string | null {\n return this.#allowedOrigin;\n }\n\n getTenantUrl(): string | null {\n return this.#tenantUrl;\n }\n\n getApiUrl(): string | null {\n if (!this.#extensionDetails) {\n return null;\n }\n\n return deriveApiUrl(this.#extensionDetails.baseUrl);\n }\n\n async apiCall<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n this.#assertConnected();\n\n return callApi<T>(\n endpoint,\n {\n baseUrl: this.#extensionDetails!.baseUrl,\n tenantDomain: this.#extensionDetails!.tenantDomain,\n authTokenJwt: this.#authToken!.jwt,\n },\n options,\n );\n }\n\n async emitRequestAuthTokenRefresh(\n options: RequestOptions = {},\n ): Promise<AuthToken> {\n this.#assertConnected();\n\n if (this.#pendingAuthTokenRefresh) {\n throw new Error(\"Auth token refresh already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<AuthToken>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingAuthTokenRefresh = null;\n reject(new Error(`Auth token refresh timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingAuthTokenRefresh = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.requestAuthTokenRefresh, allowedOrigin);\n return promise;\n }\n\n async emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options: RequestOptions = {},\n ): Promise<PageSettingsUpdatedPayload> {\n this.#assertConnected();\n\n if (this.#pendingPageSettingsUpdate) {\n throw new Error(\"Page settings update already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<PageSettingsUpdatedPayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingPageSettingsUpdate = null;\n reject(new Error(`Page settings update timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingPageSettingsUpdate = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.updatePageSettings, allowedOrigin, payload);\n return promise;\n }\n\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void {\n this.#loadPageSettingsHandlers.add(handler);\n return () => {\n this.#loadPageSettingsHandlers.delete(handler);\n };\n }\n\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void {\n this.#authTokenRefreshHandlers.add(handler);\n return () => {\n this.#authTokenRefreshHandlers.delete(handler);\n };\n }\n\n emitNavigationChange(payload: NavigationChangePayload): void {\n this.#assertConnected();\n postToParent(\n CONNECTION_EVENTS.navigationChange,\n this.#allowedOrigin!,\n payload,\n );\n }\n\n #onMessage(event: MessageEvent<ExtensionMessage>): void {\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeAck: {\n this.#handleHandshakeAck(payload as HandshakePayload | undefined);\n break;\n }\n case CONNECTION_EVENTS.authTokenRefresh: {\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n }\n case CONNECTION_EVENTS.loadPageSettings: {\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n }\n case CONNECTION_EVENTS.pageSettingsUpdated: {\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n }\n default: {\n break;\n }\n }\n }\n\n #handleHandshakeAck(payload: HandshakePayload | undefined): void {\n if (!payload?.authToken || !payload.extensionDetails || !payload.designTokens) {\n this.#rejectPending(\n this.#pendingHandshake,\n new Error(\"Invalid HANDSHAKE_ACK payload.\"),\n );\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n this.#extensionDetails = payload.extensionDetails;\n this.#designTokens = payload.designTokens;\n this.#pageSettings = payload.pageSettings ?? null;\n this.#connected = true;\n\n const pending = this.#pendingHandshake;\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#resolvePending(pending, {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n });\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleAuthTokenRefresh(payload: AuthTokenRefreshPayload | undefined): void {\n if (!payload?.authToken) {\n this.#rejectPending(\n this.#pendingAuthTokenRefresh,\n new Error(\"Invalid TOKEN_REFRESH payload.\"),\n );\n this.#pendingAuthTokenRefresh = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n\n const pending = this.#pendingAuthTokenRefresh;\n this.#pendingAuthTokenRefresh = null;\n this.#resolvePending(pending, this.#authToken);\n\n for (const handler of this.#authTokenRefreshHandlers) {\n handler(this.#authToken);\n }\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleLoadPageSettings(payload: PageSettings | undefined): void {\n const settings = payload ?? {};\n this.#pageSettings = settings;\n\n for (const handler of this.#loadPageSettingsHandlers) {\n handler(settings);\n }\n }\n\n #handlePageSettingsUpdated(\n payload: PageSettingsUpdatedPayload | undefined,\n ): void {\n if (!payload || typeof payload.success !== \"boolean\") {\n this.#rejectPending(\n this.#pendingPageSettingsUpdate,\n new Error(\"Invalid PAGE_SETTINGS_UPDATED payload.\"),\n );\n this.#pendingPageSettingsUpdate = null;\n return;\n }\n\n const pending = this.#pendingPageSettingsUpdate;\n this.#pendingPageSettingsUpdate = null;\n this.#resolvePending(pending, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"SDK has been destroyed.\");\n }\n }\n\n #assertConnected(): void {\n this.#assertNotDestroyed();\n if (!this.#connected || !this.#allowedOrigin) {\n throw new Error(\"SDK is not connected. Call connect() first.\");\n }\n }\n\n #configureAuthTokenAutoRefresh(options: ConnectOptions): void {\n if (options.authTokenBufferMs !== undefined) {\n if (\n !Number.isFinite(options.authTokenBufferMs) ||\n options.authTokenBufferMs < 0\n ) {\n throw new Error(\n \"authTokenBufferMs must be a finite non-negative number.\",\n );\n }\n }\n\n this.#authTokenAutoRefresh = options.authTokenAutoRefresh === true;\n this.#authTokenBufferMs =\n options.authTokenBufferMs ?? DEFAULT_AUTH_TOKEN_BUFFER_MS;\n this.#authTokenRefreshRetryUsed = false;\n this.#clearProactiveAuthTokenRefreshTimer();\n }\n\n #parseExpiresAtMs(authToken: AuthToken): number | null {\n const expiresSec = Number(authToken.expires);\n if (!Number.isFinite(expiresSec)) {\n return null;\n }\n return expiresSec * 1000;\n }\n\n #getProactiveRefreshDelayMs(authToken: AuthToken): number | null {\n const expiresAtMs = this.#parseExpiresAtMs(authToken);\n if (expiresAtMs === null) {\n return null;\n }\n return Math.max(0, expiresAtMs - this.#authTokenBufferMs - Date.now());\n }\n\n #clearProactiveAuthTokenRefreshTimer(): void {\n if (this.#authTokenRefreshTimer !== null) {\n clearTimeout(this.#authTokenRefreshTimer);\n this.#authTokenRefreshTimer = null;\n }\n }\n\n #scheduleProactiveAuthTokenRefresh(): void {\n this.#clearProactiveAuthTokenRefreshTimer();\n\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const delayMs = this.#getProactiveRefreshDelayMs(this.#authToken);\n if (delayMs === null) {\n return;\n }\n\n const clampedDelay = Math.min(delayMs, MAX_TIMEOUT_MS);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, clampedDelay);\n }\n\n async #runProactiveAuthTokenRefresh(): Promise<void> {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const authTokenAtRequest = this.#authToken;\n const delayMs = this.#getProactiveRefreshDelayMs(authTokenAtRequest);\n if (delayMs === null) {\n return;\n }\n\n if (delayMs > 0) {\n this.#scheduleProactiveAuthTokenRefresh();\n return;\n }\n\n if (this.#pendingAuthTokenRefresh) {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n return;\n }\n\n try {\n await this.emitRequestAuthTokenRefresh();\n } catch {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n }\n }\n\n #handleProactiveAuthTokenRefreshFailure(authTokenAtRequest: AuthToken): void {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n this.#authToken?.jwt !== authTokenAtRequest.jwt\n ) {\n return;\n }\n\n if (this.#authTokenRefreshRetryUsed) {\n return;\n }\n\n const expiresAtMs = this.#parseExpiresAtMs(authTokenAtRequest);\n if (expiresAtMs === null) {\n return;\n }\n\n const remainingMs = expiresAtMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n this.#authTokenRefreshRetryUsed = true;\n this.#clearProactiveAuthTokenRefreshTimer();\n\n const retryDelay = Math.min(AUTO_REFRESH_RETRY_DELAY_MS, remainingMs);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, retryDelay);\n }\n\n #clearPending(pending: { timer: ReturnType<typeof setTimeout> } | null): void {\n if (pending) {\n clearTimeout(pending.timer);\n }\n }\n\n #resolvePending<T>(pending: Pending<T> | null, value: T): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.resolve(value);\n }\n\n #rejectPending(\n pending: { timer: ReturnType<typeof setTimeout>; reject: (reason?: unknown) => void } | null,\n reason: unknown,\n ): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.reject(reason);\n }\n}\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type HostMessageHandler = (\n event: MessageEvent<ExtensionMessage>,\n) => void;\n\n/**\n * Posts a typed extension message to an embedded child frame.\n *\n * @param contentWindow - The iframe's `contentWindow`.\n * @param origin - Target origin for `postMessage` (extension origin).\n * @param type - Wire event type from {@link CONNECTION_EVENTS}.\n * @param payload - Optional message payload.\n */\nexport function postToChild(\n contentWindow: Window,\n origin: string,\n type: ConnectionEvent,\n payload?: unknown,\n): void {\n const message: ExtensionMessage =\n payload === undefined ? { type } : { type, payload };\n contentWindow.postMessage(message, origin);\n}\n\n/**\n * Subscribes to `message` events from a child frame at `origin`.\n *\n * Ignores events from other origins and payloads without a `type` field.\n *\n * @returns Unsubscribe function.\n */\nexport function subscribeToChildMessages(\n origin: string,\n handler: HostMessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== origin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","import { CONNECTION_EVENTS } from \"./events\";\nimport { postToChild, subscribeToChildMessages } from \"./hostMessaging\";\nimport type {\n AuthToken,\n ExtensionMessage,\n HandshakePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\n/**\n * Options for {@link ExtensionSDKHost}.\n *\n * Platform code supplies token minting and page-settings persistence via\n * callbacks; the host owns only the postMessage protocol.\n */\nexport interface ExtensionSDKHostOptions {\n /** Returns the iframe `contentWindow`, or `null` if not ready. */\n getContentWindow: () => Window | null;\n /** Extension origin used as `postMessage` targetOrigin / source filter. */\n origin: string;\n /**\n * Builds the handshake payload (including auth token) when the child sends\n * `HANDSHAKE_INIT`.\n */\n resolveHandshakePayload: () => HandshakePayload | Promise<HandshakePayload>;\n /**\n * Resolves a fresh auth token when the child sends `REQUEST_TOKEN_REFRESH`.\n */\n resolveAuthToken: () => AuthToken | Promise<AuthToken>;\n /**\n * Persists page settings from `UPDATE_PAGE_SETTINGS`.\n * Return `true` on success; the host emits `PAGE_SETTINGS_UPDATED`.\n */\n onUpdatePageSettings?: (\n settings: PageSettings,\n ) => boolean | Promise<boolean>;\n}\n\n/**\n * Parent-frame counterpart to {@link IExtensionSDK}.\n *\n * Listens for child messages and replies with the shared wire protocol.\n * Create one instance per iframe.\n */\nexport interface IExtensionSDKHost {\n /**\n * Attaches the child message listener. Safe to call once; no-ops if already\n * started or destroyed.\n */\n start(): void;\n\n /**\n * Tears down the listener. The instance cannot be restarted after destroy.\n */\n destroy(): void;\n\n /**\n * Asks the child to open page settings UI with the given values.\n *\n * Wire: `LOAD_PAGE_SETTINGS` (fire-and-forget).\n */\n emitLoadPageSettings(settings: PageSettings | null): void;\n}\n\n/** Default {@link IExtensionSDKHost} implementation. */\nexport class ExtensionSDKHost implements IExtensionSDKHost {\n #options: ExtensionSDKHostOptions;\n #unsubscribe: (() => void) | null = null;\n #started = false;\n #destroyed = false;\n\n constructor(options: ExtensionSDKHostOptions) {\n this.#options = options;\n }\n\n start(): void {\n if (this.#destroyed || this.#started) {\n return;\n }\n\n this.#unsubscribe = subscribeToChildMessages(\n this.#options.origin,\n (event) => {\n void this.#onMessage(event);\n },\n );\n this.#started = true;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#started = false;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n }\n\n emitLoadPageSettings(settings: PageSettings | null): void {\n this.#assertNotDestroyed();\n this.#post(CONNECTION_EVENTS.loadPageSettings, settings);\n }\n\n async #onMessage(event: MessageEvent<ExtensionMessage>): Promise<void> {\n if (this.#destroyed) {\n return;\n }\n\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeInit: {\n await this.#handleHandshakeInit();\n break;\n }\n case CONNECTION_EVENTS.requestAuthTokenRefresh: {\n await this.#handleAuthTokenRefresh();\n break;\n }\n case CONNECTION_EVENTS.updatePageSettings: {\n await this.#handleUpdatePageSettings(\n payload as UpdatePageSettingsPayload | undefined,\n );\n break;\n }\n default: {\n break;\n }\n }\n }\n\n async #handleHandshakeInit(): Promise<void> {\n try {\n const handshakePayload = await this.#options.resolveHandshakePayload();\n if (this.#destroyed) {\n return;\n }\n this.#post(CONNECTION_EVENTS.handshakeAck, handshakePayload);\n } catch {\n // Platform owns error UI / retry; do not reply with a partial ACK.\n }\n }\n\n async #handleAuthTokenRefresh(): Promise<void> {\n try {\n const authToken = await this.#options.resolveAuthToken();\n if (this.#destroyed) {\n return;\n }\n this.#post(CONNECTION_EVENTS.authTokenRefresh, { authToken });\n } catch {\n // Platform owns error UI / retry; do not reply with an invalid token.\n }\n }\n\n async #handleUpdatePageSettings(\n payload: UpdatePageSettingsPayload | undefined,\n ): Promise<void> {\n const settings = payload?.settings;\n if (!settings || typeof settings !== \"object\") {\n this.#postPageSettingsUpdated(false);\n return;\n }\n\n const handler = this.#options.onUpdatePageSettings;\n if (!handler) {\n this.#postPageSettingsUpdated(false);\n return;\n }\n\n try {\n const success = await handler(settings);\n if (this.#destroyed) {\n return;\n }\n this.#postPageSettingsUpdated(Boolean(success));\n } catch {\n if (this.#destroyed) {\n return;\n }\n this.#postPageSettingsUpdated(false);\n }\n }\n\n #postPageSettingsUpdated(success: boolean): void {\n const payload: PageSettingsUpdatedPayload = { success };\n this.#post(CONNECTION_EVENTS.pageSettingsUpdated, payload);\n }\n\n #post(type: (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS], payload?: unknown): void {\n const contentWindow = this.#options.getContentWindow();\n if (!contentWindow) {\n return;\n }\n postToChild(contentWindow, this.#options.origin, type, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"ExtensionSDKHost has been destroyed.\");\n }\n }\n}\n","/**\n * Returns whether `urlRaw` is a valid HTTPS extension URL.\n *\n * When `checkOrigin` is provided, the URL's origin must also pass that predicate\n * (e.g. hostname ends with `.kadanza.app`).\n */\nexport function isValidExtensionUrl(\n urlRaw: string | null | undefined,\n checkOrigin?: (origin: string) => boolean,\n): boolean {\n if (typeof urlRaw !== \"string\") {\n return false;\n }\n\n try {\n const url = new URL(urlRaw);\n const allowedProtocol = url.protocol === \"https:\";\n const allowedOrigin = checkOrigin ? checkOrigin(url.origin) : true;\n return allowedProtocol && allowedOrigin;\n } catch {\n return false;\n }\n}\n\n/**\n * Clones `urlRaw` and sets the `tenantUrl` search param (parent origin).\n *\n * @param urlRaw - Absolute extension URL string.\n * @param tenantUrl - Parent origin to embed (defaults to `window.location.origin`).\n * @returns Enriched `URL`, or `null` when `urlRaw` is missing or unparsable.\n */\nexport function enrichExtensionUrl(\n urlRaw: string | null | undefined,\n tenantUrl: string = window.location.origin,\n): URL | null {\n if (typeof urlRaw !== \"string\") {\n return null;\n }\n\n try {\n const enrichedUrl = new URL(urlRaw);\n\n if (typeof tenantUrl === \"string\") {\n enrichedUrl.searchParams.set(\"tenantUrl\", tenantUrl);\n }\n\n return enrichedUrl;\n } catch {\n return null;\n }\n}\n","import { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport {\n ExtensionSDKHost,\n type ExtensionSDKHostOptions,\n type IExtensionSDKHost,\n} from \"./ExtensionSDKHost\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\nexport {\n postToChild,\n subscribeToChildMessages,\n type HostMessageHandler,\n} from \"./hostMessaging\";\nexport { enrichExtensionUrl, isValidExtensionUrl } from \"./hostUrl\";\nexport {\n InvalidOriginError,\n readTenantUrlFromLocation,\n resolveAllowedOrigin,\n} from \"./origin\";\nexport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nlet instance: IExtensionSDK | null = null;\nlet hasWarnedDuplicateCreate = false;\n\n/** Clears the factory singleton when the managed instance is destroyed. */\nclass ManagedExtensionSDK extends ExtensionSDK {\n destroy(): void {\n super.destroy();\n if (instance === this) {\n instance = null;\n hasWarnedDuplicateCreate = false;\n }\n }\n}\n\n/**\n * Returns the shared {@link IExtensionSDK} for the current extension frame.\n *\n * Creates the instance on first call. Later calls return the same instance and\n * log a console warning — use the SDK once per app. After {@link IExtensionSDK.destroy},\n * the next call creates a fresh instance.\n */\nexport const createExtensionSDK = (): IExtensionSDK => {\n if (instance) {\n if (!hasWarnedDuplicateCreate) {\n console.warn(\n `[${__PACKAGE_NAME__}] createExtensionSDK() was called more than once. ` +\n \"The SDK is a singleton — reuse the same instance for the whole app.\",\n );\n hasWarnedDuplicateCreate = true;\n }\n return instance;\n }\n\n instance = new ManagedExtensionSDK();\n return instance;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension-sdk.js","names":["#connected","#assertNotDestroyed","#configureAuthTokenAutoRefresh","#scheduleProactiveAuthTokenRefresh","#toHandshakePayload","#handshakePromise","#allowedOrigin","#tenantUrl","#unsubscribe","#onMessage","#pendingHandshake","#clearPending","#clearProactiveAuthTokenRefreshTimer","#destroyed","#authTokenAutoRefresh","#authTokenRefreshRetryUsed","#rejectPending","#pendingAuthTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#authTokenRefreshHandlers","#authToken","#extensionDetails","#designTokens","#pageSettings","#assertConnected","#handleHandshakeAck","#handleAuthTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending","#authTokenBufferMs","#parseExpiresAtMs","#authTokenRefreshTimer","#getProactiveRefreshDelayMs","#runProactiveAuthTokenRefresh","#handleProactiveAuthTokenRefreshFailure","#options","#destroyed","#started","#unsubscribe","#onMessage","#assertNotDestroyed","#post","#handleHandshakeInit","#handleAuthTokenRefresh","#handleUpdatePageSettings","#postPageSettingsUpdated"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/hostMessaging.ts","../src/ExtensionSDKHost.ts","../src/hostUrl.ts","../src/index.ts"],"sourcesContent":["interface ApiRequestContext {\n baseUrl: string;\n tenantDomain: string;\n authTokenJwt: string;\n}\n\n/** Derives the Platform API origin from handshake `baseUrl`. */\nexport function deriveApiUrl(baseUrl: string): string {\n if (baseUrl.startsWith(\"/\")) {\n return baseUrl;\n }\n\n const url = new URL(baseUrl);\n url.hostname = `api.${url.hostname}`;\n return url.origin;\n}\n\nfunction resolveApiEndpoint(baseUrl: string, endpoint: string): string {\n if (!endpoint.startsWith(\"/\") || endpoint.startsWith(\"//\")) {\n throw new Error(\n 'API endpoint must be a root-relative path starting with a single \"/\".',\n );\n }\n\n return `${deriveApiUrl(baseUrl).replace(/\\/+$/, \"\")}${endpoint}`;\n}\n\nexport async function callApi<T>(\n endpoint: string,\n context: ApiRequestContext,\n options: RequestInit = {},\n): Promise<T> {\n const headers = new Headers(options.headers);\n\n if (!headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n headers.set(\"Authorization\", `Bearer ${context.authTokenJwt}`);\n headers.set(\"X-Tenant\", context.tenantDomain);\n\n const response = await fetch(resolveApiEndpoint(context.baseUrl, endpoint), {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n const statusText = response.statusText ? ` ${response.statusText}` : \"\";\n throw new Error(`API call failed: ${response.status}${statusText}.`);\n }\n\n return (await response.json()) as T;\n}\n","/**\n * Wire event type strings for parent/child `postMessage` communication.\n *\n * Keys are SDK-facing names; values are the strings sent on the wire.\n * Comments mark message direction relative to the extension (child).\n */\nexport const CONNECTION_EVENTS = {\n /** Child → parent: start handshake. */\n handshakeInit: \"HANDSHAKE_INIT\",\n /** Parent → child: handshake success with auth and context. */\n handshakeAck: \"HANDSHAKE_ACK\",\n /** Child → parent: request a new auth token. */\n requestAuthTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n /** Parent → child: new auth token (reply or push). */\n authTokenRefresh: \"TOKEN_REFRESH\",\n /** Parent → child: open/load page settings UI with values. */\n loadPageSettings: \"LOAD_PAGE_SETTINGS\",\n /** Child → parent: persist updated page settings. */\n updatePageSettings: \"UPDATE_PAGE_SETTINGS\",\n /** Parent → child: result of a page settings update. */\n pageSettingsUpdated: \"PAGE_SETTINGS_UPDATED\",\n /** Child → parent: extension route changed. */\n navigationChange: \"NAVIGATION_CHANGE\",\n} as const;\n\n/** Union of all {@link CONNECTION_EVENTS} wire string values. */\nexport type ConnectionEvent =\n (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS];\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type MessageHandler = (event: MessageEvent<ExtensionMessage>) => void;\n\nexport function postToParent(\n type: ConnectionEvent,\n allowedOrigin: string,\n payload?: unknown,\n): void {\n if (!window.parent || window.parent === window) {\n throw new Error(\"Extension is not running inside a parent frame.\");\n }\n\n const message: ExtensionMessage = payload === undefined ? { type } : { type, payload };\n window.parent.postMessage(message, allowedOrigin);\n}\n\nexport function subscribeToParentMessages(\n allowedOrigin: string,\n handler: MessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== allowedOrigin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","const LOCALHOST_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */\nexport class InvalidOriginError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidOriginError\";\n }\n}\n\n/**\n * Parses and validates a tenant URL into a postMessage-safe origin.\n * Allows https always; http only for localhost / 127.0.0.1 / [::1].\n *\n * @param tenantUrl - Absolute URL from the `tenantUrl` search param.\n * @returns Origin string suitable for `postMessage` targetOrigin.\n * @throws {InvalidOriginError} When missing, unparsable, or disallowed scheme/host.\n */\nexport function resolveAllowedOrigin(tenantUrl: string | null): string {\n if (!tenantUrl) {\n throw new InvalidOriginError(\n 'Missing \"tenantUrl\" search parameter required to establish parent origin.',\n );\n }\n\n let url: URL;\n try {\n url = new URL(tenantUrl);\n } catch {\n throw new InvalidOriginError(`Invalid tenantUrl: \"${tenantUrl}\".`);\n }\n\n const isLocalhost = LOCALHOST_HOSTS.has(url.hostname);\n const isHttps = url.protocol === \"https:\";\n const isLocalHttp = url.protocol === \"http:\" && isLocalhost;\n\n if (!isHttps && !isLocalHttp) {\n throw new InvalidOriginError(\n `tenantUrl must use https (or http on localhost). Received: \"${url.protocol}//${url.host}\".`,\n );\n }\n\n return url.origin;\n}\n\n/**\n * Reads the parent-supplied `tenantUrl` from a URL search string.\n *\n * The parent must pass `tenantUrl` on the iframe `src` so\n * {@link resolveAllowedOrigin} can whitelist the parent origin.\n *\n * @param search - Query string (defaults to `window.location.search`).\n */\nexport function readTenantUrlFromLocation(\n search: string = window.location.search,\n): string | null {\n return new URLSearchParams(search).get(\"tenantUrl\");\n}\n","import { callApi, deriveApiUrl } from \"./api\";\nimport { CONNECTION_EVENTS } from \"./events\";\nimport { postToParent, subscribeToParentMessages } from \"./messaging\";\nimport { readTenantUrlFromLocation, resolveAllowedOrigin } from \"./origin\";\nimport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_AUTH_TOKEN_BUFFER_MS = 120_000;\nconst AUTO_REFRESH_RETRY_DELAY_MS = 30_000;\n/** Largest delay `setTimeout` can reliably use in browsers. */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\n/**\n * Public contract for talking to the Kadanza parent frame over `postMessage`.\n *\n * Requires a `tenantUrl` search param on the extension URL so\n * {@link connect} can validate the parent origin.\n */\nexport interface IExtensionSDK {\n /**\n * Establishes the parent connection and waits for `HANDSHAKE_ACK`.\n *\n * Safe to call repeatedly (e.g. from a React `useEffect` with deps):\n * - Already connected: returns the cached handshake and re-applies\n * `authTokenAutoRefresh` / `authTokenBufferMs` without another init.\n * `timeoutMs` is ignored after connect.\n * - Handshake in progress: returns the same in-flight promise. Options from\n * the first connecting call apply until the handshake completes.\n *\n * Opt-in proactive refresh can be enabled with\n * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).\n *\n * Handshake completes when the parent sends `HANDSHAKE_ACK`. Context fields\n * (`authToken`, `extensionDetails`, `designTokens`, `pageSettings`) are\n * optional — omitted values are `null`. Use them only when the current host\n * surface provides them (e.g. `spaceId` / `pageId` on Experience Pages,\n * `designTokens` when a tenant is in context).\n *\n * @param options - Handshake timeout and optional auth-token auto-refresh.\n * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.\n * @throws When not embedded in a parent frame, destroyed, or the handshake\n * times out.\n */\n connect(options?: ConnectOptions): Promise<HandshakePayload>;\n\n /**\n * Tears down listeners, proactive refresh timers, and cached state.\n * Rejects any in-flight requests. The instance cannot be reused after destroy.\n * When created via {@link createExtensionSDK}, clears the singleton so a\n * later create call can return a fresh instance.\n */\n destroy(): void;\n\n /** Whether a successful handshake has completed and not been destroyed. */\n readonly isConnected: boolean;\n\n /**\n * Last auth token from handshake or refresh.\n * `null` until connected, or when the host omitted it.\n */\n getAuthToken(): AuthToken | null;\n\n /**\n * Extension context from handshake.\n * `null` until connected, or when the host omitted it.\n */\n getExtensionDetails(): ExtensionDetails | null;\n\n /**\n * Tenant branding from handshake.\n * `null` until connected, or when the host omitted it.\n */\n getDesignTokens(): DesignTokens | null;\n\n /**\n * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;\n * `null` until set.\n */\n getPageSettings(): PageSettings | null;\n\n /**\n * Validated parent `postMessage` origin from `tenantUrl`;\n * `null` until `connect` resolves it.\n */\n getAllowedOrigin(): string | null;\n\n /**\n * Raw `tenantUrl` search-param value used for the allowed origin;\n * `null` until `connect` reads it.\n */\n getTenantUrl(): string | null;\n\n /**\n * Platform API origin derived from handshake `baseUrl`.\n * `null` until connected, or when `extensionDetails.baseUrl` was omitted.\n */\n getApiUrl(): string | null;\n\n /**\n * Calls the Kadanza Platform API with the current connection credentials.\n *\n * The API origin is derived from the handshake `baseUrl`. Authorization and\n * tenant headers are managed by the SDK and cannot be overridden.\n *\n * @typeParam T - Expected JSON response body.\n * @param endpoint - Root-relative API path.\n * @param options - Standard fetch options.\n * @throws When not connected, handshake did not include `authToken` and\n * `extensionDetails` (`baseUrl`, `tenantDomain`), the endpoint is invalid,\n * the request fails, or the response is not successful JSON.\n */\n apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;\n\n /**\n * Asks the parent for a new auth token and waits for `TOKEN_REFRESH`.\n *\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, a refresh is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitRequestAuthTokenRefresh(\n options?: RequestOptions,\n ): Promise<AuthToken>;\n\n /**\n * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.\n *\n * @param payload - Update payload containing the settings object.\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, an update is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options?: RequestOptions,\n ): Promise<PageSettingsUpdatedPayload>;\n\n /**\n * Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.\n *\n * @returns Unsubscribe function.\n */\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;\n\n /**\n * Registers a handler for auth-token updates (requested or parent-pushed).\n *\n * @returns Unsubscribe function.\n */\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;\n\n /**\n * Notifies the parent that the extension's route changed.\n *\n * Wire: `NAVIGATION_CHANGE` (fire-and-forget).\n *\n * @param payload - Navigation change payload (`path` within the extension).\n * @throws When not connected or destroyed.\n */\n emitNavigationChange(payload: NavigationChangePayload): void;\n}\n\ntype Pending<T> = {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */\nexport class ExtensionSDK implements IExtensionSDK {\n #allowedOrigin: string | null = null;\n #tenantUrl: string | null = null;\n #unsubscribe: (() => void) | null = null;\n #connected = false;\n #destroyed = false;\n\n #authToken: AuthToken | null = null;\n #extensionDetails: ExtensionDetails | null = null;\n #designTokens: DesignTokens | null = null;\n #pageSettings: PageSettings | null = null;\n\n #authTokenAutoRefresh = false;\n #authTokenBufferMs = DEFAULT_AUTH_TOKEN_BUFFER_MS;\n #authTokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n #authTokenRefreshRetryUsed = false;\n\n #pendingHandshake: Pending<HandshakePayload> | null = null;\n #handshakePromise: Promise<HandshakePayload> | null = null;\n #pendingAuthTokenRefresh: Pending<AuthToken> | null = null;\n #pendingPageSettingsUpdate: Pending<PageSettingsUpdatedPayload> | null = null;\n\n #loadPageSettingsHandlers = new Set<(settings: PageSettings) => void>();\n #authTokenRefreshHandlers = new Set<(authToken: AuthToken) => void>();\n\n get isConnected(): boolean {\n return this.#connected;\n }\n\n async connect(options: ConnectOptions = {}): Promise<HandshakePayload> {\n this.#assertNotDestroyed();\n\n if (this.#connected) {\n this.#configureAuthTokenAutoRefresh(options);\n this.#scheduleProactiveAuthTokenRefresh();\n return this.#toHandshakePayload();\n }\n\n if (this.#handshakePromise) {\n return this.#handshakePromise;\n }\n\n this.#configureAuthTokenAutoRefresh(options);\n\n const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(tenantUrl);\n this.#tenantUrl = tenantUrl;\n\n this.#unsubscribe?.();\n this.#unsubscribe = subscribeToParentMessages(\n this.#allowedOrigin,\n (event) => this.#onMessage(event),\n );\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const handshakePromise = new Promise<HandshakePayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n this.#handshakePromise = handshakePromise;\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#tenantUrl = null;\n this.#clearProactiveAuthTokenRefreshTimer();\n throw error;\n }\n\n return handshakePromise;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#connected = false;\n\n this.#clearProactiveAuthTokenRefreshTimer();\n this.#authTokenAutoRefresh = false;\n this.#authTokenRefreshRetryUsed = false;\n\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#tenantUrl = null;\n\n this.#rejectPending(this.#pendingHandshake, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingAuthTokenRefresh, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingPageSettingsUpdate, new Error(\"SDK destroyed.\"));\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#pendingAuthTokenRefresh = null;\n this.#pendingPageSettingsUpdate = null;\n\n this.#loadPageSettingsHandlers.clear();\n this.#authTokenRefreshHandlers.clear();\n\n this.#authToken = null;\n this.#extensionDetails = null;\n this.#designTokens = null;\n this.#pageSettings = null;\n }\n\n getAuthToken(): AuthToken | null {\n return this.#authToken;\n }\n\n getExtensionDetails(): ExtensionDetails | null {\n return this.#extensionDetails;\n }\n\n getDesignTokens(): DesignTokens | null {\n return this.#designTokens;\n }\n\n getPageSettings(): PageSettings | null {\n return this.#pageSettings;\n }\n\n getAllowedOrigin(): string | null {\n return this.#allowedOrigin;\n }\n\n getTenantUrl(): string | null {\n return this.#tenantUrl;\n }\n\n getApiUrl(): string | null {\n const baseUrl = this.#extensionDetails?.baseUrl;\n if (!baseUrl) {\n return null;\n }\n\n try {\n return deriveApiUrl(baseUrl);\n } catch {\n return null;\n }\n }\n\n async apiCall<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n this.#assertConnected();\n\n const baseUrl = this.#extensionDetails?.baseUrl;\n const tenantDomain = this.#extensionDetails?.tenantDomain;\n const authTokenJwt = this.#authToken?.jwt;\n\n if (!baseUrl || !tenantDomain || !authTokenJwt) {\n throw new Error(\n \"API calls require handshake authToken and extensionDetails (baseUrl, tenantDomain).\",\n );\n }\n\n return callApi<T>(\n endpoint,\n {\n baseUrl,\n tenantDomain,\n authTokenJwt,\n },\n options,\n );\n }\n\n async emitRequestAuthTokenRefresh(\n options: RequestOptions = {},\n ): Promise<AuthToken> {\n this.#assertConnected();\n\n if (this.#pendingAuthTokenRefresh) {\n throw new Error(\"Auth token refresh already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<AuthToken>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingAuthTokenRefresh = null;\n reject(new Error(`Auth token refresh timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingAuthTokenRefresh = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.requestAuthTokenRefresh, allowedOrigin);\n return promise;\n }\n\n async emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options: RequestOptions = {},\n ): Promise<PageSettingsUpdatedPayload> {\n this.#assertConnected();\n\n if (this.#pendingPageSettingsUpdate) {\n throw new Error(\"Page settings update already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<PageSettingsUpdatedPayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingPageSettingsUpdate = null;\n reject(new Error(`Page settings update timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingPageSettingsUpdate = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.updatePageSettings, allowedOrigin, payload);\n return promise;\n }\n\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void {\n this.#loadPageSettingsHandlers.add(handler);\n return () => {\n this.#loadPageSettingsHandlers.delete(handler);\n };\n }\n\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void {\n this.#authTokenRefreshHandlers.add(handler);\n return () => {\n this.#authTokenRefreshHandlers.delete(handler);\n };\n }\n\n emitNavigationChange(payload: NavigationChangePayload): void {\n this.#assertConnected();\n postToParent(\n CONNECTION_EVENTS.navigationChange,\n this.#allowedOrigin!,\n payload,\n );\n }\n\n #onMessage(event: MessageEvent<ExtensionMessage>): void {\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeAck: {\n this.#handleHandshakeAck(payload as HandshakePayload | undefined);\n break;\n }\n case CONNECTION_EVENTS.authTokenRefresh: {\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n }\n case CONNECTION_EVENTS.loadPageSettings: {\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n }\n case CONNECTION_EVENTS.pageSettingsUpdated: {\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n }\n default: {\n break;\n }\n }\n }\n\n #toHandshakePayload(): HandshakePayload {\n return {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n };\n }\n\n #handleHandshakeAck(payload: HandshakePayload | undefined): void {\n this.#authToken = isPlainObject(payload?.authToken)\n ? (payload?.authToken as AuthToken)\n : null;\n this.#extensionDetails = isPlainObject(payload?.extensionDetails)\n ? (payload?.extensionDetails as ExtensionDetails)\n : null;\n this.#designTokens = isPlainObject(payload?.designTokens)\n ? (payload?.designTokens as DesignTokens)\n : null;\n this.#pageSettings = isPlainObject(payload?.pageSettings)\n ? (payload?.pageSettings as PageSettings)\n : null;\n this.#connected = true;\n\n const pending = this.#pendingHandshake;\n this.#pendingHandshake = null;\n this.#handshakePromise = null;\n this.#resolvePending(pending, this.#toHandshakePayload());\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleAuthTokenRefresh(payload: AuthTokenRefreshPayload | undefined): void {\n if (!payload?.authToken) {\n this.#rejectPending(\n this.#pendingAuthTokenRefresh,\n new Error(\"Invalid TOKEN_REFRESH payload.\"),\n );\n this.#pendingAuthTokenRefresh = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n\n const pending = this.#pendingAuthTokenRefresh;\n this.#pendingAuthTokenRefresh = null;\n this.#resolvePending(pending, this.#authToken);\n\n for (const handler of this.#authTokenRefreshHandlers) {\n handler(this.#authToken);\n }\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleLoadPageSettings(payload: PageSettings | undefined): void {\n const settings = payload ?? {};\n this.#pageSettings = settings;\n\n for (const handler of this.#loadPageSettingsHandlers) {\n handler(settings);\n }\n }\n\n #handlePageSettingsUpdated(\n payload: PageSettingsUpdatedPayload | undefined,\n ): void {\n if (!payload || typeof payload.success !== \"boolean\") {\n this.#rejectPending(\n this.#pendingPageSettingsUpdate,\n new Error(\"Invalid PAGE_SETTINGS_UPDATED payload.\"),\n );\n this.#pendingPageSettingsUpdate = null;\n return;\n }\n\n const pending = this.#pendingPageSettingsUpdate;\n this.#pendingPageSettingsUpdate = null;\n this.#resolvePending(pending, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"SDK has been destroyed.\");\n }\n }\n\n #assertConnected(): void {\n this.#assertNotDestroyed();\n if (!this.#connected || !this.#allowedOrigin) {\n throw new Error(\"SDK is not connected. Call connect() first.\");\n }\n }\n\n #configureAuthTokenAutoRefresh(options: ConnectOptions): void {\n if (options.authTokenBufferMs !== undefined) {\n if (\n !Number.isFinite(options.authTokenBufferMs) ||\n options.authTokenBufferMs < 0\n ) {\n throw new Error(\n \"authTokenBufferMs must be a finite non-negative number.\",\n );\n }\n }\n\n this.#authTokenAutoRefresh = options.authTokenAutoRefresh === true;\n this.#authTokenBufferMs =\n options.authTokenBufferMs ?? DEFAULT_AUTH_TOKEN_BUFFER_MS;\n this.#authTokenRefreshRetryUsed = false;\n this.#clearProactiveAuthTokenRefreshTimer();\n }\n\n #parseExpiresAtMs(authToken: AuthToken): number | null {\n const expiresSec = Number(authToken.expires);\n if (!Number.isFinite(expiresSec)) {\n return null;\n }\n return expiresSec * 1000;\n }\n\n #getProactiveRefreshDelayMs(authToken: AuthToken): number | null {\n const expiresAtMs = this.#parseExpiresAtMs(authToken);\n if (expiresAtMs === null) {\n return null;\n }\n return Math.max(0, expiresAtMs - this.#authTokenBufferMs - Date.now());\n }\n\n #clearProactiveAuthTokenRefreshTimer(): void {\n if (this.#authTokenRefreshTimer !== null) {\n clearTimeout(this.#authTokenRefreshTimer);\n this.#authTokenRefreshTimer = null;\n }\n }\n\n #scheduleProactiveAuthTokenRefresh(): void {\n this.#clearProactiveAuthTokenRefreshTimer();\n\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const delayMs = this.#getProactiveRefreshDelayMs(this.#authToken);\n if (delayMs === null) {\n return;\n }\n\n const clampedDelay = Math.min(delayMs, MAX_TIMEOUT_MS);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, clampedDelay);\n }\n\n async #runProactiveAuthTokenRefresh(): Promise<void> {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const authTokenAtRequest = this.#authToken;\n const delayMs = this.#getProactiveRefreshDelayMs(authTokenAtRequest);\n if (delayMs === null) {\n return;\n }\n\n if (delayMs > 0) {\n this.#scheduleProactiveAuthTokenRefresh();\n return;\n }\n\n if (this.#pendingAuthTokenRefresh) {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n return;\n }\n\n try {\n await this.emitRequestAuthTokenRefresh();\n } catch {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n }\n }\n\n #handleProactiveAuthTokenRefreshFailure(authTokenAtRequest: AuthToken): void {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n this.#authToken?.jwt !== authTokenAtRequest.jwt\n ) {\n return;\n }\n\n if (this.#authTokenRefreshRetryUsed) {\n return;\n }\n\n const expiresAtMs = this.#parseExpiresAtMs(authTokenAtRequest);\n if (expiresAtMs === null) {\n return;\n }\n\n const remainingMs = expiresAtMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n this.#authTokenRefreshRetryUsed = true;\n this.#clearProactiveAuthTokenRefreshTimer();\n\n const retryDelay = Math.min(AUTO_REFRESH_RETRY_DELAY_MS, remainingMs);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, retryDelay);\n }\n\n #clearPending(pending: { timer: ReturnType<typeof setTimeout> } | null): void {\n if (pending) {\n clearTimeout(pending.timer);\n }\n }\n\n #resolvePending<T>(pending: Pending<T> | null, value: T): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.resolve(value);\n }\n\n #rejectPending(\n pending: { timer: ReturnType<typeof setTimeout>; reject: (reason?: unknown) => void } | null,\n reason: unknown,\n ): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.reject(reason);\n }\n}\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type HostMessageHandler = (\n event: MessageEvent<ExtensionMessage>,\n) => void;\n\n/**\n * Posts a typed extension message to an embedded child frame.\n *\n * @param contentWindow - The iframe's `contentWindow`.\n * @param origin - Target origin for `postMessage` (extension origin).\n * @param type - Wire event type from {@link CONNECTION_EVENTS}.\n * @param payload - Optional message payload.\n */\nexport function postToChild(\n contentWindow: Window,\n origin: string,\n type: ConnectionEvent,\n payload?: unknown,\n): void {\n const message: ExtensionMessage =\n payload === undefined ? { type } : { type, payload };\n contentWindow.postMessage(message, origin);\n}\n\n/**\n * Subscribes to `message` events from a child frame at `origin`.\n *\n * Ignores events from other origins and payloads without a `type` field.\n *\n * @returns Unsubscribe function.\n */\nexport function subscribeToChildMessages(\n origin: string,\n handler: HostMessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== origin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","import { CONNECTION_EVENTS } from \"./events\";\nimport { postToChild, subscribeToChildMessages } from \"./hostMessaging\";\nimport type {\n AuthToken,\n ExtensionMessage,\n HandshakePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\n/**\n * Options for {@link ExtensionSDKHost}.\n *\n * Platform code supplies token minting and page-settings persistence via\n * callbacks; the host owns only the postMessage protocol.\n */\nexport interface ExtensionSDKHostOptions {\n /** Returns the iframe `contentWindow`, or `null` if not ready. */\n getContentWindow: () => Window | null;\n /** Extension origin used as `postMessage` targetOrigin / source filter. */\n origin: string;\n /**\n * Builds the handshake payload when the child sends `HANDSHAKE_INIT`.\n * Context fields are optional; send `designTokens` whenever a tenant is in\n * context. `spaceId` / `pageId` apply to Experience Pages only.\n */\n resolveHandshakePayload: () =>\n Partial<HandshakePayload> | Promise<Partial<HandshakePayload>>;\n /**\n * Resolves a fresh auth token when the child sends `REQUEST_TOKEN_REFRESH`.\n */\n resolveAuthToken: () => AuthToken | Promise<AuthToken>;\n /**\n * Persists page settings from `UPDATE_PAGE_SETTINGS`.\n * Return `true` on success; the host emits `PAGE_SETTINGS_UPDATED`.\n */\n onUpdatePageSettings?: (\n settings: PageSettings,\n ) => boolean | Promise<boolean>;\n}\n\n/**\n * Parent-frame counterpart to {@link IExtensionSDK}.\n *\n * Listens for child messages and replies with the shared wire protocol.\n * Create one instance per iframe.\n */\nexport interface IExtensionSDKHost {\n /**\n * Attaches the child message listener. Safe to call once; no-ops if already\n * started or destroyed.\n */\n start(): void;\n\n /**\n * Tears down the listener. The instance cannot be restarted after destroy.\n */\n destroy(): void;\n\n /**\n * Asks the child to open page settings UI with the given values.\n *\n * Wire: `LOAD_PAGE_SETTINGS` (fire-and-forget).\n */\n emitLoadPageSettings(settings: PageSettings | null): void;\n}\n\n/** Default {@link IExtensionSDKHost} implementation. */\nexport class ExtensionSDKHost implements IExtensionSDKHost {\n #options: ExtensionSDKHostOptions;\n #unsubscribe: (() => void) | null = null;\n #started = false;\n #destroyed = false;\n\n constructor(options: ExtensionSDKHostOptions) {\n this.#options = options;\n }\n\n start(): void {\n if (this.#destroyed || this.#started) {\n return;\n }\n\n this.#unsubscribe = subscribeToChildMessages(\n this.#options.origin,\n (event) => {\n void this.#onMessage(event);\n },\n );\n this.#started = true;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#started = false;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n }\n\n emitLoadPageSettings(settings: PageSettings | null): void {\n this.#assertNotDestroyed();\n this.#post(CONNECTION_EVENTS.loadPageSettings, settings);\n }\n\n async #onMessage(event: MessageEvent<ExtensionMessage>): Promise<void> {\n if (this.#destroyed) {\n return;\n }\n\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeInit: {\n await this.#handleHandshakeInit();\n break;\n }\n case CONNECTION_EVENTS.requestAuthTokenRefresh: {\n await this.#handleAuthTokenRefresh();\n break;\n }\n case CONNECTION_EVENTS.updatePageSettings: {\n await this.#handleUpdatePageSettings(\n payload as UpdatePageSettingsPayload | undefined,\n );\n break;\n }\n default: {\n break;\n }\n }\n }\n\n async #handleHandshakeInit(): Promise<void> {\n try {\n const handshakePayload = await this.#options.resolveHandshakePayload();\n if (this.#destroyed) {\n return;\n }\n this.#post(CONNECTION_EVENTS.handshakeAck, handshakePayload);\n } catch {\n // Platform owns error UI / retry; do not reply with a partial ACK.\n }\n }\n\n async #handleAuthTokenRefresh(): Promise<void> {\n try {\n const authToken = await this.#options.resolveAuthToken();\n if (this.#destroyed) {\n return;\n }\n this.#post(CONNECTION_EVENTS.authTokenRefresh, { authToken });\n } catch {\n // Platform owns error UI / retry; do not reply with an invalid token.\n }\n }\n\n async #handleUpdatePageSettings(\n payload: UpdatePageSettingsPayload | undefined,\n ): Promise<void> {\n const settings = payload?.settings;\n if (!settings || typeof settings !== \"object\") {\n this.#postPageSettingsUpdated(false);\n return;\n }\n\n const handler = this.#options.onUpdatePageSettings;\n if (!handler) {\n this.#postPageSettingsUpdated(false);\n return;\n }\n\n try {\n const success = await handler(settings);\n if (this.#destroyed) {\n return;\n }\n this.#postPageSettingsUpdated(Boolean(success));\n } catch {\n if (this.#destroyed) {\n return;\n }\n this.#postPageSettingsUpdated(false);\n }\n }\n\n #postPageSettingsUpdated(success: boolean): void {\n const payload: PageSettingsUpdatedPayload = { success };\n this.#post(CONNECTION_EVENTS.pageSettingsUpdated, payload);\n }\n\n #post(type: (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS], payload?: unknown): void {\n const contentWindow = this.#options.getContentWindow();\n if (!contentWindow) {\n return;\n }\n postToChild(contentWindow, this.#options.origin, type, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"ExtensionSDKHost has been destroyed.\");\n }\n }\n}\n","/**\n * Returns whether `urlRaw` is a valid HTTPS extension URL.\n *\n * When `checkOrigin` is provided, the URL's origin must also pass that predicate\n * (e.g. hostname ends with `.kadanza.app`).\n */\nexport function isValidExtensionUrl(\n urlRaw: string | null | undefined,\n checkOrigin?: (origin: string) => boolean,\n): boolean {\n if (typeof urlRaw !== \"string\") {\n return false;\n }\n\n try {\n const url = new URL(urlRaw);\n const allowedProtocol = url.protocol === \"https:\";\n const allowedOrigin = checkOrigin ? checkOrigin(url.origin) : true;\n return allowedProtocol && allowedOrigin;\n } catch {\n return false;\n }\n}\n\n/**\n * Clones `urlRaw` and sets the `tenantUrl` search param (parent origin).\n *\n * @param urlRaw - Absolute extension URL string.\n * @param tenantUrl - Parent origin to embed (defaults to `window.location.origin`).\n * @returns Enriched `URL`, or `null` when `urlRaw` is missing or unparsable.\n */\nexport function enrichExtensionUrl(\n urlRaw: string | null | undefined,\n tenantUrl: string = window.location.origin,\n): URL | null {\n if (typeof urlRaw !== \"string\") {\n return null;\n }\n\n try {\n const enrichedUrl = new URL(urlRaw);\n\n if (typeof tenantUrl === \"string\") {\n enrichedUrl.searchParams.set(\"tenantUrl\", tenantUrl);\n }\n\n return enrichedUrl;\n } catch {\n return null;\n }\n}\n","import { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport {\n ExtensionSDKHost,\n type ExtensionSDKHostOptions,\n type IExtensionSDKHost,\n} from \"./ExtensionSDKHost\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\nexport {\n postToChild,\n subscribeToChildMessages,\n type HostMessageHandler,\n} from \"./hostMessaging\";\nexport { enrichExtensionUrl, isValidExtensionUrl } from \"./hostUrl\";\nexport {\n InvalidOriginError,\n readTenantUrlFromLocation,\n resolveAllowedOrigin,\n} from \"./origin\";\nexport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nlet instance: IExtensionSDK | null = null;\nlet hasWarnedDuplicateCreate = false;\n\n/** Clears the factory singleton when the managed instance is destroyed. */\nclass ManagedExtensionSDK extends ExtensionSDK {\n destroy(): void {\n super.destroy();\n if (instance === this) {\n instance = null;\n hasWarnedDuplicateCreate = false;\n }\n }\n}\n\n/**\n * Returns the shared {@link IExtensionSDK} for the current extension frame.\n *\n * Creates the instance on first call. Later calls return the same instance and\n * log a console warning — use the SDK once per app. After {@link IExtensionSDK.destroy},\n * the next call creates a fresh instance.\n */\nexport const createExtensionSDK = (): IExtensionSDK => {\n if (instance) {\n if (!hasWarnedDuplicateCreate) {\n console.warn(\n `[${__PACKAGE_NAME__}] createExtensionSDK() was called more than once. ` +\n \"The SDK is a singleton — reuse the same instance for the whole app.\",\n );\n hasWarnedDuplicateCreate = true;\n }\n return instance;\n }\n\n instance = new ManagedExtensionSDK();\n return instance;\n};\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -78,11 +78,23 @@ export declare interface ConnectOptions {
78
78
  */
79
79
  export declare const createExtensionSDK: () => IExtensionSDK;
80
80
 
81
- /** Branding tokens from the parent for aligning extension UI. */
81
+ /**
82
+ * Tenant branding from the parent, for aligning extension UI with the host.
83
+ *
84
+ * The host should send this whenever a tenant is in context (Experience Pages
85
+ * and Admin Console). Handshake still succeeds if it is omitted — treat every
86
+ * field as optional.
87
+ *
88
+ * - `primaryColor` — tenant palette primary; accents, buttons, links
89
+ * - `fontFamily` — tenant font family; body and UI type
90
+ * - `borderRadius` — tenant roundness; controls and cards
91
+ *
92
+ * Values may be missing when the tenant has no setting configured.
93
+ */
82
94
  export declare interface DesignTokens {
83
- primaryColor: string;
84
- fontFamily: string;
85
- borderRadius: string;
95
+ primaryColor?: string;
96
+ fontFamily?: string;
97
+ borderRadius?: string;
86
98
  }
87
99
 
88
100
  /**
@@ -94,17 +106,28 @@ export declare interface DesignTokens {
94
106
  */
95
107
  export declare function enrichExtensionUrl(urlRaw: string | null | undefined, tenantUrl?: string): URL | null;
96
108
 
97
- /** Context about the embedded extension and its host page from handshake. */
109
+ /**
110
+ * Host context from handshake. Only set when the parent includes it.
111
+ *
112
+ * Field availability depends on where the iframe is mounted:
113
+ * - Experience Pages typically send `spaceId` and `pageId`.
114
+ * - Admin Console Pages typically omit those — there is no space page.
115
+ *
116
+ * Other identity fields are sent when the host knows them. Handshake does not
117
+ * require this object.
118
+ */
98
119
  export declare interface ExtensionDetails {
99
- extensionId: string;
100
- tenantId: string;
101
- tenantDomain: string;
102
- /** Base URL of the parent application. */
103
- baseUrl: string;
104
- spaceId: string;
105
- pageId: string;
120
+ extensionId?: string;
121
+ tenantId?: string;
122
+ tenantDomain?: string;
123
+ /** Base URL of the parent application (used to derive the Platform API origin). */
124
+ baseUrl?: string;
125
+ /** Space that owns the Experience Page. Omitted in Admin Console. */
126
+ spaceId?: string;
127
+ /** Experience Page id. Omitted in Admin Console. */
128
+ pageId?: string;
106
129
  /** BCP 47 locale from the parent. */
107
- locale: string;
130
+ locale?: string;
108
131
  }
109
132
 
110
133
  /** Envelope for parent/child `postMessage` traffic. */
@@ -155,10 +178,11 @@ export declare interface ExtensionSDKHostOptions {
155
178
  /** Extension origin used as `postMessage` targetOrigin / source filter. */
156
179
  origin: string;
157
180
  /**
158
- * Builds the handshake payload (including auth token) when the child sends
159
- * `HANDSHAKE_INIT`.
181
+ * Builds the handshake payload when the child sends `HANDSHAKE_INIT`.
182
+ * Context fields are optional; send `designTokens` whenever a tenant is in
183
+ * context. `spaceId` / `pageId` apply to Experience Pages only.
160
184
  */
161
- resolveHandshakePayload: () => HandshakePayload | Promise<HandshakePayload>;
185
+ resolveHandshakePayload: () => Partial<HandshakePayload> | Promise<Partial<HandshakePayload>>;
162
186
  /**
163
187
  * Resolves a fresh auth token when the child sends `REQUEST_TOKEN_REFRESH`.
164
188
  */
@@ -170,11 +194,17 @@ export declare interface ExtensionSDKHostOptions {
170
194
  onUpdatePageSettings?: (settings: PageSettings) => boolean | Promise<boolean>;
171
195
  }
172
196
 
173
- /** Payload delivered with a successful `HANDSHAKE_ACK`. */
197
+ /**
198
+ * Context delivered with `HANDSHAKE_ACK`.
199
+ *
200
+ * Handshake completes as soon as the parent ACKs. Every property is optional
201
+ * and only populated when that host surface provides it. After `connect()`,
202
+ * omitted wire fields are normalized to `null`.
203
+ */
174
204
  export declare interface HandshakePayload {
175
- authToken: AuthToken;
176
- extensionDetails: ExtensionDetails;
177
- designTokens: DesignTokens;
205
+ authToken: AuthToken | null;
206
+ extensionDetails: ExtensionDetails | null;
207
+ designTokens: DesignTokens | null;
178
208
  pageSettings: PageSettings | null;
179
209
  }
180
210
 
@@ -200,10 +230,16 @@ export declare interface IExtensionSDK {
200
230
  * Opt-in proactive refresh can be enabled with
201
231
  * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).
202
232
  *
233
+ * Handshake completes when the parent sends `HANDSHAKE_ACK`. Context fields
234
+ * (`authToken`, `extensionDetails`, `designTokens`, `pageSettings`) are
235
+ * optional — omitted values are `null`. Use them only when the current host
236
+ * surface provides them (e.g. `spaceId` / `pageId` on Experience Pages,
237
+ * `designTokens` when a tenant is in context).
238
+ *
203
239
  * @param options - Handshake timeout and optional auth-token auto-refresh.
204
240
  * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
205
241
  * @throws When not embedded in a parent frame, destroyed, or the handshake
206
- * times out / returns an invalid payload.
242
+ * times out.
207
243
  */
208
244
  connect(options?: ConnectOptions): Promise<HandshakePayload>;
209
245
  /**
@@ -215,11 +251,20 @@ export declare interface IExtensionSDK {
215
251
  destroy(): void;
216
252
  /** Whether a successful handshake has completed and not been destroyed. */
217
253
  readonly isConnected: boolean;
218
- /** Last auth token from handshake or refresh; `null` until connected. */
254
+ /**
255
+ * Last auth token from handshake or refresh.
256
+ * `null` until connected, or when the host omitted it.
257
+ */
219
258
  getAuthToken(): AuthToken | null;
220
- /** Extension context from handshake; `null` until connected. */
259
+ /**
260
+ * Extension context from handshake.
261
+ * `null` until connected, or when the host omitted it.
262
+ */
221
263
  getExtensionDetails(): ExtensionDetails | null;
222
- /** Design tokens from handshake; `null` until connected. */
264
+ /**
265
+ * Tenant branding from handshake.
266
+ * `null` until connected, or when the host omitted it.
267
+ */
223
268
  getDesignTokens(): DesignTokens | null;
224
269
  /**
225
270
  * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;
@@ -237,8 +282,8 @@ export declare interface IExtensionSDK {
237
282
  */
238
283
  getTenantUrl(): string | null;
239
284
  /**
240
- * Platform API origin derived from handshake `baseUrl`;
241
- * `null` until connected.
285
+ * Platform API origin derived from handshake `baseUrl`.
286
+ * `null` until connected, or when `extensionDetails.baseUrl` was omitted.
242
287
  */
243
288
  getApiUrl(): string | null;
244
289
  /**
@@ -250,8 +295,9 @@ export declare interface IExtensionSDK {
250
295
  * @typeParam T - Expected JSON response body.
251
296
  * @param endpoint - Root-relative API path.
252
297
  * @param options - Standard fetch options.
253
- * @throws When not connected, the endpoint is invalid, the request fails,
254
- * or the response is not successful JSON.
298
+ * @throws When not connected, handshake did not include `authToken` and
299
+ * `extensionDetails` (`baseUrl`, `tenantDomain`), the endpoint is invalid,
300
+ * the request fails, or the response is not successful JSON.
255
301
  */
256
302
  apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;
257
303
  /**
package/dist/index.d.ts CHANGED
@@ -78,11 +78,23 @@ export declare interface ConnectOptions {
78
78
  */
79
79
  export declare const createExtensionSDK: () => IExtensionSDK;
80
80
 
81
- /** Branding tokens from the parent for aligning extension UI. */
81
+ /**
82
+ * Tenant branding from the parent, for aligning extension UI with the host.
83
+ *
84
+ * The host should send this whenever a tenant is in context (Experience Pages
85
+ * and Admin Console). Handshake still succeeds if it is omitted — treat every
86
+ * field as optional.
87
+ *
88
+ * - `primaryColor` — tenant palette primary; accents, buttons, links
89
+ * - `fontFamily` — tenant font family; body and UI type
90
+ * - `borderRadius` — tenant roundness; controls and cards
91
+ *
92
+ * Values may be missing when the tenant has no setting configured.
93
+ */
82
94
  export declare interface DesignTokens {
83
- primaryColor: string;
84
- fontFamily: string;
85
- borderRadius: string;
95
+ primaryColor?: string;
96
+ fontFamily?: string;
97
+ borderRadius?: string;
86
98
  }
87
99
 
88
100
  /**
@@ -94,17 +106,28 @@ export declare interface DesignTokens {
94
106
  */
95
107
  export declare function enrichExtensionUrl(urlRaw: string | null | undefined, tenantUrl?: string): URL | null;
96
108
 
97
- /** Context about the embedded extension and its host page from handshake. */
109
+ /**
110
+ * Host context from handshake. Only set when the parent includes it.
111
+ *
112
+ * Field availability depends on where the iframe is mounted:
113
+ * - Experience Pages typically send `spaceId` and `pageId`.
114
+ * - Admin Console Pages typically omit those — there is no space page.
115
+ *
116
+ * Other identity fields are sent when the host knows them. Handshake does not
117
+ * require this object.
118
+ */
98
119
  export declare interface ExtensionDetails {
99
- extensionId: string;
100
- tenantId: string;
101
- tenantDomain: string;
102
- /** Base URL of the parent application. */
103
- baseUrl: string;
104
- spaceId: string;
105
- pageId: string;
120
+ extensionId?: string;
121
+ tenantId?: string;
122
+ tenantDomain?: string;
123
+ /** Base URL of the parent application (used to derive the Platform API origin). */
124
+ baseUrl?: string;
125
+ /** Space that owns the Experience Page. Omitted in Admin Console. */
126
+ spaceId?: string;
127
+ /** Experience Page id. Omitted in Admin Console. */
128
+ pageId?: string;
106
129
  /** BCP 47 locale from the parent. */
107
- locale: string;
130
+ locale?: string;
108
131
  }
109
132
 
110
133
  /** Envelope for parent/child `postMessage` traffic. */
@@ -155,10 +178,11 @@ export declare interface ExtensionSDKHostOptions {
155
178
  /** Extension origin used as `postMessage` targetOrigin / source filter. */
156
179
  origin: string;
157
180
  /**
158
- * Builds the handshake payload (including auth token) when the child sends
159
- * `HANDSHAKE_INIT`.
181
+ * Builds the handshake payload when the child sends `HANDSHAKE_INIT`.
182
+ * Context fields are optional; send `designTokens` whenever a tenant is in
183
+ * context. `spaceId` / `pageId` apply to Experience Pages only.
160
184
  */
161
- resolveHandshakePayload: () => HandshakePayload | Promise<HandshakePayload>;
185
+ resolveHandshakePayload: () => Partial<HandshakePayload> | Promise<Partial<HandshakePayload>>;
162
186
  /**
163
187
  * Resolves a fresh auth token when the child sends `REQUEST_TOKEN_REFRESH`.
164
188
  */
@@ -170,11 +194,17 @@ export declare interface ExtensionSDKHostOptions {
170
194
  onUpdatePageSettings?: (settings: PageSettings) => boolean | Promise<boolean>;
171
195
  }
172
196
 
173
- /** Payload delivered with a successful `HANDSHAKE_ACK`. */
197
+ /**
198
+ * Context delivered with `HANDSHAKE_ACK`.
199
+ *
200
+ * Handshake completes as soon as the parent ACKs. Every property is optional
201
+ * and only populated when that host surface provides it. After `connect()`,
202
+ * omitted wire fields are normalized to `null`.
203
+ */
174
204
  export declare interface HandshakePayload {
175
- authToken: AuthToken;
176
- extensionDetails: ExtensionDetails;
177
- designTokens: DesignTokens;
205
+ authToken: AuthToken | null;
206
+ extensionDetails: ExtensionDetails | null;
207
+ designTokens: DesignTokens | null;
178
208
  pageSettings: PageSettings | null;
179
209
  }
180
210
 
@@ -200,10 +230,16 @@ export declare interface IExtensionSDK {
200
230
  * Opt-in proactive refresh can be enabled with
201
231
  * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).
202
232
  *
233
+ * Handshake completes when the parent sends `HANDSHAKE_ACK`. Context fields
234
+ * (`authToken`, `extensionDetails`, `designTokens`, `pageSettings`) are
235
+ * optional — omitted values are `null`. Use them only when the current host
236
+ * surface provides them (e.g. `spaceId` / `pageId` on Experience Pages,
237
+ * `designTokens` when a tenant is in context).
238
+ *
203
239
  * @param options - Handshake timeout and optional auth-token auto-refresh.
204
240
  * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
205
241
  * @throws When not embedded in a parent frame, destroyed, or the handshake
206
- * times out / returns an invalid payload.
242
+ * times out.
207
243
  */
208
244
  connect(options?: ConnectOptions): Promise<HandshakePayload>;
209
245
  /**
@@ -215,11 +251,20 @@ export declare interface IExtensionSDK {
215
251
  destroy(): void;
216
252
  /** Whether a successful handshake has completed and not been destroyed. */
217
253
  readonly isConnected: boolean;
218
- /** Last auth token from handshake or refresh; `null` until connected. */
254
+ /**
255
+ * Last auth token from handshake or refresh.
256
+ * `null` until connected, or when the host omitted it.
257
+ */
219
258
  getAuthToken(): AuthToken | null;
220
- /** Extension context from handshake; `null` until connected. */
259
+ /**
260
+ * Extension context from handshake.
261
+ * `null` until connected, or when the host omitted it.
262
+ */
221
263
  getExtensionDetails(): ExtensionDetails | null;
222
- /** Design tokens from handshake; `null` until connected. */
264
+ /**
265
+ * Tenant branding from handshake.
266
+ * `null` until connected, or when the host omitted it.
267
+ */
223
268
  getDesignTokens(): DesignTokens | null;
224
269
  /**
225
270
  * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;
@@ -237,8 +282,8 @@ export declare interface IExtensionSDK {
237
282
  */
238
283
  getTenantUrl(): string | null;
239
284
  /**
240
- * Platform API origin derived from handshake `baseUrl`;
241
- * `null` until connected.
285
+ * Platform API origin derived from handshake `baseUrl`.
286
+ * `null` until connected, or when `extensionDetails.baseUrl` was omitted.
242
287
  */
243
288
  getApiUrl(): string | null;
244
289
  /**
@@ -250,8 +295,9 @@ export declare interface IExtensionSDK {
250
295
  * @typeParam T - Expected JSON response body.
251
296
  * @param endpoint - Root-relative API path.
252
297
  * @param options - Standard fetch options.
253
- * @throws When not connected, the endpoint is invalid, the request fails,
254
- * or the response is not successful JSON.
298
+ * @throws When not connected, handshake did not include `authToken` and
299
+ * `extensionDetails` (`baseUrl`, `tenantDomain`), the endpoint is invalid,
300
+ * the request fails, or the response is not successful JSON.
255
301
  */
256
302
  apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;
257
303
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadanza/extension-sdk",
3
- "version": "0.0.14",
3
+ "version": "0.1.0",
4
4
  "description": "Helpers for building extensions",
5
5
  "keywords": [
6
6
  "kadanza",