@kadanza/extension-sdk 0.0.9 → 0.0.11

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,31 @@
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
+ >
14
+ > - How the host embeds you: https://github.com/kadanza-io/extension-sdk/blob/main/docs/embedding.md
15
+ > - React setup and wiring: https://github.com/kadanza-io/extension-sdk/blob/main/docs/react-guide.md
16
+ > - SDK flows you must support: https://github.com/kadanza-io/extension-sdk/blob/main/docs/flows.md
17
+ >
18
+ > Do not invent your own host communication, handshake, or API auth. Prefer the patterns and APIs described in those docs over improvising.
19
+ >
20
+ > App requirements (from the builder):
21
+ > <EXTENSION_BUILDERS_DESCRIBE_THEIR_ADDITIONAL_FEATURE_REQUIREMENTS_HERE>
22
+ >
23
+ > When done, verify against the docs:
24
+ >
25
+ > - Embedding constraints in embedding.md are respected
26
+ > - Structure matches react-guide.md
27
+ > - Required flows from flows.md are implemented
28
+ > - The app requirements above are covered
29
+
5
30
  ## Get started
6
31
 
7
32
  ### Install
@@ -24,11 +49,15 @@ const sdk: IExtensionSDK = createExtensionSDK();
24
49
  const { authToken, extensionDetails, designTokens } = await sdk.connect();
25
50
  ```
26
51
 
27
- Check [Flows.md](/docs/flows.md) for all available flows.
52
+ Create and use the SDK once per app. Later `createExtensionSDK()` calls return the same instance.
53
+
54
+ Check [Flows.md](/docs/flows.md) for all available flows. Building with React? See [Building a React extension](/docs/react-guide.md).
28
55
 
29
56
  ## Documentation
30
57
 
31
58
  - [Documentation folder](docs/) — This is were the important information is stored
59
+ - [How Kadanza embeds an extension](docs/embedding.md) — Host iframe, allowed origins, and `tenantUrl`
60
+ - [Building a React extension](docs/react-guide.md) — Bootstrap a React app with the Extension SDK
32
61
  - [API reference](https://kadanza-io.github.io/extension-sdk/) — TypeDoc generated documentation describing all the small details, classes, interfaces, payloads...
33
62
 
34
63
  ## 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=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=new Set;#_=new Set;get isConnected(){return this.#r}async connect(e={}){if(this.#C(),this.#r&&this.#a&&this.#o&&this.#s)return{authToken:this.#a,extensionDetails:this.#o,designTokens:this.#s,pageSettings:this.#c};if(this.#p)throw Error(`Handshake already in progress.`);this.#T(e);let t=l();this.#e=c(t),this.#t=t,this.#n?.(),this.#n=a(this.#e,e=>this.#v(e));let n=e.timeoutMs??u,o=new Promise((e,t)=>{let r=setTimeout(()=>{this.#p=null,t(Error(`Handshake timed out after ${n}ms.`))},n);this.#p={resolve:e,reject:t,timer:r}});try{i(r.handshakeInit,this.#e)}catch(e){throw this.#M(this.#p),this.#p=null,this.#n?.(),this.#n=null,this.#e=null,this.#t=null,this.#O(),e}return o}destroy(){this.#i=!0,this.#r=!1,this.#O(),this.#l=!1,this.#f=!1,this.#n?.(),this.#n=null,this.#e=null,this.#t=null,this.#P(this.#p,Error(`SDK destroyed.`)),this.#P(this.#m,Error(`SDK destroyed.`)),this.#P(this.#h,Error(`SDK destroyed.`)),this.#p=null,this.#m=null,this.#h=null,this.#g.clear(),this.#_.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.#w(),n(e,{baseUrl:this.#o.baseUrl,tenantDomain:this.#o.tenantDomain,authTokenJwt:this.#a.jwt},t)}async emitRequestAuthTokenRefresh(e={}){if(this.#w(),this.#m)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.#m=null,n(Error(`Auth token refresh timed out after ${t}ms.`))},t);this.#m={resolve:e,reject:n,timer:r}});return i(r.requestAuthTokenRefresh,n),a}async emitUpdatePageSettings(e,t={}){if(this.#w(),this.#h)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.#h=null,t(Error(`Page settings update timed out after ${n}ms.`))},n);this.#h={resolve:e,reject:t,timer:r}});return i(r.updatePageSettings,a,e),o}onLoadPageSettings(e){return this.#g.add(e),()=>{this.#g.delete(e)}}onAuthTokenRefresh(e){return this.#_.add(e),()=>{this.#_.delete(e)}}emitNavigationChange(e){this.#w(),i(r.navigationChange,this.#e,e)}#v(e){let{type:t,payload:n}=e.data;switch(t){case r.handshakeAck:this.#y(n);break;case r.authTokenRefresh:this.#b(n);break;case r.loadPageSettings:this.#x(n);break;case r.pageSettingsUpdated:this.#S(n);break;default:break}}#y(e){if(!e?.authToken||!e.extensionDetails||!e.designTokens){this.#P(this.#p,Error(`Invalid HANDSHAKE_ACK payload.`)),this.#p=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.#N(t,{authToken:this.#a,extensionDetails:this.#o,designTokens:this.#s,pageSettings:this.#c}),this.#f=!1,this.#k()}#b(e){if(!e?.authToken){this.#P(this.#m,Error(`Invalid TOKEN_REFRESH payload.`)),this.#m=null;return}this.#a=e.authToken;let t=this.#m;this.#m=null,this.#N(t,this.#a);for(let e of this.#_)e(this.#a);this.#f=!1,this.#k()}#x(e){let t=e??{};this.#c=t;for(let e of this.#g)e(t)}#S(e){if(!e||typeof e.success!=`boolean`){this.#P(this.#h,Error(`Invalid PAGE_SETTINGS_UPDATED payload.`)),this.#h=null;return}let t=this.#h;this.#h=null,this.#N(t,e)}#C(){if(this.#i)throw Error(`SDK has been destroyed.`)}#w(){if(this.#C(),!this.#r||!this.#e)throw Error(`SDK is not connected. Call connect() first.`)}#T(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.#O()}#E(e){let t=Number(e.expires);return Number.isFinite(t)?t*1e3:null}#D(e){let t=this.#E(e);return t===null?null:Math.max(0,t-this.#u-Date.now())}#O(){this.#d!==null&&(clearTimeout(this.#d),this.#d=null)}#k(){if(this.#O(),!this.#l||!this.#r||this.#i||!this.#a)return;let e=this.#D(this.#a);if(e===null)return;let t=Math.min(e,p);this.#d=setTimeout(()=>{this.#d=null,this.#A()},t)}async#A(){if(!this.#l||!this.#r||this.#i||!this.#a)return;let e=this.#a,t=this.#D(e);if(t!==null){if(t>0){this.#k();return}if(this.#m){this.#j(e);return}try{await this.emitRequestAuthTokenRefresh()}catch{this.#j(e)}}}#j(e){if(!this.#l||!this.#r||this.#i||this.#a?.jwt!==e.jwt||this.#f)return;let t=this.#E(e);if(t===null)return;let n=t-Date.now();if(n<=0)return;this.#f=!0,this.#O();let r=Math.min(f,n);this.#d=setTimeout(()=>{this.#d=null,this.#A()},r)}#M(e){e&&clearTimeout(e.timer)}#N(e,t){e&&(clearTimeout(e.timer),e.resolve(t))}#P(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","#tenantUrl","#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\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 * 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 * 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 #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 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 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.#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.#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 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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"}
@@ -94,25 +94,26 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
94
94
  #p = null;
95
95
  #m = null;
96
96
  #h = null;
97
- #g = /* @__PURE__ */ new Set();
97
+ #g = null;
98
98
  #_ = /* @__PURE__ */ new Set();
99
+ #v = /* @__PURE__ */ new Set();
99
100
  get isConnected() {
100
101
  return this.#r;
101
102
  }
102
103
  async connect(e = {}) {
103
- if (this.#C(), this.#r && this.#a && this.#o && this.#s) return {
104
+ if (this.#w(), this.#r && this.#a && this.#o && this.#s) return this.#E(e), this.#A(), {
104
105
  authToken: this.#a,
105
106
  extensionDetails: this.#o,
106
107
  designTokens: this.#s,
107
108
  pageSettings: this.#c
108
109
  };
109
- if (this.#p) throw Error("Handshake already in progress.");
110
- this.#T(e);
110
+ if (this.#m) return this.#m;
111
+ this.#E(e);
111
112
  let t = l();
112
- this.#e = c(t), this.#t = t, this.#n?.(), this.#n = a(this.#e, (e) => this.#v(e));
113
+ this.#e = c(t), this.#t = t, this.#n?.(), this.#n = a(this.#e, (e) => this.#y(e));
113
114
  let n = e.timeoutMs ?? u, o = new Promise((e, t) => {
114
115
  let r = setTimeout(() => {
115
- this.#p = 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.`));
116
117
  }, n);
117
118
  this.#p = {
118
119
  resolve: e,
@@ -120,15 +121,16 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
120
121
  timer: r
121
122
  };
122
123
  });
124
+ this.#m = o;
123
125
  try {
124
126
  i(r.handshakeInit, this.#e);
125
127
  } catch (e) {
126
- throw this.#M(this.#p), this.#p = null, this.#n?.(), this.#n = null, this.#e = null, this.#t = null, this.#O(), 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;
127
129
  }
128
130
  return o;
129
131
  }
130
132
  destroy() {
131
- this.#i = !0, this.#r = !1, this.#O(), this.#l = !1, this.#f = !1, this.#n?.(), this.#n = null, this.#e = null, this.#t = null, this.#P(this.#p, /* @__PURE__ */ Error("SDK destroyed.")), this.#P(this.#m, /* @__PURE__ */ Error("SDK destroyed.")), this.#P(this.#h, /* @__PURE__ */ Error("SDK destroyed.")), this.#p = null, this.#m = null, this.#h = null, this.#g.clear(), this.#_.clear(), this.#a = null, this.#o = null, this.#s = null, this.#c = 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;
132
134
  }
133
135
  getAuthToken() {
134
136
  return this.#a;
@@ -152,19 +154,19 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
152
154
  return this.#o ? e(this.#o.baseUrl) : null;
153
155
  }
154
156
  async apiCall(e, t = {}) {
155
- return this.#w(), n(e, {
157
+ return this.#T(), n(e, {
156
158
  baseUrl: this.#o.baseUrl,
157
159
  tenantDomain: this.#o.tenantDomain,
158
160
  authTokenJwt: this.#a.jwt
159
161
  }, t);
160
162
  }
161
163
  async emitRequestAuthTokenRefresh(e = {}) {
162
- if (this.#w(), this.#m) throw Error("Auth token refresh already in progress.");
164
+ if (this.#T(), this.#h) throw Error("Auth token refresh already in progress.");
163
165
  let t = e.timeoutMs ?? u, n = this.#e, a = new Promise((e, n) => {
164
166
  let r = setTimeout(() => {
165
- this.#m = 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.`));
166
168
  }, t);
167
- this.#m = {
169
+ this.#h = {
168
170
  resolve: e,
169
171
  reject: n,
170
172
  timer: r
@@ -173,12 +175,12 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
173
175
  return i(r.requestAuthTokenRefresh, n), a;
174
176
  }
175
177
  async emitUpdatePageSettings(e, t = {}) {
176
- if (this.#w(), this.#h) throw Error("Page settings update already in progress.");
178
+ if (this.#T(), this.#g) throw Error("Page settings update already in progress.");
177
179
  let n = t.timeoutMs ?? u, a = this.#e, o = new Promise((e, t) => {
178
180
  let r = setTimeout(() => {
179
- this.#h = 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.`));
180
182
  }, n);
181
- this.#h = {
183
+ this.#g = {
182
184
  resolve: e,
183
185
  reject: t,
184
186
  timer: r
@@ -187,146 +189,150 @@ var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
187
189
  return i(r.updatePageSettings, a, e), o;
188
190
  }
189
191
  onLoadPageSettings(e) {
190
- return this.#g.add(e), () => {
191
- this.#g.delete(e);
192
+ return this.#_.add(e), () => {
193
+ this.#_.delete(e);
192
194
  };
193
195
  }
194
196
  onAuthTokenRefresh(e) {
195
- return this.#_.add(e), () => {
196
- this.#_.delete(e);
197
+ return this.#v.add(e), () => {
198
+ this.#v.delete(e);
197
199
  };
198
200
  }
199
201
  emitNavigationChange(e) {
200
- this.#w(), i(r.navigationChange, this.#e, e);
202
+ this.#T(), i(r.navigationChange, this.#e, e);
201
203
  }
202
- #v(e) {
204
+ #y(e) {
203
205
  let { type: t, payload: n } = e.data;
204
206
  switch (t) {
205
207
  case r.handshakeAck:
206
- this.#y(n);
208
+ this.#b(n);
207
209
  break;
208
210
  case r.authTokenRefresh:
209
- this.#b(n);
211
+ this.#x(n);
210
212
  break;
211
213
  case r.loadPageSettings:
212
- this.#x(n);
214
+ this.#S(n);
213
215
  break;
214
216
  case r.pageSettingsUpdated:
215
- this.#S(n);
217
+ this.#C(n);
216
218
  break;
217
219
  default: break;
218
220
  }
219
221
  }
220
- #y(e) {
222
+ #b(e) {
221
223
  if (!e?.authToken || !e.extensionDetails || !e.designTokens) {
222
- this.#P(this.#p, /* @__PURE__ */ Error("Invalid HANDSHAKE_ACK payload.")), this.#p = null;
224
+ this.#F(this.#p, /* @__PURE__ */ Error("Invalid HANDSHAKE_ACK payload.")), this.#p = null, this.#m = null;
223
225
  return;
224
226
  }
225
227
  this.#a = e.authToken, this.#o = e.extensionDetails, this.#s = e.designTokens, this.#c = e.pageSettings ?? null, this.#r = !0;
226
228
  let t = this.#p;
227
- this.#p = null, this.#N(t, {
229
+ this.#p = null, this.#m = null, this.#P(t, {
228
230
  authToken: this.#a,
229
231
  extensionDetails: this.#o,
230
232
  designTokens: this.#s,
231
233
  pageSettings: this.#c
232
- }), this.#f = !1, this.#k();
234
+ }), this.#f = !1, this.#A();
233
235
  }
234
- #b(e) {
236
+ #x(e) {
235
237
  if (!e?.authToken) {
236
- this.#P(this.#m, /* @__PURE__ */ Error("Invalid TOKEN_REFRESH payload.")), this.#m = null;
238
+ this.#F(this.#h, /* @__PURE__ */ Error("Invalid TOKEN_REFRESH payload.")), this.#h = null;
237
239
  return;
238
240
  }
239
241
  this.#a = e.authToken;
240
- let t = this.#m;
241
- this.#m = null, this.#N(t, this.#a);
242
- for (let e of this.#_) e(this.#a);
243
- this.#f = !1, this.#k();
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();
244
246
  }
245
- #x(e) {
247
+ #S(e) {
246
248
  let t = e ?? {};
247
249
  this.#c = t;
248
- for (let e of this.#g) e(t);
250
+ for (let e of this.#_) e(t);
249
251
  }
250
- #S(e) {
252
+ #C(e) {
251
253
  if (!e || typeof e.success != "boolean") {
252
- this.#P(this.#h, /* @__PURE__ */ Error("Invalid PAGE_SETTINGS_UPDATED payload.")), this.#h = null;
254
+ this.#F(this.#g, /* @__PURE__ */ Error("Invalid PAGE_SETTINGS_UPDATED payload.")), this.#g = null;
253
255
  return;
254
256
  }
255
- let t = this.#h;
256
- this.#h = null, this.#N(t, e);
257
+ let t = this.#g;
258
+ this.#g = null, this.#P(t, e);
257
259
  }
258
- #C() {
260
+ #w() {
259
261
  if (this.#i) throw Error("SDK has been destroyed.");
260
262
  }
261
- #w() {
262
- if (this.#C(), !this.#r || !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.");
263
265
  }
264
- #T(e) {
266
+ #E(e) {
265
267
  if (e.authTokenBufferMs !== void 0 && (!Number.isFinite(e.authTokenBufferMs) || e.authTokenBufferMs < 0)) throw Error("authTokenBufferMs must be a finite non-negative number.");
266
- this.#l = e.authTokenAutoRefresh === !0, this.#u = e.authTokenBufferMs ?? d, this.#f = !1, this.#O();
268
+ this.#l = e.authTokenAutoRefresh === !0, this.#u = e.authTokenBufferMs ?? d, this.#f = !1, this.#k();
267
269
  }
268
- #E(e) {
270
+ #D(e) {
269
271
  let t = Number(e.expires);
270
272
  return Number.isFinite(t) ? t * 1e3 : null;
271
273
  }
272
- #D(e) {
273
- let t = this.#E(e);
274
+ #O(e) {
275
+ let t = this.#D(e);
274
276
  return t === null ? null : Math.max(0, t - this.#u - Date.now());
275
277
  }
276
- #O() {
278
+ #k() {
277
279
  this.#d !== null && (clearTimeout(this.#d), this.#d = null);
278
280
  }
279
- #k() {
280
- if (this.#O(), !this.#l || !this.#r || this.#i || !this.#a) return;
281
- let e = this.#D(this.#a);
281
+ #A() {
282
+ if (this.#k(), !this.#l || !this.#r || this.#i || !this.#a) return;
283
+ let e = this.#O(this.#a);
282
284
  if (e === null) return;
283
285
  let t = Math.min(e, p);
284
286
  this.#d = setTimeout(() => {
285
- this.#d = null, this.#A();
287
+ this.#d = null, this.#j();
286
288
  }, t);
287
289
  }
288
- async #A() {
290
+ async #j() {
289
291
  if (!this.#l || !this.#r || this.#i || !this.#a) return;
290
- let e = this.#a, t = this.#D(e);
292
+ let e = this.#a, t = this.#O(e);
291
293
  if (t !== null) {
292
294
  if (t > 0) {
293
- this.#k();
295
+ this.#A();
294
296
  return;
295
297
  }
296
- if (this.#m) {
297
- this.#j(e);
298
+ if (this.#h) {
299
+ this.#M(e);
298
300
  return;
299
301
  }
300
302
  try {
301
303
  await this.emitRequestAuthTokenRefresh();
302
304
  } catch {
303
- this.#j(e);
305
+ this.#M(e);
304
306
  }
305
307
  }
306
308
  }
307
- #j(e) {
309
+ #M(e) {
308
310
  if (!this.#l || !this.#r || this.#i || this.#a?.jwt !== e.jwt || this.#f) return;
309
- let t = this.#E(e);
311
+ let t = this.#D(e);
310
312
  if (t === null) return;
311
313
  let n = t - Date.now();
312
314
  if (n <= 0) return;
313
- this.#f = !0, this.#O();
315
+ this.#f = !0, this.#k();
314
316
  let r = Math.min(f, n);
315
317
  this.#d = setTimeout(() => {
316
- this.#d = null, this.#A();
318
+ this.#d = null, this.#j();
317
319
  }, r);
318
320
  }
319
- #M(e) {
321
+ #N(e) {
320
322
  e && clearTimeout(e.timer);
321
323
  }
322
- #N(e, t) {
324
+ #P(e, t) {
323
325
  e && (clearTimeout(e.timer), e.resolve(t));
324
326
  }
325
- #P(e, t) {
327
+ #F(e, t) {
326
328
  e && (clearTimeout(e.timer), e.reject(t));
327
329
  }
328
- }, 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);
329
335
  //#endregion
330
- 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 };
331
337
 
332
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","#tenantUrl","#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\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 * 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 * 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 #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 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 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.#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.#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 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. */
@@ -137,22 +143,27 @@ export declare interface IExtensionSDK {
137
143
  /**
138
144
  * Establishes the parent connection and waits for `HANDSHAKE_ACK`.
139
145
  *
140
- * If already connected with cached handshake data, returns that payload
141
- * without sending another init. Concurrent calls while a handshake is
142
- * 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.
143
152
  *
144
153
  * Opt-in proactive refresh can be enabled with
145
154
  * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).
146
155
  *
147
156
  * @param options - Handshake timeout and optional auth-token auto-refresh.
148
157
  * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
149
- * @throws When not embedded in a parent frame, already handshaking,
150
- * 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.
151
160
  */
152
161
  connect(options?: ConnectOptions): Promise<HandshakePayload>;
153
162
  /**
154
163
  * Tears down listeners, proactive refresh timers, and cached state.
155
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.
156
167
  */
157
168
  destroy(): void;
158
169
  /** Whether a successful handshake has completed and not been destroyed. */
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. */
@@ -137,22 +143,27 @@ export declare interface IExtensionSDK {
137
143
  /**
138
144
  * Establishes the parent connection and waits for `HANDSHAKE_ACK`.
139
145
  *
140
- * If already connected with cached handshake data, returns that payload
141
- * without sending another init. Concurrent calls while a handshake is
142
- * 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.
143
152
  *
144
153
  * Opt-in proactive refresh can be enabled with
145
154
  * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).
146
155
  *
147
156
  * @param options - Handshake timeout and optional auth-token auto-refresh.
148
157
  * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
149
- * @throws When not embedded in a parent frame, already handshaking,
150
- * 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.
151
160
  */
152
161
  connect(options?: ConnectOptions): Promise<HandshakePayload>;
153
162
  /**
154
163
  * Tears down listeners, proactive refresh timers, and cached state.
155
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.
156
167
  */
157
168
  destroy(): void;
158
169
  /** Whether a successful handshake has completed and not been destroyed. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadanza/extension-sdk",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Helpers for building extensions",
5
5
  "keywords": [
6
6
  "kadanza",