@kadanza/extension-sdk 0.0.8 → 0.0.10

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
@@ -2,6 +2,29 @@
2
2
 
3
3
  Helpers for building Kadanza extensions
4
4
 
5
+ ## Quick start with AI
6
+
7
+ Fill in `<APP_NAME>` and `<EXTENSION_BUILDERS_DESCRIBE_THEIR_ADDITIONAL_FEATURE_REQUIREMENTS_HERE>`, then paste this into Cursor, Lovable, or a similar AI tool.
8
+
9
+ > You are building a Kadanza extension called "<APP_NAME>".
10
+ > It is a React + Vite + TypeScript app that uses @kadanza/extension-sdk and runs embedded in Kadanza.
11
+ >
12
+ > Before writing code, read and follow these sources of truth:
13
+ > - How the host embeds you: https://github.com/kadanza-io/extension-sdk/blob/main/docs/embedding.md
14
+ > - React setup and wiring: https://github.com/kadanza-io/extension-sdk/blob/main/docs/react.md
15
+ > - SDK flows you must support: https://github.com/kadanza-io/extension-sdk/blob/main/docs/flows.md
16
+ >
17
+ > Do not invent your own host communication, handshake, or API auth. Prefer the patterns and APIs described in those docs over improvising.
18
+ >
19
+ > App requirements (from the builder):
20
+ > <EXTENSION_BUILDERS_DESCRIBE_THEIR_ADDITIONAL_FEATURE_REQUIREMENTS_HERE>
21
+ >
22
+ > When done, verify against the docs:
23
+ > - Embedding constraints in embedding.md are respected
24
+ > - Structure matches react.md
25
+ > - Required flows from flows.md are implemented
26
+ > - The app requirements above are covered
27
+
5
28
  ## Get started
6
29
 
7
30
  ### Install
@@ -24,11 +47,15 @@ const sdk: IExtensionSDK = createExtensionSDK();
24
47
  const { authToken, extensionDetails, designTokens } = await sdk.connect();
25
48
  ```
26
49
 
27
- Check [Flows.md](/docs/flows.md) for all available flows.
50
+ Create and use the SDK once per app. Later `createExtensionSDK()` calls return the same instance.
51
+
52
+ Check [Flows.md](/docs/flows.md) for all available flows. Building with React? See [Building a React extension](/docs/react.md).
28
53
 
29
54
  ## Documentation
30
55
 
31
56
  - [Documentation folder](docs/) — This is were the important information is stored
57
+ - [How Kadanza embeds an extension](docs/embedding.md) — Host iframe, allowed origins, and `tenantUrl`
58
+ - [Building a React extension](docs/react.md) — Bootstrap the SDK in a React app
32
59
  - [API reference](https://kadanza-io.github.io/extension-sdk/) — TypeDoc generated documentation describing all the small details, classes, interfaces, payloads...
33
60
 
34
61
  ## Local development
@@ -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=!1;#r=!1;#i=null;#a=null;#o=null;#s=null;#c=!1;#l=d;#u=null;#d=!1;#f=null;#p=null;#m=null;#h=new Set;#g=new Set;get isConnected(){return this.#n}async connect(e={}){if(this.#S(),this.#n&&this.#i&&this.#a&&this.#o)return{authToken:this.#i,extensionDetails:this.#a,designTokens:this.#o,pageSettings:this.#s};if(this.#f)throw Error(`Handshake already in progress.`);this.#w(e);let t=l();this.#e=c(t),this.#t?.(),this.#t=a(this.#e,e=>this.#_(e));let n=e.timeoutMs??u,o=new Promise((e,t)=>{let r=setTimeout(()=>{this.#f=null,t(Error(`Handshake timed out after ${n}ms.`))},n);this.#f={resolve:e,reject:t,timer:r}});try{i(r.handshakeInit,this.#e)}catch(e){throw this.#j(this.#f),this.#f=null,this.#t?.(),this.#t=null,this.#e=null,this.#D(),e}return o}destroy(){this.#r=!0,this.#n=!1,this.#D(),this.#c=!1,this.#d=!1,this.#t?.(),this.#t=null,this.#e=null,this.#N(this.#f,Error(`SDK destroyed.`)),this.#N(this.#p,Error(`SDK destroyed.`)),this.#N(this.#m,Error(`SDK destroyed.`)),this.#f=null,this.#p=null,this.#m=null,this.#h.clear(),this.#g.clear(),this.#i=null,this.#a=null,this.#o=null,this.#s=null}getAuthToken(){return this.#i}getExtensionDetails(){return this.#a}getDesignTokens(){return this.#o}getPageSettings(){return this.#s}async apiCall(e,t={}){return this.#C(),n(e,{baseUrl:this.#a.baseUrl,tenantDomain:this.#a.tenantDomain,authTokenJwt:this.#i.jwt},t)}async emitRequestAuthTokenRefresh(e={}){if(this.#C(),this.#p)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.#p=null,n(Error(`Auth token refresh timed out after ${t}ms.`))},t);this.#p={resolve:e,reject:n,timer:r}});return i(r.requestAuthTokenRefresh,n),a}async emitUpdatePageSettings(e,t={}){if(this.#C(),this.#m)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.#m=null,t(Error(`Page settings update timed out after ${n}ms.`))},n);this.#m={resolve:e,reject:t,timer:r}});return i(r.updatePageSettings,a,e),o}onLoadPageSettings(e){return this.#h.add(e),()=>{this.#h.delete(e)}}onAuthTokenRefresh(e){return this.#g.add(e),()=>{this.#g.delete(e)}}emitNavigationChange(e){this.#C(),i(r.navigationChange,this.#e,e)}#_(e){let{type:t,payload:n}=e.data;switch(t){case r.handshakeAck:this.#v(n);break;case r.authTokenRefresh:this.#y(n);break;case r.loadPageSettings:this.#b(n);break;case r.pageSettingsUpdated:this.#x(n);break;default:break}}#v(e){if(!e?.authToken||!e.extensionDetails||!e.designTokens){this.#N(this.#f,Error(`Invalid HANDSHAKE_ACK payload.`)),this.#f=null;return}this.#i=e.authToken,this.#a=e.extensionDetails,this.#o=e.designTokens,this.#s=e.pageSettings??null,this.#n=!0;let t=this.#f;this.#f=null,this.#M(t,{authToken:this.#i,extensionDetails:this.#a,designTokens:this.#o,pageSettings:this.#s}),this.#d=!1,this.#O()}#y(e){if(!e?.authToken){this.#N(this.#p,Error(`Invalid TOKEN_REFRESH payload.`)),this.#p=null;return}this.#i=e.authToken;let t=this.#p;this.#p=null,this.#M(t,this.#i);for(let e of this.#g)e(this.#i);this.#d=!1,this.#O()}#b(e){let t=e??{};this.#s=t;for(let e of this.#h)e(t)}#x(e){if(!e||typeof e.success!=`boolean`){this.#N(this.#m,Error(`Invalid PAGE_SETTINGS_UPDATED payload.`)),this.#m=null;return}let t=this.#m;this.#m=null,this.#M(t,e)}#S(){if(this.#r)throw Error(`SDK has been destroyed.`)}#C(){if(this.#S(),!this.#n||!this.#e)throw Error(`SDK is not connected. Call connect() first.`)}#w(e){if(e.authTokenBufferMs!==void 0&&(!Number.isFinite(e.authTokenBufferMs)||e.authTokenBufferMs<0))throw Error(`authTokenBufferMs must be a finite non-negative number.`);this.#c=e.authTokenAutoRefresh===!0,this.#l=e.authTokenBufferMs??d,this.#d=!1,this.#D()}#T(e){let t=Number(e.expires);return Number.isFinite(t)?t*1e3:null}#E(e){let t=this.#T(e);return t===null?null:Math.max(0,t-this.#l-Date.now())}#D(){this.#u!==null&&(clearTimeout(this.#u),this.#u=null)}#O(){if(this.#D(),!this.#c||!this.#n||this.#r||!this.#i)return;let e=this.#E(this.#i);if(e===null)return;let t=Math.min(e,p);this.#u=setTimeout(()=>{this.#u=null,this.#k()},t)}async#k(){if(!this.#c||!this.#n||this.#r||!this.#i)return;let e=this.#i,t=this.#E(e);if(t!==null){if(t>0){this.#O();return}if(this.#p){this.#A(e);return}try{await this.emitRequestAuthTokenRefresh()}catch{this.#A(e)}}}#A(e){if(!this.#c||!this.#n||this.#r||this.#i?.jwt!==e.jwt||this.#d)return;let t=this.#T(e);if(t===null)return;let n=t-Date.now();if(n<=0)return;this.#d=!0,this.#D();let r=Math.min(f,n);this.#u=setTimeout(()=>{this.#u=null,this.#k()},r)}#j(e){e&&clearTimeout(e.timer)}#M(e,t){e&&(clearTimeout(e.timer),e.resolve(t))}#N(e,t){e&&(clearTimeout(e.timer),e.reject(t))}},h=()=>new m;exports.CONNECTION_EVENTS=r,exports.ExtensionSDK=m,exports.InvalidOriginError=s,exports.createExtensionSDK=h,exports.readTenantUrlFromLocation=l,exports.resolveAllowedOrigin=c;
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))}},h=null,g=!1,_=class extends m{destroy(){super.destroy(),h===this&&(h=null,g=!1)}},v=()=>h?(g||=(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),h):(h=new _,h);exports.CONNECTION_EVENTS=r,exports.ExtensionSDK=m,exports.InvalidOriginError=s,exports.createExtensionSDK=v,exports.readTenantUrlFromLocation=l,exports.resolveAllowedOrigin=c;
2
2
  //# sourceMappingURL=extension-sdk.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension-sdk.cjs","names":["#connected","#assertNotDestroyed","#authToken","#extensionDetails","#designTokens","#pageSettings","#pendingHandshake","#configureAuthTokenAutoRefresh","#allowedOrigin","#unsubscribe","#onMessage","#clearPending","#clearProactiveAuthTokenRefreshTimer","#destroyed","#authTokenAutoRefresh","#authTokenRefreshRetryUsed","#rejectPending","#pendingAuthTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#authTokenRefreshHandlers","#assertConnected","#handleHandshakeAck","#handleAuthTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending","#scheduleProactiveAuthTokenRefresh","#authTokenBufferMs","#parseExpiresAtMs","#authTokenRefreshTimer","#getProactiveRefreshDelayMs","#runProactiveAuthTokenRefresh","#handleProactiveAuthTokenRefreshFailure"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/index.ts"],"sourcesContent":["interface ApiRequestContext {\n baseUrl: string;\n tenantDomain: string;\n authTokenJwt: string;\n}\n\nfunction 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 } 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 * If already connected with cached handshake data, returns that payload\n * without sending another init. Concurrent calls while a handshake is\n * in progress throw.\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, already handshaking,\n * destroyed, or the handshake 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 */\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 * 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 #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 #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 return {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n };\n }\n\n if (this.#pendingHandshake) {\n throw new Error(\"Handshake already in progress.\");\n }\n\n this.#configureAuthTokenAutoRefresh(options);\n\n const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(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 reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = 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\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.#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 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 case CONNECTION_EVENTS.authTokenRefresh:\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n case CONNECTION_EVENTS.loadPageSettings:\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n case CONNECTION_EVENTS.pageSettingsUpdated:\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n default:\n break;\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 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.#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 { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\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\n/** Creates a new {@link IExtensionSDK} instance for the current extension frame. */\nexport const createExtensionSDK = (): IExtensionSDK => {\n return new ExtensionSDK();\n};\n"],"mappings":"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"}
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"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.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 case CONNECTION_EVENTS.authTokenRefresh:\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n case CONNECTION_EVENTS.loadPageSettings:\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n case CONNECTION_EVENTS.pageSettingsUpdated:\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n default:\n break;\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 { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\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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"}
@@ -80,81 +80,93 @@ function l(e = window.location.search) {
80
80
  var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
81
81
  #e = null;
82
82
  #t = null;
83
- #n = !1;
83
+ #n = null;
84
84
  #r = !1;
85
- #i = null;
85
+ #i = !1;
86
86
  #a = null;
87
87
  #o = null;
88
88
  #s = null;
89
- #c = !1;
90
- #l = d;
91
- #u = null;
92
- #d = !1;
93
- #f = null;
89
+ #c = null;
90
+ #l = !1;
91
+ #u = d;
92
+ #d = null;
93
+ #f = !1;
94
94
  #p = null;
95
95
  #m = null;
96
- #h = /* @__PURE__ */ new Set();
97
- #g = /* @__PURE__ */ new Set();
96
+ #h = null;
97
+ #g = null;
98
+ #_ = /* @__PURE__ */ new Set();
99
+ #v = /* @__PURE__ */ new Set();
98
100
  get isConnected() {
99
- return this.#n;
101
+ return this.#r;
100
102
  }
101
103
  async connect(e = {}) {
102
- if (this.#S(), this.#n && this.#i && this.#a && this.#o) return {
103
- authToken: this.#i,
104
- extensionDetails: this.#a,
105
- designTokens: this.#o,
106
- pageSettings: this.#s
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
107
109
  };
108
- if (this.#f) throw Error("Handshake already in progress.");
109
- this.#w(e);
110
+ if (this.#m) return this.#m;
111
+ this.#E(e);
110
112
  let t = l();
111
- this.#e = c(t), this.#t?.(), this.#t = a(this.#e, (e) => this.#_(e));
113
+ this.#e = c(t), this.#t = t, this.#n?.(), this.#n = a(this.#e, (e) => this.#y(e));
112
114
  let n = e.timeoutMs ?? u, o = new Promise((e, t) => {
113
115
  let r = setTimeout(() => {
114
- this.#f = null, t(/* @__PURE__ */ Error(`Handshake timed out after ${n}ms.`));
116
+ this.#p = null, this.#m = null, t(/* @__PURE__ */ Error(`Handshake timed out after ${n}ms.`));
115
117
  }, n);
116
- this.#f = {
118
+ this.#p = {
117
119
  resolve: e,
118
120
  reject: t,
119
121
  timer: r
120
122
  };
121
123
  });
124
+ this.#m = o;
122
125
  try {
123
126
  i(r.handshakeInit, this.#e);
124
127
  } catch (e) {
125
- throw this.#j(this.#f), this.#f = null, this.#t?.(), this.#t = null, this.#e = null, this.#D(), 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
129
  }
127
130
  return o;
128
131
  }
129
132
  destroy() {
130
- this.#r = !0, this.#n = !1, this.#D(), this.#c = !1, this.#d = !1, this.#t?.(), this.#t = null, this.#e = null, this.#N(this.#f, /* @__PURE__ */ Error("SDK destroyed.")), this.#N(this.#p, /* @__PURE__ */ Error("SDK destroyed.")), this.#N(this.#m, /* @__PURE__ */ Error("SDK destroyed.")), this.#f = null, this.#p = null, this.#m = null, this.#h.clear(), this.#g.clear(), this.#i = null, this.#a = null, this.#o = null, this.#s = null;
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
134
  }
132
135
  getAuthToken() {
133
- return this.#i;
136
+ return this.#a;
134
137
  }
135
138
  getExtensionDetails() {
136
- return this.#a;
139
+ return this.#o;
137
140
  }
138
141
  getDesignTokens() {
139
- return this.#o;
142
+ return this.#s;
140
143
  }
141
144
  getPageSettings() {
142
- return this.#s;
145
+ return this.#c;
146
+ }
147
+ getAllowedOrigin() {
148
+ return this.#e;
149
+ }
150
+ getTenantUrl() {
151
+ return this.#t;
152
+ }
153
+ getApiUrl() {
154
+ return this.#o ? e(this.#o.baseUrl) : null;
143
155
  }
144
156
  async apiCall(e, t = {}) {
145
- return this.#C(), n(e, {
146
- baseUrl: this.#a.baseUrl,
147
- tenantDomain: this.#a.tenantDomain,
148
- authTokenJwt: this.#i.jwt
157
+ return this.#T(), n(e, {
158
+ baseUrl: this.#o.baseUrl,
159
+ tenantDomain: this.#o.tenantDomain,
160
+ authTokenJwt: this.#a.jwt
149
161
  }, t);
150
162
  }
151
163
  async emitRequestAuthTokenRefresh(e = {}) {
152
- if (this.#C(), this.#p) throw Error("Auth token refresh already in progress.");
164
+ if (this.#T(), this.#h) throw Error("Auth token refresh already in progress.");
153
165
  let t = e.timeoutMs ?? u, n = this.#e, a = new Promise((e, n) => {
154
166
  let r = setTimeout(() => {
155
- this.#p = null, n(/* @__PURE__ */ Error(`Auth token refresh timed out after ${t}ms.`));
167
+ this.#h = null, n(/* @__PURE__ */ Error(`Auth token refresh timed out after ${t}ms.`));
156
168
  }, t);
157
- this.#p = {
169
+ this.#h = {
158
170
  resolve: e,
159
171
  reject: n,
160
172
  timer: r
@@ -163,12 +175,12 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
163
175
  return i(r.requestAuthTokenRefresh, n), a;
164
176
  }
165
177
  async emitUpdatePageSettings(e, t = {}) {
166
- if (this.#C(), this.#m) throw Error("Page settings update already in progress.");
178
+ if (this.#T(), this.#g) throw Error("Page settings update already in progress.");
167
179
  let n = t.timeoutMs ?? u, a = this.#e, o = new Promise((e, t) => {
168
180
  let r = setTimeout(() => {
169
- this.#m = null, t(/* @__PURE__ */ Error(`Page settings update timed out after ${n}ms.`));
181
+ this.#g = null, t(/* @__PURE__ */ Error(`Page settings update timed out after ${n}ms.`));
170
182
  }, n);
171
- this.#m = {
183
+ this.#g = {
172
184
  resolve: e,
173
185
  reject: t,
174
186
  timer: r
@@ -177,146 +189,150 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
177
189
  return i(r.updatePageSettings, a, e), o;
178
190
  }
179
191
  onLoadPageSettings(e) {
180
- return this.#h.add(e), () => {
181
- this.#h.delete(e);
192
+ return this.#_.add(e), () => {
193
+ this.#_.delete(e);
182
194
  };
183
195
  }
184
196
  onAuthTokenRefresh(e) {
185
- return this.#g.add(e), () => {
186
- this.#g.delete(e);
197
+ return this.#v.add(e), () => {
198
+ this.#v.delete(e);
187
199
  };
188
200
  }
189
201
  emitNavigationChange(e) {
190
- this.#C(), i(r.navigationChange, this.#e, e);
202
+ this.#T(), i(r.navigationChange, this.#e, e);
191
203
  }
192
- #_(e) {
204
+ #y(e) {
193
205
  let { type: t, payload: n } = e.data;
194
206
  switch (t) {
195
207
  case r.handshakeAck:
196
- this.#v(n);
208
+ this.#b(n);
197
209
  break;
198
210
  case r.authTokenRefresh:
199
- this.#y(n);
211
+ this.#x(n);
200
212
  break;
201
213
  case r.loadPageSettings:
202
- this.#b(n);
214
+ this.#S(n);
203
215
  break;
204
216
  case r.pageSettingsUpdated:
205
- this.#x(n);
217
+ this.#C(n);
206
218
  break;
207
219
  default: break;
208
220
  }
209
221
  }
210
- #v(e) {
222
+ #b(e) {
211
223
  if (!e?.authToken || !e.extensionDetails || !e.designTokens) {
212
- this.#N(this.#f, /* @__PURE__ */ Error("Invalid HANDSHAKE_ACK payload.")), this.#f = null;
224
+ this.#F(this.#p, /* @__PURE__ */ Error("Invalid HANDSHAKE_ACK payload.")), this.#p = null, this.#m = null;
213
225
  return;
214
226
  }
215
- this.#i = e.authToken, this.#a = e.extensionDetails, this.#o = e.designTokens, this.#s = e.pageSettings ?? null, this.#n = !0;
216
- let t = this.#f;
217
- this.#f = null, this.#M(t, {
218
- authToken: this.#i,
219
- extensionDetails: this.#a,
220
- designTokens: this.#o,
221
- pageSettings: this.#s
222
- }), this.#d = !1, this.#O();
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, {
230
+ authToken: this.#a,
231
+ extensionDetails: this.#o,
232
+ designTokens: this.#s,
233
+ pageSettings: this.#c
234
+ }), this.#f = !1, this.#A();
223
235
  }
224
- #y(e) {
236
+ #x(e) {
225
237
  if (!e?.authToken) {
226
- this.#N(this.#p, /* @__PURE__ */ Error("Invalid TOKEN_REFRESH payload.")), this.#p = null;
238
+ this.#F(this.#h, /* @__PURE__ */ Error("Invalid TOKEN_REFRESH payload.")), this.#h = null;
227
239
  return;
228
240
  }
229
- this.#i = e.authToken;
230
- let t = this.#p;
231
- this.#p = null, this.#M(t, this.#i);
232
- for (let e of this.#g) e(this.#i);
233
- this.#d = !1, this.#O();
241
+ this.#a = e.authToken;
242
+ let t = this.#h;
243
+ this.#h = null, this.#P(t, this.#a);
244
+ for (let e of this.#v) e(this.#a);
245
+ this.#f = !1, this.#A();
234
246
  }
235
- #b(e) {
247
+ #S(e) {
236
248
  let t = e ?? {};
237
- this.#s = t;
238
- for (let e of this.#h) e(t);
249
+ this.#c = t;
250
+ for (let e of this.#_) e(t);
239
251
  }
240
- #x(e) {
252
+ #C(e) {
241
253
  if (!e || typeof e.success != "boolean") {
242
- this.#N(this.#m, /* @__PURE__ */ Error("Invalid PAGE_SETTINGS_UPDATED payload.")), this.#m = null;
254
+ this.#F(this.#g, /* @__PURE__ */ Error("Invalid PAGE_SETTINGS_UPDATED payload.")), this.#g = null;
243
255
  return;
244
256
  }
245
- let t = this.#m;
246
- this.#m = null, this.#M(t, e);
257
+ let t = this.#g;
258
+ this.#g = null, this.#P(t, e);
247
259
  }
248
- #S() {
249
- if (this.#r) throw Error("SDK has been destroyed.");
260
+ #w() {
261
+ if (this.#i) throw Error("SDK has been destroyed.");
250
262
  }
251
- #C() {
252
- if (this.#S(), !this.#n || !this.#e) throw Error("SDK is not connected. Call connect() first.");
263
+ #T() {
264
+ if (this.#w(), !this.#r || !this.#e) throw Error("SDK is not connected. Call connect() first.");
253
265
  }
254
- #w(e) {
266
+ #E(e) {
255
267
  if (e.authTokenBufferMs !== void 0 && (!Number.isFinite(e.authTokenBufferMs) || e.authTokenBufferMs < 0)) throw Error("authTokenBufferMs must be a finite non-negative number.");
256
- this.#c = e.authTokenAutoRefresh === !0, this.#l = e.authTokenBufferMs ?? d, this.#d = !1, this.#D();
268
+ this.#l = e.authTokenAutoRefresh === !0, this.#u = e.authTokenBufferMs ?? d, this.#f = !1, this.#k();
257
269
  }
258
- #T(e) {
270
+ #D(e) {
259
271
  let t = Number(e.expires);
260
272
  return Number.isFinite(t) ? t * 1e3 : null;
261
273
  }
262
- #E(e) {
263
- let t = this.#T(e);
264
- return t === null ? null : Math.max(0, t - this.#l - Date.now());
274
+ #O(e) {
275
+ let t = this.#D(e);
276
+ return t === null ? null : Math.max(0, t - this.#u - Date.now());
265
277
  }
266
- #D() {
267
- this.#u !== null && (clearTimeout(this.#u), this.#u = null);
278
+ #k() {
279
+ this.#d !== null && (clearTimeout(this.#d), this.#d = null);
268
280
  }
269
- #O() {
270
- if (this.#D(), !this.#c || !this.#n || this.#r || !this.#i) return;
271
- let e = this.#E(this.#i);
281
+ #A() {
282
+ if (this.#k(), !this.#l || !this.#r || this.#i || !this.#a) return;
283
+ let e = this.#O(this.#a);
272
284
  if (e === null) return;
273
285
  let t = Math.min(e, p);
274
- this.#u = setTimeout(() => {
275
- this.#u = null, this.#k();
286
+ this.#d = setTimeout(() => {
287
+ this.#d = null, this.#j();
276
288
  }, t);
277
289
  }
278
- async #k() {
279
- if (!this.#c || !this.#n || this.#r || !this.#i) return;
280
- let e = this.#i, t = this.#E(e);
290
+ async #j() {
291
+ if (!this.#l || !this.#r || this.#i || !this.#a) return;
292
+ let e = this.#a, t = this.#O(e);
281
293
  if (t !== null) {
282
294
  if (t > 0) {
283
- this.#O();
295
+ this.#A();
284
296
  return;
285
297
  }
286
- if (this.#p) {
287
- this.#A(e);
298
+ if (this.#h) {
299
+ this.#M(e);
288
300
  return;
289
301
  }
290
302
  try {
291
303
  await this.emitRequestAuthTokenRefresh();
292
304
  } catch {
293
- this.#A(e);
305
+ this.#M(e);
294
306
  }
295
307
  }
296
308
  }
297
- #A(e) {
298
- if (!this.#c || !this.#n || this.#r || this.#i?.jwt !== e.jwt || this.#d) return;
299
- let t = this.#T(e);
309
+ #M(e) {
310
+ if (!this.#l || !this.#r || this.#i || this.#a?.jwt !== e.jwt || this.#f) return;
311
+ let t = this.#D(e);
300
312
  if (t === null) return;
301
313
  let n = t - Date.now();
302
314
  if (n <= 0) return;
303
- this.#d = !0, this.#D();
315
+ this.#f = !0, this.#k();
304
316
  let r = Math.min(f, n);
305
- this.#u = setTimeout(() => {
306
- this.#u = null, this.#k();
317
+ this.#d = setTimeout(() => {
318
+ this.#d = null, this.#j();
307
319
  }, r);
308
320
  }
309
- #j(e) {
321
+ #N(e) {
310
322
  e && clearTimeout(e.timer);
311
323
  }
312
- #M(e, t) {
324
+ #P(e, t) {
313
325
  e && (clearTimeout(e.timer), e.resolve(t));
314
326
  }
315
- #N(e, t) {
327
+ #F(e, t) {
316
328
  e && (clearTimeout(e.timer), e.reject(t));
317
329
  }
318
- }, h = () => new m();
330
+ }, h = null, g = !1, _ = class extends m {
331
+ destroy() {
332
+ super.destroy(), h === this && (h = null, g = !1);
333
+ }
334
+ }, v = () => h ? (g ||= (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), h) : (h = new _(), h);
319
335
  //#endregion
320
- export { r as CONNECTION_EVENTS, m as ExtensionSDK, s as InvalidOriginError, h as createExtensionSDK, l as readTenantUrlFromLocation, c as resolveAllowedOrigin };
336
+ export { r as CONNECTION_EVENTS, m as ExtensionSDK, s as InvalidOriginError, v as createExtensionSDK, l as readTenantUrlFromLocation, c as resolveAllowedOrigin };
321
337
 
322
338
  //# sourceMappingURL=extension-sdk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension-sdk.js","names":["#connected","#assertNotDestroyed","#authToken","#extensionDetails","#designTokens","#pageSettings","#pendingHandshake","#configureAuthTokenAutoRefresh","#allowedOrigin","#unsubscribe","#onMessage","#clearPending","#clearProactiveAuthTokenRefreshTimer","#destroyed","#authTokenAutoRefresh","#authTokenRefreshRetryUsed","#rejectPending","#pendingAuthTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#authTokenRefreshHandlers","#assertConnected","#handleHandshakeAck","#handleAuthTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending","#scheduleProactiveAuthTokenRefresh","#authTokenBufferMs","#parseExpiresAtMs","#authTokenRefreshTimer","#getProactiveRefreshDelayMs","#runProactiveAuthTokenRefresh","#handleProactiveAuthTokenRefreshFailure"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/index.ts"],"sourcesContent":["interface ApiRequestContext {\n baseUrl: string;\n tenantDomain: string;\n authTokenJwt: string;\n}\n\nfunction 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 } 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 * If already connected with cached handshake data, returns that payload\n * without sending another init. Concurrent calls while a handshake is\n * in progress throw.\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, already handshaking,\n * destroyed, or the handshake 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 */\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 * 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 #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 #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 return {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n };\n }\n\n if (this.#pendingHandshake) {\n throw new Error(\"Handshake already in progress.\");\n }\n\n this.#configureAuthTokenAutoRefresh(options);\n\n const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(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 reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = 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\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.#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 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 case CONNECTION_EVENTS.authTokenRefresh:\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n case CONNECTION_EVENTS.loadPageSettings:\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n case CONNECTION_EVENTS.pageSettingsUpdated:\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n default:\n break;\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 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.#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 { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\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\n/** Creates a new {@link IExtensionSDK} instance for the current extension frame. */\nexport const createExtensionSDK = (): IExtensionSDK => {\n return new ExtensionSDK();\n};\n"],"mappings":"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"}
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"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.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 case CONNECTION_EVENTS.authTokenRefresh:\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n case CONNECTION_EVENTS.loadPageSettings:\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n case CONNECTION_EVENTS.pageSettingsUpdated:\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n default:\n break;\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 { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\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
@@ -69,7 +69,13 @@ export declare interface ConnectOptions {
69
69
  authTokenBufferMs?: number;
70
70
  }
71
71
 
72
- /** Creates a new {@link IExtensionSDK} instance for the current extension frame. */
72
+ /**
73
+ * Returns the shared {@link IExtensionSDK} for the current extension frame.
74
+ *
75
+ * Creates the instance on first call. Later calls return the same instance and
76
+ * log a console warning — use the SDK once per app. After {@link IExtensionSDK.destroy},
77
+ * the next call creates a fresh instance.
78
+ */
73
79
  export declare const createExtensionSDK: () => IExtensionSDK;
74
80
 
75
81
  /** Branding tokens from the parent for aligning extension UI. */
@@ -108,6 +114,9 @@ export declare class ExtensionSDK implements IExtensionSDK {
108
114
  getExtensionDetails(): ExtensionDetails | null;
109
115
  getDesignTokens(): DesignTokens | null;
110
116
  getPageSettings(): PageSettings | null;
117
+ getAllowedOrigin(): string | null;
118
+ getTenantUrl(): string | null;
119
+ getApiUrl(): string | null;
111
120
  apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;
112
121
  emitRequestAuthTokenRefresh(options?: RequestOptions): Promise<AuthToken>;
113
122
  emitUpdatePageSettings(payload: UpdatePageSettingsPayload, options?: RequestOptions): Promise<PageSettingsUpdatedPayload>;
@@ -134,22 +143,27 @@ export declare interface IExtensionSDK {
134
143
  /**
135
144
  * Establishes the parent connection and waits for `HANDSHAKE_ACK`.
136
145
  *
137
- * If already connected with cached handshake data, returns that payload
138
- * without sending another init. Concurrent calls while a handshake is
139
- * in progress throw.
146
+ * Safe to call repeatedly (e.g. from a React `useEffect` with deps):
147
+ * - Already connected: returns the cached handshake and re-applies
148
+ * `authTokenAutoRefresh` / `authTokenBufferMs` without another init.
149
+ * `timeoutMs` is ignored after connect.
150
+ * - Handshake in progress: returns the same in-flight promise. Options from
151
+ * the first connecting call apply until the handshake completes.
140
152
  *
141
153
  * Opt-in proactive refresh can be enabled with
142
154
  * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).
143
155
  *
144
156
  * @param options - Handshake timeout and optional auth-token auto-refresh.
145
157
  * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
146
- * @throws When not embedded in a parent frame, already handshaking,
147
- * destroyed, or the handshake times out / returns an invalid payload.
158
+ * @throws When not embedded in a parent frame, destroyed, or the handshake
159
+ * times out / returns an invalid payload.
148
160
  */
149
161
  connect(options?: ConnectOptions): Promise<HandshakePayload>;
150
162
  /**
151
163
  * Tears down listeners, proactive refresh timers, and cached state.
152
164
  * Rejects any in-flight requests. The instance cannot be reused after destroy.
165
+ * When created via {@link createExtensionSDK}, clears the singleton so a
166
+ * later create call can return a fresh instance.
153
167
  */
154
168
  destroy(): void;
155
169
  /** Whether a successful handshake has completed and not been destroyed. */
@@ -165,6 +179,21 @@ export declare interface IExtensionSDK {
165
179
  * `null` until set.
166
180
  */
167
181
  getPageSettings(): PageSettings | null;
182
+ /**
183
+ * Validated parent `postMessage` origin from `tenantUrl`;
184
+ * `null` until `connect` resolves it.
185
+ */
186
+ getAllowedOrigin(): string | null;
187
+ /**
188
+ * Raw `tenantUrl` search-param value used for the allowed origin;
189
+ * `null` until `connect` reads it.
190
+ */
191
+ getTenantUrl(): string | null;
192
+ /**
193
+ * Platform API origin derived from handshake `baseUrl`;
194
+ * `null` until connected.
195
+ */
196
+ getApiUrl(): string | null;
168
197
  /**
169
198
  * Calls the Kadanza Platform API with the current connection credentials.
170
199
  *
package/dist/index.d.ts CHANGED
@@ -69,7 +69,13 @@ export declare interface ConnectOptions {
69
69
  authTokenBufferMs?: number;
70
70
  }
71
71
 
72
- /** Creates a new {@link IExtensionSDK} instance for the current extension frame. */
72
+ /**
73
+ * Returns the shared {@link IExtensionSDK} for the current extension frame.
74
+ *
75
+ * Creates the instance on first call. Later calls return the same instance and
76
+ * log a console warning — use the SDK once per app. After {@link IExtensionSDK.destroy},
77
+ * the next call creates a fresh instance.
78
+ */
73
79
  export declare const createExtensionSDK: () => IExtensionSDK;
74
80
 
75
81
  /** Branding tokens from the parent for aligning extension UI. */
@@ -108,6 +114,9 @@ export declare class ExtensionSDK implements IExtensionSDK {
108
114
  getExtensionDetails(): ExtensionDetails | null;
109
115
  getDesignTokens(): DesignTokens | null;
110
116
  getPageSettings(): PageSettings | null;
117
+ getAllowedOrigin(): string | null;
118
+ getTenantUrl(): string | null;
119
+ getApiUrl(): string | null;
111
120
  apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;
112
121
  emitRequestAuthTokenRefresh(options?: RequestOptions): Promise<AuthToken>;
113
122
  emitUpdatePageSettings(payload: UpdatePageSettingsPayload, options?: RequestOptions): Promise<PageSettingsUpdatedPayload>;
@@ -134,22 +143,27 @@ export declare interface IExtensionSDK {
134
143
  /**
135
144
  * Establishes the parent connection and waits for `HANDSHAKE_ACK`.
136
145
  *
137
- * If already connected with cached handshake data, returns that payload
138
- * without sending another init. Concurrent calls while a handshake is
139
- * in progress throw.
146
+ * Safe to call repeatedly (e.g. from a React `useEffect` with deps):
147
+ * - Already connected: returns the cached handshake and re-applies
148
+ * `authTokenAutoRefresh` / `authTokenBufferMs` without another init.
149
+ * `timeoutMs` is ignored after connect.
150
+ * - Handshake in progress: returns the same in-flight promise. Options from
151
+ * the first connecting call apply until the handshake completes.
140
152
  *
141
153
  * Opt-in proactive refresh can be enabled with
142
154
  * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).
143
155
  *
144
156
  * @param options - Handshake timeout and optional auth-token auto-refresh.
145
157
  * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
146
- * @throws When not embedded in a parent frame, already handshaking,
147
- * destroyed, or the handshake times out / returns an invalid payload.
158
+ * @throws When not embedded in a parent frame, destroyed, or the handshake
159
+ * times out / returns an invalid payload.
148
160
  */
149
161
  connect(options?: ConnectOptions): Promise<HandshakePayload>;
150
162
  /**
151
163
  * Tears down listeners, proactive refresh timers, and cached state.
152
164
  * Rejects any in-flight requests. The instance cannot be reused after destroy.
165
+ * When created via {@link createExtensionSDK}, clears the singleton so a
166
+ * later create call can return a fresh instance.
153
167
  */
154
168
  destroy(): void;
155
169
  /** Whether a successful handshake has completed and not been destroyed. */
@@ -165,6 +179,21 @@ export declare interface IExtensionSDK {
165
179
  * `null` until set.
166
180
  */
167
181
  getPageSettings(): PageSettings | null;
182
+ /**
183
+ * Validated parent `postMessage` origin from `tenantUrl`;
184
+ * `null` until `connect` resolves it.
185
+ */
186
+ getAllowedOrigin(): string | null;
187
+ /**
188
+ * Raw `tenantUrl` search-param value used for the allowed origin;
189
+ * `null` until `connect` reads it.
190
+ */
191
+ getTenantUrl(): string | null;
192
+ /**
193
+ * Platform API origin derived from handshake `baseUrl`;
194
+ * `null` until connected.
195
+ */
196
+ getApiUrl(): string | null;
168
197
  /**
169
198
  * Calls the Kadanza Platform API with the current connection credentials.
170
199
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadanza/extension-sdk",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Helpers for building extensions",
5
5
  "keywords": [
6
6
  "kadanza",