@kadanza/extension-sdk 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,53 +1,47 @@
1
1
  # @kadanza/extension-sdk
2
2
 
3
- Helpers for building Kadanza extensions embedded in the parent app via iframe `postMessage`.
3
+ Helpers for building Kadanza extensions
4
4
 
5
- ## Install
5
+ ## Get started
6
+
7
+ ### Install
6
8
 
7
9
  ```bash
8
10
  npm install @kadanza/extension-sdk
9
11
  ```
10
12
 
11
- ## Usage
12
-
13
- ### ESM
13
+ ### Usage
14
14
 
15
15
  ```ts
16
+ // For ESM
16
17
  import { createExtensionSDK, type IExtensionSDK } from "@kadanza/extension-sdk";
18
+ // For CommonJS
19
+ // const { createExtensionSDK } = require("@kadanza/extension-sdk");
17
20
 
18
21
  const sdk: IExtensionSDK = createExtensionSDK();
19
22
 
23
+ // Handshake flow
20
24
  const { authToken, extensionDetails, designTokens } = await sdk.connect();
21
-
22
- sdk.onLoadPageSettings((settings) => {
23
- // open settings UI with values from parent
24
- console.log(settings);
25
- });
26
-
27
- const refreshed = await sdk.requestTokenRefresh();
28
- console.log(refreshed.jwt);
29
-
30
- await sdk.updatePageSettings({ setting1: 123 });
31
25
  ```
32
26
 
33
- ### CommonJS
27
+ Check [Flows.md](/docs/flows.md) for all available flows.
34
28
 
35
- ```js
36
- const { createExtensionSDK } = require("@kadanza/extension-sdk");
29
+ ## Documentation
37
30
 
38
- const sdk = createExtensionSDK();
39
-
40
- sdk.connect().then(({ authToken }) => {
41
- console.log(authToken.jwt);
42
- });
43
- ```
44
-
45
- The parent must pass `tenantUrl` as a search param on the iframe `src`. See [docs/communication-process.md](docs/communication-process.md).
31
+ - [Documentation folder](docs/) — This is were the important information is stored
32
+ - [API reference](https://kadanza-io.github.io/extension-sdk/) — TypeDoc generated documentation describing all the small details, classes, interfaces, payloads...
46
33
 
47
34
  ## Local development
48
35
 
49
36
  ```bash
50
37
  npm install
51
- npm run dev # local playground/ demo
52
- npm run build # emit dist/ (ESM + CJS + types)
53
38
  ```
39
+
40
+ | Command | Description |
41
+ | --------------------- | ---------------------------------------------------- |
42
+ | `npm run dev` | Serve the playground for embedding in the parent app |
43
+ | `npm run build` | Emit `dist/` (ESM + CJS + types) |
44
+ | `npm run preview` | Preview the production playground build |
45
+ | `npm run docs` | Generate HTML API docs under `docs/api/` |
46
+ | `npm run docs:check` | Validate TypeDoc without writing output |
47
+ | `npm run check:types` | Check published package types with arethetypeswrong |
@@ -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":["export const CONNECTION_EVENTS = {\n // Child -> Parent\n handshakeInit: \"HANDSHAKE_INIT\",\n // Parent -> Child\n handshakeAck: \"HANDSHAKE_ACK\",\n // Child -> Parent\n requestTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n // Parent -> Child\n tokenRefresh: \"TOKEN_REFRESH\",\n // Parent -> Child\n loadPageSettings: \"LOAD_PAGE_SETTINGS\",\n // Child -> Parent\n updatePageSettings: \"UPDATE_PAGE_SETTINGS\",\n // Parent -> Child\n pageSettingsUpdated: \"PAGE_SETTINGS_UPDATED\",\n} as const;\n\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\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 */\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\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\nexport interface IExtensionSDK {\n connect(options?: ConnectOptions): Promise<HandshakePayload>;\n destroy(): void;\n\n readonly isConnected: boolean;\n getAuthToken(): ScopedExtensionToken | null;\n getExtensionDetails(): ExtensionDetails | null;\n getDesignTokens(): DesignTokens | null;\n getPageSettings(): PageSettings | null;\n\n requestTokenRefresh(options?: RequestOptions): Promise<ScopedExtensionToken>;\n updatePageSettings(\n settings: PageSettings,\n options?: RequestOptions,\n ): Promise<PageSettingsUpdatedPayload>;\n\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;\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\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\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","#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 +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":["export const CONNECTION_EVENTS = {\n // Child -> Parent\n handshakeInit: \"HANDSHAKE_INIT\",\n // Parent -> Child\n handshakeAck: \"HANDSHAKE_ACK\",\n // Child -> Parent\n requestTokenRefresh: \"REQUEST_TOKEN_REFRESH\",\n // Parent -> Child\n tokenRefresh: \"TOKEN_REFRESH\",\n // Parent -> Child\n loadPageSettings: \"LOAD_PAGE_SETTINGS\",\n // Child -> Parent\n updatePageSettings: \"UPDATE_PAGE_SETTINGS\",\n // Parent -> Child\n pageSettingsUpdated: \"PAGE_SETTINGS_UPDATED\",\n} as const;\n\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\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 */\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\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\nexport interface IExtensionSDK {\n connect(options?: ConnectOptions): Promise<HandshakePayload>;\n destroy(): void;\n\n readonly isConnected: boolean;\n getAuthToken(): ScopedExtensionToken | null;\n getExtensionDetails(): ExtensionDetails | null;\n getDesignTokens(): DesignTokens | null;\n getPageSettings(): PageSettings | null;\n\n requestTokenRefresh(options?: RequestOptions): Promise<ScopedExtensionToken>;\n updatePageSettings(\n settings: PageSettings,\n options?: RequestOptions,\n ): Promise<PageSettingsUpdatedPayload>;\n\n onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;\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\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\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","#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":";AAMA,IAAa,IAAoB;CAE/B,eAAe;CAEf,cAAc;CAEd,qBAAqB;CAErB,cAAc;CAEd,kBAAkB;CAElB,oBAAoB;CAEpB,qBAAqB;AACvB;;;AChBA,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;;;ACzCA,IAAM,IAAqB,KA2Fd,IAAb,MAAmD;CACjD,KAAgC;CAChC,KAAoC;CACpC,KAAa;CACb,KAAa;CAEb,KAA0C;CAC1C,KAA6C;CAC7C,KAAqC;CACrC,KAAqC;CAErC,KAAsD;CACtD,KAA6D;CAC7D,KAAyE;CAEzE,qBAA4B,IAAI,IAAsC;CACtE,qBAAwB,IAAI,IAA2C;CAEvE,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,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,KAAKH,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,KAAKC,EAAc;EACnE,SAAS,GAAO;GAMd,MALA,KAAKG,GAAc,KAAKJ,EAAiB,GACzC,KAAKA,KAAoB,MACzB,KAAKE,KAAe,GACpB,KAAKA,KAAe,MACpB,KAAKD,KAAiB,MAChB;EACR;EAEA,OAAO;CACT;CAEA,UAAgB;EAqBd,AApBA,KAAKI,KAAa,IAClB,KAAKX,KAAa,IAElB,KAAKQ,KAAe,GACpB,KAAKA,KAAe,MACpB,KAAKD,KAAiB,MAEtB,KAAKK,GAAe,KAAKN,IAAmB,gBAAI,MAAM,gBAAgB,CAAC,GACvE,KAAKM,GAAe,KAAKC,IAAsB,gBAAI,MAAM,gBAAgB,CAAC,GAC1E,KAAKD,GAAe,KAAKE,IAA4B,gBAAI,MAAM,gBAAgB,CAAC,GAChF,KAAKR,KAAoB,MACzB,KAAKO,KAAuB,MAC5B,KAAKC,KAA6B,MAElC,KAAKC,GAA0B,MAAM,GACrC,KAAKC,GAAsB,MAAM,GAEjC,KAAKd,KAAa,MAClB,KAAKC,KAAoB,MACzB,KAAKC,KAAgB,MACrB,KAAKC,KAAgB;CACvB;CAEA,eAA4C;EAC1C,OAAO,KAAKH;CACd;CAEA,sBAA+C;EAC7C,OAAO,KAAKC;CACd;CAEA,kBAAuC;EACrC,OAAO,KAAKC;CACd;CAEA,kBAAuC;EACrC,OAAO,KAAKC;CACd;CAEA,MAAM,oBACJ,IAA0B,CAAC,GACI;EAG/B,IAFA,KAAKY,GAAiB,GAElB,KAAKJ,IACP,MAAU,MAAM,oCAAoC;EAGtD,IAAM,IAAY,EAAQ,aAAa,GACjC,IAAgB,KAAKN,IAErB,IAAU,IAAI,SAA+B,GAAS,MAAW;GACrE,IAAM,IAAQ,iBAAiB;IAE7B,AADA,KAAKM,KAAuB,MAC5B,EAAO,gBAAI,MAAM,iCAAiC,EAAU,IAAI,CAAC;GACnE,GAAG,CAAS;GAEZ,KAAKA,KAAuB;IAAE;IAAS;IAAQ;GAAM;EACvD,CAAC;EAGD,OADA,EAAa,EAAkB,qBAAqB,CAAa,GAC1D;CACT;CAEA,MAAM,mBACJ,GACA,IAA0B,CAAC,GACU;EAGrC,IAFA,KAAKI,GAAiB,GAElB,KAAKH,IACP,MAAU,MAAM,2CAA2C;EAG7D,IAAM,IAAY,EAAQ,aAAa,GACjC,IAAgB,KAAKP,IAErB,IAAU,IAAI,SAAqC,GAAS,MAAW;GAC3E,IAAM,IAAQ,iBAAiB;IAE7B,AADA,KAAKO,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,CAAQ,GACnE;CACT;CAEA,mBAAmB,GAAuD;EAExE,OADA,KAAKC,GAA0B,IAAI,CAAO,SAC7B;GACX,KAAKA,GAA0B,OAAO,CAAO;EAC/C;CACF;CAEA,eAAe,GAA4D;EAEzE,OADA,KAAKC,GAAsB,IAAI,CAAO,SACzB;GACX,KAAKA,GAAsB,OAAO,CAAO;EAC3C;CACF;CAEA,GAAW,GAA6C;EACtD,IAAM,EAAE,SAAM,eAAY,EAAM;EAEhC,QAAQ,GAAR;GACE,KAAK,EAAkB;IACrB,KAAKE,GAAoB,CAAuC;IAChE;GACF,KAAK,EAAkB;IACrB,KAAKC,GAAoB,CAA0C;IACnE;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,KAAKN,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;EAErB,AADA,KAAKA,KAAoB,MACzB,KAAKgB,GAAgB,GAAS;GAC5B,WAAW,KAAKpB;GAChB,kBAAkB,KAAKC;GACvB,cAAc,KAAKC;GACnB,cAAc,KAAKC;EACrB,CAAC;CACH;CAEA,GAAoB,GAAgD;EAClE,IAAI,CAAC,GAAS,WAAW;GAKvB,AAJA,KAAKO,GACH,KAAKC,IACL,gBAAI,MAAM,gCAAgC,CAC5C,GACA,KAAKA,KAAuB;GAC5B;EACF;EAEA,KAAKX,KAAa,EAAQ;EAE1B,IAAM,IAAU,KAAKW;EAErB,AADA,KAAKA,KAAuB,MAC5B,KAAKS,GAAgB,GAAS,KAAKpB,EAAU;EAE7C,KAAK,IAAM,KAAW,KAAKc,IACzB,EAAQ,KAAKd,EAAU;CAE3B;CAEA,GAAwB,GAAyC;EAC/D,IAAM,IAAW,KAAW,CAAC;EAC7B,KAAKG,KAAgB;EAErB,KAAK,IAAM,KAAW,KAAKU,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,KAAKX,IACP,MAAU,MAAM,yBAAyB;CAE7C;CAEA,KAAyB;EAEvB,IADA,KAAKV,GAAoB,GACrB,CAAC,KAAKD,MAAc,CAAC,KAAKO,IAC5B,MAAU,MAAM,6CAA6C;CAEjE;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,GC1Ya,UACJ,IAAI,EAAa"}
package/dist/index.d.cts CHANGED
@@ -1,42 +1,65 @@
1
+ /**
2
+ * Wire event type strings for parent/child `postMessage` communication.
3
+ *
4
+ * Keys are SDK-facing names; values are the strings sent on the wire.
5
+ * Comments mark message direction relative to the extension (child).
6
+ */
1
7
  export declare const CONNECTION_EVENTS: {
8
+ /** Child → parent: start handshake. */
2
9
  readonly handshakeInit: "HANDSHAKE_INIT";
10
+ /** Parent → child: handshake success with auth and context. */
3
11
  readonly handshakeAck: "HANDSHAKE_ACK";
12
+ /** Child → parent: request a new auth token. */
4
13
  readonly requestTokenRefresh: "REQUEST_TOKEN_REFRESH";
14
+ /** Parent → child: new auth token (reply or push). */
5
15
  readonly tokenRefresh: "TOKEN_REFRESH";
16
+ /** Parent → child: open/load page settings UI with values. */
6
17
  readonly loadPageSettings: "LOAD_PAGE_SETTINGS";
18
+ /** Child → parent: persist updated page settings. */
7
19
  readonly updatePageSettings: "UPDATE_PAGE_SETTINGS";
20
+ /** Parent → child: result of a page settings update. */
8
21
  readonly pageSettingsUpdated: "PAGE_SETTINGS_UPDATED";
9
22
  };
10
23
 
24
+ /** Union of all {@link CONNECTION_EVENTS} wire string values. */
11
25
  export declare type ConnectionEvent = (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS];
12
26
 
27
+ /** Options for {@link ExtensionSDK.connect} / handshake. */
13
28
  export declare interface ConnectOptions {
29
+ /** Handshake timeout in milliseconds (default 10_000). */
14
30
  timeoutMs?: number;
15
31
  }
16
32
 
33
+ /** Creates a new {@link IExtensionSDK} instance for the current extension frame. */
17
34
  export declare const createExtensionSDK: () => IExtensionSDK;
18
35
 
36
+ /** Branding tokens from the parent for aligning extension UI. */
19
37
  export declare interface DesignTokens {
20
38
  primaryColor: string;
21
39
  fontFamily: string;
22
40
  borderRadius: string;
23
41
  }
24
42
 
43
+ /** Context about the embedded extension and its host page from handshake. */
25
44
  export declare interface ExtensionDetails {
26
45
  extensionId: string;
27
46
  tenantId: string;
28
47
  tenantDomain: string;
48
+ /** Base URL of the parent application. */
29
49
  baseUrl: string;
30
50
  spaceId: string;
31
51
  pageId: string;
52
+ /** BCP 47 locale from the parent. */
32
53
  locale: string;
33
54
  }
34
55
 
56
+ /** Envelope for parent/child `postMessage` traffic. */
35
57
  export declare interface ExtensionMessage<TPayload = unknown> {
36
58
  type: ConnectionEvent | string;
37
59
  payload?: TPayload;
38
60
  }
39
61
 
62
+ /** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */
40
63
  export declare class ExtensionSDK implements IExtensionSDK {
41
64
  #private;
42
65
  get isConnected(): boolean;
@@ -52,6 +75,7 @@ export declare class ExtensionSDK implements IExtensionSDK {
52
75
  onTokenRefresh(handler: (token: ScopedExtensionToken) => void): () => void;
53
76
  }
54
77
 
78
+ /** Payload delivered with a successful `HANDSHAKE_ACK`. */
55
79
  export declare interface HandshakePayload {
56
80
  authToken: ScopedExtensionToken;
57
81
  extensionDetails: ExtensionDetails;
@@ -59,39 +83,111 @@ export declare interface HandshakePayload {
59
83
  pageSettings: PageSettings | null;
60
84
  }
61
85
 
86
+ /**
87
+ * Public contract for talking to the Kadanza parent frame over `postMessage`.
88
+ *
89
+ * Requires a `tenantUrl` search param on the extension URL so
90
+ * {@link connect} can validate the parent origin.
91
+ */
62
92
  export declare interface IExtensionSDK {
93
+ /**
94
+ * Establishes the parent connection and waits for `HANDSHAKE_ACK`.
95
+ *
96
+ * If already connected with cached handshake data, returns that payload
97
+ * without sending another init. Concurrent calls while a handshake is
98
+ * in progress throw.
99
+ *
100
+ * @param options - Optional handshake timeout (default 10s).
101
+ * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
102
+ * @throws When not embedded in a parent frame, already handshaking,
103
+ * destroyed, or the handshake times out / returns an invalid payload.
104
+ */
63
105
  connect(options?: ConnectOptions): Promise<HandshakePayload>;
106
+ /**
107
+ * Tears down listeners and cached state. Rejects any in-flight requests.
108
+ * The instance cannot be reused after destroy.
109
+ */
64
110
  destroy(): void;
111
+ /** Whether a successful handshake has completed and not been destroyed. */
65
112
  readonly isConnected: boolean;
113
+ /** Last auth token from handshake or refresh; `null` until connected. */
66
114
  getAuthToken(): ScopedExtensionToken | null;
115
+ /** Extension context from handshake; `null` until connected. */
67
116
  getExtensionDetails(): ExtensionDetails | null;
117
+ /** Design tokens from handshake; `null` until connected. */
68
118
  getDesignTokens(): DesignTokens | null;
119
+ /**
120
+ * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;
121
+ * `null` until set.
122
+ */
69
123
  getPageSettings(): PageSettings | null;
124
+ /**
125
+ * Asks the parent for a new token and waits for `TOKEN_REFRESH`.
126
+ *
127
+ * @param options - Optional request timeout (default 10s).
128
+ * @throws When not connected, a refresh is already in progress,
129
+ * or the request times out / returns an invalid payload.
130
+ */
70
131
  requestTokenRefresh(options?: RequestOptions): Promise<ScopedExtensionToken>;
132
+ /**
133
+ * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.
134
+ *
135
+ * @param settings - Settings object to send.
136
+ * @param options - Optional request timeout (default 10s).
137
+ * @throws When not connected, an update is already in progress,
138
+ * or the request times out / returns an invalid payload.
139
+ */
71
140
  updatePageSettings(settings: PageSettings, options?: RequestOptions): Promise<PageSettingsUpdatedPayload>;
141
+ /**
142
+ * Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.
143
+ *
144
+ * @returns Unsubscribe function.
145
+ */
72
146
  onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;
147
+ /**
148
+ * Registers a handler for token updates (requested or parent-pushed).
149
+ *
150
+ * @returns Unsubscribe function.
151
+ */
73
152
  onTokenRefresh(handler: (token: ScopedExtensionToken) => void): () => void;
74
153
  }
75
154
 
155
+ /** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */
76
156
  export declare class InvalidOriginError extends Error {
77
157
  constructor(message: string);
78
158
  }
79
159
 
160
+ /** Opaque page-level settings bag owned by the extension and synced with the parent. */
80
161
  export declare type PageSettings = Record<string, unknown>;
81
162
 
163
+ /** Payload for `PAGE_SETTINGS_UPDATED` after a page settings update request. */
82
164
  export declare interface PageSettingsUpdatedPayload {
83
165
  success: boolean;
84
166
  }
85
167
 
168
+ /**
169
+ * Reads the parent-supplied `tenantUrl` from a URL search string.
170
+ *
171
+ * The parent must pass `tenantUrl` on the iframe `src` so
172
+ * {@link resolveAllowedOrigin} can whitelist the parent origin.
173
+ *
174
+ * @param search - Query string (defaults to `window.location.search`).
175
+ */
86
176
  export declare function readTenantUrlFromLocation(search?: string): string | null;
87
177
 
178
+ /** Options for request/response SDK methods that wait on a parent reply. */
88
179
  export declare interface RequestOptions {
180
+ /** Request timeout in milliseconds (default 10_000). */
89
181
  timeoutMs?: number;
90
182
  }
91
183
 
92
184
  /**
93
185
  * Parses and validates a tenant URL into a postMessage-safe origin.
94
186
  * Allows https always; http only for localhost / 127.0.0.1 / [::1].
187
+ *
188
+ * @param tenantUrl - Absolute URL from the `tenantUrl` search param.
189
+ * @returns Origin string suitable for `postMessage` targetOrigin.
190
+ * @throws {InvalidOriginError} When missing, unparsable, or disallowed scheme/host.
95
191
  */
96
192
  export declare function resolveAllowedOrigin(tenantUrl: string | null): string;
97
193
 
@@ -102,6 +198,7 @@ export declare interface ScopedExtensionToken {
102
198
  expires: string;
103
199
  }
104
200
 
201
+ /** Payload for `TOKEN_REFRESH` (requested or parent-pushed). */
105
202
  export declare interface TokenRefreshPayload {
106
203
  authToken: ScopedExtensionToken;
107
204
  }
package/dist/index.d.ts CHANGED
@@ -1,42 +1,65 @@
1
+ /**
2
+ * Wire event type strings for parent/child `postMessage` communication.
3
+ *
4
+ * Keys are SDK-facing names; values are the strings sent on the wire.
5
+ * Comments mark message direction relative to the extension (child).
6
+ */
1
7
  export declare const CONNECTION_EVENTS: {
8
+ /** Child → parent: start handshake. */
2
9
  readonly handshakeInit: "HANDSHAKE_INIT";
10
+ /** Parent → child: handshake success with auth and context. */
3
11
  readonly handshakeAck: "HANDSHAKE_ACK";
12
+ /** Child → parent: request a new auth token. */
4
13
  readonly requestTokenRefresh: "REQUEST_TOKEN_REFRESH";
14
+ /** Parent → child: new auth token (reply or push). */
5
15
  readonly tokenRefresh: "TOKEN_REFRESH";
16
+ /** Parent → child: open/load page settings UI with values. */
6
17
  readonly loadPageSettings: "LOAD_PAGE_SETTINGS";
18
+ /** Child → parent: persist updated page settings. */
7
19
  readonly updatePageSettings: "UPDATE_PAGE_SETTINGS";
20
+ /** Parent → child: result of a page settings update. */
8
21
  readonly pageSettingsUpdated: "PAGE_SETTINGS_UPDATED";
9
22
  };
10
23
 
24
+ /** Union of all {@link CONNECTION_EVENTS} wire string values. */
11
25
  export declare type ConnectionEvent = (typeof CONNECTION_EVENTS)[keyof typeof CONNECTION_EVENTS];
12
26
 
27
+ /** Options for {@link ExtensionSDK.connect} / handshake. */
13
28
  export declare interface ConnectOptions {
29
+ /** Handshake timeout in milliseconds (default 10_000). */
14
30
  timeoutMs?: number;
15
31
  }
16
32
 
33
+ /** Creates a new {@link IExtensionSDK} instance for the current extension frame. */
17
34
  export declare const createExtensionSDK: () => IExtensionSDK;
18
35
 
36
+ /** Branding tokens from the parent for aligning extension UI. */
19
37
  export declare interface DesignTokens {
20
38
  primaryColor: string;
21
39
  fontFamily: string;
22
40
  borderRadius: string;
23
41
  }
24
42
 
43
+ /** Context about the embedded extension and its host page from handshake. */
25
44
  export declare interface ExtensionDetails {
26
45
  extensionId: string;
27
46
  tenantId: string;
28
47
  tenantDomain: string;
48
+ /** Base URL of the parent application. */
29
49
  baseUrl: string;
30
50
  spaceId: string;
31
51
  pageId: string;
52
+ /** BCP 47 locale from the parent. */
32
53
  locale: string;
33
54
  }
34
55
 
56
+ /** Envelope for parent/child `postMessage` traffic. */
35
57
  export declare interface ExtensionMessage<TPayload = unknown> {
36
58
  type: ConnectionEvent | string;
37
59
  payload?: TPayload;
38
60
  }
39
61
 
62
+ /** Default {@link IExtensionSDK} implementation. Prefer {@link createExtensionSDK}. */
40
63
  export declare class ExtensionSDK implements IExtensionSDK {
41
64
  #private;
42
65
  get isConnected(): boolean;
@@ -52,6 +75,7 @@ export declare class ExtensionSDK implements IExtensionSDK {
52
75
  onTokenRefresh(handler: (token: ScopedExtensionToken) => void): () => void;
53
76
  }
54
77
 
78
+ /** Payload delivered with a successful `HANDSHAKE_ACK`. */
55
79
  export declare interface HandshakePayload {
56
80
  authToken: ScopedExtensionToken;
57
81
  extensionDetails: ExtensionDetails;
@@ -59,39 +83,111 @@ export declare interface HandshakePayload {
59
83
  pageSettings: PageSettings | null;
60
84
  }
61
85
 
86
+ /**
87
+ * Public contract for talking to the Kadanza parent frame over `postMessage`.
88
+ *
89
+ * Requires a `tenantUrl` search param on the extension URL so
90
+ * {@link connect} can validate the parent origin.
91
+ */
62
92
  export declare interface IExtensionSDK {
93
+ /**
94
+ * Establishes the parent connection and waits for `HANDSHAKE_ACK`.
95
+ *
96
+ * If already connected with cached handshake data, returns that payload
97
+ * without sending another init. Concurrent calls while a handshake is
98
+ * in progress throw.
99
+ *
100
+ * @param options - Optional handshake timeout (default 10s).
101
+ * @throws {InvalidOriginError} When `tenantUrl` is missing or invalid.
102
+ * @throws When not embedded in a parent frame, already handshaking,
103
+ * destroyed, or the handshake times out / returns an invalid payload.
104
+ */
63
105
  connect(options?: ConnectOptions): Promise<HandshakePayload>;
106
+ /**
107
+ * Tears down listeners and cached state. Rejects any in-flight requests.
108
+ * The instance cannot be reused after destroy.
109
+ */
64
110
  destroy(): void;
111
+ /** Whether a successful handshake has completed and not been destroyed. */
65
112
  readonly isConnected: boolean;
113
+ /** Last auth token from handshake or refresh; `null` until connected. */
66
114
  getAuthToken(): ScopedExtensionToken | null;
115
+ /** Extension context from handshake; `null` until connected. */
67
116
  getExtensionDetails(): ExtensionDetails | null;
117
+ /** Design tokens from handshake; `null` until connected. */
68
118
  getDesignTokens(): DesignTokens | null;
119
+ /**
120
+ * Latest page settings from handshake or `LOAD_PAGE_SETTINGS`;
121
+ * `null` until set.
122
+ */
69
123
  getPageSettings(): PageSettings | null;
124
+ /**
125
+ * Asks the parent for a new token and waits for `TOKEN_REFRESH`.
126
+ *
127
+ * @param options - Optional request timeout (default 10s).
128
+ * @throws When not connected, a refresh is already in progress,
129
+ * or the request times out / returns an invalid payload.
130
+ */
70
131
  requestTokenRefresh(options?: RequestOptions): Promise<ScopedExtensionToken>;
132
+ /**
133
+ * Pushes page settings to the parent and waits for `PAGE_SETTINGS_UPDATED`.
134
+ *
135
+ * @param settings - Settings object to send.
136
+ * @param options - Optional request timeout (default 10s).
137
+ * @throws When not connected, an update is already in progress,
138
+ * or the request times out / returns an invalid payload.
139
+ */
71
140
  updatePageSettings(settings: PageSettings, options?: RequestOptions): Promise<PageSettingsUpdatedPayload>;
141
+ /**
142
+ * Registers a handler for parent-initiated `LOAD_PAGE_SETTINGS`.
143
+ *
144
+ * @returns Unsubscribe function.
145
+ */
72
146
  onLoadPageSettings(handler: (settings: PageSettings) => void): () => void;
147
+ /**
148
+ * Registers a handler for token updates (requested or parent-pushed).
149
+ *
150
+ * @returns Unsubscribe function.
151
+ */
73
152
  onTokenRefresh(handler: (token: ScopedExtensionToken) => void): () => void;
74
153
  }
75
154
 
155
+ /** Thrown when `tenantUrl` is missing or not a safe postMessage origin. */
76
156
  export declare class InvalidOriginError extends Error {
77
157
  constructor(message: string);
78
158
  }
79
159
 
160
+ /** Opaque page-level settings bag owned by the extension and synced with the parent. */
80
161
  export declare type PageSettings = Record<string, unknown>;
81
162
 
163
+ /** Payload for `PAGE_SETTINGS_UPDATED` after a page settings update request. */
82
164
  export declare interface PageSettingsUpdatedPayload {
83
165
  success: boolean;
84
166
  }
85
167
 
168
+ /**
169
+ * Reads the parent-supplied `tenantUrl` from a URL search string.
170
+ *
171
+ * The parent must pass `tenantUrl` on the iframe `src` so
172
+ * {@link resolveAllowedOrigin} can whitelist the parent origin.
173
+ *
174
+ * @param search - Query string (defaults to `window.location.search`).
175
+ */
86
176
  export declare function readTenantUrlFromLocation(search?: string): string | null;
87
177
 
178
+ /** Options for request/response SDK methods that wait on a parent reply. */
88
179
  export declare interface RequestOptions {
180
+ /** Request timeout in milliseconds (default 10_000). */
89
181
  timeoutMs?: number;
90
182
  }
91
183
 
92
184
  /**
93
185
  * Parses and validates a tenant URL into a postMessage-safe origin.
94
186
  * Allows https always; http only for localhost / 127.0.0.1 / [::1].
187
+ *
188
+ * @param tenantUrl - Absolute URL from the `tenantUrl` search param.
189
+ * @returns Origin string suitable for `postMessage` targetOrigin.
190
+ * @throws {InvalidOriginError} When missing, unparsable, or disallowed scheme/host.
95
191
  */
96
192
  export declare function resolveAllowedOrigin(tenantUrl: string | null): string;
97
193
 
@@ -102,6 +198,7 @@ export declare interface ScopedExtensionToken {
102
198
  expires: string;
103
199
  }
104
200
 
201
+ /** Payload for `TOKEN_REFRESH` (requested or parent-pushed). */
105
202
  export declare interface TokenRefreshPayload {
106
203
  authToken: ScopedExtensionToken;
107
204
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadanza/extension-sdk",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Helpers for building extensions",
5
5
  "keywords": [
6
6
  "kadanza",
@@ -40,10 +40,13 @@
40
40
  "dev": "vite",
41
41
  "build": "tsc -p tsconfig.lib.json --noEmit && vite build",
42
42
  "preview": "vite preview",
43
+ "docs": "typedoc",
44
+ "docs:check": "typedoc --emit none",
43
45
  "check:types": "npx --yes @arethetypeswrong/cli --pack .",
44
46
  "prepublishOnly": "npm run build && npm run check:types"
45
47
  },
46
48
  "devDependencies": {
49
+ "typedoc": "^0.28.20",
47
50
  "typescript": "~6.0.2",
48
51
  "vite": "^8.1.1",
49
52
  "vite-plugin-dts": "^4.5.4"