@kadanza/extension-sdk 0.0.6 → 0.0.8
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 +4 -0
- package/dist/extension-sdk.cjs +1 -1
- package/dist/extension-sdk.cjs.map +1 -1
- package/dist/extension-sdk.js +190 -94
- package/dist/extension-sdk.js.map +1 -1
- package/dist/index.d.cts +91 -27
- package/dist/index.d.ts +91 -27
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -45,3 +45,7 @@ npm install
|
|
|
45
45
|
| `npm run docs` | Generate HTML API docs under `docs/api/` |
|
|
46
46
|
| `npm run docs:check` | Validate TypeDoc without writing output |
|
|
47
47
|
| `npm run check:types` | Check published package types with arethetypeswrong |
|
|
48
|
+
|
|
49
|
+
`npm run dev` serves the playground at `https://localhost:5000` (accept the
|
|
50
|
+
self-signed certificate warning once). Use this HTTPS origin for the parent
|
|
51
|
+
iframe URL and add it to the API's CORS allowlist.
|
package/dist/extension-sdk.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){if(e.startsWith(`/`))return e;let t=new URL(e);return t.hostname=`api.${t.hostname}`,t.origin}function t(t,n){if(!n.startsWith(`/`)||n.startsWith(`//`))throw Error(`API endpoint must be a root-relative path starting with a single "/".`);return`${e(t).replace(/\/+$/,``)}${n}`}async function n(e,n,r={}){let i=new Headers(r.headers);i.has(`Content-Type`)||i.set(`Content-Type`,`application/json`),i.set(`Authorization`,`Bearer ${n.authTokenJwt}`),i.set(`X-Tenant`,n.tenantDomain);let a=await fetch(t(n.baseUrl,e),{...r,headers:i});if(!a.ok){let e=a.statusText?` ${a.statusText}`:``;throw Error(`API call failed: ${a.status}${e}.`)}return await a.json()}var r={handshakeInit:`HANDSHAKE_INIT`,handshakeAck:`HANDSHAKE_ACK`,requestAuthTokenRefresh:`REQUEST_TOKEN_REFRESH`,authTokenRefresh:`TOKEN_REFRESH`,loadPageSettings:`LOAD_PAGE_SETTINGS`,updatePageSettings:`UPDATE_PAGE_SETTINGS`,pageSettingsUpdated:`PAGE_SETTINGS_UPDATED`,navigationChange:`NAVIGATION_CHANGE`};function i(e,t,n){if(!window.parent||window.parent===window)throw Error(`Extension is not running inside a parent frame.`);let r=n===void 0?{type:e}:{type:e,payload:n};window.parent.postMessage(r,t)}function a(e,t){let n=n=>{n.origin===e&&(!n.data||typeof n.data!=`object`||!(`type`in n.data)||t(n))};return window.addEventListener(`message`,n),()=>window.removeEventListener(`message`,n)}var o=new Set([`localhost`,`127.0.0.1`,`[::1]`]),s=class extends Error{constructor(e){super(e),this.name=`InvalidOriginError`}};function c(e){if(!e)throw new s(`Missing "tenantUrl" search parameter required to establish parent origin.`);let t;try{t=new URL(e)}catch{throw new s(`Invalid tenantUrl: "${e}".`)}let n=o.has(t.hostname),r=t.protocol===`https:`,i=t.protocol===`http:`&&n;if(!r&&!i)throw new s(`tenantUrl must use https (or http on localhost). Received: "${t.protocol}//${t.host}".`);return t.origin}function l(e=window.location.search){return new URLSearchParams(e).get(`tenantUrl`)}var u=1e4,d=12e4,f=3e4,p=2147483647,m=class{#e=null;#t=null;#n=!1;#r=!1;#i=null;#a=null;#o=null;#s=null;#c=!1;#l=d;#u=null;#d=!1;#f=null;#p=null;#m=null;#h=new Set;#g=new Set;get isConnected(){return this.#n}async connect(e={}){if(this.#S(),this.#n&&this.#i&&this.#a&&this.#o)return{authToken:this.#i,extensionDetails:this.#a,designTokens:this.#o,pageSettings:this.#s};if(this.#f)throw Error(`Handshake already in progress.`);this.#w(e);let t=l();this.#e=c(t),this.#t?.(),this.#t=a(this.#e,e=>this.#_(e));let n=e.timeoutMs??u,o=new Promise((e,t)=>{let r=setTimeout(()=>{this.#f=null,t(Error(`Handshake timed out after ${n}ms.`))},n);this.#f={resolve:e,reject:t,timer:r}});try{i(r.handshakeInit,this.#e)}catch(e){throw this.#j(this.#f),this.#f=null,this.#t?.(),this.#t=null,this.#e=null,this.#D(),e}return o}destroy(){this.#r=!0,this.#n=!1,this.#D(),this.#c=!1,this.#d=!1,this.#t?.(),this.#t=null,this.#e=null,this.#N(this.#f,Error(`SDK destroyed.`)),this.#N(this.#p,Error(`SDK destroyed.`)),this.#N(this.#m,Error(`SDK destroyed.`)),this.#f=null,this.#p=null,this.#m=null,this.#h.clear(),this.#g.clear(),this.#i=null,this.#a=null,this.#o=null,this.#s=null}getAuthToken(){return this.#i}getExtensionDetails(){return this.#a}getDesignTokens(){return this.#o}getPageSettings(){return this.#s}async apiCall(e,t={}){return this.#C(),n(e,{baseUrl:this.#a.baseUrl,tenantDomain:this.#a.tenantDomain,authTokenJwt:this.#i.jwt},t)}async emitRequestAuthTokenRefresh(e={}){if(this.#C(),this.#p)throw Error(`Auth token refresh already in progress.`);let t=e.timeoutMs??u,n=this.#e,a=new Promise((e,n)=>{let r=setTimeout(()=>{this.#p=null,n(Error(`Auth token refresh timed out after ${t}ms.`))},t);this.#p={resolve:e,reject:n,timer:r}});return i(r.requestAuthTokenRefresh,n),a}async emitUpdatePageSettings(e,t={}){if(this.#C(),this.#m)throw Error(`Page settings update already in progress.`);let n=t.timeoutMs??u,a=this.#e,o=new Promise((e,t)=>{let r=setTimeout(()=>{this.#m=null,t(Error(`Page settings update timed out after ${n}ms.`))},n);this.#m={resolve:e,reject:t,timer:r}});return i(r.updatePageSettings,a,e),o}onLoadPageSettings(e){return this.#h.add(e),()=>{this.#h.delete(e)}}onAuthTokenRefresh(e){return this.#g.add(e),()=>{this.#g.delete(e)}}emitNavigationChange(e){this.#C(),i(r.navigationChange,this.#e,e)}#_(e){let{type:t,payload:n}=e.data;switch(t){case r.handshakeAck:this.#v(n);break;case r.authTokenRefresh:this.#y(n);break;case r.loadPageSettings:this.#b(n);break;case r.pageSettingsUpdated:this.#x(n);break;default:break}}#v(e){if(!e?.authToken||!e.extensionDetails||!e.designTokens){this.#N(this.#f,Error(`Invalid HANDSHAKE_ACK payload.`)),this.#f=null;return}this.#i=e.authToken,this.#a=e.extensionDetails,this.#o=e.designTokens,this.#s=e.pageSettings??null,this.#n=!0;let t=this.#f;this.#f=null,this.#M(t,{authToken:this.#i,extensionDetails:this.#a,designTokens:this.#o,pageSettings:this.#s}),this.#d=!1,this.#O()}#y(e){if(!e?.authToken){this.#N(this.#p,Error(`Invalid TOKEN_REFRESH payload.`)),this.#p=null;return}this.#i=e.authToken;let t=this.#p;this.#p=null,this.#M(t,this.#i);for(let e of this.#g)e(this.#i);this.#d=!1,this.#O()}#b(e){let t=e??{};this.#s=t;for(let e of this.#h)e(t)}#x(e){if(!e||typeof e.success!=`boolean`){this.#N(this.#m,Error(`Invalid PAGE_SETTINGS_UPDATED payload.`)),this.#m=null;return}let t=this.#m;this.#m=null,this.#M(t,e)}#S(){if(this.#r)throw Error(`SDK has been destroyed.`)}#C(){if(this.#S(),!this.#n||!this.#e)throw Error(`SDK is not connected. Call connect() first.`)}#w(e){if(e.authTokenBufferMs!==void 0&&(!Number.isFinite(e.authTokenBufferMs)||e.authTokenBufferMs<0))throw Error(`authTokenBufferMs must be a finite non-negative number.`);this.#c=e.authTokenAutoRefresh===!0,this.#l=e.authTokenBufferMs??d,this.#d=!1,this.#D()}#T(e){let t=Number(e.expires);return Number.isFinite(t)?t*1e3:null}#E(e){let t=this.#T(e);return t===null?null:Math.max(0,t-this.#l-Date.now())}#D(){this.#u!==null&&(clearTimeout(this.#u),this.#u=null)}#O(){if(this.#D(),!this.#c||!this.#n||this.#r||!this.#i)return;let e=this.#E(this.#i);if(e===null)return;let t=Math.min(e,p);this.#u=setTimeout(()=>{this.#u=null,this.#k()},t)}async#k(){if(!this.#c||!this.#n||this.#r||!this.#i)return;let e=this.#i,t=this.#E(e);if(t!==null){if(t>0){this.#O();return}if(this.#p){this.#A(e);return}try{await this.emitRequestAuthTokenRefresh()}catch{this.#A(e)}}}#A(e){if(!this.#c||!this.#n||this.#r||this.#i?.jwt!==e.jwt||this.#d)return;let t=this.#T(e);if(t===null)return;let n=t-Date.now();if(n<=0)return;this.#d=!0,this.#D();let r=Math.min(f,n);this.#u=setTimeout(()=>{this.#u=null,this.#k()},r)}#j(e){e&&clearTimeout(e.timer)}#M(e,t){e&&(clearTimeout(e.timer),e.resolve(t))}#N(e,t){e&&(clearTimeout(e.timer),e.reject(t))}},h=()=>new m;exports.CONNECTION_EVENTS=r,exports.ExtensionSDK=m,exports.InvalidOriginError=s,exports.createExtensionSDK=h,exports.readTenantUrlFromLocation=l,exports.resolveAllowedOrigin=c;
|
|
2
2
|
//# sourceMappingURL=extension-sdk.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-sdk.cjs","names":["#connected","#assertNotDestroyed","#authToken","#extensionDetails","#designTokens","#pageSettings","#pendingHandshake","#allowedOrigin","#unsubscribe","#onMessage","#clearPending","#destroyed","#rejectPending","#pendingTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#tokenRefreshHandlers","#assertConnected","#handleHandshakeAck","#handleTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending"],"sources":["../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/index.ts"],"sourcesContent":["/**\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 requestTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n /** Parent → child: new auth token (reply or push). */\n tokenRefresh: \"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} 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 { CONNECTION_EVENTS } from \"./events\";\nimport { postToParent, subscribeToParentMessages } from \"./messaging\";\nimport { readTenantUrlFromLocation, resolveAllowedOrigin } from \"./origin\";\nimport type {\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n ScopedExtensionToken,\n TokenRefreshPayload,\n} from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\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 * @param options - Optional handshake timeout (default 10s).\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 and cached state. Rejects any in-flight requests.\n * 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(): ScopedExtensionToken | 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 * Asks the parent for a new 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 requestTokenRefresh(options?: RequestOptions): Promise<ScopedExtensionToken>;\n\n /**\n * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.\n *\n * @param settings - Settings object to send.\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 updatePageSettings(\n settings: PageSettings,\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 token updates (requested or parent-pushed).\n *\n * @returns Unsubscribe function.\n */\n onTokenRefresh(handler: (token: ScopedExtensionToken) => void): () => void;\n}\n\ntype Pending<T> = {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */\nexport class ExtensionSDK implements IExtensionSDK {\n #allowedOrigin: string | null = null;\n #unsubscribe: (() => void) | null = null;\n #connected = false;\n #destroyed = false;\n\n #authToken: ScopedExtensionToken | null = null;\n #extensionDetails: ExtensionDetails | null = null;\n #designTokens: DesignTokens | null = null;\n #pageSettings: PageSettings | null = null;\n\n #pendingHandshake: Pending<HandshakePayload> | null = null;\n #pendingTokenRefresh: Pending<ScopedExtensionToken> | null = null;\n #pendingPageSettingsUpdate: Pending<PageSettingsUpdatedPayload> | null = null;\n\n #loadPageSettingsHandlers = new Set<(settings: PageSettings) => void>();\n #tokenRefreshHandlers = new Set<(token: ScopedExtensionToken) => 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 const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(tenantUrl);\n\n this.#unsubscribe?.();\n this.#unsubscribe = subscribeToParentMessages(\n this.#allowedOrigin,\n (event) => this.#onMessage(event),\n );\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const handshakePromise = new Promise<HandshakePayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingHandshake = null;\n reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n throw error;\n }\n\n return handshakePromise;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#connected = false;\n\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n\n this.#rejectPending(this.#pendingHandshake, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingTokenRefresh, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingPageSettingsUpdate, new Error(\"SDK destroyed.\"));\n this.#pendingHandshake = null;\n this.#pendingTokenRefresh = null;\n this.#pendingPageSettingsUpdate = null;\n\n this.#loadPageSettingsHandlers.clear();\n this.#tokenRefreshHandlers.clear();\n\n this.#authToken = null;\n this.#extensionDetails = null;\n this.#designTokens = null;\n this.#pageSettings = null;\n }\n\n getAuthToken(): ScopedExtensionToken | null {\n return this.#authToken;\n }\n\n getExtensionDetails(): ExtensionDetails | null {\n return this.#extensionDetails;\n }\n\n getDesignTokens(): DesignTokens | null {\n return this.#designTokens;\n }\n\n getPageSettings(): PageSettings | null {\n return this.#pageSettings;\n }\n\n async requestTokenRefresh(\n options: RequestOptions = {},\n ): Promise<ScopedExtensionToken> {\n this.#assertConnected();\n\n if (this.#pendingTokenRefresh) {\n throw new Error(\"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<ScopedExtensionToken>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingTokenRefresh = null;\n reject(new Error(`Token refresh timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingTokenRefresh = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.requestTokenRefresh, allowedOrigin);\n return promise;\n }\n\n async updatePageSettings(\n settings: PageSettings,\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, settings);\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 onTokenRefresh(handler: (token: ScopedExtensionToken) => void): () => void {\n this.#tokenRefreshHandlers.add(handler);\n return () => {\n this.#tokenRefreshHandlers.delete(handler);\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.tokenRefresh:\n this.#handleTokenRefresh(payload as TokenRefreshPayload | 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\n #handleTokenRefresh(payload: TokenRefreshPayload | undefined): void {\n if (!payload?.authToken) {\n this.#rejectPending(\n this.#pendingTokenRefresh,\n new Error(\"Invalid TOKEN_REFRESH payload.\"),\n );\n this.#pendingTokenRefresh = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n\n const pending = this.#pendingTokenRefresh;\n this.#pendingTokenRefresh = null;\n this.#resolvePending(pending, this.#authToken);\n\n for (const handler of this.#tokenRefreshHandlers) {\n handler(this.#authToken);\n }\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 #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 ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n ScopedExtensionToken,\n TokenRefreshPayload,\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","#pageSettings","#pendingHandshake","#configureAuthTokenAutoRefresh","#allowedOrigin","#unsubscribe","#onMessage","#clearPending","#clearProactiveAuthTokenRefreshTimer","#destroyed","#authTokenAutoRefresh","#authTokenRefreshRetryUsed","#rejectPending","#pendingAuthTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#authTokenRefreshHandlers","#assertConnected","#handleHandshakeAck","#handleAuthTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending","#scheduleProactiveAuthTokenRefresh","#authTokenBufferMs","#parseExpiresAtMs","#authTokenRefreshTimer","#getProactiveRefreshDelayMs","#runProactiveAuthTokenRefresh","#handleProactiveAuthTokenRefreshFailure"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/index.ts"],"sourcesContent":["interface ApiRequestContext {\n baseUrl: string;\n tenantDomain: string;\n authTokenJwt: string;\n}\n\nfunction deriveApiUrl(baseUrl: string): string {\n if (baseUrl.startsWith(\"/\")) {\n return baseUrl;\n }\n\n const url = new URL(baseUrl);\n url.hostname = `api.${url.hostname}`;\n return url.origin;\n}\n\nfunction resolveApiEndpoint(baseUrl: string, endpoint: string): string {\n if (!endpoint.startsWith(\"/\") || endpoint.startsWith(\"//\")) {\n throw new Error(\n 'API endpoint must be a root-relative path starting with a single \"/\".',\n );\n }\n\n return `${deriveApiUrl(baseUrl).replace(/\\/+$/, \"\")}${endpoint}`;\n}\n\nexport async function callApi<T>(\n endpoint: string,\n context: ApiRequestContext,\n options: RequestInit = {},\n): Promise<T> {\n const headers = new Headers(options.headers);\n\n if (!headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n headers.set(\"Authorization\", `Bearer ${context.authTokenJwt}`);\n headers.set(\"X-Tenant\", context.tenantDomain);\n\n const response = await fetch(resolveApiEndpoint(context.baseUrl, endpoint), {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n const statusText = response.statusText ? ` ${response.statusText}` : \"\";\n throw new Error(`API call failed: ${response.status}${statusText}.`);\n }\n\n return (await response.json()) as T;\n}\n","/**\n * Wire event type strings for parent/child `postMessage` communication.\n *\n * Keys are SDK-facing names; values are the strings sent on the wire.\n * Comments mark message direction relative to the extension (child).\n */\nexport const CONNECTION_EVENTS = {\n /** Child → parent: start handshake. */\n handshakeInit: \"HANDSHAKE_INIT\",\n /** Parent → child: handshake success with auth and context. */\n handshakeAck: \"HANDSHAKE_ACK\",\n /** Child → parent: request a new auth token. */\n requestAuthTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n /** Parent → child: new auth token (reply or push). */\n authTokenRefresh: \"TOKEN_REFRESH\",\n /** Parent → child: open/load page settings UI with values. */\n loadPageSettings: \"LOAD_PAGE_SETTINGS\",\n /** Child → parent: persist updated page settings. */\n updatePageSettings: \"UPDATE_PAGE_SETTINGS\",\n /** Parent → child: result of a page settings update. */\n pageSettingsUpdated: \"PAGE_SETTINGS_UPDATED\",\n /** Child → parent: extension route changed. */\n navigationChange: \"NAVIGATION_CHANGE\",\n} as const;\n\n/** Union of all {@link CONNECTION_EVENTS} wire string values. */\nexport type ConnectionEvent =\n (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS];\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type MessageHandler = (event: MessageEvent<ExtensionMessage>) => void;\n\nexport function postToParent(\n type: ConnectionEvent,\n allowedOrigin: string,\n payload?: unknown,\n): void {\n if (!window.parent || window.parent === window) {\n throw new Error(\"Extension is not running inside a parent frame.\");\n }\n\n const message: ExtensionMessage = payload === undefined ? { type } : { type, payload };\n window.parent.postMessage(message, allowedOrigin);\n}\n\nexport function subscribeToParentMessages(\n allowedOrigin: string,\n handler: MessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== allowedOrigin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","const LOCALHOST_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */\nexport class InvalidOriginError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidOriginError\";\n }\n}\n\n/**\n * Parses and validates a tenant URL into a postMessage-safe origin.\n * Allows https always; http only for localhost / 127.0.0.1 / [::1].\n *\n * @param tenantUrl - Absolute URL from the `tenantUrl` search param.\n * @returns Origin string suitable for `postMessage` targetOrigin.\n * @throws {InvalidOriginError} When missing, unparsable, or disallowed scheme/host.\n */\nexport function resolveAllowedOrigin(tenantUrl: string | null): string {\n if (!tenantUrl) {\n throw new InvalidOriginError(\n 'Missing \"tenantUrl\" search parameter required to establish parent origin.',\n );\n }\n\n let url: URL;\n try {\n url = new URL(tenantUrl);\n } catch {\n throw new InvalidOriginError(`Invalid tenantUrl: \"${tenantUrl}\".`);\n }\n\n const isLocalhost = LOCALHOST_HOSTS.has(url.hostname);\n const isHttps = url.protocol === \"https:\";\n const isLocalHttp = url.protocol === \"http:\" && isLocalhost;\n\n if (!isHttps && !isLocalHttp) {\n throw new InvalidOriginError(\n `tenantUrl must use https (or http on localhost). Received: \"${url.protocol}//${url.host}\".`,\n );\n }\n\n return url.origin;\n}\n\n/**\n * Reads the parent-supplied `tenantUrl` from a URL search string.\n *\n * The parent must pass `tenantUrl` on the iframe `src` so\n * {@link resolveAllowedOrigin} can whitelist the parent origin.\n *\n * @param search - Query string (defaults to `window.location.search`).\n */\nexport function readTenantUrlFromLocation(\n search: string = window.location.search,\n): string | null {\n return new URLSearchParams(search).get(\"tenantUrl\");\n}\n","import { callApi } from \"./api\";\nimport { CONNECTION_EVENTS } from \"./events\";\nimport { postToParent, subscribeToParentMessages } from \"./messaging\";\nimport { readTenantUrlFromLocation, resolveAllowedOrigin } from \"./origin\";\nimport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_AUTH_TOKEN_BUFFER_MS = 120_000;\nconst AUTO_REFRESH_RETRY_DELAY_MS = 30_000;\n/** Largest delay `setTimeout` can reliably use in browsers. */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\n/**\n * Public contract for talking to the Kadanza parent frame over `postMessage`.\n *\n * Requires a `tenantUrl` search param on the extension URL so\n * {@link connect} can validate the parent origin.\n */\nexport interface IExtensionSDK {\n /**\n * Establishes the parent connection and waits for `HANDSHAKE_ACK`.\n *\n * If already connected with cached handshake data, returns that payload\n * without sending another init. Concurrent calls while a handshake is\n * in progress throw.\n *\n * Opt-in proactive refresh can be enabled with\n * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).\n *\n * @param options - Handshake timeout and optional auth-token auto-refresh.\n * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.\n * @throws When not embedded in a parent frame, already handshaking,\n * destroyed, or the handshake times out / returns an invalid payload.\n */\n connect(options?: ConnectOptions): Promise<HandshakePayload>;\n\n /**\n * Tears down listeners, proactive refresh timers, and cached state.\n * Rejects any in-flight requests. The instance cannot be reused after destroy.\n */\n destroy(): void;\n\n /** Whether a successful handshake has completed and not been destroyed. */\n readonly isConnected: boolean;\n\n /** Last auth token from handshake or refresh; `null` until connected. */\n getAuthToken(): AuthToken | null;\n\n /** Extension context from handshake; `null` until connected. */\n getExtensionDetails(): ExtensionDetails | null;\n\n /** Design tokens from handshake; `null` until connected. */\n getDesignTokens(): DesignTokens | null;\n\n /**\n * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;\n * `null` until set.\n */\n getPageSettings(): PageSettings | null;\n\n /**\n * Calls the Kadanza Platform API with the current connection credentials.\n *\n * The API origin is derived from the handshake `baseUrl`. Authorization and\n * tenant headers are managed by the SDK and cannot be overridden.\n *\n * @typeParam T - Expected JSON response body.\n * @param endpoint - Root-relative API path.\n * @param options - Standard fetch options.\n * @throws When not connected, the endpoint is invalid, the request fails,\n * or the response is not successful JSON.\n */\n apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;\n\n /**\n * Asks the parent for a new auth token and waits for `TOKEN_REFRESH`.\n *\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, a refresh is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitRequestAuthTokenRefresh(\n options?: RequestOptions,\n ): Promise<AuthToken>;\n\n /**\n * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.\n *\n * @param payload - Update payload containing the settings object.\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, an update is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options?: RequestOptions,\n ): Promise<PageSettingsUpdatedPayload>;\n\n /**\n * Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.\n *\n * @returns Unsubscribe function.\n */\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;\n\n /**\n * Registers a handler for auth-token updates (requested or parent-pushed).\n *\n * @returns Unsubscribe function.\n */\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;\n\n /**\n * Notifies the parent that the extension's route changed.\n *\n * Wire: `NAVIGATION_CHANGE` (fire-and-forget).\n *\n * @param payload - Navigation change payload (`path` within the extension).\n * @throws When not connected or destroyed.\n */\n emitNavigationChange(payload: NavigationChangePayload): void;\n}\n\ntype Pending<T> = {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */\nexport class ExtensionSDK implements IExtensionSDK {\n #allowedOrigin: string | null = null;\n #unsubscribe: (() => void) | null = null;\n #connected = false;\n #destroyed = false;\n\n #authToken: AuthToken | null = null;\n #extensionDetails: ExtensionDetails | null = null;\n #designTokens: DesignTokens | null = null;\n #pageSettings: PageSettings | null = null;\n\n #authTokenAutoRefresh = false;\n #authTokenBufferMs = DEFAULT_AUTH_TOKEN_BUFFER_MS;\n #authTokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n #authTokenRefreshRetryUsed = false;\n\n #pendingHandshake: Pending<HandshakePayload> | null = null;\n #pendingAuthTokenRefresh: Pending<AuthToken> | null = null;\n #pendingPageSettingsUpdate: Pending<PageSettingsUpdatedPayload> | null = null;\n\n #loadPageSettingsHandlers = new Set<(settings: PageSettings) => void>();\n #authTokenRefreshHandlers = new Set<(authToken: AuthToken) => void>();\n\n get isConnected(): boolean {\n return this.#connected;\n }\n\n async connect(options: ConnectOptions = {}): Promise<HandshakePayload> {\n this.#assertNotDestroyed();\n\n if (this.#connected && this.#authToken && this.#extensionDetails && this.#designTokens) {\n return {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n };\n }\n\n if (this.#pendingHandshake) {\n throw new Error(\"Handshake already in progress.\");\n }\n\n this.#configureAuthTokenAutoRefresh(options);\n\n const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(tenantUrl);\n\n this.#unsubscribe?.();\n this.#unsubscribe = subscribeToParentMessages(\n this.#allowedOrigin,\n (event) => this.#onMessage(event),\n );\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const handshakePromise = new Promise<HandshakePayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingHandshake = null;\n reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#clearProactiveAuthTokenRefreshTimer();\n throw error;\n }\n\n return handshakePromise;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#connected = false;\n\n this.#clearProactiveAuthTokenRefreshTimer();\n this.#authTokenAutoRefresh = false;\n this.#authTokenRefreshRetryUsed = false;\n\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n\n this.#rejectPending(this.#pendingHandshake, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingAuthTokenRefresh, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingPageSettingsUpdate, new Error(\"SDK destroyed.\"));\n this.#pendingHandshake = null;\n this.#pendingAuthTokenRefresh = null;\n this.#pendingPageSettingsUpdate = null;\n\n this.#loadPageSettingsHandlers.clear();\n this.#authTokenRefreshHandlers.clear();\n\n this.#authToken = null;\n this.#extensionDetails = null;\n this.#designTokens = null;\n this.#pageSettings = null;\n }\n\n getAuthToken(): AuthToken | null {\n return this.#authToken;\n }\n\n getExtensionDetails(): ExtensionDetails | null {\n return this.#extensionDetails;\n }\n\n getDesignTokens(): DesignTokens | null {\n return this.#designTokens;\n }\n\n getPageSettings(): PageSettings | null {\n return this.#pageSettings;\n }\n\n async apiCall<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n this.#assertConnected();\n\n return callApi<T>(\n endpoint,\n {\n baseUrl: this.#extensionDetails!.baseUrl,\n tenantDomain: this.#extensionDetails!.tenantDomain,\n authTokenJwt: this.#authToken!.jwt,\n },\n options,\n );\n }\n\n async emitRequestAuthTokenRefresh(\n options: RequestOptions = {},\n ): Promise<AuthToken> {\n this.#assertConnected();\n\n if (this.#pendingAuthTokenRefresh) {\n throw new Error(\"Auth token refresh already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<AuthToken>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingAuthTokenRefresh = null;\n reject(new Error(`Auth token refresh timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingAuthTokenRefresh = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.requestAuthTokenRefresh, allowedOrigin);\n return promise;\n }\n\n async emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options: RequestOptions = {},\n ): Promise<PageSettingsUpdatedPayload> {\n this.#assertConnected();\n\n if (this.#pendingPageSettingsUpdate) {\n throw new Error(\"Page settings update already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<PageSettingsUpdatedPayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingPageSettingsUpdate = null;\n reject(new Error(`Page settings update timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingPageSettingsUpdate = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.updatePageSettings, allowedOrigin, payload);\n return promise;\n }\n\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void {\n this.#loadPageSettingsHandlers.add(handler);\n return () => {\n this.#loadPageSettingsHandlers.delete(handler);\n };\n }\n\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void {\n this.#authTokenRefreshHandlers.add(handler);\n return () => {\n this.#authTokenRefreshHandlers.delete(handler);\n };\n }\n\n emitNavigationChange(payload: NavigationChangePayload): void {\n this.#assertConnected();\n postToParent(\n CONNECTION_EVENTS.navigationChange,\n this.#allowedOrigin!,\n payload,\n );\n }\n\n #onMessage(event: MessageEvent<ExtensionMessage>): void {\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeAck:\n this.#handleHandshakeAck(payload as HandshakePayload | undefined);\n break;\n case CONNECTION_EVENTS.authTokenRefresh:\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n case CONNECTION_EVENTS.loadPageSettings:\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n case CONNECTION_EVENTS.pageSettingsUpdated:\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n default:\n break;\n }\n }\n\n #handleHandshakeAck(payload: HandshakePayload | undefined): void {\n if (!payload?.authToken || !payload.extensionDetails || !payload.designTokens) {\n this.#rejectPending(\n this.#pendingHandshake,\n new Error(\"Invalid HANDSHAKE_ACK payload.\"),\n );\n this.#pendingHandshake = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n this.#extensionDetails = payload.extensionDetails;\n this.#designTokens = payload.designTokens;\n this.#pageSettings = payload.pageSettings ?? null;\n this.#connected = true;\n\n const pending = this.#pendingHandshake;\n this.#pendingHandshake = null;\n this.#resolvePending(pending, {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n });\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleAuthTokenRefresh(payload: AuthTokenRefreshPayload | undefined): void {\n if (!payload?.authToken) {\n this.#rejectPending(\n this.#pendingAuthTokenRefresh,\n new Error(\"Invalid TOKEN_REFRESH payload.\"),\n );\n this.#pendingAuthTokenRefresh = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n\n const pending = this.#pendingAuthTokenRefresh;\n this.#pendingAuthTokenRefresh = null;\n this.#resolvePending(pending, this.#authToken);\n\n for (const handler of this.#authTokenRefreshHandlers) {\n handler(this.#authToken);\n }\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleLoadPageSettings(payload: PageSettings | undefined): void {\n const settings = payload ?? {};\n this.#pageSettings = settings;\n\n for (const handler of this.#loadPageSettingsHandlers) {\n handler(settings);\n }\n }\n\n #handlePageSettingsUpdated(\n payload: PageSettingsUpdatedPayload | undefined,\n ): void {\n if (!payload || typeof payload.success !== \"boolean\") {\n this.#rejectPending(\n this.#pendingPageSettingsUpdate,\n new Error(\"Invalid PAGE_SETTINGS_UPDATED payload.\"),\n );\n this.#pendingPageSettingsUpdate = null;\n return;\n }\n\n const pending = this.#pendingPageSettingsUpdate;\n this.#pendingPageSettingsUpdate = null;\n this.#resolvePending(pending, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"SDK has been destroyed.\");\n }\n }\n\n #assertConnected(): void {\n this.#assertNotDestroyed();\n if (!this.#connected || !this.#allowedOrigin) {\n throw new Error(\"SDK is not connected. Call connect() first.\");\n }\n }\n\n #configureAuthTokenAutoRefresh(options: ConnectOptions): void {\n if (options.authTokenBufferMs !== undefined) {\n if (\n !Number.isFinite(options.authTokenBufferMs) ||\n options.authTokenBufferMs < 0\n ) {\n throw new Error(\n \"authTokenBufferMs must be a finite non-negative number.\",\n );\n }\n }\n\n this.#authTokenAutoRefresh = options.authTokenAutoRefresh === true;\n this.#authTokenBufferMs =\n options.authTokenBufferMs ?? DEFAULT_AUTH_TOKEN_BUFFER_MS;\n this.#authTokenRefreshRetryUsed = false;\n this.#clearProactiveAuthTokenRefreshTimer();\n }\n\n #parseExpiresAtMs(authToken: AuthToken): number | null {\n const expiresSec = Number(authToken.expires);\n if (!Number.isFinite(expiresSec)) {\n return null;\n }\n return expiresSec * 1000;\n }\n\n #getProactiveRefreshDelayMs(authToken: AuthToken): number | null {\n const expiresAtMs = this.#parseExpiresAtMs(authToken);\n if (expiresAtMs === null) {\n return null;\n }\n return Math.max(0, expiresAtMs - this.#authTokenBufferMs - Date.now());\n }\n\n #clearProactiveAuthTokenRefreshTimer(): void {\n if (this.#authTokenRefreshTimer !== null) {\n clearTimeout(this.#authTokenRefreshTimer);\n this.#authTokenRefreshTimer = null;\n }\n }\n\n #scheduleProactiveAuthTokenRefresh(): void {\n this.#clearProactiveAuthTokenRefreshTimer();\n\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const delayMs = this.#getProactiveRefreshDelayMs(this.#authToken);\n if (delayMs === null) {\n return;\n }\n\n const clampedDelay = Math.min(delayMs, MAX_TIMEOUT_MS);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, clampedDelay);\n }\n\n async #runProactiveAuthTokenRefresh(): Promise<void> {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const authTokenAtRequest = this.#authToken;\n const delayMs = this.#getProactiveRefreshDelayMs(authTokenAtRequest);\n if (delayMs === null) {\n return;\n }\n\n if (delayMs > 0) {\n this.#scheduleProactiveAuthTokenRefresh();\n return;\n }\n\n if (this.#pendingAuthTokenRefresh) {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n return;\n }\n\n try {\n await this.emitRequestAuthTokenRefresh();\n } catch {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n }\n }\n\n #handleProactiveAuthTokenRefreshFailure(authTokenAtRequest: AuthToken): void {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n this.#authToken?.jwt !== authTokenAtRequest.jwt\n ) {\n return;\n }\n\n if (this.#authTokenRefreshRetryUsed) {\n return;\n }\n\n const expiresAtMs = this.#parseExpiresAtMs(authTokenAtRequest);\n if (expiresAtMs === null) {\n return;\n }\n\n const remainingMs = expiresAtMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n this.#authTokenRefreshRetryUsed = true;\n this.#clearProactiveAuthTokenRefreshTimer();\n\n const retryDelay = Math.min(AUTO_REFRESH_RETRY_DELAY_MS, remainingMs);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, retryDelay);\n }\n\n #clearPending(pending: { timer: ReturnType<typeof setTimeout> } | null): void {\n if (pending) {\n clearTimeout(pending.timer);\n }\n }\n\n #resolvePending<T>(pending: Pending<T> | null, value: T): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.resolve(value);\n }\n\n #rejectPending(\n pending: { timer: ReturnType<typeof setTimeout>; reject: (reason?: unknown) => void } | null,\n reason: unknown,\n ): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.reject(reason);\n }\n}\n","import { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\nexport {\n InvalidOriginError,\n readTenantUrlFromLocation,\n resolveAllowedOrigin,\n} from \"./origin\";\nexport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\n/** Creates a new {@link IExtensionSDK} instance for the current extension frame. */\nexport const createExtensionSDK = (): IExtensionSDK => {\n return new ExtensionSDK();\n};\n"],"mappings":"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"}
|
package/dist/extension-sdk.js
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
|
+
//#region src/api.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
if (e.startsWith("/")) return e;
|
|
4
|
+
let t = new URL(e);
|
|
5
|
+
return t.hostname = `api.${t.hostname}`, t.origin;
|
|
6
|
+
}
|
|
7
|
+
function t(t, n) {
|
|
8
|
+
if (!n.startsWith("/") || n.startsWith("//")) throw Error("API endpoint must be a root-relative path starting with a single \"/\".");
|
|
9
|
+
return `${e(t).replace(/\/+$/, "")}${n}`;
|
|
10
|
+
}
|
|
11
|
+
async function n(e, n, r = {}) {
|
|
12
|
+
let i = new Headers(r.headers);
|
|
13
|
+
i.has("Content-Type") || i.set("Content-Type", "application/json"), i.set("Authorization", `Bearer ${n.authTokenJwt}`), i.set("X-Tenant", n.tenantDomain);
|
|
14
|
+
let a = await fetch(t(n.baseUrl, e), {
|
|
15
|
+
...r,
|
|
16
|
+
headers: i
|
|
17
|
+
});
|
|
18
|
+
if (!a.ok) {
|
|
19
|
+
let e = a.statusText ? ` ${a.statusText}` : "";
|
|
20
|
+
throw Error(`API call failed: ${a.status}${e}.`);
|
|
21
|
+
}
|
|
22
|
+
return await a.json();
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
1
25
|
//#region src/events.ts
|
|
2
|
-
var
|
|
26
|
+
var r = {
|
|
3
27
|
handshakeInit: "HANDSHAKE_INIT",
|
|
4
28
|
handshakeAck: "HANDSHAKE_ACK",
|
|
5
|
-
|
|
6
|
-
|
|
29
|
+
requestAuthTokenRefresh: "REQUEST_TOKEN_REFRESH",
|
|
30
|
+
authTokenRefresh: "TOKEN_REFRESH",
|
|
7
31
|
loadPageSettings: "LOAD_PAGE_SETTINGS",
|
|
8
32
|
updatePageSettings: "UPDATE_PAGE_SETTINGS",
|
|
9
|
-
pageSettingsUpdated: "PAGE_SETTINGS_UPDATED"
|
|
33
|
+
pageSettingsUpdated: "PAGE_SETTINGS_UPDATED",
|
|
34
|
+
navigationChange: "NAVIGATION_CHANGE"
|
|
10
35
|
};
|
|
11
36
|
//#endregion
|
|
12
37
|
//#region src/messaging.ts
|
|
13
|
-
function
|
|
38
|
+
function i(e, t, n) {
|
|
14
39
|
if (!window.parent || window.parent === window) throw Error("Extension is not running inside a parent frame.");
|
|
15
40
|
let r = n === void 0 ? { type: e } : {
|
|
16
41
|
type: e,
|
|
@@ -18,7 +43,7 @@ function t(e, t, n) {
|
|
|
18
43
|
};
|
|
19
44
|
window.parent.postMessage(r, t);
|
|
20
45
|
}
|
|
21
|
-
function
|
|
46
|
+
function a(e, t) {
|
|
22
47
|
let n = (n) => {
|
|
23
48
|
n.origin === e && (!n.data || typeof n.data != "object" || !("type" in n.data) || t(n));
|
|
24
49
|
};
|
|
@@ -26,33 +51,33 @@ function n(e, t) {
|
|
|
26
51
|
}
|
|
27
52
|
//#endregion
|
|
28
53
|
//#region src/origin.ts
|
|
29
|
-
var
|
|
54
|
+
var o = /* @__PURE__ */ new Set([
|
|
30
55
|
"localhost",
|
|
31
56
|
"127.0.0.1",
|
|
32
57
|
"[::1]"
|
|
33
|
-
]),
|
|
58
|
+
]), s = class extends Error {
|
|
34
59
|
constructor(e) {
|
|
35
60
|
super(e), this.name = "InvalidOriginError";
|
|
36
61
|
}
|
|
37
62
|
};
|
|
38
|
-
function
|
|
39
|
-
if (!e) throw new
|
|
63
|
+
function c(e) {
|
|
64
|
+
if (!e) throw new s("Missing \"tenantUrl\" search parameter required to establish parent origin.");
|
|
40
65
|
let t;
|
|
41
66
|
try {
|
|
42
67
|
t = new URL(e);
|
|
43
68
|
} catch {
|
|
44
|
-
throw new
|
|
69
|
+
throw new s(`Invalid tenantUrl: "${e}".`);
|
|
45
70
|
}
|
|
46
|
-
let n =
|
|
47
|
-
if (!
|
|
71
|
+
let n = o.has(t.hostname), r = t.protocol === "https:", i = t.protocol === "http:" && n;
|
|
72
|
+
if (!r && !i) throw new s(`tenantUrl must use https (or http on localhost). Received: "${t.protocol}//${t.host}".`);
|
|
48
73
|
return t.origin;
|
|
49
74
|
}
|
|
50
|
-
function
|
|
75
|
+
function l(e = window.location.search) {
|
|
51
76
|
return new URLSearchParams(e).get("tenantUrl");
|
|
52
77
|
}
|
|
53
78
|
//#endregion
|
|
54
79
|
//#region src/ExtensionSDK.ts
|
|
55
|
-
var
|
|
80
|
+
var u = 1e4, d = 12e4, f = 3e4, p = 2147483647, m = class {
|
|
56
81
|
#e = null;
|
|
57
82
|
#t = null;
|
|
58
83
|
#n = !1;
|
|
@@ -61,43 +86,48 @@ var s = 1e4, c = class {
|
|
|
61
86
|
#a = null;
|
|
62
87
|
#o = null;
|
|
63
88
|
#s = null;
|
|
64
|
-
#c =
|
|
65
|
-
#l =
|
|
89
|
+
#c = !1;
|
|
90
|
+
#l = d;
|
|
66
91
|
#u = null;
|
|
67
|
-
#d =
|
|
68
|
-
#f =
|
|
92
|
+
#d = !1;
|
|
93
|
+
#f = null;
|
|
94
|
+
#p = null;
|
|
95
|
+
#m = null;
|
|
96
|
+
#h = /* @__PURE__ */ new Set();
|
|
97
|
+
#g = /* @__PURE__ */ new Set();
|
|
69
98
|
get isConnected() {
|
|
70
99
|
return this.#n;
|
|
71
100
|
}
|
|
72
|
-
async connect(
|
|
73
|
-
if (this.#
|
|
101
|
+
async connect(e = {}) {
|
|
102
|
+
if (this.#S(), this.#n && this.#i && this.#a && this.#o) return {
|
|
74
103
|
authToken: this.#i,
|
|
75
104
|
extensionDetails: this.#a,
|
|
76
105
|
designTokens: this.#o,
|
|
77
106
|
pageSettings: this.#s
|
|
78
107
|
};
|
|
79
|
-
if (this.#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
if (this.#f) throw Error("Handshake already in progress.");
|
|
109
|
+
this.#w(e);
|
|
110
|
+
let t = l();
|
|
111
|
+
this.#e = c(t), this.#t?.(), this.#t = a(this.#e, (e) => this.#_(e));
|
|
112
|
+
let n = e.timeoutMs ?? u, o = new Promise((e, t) => {
|
|
113
|
+
let r = setTimeout(() => {
|
|
114
|
+
this.#f = null, t(/* @__PURE__ */ Error(`Handshake timed out after ${n}ms.`));
|
|
115
|
+
}, n);
|
|
116
|
+
this.#f = {
|
|
87
117
|
resolve: e,
|
|
88
118
|
reject: t,
|
|
89
|
-
timer:
|
|
119
|
+
timer: r
|
|
90
120
|
};
|
|
91
121
|
});
|
|
92
122
|
try {
|
|
93
|
-
|
|
123
|
+
i(r.handshakeInit, this.#e);
|
|
94
124
|
} catch (e) {
|
|
95
|
-
throw this.#
|
|
125
|
+
throw this.#j(this.#f), this.#f = null, this.#t?.(), this.#t = null, this.#e = null, this.#D(), e;
|
|
96
126
|
}
|
|
97
|
-
return
|
|
127
|
+
return o;
|
|
98
128
|
}
|
|
99
129
|
destroy() {
|
|
100
|
-
this.#r = !0, this.#n = !1, this.#t?.(), this.#t = null, this.#e = null, this.#
|
|
130
|
+
this.#r = !0, this.#n = !1, this.#D(), this.#c = !1, this.#d = !1, this.#t?.(), this.#t = null, this.#e = null, this.#N(this.#f, /* @__PURE__ */ Error("SDK destroyed.")), this.#N(this.#p, /* @__PURE__ */ Error("SDK destroyed.")), this.#N(this.#m, /* @__PURE__ */ Error("SDK destroyed.")), this.#f = null, this.#p = null, this.#m = null, this.#h.clear(), this.#g.clear(), this.#i = null, this.#a = null, this.#o = null, this.#s = null;
|
|
101
131
|
}
|
|
102
132
|
getAuthToken() {
|
|
103
133
|
return this.#i;
|
|
@@ -111,116 +141,182 @@ var s = 1e4, c = class {
|
|
|
111
141
|
getPageSettings() {
|
|
112
142
|
return this.#s;
|
|
113
143
|
}
|
|
114
|
-
async
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
144
|
+
async apiCall(e, t = {}) {
|
|
145
|
+
return this.#C(), n(e, {
|
|
146
|
+
baseUrl: this.#a.baseUrl,
|
|
147
|
+
tenantDomain: this.#a.tenantDomain,
|
|
148
|
+
authTokenJwt: this.#i.jwt
|
|
149
|
+
}, t);
|
|
150
|
+
}
|
|
151
|
+
async emitRequestAuthTokenRefresh(e = {}) {
|
|
152
|
+
if (this.#C(), this.#p) throw Error("Auth token refresh already in progress.");
|
|
153
|
+
let t = e.timeoutMs ?? u, n = this.#e, a = new Promise((e, n) => {
|
|
154
|
+
let r = setTimeout(() => {
|
|
155
|
+
this.#p = null, n(/* @__PURE__ */ Error(`Auth token refresh timed out after ${t}ms.`));
|
|
156
|
+
}, t);
|
|
157
|
+
this.#p = {
|
|
121
158
|
resolve: e,
|
|
122
|
-
reject:
|
|
123
|
-
timer:
|
|
159
|
+
reject: n,
|
|
160
|
+
timer: r
|
|
124
161
|
};
|
|
125
162
|
});
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
async
|
|
129
|
-
if (this.#
|
|
130
|
-
let
|
|
131
|
-
let
|
|
132
|
-
this.#
|
|
133
|
-
},
|
|
134
|
-
this.#
|
|
163
|
+
return i(r.requestAuthTokenRefresh, n), a;
|
|
164
|
+
}
|
|
165
|
+
async emitUpdatePageSettings(e, t = {}) {
|
|
166
|
+
if (this.#C(), this.#m) throw Error("Page settings update already in progress.");
|
|
167
|
+
let n = t.timeoutMs ?? u, a = this.#e, o = new Promise((e, t) => {
|
|
168
|
+
let r = setTimeout(() => {
|
|
169
|
+
this.#m = null, t(/* @__PURE__ */ Error(`Page settings update timed out after ${n}ms.`));
|
|
170
|
+
}, n);
|
|
171
|
+
this.#m = {
|
|
135
172
|
resolve: e,
|
|
136
173
|
reject: t,
|
|
137
|
-
timer:
|
|
174
|
+
timer: r
|
|
138
175
|
};
|
|
139
176
|
});
|
|
140
|
-
return
|
|
177
|
+
return i(r.updatePageSettings, a, e), o;
|
|
141
178
|
}
|
|
142
179
|
onLoadPageSettings(e) {
|
|
143
|
-
return this.#
|
|
144
|
-
this.#
|
|
180
|
+
return this.#h.add(e), () => {
|
|
181
|
+
this.#h.delete(e);
|
|
145
182
|
};
|
|
146
183
|
}
|
|
147
|
-
|
|
148
|
-
return this.#
|
|
149
|
-
this.#
|
|
184
|
+
onAuthTokenRefresh(e) {
|
|
185
|
+
return this.#g.add(e), () => {
|
|
186
|
+
this.#g.delete(e);
|
|
150
187
|
};
|
|
151
188
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
emitNavigationChange(e) {
|
|
190
|
+
this.#C(), i(r.navigationChange, this.#e, e);
|
|
191
|
+
}
|
|
192
|
+
#_(e) {
|
|
193
|
+
let { type: t, payload: n } = e.data;
|
|
194
|
+
switch (t) {
|
|
195
|
+
case r.handshakeAck:
|
|
196
|
+
this.#v(n);
|
|
157
197
|
break;
|
|
158
|
-
case
|
|
159
|
-
this.#
|
|
198
|
+
case r.authTokenRefresh:
|
|
199
|
+
this.#y(n);
|
|
160
200
|
break;
|
|
161
|
-
case
|
|
162
|
-
this.#
|
|
201
|
+
case r.loadPageSettings:
|
|
202
|
+
this.#b(n);
|
|
163
203
|
break;
|
|
164
|
-
case
|
|
165
|
-
this.#
|
|
204
|
+
case r.pageSettingsUpdated:
|
|
205
|
+
this.#x(n);
|
|
166
206
|
break;
|
|
167
207
|
default: break;
|
|
168
208
|
}
|
|
169
209
|
}
|
|
170
|
-
#
|
|
210
|
+
#v(e) {
|
|
171
211
|
if (!e?.authToken || !e.extensionDetails || !e.designTokens) {
|
|
172
|
-
this.#
|
|
212
|
+
this.#N(this.#f, /* @__PURE__ */ Error("Invalid HANDSHAKE_ACK payload.")), this.#f = null;
|
|
173
213
|
return;
|
|
174
214
|
}
|
|
175
215
|
this.#i = e.authToken, this.#a = e.extensionDetails, this.#o = e.designTokens, this.#s = e.pageSettings ?? null, this.#n = !0;
|
|
176
|
-
let t = this.#
|
|
177
|
-
this.#
|
|
216
|
+
let t = this.#f;
|
|
217
|
+
this.#f = null, this.#M(t, {
|
|
178
218
|
authToken: this.#i,
|
|
179
219
|
extensionDetails: this.#a,
|
|
180
220
|
designTokens: this.#o,
|
|
181
221
|
pageSettings: this.#s
|
|
182
|
-
});
|
|
222
|
+
}), this.#d = !1, this.#O();
|
|
183
223
|
}
|
|
184
|
-
#
|
|
224
|
+
#y(e) {
|
|
185
225
|
if (!e?.authToken) {
|
|
186
|
-
this.#
|
|
226
|
+
this.#N(this.#p, /* @__PURE__ */ Error("Invalid TOKEN_REFRESH payload.")), this.#p = null;
|
|
187
227
|
return;
|
|
188
228
|
}
|
|
189
229
|
this.#i = e.authToken;
|
|
190
|
-
let t = this.#
|
|
191
|
-
this.#
|
|
192
|
-
for (let e of this.#
|
|
230
|
+
let t = this.#p;
|
|
231
|
+
this.#p = null, this.#M(t, this.#i);
|
|
232
|
+
for (let e of this.#g) e(this.#i);
|
|
233
|
+
this.#d = !1, this.#O();
|
|
193
234
|
}
|
|
194
|
-
#
|
|
235
|
+
#b(e) {
|
|
195
236
|
let t = e ?? {};
|
|
196
237
|
this.#s = t;
|
|
197
|
-
for (let e of this.#
|
|
238
|
+
for (let e of this.#h) e(t);
|
|
198
239
|
}
|
|
199
|
-
#
|
|
240
|
+
#x(e) {
|
|
200
241
|
if (!e || typeof e.success != "boolean") {
|
|
201
|
-
this.#
|
|
242
|
+
this.#N(this.#m, /* @__PURE__ */ Error("Invalid PAGE_SETTINGS_UPDATED payload.")), this.#m = null;
|
|
202
243
|
return;
|
|
203
244
|
}
|
|
204
|
-
let t = this.#
|
|
205
|
-
this.#
|
|
245
|
+
let t = this.#m;
|
|
246
|
+
this.#m = null, this.#M(t, e);
|
|
206
247
|
}
|
|
207
|
-
#
|
|
248
|
+
#S() {
|
|
208
249
|
if (this.#r) throw Error("SDK has been destroyed.");
|
|
209
250
|
}
|
|
210
|
-
#
|
|
211
|
-
if (this.#
|
|
251
|
+
#C() {
|
|
252
|
+
if (this.#S(), !this.#n || !this.#e) throw Error("SDK is not connected. Call connect() first.");
|
|
212
253
|
}
|
|
213
|
-
#
|
|
254
|
+
#w(e) {
|
|
255
|
+
if (e.authTokenBufferMs !== void 0 && (!Number.isFinite(e.authTokenBufferMs) || e.authTokenBufferMs < 0)) throw Error("authTokenBufferMs must be a finite non-negative number.");
|
|
256
|
+
this.#c = e.authTokenAutoRefresh === !0, this.#l = e.authTokenBufferMs ?? d, this.#d = !1, this.#D();
|
|
257
|
+
}
|
|
258
|
+
#T(e) {
|
|
259
|
+
let t = Number(e.expires);
|
|
260
|
+
return Number.isFinite(t) ? t * 1e3 : null;
|
|
261
|
+
}
|
|
262
|
+
#E(e) {
|
|
263
|
+
let t = this.#T(e);
|
|
264
|
+
return t === null ? null : Math.max(0, t - this.#l - Date.now());
|
|
265
|
+
}
|
|
266
|
+
#D() {
|
|
267
|
+
this.#u !== null && (clearTimeout(this.#u), this.#u = null);
|
|
268
|
+
}
|
|
269
|
+
#O() {
|
|
270
|
+
if (this.#D(), !this.#c || !this.#n || this.#r || !this.#i) return;
|
|
271
|
+
let e = this.#E(this.#i);
|
|
272
|
+
if (e === null) return;
|
|
273
|
+
let t = Math.min(e, p);
|
|
274
|
+
this.#u = setTimeout(() => {
|
|
275
|
+
this.#u = null, this.#k();
|
|
276
|
+
}, t);
|
|
277
|
+
}
|
|
278
|
+
async #k() {
|
|
279
|
+
if (!this.#c || !this.#n || this.#r || !this.#i) return;
|
|
280
|
+
let e = this.#i, t = this.#E(e);
|
|
281
|
+
if (t !== null) {
|
|
282
|
+
if (t > 0) {
|
|
283
|
+
this.#O();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (this.#p) {
|
|
287
|
+
this.#A(e);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
await this.emitRequestAuthTokenRefresh();
|
|
292
|
+
} catch {
|
|
293
|
+
this.#A(e);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
#A(e) {
|
|
298
|
+
if (!this.#c || !this.#n || this.#r || this.#i?.jwt !== e.jwt || this.#d) return;
|
|
299
|
+
let t = this.#T(e);
|
|
300
|
+
if (t === null) return;
|
|
301
|
+
let n = t - Date.now();
|
|
302
|
+
if (n <= 0) return;
|
|
303
|
+
this.#d = !0, this.#D();
|
|
304
|
+
let r = Math.min(f, n);
|
|
305
|
+
this.#u = setTimeout(() => {
|
|
306
|
+
this.#u = null, this.#k();
|
|
307
|
+
}, r);
|
|
308
|
+
}
|
|
309
|
+
#j(e) {
|
|
214
310
|
e && clearTimeout(e.timer);
|
|
215
311
|
}
|
|
216
|
-
#
|
|
312
|
+
#M(e, t) {
|
|
217
313
|
e && (clearTimeout(e.timer), e.resolve(t));
|
|
218
314
|
}
|
|
219
|
-
#
|
|
315
|
+
#N(e, t) {
|
|
220
316
|
e && (clearTimeout(e.timer), e.reject(t));
|
|
221
317
|
}
|
|
222
|
-
},
|
|
318
|
+
}, h = () => new m();
|
|
223
319
|
//#endregion
|
|
224
|
-
export {
|
|
320
|
+
export { r as CONNECTION_EVENTS, m as ExtensionSDK, s as InvalidOriginError, h as createExtensionSDK, l as readTenantUrlFromLocation, c as resolveAllowedOrigin };
|
|
225
321
|
|
|
226
322
|
//# sourceMappingURL=extension-sdk.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-sdk.js","names":["#connected","#assertNotDestroyed","#authToken","#extensionDetails","#designTokens","#pageSettings","#pendingHandshake","#allowedOrigin","#unsubscribe","#onMessage","#clearPending","#destroyed","#rejectPending","#pendingTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#tokenRefreshHandlers","#assertConnected","#handleHandshakeAck","#handleTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending"],"sources":["../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/index.ts"],"sourcesContent":["/**\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 requestTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n /** Parent → child: new auth token (reply or push). */\n tokenRefresh: \"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} 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 { CONNECTION_EVENTS } from \"./events\";\nimport { postToParent, subscribeToParentMessages } from \"./messaging\";\nimport { readTenantUrlFromLocation, resolveAllowedOrigin } from \"./origin\";\nimport type {\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n ScopedExtensionToken,\n TokenRefreshPayload,\n} from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\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 * @param options - Optional handshake timeout (default 10s).\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 and cached state. Rejects any in-flight requests.\n * 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(): ScopedExtensionToken | 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 * Asks the parent for a new 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 requestTokenRefresh(options?: RequestOptions): Promise<ScopedExtensionToken>;\n\n /**\n * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.\n *\n * @param settings - Settings object to send.\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 updatePageSettings(\n settings: PageSettings,\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 token updates (requested or parent-pushed).\n *\n * @returns Unsubscribe function.\n */\n onTokenRefresh(handler: (token: ScopedExtensionToken) => void): () => void;\n}\n\ntype Pending<T> = {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */\nexport class ExtensionSDK implements IExtensionSDK {\n #allowedOrigin: string | null = null;\n #unsubscribe: (() => void) | null = null;\n #connected = false;\n #destroyed = false;\n\n #authToken: ScopedExtensionToken | null = null;\n #extensionDetails: ExtensionDetails | null = null;\n #designTokens: DesignTokens | null = null;\n #pageSettings: PageSettings | null = null;\n\n #pendingHandshake: Pending<HandshakePayload> | null = null;\n #pendingTokenRefresh: Pending<ScopedExtensionToken> | null = null;\n #pendingPageSettingsUpdate: Pending<PageSettingsUpdatedPayload> | null = null;\n\n #loadPageSettingsHandlers = new Set<(settings: PageSettings) => void>();\n #tokenRefreshHandlers = new Set<(token: ScopedExtensionToken) => 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 const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(tenantUrl);\n\n this.#unsubscribe?.();\n this.#unsubscribe = subscribeToParentMessages(\n this.#allowedOrigin,\n (event) => this.#onMessage(event),\n );\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const handshakePromise = new Promise<HandshakePayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingHandshake = null;\n reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n throw error;\n }\n\n return handshakePromise;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#connected = false;\n\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n\n this.#rejectPending(this.#pendingHandshake, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingTokenRefresh, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingPageSettingsUpdate, new Error(\"SDK destroyed.\"));\n this.#pendingHandshake = null;\n this.#pendingTokenRefresh = null;\n this.#pendingPageSettingsUpdate = null;\n\n this.#loadPageSettingsHandlers.clear();\n this.#tokenRefreshHandlers.clear();\n\n this.#authToken = null;\n this.#extensionDetails = null;\n this.#designTokens = null;\n this.#pageSettings = null;\n }\n\n getAuthToken(): ScopedExtensionToken | null {\n return this.#authToken;\n }\n\n getExtensionDetails(): ExtensionDetails | null {\n return this.#extensionDetails;\n }\n\n getDesignTokens(): DesignTokens | null {\n return this.#designTokens;\n }\n\n getPageSettings(): PageSettings | null {\n return this.#pageSettings;\n }\n\n async requestTokenRefresh(\n options: RequestOptions = {},\n ): Promise<ScopedExtensionToken> {\n this.#assertConnected();\n\n if (this.#pendingTokenRefresh) {\n throw new Error(\"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<ScopedExtensionToken>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingTokenRefresh = null;\n reject(new Error(`Token refresh timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingTokenRefresh = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.requestTokenRefresh, allowedOrigin);\n return promise;\n }\n\n async updatePageSettings(\n settings: PageSettings,\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, settings);\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 onTokenRefresh(handler: (token: ScopedExtensionToken) => void): () => void {\n this.#tokenRefreshHandlers.add(handler);\n return () => {\n this.#tokenRefreshHandlers.delete(handler);\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.tokenRefresh:\n this.#handleTokenRefresh(payload as TokenRefreshPayload | 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\n #handleTokenRefresh(payload: TokenRefreshPayload | undefined): void {\n if (!payload?.authToken) {\n this.#rejectPending(\n this.#pendingTokenRefresh,\n new Error(\"Invalid TOKEN_REFRESH payload.\"),\n );\n this.#pendingTokenRefresh = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n\n const pending = this.#pendingTokenRefresh;\n this.#pendingTokenRefresh = null;\n this.#resolvePending(pending, this.#authToken);\n\n for (const handler of this.#tokenRefreshHandlers) {\n handler(this.#authToken);\n }\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 #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 ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n ScopedExtensionToken,\n TokenRefreshPayload,\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","#pageSettings","#pendingHandshake","#configureAuthTokenAutoRefresh","#allowedOrigin","#unsubscribe","#onMessage","#clearPending","#clearProactiveAuthTokenRefreshTimer","#destroyed","#authTokenAutoRefresh","#authTokenRefreshRetryUsed","#rejectPending","#pendingAuthTokenRefresh","#pendingPageSettingsUpdate","#loadPageSettingsHandlers","#authTokenRefreshHandlers","#assertConnected","#handleHandshakeAck","#handleAuthTokenRefresh","#handleLoadPageSettings","#handlePageSettingsUpdated","#resolvePending","#scheduleProactiveAuthTokenRefresh","#authTokenBufferMs","#parseExpiresAtMs","#authTokenRefreshTimer","#getProactiveRefreshDelayMs","#runProactiveAuthTokenRefresh","#handleProactiveAuthTokenRefreshFailure"],"sources":["../src/api.ts","../src/events.ts","../src/messaging.ts","../src/origin.ts","../src/ExtensionSDK.ts","../src/index.ts"],"sourcesContent":["interface ApiRequestContext {\n baseUrl: string;\n tenantDomain: string;\n authTokenJwt: string;\n}\n\nfunction deriveApiUrl(baseUrl: string): string {\n if (baseUrl.startsWith(\"/\")) {\n return baseUrl;\n }\n\n const url = new URL(baseUrl);\n url.hostname = `api.${url.hostname}`;\n return url.origin;\n}\n\nfunction resolveApiEndpoint(baseUrl: string, endpoint: string): string {\n if (!endpoint.startsWith(\"/\") || endpoint.startsWith(\"//\")) {\n throw new Error(\n 'API endpoint must be a root-relative path starting with a single \"/\".',\n );\n }\n\n return `${deriveApiUrl(baseUrl).replace(/\\/+$/, \"\")}${endpoint}`;\n}\n\nexport async function callApi<T>(\n endpoint: string,\n context: ApiRequestContext,\n options: RequestInit = {},\n): Promise<T> {\n const headers = new Headers(options.headers);\n\n if (!headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n headers.set(\"Authorization\", `Bearer ${context.authTokenJwt}`);\n headers.set(\"X-Tenant\", context.tenantDomain);\n\n const response = await fetch(resolveApiEndpoint(context.baseUrl, endpoint), {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n const statusText = response.statusText ? ` ${response.statusText}` : \"\";\n throw new Error(`API call failed: ${response.status}${statusText}.`);\n }\n\n return (await response.json()) as T;\n}\n","/**\n * Wire event type strings for parent/child `postMessage` communication.\n *\n * Keys are SDK-facing names; values are the strings sent on the wire.\n * Comments mark message direction relative to the extension (child).\n */\nexport const CONNECTION_EVENTS = {\n /** Child → parent: start handshake. */\n handshakeInit: \"HANDSHAKE_INIT\",\n /** Parent → child: handshake success with auth and context. */\n handshakeAck: \"HANDSHAKE_ACK\",\n /** Child → parent: request a new auth token. */\n requestAuthTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n /** Parent → child: new auth token (reply or push). */\n authTokenRefresh: \"TOKEN_REFRESH\",\n /** Parent → child: open/load page settings UI with values. */\n loadPageSettings: \"LOAD_PAGE_SETTINGS\",\n /** Child → parent: persist updated page settings. */\n updatePageSettings: \"UPDATE_PAGE_SETTINGS\",\n /** Parent → child: result of a page settings update. */\n pageSettingsUpdated: \"PAGE_SETTINGS_UPDATED\",\n /** Child → parent: extension route changed. */\n navigationChange: \"NAVIGATION_CHANGE\",\n} as const;\n\n/** Union of all {@link CONNECTION_EVENTS} wire string values. */\nexport type ConnectionEvent =\n (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS];\n","import type { ConnectionEvent } from \"./events\";\nimport type { ExtensionMessage } from \"./types\";\n\nexport type MessageHandler = (event: MessageEvent<ExtensionMessage>) => void;\n\nexport function postToParent(\n type: ConnectionEvent,\n allowedOrigin: string,\n payload?: unknown,\n): void {\n if (!window.parent || window.parent === window) {\n throw new Error(\"Extension is not running inside a parent frame.\");\n }\n\n const message: ExtensionMessage = payload === undefined ? { type } : { type, payload };\n window.parent.postMessage(message, allowedOrigin);\n}\n\nexport function subscribeToParentMessages(\n allowedOrigin: string,\n handler: MessageHandler,\n): () => void {\n const listener = (event: MessageEvent) => {\n if (event.origin !== allowedOrigin) {\n return;\n }\n\n if (!event.data || typeof event.data !== \"object\" || !(\"type\" in event.data)) {\n return;\n }\n\n handler(event as MessageEvent<ExtensionMessage>);\n };\n\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n","const LOCALHOST_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */\nexport class InvalidOriginError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidOriginError\";\n }\n}\n\n/**\n * Parses and validates a tenant URL into a postMessage-safe origin.\n * Allows https always; http only for localhost / 127.0.0.1 / [::1].\n *\n * @param tenantUrl - Absolute URL from the `tenantUrl` search param.\n * @returns Origin string suitable for `postMessage` targetOrigin.\n * @throws {InvalidOriginError} When missing, unparsable, or disallowed scheme/host.\n */\nexport function resolveAllowedOrigin(tenantUrl: string | null): string {\n if (!tenantUrl) {\n throw new InvalidOriginError(\n 'Missing \"tenantUrl\" search parameter required to establish parent origin.',\n );\n }\n\n let url: URL;\n try {\n url = new URL(tenantUrl);\n } catch {\n throw new InvalidOriginError(`Invalid tenantUrl: \"${tenantUrl}\".`);\n }\n\n const isLocalhost = LOCALHOST_HOSTS.has(url.hostname);\n const isHttps = url.protocol === \"https:\";\n const isLocalHttp = url.protocol === \"http:\" && isLocalhost;\n\n if (!isHttps && !isLocalHttp) {\n throw new InvalidOriginError(\n `tenantUrl must use https (or http on localhost). Received: \"${url.protocol}//${url.host}\".`,\n );\n }\n\n return url.origin;\n}\n\n/**\n * Reads the parent-supplied `tenantUrl` from a URL search string.\n *\n * The parent must pass `tenantUrl` on the iframe `src` so\n * {@link resolveAllowedOrigin} can whitelist the parent origin.\n *\n * @param search - Query string (defaults to `window.location.search`).\n */\nexport function readTenantUrlFromLocation(\n search: string = window.location.search,\n): string | null {\n return new URLSearchParams(search).get(\"tenantUrl\");\n}\n","import { callApi } from \"./api\";\nimport { CONNECTION_EVENTS } from \"./events\";\nimport { postToParent, subscribeToParentMessages } from \"./messaging\";\nimport { readTenantUrlFromLocation, resolveAllowedOrigin } from \"./origin\";\nimport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_AUTH_TOKEN_BUFFER_MS = 120_000;\nconst AUTO_REFRESH_RETRY_DELAY_MS = 30_000;\n/** Largest delay `setTimeout` can reliably use in browsers. */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\n/**\n * Public contract for talking to the Kadanza parent frame over `postMessage`.\n *\n * Requires a `tenantUrl` search param on the extension URL so\n * {@link connect} can validate the parent origin.\n */\nexport interface IExtensionSDK {\n /**\n * Establishes the parent connection and waits for `HANDSHAKE_ACK`.\n *\n * If already connected with cached handshake data, returns that payload\n * without sending another init. Concurrent calls while a handshake is\n * in progress throw.\n *\n * Opt-in proactive refresh can be enabled with\n * `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).\n *\n * @param options - Handshake timeout and optional auth-token auto-refresh.\n * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.\n * @throws When not embedded in a parent frame, already handshaking,\n * destroyed, or the handshake times out / returns an invalid payload.\n */\n connect(options?: ConnectOptions): Promise<HandshakePayload>;\n\n /**\n * Tears down listeners, proactive refresh timers, and cached state.\n * Rejects any in-flight requests. The instance cannot be reused after destroy.\n */\n destroy(): void;\n\n /** Whether a successful handshake has completed and not been destroyed. */\n readonly isConnected: boolean;\n\n /** Last auth token from handshake or refresh; `null` until connected. */\n getAuthToken(): AuthToken | null;\n\n /** Extension context from handshake; `null` until connected. */\n getExtensionDetails(): ExtensionDetails | null;\n\n /** Design tokens from handshake; `null` until connected. */\n getDesignTokens(): DesignTokens | null;\n\n /**\n * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;\n * `null` until set.\n */\n getPageSettings(): PageSettings | null;\n\n /**\n * Calls the Kadanza Platform API with the current connection credentials.\n *\n * The API origin is derived from the handshake `baseUrl`. Authorization and\n * tenant headers are managed by the SDK and cannot be overridden.\n *\n * @typeParam T - Expected JSON response body.\n * @param endpoint - Root-relative API path.\n * @param options - Standard fetch options.\n * @throws When not connected, the endpoint is invalid, the request fails,\n * or the response is not successful JSON.\n */\n apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;\n\n /**\n * Asks the parent for a new auth token and waits for `TOKEN_REFRESH`.\n *\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, a refresh is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitRequestAuthTokenRefresh(\n options?: RequestOptions,\n ): Promise<AuthToken>;\n\n /**\n * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.\n *\n * @param payload - Update payload containing the settings object.\n * @param options - Optional request timeout (default 10s).\n * @throws When not connected, an update is already in progress,\n * or the request times out / returns an invalid payload.\n */\n emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options?: RequestOptions,\n ): Promise<PageSettingsUpdatedPayload>;\n\n /**\n * Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.\n *\n * @returns Unsubscribe function.\n */\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;\n\n /**\n * Registers a handler for auth-token updates (requested or parent-pushed).\n *\n * @returns Unsubscribe function.\n */\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;\n\n /**\n * Notifies the parent that the extension's route changed.\n *\n * Wire: `NAVIGATION_CHANGE` (fire-and-forget).\n *\n * @param payload - Navigation change payload (`path` within the extension).\n * @throws When not connected or destroyed.\n */\n emitNavigationChange(payload: NavigationChangePayload): void;\n}\n\ntype Pending<T> = {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */\nexport class ExtensionSDK implements IExtensionSDK {\n #allowedOrigin: string | null = null;\n #unsubscribe: (() => void) | null = null;\n #connected = false;\n #destroyed = false;\n\n #authToken: AuthToken | null = null;\n #extensionDetails: ExtensionDetails | null = null;\n #designTokens: DesignTokens | null = null;\n #pageSettings: PageSettings | null = null;\n\n #authTokenAutoRefresh = false;\n #authTokenBufferMs = DEFAULT_AUTH_TOKEN_BUFFER_MS;\n #authTokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n #authTokenRefreshRetryUsed = false;\n\n #pendingHandshake: Pending<HandshakePayload> | null = null;\n #pendingAuthTokenRefresh: Pending<AuthToken> | null = null;\n #pendingPageSettingsUpdate: Pending<PageSettingsUpdatedPayload> | null = null;\n\n #loadPageSettingsHandlers = new Set<(settings: PageSettings) => void>();\n #authTokenRefreshHandlers = new Set<(authToken: AuthToken) => void>();\n\n get isConnected(): boolean {\n return this.#connected;\n }\n\n async connect(options: ConnectOptions = {}): Promise<HandshakePayload> {\n this.#assertNotDestroyed();\n\n if (this.#connected && this.#authToken && this.#extensionDetails && this.#designTokens) {\n return {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n };\n }\n\n if (this.#pendingHandshake) {\n throw new Error(\"Handshake already in progress.\");\n }\n\n this.#configureAuthTokenAutoRefresh(options);\n\n const tenantUrl = readTenantUrlFromLocation();\n this.#allowedOrigin = resolveAllowedOrigin(tenantUrl);\n\n this.#unsubscribe?.();\n this.#unsubscribe = subscribeToParentMessages(\n this.#allowedOrigin,\n (event) => this.#onMessage(event),\n );\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const handshakePromise = new Promise<HandshakePayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingHandshake = null;\n reject(new Error(`Handshake timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingHandshake = { resolve, reject, timer };\n });\n\n try {\n postToParent(CONNECTION_EVENTS.handshakeInit, this.#allowedOrigin);\n } catch (error) {\n this.#clearPending(this.#pendingHandshake);\n this.#pendingHandshake = null;\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n this.#clearProactiveAuthTokenRefreshTimer();\n throw error;\n }\n\n return handshakePromise;\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.#connected = false;\n\n this.#clearProactiveAuthTokenRefreshTimer();\n this.#authTokenAutoRefresh = false;\n this.#authTokenRefreshRetryUsed = false;\n\n this.#unsubscribe?.();\n this.#unsubscribe = null;\n this.#allowedOrigin = null;\n\n this.#rejectPending(this.#pendingHandshake, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingAuthTokenRefresh, new Error(\"SDK destroyed.\"));\n this.#rejectPending(this.#pendingPageSettingsUpdate, new Error(\"SDK destroyed.\"));\n this.#pendingHandshake = null;\n this.#pendingAuthTokenRefresh = null;\n this.#pendingPageSettingsUpdate = null;\n\n this.#loadPageSettingsHandlers.clear();\n this.#authTokenRefreshHandlers.clear();\n\n this.#authToken = null;\n this.#extensionDetails = null;\n this.#designTokens = null;\n this.#pageSettings = null;\n }\n\n getAuthToken(): AuthToken | null {\n return this.#authToken;\n }\n\n getExtensionDetails(): ExtensionDetails | null {\n return this.#extensionDetails;\n }\n\n getDesignTokens(): DesignTokens | null {\n return this.#designTokens;\n }\n\n getPageSettings(): PageSettings | null {\n return this.#pageSettings;\n }\n\n async apiCall<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n this.#assertConnected();\n\n return callApi<T>(\n endpoint,\n {\n baseUrl: this.#extensionDetails!.baseUrl,\n tenantDomain: this.#extensionDetails!.tenantDomain,\n authTokenJwt: this.#authToken!.jwt,\n },\n options,\n );\n }\n\n async emitRequestAuthTokenRefresh(\n options: RequestOptions = {},\n ): Promise<AuthToken> {\n this.#assertConnected();\n\n if (this.#pendingAuthTokenRefresh) {\n throw new Error(\"Auth token refresh already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<AuthToken>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingAuthTokenRefresh = null;\n reject(new Error(`Auth token refresh timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingAuthTokenRefresh = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.requestAuthTokenRefresh, allowedOrigin);\n return promise;\n }\n\n async emitUpdatePageSettings(\n payload: UpdatePageSettingsPayload,\n options: RequestOptions = {},\n ): Promise<PageSettingsUpdatedPayload> {\n this.#assertConnected();\n\n if (this.#pendingPageSettingsUpdate) {\n throw new Error(\"Page settings update already in progress.\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const allowedOrigin = this.#allowedOrigin!;\n\n const promise = new Promise<PageSettingsUpdatedPayload>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pendingPageSettingsUpdate = null;\n reject(new Error(`Page settings update timed out after ${timeoutMs}ms.`));\n }, timeoutMs);\n\n this.#pendingPageSettingsUpdate = { resolve, reject, timer };\n });\n\n postToParent(CONNECTION_EVENTS.updatePageSettings, allowedOrigin, payload);\n return promise;\n }\n\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void {\n this.#loadPageSettingsHandlers.add(handler);\n return () => {\n this.#loadPageSettingsHandlers.delete(handler);\n };\n }\n\n onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void {\n this.#authTokenRefreshHandlers.add(handler);\n return () => {\n this.#authTokenRefreshHandlers.delete(handler);\n };\n }\n\n emitNavigationChange(payload: NavigationChangePayload): void {\n this.#assertConnected();\n postToParent(\n CONNECTION_EVENTS.navigationChange,\n this.#allowedOrigin!,\n payload,\n );\n }\n\n #onMessage(event: MessageEvent<ExtensionMessage>): void {\n const { type, payload } = event.data;\n\n switch (type) {\n case CONNECTION_EVENTS.handshakeAck:\n this.#handleHandshakeAck(payload as HandshakePayload | undefined);\n break;\n case CONNECTION_EVENTS.authTokenRefresh:\n this.#handleAuthTokenRefresh(payload as AuthTokenRefreshPayload | undefined);\n break;\n case CONNECTION_EVENTS.loadPageSettings:\n this.#handleLoadPageSettings(payload as PageSettings | undefined);\n break;\n case CONNECTION_EVENTS.pageSettingsUpdated:\n this.#handlePageSettingsUpdated(\n payload as PageSettingsUpdatedPayload | undefined,\n );\n break;\n default:\n break;\n }\n }\n\n #handleHandshakeAck(payload: HandshakePayload | undefined): void {\n if (!payload?.authToken || !payload.extensionDetails || !payload.designTokens) {\n this.#rejectPending(\n this.#pendingHandshake,\n new Error(\"Invalid HANDSHAKE_ACK payload.\"),\n );\n this.#pendingHandshake = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n this.#extensionDetails = payload.extensionDetails;\n this.#designTokens = payload.designTokens;\n this.#pageSettings = payload.pageSettings ?? null;\n this.#connected = true;\n\n const pending = this.#pendingHandshake;\n this.#pendingHandshake = null;\n this.#resolvePending(pending, {\n authToken: this.#authToken,\n extensionDetails: this.#extensionDetails,\n designTokens: this.#designTokens,\n pageSettings: this.#pageSettings,\n });\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleAuthTokenRefresh(payload: AuthTokenRefreshPayload | undefined): void {\n if (!payload?.authToken) {\n this.#rejectPending(\n this.#pendingAuthTokenRefresh,\n new Error(\"Invalid TOKEN_REFRESH payload.\"),\n );\n this.#pendingAuthTokenRefresh = null;\n return;\n }\n\n this.#authToken = payload.authToken;\n\n const pending = this.#pendingAuthTokenRefresh;\n this.#pendingAuthTokenRefresh = null;\n this.#resolvePending(pending, this.#authToken);\n\n for (const handler of this.#authTokenRefreshHandlers) {\n handler(this.#authToken);\n }\n\n this.#authTokenRefreshRetryUsed = false;\n this.#scheduleProactiveAuthTokenRefresh();\n }\n\n #handleLoadPageSettings(payload: PageSettings | undefined): void {\n const settings = payload ?? {};\n this.#pageSettings = settings;\n\n for (const handler of this.#loadPageSettingsHandlers) {\n handler(settings);\n }\n }\n\n #handlePageSettingsUpdated(\n payload: PageSettingsUpdatedPayload | undefined,\n ): void {\n if (!payload || typeof payload.success !== \"boolean\") {\n this.#rejectPending(\n this.#pendingPageSettingsUpdate,\n new Error(\"Invalid PAGE_SETTINGS_UPDATED payload.\"),\n );\n this.#pendingPageSettingsUpdate = null;\n return;\n }\n\n const pending = this.#pendingPageSettingsUpdate;\n this.#pendingPageSettingsUpdate = null;\n this.#resolvePending(pending, payload);\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new Error(\"SDK has been destroyed.\");\n }\n }\n\n #assertConnected(): void {\n this.#assertNotDestroyed();\n if (!this.#connected || !this.#allowedOrigin) {\n throw new Error(\"SDK is not connected. Call connect() first.\");\n }\n }\n\n #configureAuthTokenAutoRefresh(options: ConnectOptions): void {\n if (options.authTokenBufferMs !== undefined) {\n if (\n !Number.isFinite(options.authTokenBufferMs) ||\n options.authTokenBufferMs < 0\n ) {\n throw new Error(\n \"authTokenBufferMs must be a finite non-negative number.\",\n );\n }\n }\n\n this.#authTokenAutoRefresh = options.authTokenAutoRefresh === true;\n this.#authTokenBufferMs =\n options.authTokenBufferMs ?? DEFAULT_AUTH_TOKEN_BUFFER_MS;\n this.#authTokenRefreshRetryUsed = false;\n this.#clearProactiveAuthTokenRefreshTimer();\n }\n\n #parseExpiresAtMs(authToken: AuthToken): number | null {\n const expiresSec = Number(authToken.expires);\n if (!Number.isFinite(expiresSec)) {\n return null;\n }\n return expiresSec * 1000;\n }\n\n #getProactiveRefreshDelayMs(authToken: AuthToken): number | null {\n const expiresAtMs = this.#parseExpiresAtMs(authToken);\n if (expiresAtMs === null) {\n return null;\n }\n return Math.max(0, expiresAtMs - this.#authTokenBufferMs - Date.now());\n }\n\n #clearProactiveAuthTokenRefreshTimer(): void {\n if (this.#authTokenRefreshTimer !== null) {\n clearTimeout(this.#authTokenRefreshTimer);\n this.#authTokenRefreshTimer = null;\n }\n }\n\n #scheduleProactiveAuthTokenRefresh(): void {\n this.#clearProactiveAuthTokenRefreshTimer();\n\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const delayMs = this.#getProactiveRefreshDelayMs(this.#authToken);\n if (delayMs === null) {\n return;\n }\n\n const clampedDelay = Math.min(delayMs, MAX_TIMEOUT_MS);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, clampedDelay);\n }\n\n async #runProactiveAuthTokenRefresh(): Promise<void> {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n !this.#authToken\n ) {\n return;\n }\n\n const authTokenAtRequest = this.#authToken;\n const delayMs = this.#getProactiveRefreshDelayMs(authTokenAtRequest);\n if (delayMs === null) {\n return;\n }\n\n if (delayMs > 0) {\n this.#scheduleProactiveAuthTokenRefresh();\n return;\n }\n\n if (this.#pendingAuthTokenRefresh) {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n return;\n }\n\n try {\n await this.emitRequestAuthTokenRefresh();\n } catch {\n this.#handleProactiveAuthTokenRefreshFailure(authTokenAtRequest);\n }\n }\n\n #handleProactiveAuthTokenRefreshFailure(authTokenAtRequest: AuthToken): void {\n if (\n !this.#authTokenAutoRefresh ||\n !this.#connected ||\n this.#destroyed ||\n this.#authToken?.jwt !== authTokenAtRequest.jwt\n ) {\n return;\n }\n\n if (this.#authTokenRefreshRetryUsed) {\n return;\n }\n\n const expiresAtMs = this.#parseExpiresAtMs(authTokenAtRequest);\n if (expiresAtMs === null) {\n return;\n }\n\n const remainingMs = expiresAtMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n this.#authTokenRefreshRetryUsed = true;\n this.#clearProactiveAuthTokenRefreshTimer();\n\n const retryDelay = Math.min(AUTO_REFRESH_RETRY_DELAY_MS, remainingMs);\n this.#authTokenRefreshTimer = setTimeout(() => {\n this.#authTokenRefreshTimer = null;\n void this.#runProactiveAuthTokenRefresh();\n }, retryDelay);\n }\n\n #clearPending(pending: { timer: ReturnType<typeof setTimeout> } | null): void {\n if (pending) {\n clearTimeout(pending.timer);\n }\n }\n\n #resolvePending<T>(pending: Pending<T> | null, value: T): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.resolve(value);\n }\n\n #rejectPending(\n pending: { timer: ReturnType<typeof setTimeout>; reject: (reason?: unknown) => void } | null,\n reason: unknown,\n ): void {\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n pending.reject(reason);\n }\n}\n","import { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\n\nexport { ExtensionSDK, type IExtensionSDK } from \"./ExtensionSDK\";\nexport { CONNECTION_EVENTS, type ConnectionEvent } from \"./events\";\nexport {\n InvalidOriginError,\n readTenantUrlFromLocation,\n resolveAllowedOrigin,\n} from \"./origin\";\nexport type {\n AuthToken,\n AuthTokenRefreshPayload,\n ConnectOptions,\n DesignTokens,\n ExtensionDetails,\n ExtensionMessage,\n HandshakePayload,\n NavigationChangePayload,\n PageSettings,\n PageSettingsUpdatedPayload,\n RequestOptions,\n UpdatePageSettingsPayload,\n} from \"./types\";\n\n/** Creates a new {@link IExtensionSDK} instance for the current extension frame. */\nexport const createExtensionSDK = (): IExtensionSDK => {\n return new ExtensionSDK();\n};\n"],"mappings":";AAMA,SAAS,EAAa,GAAyB;CAC7C,IAAI,EAAQ,WAAW,GAAG,GACxB,OAAO;CAGT,IAAM,IAAM,IAAI,IAAI,CAAO;CAE3B,OADA,EAAI,WAAW,OAAO,EAAI,YACnB,EAAI;AACb;AAEA,SAAS,EAAmB,GAAiB,GAA0B;CACrE,IAAI,CAAC,EAAS,WAAW,GAAG,KAAK,EAAS,WAAW,IAAI,GACvD,MAAU,MACR,yEACF;CAGF,OAAO,GAAG,EAAa,CAAO,CAAC,CAAC,QAAQ,QAAQ,EAAE,IAAI;AACxD;AAEA,eAAsB,EACpB,GACA,GACA,IAAuB,CAAC,GACZ;CACZ,IAAM,IAAU,IAAI,QAAQ,EAAQ,OAAO;CAO3C,AALK,EAAQ,IAAI,cAAc,KAC7B,EAAQ,IAAI,gBAAgB,kBAAkB,GAGhD,EAAQ,IAAI,iBAAiB,UAAU,EAAQ,cAAc,GAC7D,EAAQ,IAAI,YAAY,EAAQ,YAAY;CAE5C,IAAM,IAAW,MAAM,MAAM,EAAmB,EAAQ,SAAS,CAAQ,GAAG;EAC1E,GAAG;EACH;CACF,CAAC;CAED,IAAI,CAAC,EAAS,IAAI;EAChB,IAAM,IAAa,EAAS,aAAa,IAAI,EAAS,eAAe;EACrE,MAAU,MAAM,oBAAoB,EAAS,SAAS,EAAW,EAAE;CACrE;CAEA,OAAQ,MAAM,EAAS,KAAK;AAC9B;;;AC7CA,IAAa,IAAoB;CAE/B,eAAe;CAEf,cAAc;CAEd,yBAAyB;CAEzB,kBAAkB;CAElB,kBAAkB;CAElB,oBAAoB;CAEpB,qBAAqB;CAErB,kBAAkB;AACpB;;;AClBA,SAAgB,EACd,GACA,GACA,GACM;CACN,IAAI,CAAC,OAAO,UAAU,OAAO,WAAW,QACtC,MAAU,MAAM,iDAAiD;CAGnE,IAAM,IAA4B,MAAY,KAAA,IAAY,EAAE,QAAK,IAAI;EAAE;EAAM;CAAQ;CACrF,OAAO,OAAO,YAAY,GAAS,CAAa;AAClD;AAEA,SAAgB,EACd,GACA,GACY;CACZ,IAAM,KAAY,MAAwB;EACpC,EAAM,WAAW,MAIjB,CAAC,EAAM,QAAQ,OAAO,EAAM,QAAS,YAAY,EAAE,UAAU,EAAM,SAIvE,EAAQ,CAAuC;CACjD;CAGA,OADA,OAAO,iBAAiB,WAAW,CAAQ,SAC9B,OAAO,oBAAoB,WAAW,CAAQ;AAC7D;;;ACpCA,IAAM,oBAAkB,IAAI,IAAI;CAAC;CAAa;CAAa;AAAO,CAAC,GAGtD,IAAb,cAAwC,MAAM;CAC5C,YAAY,GAAiB;EAE3B,AADA,MAAM,CAAO,GACb,KAAK,OAAO;CACd;AACF;AAUA,SAAgB,EAAqB,GAAkC;CACrE,IAAI,CAAC,GACH,MAAM,IAAI,EACR,6EACF;CAGF,IAAI;CACJ,IAAI;EACF,IAAM,IAAI,IAAI,CAAS;CACzB,QAAQ;EACN,MAAM,IAAI,EAAmB,uBAAuB,EAAU,GAAG;CACnE;CAEA,IAAM,IAAc,EAAgB,IAAI,EAAI,QAAQ,GAC9C,IAAU,EAAI,aAAa,UAC3B,IAAc,EAAI,aAAa,WAAW;CAEhD,IAAI,CAAC,KAAW,CAAC,GACf,MAAM,IAAI,EACR,+DAA+D,EAAI,SAAS,IAAI,EAAI,KAAK,GAC3F;CAGF,OAAO,EAAI;AACb;AAUA,SAAgB,EACd,IAAiB,OAAO,SAAS,QAClB;CACf,OAAO,IAAI,gBAAgB,CAAM,CAAC,CAAC,IAAI,WAAW;AACpD;;;ACtCA,IAAM,IAAqB,KACrB,IAA+B,MAC/B,IAA8B,KAE9B,IAAiB,YAwHV,IAAb,MAAmD;CACjD,KAAgC;CAChC,KAAoC;CACpC,KAAa;CACb,KAAa;CAEb,KAA+B;CAC/B,KAA6C;CAC7C,KAAqC;CACrC,KAAqC;CAErC,KAAwB;CACxB,KAAqB;CACrB,KAA+D;CAC/D,KAA6B;CAE7B,KAAsD;CACtD,KAAsD;CACtD,KAAyE;CAEzE,qBAA4B,IAAI,IAAsC;CACtE,qBAA4B,IAAI,IAAoC;CAEpE,IAAI,cAAuB;EACzB,OAAO,KAAKA;CACd;CAEA,MAAM,QAAQ,IAA0B,CAAC,GAA8B;EAGrE,IAFA,KAAKC,GAAoB,GAErB,KAAKD,MAAc,KAAKE,MAAc,KAAKC,MAAqB,KAAKC,IACvE,OAAO;GACL,WAAW,KAAKF;GAChB,kBAAkB,KAAKC;GACvB,cAAc,KAAKC;GACnB,cAAc,KAAKC;EACrB;EAGF,IAAI,KAAKC,IACP,MAAU,MAAM,gCAAgC;EAGlD,KAAKC,GAA+B,CAAO;EAE3C,IAAM,IAAY,EAA0B;EAI5C,AAHA,KAAKC,KAAiB,EAAqB,CAAS,GAEpD,KAAKC,KAAe,GACpB,KAAKA,KAAe,EAClB,KAAKD,KACJ,MAAU,KAAKE,GAAW,CAAK,CAClC;EAEA,IAAM,IAAY,EAAQ,aAAa,GAEjC,IAAmB,IAAI,SAA2B,GAAS,MAAW;GAC1E,IAAM,IAAQ,iBAAiB;IAE7B,AADA,KAAKJ,KAAoB,MACzB,EAAO,gBAAI,MAAM,6BAA6B,EAAU,IAAI,CAAC;GAC/D,GAAG,CAAS;GAEZ,KAAKA,KAAoB;IAAE;IAAS;IAAQ;GAAM;EACpD,CAAC;EAED,IAAI;GACF,EAAa,EAAkB,eAAe,KAAKE,EAAc;EACnE,SAAS,GAAO;GAOd,MANA,KAAKG,GAAc,KAAKL,EAAiB,GACzC,KAAKA,KAAoB,MACzB,KAAKG,KAAe,GACpB,KAAKA,KAAe,MACpB,KAAKD,KAAiB,MACtB,KAAKI,GAAqC,GACpC;EACR;EAEA,OAAO;CACT;CAEA,UAAgB;EAyBd,AAxBA,KAAKC,KAAa,IAClB,KAAKb,KAAa,IAElB,KAAKY,GAAqC,GAC1C,KAAKE,KAAwB,IAC7B,KAAKC,KAA6B,IAElC,KAAKN,KAAe,GACpB,KAAKA,KAAe,MACpB,KAAKD,KAAiB,MAEtB,KAAKQ,GAAe,KAAKV,IAAmB,gBAAI,MAAM,gBAAgB,CAAC,GACvE,KAAKU,GAAe,KAAKC,IAA0B,gBAAI,MAAM,gBAAgB,CAAC,GAC9E,KAAKD,GAAe,KAAKE,IAA4B,gBAAI,MAAM,gBAAgB,CAAC,GAChF,KAAKZ,KAAoB,MACzB,KAAKW,KAA2B,MAChC,KAAKC,KAA6B,MAElC,KAAKC,GAA0B,MAAM,GACrC,KAAKC,GAA0B,MAAM,GAErC,KAAKlB,KAAa,MAClB,KAAKC,KAAoB,MACzB,KAAKC,KAAgB,MACrB,KAAKC,KAAgB;CACvB;CAEA,eAAiC;EAC/B,OAAO,KAAKH;CACd;CAEA,sBAA+C;EAC7C,OAAO,KAAKC;CACd;CAEA,kBAAuC;EACrC,OAAO,KAAKC;CACd;CAEA,kBAAuC;EACrC,OAAO,KAAKC;CACd;CAEA,MAAM,QACJ,GACA,IAAuB,CAAC,GACZ;EAGZ,OAFA,KAAKgB,GAAiB,GAEf,EACL,GACA;GACE,SAAS,KAAKlB,GAAmB;GACjC,cAAc,KAAKA,GAAmB;GACtC,cAAc,KAAKD,GAAY;EACjC,GACA,CACF;CACF;CAEA,MAAM,4BACJ,IAA0B,CAAC,GACP;EAGpB,IAFA,KAAKmB,GAAiB,GAElB,KAAKJ,IACP,MAAU,MAAM,yCAAyC;EAG3D,IAAM,IAAY,EAAQ,aAAa,GACjC,IAAgB,KAAKT,IAErB,IAAU,IAAI,SAAoB,GAAS,MAAW;GAC1D,IAAM,IAAQ,iBAAiB;IAE7B,AADA,KAAKS,KAA2B,MAChC,EAAO,gBAAI,MAAM,sCAAsC,EAAU,IAAI,CAAC;GACxE,GAAG,CAAS;GAEZ,KAAKA,KAA2B;IAAE;IAAS;IAAQ;GAAM;EAC3D,CAAC;EAGD,OADA,EAAa,EAAkB,yBAAyB,CAAa,GAC9D;CACT;CAEA,MAAM,uBACJ,GACA,IAA0B,CAAC,GACU;EAGrC,IAFA,KAAKI,GAAiB,GAElB,KAAKH,IACP,MAAU,MAAM,2CAA2C;EAG7D,IAAM,IAAY,EAAQ,aAAa,GACjC,IAAgB,KAAKV,IAErB,IAAU,IAAI,SAAqC,GAAS,MAAW;GAC3E,IAAM,IAAQ,iBAAiB;IAE7B,AADA,KAAKU,KAA6B,MAClC,EAAO,gBAAI,MAAM,wCAAwC,EAAU,IAAI,CAAC;GAC1E,GAAG,CAAS;GAEZ,KAAKA,KAA6B;IAAE;IAAS;IAAQ;GAAM;EAC7D,CAAC;EAGD,OADA,EAAa,EAAkB,oBAAoB,GAAe,CAAO,GAClE;CACT;CAEA,mBAAmB,GAAuD;EAExE,OADA,KAAKC,GAA0B,IAAI,CAAO,SAC7B;GACX,KAAKA,GAA0B,OAAO,CAAO;EAC/C;CACF;CAEA,mBAAmB,GAAqD;EAEtE,OADA,KAAKC,GAA0B,IAAI,CAAO,SAC7B;GACX,KAAKA,GAA0B,OAAO,CAAO;EAC/C;CACF;CAEA,qBAAqB,GAAwC;EAE3D,AADA,KAAKC,GAAiB,GACtB,EACE,EAAkB,kBAClB,KAAKb,IACL,CACF;CACF;CAEA,GAAW,GAA6C;EACtD,IAAM,EAAE,SAAM,eAAY,EAAM;EAEhC,QAAQ,GAAR;GACE,KAAK,EAAkB;IACrB,KAAKc,GAAoB,CAAuC;IAChE;GACF,KAAK,EAAkB;IACrB,KAAKC,GAAwB,CAA8C;IAC3E;GACF,KAAK,EAAkB;IACrB,KAAKC,GAAwB,CAAmC;IAChE;GACF,KAAK,EAAkB;IACrB,KAAKC,GACH,CACF;IACA;GACF,SACE;EACJ;CACF;CAEA,GAAoB,GAA6C;EAC/D,IAAI,CAAC,GAAS,aAAa,CAAC,EAAQ,oBAAoB,CAAC,EAAQ,cAAc;GAK7E,AAJA,KAAKT,GACH,KAAKV,IACL,gBAAI,MAAM,gCAAgC,CAC5C,GACA,KAAKA,KAAoB;GACzB;EACF;EAMA,AAJA,KAAKJ,KAAa,EAAQ,WAC1B,KAAKC,KAAoB,EAAQ,kBACjC,KAAKC,KAAgB,EAAQ,cAC7B,KAAKC,KAAgB,EAAQ,gBAAgB,MAC7C,KAAKL,KAAa;EAElB,IAAM,IAAU,KAAKM;EAUrB,AATA,KAAKA,KAAoB,MACzB,KAAKoB,GAAgB,GAAS;GAC5B,WAAW,KAAKxB;GAChB,kBAAkB,KAAKC;GACvB,cAAc,KAAKC;GACnB,cAAc,KAAKC;EACrB,CAAC,GAED,KAAKU,KAA6B,IAClC,KAAKY,GAAmC;CAC1C;CAEA,GAAwB,GAAoD;EAC1E,IAAI,CAAC,GAAS,WAAW;GAKvB,AAJA,KAAKX,GACH,KAAKC,IACL,gBAAI,MAAM,gCAAgC,CAC5C,GACA,KAAKA,KAA2B;GAChC;EACF;EAEA,KAAKf,KAAa,EAAQ;EAE1B,IAAM,IAAU,KAAKe;EAErB,AADA,KAAKA,KAA2B,MAChC,KAAKS,GAAgB,GAAS,KAAKxB,EAAU;EAE7C,KAAK,IAAM,KAAW,KAAKkB,IACzB,EAAQ,KAAKlB,EAAU;EAIzB,AADA,KAAKa,KAA6B,IAClC,KAAKY,GAAmC;CAC1C;CAEA,GAAwB,GAAyC;EAC/D,IAAM,IAAW,KAAW,CAAC;EAC7B,KAAKtB,KAAgB;EAErB,KAAK,IAAM,KAAW,KAAKc,IACzB,EAAQ,CAAQ;CAEpB;CAEA,GACE,GACM;EACN,IAAI,CAAC,KAAW,OAAO,EAAQ,WAAY,WAAW;GAKpD,AAJA,KAAKH,GACH,KAAKE,IACL,gBAAI,MAAM,wCAAwC,CACpD,GACA,KAAKA,KAA6B;GAClC;EACF;EAEA,IAAM,IAAU,KAAKA;EAErB,AADA,KAAKA,KAA6B,MAClC,KAAKQ,GAAgB,GAAS,CAAO;CACvC;CAEA,KAA4B;EAC1B,IAAI,KAAKb,IACP,MAAU,MAAM,yBAAyB;CAE7C;CAEA,KAAyB;EAEvB,IADA,KAAKZ,GAAoB,GACrB,CAAC,KAAKD,MAAc,CAAC,KAAKQ,IAC5B,MAAU,MAAM,6CAA6C;CAEjE;CAEA,GAA+B,GAA+B;EAC5D,IAAI,EAAQ,sBAAsB,KAAA,MAE9B,CAAC,OAAO,SAAS,EAAQ,iBAAiB,KAC1C,EAAQ,oBAAoB,IAE5B,MAAU,MACR,yDACF;EAQJ,AAJA,KAAKM,KAAwB,EAAQ,yBAAyB,IAC9D,KAAKc,KACH,EAAQ,qBAAqB,GAC/B,KAAKb,KAA6B,IAClC,KAAKH,GAAqC;CAC5C;CAEA,GAAkB,GAAqC;EACrD,IAAM,IAAa,OAAO,EAAU,OAAO;EAI3C,OAHK,OAAO,SAAS,CAAU,IAGxB,IAAa,MAFX;CAGX;CAEA,GAA4B,GAAqC;EAC/D,IAAM,IAAc,KAAKiB,GAAkB,CAAS;EAIpD,OAHI,MAAgB,OACX,OAEF,KAAK,IAAI,GAAG,IAAc,KAAKD,KAAqB,KAAK,IAAI,CAAC;CACvE;CAEA,KAA6C;EAC3C,AAAI,KAAKE,OAA2B,SAClC,aAAa,KAAKA,EAAsB,GACxC,KAAKA,KAAyB;CAElC;CAEA,KAA2C;EAGzC,IAFA,KAAKlB,GAAqC,GAGxC,CAAC,KAAKE,MACN,CAAC,KAAKd,MACN,KAAKa,MACL,CAAC,KAAKX,IAEN;EAGF,IAAM,IAAU,KAAK6B,GAA4B,KAAK7B,EAAU;EAChE,IAAI,MAAY,MACd;EAGF,IAAM,IAAe,KAAK,IAAI,GAAS,CAAc;EACrD,KAAK4B,KAAyB,iBAAiB;GAE7C,AADA,KAAKA,KAAyB,MAC9B,KAAUE,GAA8B;EAC1C,GAAG,CAAY;CACjB;CAEA,MAAMA,KAA+C;EACnD,IACE,CAAC,KAAKlB,MACN,CAAC,KAAKd,MACN,KAAKa,MACL,CAAC,KAAKX,IAEN;EAGF,IAAM,IAAqB,KAAKA,IAC1B,IAAU,KAAK6B,GAA4B,CAAkB;EAC/D,UAAY,MAIhB;OAAI,IAAU,GAAG;IACf,KAAKJ,GAAmC;IACxC;GACF;GAEA,IAAI,KAAKV,IAA0B;IACjC,KAAKgB,GAAwC,CAAkB;IAC/D;GACF;GAEA,IAAI;IACF,MAAM,KAAK,4BAA4B;GACzC,QAAQ;IACN,KAAKA,GAAwC,CAAkB;GACjE;EAXA;CAYF;CAEA,GAAwC,GAAqC;EAU3E,IARE,CAAC,KAAKnB,MACN,CAAC,KAAKd,MACN,KAAKa,MACL,KAAKX,IAAY,QAAQ,EAAmB,OAK1C,KAAKa,IACP;EAGF,IAAM,IAAc,KAAKc,GAAkB,CAAkB;EAC7D,IAAI,MAAgB,MAClB;EAGF,IAAM,IAAc,IAAc,KAAK,IAAI;EAC3C,IAAI,KAAe,GACjB;EAIF,AADA,KAAKd,KAA6B,IAClC,KAAKH,GAAqC;EAE1C,IAAM,IAAa,KAAK,IAAI,GAA6B,CAAW;EACpE,KAAKkB,KAAyB,iBAAiB;GAE7C,AADA,KAAKA,KAAyB,MAC9B,KAAUE,GAA8B;EAC1C,GAAG,CAAU;CACf;CAEA,GAAc,GAAgE;EAC5E,AAAI,KACF,aAAa,EAAQ,KAAK;CAE9B;CAEA,GAAmB,GAA4B,GAAgB;EACxD,MAGL,aAAa,EAAQ,KAAK,GAC1B,EAAQ,QAAQ,CAAK;CACvB;CAEA,GACE,GACA,GACM;EACD,MAGL,aAAa,EAAQ,KAAK,GAC1B,EAAQ,OAAO,CAAM;CACvB;AACF,GC7lBa,UACJ,IAAI,EAAa"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/** Auth token issued by the parent (`authToken` on the wire). */
|
|
2
|
+
export declare interface AuthToken {
|
|
3
|
+
jwt: string;
|
|
4
|
+
/** Unix expiry seconds as a string, per parent payload. */
|
|
5
|
+
expires: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Payload for `TOKEN_REFRESH` (requested or parent-pushed). */
|
|
9
|
+
export declare interface AuthTokenRefreshPayload {
|
|
10
|
+
authToken: AuthToken;
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
/**
|
|
2
14
|
* Wire event type strings for parent/child `postMessage` communication.
|
|
3
15
|
*
|
|
@@ -10,15 +22,17 @@ export declare const CONNECTION_EVENTS: {
|
|
|
10
22
|
/** Parent → child: handshake success with auth and context. */
|
|
11
23
|
readonly handshakeAck: "HANDSHAKE_ACK";
|
|
12
24
|
/** Child → parent: request a new auth token. */
|
|
13
|
-
readonly
|
|
25
|
+
readonly requestAuthTokenRefresh: "REQUEST_TOKEN_REFRESH";
|
|
14
26
|
/** Parent → child: new auth token (reply or push). */
|
|
15
|
-
readonly
|
|
27
|
+
readonly authTokenRefresh: "TOKEN_REFRESH";
|
|
16
28
|
/** Parent → child: open/load page settings UI with values. */
|
|
17
29
|
readonly loadPageSettings: "LOAD_PAGE_SETTINGS";
|
|
18
30
|
/** Child → parent: persist updated page settings. */
|
|
19
31
|
readonly updatePageSettings: "UPDATE_PAGE_SETTINGS";
|
|
20
32
|
/** Parent → child: result of a page settings update. */
|
|
21
33
|
readonly pageSettingsUpdated: "PAGE_SETTINGS_UPDATED";
|
|
34
|
+
/** Child → parent: extension route changed. */
|
|
35
|
+
readonly navigationChange: "NAVIGATION_CHANGE";
|
|
22
36
|
};
|
|
23
37
|
|
|
24
38
|
/** Union of all {@link CONNECTION_EVENTS} wire string values. */
|
|
@@ -28,6 +42,31 @@ export declare type ConnectionEvent = (typeof CONNECTION_EVENTS)[keyof typeof CO
|
|
|
28
42
|
export declare interface ConnectOptions {
|
|
29
43
|
/** Handshake timeout in milliseconds (default 10_000). */
|
|
30
44
|
timeoutMs?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Enables proactive auth-token refresh. After the handshake, the SDK reads
|
|
47
|
+
* the auth token's Unix-seconds `expires` value and arms a one-shot timer
|
|
48
|
+
* for `expires - authTokenBufferMs`; it does not run a polling interval.
|
|
49
|
+
*
|
|
50
|
+
* Every successful manual, automatic, or parent-pushed auth-token update
|
|
51
|
+
* cancels the previous timer and schedules a new one from the new auth
|
|
52
|
+
* auth token's expiry. If an automatic request fails, it retries once after 30
|
|
53
|
+
* seconds while the same auth token is still current and unexpired.
|
|
54
|
+
* {@link ExtensionSDK.destroy} clears the timer. Missing or invalid expiry
|
|
55
|
+
* values disable scheduling for that auth token without failing the
|
|
56
|
+
* connection.
|
|
57
|
+
*
|
|
58
|
+
* Default: `false`.
|
|
59
|
+
*/
|
|
60
|
+
authTokenAutoRefresh?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* How many milliseconds before auth-token expiry the one-shot refresh timer
|
|
63
|
+
* should fire. Must be finite and non-negative. If the auth token is already
|
|
64
|
+
* inside this buffer when received, refresh is requested immediately.
|
|
65
|
+
*
|
|
66
|
+
* Default: `120_000` (2 minutes). Ignored unless
|
|
67
|
+
* {@link authTokenAutoRefresh} is enabled.
|
|
68
|
+
*/
|
|
69
|
+
authTokenBufferMs?: number;
|
|
31
70
|
}
|
|
32
71
|
|
|
33
72
|
/** Creates a new {@link IExtensionSDK} instance for the current extension frame. */
|
|
@@ -65,19 +104,21 @@ export declare class ExtensionSDK implements IExtensionSDK {
|
|
|
65
104
|
get isConnected(): boolean;
|
|
66
105
|
connect(options?: ConnectOptions): Promise<HandshakePayload>;
|
|
67
106
|
destroy(): void;
|
|
68
|
-
getAuthToken():
|
|
107
|
+
getAuthToken(): AuthToken | null;
|
|
69
108
|
getExtensionDetails(): ExtensionDetails | null;
|
|
70
109
|
getDesignTokens(): DesignTokens | null;
|
|
71
110
|
getPageSettings(): PageSettings | null;
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;
|
|
112
|
+
emitRequestAuthTokenRefresh(options?: RequestOptions): Promise<AuthToken>;
|
|
113
|
+
emitUpdatePageSettings(payload: UpdatePageSettingsPayload, options?: RequestOptions): Promise<PageSettingsUpdatedPayload>;
|
|
74
114
|
onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;
|
|
75
|
-
|
|
115
|
+
onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;
|
|
116
|
+
emitNavigationChange(payload: NavigationChangePayload): void;
|
|
76
117
|
}
|
|
77
118
|
|
|
78
119
|
/** Payload delivered with a successful `HANDSHAKE_ACK`. */
|
|
79
120
|
export declare interface HandshakePayload {
|
|
80
|
-
authToken:
|
|
121
|
+
authToken: AuthToken;
|
|
81
122
|
extensionDetails: ExtensionDetails;
|
|
82
123
|
designTokens: DesignTokens;
|
|
83
124
|
pageSettings: PageSettings | null;
|
|
@@ -97,21 +138,24 @@ export declare interface IExtensionSDK {
|
|
|
97
138
|
* without sending another init. Concurrent calls while a handshake is
|
|
98
139
|
* in progress throw.
|
|
99
140
|
*
|
|
100
|
-
*
|
|
141
|
+
* Opt-in proactive refresh can be enabled with
|
|
142
|
+
* `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).
|
|
143
|
+
*
|
|
144
|
+
* @param options - Handshake timeout and optional auth-token auto-refresh.
|
|
101
145
|
* @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
|
|
102
146
|
* @throws When not embedded in a parent frame, already handshaking,
|
|
103
147
|
* destroyed, or the handshake times out / returns an invalid payload.
|
|
104
148
|
*/
|
|
105
149
|
connect(options?: ConnectOptions): Promise<HandshakePayload>;
|
|
106
150
|
/**
|
|
107
|
-
* Tears down listeners and cached state.
|
|
108
|
-
* The instance cannot be reused after destroy.
|
|
151
|
+
* Tears down listeners, proactive refresh timers, and cached state.
|
|
152
|
+
* Rejects any in-flight requests. The instance cannot be reused after destroy.
|
|
109
153
|
*/
|
|
110
154
|
destroy(): void;
|
|
111
155
|
/** Whether a successful handshake has completed and not been destroyed. */
|
|
112
156
|
readonly isConnected: boolean;
|
|
113
157
|
/** Last auth token from handshake or refresh; `null` until connected. */
|
|
114
|
-
getAuthToken():
|
|
158
|
+
getAuthToken(): AuthToken | null;
|
|
115
159
|
/** Extension context from handshake; `null` until connected. */
|
|
116
160
|
getExtensionDetails(): ExtensionDetails | null;
|
|
117
161
|
/** Design tokens from handshake; `null` until connected. */
|
|
@@ -122,22 +166,35 @@ export declare interface IExtensionSDK {
|
|
|
122
166
|
*/
|
|
123
167
|
getPageSettings(): PageSettings | null;
|
|
124
168
|
/**
|
|
125
|
-
*
|
|
169
|
+
* Calls the Kadanza Platform API with the current connection credentials.
|
|
170
|
+
*
|
|
171
|
+
* The API origin is derived from the handshake `baseUrl`. Authorization and
|
|
172
|
+
* tenant headers are managed by the SDK and cannot be overridden.
|
|
173
|
+
*
|
|
174
|
+
* @typeParam T - Expected JSON response body.
|
|
175
|
+
* @param endpoint - Root-relative API path.
|
|
176
|
+
* @param options - Standard fetch options.
|
|
177
|
+
* @throws When not connected, the endpoint is invalid, the request fails,
|
|
178
|
+
* or the response is not successful JSON.
|
|
179
|
+
*/
|
|
180
|
+
apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;
|
|
181
|
+
/**
|
|
182
|
+
* Asks the parent for a new auth token and waits for `TOKEN_REFRESH`.
|
|
126
183
|
*
|
|
127
184
|
* @param options - Optional request timeout (default 10s).
|
|
128
185
|
* @throws When not connected, a refresh is already in progress,
|
|
129
186
|
* or the request times out / returns an invalid payload.
|
|
130
187
|
*/
|
|
131
|
-
|
|
188
|
+
emitRequestAuthTokenRefresh(options?: RequestOptions): Promise<AuthToken>;
|
|
132
189
|
/**
|
|
133
190
|
* Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.
|
|
134
191
|
*
|
|
135
|
-
* @param
|
|
192
|
+
* @param payload - Update payload containing the settings object.
|
|
136
193
|
* @param options - Optional request timeout (default 10s).
|
|
137
194
|
* @throws When not connected, an update is already in progress,
|
|
138
195
|
* or the request times out / returns an invalid payload.
|
|
139
196
|
*/
|
|
140
|
-
|
|
197
|
+
emitUpdatePageSettings(payload: UpdatePageSettingsPayload, options?: RequestOptions): Promise<PageSettingsUpdatedPayload>;
|
|
141
198
|
/**
|
|
142
199
|
* Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.
|
|
143
200
|
*
|
|
@@ -145,11 +202,20 @@ export declare interface IExtensionSDK {
|
|
|
145
202
|
*/
|
|
146
203
|
onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;
|
|
147
204
|
/**
|
|
148
|
-
* Registers a handler for token updates (requested or parent-pushed).
|
|
205
|
+
* Registers a handler for auth-token updates (requested or parent-pushed).
|
|
149
206
|
*
|
|
150
207
|
* @returns Unsubscribe function.
|
|
151
208
|
*/
|
|
152
|
-
|
|
209
|
+
onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;
|
|
210
|
+
/**
|
|
211
|
+
* Notifies the parent that the extension's route changed.
|
|
212
|
+
*
|
|
213
|
+
* Wire: `NAVIGATION_CHANGE` (fire-and-forget).
|
|
214
|
+
*
|
|
215
|
+
* @param payload - Navigation change payload (`path` within the extension).
|
|
216
|
+
* @throws When not connected or destroyed.
|
|
217
|
+
*/
|
|
218
|
+
emitNavigationChange(payload: NavigationChangePayload): void;
|
|
153
219
|
}
|
|
154
220
|
|
|
155
221
|
/** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */
|
|
@@ -157,6 +223,11 @@ export declare class InvalidOriginError extends Error {
|
|
|
157
223
|
constructor(message: string);
|
|
158
224
|
}
|
|
159
225
|
|
|
226
|
+
/** Payload for `NAVIGATION_CHANGE` (child → parent). */
|
|
227
|
+
export declare interface NavigationChangePayload {
|
|
228
|
+
path: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
160
231
|
/** Opaque page-level settings bag owned by the extension and synced with the parent. */
|
|
161
232
|
export declare type PageSettings = Record<string, unknown>;
|
|
162
233
|
|
|
@@ -191,16 +262,9 @@ export declare interface RequestOptions {
|
|
|
191
262
|
*/
|
|
192
263
|
export declare function resolveAllowedOrigin(tenantUrl: string | null): string;
|
|
193
264
|
|
|
194
|
-
/**
|
|
195
|
-
export declare interface
|
|
196
|
-
|
|
197
|
-
/** Unix expiry seconds as a string, per parent payload. */
|
|
198
|
-
expires: string;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Payload for `TOKEN_REFRESH` (requested or parent-pushed). */
|
|
202
|
-
export declare interface TokenRefreshPayload {
|
|
203
|
-
authToken: ScopedExtensionToken;
|
|
265
|
+
/** Payload for `UPDATE_PAGE_SETTINGS` (child → parent). */
|
|
266
|
+
export declare interface UpdatePageSettingsPayload {
|
|
267
|
+
settings: PageSettings;
|
|
204
268
|
}
|
|
205
269
|
|
|
206
270
|
export { }
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/** Auth token issued by the parent (`authToken` on the wire). */
|
|
2
|
+
export declare interface AuthToken {
|
|
3
|
+
jwt: string;
|
|
4
|
+
/** Unix expiry seconds as a string, per parent payload. */
|
|
5
|
+
expires: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Payload for `TOKEN_REFRESH` (requested or parent-pushed). */
|
|
9
|
+
export declare interface AuthTokenRefreshPayload {
|
|
10
|
+
authToken: AuthToken;
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
/**
|
|
2
14
|
* Wire event type strings for parent/child `postMessage` communication.
|
|
3
15
|
*
|
|
@@ -10,15 +22,17 @@ export declare const CONNECTION_EVENTS: {
|
|
|
10
22
|
/** Parent → child: handshake success with auth and context. */
|
|
11
23
|
readonly handshakeAck: "HANDSHAKE_ACK";
|
|
12
24
|
/** Child → parent: request a new auth token. */
|
|
13
|
-
readonly
|
|
25
|
+
readonly requestAuthTokenRefresh: "REQUEST_TOKEN_REFRESH";
|
|
14
26
|
/** Parent → child: new auth token (reply or push). */
|
|
15
|
-
readonly
|
|
27
|
+
readonly authTokenRefresh: "TOKEN_REFRESH";
|
|
16
28
|
/** Parent → child: open/load page settings UI with values. */
|
|
17
29
|
readonly loadPageSettings: "LOAD_PAGE_SETTINGS";
|
|
18
30
|
/** Child → parent: persist updated page settings. */
|
|
19
31
|
readonly updatePageSettings: "UPDATE_PAGE_SETTINGS";
|
|
20
32
|
/** Parent → child: result of a page settings update. */
|
|
21
33
|
readonly pageSettingsUpdated: "PAGE_SETTINGS_UPDATED";
|
|
34
|
+
/** Child → parent: extension route changed. */
|
|
35
|
+
readonly navigationChange: "NAVIGATION_CHANGE";
|
|
22
36
|
};
|
|
23
37
|
|
|
24
38
|
/** Union of all {@link CONNECTION_EVENTS} wire string values. */
|
|
@@ -28,6 +42,31 @@ export declare type ConnectionEvent = (typeof CONNECTION_EVENTS)[keyof typeof CO
|
|
|
28
42
|
export declare interface ConnectOptions {
|
|
29
43
|
/** Handshake timeout in milliseconds (default 10_000). */
|
|
30
44
|
timeoutMs?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Enables proactive auth-token refresh. After the handshake, the SDK reads
|
|
47
|
+
* the auth token's Unix-seconds `expires` value and arms a one-shot timer
|
|
48
|
+
* for `expires - authTokenBufferMs`; it does not run a polling interval.
|
|
49
|
+
*
|
|
50
|
+
* Every successful manual, automatic, or parent-pushed auth-token update
|
|
51
|
+
* cancels the previous timer and schedules a new one from the new auth
|
|
52
|
+
* auth token's expiry. If an automatic request fails, it retries once after 30
|
|
53
|
+
* seconds while the same auth token is still current and unexpired.
|
|
54
|
+
* {@link ExtensionSDK.destroy} clears the timer. Missing or invalid expiry
|
|
55
|
+
* values disable scheduling for that auth token without failing the
|
|
56
|
+
* connection.
|
|
57
|
+
*
|
|
58
|
+
* Default: `false`.
|
|
59
|
+
*/
|
|
60
|
+
authTokenAutoRefresh?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* How many milliseconds before auth-token expiry the one-shot refresh timer
|
|
63
|
+
* should fire. Must be finite and non-negative. If the auth token is already
|
|
64
|
+
* inside this buffer when received, refresh is requested immediately.
|
|
65
|
+
*
|
|
66
|
+
* Default: `120_000` (2 minutes). Ignored unless
|
|
67
|
+
* {@link authTokenAutoRefresh} is enabled.
|
|
68
|
+
*/
|
|
69
|
+
authTokenBufferMs?: number;
|
|
31
70
|
}
|
|
32
71
|
|
|
33
72
|
/** Creates a new {@link IExtensionSDK} instance for the current extension frame. */
|
|
@@ -65,19 +104,21 @@ export declare class ExtensionSDK implements IExtensionSDK {
|
|
|
65
104
|
get isConnected(): boolean;
|
|
66
105
|
connect(options?: ConnectOptions): Promise<HandshakePayload>;
|
|
67
106
|
destroy(): void;
|
|
68
|
-
getAuthToken():
|
|
107
|
+
getAuthToken(): AuthToken | null;
|
|
69
108
|
getExtensionDetails(): ExtensionDetails | null;
|
|
70
109
|
getDesignTokens(): DesignTokens | null;
|
|
71
110
|
getPageSettings(): PageSettings | null;
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;
|
|
112
|
+
emitRequestAuthTokenRefresh(options?: RequestOptions): Promise<AuthToken>;
|
|
113
|
+
emitUpdatePageSettings(payload: UpdatePageSettingsPayload, options?: RequestOptions): Promise<PageSettingsUpdatedPayload>;
|
|
74
114
|
onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;
|
|
75
|
-
|
|
115
|
+
onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;
|
|
116
|
+
emitNavigationChange(payload: NavigationChangePayload): void;
|
|
76
117
|
}
|
|
77
118
|
|
|
78
119
|
/** Payload delivered with a successful `HANDSHAKE_ACK`. */
|
|
79
120
|
export declare interface HandshakePayload {
|
|
80
|
-
authToken:
|
|
121
|
+
authToken: AuthToken;
|
|
81
122
|
extensionDetails: ExtensionDetails;
|
|
82
123
|
designTokens: DesignTokens;
|
|
83
124
|
pageSettings: PageSettings | null;
|
|
@@ -97,21 +138,24 @@ export declare interface IExtensionSDK {
|
|
|
97
138
|
* without sending another init. Concurrent calls while a handshake is
|
|
98
139
|
* in progress throw.
|
|
99
140
|
*
|
|
100
|
-
*
|
|
141
|
+
* Opt-in proactive refresh can be enabled with
|
|
142
|
+
* `authTokenAutoRefresh: true` (optional `authTokenBufferMs`).
|
|
143
|
+
*
|
|
144
|
+
* @param options - Handshake timeout and optional auth-token auto-refresh.
|
|
101
145
|
* @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
|
|
102
146
|
* @throws When not embedded in a parent frame, already handshaking,
|
|
103
147
|
* destroyed, or the handshake times out / returns an invalid payload.
|
|
104
148
|
*/
|
|
105
149
|
connect(options?: ConnectOptions): Promise<HandshakePayload>;
|
|
106
150
|
/**
|
|
107
|
-
* Tears down listeners and cached state.
|
|
108
|
-
* The instance cannot be reused after destroy.
|
|
151
|
+
* Tears down listeners, proactive refresh timers, and cached state.
|
|
152
|
+
* Rejects any in-flight requests. The instance cannot be reused after destroy.
|
|
109
153
|
*/
|
|
110
154
|
destroy(): void;
|
|
111
155
|
/** Whether a successful handshake has completed and not been destroyed. */
|
|
112
156
|
readonly isConnected: boolean;
|
|
113
157
|
/** Last auth token from handshake or refresh; `null` until connected. */
|
|
114
|
-
getAuthToken():
|
|
158
|
+
getAuthToken(): AuthToken | null;
|
|
115
159
|
/** Extension context from handshake; `null` until connected. */
|
|
116
160
|
getExtensionDetails(): ExtensionDetails | null;
|
|
117
161
|
/** Design tokens from handshake; `null` until connected. */
|
|
@@ -122,22 +166,35 @@ export declare interface IExtensionSDK {
|
|
|
122
166
|
*/
|
|
123
167
|
getPageSettings(): PageSettings | null;
|
|
124
168
|
/**
|
|
125
|
-
*
|
|
169
|
+
* Calls the Kadanza Platform API with the current connection credentials.
|
|
170
|
+
*
|
|
171
|
+
* The API origin is derived from the handshake `baseUrl`. Authorization and
|
|
172
|
+
* tenant headers are managed by the SDK and cannot be overridden.
|
|
173
|
+
*
|
|
174
|
+
* @typeParam T - Expected JSON response body.
|
|
175
|
+
* @param endpoint - Root-relative API path.
|
|
176
|
+
* @param options - Standard fetch options.
|
|
177
|
+
* @throws When not connected, the endpoint is invalid, the request fails,
|
|
178
|
+
* or the response is not successful JSON.
|
|
179
|
+
*/
|
|
180
|
+
apiCall<T>(endpoint: string, options?: RequestInit): Promise<T>;
|
|
181
|
+
/**
|
|
182
|
+
* Asks the parent for a new auth token and waits for `TOKEN_REFRESH`.
|
|
126
183
|
*
|
|
127
184
|
* @param options - Optional request timeout (default 10s).
|
|
128
185
|
* @throws When not connected, a refresh is already in progress,
|
|
129
186
|
* or the request times out / returns an invalid payload.
|
|
130
187
|
*/
|
|
131
|
-
|
|
188
|
+
emitRequestAuthTokenRefresh(options?: RequestOptions): Promise<AuthToken>;
|
|
132
189
|
/**
|
|
133
190
|
* Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.
|
|
134
191
|
*
|
|
135
|
-
* @param
|
|
192
|
+
* @param payload - Update payload containing the settings object.
|
|
136
193
|
* @param options - Optional request timeout (default 10s).
|
|
137
194
|
* @throws When not connected, an update is already in progress,
|
|
138
195
|
* or the request times out / returns an invalid payload.
|
|
139
196
|
*/
|
|
140
|
-
|
|
197
|
+
emitUpdatePageSettings(payload: UpdatePageSettingsPayload, options?: RequestOptions): Promise<PageSettingsUpdatedPayload>;
|
|
141
198
|
/**
|
|
142
199
|
* Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.
|
|
143
200
|
*
|
|
@@ -145,11 +202,20 @@ export declare interface IExtensionSDK {
|
|
|
145
202
|
*/
|
|
146
203
|
onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;
|
|
147
204
|
/**
|
|
148
|
-
* Registers a handler for token updates (requested or parent-pushed).
|
|
205
|
+
* Registers a handler for auth-token updates (requested or parent-pushed).
|
|
149
206
|
*
|
|
150
207
|
* @returns Unsubscribe function.
|
|
151
208
|
*/
|
|
152
|
-
|
|
209
|
+
onAuthTokenRefresh(handler: (authToken: AuthToken) => void): () => void;
|
|
210
|
+
/**
|
|
211
|
+
* Notifies the parent that the extension's route changed.
|
|
212
|
+
*
|
|
213
|
+
* Wire: `NAVIGATION_CHANGE` (fire-and-forget).
|
|
214
|
+
*
|
|
215
|
+
* @param payload - Navigation change payload (`path` within the extension).
|
|
216
|
+
* @throws When not connected or destroyed.
|
|
217
|
+
*/
|
|
218
|
+
emitNavigationChange(payload: NavigationChangePayload): void;
|
|
153
219
|
}
|
|
154
220
|
|
|
155
221
|
/** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */
|
|
@@ -157,6 +223,11 @@ export declare class InvalidOriginError extends Error {
|
|
|
157
223
|
constructor(message: string);
|
|
158
224
|
}
|
|
159
225
|
|
|
226
|
+
/** Payload for `NAVIGATION_CHANGE` (child → parent). */
|
|
227
|
+
export declare interface NavigationChangePayload {
|
|
228
|
+
path: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
160
231
|
/** Opaque page-level settings bag owned by the extension and synced with the parent. */
|
|
161
232
|
export declare type PageSettings = Record<string, unknown>;
|
|
162
233
|
|
|
@@ -191,16 +262,9 @@ export declare interface RequestOptions {
|
|
|
191
262
|
*/
|
|
192
263
|
export declare function resolveAllowedOrigin(tenantUrl: string | null): string;
|
|
193
264
|
|
|
194
|
-
/**
|
|
195
|
-
export declare interface
|
|
196
|
-
|
|
197
|
-
/** Unix expiry seconds as a string, per parent payload. */
|
|
198
|
-
expires: string;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Payload for `TOKEN_REFRESH` (requested or parent-pushed). */
|
|
202
|
-
export declare interface TokenRefreshPayload {
|
|
203
|
-
authToken: ScopedExtensionToken;
|
|
265
|
+
/** Payload for `UPDATE_PAGE_SETTINGS` (child → parent). */
|
|
266
|
+
export declare interface UpdatePageSettingsPayload {
|
|
267
|
+
settings: PageSettings;
|
|
204
268
|
}
|
|
205
269
|
|
|
206
270
|
export { }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kadanza/extension-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Helpers for building extensions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kadanza",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"prepublishOnly": "npm run build && npm run check:types"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
+
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
|
49
50
|
"typedoc": "^0.28.20",
|
|
50
51
|
"typescript": "~6.0.2",
|
|
51
52
|
"vite": "^8.1.1",
|