@basis-theory/web-elements 3.0.0-beta.6 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { E as ElementError, c as createIframe, a as createPostMessageClient, b as bindCoordinatorPort, d as createEventDispatcher, l as log, e as createMounter } from "./index-DPhSs28B.js";
1
+ import { E as ElementError, c as createIframe, a as createPostMessageClient, b as bindCoordinatorPort, d as createEventDispatcher, l as log, e as createMounter } from "./index-DIVQdm8b.js";
2
2
  const DEFAULT_CARD_STACK_THRESHOLD = 400;
3
3
  const createCardElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount, coordinatorRequest) => {
4
4
  let iframe = null;
@@ -1 +1 @@
1
- {"version":3,"file":"create-card-C0MwL3Ou.js","sources":["../src/elements/create-card.ts"],"sourcesContent":["/**\n * Card Element Factory (Unified)\n */\n\nimport type { PostMessageClient } from '@basis-theory-elements/postmessage';\nimport { createPostMessageClient } from '@basis-theory-elements/postmessage';\nimport { ElementError, log } from '@basis-theory-elements/shared';\nimport { createMounter } from '../mounter/create-mounter';\nimport type {\n CardElement,\n ElementMounter,\n ElementOptions,\n EventListener,\n EventType,\n SDKConfig,\n SubElementRef,\n} from '../types';\nimport type { CoordinatorRequest } from '../utils/bind-coordinator-port';\nimport { bindCoordinatorPort } from '../utils/bind-coordinator-port';\nimport { createIframe } from '../utils/create-iframe';\nimport { createEventDispatcher } from '../utils/create-event-dispatcher';\n\n/**\n * Default stack threshold in pixels for CardElement auto-layout\n * When container width is below this threshold, card inputs stack vertically\n */\nconst DEFAULT_CARD_STACK_THRESHOLD = 400;\n\n/**\n * Card element options\n */\nexport interface CardElementOptions {\n /** CSS styling */\n style?: Record<string, any>;\n\n /** Placeholder text for each sub-field */\n placeholder?: {\n cardNumber?: string;\n expiryDate?: string;\n cvc?: string;\n };\n\n /** Accessibility label */\n ariaLabel?: string;\n\n /** Disabled state (applies to all 3 sub-fields) */\n disabled?: boolean;\n\n /** Read-only state (applies to all 3 sub-fields) */\n readOnly?: boolean;\n\n /** Layout mode */\n layout?: 'row' | 'column' | 'auto';\n\n /** Stack threshold in pixels (only applies when layout: 'auto') */\n stackAt?: number;\n\n /** Icon position */\n iconPosition?: 'left' | 'right' | 'none';\n\n /** Restrict accepted card brands */\n cardBrands?: string[];\n\n /**\n * BIN enrichment for the card number field. Setting `coBadge` turns enrichment on and\n * overrides this — an explicit `binLookup: false` is ignored when `coBadge` is set.\n */\n binLookup?: boolean | { enabled?: boolean; debounceMs?: number };\n\n /** Co-badge options; forces BIN enrichment on when set, overriding `binLookup`. */\n coBadge?: {\n preferredNetworks?: string[];\n mode?: 'auto' | 'manual';\n };\n}\n\n/**\n * Factory function to create a unified card element\n * No class, pure functional approach with closure-based state\n */\nexport const createCardElement = (\n apiKey: string,\n sdkConfig: SDKConfig,\n options: CardElementOptions = {},\n coordinatorElementId: string,\n ensureCoordinatorReady: () => Promise<void>,\n notifyCoordinatorElementUnmount: (elementId: string) => Promise<void>, // ← Notify coordinator on unmount\n coordinatorRequest: CoordinatorRequest // ← Brokers this element's coordinator port\n): CardElement => {\n // Private state (closure)\n let iframe: HTMLIFrameElement | null = null;\n let client: PostMessageClient | null = null;\n let mounter: ElementMounter | null = null;\n let isMounted = false;\n let container: HTMLElement | null = null;\n let eventDispatcher: ReturnType<typeof createEventDispatcher> | null = null;\n\n // Event target for event emission\n const eventTarget = new EventTarget();\n\n // Use coordinator-generated ID for all references\n const id = coordinatorElementId;\n const type = 'card';\n\n // Setup event dispatcher\n const setupEventListeners = () => {\n if (!client) {\n return;\n }\n\n eventDispatcher = createEventDispatcher({\n elementType: type,\n elementId: id,\n client,\n eventTarget,\n });\n\n eventDispatcher.setupSubscriptions();\n };\n\n /**\n * Create sub-element reference for tokenization\n * These refs are used in bt.tokens.create() data payload\n */\n const createSubElementRef = (subId: string): SubElementRef => ({\n id: subId,\n type: 'card',\n get mounted() {\n return isMounted;\n },\n mount: () => {\n throw ElementError('Sub-field refs cannot be mounted independently');\n },\n unmount: () => {},\n update: () => Promise.resolve(),\n focus: () => {},\n blur: () => {},\n clear: () => {},\n on: () => () => {},\n });\n\n const numberRef = createSubElementRef(`${id}__number`);\n const expiryDateRef = createSubElementRef(`${id}__expiry`);\n const cvcRef = createSubElementRef(`${id}__cvc`);\n\n // Public API (Element interface with sub-field refs)\n const cardElement = {\n id,\n type,\n get mounted() {\n return isMounted;\n },\n get loaded() {\n return isMounted;\n },\n\n // Sub-field references for tokenization\n number: numberRef,\n expiryDate: expiryDateRef,\n cvc: cvcRef,\n\n mount: async (selector: string | HTMLElement) => {\n if (isMounted) {\n throw ElementError('Element is already mounted');\n }\n\n // Resolve container\n container =\n typeof selector === 'string'\n ? document.querySelector(selector)\n : selector;\n\n if (!container) {\n throw ElementError(`Container not found: ${selector}`);\n }\n\n // Create iframe\n const parentOrigin = window.location.origin;\n const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/card.html`);\n iframeUrl.searchParams.set('parentOrigin', parentOrigin);\n iframeUrl.searchParams.set('elementId', id);\n iframeUrl.searchParams.set(\n 'measurePerformance',\n String(sdkConfig.measurePerformance || false)\n );\n iframeUrl.searchParams.set('debug', String(sdkConfig.debug || false));\n\n iframe = createIframe({\n src: iframeUrl.toString(),\n sandbox: 'allow-scripts allow-same-origin',\n title: options.ariaLabel || 'Card Input',\n });\n\n // Add element ID and theme mode attributes\n iframe.setAttribute('data-bt-element-id', id);\n iframe.setAttribute('data-theme-mode', sdkConfig.themeMode || 'auto');\n\n // Create PostMessage client\n client = createPostMessageClient(iframe, {\n targetOrigin: sdkConfig.iframeOrigin,\n timeout: sdkConfig.timeoutMs,\n retries: sdkConfig.retryConfig.maxRetries,\n debug: sdkConfig.debug,\n });\n\n // Create mounter\n mounter = createMounter();\n\n // Set up event listeners\n setupEventListeners();\n\n // Subscribe to 'ready' event BEFORE mounting iframe\n const readyPromise = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(\n ElementError(\n `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,\n id,\n { elementType: type, timeoutMs: sdkConfig.timeoutMs }\n )\n );\n }, sdkConfig.timeoutMs);\n\n const handleReady = () => {\n clearTimeout(timeout);\n resolve();\n };\n\n eventTarget.addEventListener('ready', handleReady, { once: true });\n });\n\n // Mount iframe to DOM (synchronous)\n mounter.mount(container, iframe, {\n timeoutMs: sdkConfig.timeoutMs,\n });\n\n isMounted = true;\n\n // Wait for coordinator + configuration to complete\n try {\n // Wait for coordinator (loads Core SDK if needed)\n await ensureCoordinatorReady();\n\n // Wait for EVENT_READY\n await readyPromise;\n\n // Bind the private channel before any value can be typed; all three\n // sub-values travel over this one port.\n await bindCoordinatorPort({\n elementId: id,\n elementType: type,\n client: client!,\n coordinatorRequest,\n });\n\n // Send initial configuration with card-specific options.\n // Omit `binLookup` / `coBadge` when undefined so the iframe never receives\n // explicit `undefined` (object literals include own keys with undefined values).\n await client!.sendRequest('CMD_SET_CONFIG', {\n elementId: id,\n apiKey,\n apiTimeout: sdkConfig.timeoutMs,\n apiRetries: sdkConfig.retryConfig.maxRetries,\n theme: sdkConfig.theme,\n darkTheme: sdkConfig.darkTheme,\n themeMode: sdkConfig.themeMode,\n placeholder: options.placeholder,\n ...(options.style !== undefined && { style: options.style }),\n disabled: options.disabled,\n readOnly: options.readOnly,\n layout: options.layout || 'auto',\n stackAt: options.stackAt || DEFAULT_CARD_STACK_THRESHOLD,\n iconPosition: options.iconPosition || 'left',\n ...(options.cardBrands !== undefined && { cardBrands: options.cardBrands }),\n ...(options.binLookup !== undefined && { binLookup: options.binLookup }),\n ...(options.coBadge !== undefined && { coBadge: options.coBadge }),\n debug: sdkConfig.debug,\n });\n\n // Reveal iframe now that theme is fully applied\n if (iframe) iframe.style.opacity = '1';\n } catch (error) {\n // Dispatch error event before cleanup\n if (eventDispatcher) {\n eventDispatcher.dispatchError(\n 'MOUNT_ERROR',\n (error as Error).message,\n error as Error\n );\n }\n\n // Mount failed - clean up and mark as unmounted\n isMounted = false;\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n eventDispatcher = null;\n container = null;\n\n throw error;\n }\n },\n\n unmount: () => {\n if (!isMounted) {\n throw ElementError('Element is not mounted');\n }\n\n // PCI DSS 3.2.1: the coordinator clears every value this element owns\n // (__number, __expiry, __cvc) and releases its port.\n notifyCoordinatorElementUnmount(id).catch((error) => {\n log('Failed to notify coordinator of element unmount', {\n elementId: id,\n error: (error as Error).message,\n });\n });\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n eventDispatcher = null;\n container = null;\n isMounted = false;\n },\n\n update: async (newOptions: Partial<ElementOptions> & { themeMode?: string }) => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before updating');\n }\n\n const cardOptions = newOptions as Partial<CardElementOptions> & { themeMode?: string };\n\n // Only send fields that are present (matches create-card-number) so we never\n // overwrite iframe config with undefined/null over postMessage.\n const updatePayload: Record<string, any> = {\n elementId: id,\n };\n\n if (cardOptions.placeholder !== undefined) {\n updatePayload.placeholder = cardOptions.placeholder;\n }\n if (cardOptions.style !== undefined) {\n updatePayload.style = cardOptions.style;\n }\n if (cardOptions.disabled !== undefined) {\n updatePayload.disabled = cardOptions.disabled;\n }\n if (cardOptions.readOnly !== undefined) {\n updatePayload.readOnly = cardOptions.readOnly;\n }\n if (cardOptions.layout !== undefined) {\n updatePayload.layout = cardOptions.layout;\n }\n if (cardOptions.stackAt !== undefined) {\n updatePayload.stackAt = cardOptions.stackAt;\n }\n if (cardOptions.iconPosition !== undefined) {\n updatePayload.iconPosition = cardOptions.iconPosition;\n }\n if (cardOptions.cardBrands !== undefined) {\n updatePayload.cardBrands = cardOptions.cardBrands;\n }\n if (cardOptions.binLookup !== undefined) {\n updatePayload.binLookup = cardOptions.binLookup;\n }\n if (cardOptions.coBadge !== undefined) {\n updatePayload.coBadge = cardOptions.coBadge;\n }\n\n if (newOptions.themeMode !== undefined) {\n updatePayload.themeMode = newOptions.themeMode;\n updatePayload.theme = sdkConfig.theme;\n updatePayload.darkTheme = sdkConfig.darkTheme;\n }\n\n await client.sendRequest('CMD_UPDATE_CONFIG', updatePayload);\n },\n\n focus: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before focusing');\n }\n\n client.sendRequest('CMD_FOCUS', {});\n },\n\n blur: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before blurring');\n }\n\n client.sendRequest('CMD_BLUR', {});\n },\n\n clear: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before clearing');\n }\n\n client.sendRequest('CMD_CLEAR', {});\n },\n\n on: (eventType: EventType, listener: EventListener) => {\n eventTarget.addEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n\n // Return unsubscribe function\n return () =>\n eventTarget.removeEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n },\n\n setNetwork: async (network: string) => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before setNetwork');\n }\n await client.sendRequest('CMD_SET_NETWORK', { network });\n },\n };\n\n return cardElement as CardElement;\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-card-C94szxMV.js","sources":["../src/elements/create-card.ts"],"sourcesContent":["/**\n * Card Element Factory (Unified)\n */\n\nimport type { PostMessageClient } from '@basis-theory-elements/postmessage';\nimport { createPostMessageClient } from '@basis-theory-elements/postmessage';\nimport { ElementError, log } from '@basis-theory-elements/shared';\nimport { createMounter } from '../mounter/create-mounter';\nimport type {\n CardElement,\n ElementMounter,\n ElementOptions,\n EventListener,\n EventType,\n SDKConfig,\n SubElementRef,\n} from '../types';\nimport type { CoordinatorRequest } from '../utils/bind-coordinator-port';\nimport { bindCoordinatorPort } from '../utils/bind-coordinator-port';\nimport { createIframe } from '../utils/create-iframe';\nimport { createEventDispatcher } from '../utils/create-event-dispatcher';\n\n/**\n * Default stack threshold in pixels for CardElement auto-layout\n * When container width is below this threshold, card inputs stack vertically\n */\nconst DEFAULT_CARD_STACK_THRESHOLD = 400;\n\n/**\n * Card element options\n */\nexport interface CardElementOptions {\n /** CSS styling */\n style?: Record<string, any>;\n\n /** Placeholder text for each sub-field */\n placeholder?: {\n cardNumber?: string;\n expiryDate?: string;\n cvc?: string;\n };\n\n /** Accessibility label */\n ariaLabel?: string;\n\n /** Disabled state (applies to all 3 sub-fields) */\n disabled?: boolean;\n\n /** Read-only state (applies to all 3 sub-fields) */\n readOnly?: boolean;\n\n /** Layout mode */\n layout?: 'row' | 'column' | 'auto';\n\n /** Stack threshold in pixels (only applies when layout: 'auto') */\n stackAt?: number;\n\n /** Icon position */\n iconPosition?: 'left' | 'right' | 'none';\n\n /** Restrict accepted card brands */\n cardBrands?: string[];\n\n /**\n * BIN enrichment for the card number field. Setting `coBadge` turns enrichment on and\n * overrides this — an explicit `binLookup: false` is ignored when `coBadge` is set.\n */\n binLookup?: boolean | { enabled?: boolean; debounceMs?: number };\n\n /** Co-badge options; forces BIN enrichment on when set, overriding `binLookup`. */\n coBadge?: {\n preferredNetworks?: string[];\n mode?: 'auto' | 'manual';\n };\n}\n\n/**\n * Factory function to create a unified card element\n * No class, pure functional approach with closure-based state\n */\nexport const createCardElement = (\n apiKey: string,\n sdkConfig: SDKConfig,\n options: CardElementOptions = {},\n coordinatorElementId: string,\n ensureCoordinatorReady: () => Promise<void>,\n notifyCoordinatorElementUnmount: (elementId: string) => Promise<void>, // ← Notify coordinator on unmount\n coordinatorRequest: CoordinatorRequest // ← Brokers this element's coordinator port\n): CardElement => {\n // Private state (closure)\n let iframe: HTMLIFrameElement | null = null;\n let client: PostMessageClient | null = null;\n let mounter: ElementMounter | null = null;\n let isMounted = false;\n let container: HTMLElement | null = null;\n let eventDispatcher: ReturnType<typeof createEventDispatcher> | null = null;\n\n // Event target for event emission\n const eventTarget = new EventTarget();\n\n // Use coordinator-generated ID for all references\n const id = coordinatorElementId;\n const type = 'card';\n\n // Setup event dispatcher\n const setupEventListeners = () => {\n if (!client) {\n return;\n }\n\n eventDispatcher = createEventDispatcher({\n elementType: type,\n elementId: id,\n client,\n eventTarget,\n });\n\n eventDispatcher.setupSubscriptions();\n };\n\n /**\n * Create sub-element reference for tokenization\n * These refs are used in bt.tokens.create() data payload\n */\n const createSubElementRef = (subId: string): SubElementRef => ({\n id: subId,\n type: 'card',\n get mounted() {\n return isMounted;\n },\n mount: () => {\n throw ElementError('Sub-field refs cannot be mounted independently');\n },\n unmount: () => {},\n update: () => Promise.resolve(),\n focus: () => {},\n blur: () => {},\n clear: () => {},\n on: () => () => {},\n });\n\n const numberRef = createSubElementRef(`${id}__number`);\n const expiryDateRef = createSubElementRef(`${id}__expiry`);\n const cvcRef = createSubElementRef(`${id}__cvc`);\n\n // Public API (Element interface with sub-field refs)\n const cardElement = {\n id,\n type,\n get mounted() {\n return isMounted;\n },\n get loaded() {\n return isMounted;\n },\n\n // Sub-field references for tokenization\n number: numberRef,\n expiryDate: expiryDateRef,\n cvc: cvcRef,\n\n mount: async (selector: string | HTMLElement) => {\n if (isMounted) {\n throw ElementError('Element is already mounted');\n }\n\n // Resolve container\n container =\n typeof selector === 'string'\n ? document.querySelector(selector)\n : selector;\n\n if (!container) {\n throw ElementError(`Container not found: ${selector}`);\n }\n\n // Create iframe\n const parentOrigin = window.location.origin;\n const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/card.html`);\n iframeUrl.searchParams.set('parentOrigin', parentOrigin);\n iframeUrl.searchParams.set('elementId', id);\n iframeUrl.searchParams.set(\n 'measurePerformance',\n String(sdkConfig.measurePerformance || false)\n );\n iframeUrl.searchParams.set('debug', String(sdkConfig.debug || false));\n\n iframe = createIframe({\n src: iframeUrl.toString(),\n sandbox: 'allow-scripts allow-same-origin',\n title: options.ariaLabel || 'Card Input',\n });\n\n // Add element ID and theme mode attributes\n iframe.setAttribute('data-bt-element-id', id);\n iframe.setAttribute('data-theme-mode', sdkConfig.themeMode || 'auto');\n\n // Create PostMessage client\n client = createPostMessageClient(iframe, {\n targetOrigin: sdkConfig.iframeOrigin,\n timeout: sdkConfig.timeoutMs,\n retries: sdkConfig.retryConfig.maxRetries,\n debug: sdkConfig.debug,\n });\n\n // Create mounter\n mounter = createMounter();\n\n // Set up event listeners\n setupEventListeners();\n\n // Subscribe to 'ready' event BEFORE mounting iframe\n const readyPromise = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(\n ElementError(\n `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,\n id,\n { elementType: type, timeoutMs: sdkConfig.timeoutMs }\n )\n );\n }, sdkConfig.timeoutMs);\n\n const handleReady = () => {\n clearTimeout(timeout);\n resolve();\n };\n\n eventTarget.addEventListener('ready', handleReady, { once: true });\n });\n\n // Mount iframe to DOM (synchronous)\n mounter.mount(container, iframe, {\n timeoutMs: sdkConfig.timeoutMs,\n });\n\n isMounted = true;\n\n // Wait for coordinator + configuration to complete\n try {\n // Wait for coordinator (loads Core SDK if needed)\n await ensureCoordinatorReady();\n\n // Wait for EVENT_READY\n await readyPromise;\n\n // Bind the private channel before any value can be typed; all three\n // sub-values travel over this one port.\n await bindCoordinatorPort({\n elementId: id,\n elementType: type,\n client: client!,\n coordinatorRequest,\n });\n\n // Send initial configuration with card-specific options.\n // Omit `binLookup` / `coBadge` when undefined so the iframe never receives\n // explicit `undefined` (object literals include own keys with undefined values).\n await client!.sendRequest('CMD_SET_CONFIG', {\n elementId: id,\n apiKey,\n apiTimeout: sdkConfig.timeoutMs,\n apiRetries: sdkConfig.retryConfig.maxRetries,\n theme: sdkConfig.theme,\n darkTheme: sdkConfig.darkTheme,\n themeMode: sdkConfig.themeMode,\n placeholder: options.placeholder,\n ...(options.style !== undefined && { style: options.style }),\n disabled: options.disabled,\n readOnly: options.readOnly,\n layout: options.layout || 'auto',\n stackAt: options.stackAt || DEFAULT_CARD_STACK_THRESHOLD,\n iconPosition: options.iconPosition || 'left',\n ...(options.cardBrands !== undefined && { cardBrands: options.cardBrands }),\n ...(options.binLookup !== undefined && { binLookup: options.binLookup }),\n ...(options.coBadge !== undefined && { coBadge: options.coBadge }),\n debug: sdkConfig.debug,\n });\n\n // Reveal iframe now that theme is fully applied\n if (iframe) iframe.style.opacity = '1';\n } catch (error) {\n // Dispatch error event before cleanup\n if (eventDispatcher) {\n eventDispatcher.dispatchError(\n 'MOUNT_ERROR',\n (error as Error).message,\n error as Error\n );\n }\n\n // Mount failed - clean up and mark as unmounted\n isMounted = false;\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n eventDispatcher = null;\n container = null;\n\n throw error;\n }\n },\n\n unmount: () => {\n if (!isMounted) {\n throw ElementError('Element is not mounted');\n }\n\n // PCI DSS 3.2.1: the coordinator clears every value this element owns\n // (__number, __expiry, __cvc) and releases its port.\n notifyCoordinatorElementUnmount(id).catch((error) => {\n log('Failed to notify coordinator of element unmount', {\n elementId: id,\n error: (error as Error).message,\n });\n });\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n eventDispatcher = null;\n container = null;\n isMounted = false;\n },\n\n update: async (newOptions: Partial<ElementOptions> & { themeMode?: string }) => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before updating');\n }\n\n const cardOptions = newOptions as Partial<CardElementOptions> & { themeMode?: string };\n\n // Only send fields that are present (matches create-card-number) so we never\n // overwrite iframe config with undefined/null over postMessage.\n const updatePayload: Record<string, any> = {\n elementId: id,\n };\n\n if (cardOptions.placeholder !== undefined) {\n updatePayload.placeholder = cardOptions.placeholder;\n }\n if (cardOptions.style !== undefined) {\n updatePayload.style = cardOptions.style;\n }\n if (cardOptions.disabled !== undefined) {\n updatePayload.disabled = cardOptions.disabled;\n }\n if (cardOptions.readOnly !== undefined) {\n updatePayload.readOnly = cardOptions.readOnly;\n }\n if (cardOptions.layout !== undefined) {\n updatePayload.layout = cardOptions.layout;\n }\n if (cardOptions.stackAt !== undefined) {\n updatePayload.stackAt = cardOptions.stackAt;\n }\n if (cardOptions.iconPosition !== undefined) {\n updatePayload.iconPosition = cardOptions.iconPosition;\n }\n if (cardOptions.cardBrands !== undefined) {\n updatePayload.cardBrands = cardOptions.cardBrands;\n }\n if (cardOptions.binLookup !== undefined) {\n updatePayload.binLookup = cardOptions.binLookup;\n }\n if (cardOptions.coBadge !== undefined) {\n updatePayload.coBadge = cardOptions.coBadge;\n }\n\n if (newOptions.themeMode !== undefined) {\n updatePayload.themeMode = newOptions.themeMode;\n updatePayload.theme = sdkConfig.theme;\n updatePayload.darkTheme = sdkConfig.darkTheme;\n }\n\n await client.sendRequest('CMD_UPDATE_CONFIG', updatePayload);\n },\n\n focus: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before focusing');\n }\n\n client.sendRequest('CMD_FOCUS', {});\n },\n\n blur: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before blurring');\n }\n\n client.sendRequest('CMD_BLUR', {});\n },\n\n clear: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before clearing');\n }\n\n client.sendRequest('CMD_CLEAR', {});\n },\n\n on: (eventType: EventType, listener: EventListener) => {\n eventTarget.addEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n\n // Return unsubscribe function\n return () =>\n eventTarget.removeEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n },\n\n setNetwork: async (network: string) => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before setNetwork');\n }\n await client.sendRequest('CMD_SET_NETWORK', { network });\n },\n };\n\n return cardElement as CardElement;\n};\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { C as ConfigurationError, E as ElementError, c as createIframe, a as createPostMessageClient, b as bindCoordinatorPort, l as log, e as createMounter } from "./index-DPhSs28B.js";
1
+ import { C as ConfigurationError, E as ElementError, c as createIframe, a as createPostMessageClient, b as bindCoordinatorPort, l as log, e as createMounter } from "./index-DIVQdm8b.js";
2
2
  const SESSION_AUTH_TIMEOUT_MS = 3e4;
3
3
  const createCardDisplayElement = (_apiKey, sdkConfig, options, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount, coordinatorRequest) => {
4
4
  if (!options.tokenId || typeof options.tokenId !== "string") {
@@ -1 +1 @@
1
- {"version":3,"file":"create-card-display-DscvxIs4.js","sources":["../src/elements/create-card-display.ts"],"sourcesContent":["/**\n * Card Display Element Factory\n *\n * Iframe lifecycle for read-only display of tokenized card data.\n * Token loading is orchestrated by BasisTheory.ts — this factory\n * only handles iframe creation, mounting, config, and events.\n */\n\nimport type { PostMessageClient } from '@basis-theory-elements/postmessage';\nimport { createPostMessageClient } from '@basis-theory-elements/postmessage';\nimport { ConfigurationError, ElementError, log } from '@basis-theory-elements/shared';\nimport { createMounter } from '../mounter/create-mounter';\nimport type {\n CardDisplayElement,\n CardDisplayEventType,\n CardDisplayOptions,\n ElementMounter,\n EventListener,\n SDKConfig,\n} from '../types';\nimport type { CoordinatorRequest } from '../utils/bind-coordinator-port';\nimport { bindCoordinatorPort } from '../utils/bind-coordinator-port';\nimport { createIframe } from '../utils/create-iframe';\n\n/**\n * Factory function to create a card display element\n */\n// Authorization request to the merchant backend can be slow; bound it so a\n// hung endpoint surfaces as an error instead of leaving the element loading.\nconst SESSION_AUTH_TIMEOUT_MS = 30_000;\n\nexport const createCardDisplayElement = (\n _apiKey: string, // Unused - coordinator handles API calls\n sdkConfig: SDKConfig,\n options: CardDisplayOptions,\n coordinatorElementId: string,\n ensureCoordinatorReady: () => Promise<void>,\n notifyCoordinatorElementUnmount: (elementId: string) => Promise<void>,\n coordinatorRequest: CoordinatorRequest\n): CardDisplayElement => {\n // Validate required options\n if (!options.tokenId || typeof options.tokenId !== 'string') {\n throw ConfigurationError('tokenId is required for cardDisplay element');\n }\n\n // Private state (closure)\n let iframe: HTMLIFrameElement | null = null;\n let client: PostMessageClient | null = null;\n let mounter: ElementMounter | null = null;\n let isMounted = false;\n let isLoaded = false;\n let container: HTMLElement | null = null;\n let currentTokenId = options.tokenId;\n\n const eventTarget = new EventTarget();\n const id = coordinatorElementId;\n const type = 'cardDisplay' as const;\n\n /**\n * Dispatch custom event to listeners\n */\n const dispatchEvent = (eventType: CardDisplayEventType, detail: Record<string, unknown>): void => {\n eventTarget.dispatchEvent(new CustomEvent(eventType, {\n detail: {\n elementType: type,\n elementId: id,\n timestamp: Date.now(),\n ...detail,\n },\n }));\n };\n\n /**\n * Dispatch a display-load failure to listeners with a specific error code,\n * mirroring the codes the coordinator broadcasts so the consumer sees one\n * consistent vocabulary regardless of which hop failed.\n */\n const dispatchLoadError = (code: string, error: unknown): void => {\n log('Failed to load display token', {\n elementId: id,\n code,\n error: (error as Error).message,\n });\n\n dispatchEvent('error', { code, message: (error as Error).message });\n };\n\n /**\n * Best-effort release of the coordinator-held session after a parent-side\n * failure (or unmount). Without this the session key would linger in the\n * coordinator until the lazy TTL sweep — undesirable in a PCI context.\n */\n const releaseDisplaySession = async (): Promise<void> => {\n try {\n await coordinatorRequest('CMD_CLEAR_DISPLAY_SESSION', { elementId: id });\n } catch (error) {\n log('Failed to release display session', {\n elementId: id,\n error: (error as Error).message,\n });\n }\n };\n\n /**\n * Drive the display-token flow across the iframe boundary.\n *\n * 1. Ask the coordinator to create a session and return its nonce.\n * 2. Authorize the session against the merchant backend FROM HERE (the parent\n * page). The coordinator iframe's CSP `connect-src` only allows BasisTheory\n * domains, so it can't reach an arbitrary merchant URL — the merchant's own\n * page can, because the merchant controls its CSP and cookies flow here.\n * 3. Tell the coordinator to retrieve the token and broadcast it to the iframe.\n *\n * Fire-and-forget from the caller's perspective: failures surface as an\n * 'error' event rather than rejecting mount()/update().\n */\n const loadDisplayToken = async (tokenId: string): Promise<void> => {\n // Check config before creating a session we couldn't use anyway.\n if (!sdkConfig.sessionAuthorizationUrl) {\n dispatchLoadError(\n 'SESSION_AUTHORIZATION_FAILED',\n ConfigurationError('sessionAuthorizationUrl is required to load cardDisplay data')\n );\n\n return;\n }\n\n // Hop 1: coordinator creates the session, returns the nonce. A null nonce\n // means createDisplaySession already broadcast a DISPLAY_LOAD_ERROR to the\n // element — return without dispatching again so we don't double-report.\n let nonce: string;\n\n try {\n const result = await coordinatorRequest<{ nonce: string | null }>(\n 'CMD_CREATE_DISPLAY_SESSION',\n { tokenId, elementId: id }\n );\n\n if (!result?.nonce) {\n return;\n }\n\n nonce = result.nonce;\n } catch (error) {\n dispatchLoadError('SESSION_CREATION_FAILED', error);\n\n return;\n }\n\n // Hop 2: authorize via the merchant backend (runs in the parent page)\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), SESSION_AUTH_TIMEOUT_MS);\n\n let authResponse: Response;\n\n try {\n authResponse = await fetch(sdkConfig.sessionAuthorizationUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n // No credentials: the request only carries the session nonce, and\n // sending credentials is incompatible with a wildcard CORS origin\n // (Access-Control-Allow-Origin: *), which merchant authorize endpoints\n // commonly use.\n body: JSON.stringify({ nonce }),\n signal: controller.signal,\n });\n } catch (error) {\n // Network failure or timeout abort — release the orphaned coordinator session.\n await releaseDisplaySession();\n dispatchLoadError('NETWORK_ERROR', error);\n\n return;\n } finally {\n clearTimeout(timeout);\n }\n\n if (!authResponse.ok) {\n await releaseDisplaySession();\n dispatchLoadError(\n 'SESSION_AUTHORIZATION_FAILED',\n ElementError(\n `Session authorization failed: ${authResponse.status} ${authResponse.statusText}`,\n id\n )\n );\n\n return;\n }\n\n // Hop 3: coordinator retrieves the token and sends it to the iframe.\n // Card values reach the element over its coordinator port\n // (DISPLAY_VALUE_UPDATED), never through this response — keep it that way\n // for PCI isolation.\n // Retrieval errors are broadcast by the coordinator (throwOnError: false), so\n // a rejection here means the request itself failed to reach the coordinator.\n try {\n await coordinatorRequest('CMD_RETRIEVE_DISPLAY_TOKEN', {\n tokenId,\n elementId: id,\n });\n } catch (error) {\n dispatchLoadError('TOKEN_RETRIEVAL_FAILED', error);\n }\n };\n\n const element: CardDisplayElement = {\n id,\n type,\n\n get mounted() {\n return isMounted;\n },\n\n get loaded() {\n return isLoaded;\n },\n\n mount: async (selector: string | HTMLElement) => {\n if (isMounted) {\n throw ElementError('Element is already mounted', id);\n }\n\n container = typeof selector === 'string'\n ? document.querySelector(selector)\n : selector;\n\n if (!container) {\n throw ElementError(`Container not found: ${selector}`);\n }\n\n // Create iframe URL\n const parentOrigin = window.location.origin;\n const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/card-display.html`);\n iframeUrl.searchParams.set('parentOrigin', parentOrigin);\n iframeUrl.searchParams.set('elementId', id);\n iframeUrl.searchParams.set('debug', String(sdkConfig.debug || false));\n\n iframe = createIframe({\n src: iframeUrl.toString(),\n sandbox: 'allow-scripts allow-same-origin',\n allow: 'clipboard-write',\n title: 'Card Display',\n });\n\n iframe.setAttribute('data-bt-element-id', id);\n\n client = createPostMessageClient(iframe, {\n targetOrigin: sdkConfig.iframeOrigin,\n timeout: sdkConfig.timeoutMs,\n retries: sdkConfig.retryConfig.maxRetries,\n debug: sdkConfig.debug,\n });\n\n mounter = createMounter();\n\n // Subscribe to ready event before mounting\n const readyPromise = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(ElementError(\n `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,\n id,\n { elementType: type, timeoutMs: sdkConfig.timeoutMs }\n ));\n }, sdkConfig.timeoutMs);\n\n const unsubscribe = client!.subscribe('EVENT_READY', () => {\n clearTimeout(timeout);\n unsubscribe();\n resolve();\n });\n });\n\n // Subscribe to iframe events\n client.subscribe('EVENT_CHANGE', (payload: Record<string, unknown>) => {\n if (payload.loaded) {\n isLoaded = true;\n }\n\n dispatchEvent('change', payload);\n });\n\n client.subscribe('EVENT_ERROR', (payload: Record<string, unknown>) => {\n dispatchEvent('error', payload);\n });\n\n // Mount iframe\n mounter.mount(container, iframe, {\n timeoutMs: sdkConfig.timeoutMs,\n });\n\n isMounted = true;\n\n try {\n // Ensure coordinator is ready\n await ensureCoordinatorReady();\n\n // Wait for iframe ready\n await readyPromise;\n\n // Card data arrives on this port, so bind before the token load starts\n await bindCoordinatorPort({\n elementId: id,\n elementType: type,\n client: client!,\n coordinatorRequest,\n });\n\n // Send initial configuration\n await client!.sendRequest('CMD_SET_CONFIG', {\n elementId: id,\n tokenId: currentTokenId,\n theme: sdkConfig.theme,\n darkTheme: sdkConfig.darkTheme,\n themeMode: sdkConfig.themeMode,\n number: options.number,\n cvc: options.cvc,\n expiration: options.expiration,\n debug: sdkConfig.debug,\n });\n\n // Reveal iframe\n if (iframe) iframe.style.opacity = '1';\n\n // Dispatch ready event\n dispatchEvent('ready', {});\n\n // Drive the display-token flow from the parent page (session create ->\n // merchant authorization -> token retrieve). Fire-and-forget: a token\n // load failure surfaces as an 'error' event, not a mount() rejection.\n if (currentTokenId) {\n void loadDisplayToken(currentTokenId);\n }\n\n } catch (error) {\n // Dispatch error event\n dispatchEvent('error', {\n code: 'MOUNT_ERROR',\n message: (error as Error).message,\n });\n\n // Cleanup on failure\n isMounted = false;\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n container = null;\n\n throw error;\n }\n },\n\n unmount: () => {\n if (!isMounted) {\n throw ElementError('Element is not mounted');\n }\n\n // Release any in-flight coordinator-held session so its key doesn't\n // outlive the element (PCI). Fire-and-forget — unmount stays synchronous.\n void releaseDisplaySession();\n\n // Releases this element's coordinator port\n notifyCoordinatorElementUnmount(id).catch((error) => {\n log('Failed to notify coordinator of element unmount', {\n elementId: id,\n error: (error as Error).message,\n });\n });\n\n // Send clear command for PCI compliance\n if (client) {\n client.sendRequest('CMD_CLEAR', {}).catch((error) => {\n log('Failed to send clear command', {\n elementId: id,\n error: (error as Error).message,\n });\n });\n\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n container = null;\n isMounted = false;\n isLoaded = false;\n },\n\n update: async (newOptions: Partial<CardDisplayOptions> & { themeMode?: string }) => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before updating', id);\n }\n\n // If tokenId changed, reset state and re-run the token load\n if (newOptions.tokenId !== undefined && newOptions.tokenId !== currentTokenId) {\n currentTokenId = newOptions.tokenId;\n isLoaded = false;\n\n await client.sendRequest('CMD_CLEAR', {});\n\n // Re-run the parent-driven token load for the new tokenId\n void loadDisplayToken(currentTokenId);\n }\n\n // Update display configurations\n const updatePayload: Record<string, unknown> = {};\n\n if (newOptions.number !== undefined) {\n updatePayload.number = newOptions.number;\n }\n if (newOptions.cvc !== undefined) {\n updatePayload.cvc = newOptions.cvc;\n }\n if (newOptions.expiration !== undefined) {\n updatePayload.expiration = newOptions.expiration;\n }\n\n // Include theme mode update if provided\n if (newOptions.themeMode !== undefined) {\n updatePayload.themeMode = newOptions.themeMode;\n updatePayload.theme = sdkConfig.theme;\n updatePayload.darkTheme = sdkConfig.darkTheme;\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await client.sendRequest('CMD_UPDATE_CONFIG', updatePayload);\n }\n },\n\n on: (eventType: CardDisplayEventType, listener: EventListener) => {\n eventTarget.addEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n\n return () => eventTarget.removeEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n },\n\n // No-ops for API compatibility with Element interface\n focus: () => {},\n blur: () => {},\n clear: () => {},\n };\n\n return element;\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-card-display-DsLzA_vl.js","sources":["../src/elements/create-card-display.ts"],"sourcesContent":["/**\n * Card Display Element Factory\n *\n * Iframe lifecycle for read-only display of tokenized card data.\n * Token loading is orchestrated by BasisTheory.ts — this factory\n * only handles iframe creation, mounting, config, and events.\n */\n\nimport type { PostMessageClient } from '@basis-theory-elements/postmessage';\nimport { createPostMessageClient } from '@basis-theory-elements/postmessage';\nimport { ConfigurationError, ElementError, log } from '@basis-theory-elements/shared';\nimport { createMounter } from '../mounter/create-mounter';\nimport type {\n CardDisplayElement,\n CardDisplayEventType,\n CardDisplayOptions,\n ElementMounter,\n EventListener,\n SDKConfig,\n} from '../types';\nimport type { CoordinatorRequest } from '../utils/bind-coordinator-port';\nimport { bindCoordinatorPort } from '../utils/bind-coordinator-port';\nimport { createIframe } from '../utils/create-iframe';\n\n/**\n * Factory function to create a card display element\n */\n// Authorization request to the merchant backend can be slow; bound it so a\n// hung endpoint surfaces as an error instead of leaving the element loading.\nconst SESSION_AUTH_TIMEOUT_MS = 30_000;\n\nexport const createCardDisplayElement = (\n _apiKey: string, // Unused - coordinator handles API calls\n sdkConfig: SDKConfig,\n options: CardDisplayOptions,\n coordinatorElementId: string,\n ensureCoordinatorReady: () => Promise<void>,\n notifyCoordinatorElementUnmount: (elementId: string) => Promise<void>,\n coordinatorRequest: CoordinatorRequest\n): CardDisplayElement => {\n // Validate required options\n if (!options.tokenId || typeof options.tokenId !== 'string') {\n throw ConfigurationError('tokenId is required for cardDisplay element');\n }\n\n // Private state (closure)\n let iframe: HTMLIFrameElement | null = null;\n let client: PostMessageClient | null = null;\n let mounter: ElementMounter | null = null;\n let isMounted = false;\n let isLoaded = false;\n let container: HTMLElement | null = null;\n let currentTokenId = options.tokenId;\n\n const eventTarget = new EventTarget();\n const id = coordinatorElementId;\n const type = 'cardDisplay' as const;\n\n /**\n * Dispatch custom event to listeners\n */\n const dispatchEvent = (eventType: CardDisplayEventType, detail: Record<string, unknown>): void => {\n eventTarget.dispatchEvent(new CustomEvent(eventType, {\n detail: {\n elementType: type,\n elementId: id,\n timestamp: Date.now(),\n ...detail,\n },\n }));\n };\n\n /**\n * Dispatch a display-load failure to listeners with a specific error code,\n * mirroring the codes the coordinator broadcasts so the consumer sees one\n * consistent vocabulary regardless of which hop failed.\n */\n const dispatchLoadError = (code: string, error: unknown): void => {\n log('Failed to load display token', {\n elementId: id,\n code,\n error: (error as Error).message,\n });\n\n dispatchEvent('error', { code, message: (error as Error).message });\n };\n\n /**\n * Best-effort release of the coordinator-held session after a parent-side\n * failure (or unmount). Without this the session key would linger in the\n * coordinator until the lazy TTL sweep — undesirable in a PCI context.\n */\n const releaseDisplaySession = async (): Promise<void> => {\n try {\n await coordinatorRequest('CMD_CLEAR_DISPLAY_SESSION', { elementId: id });\n } catch (error) {\n log('Failed to release display session', {\n elementId: id,\n error: (error as Error).message,\n });\n }\n };\n\n /**\n * Drive the display-token flow across the iframe boundary.\n *\n * 1. Ask the coordinator to create a session and return its nonce.\n * 2. Authorize the session against the merchant backend FROM HERE (the parent\n * page). The coordinator iframe's CSP `connect-src` only allows BasisTheory\n * domains, so it can't reach an arbitrary merchant URL — the merchant's own\n * page can, because the merchant controls its CSP and cookies flow here.\n * 3. Tell the coordinator to retrieve the token and broadcast it to the iframe.\n *\n * Fire-and-forget from the caller's perspective: failures surface as an\n * 'error' event rather than rejecting mount()/update().\n */\n const loadDisplayToken = async (tokenId: string): Promise<void> => {\n // Check config before creating a session we couldn't use anyway.\n if (!sdkConfig.sessionAuthorizationUrl) {\n dispatchLoadError(\n 'SESSION_AUTHORIZATION_FAILED',\n ConfigurationError('sessionAuthorizationUrl is required to load cardDisplay data')\n );\n\n return;\n }\n\n // Hop 1: coordinator creates the session, returns the nonce. A null nonce\n // means createDisplaySession already broadcast a DISPLAY_LOAD_ERROR to the\n // element — return without dispatching again so we don't double-report.\n let nonce: string;\n\n try {\n const result = await coordinatorRequest<{ nonce: string | null }>(\n 'CMD_CREATE_DISPLAY_SESSION',\n { tokenId, elementId: id }\n );\n\n if (!result?.nonce) {\n return;\n }\n\n nonce = result.nonce;\n } catch (error) {\n dispatchLoadError('SESSION_CREATION_FAILED', error);\n\n return;\n }\n\n // Hop 2: authorize via the merchant backend (runs in the parent page)\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), SESSION_AUTH_TIMEOUT_MS);\n\n let authResponse: Response;\n\n try {\n authResponse = await fetch(sdkConfig.sessionAuthorizationUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n // No credentials: the request only carries the session nonce, and\n // sending credentials is incompatible with a wildcard CORS origin\n // (Access-Control-Allow-Origin: *), which merchant authorize endpoints\n // commonly use.\n body: JSON.stringify({ nonce }),\n signal: controller.signal,\n });\n } catch (error) {\n // Network failure or timeout abort — release the orphaned coordinator session.\n await releaseDisplaySession();\n dispatchLoadError('NETWORK_ERROR', error);\n\n return;\n } finally {\n clearTimeout(timeout);\n }\n\n if (!authResponse.ok) {\n await releaseDisplaySession();\n dispatchLoadError(\n 'SESSION_AUTHORIZATION_FAILED',\n ElementError(\n `Session authorization failed: ${authResponse.status} ${authResponse.statusText}`,\n id\n )\n );\n\n return;\n }\n\n // Hop 3: coordinator retrieves the token and sends it to the iframe.\n // Card values reach the element over its coordinator port\n // (DISPLAY_VALUE_UPDATED), never through this response — keep it that way\n // for PCI isolation.\n // Retrieval errors are broadcast by the coordinator (throwOnError: false), so\n // a rejection here means the request itself failed to reach the coordinator.\n try {\n await coordinatorRequest('CMD_RETRIEVE_DISPLAY_TOKEN', {\n tokenId,\n elementId: id,\n });\n } catch (error) {\n dispatchLoadError('TOKEN_RETRIEVAL_FAILED', error);\n }\n };\n\n const element: CardDisplayElement = {\n id,\n type,\n\n get mounted() {\n return isMounted;\n },\n\n get loaded() {\n return isLoaded;\n },\n\n mount: async (selector: string | HTMLElement) => {\n if (isMounted) {\n throw ElementError('Element is already mounted', id);\n }\n\n container = typeof selector === 'string'\n ? document.querySelector(selector)\n : selector;\n\n if (!container) {\n throw ElementError(`Container not found: ${selector}`);\n }\n\n // Create iframe URL\n const parentOrigin = window.location.origin;\n const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/card-display.html`);\n iframeUrl.searchParams.set('parentOrigin', parentOrigin);\n iframeUrl.searchParams.set('elementId', id);\n iframeUrl.searchParams.set('debug', String(sdkConfig.debug || false));\n\n iframe = createIframe({\n src: iframeUrl.toString(),\n sandbox: 'allow-scripts allow-same-origin',\n allow: 'clipboard-write',\n title: 'Card Display',\n });\n\n iframe.setAttribute('data-bt-element-id', id);\n\n client = createPostMessageClient(iframe, {\n targetOrigin: sdkConfig.iframeOrigin,\n timeout: sdkConfig.timeoutMs,\n retries: sdkConfig.retryConfig.maxRetries,\n debug: sdkConfig.debug,\n });\n\n mounter = createMounter();\n\n // Subscribe to ready event before mounting\n const readyPromise = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(ElementError(\n `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,\n id,\n { elementType: type, timeoutMs: sdkConfig.timeoutMs }\n ));\n }, sdkConfig.timeoutMs);\n\n const unsubscribe = client!.subscribe('EVENT_READY', () => {\n clearTimeout(timeout);\n unsubscribe();\n resolve();\n });\n });\n\n // Subscribe to iframe events\n client.subscribe('EVENT_CHANGE', (payload: Record<string, unknown>) => {\n if (payload.loaded) {\n isLoaded = true;\n }\n\n dispatchEvent('change', payload);\n });\n\n client.subscribe('EVENT_ERROR', (payload: Record<string, unknown>) => {\n dispatchEvent('error', payload);\n });\n\n // Mount iframe\n mounter.mount(container, iframe, {\n timeoutMs: sdkConfig.timeoutMs,\n });\n\n isMounted = true;\n\n try {\n // Ensure coordinator is ready\n await ensureCoordinatorReady();\n\n // Wait for iframe ready\n await readyPromise;\n\n // Card data arrives on this port, so bind before the token load starts\n await bindCoordinatorPort({\n elementId: id,\n elementType: type,\n client: client!,\n coordinatorRequest,\n });\n\n // Send initial configuration\n await client!.sendRequest('CMD_SET_CONFIG', {\n elementId: id,\n tokenId: currentTokenId,\n theme: sdkConfig.theme,\n darkTheme: sdkConfig.darkTheme,\n themeMode: sdkConfig.themeMode,\n number: options.number,\n cvc: options.cvc,\n expiration: options.expiration,\n debug: sdkConfig.debug,\n });\n\n // Reveal iframe\n if (iframe) iframe.style.opacity = '1';\n\n // Dispatch ready event\n dispatchEvent('ready', {});\n\n // Drive the display-token flow from the parent page (session create ->\n // merchant authorization -> token retrieve). Fire-and-forget: a token\n // load failure surfaces as an 'error' event, not a mount() rejection.\n if (currentTokenId) {\n void loadDisplayToken(currentTokenId);\n }\n\n } catch (error) {\n // Dispatch error event\n dispatchEvent('error', {\n code: 'MOUNT_ERROR',\n message: (error as Error).message,\n });\n\n // Cleanup on failure\n isMounted = false;\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n container = null;\n\n throw error;\n }\n },\n\n unmount: () => {\n if (!isMounted) {\n throw ElementError('Element is not mounted');\n }\n\n // Release any in-flight coordinator-held session so its key doesn't\n // outlive the element (PCI). Fire-and-forget — unmount stays synchronous.\n void releaseDisplaySession();\n\n // Releases this element's coordinator port\n notifyCoordinatorElementUnmount(id).catch((error) => {\n log('Failed to notify coordinator of element unmount', {\n elementId: id,\n error: (error as Error).message,\n });\n });\n\n // Send clear command for PCI compliance\n if (client) {\n client.sendRequest('CMD_CLEAR', {}).catch((error) => {\n log('Failed to send clear command', {\n elementId: id,\n error: (error as Error).message,\n });\n });\n\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n container = null;\n isMounted = false;\n isLoaded = false;\n },\n\n update: async (newOptions: Partial<CardDisplayOptions> & { themeMode?: string }) => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before updating', id);\n }\n\n // If tokenId changed, reset state and re-run the token load\n if (newOptions.tokenId !== undefined && newOptions.tokenId !== currentTokenId) {\n currentTokenId = newOptions.tokenId;\n isLoaded = false;\n\n await client.sendRequest('CMD_CLEAR', {});\n\n // Re-run the parent-driven token load for the new tokenId\n void loadDisplayToken(currentTokenId);\n }\n\n // Update display configurations\n const updatePayload: Record<string, unknown> = {};\n\n if (newOptions.number !== undefined) {\n updatePayload.number = newOptions.number;\n }\n if (newOptions.cvc !== undefined) {\n updatePayload.cvc = newOptions.cvc;\n }\n if (newOptions.expiration !== undefined) {\n updatePayload.expiration = newOptions.expiration;\n }\n\n // Include theme mode update if provided\n if (newOptions.themeMode !== undefined) {\n updatePayload.themeMode = newOptions.themeMode;\n updatePayload.theme = sdkConfig.theme;\n updatePayload.darkTheme = sdkConfig.darkTheme;\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await client.sendRequest('CMD_UPDATE_CONFIG', updatePayload);\n }\n },\n\n on: (eventType: CardDisplayEventType, listener: EventListener) => {\n eventTarget.addEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n\n return () => eventTarget.removeEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n },\n\n // No-ops for API compatibility with Element interface\n focus: () => {},\n blur: () => {},\n clear: () => {},\n };\n\n return element;\n};\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { C as ConfigurationError, l as log, E as ElementError, c as createIframe, a as createPostMessageClient, b as bindCoordinatorPort, d as createEventDispatcher, e as createMounter } from "./index-DPhSs28B.js";
1
+ import { C as ConfigurationError, l as log, E as ElementError, c as createIframe, a as createPostMessageClient, b as bindCoordinatorPort, d as createEventDispatcher, e as createMounter } from "./index-DIVQdm8b.js";
2
2
  const createCopyButtonElement = (_apiKey, sdkConfig, inputOptions, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount, coordinatorRequest) => {
3
3
  const options = inputOptions;
4
4
  if ((options == null ? void 0 : options.allowClipboard) !== true) {
@@ -1 +1 @@
1
- {"version":3,"file":"create-copy-button-DlJ3VvAn.js","sources":["../src/elements/create-copy-button.ts"],"sourcesContent":["/**\n * Copy Button Element Factory\n *\n * Creates a secure button that copies values to clipboard.\n * Can copy either a static value or a value from a linked BT element.\n *\n * SECURITY NOTE: This element is opt-in only via allowClipboard: true.\n * Clipboard access for raw card data may not be PCI compliant and must\n * never be enabled inadvertently.\n */\n\nimport type { PostMessageClient } from '@basis-theory-elements/postmessage';\nimport { createPostMessageClient } from '@basis-theory-elements/postmessage';\nimport { ConfigurationError, ElementError, log } from '@basis-theory-elements/shared';\nimport { createMounter } from '../mounter/create-mounter';\nimport type {\n CopyButtonElement,\n CopyButtonOptions,\n Element,\n ElementMounter,\n ElementOptions,\n EventListener,\n EventType,\n SDKConfig,\n} from '../types';\nimport type { CoordinatorRequest } from '../utils/bind-coordinator-port';\nimport { bindCoordinatorPort } from '../utils/bind-coordinator-port';\nimport { createIframe } from '../utils/create-iframe';\nimport { createEventDispatcher } from '../utils/create-event-dispatcher';\n\n/**\n * Factory function to create a copy button element\n *\n * @throws ConfigurationError if allowClipboard is not set to true\n */\nexport const createCopyButtonElement = (\n _apiKey: string, // Reserved for future API calls\n sdkConfig: SDKConfig,\n inputOptions: ElementOptions | CopyButtonOptions | undefined,\n coordinatorElementId: string,\n ensureCoordinatorReady: () => Promise<void>,\n notifyCoordinatorElementUnmount: (elementId: string) => Promise<void>,\n coordinatorRequest: CoordinatorRequest // ← Brokers this button's coordinator port\n): CopyButtonElement => {\n // SECURITY: Defense-in-depth check for allowClipboard flag\n // Primary validation is in validate-element-options.ts, but we keep this\n // as a backup in case the factory is called directly (e.g., lazy loading)\n const options = inputOptions as CopyButtonOptions | undefined;\n if (options?.allowClipboard !== true) {\n throw ConfigurationError(\n 'CopyButton requires allowClipboard: true. ' +\n 'Clipboard access for raw card data may not be PCI compliant ' +\n 'and must be explicitly enabled.',\n {\n received: options?.allowClipboard,\n expected: true,\n elementType: 'copyButton',\n }\n );\n }\n\n // The coordinator freezes clipboard access at initialization and refuses copy\n // references without it, so fail here rather than at setValueRef.\n if (!sdkConfig.allowClipboard) {\n throw ConfigurationError(\n 'CopyButton also requires allowClipboard: true on BasisTheory() ' +\n 'initialization. The coordinator only honors copy references when ' +\n 'clipboard access was enabled at init.',\n {\n received: sdkConfig.allowClipboard,\n expected: true,\n elementType: 'copyButton',\n }\n );\n }\n\n // Private state (closure)\n let iframe: HTMLIFrameElement | null = null;\n let client: PostMessageClient | null = null;\n let mounter: ElementMounter | null = null;\n let isMounted = false;\n let container: HTMLElement | null = null;\n let eventDispatcher: ReturnType<typeof createEventDispatcher> | null = null;\n\n const eventTarget = new EventTarget();\n const id = coordinatorElementId;\n const type = 'copyButton' as const;\n\n // Setup event dispatcher (centralized event mapping logic)\n const setupEventListeners = () => {\n if (!client) return;\n\n eventDispatcher = createEventDispatcher({\n elementType: type,\n elementId: id,\n client,\n eventTarget,\n });\n\n eventDispatcher.setupSubscriptions();\n\n // Subscribe to copy events from the iframe\n client.subscribe('EVENT_COPY', (payload) => {\n const event = new CustomEvent('copy', {\n detail: {\n elementType: type,\n elementId: id,\n timestamp: Date.now(),\n success: payload?.success ?? false,\n error: payload?.error,\n },\n });\n eventTarget.dispatchEvent(event);\n });\n };\n\n const element: CopyButtonElement = {\n id,\n type,\n get mounted() {\n return isMounted;\n },\n get loaded() {\n return isMounted;\n },\n\n mount: async (selector: string | HTMLElement) => {\n if (isMounted) {\n throw ElementError('Element is already mounted', id);\n }\n\n container =\n typeof selector === 'string'\n ? document.querySelector(selector)\n : selector;\n\n if (!container) {\n throw ElementError(`Container not found: ${selector}`);\n }\n\n // Pass parent origin + element ID via URL parameters\n const parentOrigin = window.location.origin;\n const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/copy-button.html`);\n iframeUrl.searchParams.set('parentOrigin', parentOrigin);\n iframeUrl.searchParams.set('elementId', id);\n iframeUrl.searchParams.set(\n 'measurePerformance',\n String(sdkConfig.measurePerformance || false)\n );\n iframeUrl.searchParams.set('debug', String(sdkConfig.debug || false));\n\n iframe = createIframe({\n src: iframeUrl.toString(),\n sandbox: 'allow-scripts allow-same-origin',\n // clipboard-write is required for Firefox/Safari clipboard access in iframes\n allow: 'clipboard-write',\n title: 'Copy Button',\n });\n\n // Add element ID attribute for debugging\n iframe.setAttribute('data-bt-element-id', id);\n\n client = createPostMessageClient(iframe, {\n targetOrigin: sdkConfig.iframeOrigin,\n timeout: sdkConfig.timeoutMs,\n retries: sdkConfig.retryConfig.maxRetries,\n debug: sdkConfig.debug,\n });\n\n mounter = createMounter();\n setupEventListeners();\n\n // Subscribe to 'ready' event BEFORE mounting iframe (prevents race condition)\n const readyPromise = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(\n ElementError(\n `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,\n id,\n { elementType: type, timeoutMs: sdkConfig.timeoutMs }\n )\n );\n }, sdkConfig.timeoutMs);\n\n const handleReady = () => {\n clearTimeout(timeout);\n resolve();\n };\n\n eventTarget.addEventListener('ready', handleReady, { once: true });\n });\n\n // Mount iframe to DOM\n mounter.mount(container, iframe, {\n timeoutMs: sdkConfig.timeoutMs,\n });\n\n isMounted = true;\n\n // Wait for coordinator + configuration to complete\n try {\n await ensureCoordinatorReady();\n await readyPromise;\n\n // Referenced values arrive only over this port, so a copy button framed\n // without the SDK has nothing to copy.\n await bindCoordinatorPort({\n elementId: id,\n elementType: type,\n client: client!,\n coordinatorRequest,\n });\n\n await client!.sendRequest('CMD_SET_CONFIG', {\n elementId: id,\n theme: sdkConfig.theme,\n darkTheme: sdkConfig.darkTheme,\n themeMode: sdkConfig.themeMode,\n text: options.text || 'Copy',\n value: options.value,\n disabled: options.disabled,\n debug: sdkConfig.debug,\n });\n\n // Reveal iframe now that theme is applied\n if (iframe) iframe.style.opacity = '1';\n } catch (error) {\n // Dispatch error event before cleanup\n if (eventDispatcher) {\n eventDispatcher.dispatchError(\n 'MOUNT_ERROR',\n (error as Error).message,\n error as Error\n );\n }\n\n // Mount failed - clean up\n isMounted = false;\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n eventDispatcher = null;\n container = null;\n\n throw error;\n }\n },\n\n unmount: () => {\n if (!isMounted) {\n throw ElementError('Element is not mounted');\n }\n\n // Drops the coordinator's copy reference and releases this button's port\n notifyCoordinatorElementUnmount(id).catch((error) => {\n log('Failed to notify coordinator of element unmount', {\n elementId: id,\n error: (error as Error).message,\n });\n });\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n eventDispatcher = null;\n container = null;\n isMounted = false;\n },\n\n update: async (newOptions: Partial<CopyButtonOptions> & { themeMode?: string }) => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before updating', id);\n }\n\n // Only send fields that are present (matches create-card-number) so we never\n // overwrite iframe config with undefined/null over postMessage.\n const updatePayload: Record<string, any> = {};\n\n const copyButtonOptions = newOptions as Partial<CopyButtonOptions> & { themeMode?: string };\n\n if (copyButtonOptions.text !== undefined) {\n updatePayload.text = copyButtonOptions.text;\n }\n if (copyButtonOptions.value !== undefined) {\n updatePayload.value = copyButtonOptions.value;\n }\n if (copyButtonOptions.disabled !== undefined) {\n updatePayload.disabled = copyButtonOptions.disabled;\n }\n\n // Include theme mode update if provided\n if (newOptions.themeMode !== undefined) {\n updatePayload.themeMode = newOptions.themeMode;\n updatePayload.theme = sdkConfig.theme;\n updatePayload.darkTheme = sdkConfig.darkTheme;\n }\n\n await client.sendRequest('CMD_UPDATE_CONFIG', updatePayload);\n },\n\n focus: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before focusing', id);\n }\n\n client.sendRequest('CMD_FOCUS', {});\n },\n\n blur: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before blurring', id);\n }\n\n client.sendRequest('CMD_BLUR', {});\n },\n\n // No-op for CopyButton (nothing to clear in a button)\n clear: () => {},\n\n on: (eventType: EventType, listener: EventListener) => {\n eventTarget.addEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n\n return () =>\n eventTarget.removeEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n },\n\n /**\n * Link this copy button to another element's value.\n *\n * Authorized by the coordinator, not the iframe: it checks both ids are ones\n * it registered, then forwards the value over this button's port.\n */\n setValueRef: async (refElement: Element): Promise<void> => {\n log('setValueRef called', {\n copyButtonId: id,\n refElementId: refElement?.id,\n });\n\n if (!refElement || !refElement.id) {\n throw ElementError('setValueRef requires a valid Element with an ID', id);\n }\n\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before calling setValueRef', id);\n }\n\n const result = await coordinatorRequest<{ success: boolean; error?: string }>(\n 'CMD_SET_COPY_REFERENCE',\n { elementId: id, referenceElementId: refElement.id }\n );\n\n if (!result?.success) {\n throw ElementError(\n `Coordinator rejected the copy reference (${result?.error ?? 'UNKNOWN'})`,\n id\n );\n }\n },\n };\n\n return element;\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-copy-button-Z1s9ao67.js","sources":["../src/elements/create-copy-button.ts"],"sourcesContent":["/**\n * Copy Button Element Factory\n *\n * Creates a secure button that copies values to clipboard.\n * Can copy either a static value or a value from a linked BT element.\n *\n * SECURITY NOTE: This element is opt-in only via allowClipboard: true.\n * Clipboard access for raw card data may not be PCI compliant and must\n * never be enabled inadvertently.\n */\n\nimport type { PostMessageClient } from '@basis-theory-elements/postmessage';\nimport { createPostMessageClient } from '@basis-theory-elements/postmessage';\nimport { ConfigurationError, ElementError, log } from '@basis-theory-elements/shared';\nimport { createMounter } from '../mounter/create-mounter';\nimport type {\n CopyButtonElement,\n CopyButtonOptions,\n Element,\n ElementMounter,\n ElementOptions,\n EventListener,\n EventType,\n SDKConfig,\n} from '../types';\nimport type { CoordinatorRequest } from '../utils/bind-coordinator-port';\nimport { bindCoordinatorPort } from '../utils/bind-coordinator-port';\nimport { createIframe } from '../utils/create-iframe';\nimport { createEventDispatcher } from '../utils/create-event-dispatcher';\n\n/**\n * Factory function to create a copy button element\n *\n * @throws ConfigurationError if allowClipboard is not set to true\n */\nexport const createCopyButtonElement = (\n _apiKey: string, // Reserved for future API calls\n sdkConfig: SDKConfig,\n inputOptions: ElementOptions | CopyButtonOptions | undefined,\n coordinatorElementId: string,\n ensureCoordinatorReady: () => Promise<void>,\n notifyCoordinatorElementUnmount: (elementId: string) => Promise<void>,\n coordinatorRequest: CoordinatorRequest // ← Brokers this button's coordinator port\n): CopyButtonElement => {\n // SECURITY: Defense-in-depth check for allowClipboard flag\n // Primary validation is in validate-element-options.ts, but we keep this\n // as a backup in case the factory is called directly (e.g., lazy loading)\n const options = inputOptions as CopyButtonOptions | undefined;\n if (options?.allowClipboard !== true) {\n throw ConfigurationError(\n 'CopyButton requires allowClipboard: true. ' +\n 'Clipboard access for raw card data may not be PCI compliant ' +\n 'and must be explicitly enabled.',\n {\n received: options?.allowClipboard,\n expected: true,\n elementType: 'copyButton',\n }\n );\n }\n\n // The coordinator freezes clipboard access at initialization and refuses copy\n // references without it, so fail here rather than at setValueRef.\n if (!sdkConfig.allowClipboard) {\n throw ConfigurationError(\n 'CopyButton also requires allowClipboard: true on BasisTheory() ' +\n 'initialization. The coordinator only honors copy references when ' +\n 'clipboard access was enabled at init.',\n {\n received: sdkConfig.allowClipboard,\n expected: true,\n elementType: 'copyButton',\n }\n );\n }\n\n // Private state (closure)\n let iframe: HTMLIFrameElement | null = null;\n let client: PostMessageClient | null = null;\n let mounter: ElementMounter | null = null;\n let isMounted = false;\n let container: HTMLElement | null = null;\n let eventDispatcher: ReturnType<typeof createEventDispatcher> | null = null;\n\n const eventTarget = new EventTarget();\n const id = coordinatorElementId;\n const type = 'copyButton' as const;\n\n // Setup event dispatcher (centralized event mapping logic)\n const setupEventListeners = () => {\n if (!client) return;\n\n eventDispatcher = createEventDispatcher({\n elementType: type,\n elementId: id,\n client,\n eventTarget,\n });\n\n eventDispatcher.setupSubscriptions();\n\n // Subscribe to copy events from the iframe\n client.subscribe('EVENT_COPY', (payload) => {\n const event = new CustomEvent('copy', {\n detail: {\n elementType: type,\n elementId: id,\n timestamp: Date.now(),\n success: payload?.success ?? false,\n error: payload?.error,\n },\n });\n eventTarget.dispatchEvent(event);\n });\n };\n\n const element: CopyButtonElement = {\n id,\n type,\n get mounted() {\n return isMounted;\n },\n get loaded() {\n return isMounted;\n },\n\n mount: async (selector: string | HTMLElement) => {\n if (isMounted) {\n throw ElementError('Element is already mounted', id);\n }\n\n container =\n typeof selector === 'string'\n ? document.querySelector(selector)\n : selector;\n\n if (!container) {\n throw ElementError(`Container not found: ${selector}`);\n }\n\n // Pass parent origin + element ID via URL parameters\n const parentOrigin = window.location.origin;\n const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/copy-button.html`);\n iframeUrl.searchParams.set('parentOrigin', parentOrigin);\n iframeUrl.searchParams.set('elementId', id);\n iframeUrl.searchParams.set(\n 'measurePerformance',\n String(sdkConfig.measurePerformance || false)\n );\n iframeUrl.searchParams.set('debug', String(sdkConfig.debug || false));\n\n iframe = createIframe({\n src: iframeUrl.toString(),\n sandbox: 'allow-scripts allow-same-origin',\n // clipboard-write is required for Firefox/Safari clipboard access in iframes\n allow: 'clipboard-write',\n title: 'Copy Button',\n });\n\n // Add element ID attribute for debugging\n iframe.setAttribute('data-bt-element-id', id);\n\n client = createPostMessageClient(iframe, {\n targetOrigin: sdkConfig.iframeOrigin,\n timeout: sdkConfig.timeoutMs,\n retries: sdkConfig.retryConfig.maxRetries,\n debug: sdkConfig.debug,\n });\n\n mounter = createMounter();\n setupEventListeners();\n\n // Subscribe to 'ready' event BEFORE mounting iframe (prevents race condition)\n const readyPromise = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(\n ElementError(\n `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,\n id,\n { elementType: type, timeoutMs: sdkConfig.timeoutMs }\n )\n );\n }, sdkConfig.timeoutMs);\n\n const handleReady = () => {\n clearTimeout(timeout);\n resolve();\n };\n\n eventTarget.addEventListener('ready', handleReady, { once: true });\n });\n\n // Mount iframe to DOM\n mounter.mount(container, iframe, {\n timeoutMs: sdkConfig.timeoutMs,\n });\n\n isMounted = true;\n\n // Wait for coordinator + configuration to complete\n try {\n await ensureCoordinatorReady();\n await readyPromise;\n\n // Referenced values arrive only over this port, so a copy button framed\n // without the SDK has nothing to copy.\n await bindCoordinatorPort({\n elementId: id,\n elementType: type,\n client: client!,\n coordinatorRequest,\n });\n\n await client!.sendRequest('CMD_SET_CONFIG', {\n elementId: id,\n theme: sdkConfig.theme,\n darkTheme: sdkConfig.darkTheme,\n themeMode: sdkConfig.themeMode,\n text: options.text || 'Copy',\n value: options.value,\n disabled: options.disabled,\n debug: sdkConfig.debug,\n });\n\n // Reveal iframe now that theme is applied\n if (iframe) iframe.style.opacity = '1';\n } catch (error) {\n // Dispatch error event before cleanup\n if (eventDispatcher) {\n eventDispatcher.dispatchError(\n 'MOUNT_ERROR',\n (error as Error).message,\n error as Error\n );\n }\n\n // Mount failed - clean up\n isMounted = false;\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n eventDispatcher = null;\n container = null;\n\n throw error;\n }\n },\n\n unmount: () => {\n if (!isMounted) {\n throw ElementError('Element is not mounted');\n }\n\n // Drops the coordinator's copy reference and releases this button's port\n notifyCoordinatorElementUnmount(id).catch((error) => {\n log('Failed to notify coordinator of element unmount', {\n elementId: id,\n error: (error as Error).message,\n });\n });\n\n if (client) {\n client.destroy();\n client = null;\n }\n\n if (mounter && iframe) {\n mounter.unmount(iframe);\n iframe = null;\n mounter = null;\n }\n\n eventDispatcher = null;\n container = null;\n isMounted = false;\n },\n\n update: async (newOptions: Partial<CopyButtonOptions> & { themeMode?: string }) => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before updating', id);\n }\n\n // Only send fields that are present (matches create-card-number) so we never\n // overwrite iframe config with undefined/null over postMessage.\n const updatePayload: Record<string, any> = {};\n\n const copyButtonOptions = newOptions as Partial<CopyButtonOptions> & { themeMode?: string };\n\n if (copyButtonOptions.text !== undefined) {\n updatePayload.text = copyButtonOptions.text;\n }\n if (copyButtonOptions.value !== undefined) {\n updatePayload.value = copyButtonOptions.value;\n }\n if (copyButtonOptions.disabled !== undefined) {\n updatePayload.disabled = copyButtonOptions.disabled;\n }\n\n // Include theme mode update if provided\n if (newOptions.themeMode !== undefined) {\n updatePayload.themeMode = newOptions.themeMode;\n updatePayload.theme = sdkConfig.theme;\n updatePayload.darkTheme = sdkConfig.darkTheme;\n }\n\n await client.sendRequest('CMD_UPDATE_CONFIG', updatePayload);\n },\n\n focus: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before focusing', id);\n }\n\n client.sendRequest('CMD_FOCUS', {});\n },\n\n blur: () => {\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before blurring', id);\n }\n\n client.sendRequest('CMD_BLUR', {});\n },\n\n // No-op for CopyButton (nothing to clear in a button)\n clear: () => {},\n\n on: (eventType: EventType, listener: EventListener) => {\n eventTarget.addEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n\n return () =>\n eventTarget.removeEventListener(\n eventType,\n listener as EventListenerOrEventListenerObject\n );\n },\n\n /**\n * Link this copy button to another element's value.\n *\n * Authorized by the coordinator, not the iframe: it checks both ids are ones\n * it registered, then forwards the value over this button's port.\n */\n setValueRef: async (refElement: Element): Promise<void> => {\n log('setValueRef called', {\n copyButtonId: id,\n refElementId: refElement?.id,\n });\n\n if (!refElement || !refElement.id) {\n throw ElementError('setValueRef requires a valid Element with an ID', id);\n }\n\n if (!isMounted || !client) {\n throw ElementError('Element must be mounted before calling setValueRef', id);\n }\n\n const result = await coordinatorRequest<{ success: boolean; error?: string }>(\n 'CMD_SET_COPY_REFERENCE',\n { elementId: id, referenceElementId: refElement.id }\n );\n\n if (!result?.success) {\n throw ElementError(\n `Coordinator rejected the copy reference (${result?.error ?? 'UNKNOWN'})`,\n id\n );\n }\n },\n };\n\n return element;\n};\n"],"names":[],"mappings":"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"}
@@ -1928,10 +1928,10 @@ const validateSDKConfig = (options = {}) => {
1928
1928
  }
1929
1929
  throw ConfigurationError("whitelabelDomain must be a valid URL");
1930
1930
  }
1931
- const { pathname } = new URL("https://js.basistheory.com/3.0.0-beta.6/elements");
1931
+ const { pathname } = new URL("https://js.basistheory.com/3.0.0/elements");
1932
1932
  resolvedBaseUrl = `${domainOrigin}${pathname}`;
1933
1933
  }
1934
- const finalBaseUrl = resolvedBaseUrl || "https://js.basistheory.com/3.0.0-beta.6/elements";
1934
+ const finalBaseUrl = resolvedBaseUrl || "https://js.basistheory.com/3.0.0/elements";
1935
1935
  const iframeOrigin = new URL(finalBaseUrl).origin;
1936
1936
  return {
1937
1937
  iframeOrigin,
@@ -2227,21 +2227,21 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2227
2227
  let cardDisplayElementFactory = null;
2228
2228
  const lazyCardElement = async () => {
2229
2229
  if (!cardElementFactory) {
2230
- const module = await import("./create-card-C0MwL3Ou.js");
2230
+ const module = await import("./create-card-C94szxMV.js");
2231
2231
  cardElementFactory = module.createCardElement;
2232
2232
  }
2233
2233
  return cardElementFactory;
2234
2234
  };
2235
2235
  const lazyCopyButtonElement = async () => {
2236
2236
  if (!copyButtonElementFactory) {
2237
- const module = await import("./create-copy-button-DlJ3VvAn.js");
2237
+ const module = await import("./create-copy-button-Z1s9ao67.js");
2238
2238
  copyButtonElementFactory = module.createCopyButtonElement;
2239
2239
  }
2240
2240
  return copyButtonElementFactory;
2241
2241
  };
2242
2242
  const lazyCardDisplayElement = async () => {
2243
2243
  if (!cardDisplayElementFactory) {
2244
- const module = await import("./create-card-display-DscvxIs4.js");
2244
+ const module = await import("./create-card-display-DsLzA_vl.js");
2245
2245
  cardDisplayElementFactory = module.createCardDisplayElement;
2246
2246
  }
2247
2247
  return cardDisplayElementFactory;
@@ -2960,8 +2960,8 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2960
2960
  if (typeof window !== "undefined" && typeof document !== "undefined" && document.currentScript && !window.BasisTheory) {
2961
2961
  window.BasisTheory = BasisTheory;
2962
2962
  }
2963
- const version = "3.0.0-beta.6";
2964
- const buildDate = "2026-08-05T15:06:02.574Z";
2963
+ const version = "3.0.0";
2964
+ const buildDate = "2026-08-13T19:30:19.198Z";
2965
2965
  if (typeof window !== "undefined") {
2966
2966
  window.__BasisTheorySDK__ = BasisTheory;
2967
2967
  }