@basis-theory/web-elements 2.11.1 → 3.0.0-beta.2

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.
Files changed (104) hide show
  1. package/README.md +668 -2
  2. package/dist/.vite/manifest.json +27 -0
  3. package/dist/api-client/src/api-client.d.ts +11 -0
  4. package/dist/api-client/src/api-client.d.ts.map +1 -0
  5. package/dist/api-client/src/http-client.d.ts +24 -0
  6. package/dist/api-client/src/http-client.d.ts.map +1 -0
  7. package/dist/api-client/src/index.d.ts +9 -0
  8. package/dist/api-client/src/index.d.ts.map +1 -0
  9. package/dist/api-client/src/services/sessions.d.ts +19 -0
  10. package/dist/api-client/src/services/sessions.d.ts.map +1 -0
  11. package/dist/api-client/src/services/token-intents.d.ts +17 -0
  12. package/dist/api-client/src/services/token-intents.d.ts.map +1 -0
  13. package/dist/api-client/src/services/tokens.d.ts +19 -0
  14. package/dist/api-client/src/services/tokens.d.ts.map +1 -0
  15. package/dist/api-client/src/types.d.ts +313 -0
  16. package/dist/api-client/src/types.d.ts.map +1 -0
  17. package/dist/basis-theory-sdk.es.js +6 -0
  18. package/dist/basis-theory-sdk.es.js.map +1 -0
  19. package/dist/basis-theory-sdk.umd.js +1 -0
  20. package/dist/basis-theory-sdk.umd.js.map +1 -0
  21. package/dist/create-card-BuOvimjH.js +232 -0
  22. package/dist/create-card-BuOvimjH.js.map +1 -0
  23. package/dist/index-oREjBjga.js +2317 -0
  24. package/dist/index-oREjBjga.js.map +1 -0
  25. package/dist/loader/basis-theory.min.js +1 -0
  26. package/dist/postmessage/src/client.d.ts +7 -0
  27. package/dist/postmessage/src/client.d.ts.map +1 -0
  28. package/dist/postmessage/src/event-map.d.ts +135 -0
  29. package/dist/postmessage/src/event-map.d.ts.map +1 -0
  30. package/dist/postmessage/src/index.d.ts +9 -0
  31. package/dist/postmessage/src/index.d.ts.map +1 -0
  32. package/dist/postmessage/src/server.d.ts +11 -0
  33. package/dist/postmessage/src/server.d.ts.map +1 -0
  34. package/dist/postmessage/src/types.d.ts +71 -0
  35. package/dist/postmessage/src/types.d.ts.map +1 -0
  36. package/dist/shared/src/api-url-resolver.d.ts +22 -0
  37. package/dist/shared/src/api-url-resolver.d.ts.map +1 -0
  38. package/dist/shared/src/debug-logger.d.ts +16 -0
  39. package/dist/shared/src/debug-logger.d.ts.map +1 -0
  40. package/dist/shared/src/deep-sanitization.d.ts +24 -0
  41. package/dist/shared/src/deep-sanitization.d.ts.map +1 -0
  42. package/dist/shared/src/errors.d.ts +93 -0
  43. package/dist/shared/src/errors.d.ts.map +1 -0
  44. package/dist/shared/src/generate-id.d.ts +13 -0
  45. package/dist/shared/src/generate-id.d.ts.map +1 -0
  46. package/dist/shared/src/index.d.ts +13 -0
  47. package/dist/shared/src/index.d.ts.map +1 -0
  48. package/dist/shared/src/logger-context.d.ts +25 -0
  49. package/dist/shared/src/logger-context.d.ts.map +1 -0
  50. package/dist/sri.json +22 -0
  51. package/dist/stats.html +4949 -0
  52. package/dist/web-elements/src/BasisTheory.d.ts +47 -0
  53. package/dist/web-elements/src/BasisTheory.d.ts.map +1 -0
  54. package/dist/web-elements/src/basis-theory.d.ts +62 -0
  55. package/dist/web-elements/src/basis-theory.d.ts.map +1 -0
  56. package/dist/web-elements/src/elements/create-card-number.d.ts +14 -0
  57. package/dist/web-elements/src/elements/create-card-number.d.ts.map +1 -0
  58. package/dist/web-elements/src/elements/create-card.d.ts +37 -0
  59. package/dist/web-elements/src/elements/create-card.d.ts.map +1 -0
  60. package/dist/web-elements/src/elements/create-cvv.d.ts +10 -0
  61. package/dist/web-elements/src/elements/create-cvv.d.ts.map +1 -0
  62. package/dist/web-elements/src/elements/create-expiry.d.ts +10 -0
  63. package/dist/web-elements/src/elements/create-expiry.d.ts.map +1 -0
  64. package/dist/web-elements/src/elements/create-text.d.ts +13 -0
  65. package/dist/web-elements/src/elements/create-text.d.ts.map +1 -0
  66. package/dist/web-elements/src/index.d.ts +27 -0
  67. package/dist/web-elements/src/index.d.ts.map +1 -0
  68. package/dist/web-elements/src/mounter/create-mounter.d.ts +11 -0
  69. package/dist/web-elements/src/mounter/create-mounter.d.ts.map +1 -0
  70. package/dist/web-elements/src/theme/default-theme.d.ts +22 -0
  71. package/dist/web-elements/src/theme/default-theme.d.ts.map +1 -0
  72. package/dist/web-elements/src/theme/inject-theme-dark.d.ts +13 -0
  73. package/dist/web-elements/src/theme/inject-theme-dark.d.ts.map +1 -0
  74. package/dist/web-elements/src/theme/inject-theme.d.ts +20 -0
  75. package/dist/web-elements/src/theme/inject-theme.d.ts.map +1 -0
  76. package/dist/web-elements/src/types/events.d.ts +137 -0
  77. package/dist/web-elements/src/types/events.d.ts.map +1 -0
  78. package/dist/web-elements/src/types/theme.d.ts +91 -0
  79. package/dist/web-elements/src/types/theme.d.ts.map +1 -0
  80. package/dist/web-elements/src/types.d.ts +537 -0
  81. package/dist/web-elements/src/types.d.ts.map +1 -0
  82. package/dist/web-elements/src/utils/create-event-dispatcher.d.ts +47 -0
  83. package/dist/web-elements/src/utils/create-event-dispatcher.d.ts.map +1 -0
  84. package/dist/web-elements/src/utils/create-iframe.d.ts +10 -0
  85. package/dist/web-elements/src/utils/create-iframe.d.ts.map +1 -0
  86. package/dist/web-elements/src/utils/resolve-element-references.d.ts +20 -0
  87. package/dist/web-elements/src/utils/resolve-element-references.d.ts.map +1 -0
  88. package/dist/web-elements/src/utils/validate-element-options.d.ts +11 -0
  89. package/dist/web-elements/src/utils/validate-element-options.d.ts.map +1 -0
  90. package/dist/web-elements/src/utils/validate-sdk-config.d.ts +15 -0
  91. package/dist/web-elements/src/utils/validate-sdk-config.d.ts.map +1 -0
  92. package/dist/web-elements/src/utils/warn-unknown-keys.d.ts +15 -0
  93. package/dist/web-elements/src/utils/warn-unknown-keys.d.ts.map +1 -0
  94. package/dist/web-elements/src/version.d.ts +25 -0
  95. package/dist/web-elements/src/version.d.ts.map +1 -0
  96. package/package.json +53 -72
  97. package/LICENSE +0 -202
  98. package/dist/LICENSE +0 -202
  99. package/dist/README.md +0 -3
  100. package/dist/bundle/index.js +0 -1
  101. package/dist/main/index.js +0 -1160
  102. package/dist/module/module.js +0 -1158
  103. package/dist/package.json +0 -37
  104. package/dist/types/index.d.ts +0 -732
@@ -0,0 +1,2317 @@
1
+ const PAN_PATTERNS = [
2
+ /cardnumber/i,
3
+ /^pan$/i,
4
+ /accountnumber/i,
5
+ /^number$/i
6
+ // Common in card objects
7
+ ];
8
+ const REDACT_PATTERNS = [
9
+ /cvv/i,
10
+ /cvc/i,
11
+ /cvv2/i,
12
+ /securitycode/i,
13
+ /pin/i,
14
+ /password/i,
15
+ /secret/i,
16
+ /token/i,
17
+ /ssn/i,
18
+ /track/i
19
+ ];
20
+ const isPANField = (key) => {
21
+ return PAN_PATTERNS.some((pattern) => pattern.test(key));
22
+ };
23
+ const isRedactField = (key) => {
24
+ return REDACT_PATTERNS.some((pattern) => pattern.test(key));
25
+ };
26
+ const maskPAN = (value) => {
27
+ const digits = value.replace(/\D/g, "");
28
+ if (digits.length < 10) {
29
+ return "***";
30
+ }
31
+ const first6 = digits.substring(0, 6);
32
+ const last4 = digits.substring(digits.length - 4);
33
+ const maskedLength = Math.max(digits.length - 10, 6);
34
+ const masked = "*".repeat(maskedLength);
35
+ return `${first6}${masked}${last4}`;
36
+ };
37
+ const looksLikePAN = (value) => {
38
+ const digits = value.replace(/[\s-]/g, "");
39
+ return /^\d{13,19}$/.test(digits);
40
+ };
41
+ const sanitizeValue = (key, value) => {
42
+ if (value === null || value === void 0) {
43
+ return value;
44
+ }
45
+ if (typeof value === "function") {
46
+ return "[Function]";
47
+ }
48
+ if (typeof value === "symbol") {
49
+ return "[Symbol]";
50
+ }
51
+ if (value instanceof RegExp) {
52
+ return "[RegExp]";
53
+ }
54
+ if (value instanceof Date) {
55
+ return value;
56
+ }
57
+ if (isPANField(key)) {
58
+ if (typeof value === "string") {
59
+ return maskPAN(value);
60
+ }
61
+ return "***";
62
+ }
63
+ if (isRedactField(key)) {
64
+ return "***";
65
+ }
66
+ if (typeof value === "string" && looksLikePAN(value)) {
67
+ return maskPAN(value);
68
+ }
69
+ return value;
70
+ };
71
+ const sanitizeDeep = (data, depth = 0, ancestors = /* @__PURE__ */ new WeakSet()) => {
72
+ if (depth >= 5) {
73
+ return "[Max Depth]";
74
+ }
75
+ if (data === null || data === void 0) {
76
+ return data;
77
+ }
78
+ if (typeof data === "string") {
79
+ return looksLikePAN(data) ? maskPAN(data) : data;
80
+ }
81
+ if (typeof data !== "object") {
82
+ return data;
83
+ }
84
+ if (data instanceof RegExp) {
85
+ return "[RegExp]";
86
+ }
87
+ if (data instanceof Date) {
88
+ return data;
89
+ }
90
+ if (ancestors.has(data)) {
91
+ return "[Circular]";
92
+ }
93
+ ancestors.add(data);
94
+ let result;
95
+ if (Array.isArray(data)) {
96
+ result = data.map((item) => sanitizeDeep(item, depth + 1, ancestors));
97
+ } else {
98
+ const sanitized = {};
99
+ for (const [key, value] of Object.entries(data)) {
100
+ const sanitizedValue = sanitizeValue(key, value);
101
+ if (sanitizedValue !== null && typeof sanitizedValue === "object") {
102
+ sanitized[key] = sanitizeDeep(sanitizedValue, depth + 1, ancestors);
103
+ } else {
104
+ sanitized[key] = sanitizedValue;
105
+ }
106
+ }
107
+ result = sanitized;
108
+ }
109
+ ancestors.delete(data);
110
+ return result;
111
+ };
112
+ const sanitizeMetadata = (metadata) => {
113
+ const result = {};
114
+ for (const [key, value] of Object.entries(metadata)) {
115
+ if (isPANField(key) || isRedactField(key)) {
116
+ const len = typeof value === "string" ? value.length : typeof value === "number" ? String(value).length : 0;
117
+ result[`${key}_length`] = len;
118
+ } else {
119
+ result[key] = sanitizeDeep(value);
120
+ }
121
+ }
122
+ return result;
123
+ };
124
+ const formatTimestamp = () => {
125
+ const now = /* @__PURE__ */ new Date();
126
+ const hours = String(now.getHours()).padStart(2, "0");
127
+ const minutes = String(now.getMinutes()).padStart(2, "0");
128
+ const seconds = String(now.getSeconds()).padStart(2, "0");
129
+ const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
130
+ return `${hours}:${minutes}:${seconds}.${milliseconds}`;
131
+ };
132
+ const createDebugLogger = (packageName, enabled) => {
133
+ if (!enabled) {
134
+ return () => {
135
+ };
136
+ }
137
+ return (action, metadata) => {
138
+ const timestamp = formatTimestamp();
139
+ const sanitized = metadata ? sanitizeMetadata(metadata) : {};
140
+ console.log(`🔍 [${timestamp}] [${packageName}] ${action}`, sanitized);
141
+ };
142
+ };
143
+ const createBasisTheoryError = (code, message, details) => {
144
+ const error = new Error(message);
145
+ error.name = "BasisTheoryError";
146
+ error.code = code;
147
+ error.details = details;
148
+ if (Error.captureStackTrace) {
149
+ Error.captureStackTrace(error, createBasisTheoryError);
150
+ }
151
+ error.toJSON = () => ({
152
+ name: error.name,
153
+ code: error.code,
154
+ message: error.message,
155
+ ...error.details && { details: error.details }
156
+ });
157
+ return error;
158
+ };
159
+ const BasisTheoryError = (code, message, details) => {
160
+ const error = createBasisTheoryError(code, message, details);
161
+ error.name = "BasisTheoryError";
162
+ return error;
163
+ };
164
+ const ValidationError = (message, field, details) => {
165
+ const error = createBasisTheoryError("VALIDATION_ERROR", message, details);
166
+ error.name = "ValidationError";
167
+ error.field = field;
168
+ return error;
169
+ };
170
+ const CoordinatorError = (message, details) => {
171
+ const error = createBasisTheoryError("COORDINATOR_ERROR", message, details);
172
+ error.name = "CoordinatorError";
173
+ return error;
174
+ };
175
+ const ElementError = (message, elementId, details) => {
176
+ const error = createBasisTheoryError("ELEMENT_ERROR", message, elementId ? { elementId, ...details } : details);
177
+ error.name = "ElementError";
178
+ error.elementId = elementId;
179
+ return error;
180
+ };
181
+ const ConfigurationError = (message, details) => {
182
+ const error = createBasisTheoryError("CONFIGURATION_ERROR", message, details);
183
+ error.name = "ConfigurationError";
184
+ return error;
185
+ };
186
+ const log = (action, metadata) => {
187
+ };
188
+ const DEFAULT_RETRY_CONFIG = {
189
+ maxRetries: 2,
190
+ initialDelay: 500,
191
+ maxDelay: 3e3,
192
+ backoffFactor: 2
193
+ };
194
+ const getRetryDelay = (attempt, config) => {
195
+ const delay = config.initialDelay * Math.pow(config.backoffFactor, attempt);
196
+ return Math.min(delay, config.maxDelay);
197
+ };
198
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
199
+ const createPostMessageClient = (iframe, config) => {
200
+ const pendingRequests = /* @__PURE__ */ new Map();
201
+ const eventSubscribers = /* @__PURE__ */ new Map();
202
+ const log2 = createDebugLogger("PostMessageClient", config.debug || false);
203
+ const isValidOrigin = (origin) => {
204
+ return origin === config.targetOrigin;
205
+ };
206
+ const handleMessage = (event) => {
207
+ const { nonce, type, payload, error } = event.data || {};
208
+ if (!type) {
209
+ return;
210
+ }
211
+ if (!isValidOrigin(event.origin)) {
212
+ log2("Rejected message (origin mismatch)", {
213
+ receivedOrigin: event.origin,
214
+ expectedOrigin: config.targetOrigin,
215
+ messageType: type
216
+ });
217
+ return;
218
+ }
219
+ if (event.source !== iframe.contentWindow) {
220
+ return;
221
+ }
222
+ if (nonce) {
223
+ const pending = pendingRequests.get(nonce);
224
+ if (pending) {
225
+ log2("Message received", {
226
+ origin: event.origin,
227
+ expectedOrigin: config.targetOrigin,
228
+ type,
229
+ nonce
230
+ });
231
+ clearTimeout(pending.timeout);
232
+ pendingRequests.delete(nonce);
233
+ if (error) {
234
+ const reconstructedError = BasisTheoryError(error.code, `[${error.code}] ${error.message}`);
235
+ Object.assign(reconstructedError, {
236
+ ...error.status !== void 0 && { status: error.status },
237
+ ...error.statusCode !== void 0 && {
238
+ statusCode: error.statusCode
239
+ },
240
+ ...error.validationErrors && {
241
+ validationErrors: error.validationErrors
242
+ },
243
+ ...error.body && { body: error.body },
244
+ ...error.retryCount !== void 0 && {
245
+ retryCount: error.retryCount
246
+ },
247
+ ...error.details && { details: error.details }
248
+ });
249
+ pending.reject(reconstructedError);
250
+ } else {
251
+ pending.resolve(payload);
252
+ }
253
+ return;
254
+ }
255
+ return;
256
+ }
257
+ const subscribers = eventSubscribers.get(type);
258
+ if (subscribers && subscribers.size > 0) {
259
+ log2("Event received", {
260
+ origin: event.origin,
261
+ expectedOrigin: config.targetOrigin,
262
+ type,
263
+ nonce,
264
+ subscriberCount: subscribers.size
265
+ });
266
+ subscribers.forEach((callback) => {
267
+ try {
268
+ callback(payload);
269
+ } catch (err) {
270
+ if (config.debug) {
271
+ console.error(`[PostMessage Client] Error in event subscriber for ${type}:`, err);
272
+ }
273
+ }
274
+ });
275
+ } else if (!nonce) {
276
+ log2("Event received but no subscribers", {
277
+ type,
278
+ availableSubscribers: Array.from(eventSubscribers.keys())
279
+ });
280
+ }
281
+ };
282
+ window.addEventListener("message", handleMessage);
283
+ const retryConfig = typeof config.retries === "number" ? { ...DEFAULT_RETRY_CONFIG, maxRetries: config.retries } : typeof config.retries === "object" ? config.retries : DEFAULT_RETRY_CONFIG;
284
+ const sendRequestOnce = (type, payload) => {
285
+ return new Promise((resolve, reject) => {
286
+ const nonce = crypto.randomUUID();
287
+ const timestamp = Date.now();
288
+ if (!iframe.contentWindow) {
289
+ reject(BasisTheoryError("IFRAME_NOT_READY", "Iframe not ready"));
290
+ return;
291
+ }
292
+ const timeout = setTimeout(() => {
293
+ pendingRequests.delete(nonce);
294
+ reject(BasisTheoryError("REQUEST_TIMEOUT", `Request timeout: ${type} (${config.timeout || 5e3}ms)`));
295
+ }, config.timeout || 5e3);
296
+ pendingRequests.set(nonce, { resolve, reject, timeout, timestamp });
297
+ log2("Sending request", { type, nonce });
298
+ iframe.contentWindow.postMessage({ type, nonce, timestamp, payload }, config.targetOrigin);
299
+ });
300
+ };
301
+ const sendRequest = async (type, payload) => {
302
+ let lastError = null;
303
+ for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
304
+ try {
305
+ return await sendRequestOnce(type, payload);
306
+ } catch (error) {
307
+ lastError = error;
308
+ const errorMessage = error.message;
309
+ const isHandlerError = errorMessage.startsWith("[HANDLER_ERROR]");
310
+ const isClientDestroyed = errorMessage === "Client destroyed";
311
+ const isIframeNotReady = errorMessage === "Iframe not ready";
312
+ if (isHandlerError || isClientDestroyed || isIframeNotReady) {
313
+ log2("Not retrying error", {
314
+ error: errorMessage,
315
+ reason: "Non-retriable error"
316
+ });
317
+ throw error;
318
+ }
319
+ if (attempt < retryConfig.maxRetries) {
320
+ const delay = getRetryDelay(attempt, retryConfig);
321
+ log2("Retrying request", {
322
+ attempt: attempt + 1,
323
+ maxRetries: retryConfig.maxRetries,
324
+ delayMs: delay,
325
+ error: errorMessage
326
+ });
327
+ await sleep(delay);
328
+ }
329
+ }
330
+ }
331
+ throw lastError || new Error(`Request failed after ${retryConfig.maxRetries} retries: ${type}`);
332
+ };
333
+ const ping = async (timeout = 1e3) => {
334
+ const start = Date.now();
335
+ log2("Starting health check", {
336
+ timeoutMs: timeout,
337
+ iframeReady: !!iframe.contentWindow,
338
+ targetOrigin: config.targetOrigin
339
+ });
340
+ try {
341
+ await sendRequestOnce("SYS_PING", void 0);
342
+ const latency = Date.now() - start;
343
+ log2("Health check passed", { latencyMs: latency });
344
+ return latency;
345
+ } catch (error) {
346
+ const errorMsg = `Health check failed after ${Date.now() - start}ms: ${error.message}. Target: ${config.targetOrigin}`;
347
+ if (config.debug) {
348
+ log2(`❌ [PostMessage Client] ${errorMsg}`);
349
+ log2(`📊 [PostMessage Client] Pending requests: ${pendingRequests.size}`);
350
+ }
351
+ throw BasisTheoryError("HEALTH_CHECK_FAILED", errorMsg);
352
+ }
353
+ };
354
+ const subscribe = (type, callback) => {
355
+ if (!eventSubscribers.has(type)) {
356
+ eventSubscribers.set(type, /* @__PURE__ */ new Set());
357
+ }
358
+ eventSubscribers.get(type).add(callback);
359
+ return () => {
360
+ const subscribers = eventSubscribers.get(type);
361
+ if (subscribers) {
362
+ subscribers.delete(callback);
363
+ if (subscribers.size === 0) {
364
+ eventSubscribers.delete(type);
365
+ }
366
+ }
367
+ };
368
+ };
369
+ const destroy = () => {
370
+ window.removeEventListener("message", handleMessage);
371
+ pendingRequests.forEach(({ timeout, reject }) => {
372
+ clearTimeout(timeout);
373
+ reject(BasisTheoryError("CLIENT_DESTROYED", "Client destroyed"));
374
+ });
375
+ pendingRequests.clear();
376
+ eventSubscribers.clear();
377
+ };
378
+ return {
379
+ sendRequest,
380
+ ping,
381
+ subscribe,
382
+ destroy,
383
+ getPendingCount: () => pendingRequests.size
384
+ };
385
+ };
386
+ const createMounter = () => {
387
+ const applyIframeStyles = (iframe) => {
388
+ iframe.style.border = "none";
389
+ iframe.style.width = "100%";
390
+ iframe.style.height = "100%";
391
+ iframe.style.display = "block";
392
+ };
393
+ return {
394
+ mount: async (container, iframe, _config) => {
395
+ applyIframeStyles(iframe);
396
+ container.appendChild(iframe);
397
+ },
398
+ unmount: (iframe) => {
399
+ iframe.remove();
400
+ }
401
+ };
402
+ };
403
+ const createIframe = (config) => {
404
+ const iframe = document.createElement("iframe");
405
+ iframe.src = config.src;
406
+ iframe.setAttribute("sandbox", config.sandbox);
407
+ iframe.title = config.title;
408
+ iframe.setAttribute("allow", "payment *");
409
+ iframe.setAttribute("importance", "high");
410
+ iframe.style.opacity = "0";
411
+ return iframe;
412
+ };
413
+ const createEventDispatcher = (config) => {
414
+ const { elementType, elementId, client, eventTarget } = config;
415
+ const createBaseDetail = () => ({
416
+ elementType,
417
+ elementId,
418
+ timestamp: Date.now()
419
+ });
420
+ const dispatch = (eventType, detail) => {
421
+ const event = new CustomEvent(eventType, {
422
+ detail: {
423
+ ...createBaseDetail(),
424
+ ...detail
425
+ }
426
+ });
427
+ eventTarget.dispatchEvent(event);
428
+ };
429
+ const setupSubscriptions = () => {
430
+ client.subscribe("EVENT_READY", () => {
431
+ dispatch("ready", {});
432
+ });
433
+ client.subscribe("EVENT_VALUE_CHANGED", (payload) => {
434
+ const changeDetail = {
435
+ isValid: payload.isValid,
436
+ isEmpty: payload.isEmpty,
437
+ error: payload.error || null
438
+ };
439
+ if (elementType === "cardNumber") {
440
+ const cardDetail = {
441
+ ...changeDetail,
442
+ cardBrand: payload.cardBrand,
443
+ last4: payload.last4,
444
+ bin: payload.bin,
445
+ cvvLengths: payload.cvvLengths,
446
+ potentialBrands: payload.potentialBrands,
447
+ matchStrength: payload.matchStrength
448
+ };
449
+ dispatch("change", cardDetail);
450
+ } else if (elementType === "card") {
451
+ dispatch("change", {
452
+ ...changeDetail,
453
+ cardBrand: payload.cardBrand,
454
+ last4: payload.last4,
455
+ bin: payload.bin,
456
+ errors: payload.errors || null
457
+ });
458
+ } else {
459
+ dispatch("change", changeDetail);
460
+ }
461
+ });
462
+ client.subscribe("EVENT_FOCUS", () => {
463
+ dispatch("focus", {});
464
+ });
465
+ client.subscribe("EVENT_BLUR", () => {
466
+ dispatch("blur", {});
467
+ });
468
+ client.subscribe("EVENT_ERROR", (payload) => {
469
+ dispatch("error", {
470
+ code: payload.code,
471
+ message: payload.message,
472
+ status: payload.status,
473
+ validationErrors: payload.validationErrors,
474
+ metadata: payload.metadata || {}
475
+ });
476
+ });
477
+ };
478
+ const dispatchError = (code, message, error) => {
479
+ dispatch("error", {
480
+ code,
481
+ message,
482
+ metadata: {
483
+ source: "initialization",
484
+ originalError: error
485
+ }
486
+ });
487
+ };
488
+ return {
489
+ setupSubscriptions,
490
+ dispatchError
491
+ };
492
+ };
493
+ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
494
+ let iframe = null;
495
+ let client = null;
496
+ let mounter = null;
497
+ let isMounted = false;
498
+ let container = null;
499
+ let eventDispatcher = null;
500
+ const eventTarget = new EventTarget();
501
+ const id = coordinatorElementId;
502
+ const type = "cardNumber";
503
+ const setupEventListeners = () => {
504
+ if (!client) {
505
+ return;
506
+ }
507
+ eventDispatcher = createEventDispatcher({
508
+ elementType: type,
509
+ elementId: id,
510
+ client,
511
+ eventTarget
512
+ });
513
+ eventDispatcher.setupSubscriptions();
514
+ };
515
+ return {
516
+ id,
517
+ type,
518
+ get mounted() {
519
+ return isMounted;
520
+ },
521
+ mount: async (selector) => {
522
+ if (isMounted) {
523
+ throw ElementError("Element is already mounted");
524
+ }
525
+ container = typeof selector === "string" ? document.querySelector(selector) : selector;
526
+ if (!container) {
527
+ throw ElementError(`Container not found: ${selector}`);
528
+ }
529
+ const parentOrigin = window.location.origin;
530
+ const iframeUrl = new URL(
531
+ `${sdkConfig.elementsBaseUrl}/card-number.html`
532
+ );
533
+ iframeUrl.searchParams.set("parentOrigin", parentOrigin);
534
+ iframeUrl.searchParams.set("elementId", id);
535
+ iframeUrl.searchParams.set(
536
+ "measurePerformance",
537
+ String(sdkConfig.measurePerformance || false)
538
+ );
539
+ iframeUrl.searchParams.set("debug", String(sdkConfig.debug || false));
540
+ iframe = createIframe({
541
+ src: iframeUrl.toString(),
542
+ sandbox: "allow-scripts allow-same-origin",
543
+ title: options.ariaLabel || "Card Number Input"
544
+ });
545
+ iframe.setAttribute("data-bt-element-id", id);
546
+ iframe.setAttribute("data-theme-mode", sdkConfig.themeMode || "auto");
547
+ client = createPostMessageClient(iframe, {
548
+ targetOrigin: sdkConfig.iframeOrigin,
549
+ timeout: sdkConfig.timeoutMs,
550
+ retries: sdkConfig.retryConfig.maxRetries,
551
+ debug: sdkConfig.debug
552
+ });
553
+ mounter = createMounter();
554
+ setupEventListeners();
555
+ const readyPromise = new Promise((resolve, reject) => {
556
+ const timeout = setTimeout(() => {
557
+ reject(
558
+ ElementError(
559
+ `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,
560
+ id,
561
+ { elementType: type, timeoutMs: sdkConfig.timeoutMs }
562
+ )
563
+ );
564
+ }, sdkConfig.timeoutMs);
565
+ const handleReady = () => {
566
+ clearTimeout(timeout);
567
+ resolve();
568
+ };
569
+ eventTarget.addEventListener("ready", handleReady, { once: true });
570
+ });
571
+ mounter.mount(container, iframe, {
572
+ timeoutMs: sdkConfig.timeoutMs
573
+ });
574
+ isMounted = true;
575
+ try {
576
+ await ensureCoordinatorReady();
577
+ await readyPromise;
578
+ await client.sendRequest("CMD_SET_CONFIG", {
579
+ elementId: id,
580
+ // Include element ID for validation
581
+ apiKey,
582
+ apiTimeout: sdkConfig.timeoutMs,
583
+ apiRetries: sdkConfig.retryConfig.maxRetries,
584
+ theme: sdkConfig.theme,
585
+ darkTheme: sdkConfig.darkTheme,
586
+ themeMode: sdkConfig.themeMode,
587
+ placeholder: options.placeholder,
588
+ disabled: options.disabled,
589
+ readOnly: options.readOnly,
590
+ debug: sdkConfig.debug
591
+ });
592
+ if (iframe) iframe.style.opacity = "1";
593
+ } catch (error) {
594
+ if (eventDispatcher) {
595
+ eventDispatcher.dispatchError(
596
+ "MOUNT_ERROR",
597
+ error.message,
598
+ error
599
+ );
600
+ }
601
+ isMounted = false;
602
+ if (client) {
603
+ client.destroy();
604
+ client = null;
605
+ }
606
+ if (mounter && iframe) {
607
+ mounter.unmount(iframe);
608
+ iframe = null;
609
+ mounter = null;
610
+ }
611
+ eventDispatcher = null;
612
+ container = null;
613
+ throw error;
614
+ }
615
+ },
616
+ unmount: () => {
617
+ if (!isMounted) {
618
+ throw ElementError("Element is not mounted");
619
+ }
620
+ if (client) {
621
+ client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
622
+ log("Failed to send clear cache command", {
623
+ error: error.message
624
+ });
625
+ });
626
+ client.destroy();
627
+ client = null;
628
+ }
629
+ if (mounter && iframe) {
630
+ mounter.unmount(iframe);
631
+ iframe = null;
632
+ mounter = null;
633
+ }
634
+ eventDispatcher = null;
635
+ container = null;
636
+ isMounted = false;
637
+ },
638
+ update: async (newOptions) => {
639
+ if (!isMounted || !client) {
640
+ throw ElementError("Element must be mounted before updating");
641
+ }
642
+ const updatePayload = {
643
+ placeholder: newOptions.placeholder,
644
+ disabled: newOptions.disabled,
645
+ readOnly: newOptions.readOnly
646
+ };
647
+ if (newOptions.themeMode !== void 0) {
648
+ updatePayload.themeMode = newOptions.themeMode;
649
+ updatePayload.theme = sdkConfig.theme;
650
+ updatePayload.darkTheme = sdkConfig.darkTheme;
651
+ }
652
+ await client.sendRequest("CMD_UPDATE_CONFIG", updatePayload);
653
+ },
654
+ focus: () => {
655
+ if (!isMounted || !client) {
656
+ throw ElementError("Element must be mounted before focusing");
657
+ }
658
+ client.sendRequest("CMD_FOCUS", {});
659
+ },
660
+ blur: () => {
661
+ if (!isMounted || !client) {
662
+ throw ElementError("Element must be mounted before blurring");
663
+ }
664
+ client.sendRequest("CMD_BLUR", {});
665
+ },
666
+ clear: () => {
667
+ if (!isMounted || !client) {
668
+ throw ElementError("Element must be mounted before clearing");
669
+ }
670
+ client.sendRequest("CMD_CLEAR", {});
671
+ },
672
+ on: (eventType, listener) => {
673
+ eventTarget.addEventListener(
674
+ eventType,
675
+ listener
676
+ );
677
+ return () => eventTarget.removeEventListener(
678
+ eventType,
679
+ listener
680
+ );
681
+ }
682
+ };
683
+ };
684
+ const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
685
+ let iframe = null;
686
+ let client = null;
687
+ let mounter = null;
688
+ let isMounted = false;
689
+ let container = null;
690
+ let eventDispatcher = null;
691
+ const eventTarget = new EventTarget();
692
+ const id = coordinatorElementId;
693
+ const type = "cvv";
694
+ const setupEventListeners = () => {
695
+ if (!client) return;
696
+ eventDispatcher = createEventDispatcher({
697
+ elementType: type,
698
+ elementId: id,
699
+ client,
700
+ eventTarget
701
+ });
702
+ eventDispatcher.setupSubscriptions();
703
+ };
704
+ return {
705
+ id,
706
+ type,
707
+ get mounted() {
708
+ return isMounted;
709
+ },
710
+ mount: async (selector) => {
711
+ if (isMounted) {
712
+ throw ElementError("Element is already mounted", id);
713
+ }
714
+ container = typeof selector === "string" ? document.querySelector(selector) : selector;
715
+ if (!container) {
716
+ throw ElementError(`Container not found: ${selector}`);
717
+ }
718
+ const parentOrigin = window.location.origin;
719
+ const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/cvv.html`);
720
+ iframeUrl.searchParams.set("parentOrigin", parentOrigin);
721
+ iframeUrl.searchParams.set("elementId", id);
722
+ iframeUrl.searchParams.set(
723
+ "measurePerformance",
724
+ String(sdkConfig.measurePerformance || false)
725
+ );
726
+ iframeUrl.searchParams.set("debug", String(sdkConfig.debug || false));
727
+ iframe = createIframe({
728
+ src: iframeUrl.toString(),
729
+ sandbox: "allow-scripts allow-same-origin",
730
+ title: options.ariaLabel || "CVV Input"
731
+ });
732
+ iframe.setAttribute("data-bt-element-id", id);
733
+ client = createPostMessageClient(iframe, {
734
+ targetOrigin: sdkConfig.iframeOrigin,
735
+ timeout: sdkConfig.timeoutMs,
736
+ retries: sdkConfig.retryConfig.maxRetries,
737
+ debug: sdkConfig.debug
738
+ });
739
+ mounter = createMounter();
740
+ setupEventListeners();
741
+ const readyPromise = new Promise((resolve, reject) => {
742
+ const timeout = setTimeout(() => {
743
+ reject(
744
+ ElementError(
745
+ `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,
746
+ id,
747
+ { elementType: type, timeoutMs: sdkConfig.timeoutMs }
748
+ )
749
+ );
750
+ }, sdkConfig.timeoutMs);
751
+ const handleReady = () => {
752
+ clearTimeout(timeout);
753
+ resolve();
754
+ };
755
+ eventTarget.addEventListener("ready", handleReady, { once: true });
756
+ });
757
+ mounter.mount(container, iframe, {
758
+ timeoutMs: sdkConfig.timeoutMs
759
+ });
760
+ isMounted = true;
761
+ try {
762
+ await ensureCoordinatorReady();
763
+ await readyPromise;
764
+ await client.sendRequest("CMD_SET_CONFIG", {
765
+ elementId: id,
766
+ // Include element ID for validation
767
+ theme: sdkConfig.theme,
768
+ darkTheme: sdkConfig.darkTheme,
769
+ themeMode: sdkConfig.themeMode,
770
+ apiKey,
771
+ apiTimeout: sdkConfig.timeoutMs,
772
+ apiRetries: sdkConfig.retryConfig.maxRetries,
773
+ placeholder: options.placeholder,
774
+ disabled: options.disabled,
775
+ readOnly: options.readOnly,
776
+ showToggle: options.showToggle,
777
+ debug: sdkConfig.debug
778
+ });
779
+ if (iframe) iframe.style.opacity = "1";
780
+ } catch (error) {
781
+ if (eventDispatcher) {
782
+ eventDispatcher.dispatchError(
783
+ "MOUNT_ERROR",
784
+ error.message,
785
+ error
786
+ );
787
+ }
788
+ isMounted = false;
789
+ if (client) {
790
+ client.destroy();
791
+ client = null;
792
+ }
793
+ if (mounter && iframe) {
794
+ mounter.unmount(iframe);
795
+ iframe = null;
796
+ mounter = null;
797
+ }
798
+ eventDispatcher = null;
799
+ container = null;
800
+ throw error;
801
+ }
802
+ },
803
+ unmount: () => {
804
+ if (!isMounted) {
805
+ throw ElementError("Element is not mounted");
806
+ }
807
+ if (client) {
808
+ client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
809
+ log("Failed to send clear cache command", {
810
+ error: error.message
811
+ });
812
+ });
813
+ client.destroy();
814
+ client = null;
815
+ }
816
+ if (mounter && iframe) {
817
+ mounter.unmount(iframe);
818
+ iframe = null;
819
+ mounter = null;
820
+ }
821
+ eventDispatcher = null;
822
+ container = null;
823
+ isMounted = false;
824
+ },
825
+ update: async (newOptions) => {
826
+ if (!isMounted || !client) {
827
+ throw ElementError("Element must be mounted before updating", id);
828
+ }
829
+ const updatePayload = {
830
+ placeholder: newOptions.placeholder,
831
+ disabled: newOptions.disabled,
832
+ readOnly: newOptions.readOnly,
833
+ // ✅ SDK auto-coordination: Pass brand info for dynamic maxlength
834
+ cardBrand: newOptions.cardBrand,
835
+ cvvLengths: newOptions.cvvLengths
836
+ };
837
+ if (newOptions.themeMode !== void 0) {
838
+ updatePayload.themeMode = newOptions.themeMode;
839
+ updatePayload.theme = sdkConfig.theme;
840
+ updatePayload.darkTheme = sdkConfig.darkTheme;
841
+ }
842
+ await client.sendRequest("CMD_UPDATE_CONFIG", updatePayload);
843
+ },
844
+ focus: () => {
845
+ if (!isMounted || !client) {
846
+ throw ElementError("Element must be mounted before focusing", id);
847
+ }
848
+ client.sendRequest("CMD_FOCUS", {});
849
+ },
850
+ blur: () => {
851
+ if (!isMounted || !client) {
852
+ throw ElementError("Element must be mounted before blurring", id);
853
+ }
854
+ client.sendRequest("CMD_BLUR", {});
855
+ },
856
+ clear: () => {
857
+ if (!isMounted || !client) {
858
+ throw ElementError("Element must be mounted before clearing", id);
859
+ }
860
+ client.sendRequest("CMD_CLEAR", {});
861
+ },
862
+ on: (eventType, listener) => {
863
+ eventTarget.addEventListener(
864
+ eventType,
865
+ listener
866
+ );
867
+ return () => eventTarget.removeEventListener(
868
+ eventType,
869
+ listener
870
+ );
871
+ }
872
+ };
873
+ };
874
+ const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
875
+ let iframe = null;
876
+ let client = null;
877
+ let mounter = null;
878
+ let isMounted = false;
879
+ let container = null;
880
+ let eventDispatcher = null;
881
+ const eventTarget = new EventTarget();
882
+ const id = coordinatorElementId;
883
+ const type = "expiry";
884
+ const setupEventListeners = () => {
885
+ if (!client) return;
886
+ eventDispatcher = createEventDispatcher({
887
+ elementType: type,
888
+ elementId: id,
889
+ client,
890
+ eventTarget
891
+ });
892
+ eventDispatcher.setupSubscriptions();
893
+ };
894
+ return {
895
+ id,
896
+ type,
897
+ get mounted() {
898
+ return isMounted;
899
+ },
900
+ mount: async (selector) => {
901
+ if (isMounted) {
902
+ throw ElementError("Element is already mounted", id);
903
+ }
904
+ container = typeof selector === "string" ? document.querySelector(selector) : selector;
905
+ if (!container) {
906
+ throw ElementError(`Container not found: ${selector}`);
907
+ }
908
+ const parentOrigin = window.location.origin;
909
+ const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/expiry.html`);
910
+ iframeUrl.searchParams.set("parentOrigin", parentOrigin);
911
+ iframeUrl.searchParams.set("elementId", id);
912
+ iframeUrl.searchParams.set(
913
+ "measurePerformance",
914
+ String(sdkConfig.measurePerformance || false)
915
+ );
916
+ iframeUrl.searchParams.set("debug", String(sdkConfig.debug || false));
917
+ iframe = createIframe({
918
+ src: iframeUrl.toString(),
919
+ sandbox: "allow-scripts allow-same-origin",
920
+ title: options.ariaLabel || "Expiry Date Input"
921
+ });
922
+ iframe.setAttribute("data-bt-element-id", id);
923
+ client = createPostMessageClient(iframe, {
924
+ targetOrigin: sdkConfig.iframeOrigin,
925
+ timeout: sdkConfig.timeoutMs,
926
+ retries: sdkConfig.retryConfig.maxRetries,
927
+ debug: sdkConfig.debug
928
+ });
929
+ mounter = createMounter();
930
+ setupEventListeners();
931
+ const readyPromise = new Promise((resolve, reject) => {
932
+ const timeout = setTimeout(() => {
933
+ reject(
934
+ ElementError(
935
+ `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,
936
+ id,
937
+ { elementType: type, timeoutMs: sdkConfig.timeoutMs }
938
+ )
939
+ );
940
+ }, sdkConfig.timeoutMs);
941
+ const handleReady = () => {
942
+ clearTimeout(timeout);
943
+ resolve();
944
+ };
945
+ eventTarget.addEventListener("ready", handleReady, { once: true });
946
+ });
947
+ mounter.mount(container, iframe, {
948
+ timeoutMs: sdkConfig.timeoutMs
949
+ });
950
+ isMounted = true;
951
+ try {
952
+ await ensureCoordinatorReady();
953
+ await readyPromise;
954
+ await client.sendRequest("CMD_SET_CONFIG", {
955
+ elementId: id,
956
+ // Include element ID for validation
957
+ theme: sdkConfig.theme,
958
+ darkTheme: sdkConfig.darkTheme,
959
+ themeMode: sdkConfig.themeMode,
960
+ apiKey,
961
+ apiTimeout: sdkConfig.timeoutMs,
962
+ apiRetries: sdkConfig.retryConfig.maxRetries,
963
+ placeholder: options.placeholder,
964
+ disabled: options.disabled,
965
+ readOnly: options.readOnly,
966
+ debug: sdkConfig.debug
967
+ });
968
+ if (iframe) iframe.style.opacity = "1";
969
+ } catch (error) {
970
+ if (eventDispatcher) {
971
+ eventDispatcher.dispatchError(
972
+ "MOUNT_ERROR",
973
+ error.message,
974
+ error
975
+ );
976
+ }
977
+ isMounted = false;
978
+ if (client) {
979
+ client.destroy();
980
+ client = null;
981
+ }
982
+ if (mounter && iframe) {
983
+ mounter.unmount(iframe);
984
+ iframe = null;
985
+ mounter = null;
986
+ }
987
+ eventDispatcher = null;
988
+ container = null;
989
+ throw error;
990
+ }
991
+ },
992
+ unmount: () => {
993
+ if (!isMounted) {
994
+ throw ElementError("Element is not mounted");
995
+ }
996
+ if (client) {
997
+ client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
998
+ log("Failed to send clear cache command", {
999
+ error: error.message
1000
+ });
1001
+ });
1002
+ client.destroy();
1003
+ client = null;
1004
+ }
1005
+ if (mounter && iframe) {
1006
+ mounter.unmount(iframe);
1007
+ iframe = null;
1008
+ mounter = null;
1009
+ }
1010
+ eventDispatcher = null;
1011
+ container = null;
1012
+ isMounted = false;
1013
+ },
1014
+ update: async (newOptions) => {
1015
+ if (!isMounted || !client) {
1016
+ throw ElementError("Element must be mounted before updating", id);
1017
+ }
1018
+ const updatePayload = {
1019
+ placeholder: newOptions.placeholder,
1020
+ disabled: newOptions.disabled,
1021
+ readOnly: newOptions.readOnly
1022
+ };
1023
+ if (newOptions.themeMode !== void 0) {
1024
+ updatePayload.themeMode = newOptions.themeMode;
1025
+ updatePayload.theme = sdkConfig.theme;
1026
+ updatePayload.darkTheme = sdkConfig.darkTheme;
1027
+ }
1028
+ await client.sendRequest("CMD_UPDATE_CONFIG", updatePayload);
1029
+ },
1030
+ focus: () => {
1031
+ if (!isMounted || !client) {
1032
+ throw ElementError("Element must be mounted before focusing", id);
1033
+ }
1034
+ client.sendRequest("CMD_FOCUS", {});
1035
+ },
1036
+ blur: () => {
1037
+ if (!isMounted || !client) {
1038
+ throw ElementError("Element must be mounted before blurring", id);
1039
+ }
1040
+ client.sendRequest("CMD_BLUR", {});
1041
+ },
1042
+ clear: () => {
1043
+ if (!isMounted || !client) {
1044
+ throw ElementError("Element must be mounted before clearing", id);
1045
+ }
1046
+ client.sendRequest("CMD_CLEAR", {});
1047
+ },
1048
+ on: (eventType, listener) => {
1049
+ eventTarget.addEventListener(
1050
+ eventType,
1051
+ listener
1052
+ );
1053
+ return () => eventTarget.removeEventListener(
1054
+ eventType,
1055
+ listener
1056
+ );
1057
+ }
1058
+ };
1059
+ };
1060
+ const createTextElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
1061
+ let iframe = null;
1062
+ let client = null;
1063
+ let mounter = null;
1064
+ let isMounted = false;
1065
+ let container = null;
1066
+ let eventDispatcher = null;
1067
+ const eventTarget = new EventTarget();
1068
+ const id = coordinatorElementId;
1069
+ const type = "text";
1070
+ if (options.transform) {
1071
+ if (!Array.isArray(options.transform) || options.transform.length !== 2 || !(options.transform[0] instanceof RegExp) || typeof options.transform[1] !== "string") {
1072
+ throw new TypeError(
1073
+ 'transform must be a tuple of [RegExp, string]. Example: [/-/g, ""]'
1074
+ );
1075
+ }
1076
+ }
1077
+ const setupEventListeners = () => {
1078
+ if (!client) return;
1079
+ eventDispatcher = createEventDispatcher({
1080
+ elementType: type,
1081
+ elementId: id,
1082
+ client,
1083
+ eventTarget
1084
+ });
1085
+ eventDispatcher.setupSubscriptions();
1086
+ };
1087
+ return {
1088
+ id,
1089
+ type,
1090
+ get mounted() {
1091
+ return isMounted;
1092
+ },
1093
+ mount: async (selector) => {
1094
+ if (isMounted) {
1095
+ throw ElementError("Element is already mounted", id);
1096
+ }
1097
+ container = typeof selector === "string" ? document.querySelector(selector) : selector;
1098
+ if (!container) {
1099
+ throw ElementError(`Container not found: ${selector}`);
1100
+ }
1101
+ const parentOrigin = window.location.origin;
1102
+ const iframeUrl = new URL(`${sdkConfig.elementsBaseUrl}/text.html`);
1103
+ iframeUrl.searchParams.set("parentOrigin", parentOrigin);
1104
+ iframeUrl.searchParams.set("elementId", id);
1105
+ iframeUrl.searchParams.set(
1106
+ "measurePerformance",
1107
+ String(sdkConfig.measurePerformance || false)
1108
+ );
1109
+ iframeUrl.searchParams.set("debug", String(sdkConfig.debug || false));
1110
+ iframe = createIframe({
1111
+ src: iframeUrl.toString(),
1112
+ sandbox: "allow-scripts allow-same-origin",
1113
+ title: options.ariaLabel || "Text Input"
1114
+ });
1115
+ iframe.setAttribute("data-bt-element-id", id);
1116
+ client = createPostMessageClient(iframe, {
1117
+ targetOrigin: sdkConfig.iframeOrigin,
1118
+ timeout: sdkConfig.timeoutMs,
1119
+ retries: sdkConfig.retryConfig.maxRetries,
1120
+ debug: sdkConfig.debug
1121
+ });
1122
+ mounter = createMounter();
1123
+ setupEventListeners();
1124
+ const readyPromise = new Promise((resolve, reject) => {
1125
+ const timeout = setTimeout(() => {
1126
+ reject(
1127
+ ElementError(
1128
+ `Iframe EVENT_READY not received within ${sdkConfig.timeoutMs}ms`,
1129
+ id,
1130
+ { elementType: type, timeoutMs: sdkConfig.timeoutMs }
1131
+ )
1132
+ );
1133
+ }, sdkConfig.timeoutMs);
1134
+ const handleReady = () => {
1135
+ clearTimeout(timeout);
1136
+ resolve();
1137
+ };
1138
+ eventTarget.addEventListener("ready", handleReady, { once: true });
1139
+ });
1140
+ mounter.mount(container, iframe, {
1141
+ timeoutMs: sdkConfig.timeoutMs
1142
+ });
1143
+ isMounted = true;
1144
+ try {
1145
+ await ensureCoordinatorReady();
1146
+ await readyPromise;
1147
+ const serializedTransform = options.transform ? {
1148
+ pattern: options.transform[0].source,
1149
+ flags: options.transform[0].flags,
1150
+ replacement: options.transform[1]
1151
+ } : void 0;
1152
+ const serializedMask = options.mask ? options.mask.map((item) => {
1153
+ if (typeof item === "string") {
1154
+ return item;
1155
+ } else if (item instanceof RegExp) {
1156
+ return { source: item.source, flags: item.flags };
1157
+ }
1158
+ return item;
1159
+ }) : void 0;
1160
+ await client.sendRequest("CMD_SET_CONFIG", {
1161
+ elementId: id,
1162
+ // Include element ID for validation
1163
+ theme: sdkConfig.theme,
1164
+ darkTheme: sdkConfig.darkTheme,
1165
+ themeMode: sdkConfig.themeMode,
1166
+ apiKey,
1167
+ apiTimeout: sdkConfig.timeoutMs,
1168
+ apiRetries: sdkConfig.retryConfig.maxRetries,
1169
+ placeholder: options.placeholder,
1170
+ disabled: options.disabled,
1171
+ readOnly: options.readOnly,
1172
+ // Text-specific options
1173
+ validation: options.validation,
1174
+ required: options.required,
1175
+ maxLength: options.maxLength,
1176
+ password: options.password,
1177
+ inputMode: options.inputMode,
1178
+ mask: serializedMask,
1179
+ transform: serializedTransform,
1180
+ debug: sdkConfig.debug
1181
+ });
1182
+ if (iframe) iframe.style.opacity = "1";
1183
+ } catch (error) {
1184
+ if (eventDispatcher) {
1185
+ eventDispatcher.dispatchError(
1186
+ "MOUNT_ERROR",
1187
+ error.message,
1188
+ error
1189
+ );
1190
+ }
1191
+ isMounted = false;
1192
+ if (client) {
1193
+ client.destroy();
1194
+ client = null;
1195
+ }
1196
+ if (mounter && iframe) {
1197
+ mounter.unmount(iframe);
1198
+ iframe = null;
1199
+ mounter = null;
1200
+ }
1201
+ eventDispatcher = null;
1202
+ container = null;
1203
+ throw error;
1204
+ }
1205
+ },
1206
+ unmount: () => {
1207
+ if (!isMounted) {
1208
+ throw ElementError("Element is not mounted");
1209
+ }
1210
+ if (client) {
1211
+ client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
1212
+ log("Failed to send clear cache command", {
1213
+ error: error.message
1214
+ });
1215
+ });
1216
+ client.destroy();
1217
+ client = null;
1218
+ }
1219
+ if (mounter && iframe) {
1220
+ mounter.unmount(iframe);
1221
+ iframe = null;
1222
+ mounter = null;
1223
+ }
1224
+ eventDispatcher = null;
1225
+ container = null;
1226
+ isMounted = false;
1227
+ },
1228
+ update: async (newOptions) => {
1229
+ if (!isMounted || !client) {
1230
+ throw ElementError("Element must be mounted before updating", id);
1231
+ }
1232
+ const updatePayload = {
1233
+ placeholder: newOptions.placeholder,
1234
+ disabled: newOptions.disabled,
1235
+ readOnly: newOptions.readOnly,
1236
+ // Text-specific UI updates (not data schema)
1237
+ required: newOptions.required,
1238
+ maxLength: newOptions.maxLength,
1239
+ password: newOptions.password,
1240
+ inputMode: newOptions.inputMode
1241
+ };
1242
+ if (newOptions.themeMode !== void 0) {
1243
+ updatePayload.themeMode = newOptions.themeMode;
1244
+ updatePayload.theme = sdkConfig.theme;
1245
+ updatePayload.darkTheme = sdkConfig.darkTheme;
1246
+ }
1247
+ await client.sendRequest("CMD_UPDATE_CONFIG", updatePayload);
1248
+ },
1249
+ focus: () => {
1250
+ if (!isMounted || !client) {
1251
+ throw ElementError("Element must be mounted before focusing", id);
1252
+ }
1253
+ client.sendRequest("CMD_FOCUS", {});
1254
+ },
1255
+ blur: () => {
1256
+ if (!isMounted || !client) {
1257
+ throw ElementError("Element must be mounted before blurring", id);
1258
+ }
1259
+ client.sendRequest("CMD_BLUR", {});
1260
+ },
1261
+ clear: () => {
1262
+ if (!isMounted || !client) {
1263
+ throw ElementError("Element must be mounted before clearing", id);
1264
+ }
1265
+ client.sendRequest("CMD_CLEAR", {});
1266
+ },
1267
+ on: (eventType, listener) => {
1268
+ eventTarget.addEventListener(
1269
+ eventType,
1270
+ listener
1271
+ );
1272
+ return () => eventTarget.removeEventListener(
1273
+ eventType,
1274
+ listener
1275
+ );
1276
+ }
1277
+ };
1278
+ };
1279
+ const isPrimitive = (value) => value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1280
+ const isElement = (value) => {
1281
+ if (typeof value !== "object" || value === null) return false;
1282
+ const v = value;
1283
+ return typeof v.mount === "function" && typeof v.update === "function" && typeof v.on === "function";
1284
+ };
1285
+ const resolveElementReferences = (value) => {
1286
+ if (isPrimitive(value)) return value;
1287
+ if (isElement(value)) {
1288
+ if (!value.id) {
1289
+ throw ElementError(
1290
+ "Element is missing id property. Ensure createElement is called after the SDK loads and all elements are mounted before calling tokenize()."
1291
+ );
1292
+ }
1293
+ return value.id;
1294
+ }
1295
+ if (Array.isArray(value)) return value.map(resolveElementReferences);
1296
+ if (typeof value === "object" && value !== null) {
1297
+ const resolved = {};
1298
+ for (const [key, v] of Object.entries(value)) {
1299
+ resolved[key] = resolveElementReferences(v);
1300
+ }
1301
+ return resolved;
1302
+ }
1303
+ return value;
1304
+ };
1305
+ const DEFAULT_THEME = {
1306
+ colors: {
1307
+ primary: "#3b82f6",
1308
+ // blue-500
1309
+ error: "#ef4444",
1310
+ // red-500
1311
+ success: "#10b981",
1312
+ // emerald-500
1313
+ text: {
1314
+ primary: "#1f2937",
1315
+ // gray-800
1316
+ secondary: "#6b7280",
1317
+ // gray-500
1318
+ placeholder: "#9ca3af",
1319
+ // gray-400
1320
+ disabled: "#d1d5db"
1321
+ // gray-300
1322
+ },
1323
+ background: {
1324
+ default: "#ffffff",
1325
+ hover: "#f9fafb",
1326
+ // gray-50
1327
+ focus: "#eff6ff",
1328
+ // blue-50
1329
+ disabled: "#f9fafb"
1330
+ // gray-50
1331
+ },
1332
+ border: {
1333
+ default: "#d1d5db",
1334
+ // gray-300
1335
+ hover: "#9ca3af",
1336
+ // gray-400
1337
+ focus: "#3b82f6",
1338
+ // blue-500
1339
+ error: "#ef4444"
1340
+ // red-500
1341
+ }
1342
+ },
1343
+ typography: {
1344
+ fontFamily: '"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
1345
+ fontSize: {
1346
+ sm: "14px",
1347
+ base: "16px",
1348
+ lg: "18px"
1349
+ },
1350
+ fontWeight: {
1351
+ normal: "400",
1352
+ medium: "500",
1353
+ semibold: "600"
1354
+ },
1355
+ lineHeight: {
1356
+ tight: "1.25",
1357
+ normal: "1.5",
1358
+ relaxed: "1.75"
1359
+ }
1360
+ },
1361
+ spacing: {
1362
+ xs: "4px",
1363
+ sm: "8px",
1364
+ md: "12px",
1365
+ lg: "16px",
1366
+ xl: "24px"
1367
+ },
1368
+ borders: {
1369
+ radius: {
1370
+ none: "0",
1371
+ sm: "2px",
1372
+ base: "4px",
1373
+ md: "6px",
1374
+ lg: "8px",
1375
+ full: "9999px"
1376
+ },
1377
+ width: {
1378
+ thin: "1px",
1379
+ base: "2px",
1380
+ thick: "3px"
1381
+ }
1382
+ },
1383
+ shadows: {
1384
+ none: "none",
1385
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
1386
+ base: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)",
1387
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
1388
+ lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
1389
+ focus: "0 0 0 3px rgba(59, 130, 246, 0.1)"
1390
+ // blue-500 with alpha
1391
+ },
1392
+ transitions: {
1393
+ fast: "150ms cubic-bezier(0.4, 0, 0.2, 1)",
1394
+ base: "200ms cubic-bezier(0.4, 0, 0.2, 1)",
1395
+ slow: "300ms cubic-bezier(0.4, 0, 0.2, 1)"
1396
+ }
1397
+ };
1398
+ const mergeWithDefaultTheme = (userTheme) => {
1399
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1400
+ if (!userTheme) return DEFAULT_THEME;
1401
+ return {
1402
+ colors: {
1403
+ primary: ((_a = userTheme.colors) == null ? void 0 : _a.primary) ?? DEFAULT_THEME.colors.primary,
1404
+ error: ((_b = userTheme.colors) == null ? void 0 : _b.error) ?? DEFAULT_THEME.colors.error,
1405
+ success: ((_c = userTheme.colors) == null ? void 0 : _c.success) ?? DEFAULT_THEME.colors.success,
1406
+ text: {
1407
+ ...DEFAULT_THEME.colors.text,
1408
+ ...(_d = userTheme.colors) == null ? void 0 : _d.text
1409
+ },
1410
+ background: {
1411
+ ...DEFAULT_THEME.colors.background,
1412
+ ...(_e = userTheme.colors) == null ? void 0 : _e.background
1413
+ },
1414
+ border: {
1415
+ ...DEFAULT_THEME.colors.border,
1416
+ ...(_f = userTheme.colors) == null ? void 0 : _f.border
1417
+ }
1418
+ },
1419
+ typography: {
1420
+ ...DEFAULT_THEME.typography,
1421
+ ...userTheme.typography,
1422
+ fontSize: {
1423
+ ...DEFAULT_THEME.typography.fontSize,
1424
+ ...(_g = userTheme.typography) == null ? void 0 : _g.fontSize
1425
+ },
1426
+ fontWeight: {
1427
+ ...DEFAULT_THEME.typography.fontWeight,
1428
+ ...(_h = userTheme.typography) == null ? void 0 : _h.fontWeight
1429
+ },
1430
+ lineHeight: {
1431
+ ...DEFAULT_THEME.typography.lineHeight,
1432
+ ...(_i = userTheme.typography) == null ? void 0 : _i.lineHeight
1433
+ }
1434
+ },
1435
+ spacing: { ...DEFAULT_THEME.spacing, ...userTheme.spacing },
1436
+ borders: {
1437
+ radius: {
1438
+ ...DEFAULT_THEME.borders.radius,
1439
+ ...(_j = userTheme.borders) == null ? void 0 : _j.radius
1440
+ },
1441
+ width: {
1442
+ ...DEFAULT_THEME.borders.width,
1443
+ ...(_k = userTheme.borders) == null ? void 0 : _k.width
1444
+ }
1445
+ },
1446
+ shadows: { ...DEFAULT_THEME.shadows, ...userTheme.shadows },
1447
+ transitions: { ...DEFAULT_THEME.transitions, ...userTheme.transitions }
1448
+ };
1449
+ };
1450
+ const warnUnknownKeys = (label, obj, knownKeys, deprecatedKeys) => {
1451
+ for (const key of Object.keys(obj)) {
1452
+ if (knownKeys.has(key)) {
1453
+ continue;
1454
+ }
1455
+ if (deprecatedKeys == null ? void 0 : deprecatedKeys.has(key)) {
1456
+ console.warn(
1457
+ `[BasisTheory] Deprecated option "${key}" in ${label}. ${deprecatedKeys.get(key)}`
1458
+ );
1459
+ continue;
1460
+ }
1461
+ console.warn(
1462
+ `[BasisTheory] Unknown option "${key}" in ${label}. This option will be ignored. See https://developers.basistheory.com/docs/ for supported options.`
1463
+ );
1464
+ }
1465
+ };
1466
+ const KNOWN_SDK_KEYS = /* @__PURE__ */ new Set([
1467
+ "elementsBaseUrl",
1468
+ "apiBaseUrl",
1469
+ "debug",
1470
+ "measurePerformance",
1471
+ "timeoutMs",
1472
+ "retryConfig",
1473
+ "themeMode",
1474
+ "theme",
1475
+ "darkTheme"
1476
+ ]);
1477
+ const DEPRECATED_SDK_KEYS = /* @__PURE__ */ new Map([
1478
+ ["iframeOrigin", 'Use "elementsBaseUrl" instead.']
1479
+ ]);
1480
+ const validateAPIKey = (apiKey) => {
1481
+ if (!apiKey || typeof apiKey !== "string" || apiKey.trim().length === 0) {
1482
+ throw ConfigurationError("API key is required");
1483
+ }
1484
+ };
1485
+ const validateSDKConfig = (options = {}) => {
1486
+ var _a, _b, _c;
1487
+ warnUnknownKeys(
1488
+ "SDKOptions",
1489
+ options,
1490
+ KNOWN_SDK_KEYS,
1491
+ DEPRECATED_SDK_KEYS
1492
+ );
1493
+ const baseUrl = options.elementsBaseUrl || options.iframeOrigin;
1494
+ if (baseUrl !== void 0) {
1495
+ if (typeof baseUrl !== "string" || baseUrl.trim().length === 0) {
1496
+ throw ConfigurationError("elementsBaseUrl must be a non-empty string");
1497
+ }
1498
+ try {
1499
+ new URL(baseUrl);
1500
+ } catch {
1501
+ throw ConfigurationError("elementsBaseUrl must be a valid URL");
1502
+ }
1503
+ }
1504
+ if (options.apiBaseUrl !== void 0) {
1505
+ if (typeof options.apiBaseUrl !== "string" || options.apiBaseUrl.trim().length === 0) {
1506
+ throw ConfigurationError("apiBaseUrl must be a non-empty string");
1507
+ }
1508
+ try {
1509
+ new URL(options.apiBaseUrl);
1510
+ } catch {
1511
+ throw ConfigurationError("apiBaseUrl must be a valid URL");
1512
+ }
1513
+ }
1514
+ if (options.debug !== void 0 && typeof options.debug !== "boolean") {
1515
+ throw ConfigurationError("debug must be a boolean");
1516
+ }
1517
+ if (options.measurePerformance !== void 0 && typeof options.measurePerformance !== "boolean") {
1518
+ throw ConfigurationError("measurePerformance must be a boolean");
1519
+ }
1520
+ if (options.timeoutMs !== void 0) {
1521
+ if (typeof options.timeoutMs !== "number" || options.timeoutMs <= 0) {
1522
+ throw ConfigurationError("timeoutMs must be a positive number");
1523
+ }
1524
+ }
1525
+ const retryConfig = {
1526
+ maxRetries: ((_a = options.retryConfig) == null ? void 0 : _a.maxRetries) ?? 3,
1527
+ delayMs: ((_b = options.retryConfig) == null ? void 0 : _b.delayMs) ?? 1e3,
1528
+ backoffFactor: ((_c = options.retryConfig) == null ? void 0 : _c.backoffFactor) ?? 2
1529
+ };
1530
+ if (typeof retryConfig.maxRetries !== "number" || retryConfig.maxRetries < 0) {
1531
+ throw ConfigurationError("maxRetries must be a non-negative number");
1532
+ }
1533
+ if (typeof retryConfig.delayMs !== "number" || retryConfig.delayMs <= 0) {
1534
+ throw ConfigurationError("retryConfig.delayMs must be a positive number");
1535
+ }
1536
+ if (typeof retryConfig.backoffFactor !== "number" || retryConfig.backoffFactor < 1) {
1537
+ throw ConfigurationError("retryConfig.backoffFactor must be >= 1");
1538
+ }
1539
+ const themeMode = options.themeMode || "auto";
1540
+ if (!["light", "dark", "auto"].includes(themeMode)) {
1541
+ throw ConfigurationError('themeMode must be "light", "dark", or "auto"');
1542
+ }
1543
+ const finalBaseUrl = baseUrl || "https://js.basistheory.com/3.0.0-beta.2/elements";
1544
+ const iframeOrigin = new URL(finalBaseUrl).origin;
1545
+ return {
1546
+ iframeOrigin,
1547
+ elementsBaseUrl: finalBaseUrl,
1548
+ apiBaseUrl: options.apiBaseUrl,
1549
+ // Optional - auto-detects if not provided
1550
+ debug: options.debug ?? false,
1551
+ measurePerformance: options.measurePerformance ?? false,
1552
+ timeoutMs: options.timeoutMs || 3e4,
1553
+ // 30s default for slow 3G connections
1554
+ themeMode,
1555
+ theme: mergeWithDefaultTheme(options.theme),
1556
+ // Light theme or only theme
1557
+ darkTheme: options.darkTheme ? mergeWithDefaultTheme(options.darkTheme) : void 0,
1558
+ // Optional dark theme
1559
+ retryConfig
1560
+ };
1561
+ };
1562
+ const COMMON_ELEMENT_KEYS = /* @__PURE__ */ new Set([
1563
+ "placeholder",
1564
+ "ariaLabel",
1565
+ "disabled",
1566
+ "readOnly"
1567
+ ]);
1568
+ const ELEMENT_TYPE_KEYS = {
1569
+ cardNumber: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS]),
1570
+ cvv: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS, "cardBrand", "cvvLengths"]),
1571
+ // Internal coordination props
1572
+ expiry: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS]),
1573
+ text: /* @__PURE__ */ new Set([
1574
+ ...COMMON_ELEMENT_KEYS,
1575
+ "validation",
1576
+ "required",
1577
+ "maxLength",
1578
+ "password",
1579
+ "inputMode",
1580
+ "mask",
1581
+ "transform"
1582
+ ]),
1583
+ card: /* @__PURE__ */ new Set([
1584
+ ...COMMON_ELEMENT_KEYS,
1585
+ "layout",
1586
+ "stackAt",
1587
+ "iconPosition",
1588
+ "cardBrands"
1589
+ ])
1590
+ };
1591
+ const validateElementOptions = (type, options = {}) => {
1592
+ const validKeys = ELEMENT_TYPE_KEYS[type];
1593
+ warnUnknownKeys(
1594
+ `ElementOptions (${type})`,
1595
+ options,
1596
+ validKeys
1597
+ );
1598
+ if (options.placeholder !== void 0) {
1599
+ if (type === "card") {
1600
+ if (typeof options.placeholder !== "object" || options.placeholder === null) {
1601
+ throw ConfigurationError("placeholder for card element must be an object with cardNumber, expiryDate, and cvv keys");
1602
+ }
1603
+ } else if (typeof options.placeholder !== "string") {
1604
+ throw ConfigurationError("placeholder must be a string");
1605
+ }
1606
+ }
1607
+ if (options.disabled !== void 0 && typeof options.disabled !== "boolean") {
1608
+ throw ConfigurationError("disabled must be a boolean");
1609
+ }
1610
+ if (options.readOnly !== void 0 && typeof options.readOnly !== "boolean") {
1611
+ throw ConfigurationError("readOnly must be a boolean");
1612
+ }
1613
+ if (options.ariaLabel !== void 0 && typeof options.ariaLabel !== "string") {
1614
+ throw ConfigurationError("ariaLabel must be a string");
1615
+ }
1616
+ if (type === "text") {
1617
+ if (options.maxLength !== void 0) {
1618
+ if (typeof options.maxLength !== "number" || options.maxLength <= 0) {
1619
+ throw ConfigurationError("maxLength must be a positive number");
1620
+ }
1621
+ }
1622
+ if (options.password !== void 0 && typeof options.password !== "boolean") {
1623
+ throw ConfigurationError("password must be a boolean");
1624
+ }
1625
+ if (options.required !== void 0 && typeof options.required !== "boolean") {
1626
+ throw ConfigurationError("required must be a boolean");
1627
+ }
1628
+ if (options.validation !== void 0 && !(options.validation instanceof RegExp)) {
1629
+ throw ConfigurationError("validation must be a RegExp");
1630
+ }
1631
+ if (options.transform) {
1632
+ if (!Array.isArray(options.transform) || options.transform.length !== 2 || !(options.transform[0] instanceof RegExp) || typeof options.transform[1] !== "string") {
1633
+ throw ConfigurationError(
1634
+ 'transform must be a tuple of [RegExp, string]. Example: [/-/g, ""]'
1635
+ );
1636
+ }
1637
+ }
1638
+ }
1639
+ if (type === "card") {
1640
+ const cardOptions = options;
1641
+ if (cardOptions.layout !== void 0) {
1642
+ if (!["row", "column", "auto"].includes(cardOptions.layout)) {
1643
+ throw ConfigurationError('layout must be "row", "column", or "auto"');
1644
+ }
1645
+ }
1646
+ if (cardOptions.stackAt !== void 0) {
1647
+ if (typeof cardOptions.stackAt !== "number" || cardOptions.stackAt <= 0) {
1648
+ throw ConfigurationError("stackAt must be a positive number");
1649
+ }
1650
+ }
1651
+ if (cardOptions.iconPosition !== void 0) {
1652
+ if (!["left", "right", "none"].includes(cardOptions.iconPosition)) {
1653
+ throw ConfigurationError('iconPosition must be "left", "right", or "none"');
1654
+ }
1655
+ }
1656
+ if (cardOptions.cardBrands !== void 0) {
1657
+ if (!Array.isArray(cardOptions.cardBrands)) {
1658
+ throw ConfigurationError("cardBrands must be an array of strings");
1659
+ }
1660
+ if (cardOptions.cardBrands.some((brand) => typeof brand !== "string")) {
1661
+ throw ConfigurationError("All cardBrands must be strings");
1662
+ }
1663
+ }
1664
+ }
1665
+ };
1666
+ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1667
+ validateAPIKey(apiKey);
1668
+ const config = validateSDKConfig(options);
1669
+ const log2 = createDebugLogger("SDK", config.debug || false);
1670
+ log2("SDK initialized", {
1671
+ apiKeyPrefix: apiKey.substring(0, 7) + "...",
1672
+ elementsBaseUrl: config.elementsBaseUrl,
1673
+ iframeOrigin: config.iframeOrigin,
1674
+ debug: config.debug || false
1675
+ });
1676
+ const elements = /* @__PURE__ */ new Map();
1677
+ let coordinatorIframe = null;
1678
+ let coordinatorClient = null;
1679
+ let coordinatorReadyPromise = null;
1680
+ let coordinatorInitStarted = false;
1681
+ let currentThemeMode = options.themeMode || "auto";
1682
+ let cardElementFactory = null;
1683
+ const lazyCardElement = async () => {
1684
+ if (!cardElementFactory) {
1685
+ const module = await import("./create-card-BuOvimjH.js");
1686
+ cardElementFactory = module.createCardElement;
1687
+ }
1688
+ return cardElementFactory;
1689
+ };
1690
+ const syncFactories = {
1691
+ cardNumber: createCardNumberElement,
1692
+ cvv: createCVVElement,
1693
+ expiry: createExpiryElement,
1694
+ text: createTextElement
1695
+ };
1696
+ const lazyFactories = {
1697
+ card: lazyCardElement
1698
+ };
1699
+ const ensureCoordinatorInitialized = () => {
1700
+ if (coordinatorReadyPromise) {
1701
+ return coordinatorReadyPromise;
1702
+ }
1703
+ if (coordinatorInitStarted) {
1704
+ return coordinatorReadyPromise;
1705
+ }
1706
+ coordinatorInitStarted = true;
1707
+ coordinatorReadyPromise = initializeCoordinator();
1708
+ return coordinatorReadyPromise;
1709
+ };
1710
+ const initializeCoordinator = async () => {
1711
+ if (coordinatorIframe) {
1712
+ log2("Coordinator already initialized", {
1713
+ loaded: coordinatorIframe.contentWindow !== null
1714
+ });
1715
+ return;
1716
+ }
1717
+ log2("Initializing coordinator iframe", {
1718
+ elementsBaseUrl: config.elementsBaseUrl
1719
+ });
1720
+ coordinatorIframe = document.createElement("iframe");
1721
+ coordinatorIframe.setAttribute("name", "bt-coordinator");
1722
+ coordinatorIframe.setAttribute("title", "BasisTheory Coordinator");
1723
+ coordinatorIframe.setAttribute("role", "none");
1724
+ coordinatorIframe.style.border = "none";
1725
+ coordinatorIframe.style.width = "0";
1726
+ coordinatorIframe.style.height = "0";
1727
+ coordinatorIframe.style.position = "absolute";
1728
+ coordinatorIframe.style.top = "-9999px";
1729
+ coordinatorIframe.style.left = "-9999px";
1730
+ const baseUrl = config.elementsBaseUrl.endsWith("/") ? config.elementsBaseUrl : `${config.elementsBaseUrl}/`;
1731
+ const coordinatorUrl = new URL("coordinator.html", baseUrl);
1732
+ coordinatorUrl.searchParams.set("parentOrigin", window.location.origin);
1733
+ coordinatorIframe.src = coordinatorUrl.toString();
1734
+ document.body.appendChild(coordinatorIframe);
1735
+ log2("Coordinator iframe mounted", {
1736
+ url: coordinatorIframe.src
1737
+ });
1738
+ await new Promise((resolve, reject) => {
1739
+ const timeout = setTimeout(() => {
1740
+ reject(
1741
+ new Error(
1742
+ `Coordinator load timeout (${config.timeoutMs}ms). Try increasing timeoutMs for slow connections.`
1743
+ )
1744
+ );
1745
+ }, config.timeoutMs);
1746
+ coordinatorIframe.addEventListener(
1747
+ "load",
1748
+ () => {
1749
+ clearTimeout(timeout);
1750
+ log2("Coordinator iframe loaded");
1751
+ resolve();
1752
+ },
1753
+ { once: true }
1754
+ );
1755
+ });
1756
+ coordinatorClient = createPostMessageClient(coordinatorIframe, {
1757
+ targetOrigin: new URL(config.elementsBaseUrl).origin,
1758
+ timeout: 3e4,
1759
+ retries: 3,
1760
+ debug: config.debug || false
1761
+ });
1762
+ log2("Coordinator PostMessage client created");
1763
+ await coordinatorClient.sendRequest("CMD_SET_CONFIG", {
1764
+ apiKey,
1765
+ apiBaseUrl: config.apiBaseUrl,
1766
+ // Optional - auto-detects if not provided
1767
+ apiTimeout: 3e4,
1768
+ apiRetries: 3,
1769
+ debug: config.debug || false,
1770
+ theme: config.theme,
1771
+ darkTheme: config.darkTheme,
1772
+ themeMode: config.themeMode
1773
+ });
1774
+ log2("Coordinator initialized successfully");
1775
+ };
1776
+ const sdk = {
1777
+ /**
1778
+ * PERFORMANCE OPTIMIZATION (Step 2 & 3):
1779
+ * createElement is now SYNCHRONOUS - returns Element instantly
1780
+ * Coordinator initialization starts on first element (but doesn't block)
1781
+ */
1782
+ createElement: (type, elementOptions) => {
1783
+ const syncFactory = syncFactories[type];
1784
+ const lazyFactory = lazyFactories[type];
1785
+ if (!syncFactory && !lazyFactory) {
1786
+ throw ConfigurationError(`Unknown element type: ${type}`);
1787
+ }
1788
+ if (elementOptions) {
1789
+ validateElementOptions(type, elementOptions);
1790
+ }
1791
+ const localElementId = `${type}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1792
+ log2("Creating element (sync)", {
1793
+ type,
1794
+ elementId: localElementId,
1795
+ coordinatorInitialized: coordinatorIframe !== null,
1796
+ isLazyLoaded: !!lazyFactory
1797
+ });
1798
+ let element;
1799
+ if (syncFactory) {
1800
+ element = syncFactory(
1801
+ apiKey,
1802
+ config,
1803
+ elementOptions,
1804
+ localElementId,
1805
+ ensureCoordinatorInitialized
1806
+ );
1807
+ } else {
1808
+ let realElement = null;
1809
+ let loadPromise = null;
1810
+ const loadRealElement = async () => {
1811
+ if (!loadPromise) {
1812
+ loadPromise = (async () => {
1813
+ const factory = await lazyFactory();
1814
+ realElement = factory(
1815
+ apiKey,
1816
+ config,
1817
+ elementOptions,
1818
+ localElementId,
1819
+ ensureCoordinatorInitialized
1820
+ );
1821
+ })();
1822
+ }
1823
+ await loadPromise;
1824
+ };
1825
+ element = {
1826
+ id: localElementId,
1827
+ type,
1828
+ get mounted() {
1829
+ return (realElement == null ? void 0 : realElement.mounted) ?? false;
1830
+ },
1831
+ get fields() {
1832
+ return realElement == null ? void 0 : realElement.fields;
1833
+ },
1834
+ mount: async (selector) => {
1835
+ await loadRealElement();
1836
+ return realElement.mount(selector);
1837
+ },
1838
+ unmount: () => {
1839
+ if (!realElement) throw ElementError("Element not initialized");
1840
+ return realElement.unmount();
1841
+ },
1842
+ update: async (opts) => {
1843
+ if (!realElement) throw ElementError("Element not initialized");
1844
+ return realElement.update(opts);
1845
+ },
1846
+ focus: () => {
1847
+ if (!realElement) throw ElementError("Element not initialized");
1848
+ return realElement.focus();
1849
+ },
1850
+ blur: () => {
1851
+ if (!realElement) throw ElementError("Element not initialized");
1852
+ return realElement.blur();
1853
+ },
1854
+ clear: () => {
1855
+ if (!realElement) throw ElementError("Element not initialized");
1856
+ return realElement.clear();
1857
+ },
1858
+ on: (eventType, listener) => {
1859
+ if (!realElement) {
1860
+ loadRealElement().then(() => realElement.on(eventType, listener));
1861
+ return () => {
1862
+ };
1863
+ }
1864
+ return realElement.on(eventType, listener);
1865
+ }
1866
+ };
1867
+ }
1868
+ elements.set(element.id, element);
1869
+ if (type === "cardNumber") {
1870
+ element.on("change", (event) => {
1871
+ const { cardBrand, cvvLengths } = event.detail;
1872
+ log2("Card number change event received", {
1873
+ cardBrand,
1874
+ cvvLengths,
1875
+ cvvElementsCount: Array.from(elements.values()).filter(
1876
+ (el) => el.type === "cvv"
1877
+ ).length
1878
+ });
1879
+ if (cardBrand && cardBrand !== "Unknown" && cvvLengths) {
1880
+ for (const [id, cvvElement] of elements) {
1881
+ if (cvvElement.type === "cvv" && cvvElement.mounted) {
1882
+ log2("Coordinating CVV element with card brand", {
1883
+ cvvElementId: id,
1884
+ cardBrand,
1885
+ cvvLengths,
1886
+ mounted: cvvElement.mounted
1887
+ });
1888
+ cvvElement.update({
1889
+ cardBrand,
1890
+ cvvLengths
1891
+ // Send cvvLengths array instead of full brandDefinition
1892
+ }).catch((error) => {
1893
+ log2("Failed to update CVV element with card brand", {
1894
+ cvvElementId: id,
1895
+ cardBrand,
1896
+ error: error.message
1897
+ });
1898
+ });
1899
+ }
1900
+ }
1901
+ }
1902
+ });
1903
+ }
1904
+ return element;
1905
+ },
1906
+ tokenize: async (payload, options2) => {
1907
+ log2("tokenize called", {
1908
+ hasPayload: !!payload,
1909
+ hasOptions: !!options2,
1910
+ coordinatorReady: !!coordinatorClient
1911
+ });
1912
+ if (!coordinatorClient) {
1913
+ log2("Waiting for coordinator initialization");
1914
+ await initializeCoordinator();
1915
+ }
1916
+ if (!coordinatorClient) {
1917
+ throw CoordinatorError("Coordinator failed to initialize");
1918
+ }
1919
+ try {
1920
+ const resolvedPayload = payload ? resolveElementReferences(payload) : {};
1921
+ log2("Element references resolved", {
1922
+ originalPayload: !!payload,
1923
+ resolvedPayload: !!resolvedPayload
1924
+ });
1925
+ log2("Delegating tokenization to coordinator");
1926
+ const token = await coordinatorClient.sendRequest(
1927
+ "CMD_TOKENIZE",
1928
+ {
1929
+ payload: resolvedPayload,
1930
+ options: options2
1931
+ }
1932
+ );
1933
+ log2("tokenize successful", {
1934
+ tokenId: token.id
1935
+ });
1936
+ return token;
1937
+ } catch (error) {
1938
+ log2("tokenize failed", {
1939
+ error: error.message
1940
+ });
1941
+ throw error;
1942
+ }
1943
+ },
1944
+ /**
1945
+ * Token CRUD operations
1946
+ * Supports Element references - automatically resolves to secure values
1947
+ */
1948
+ tokens: {
1949
+ /**
1950
+ * Create a new token
1951
+ * Supports Element references in data (same as tokenize)
1952
+ */
1953
+ create: async (request) => {
1954
+ if (!request || typeof request !== "object" || Array.isArray(request)) {
1955
+ throw ValidationError("Token data must be a non-null object");
1956
+ }
1957
+ log2("tokens.create called", {
1958
+ type: request.type,
1959
+ hasData: !!request.data
1960
+ });
1961
+ if (!coordinatorClient) {
1962
+ log2("Waiting for coordinator initialization");
1963
+ await initializeCoordinator();
1964
+ }
1965
+ if (!coordinatorClient) {
1966
+ throw new Error("Coordinator failed to initialize");
1967
+ }
1968
+ try {
1969
+ const resolvedRequest = {
1970
+ ...request,
1971
+ data: resolveElementReferences(request.data)
1972
+ };
1973
+ log2("Element references resolved for tokens.create", {
1974
+ resolvedData: !!resolvedRequest
1975
+ });
1976
+ const token = await coordinatorClient.sendRequest(
1977
+ "CMD_TOKENS_CREATE",
1978
+ { data: resolvedRequest }
1979
+ );
1980
+ log2("tokens.create successful", { tokenId: token.id });
1981
+ return token;
1982
+ } catch (error) {
1983
+ log2("tokens.create failed", {
1984
+ error: error.message
1985
+ });
1986
+ throw error;
1987
+ }
1988
+ },
1989
+ /**
1990
+ * Retrieve a token by ID
1991
+ *
1992
+ * @param id - Token ID to retrieve
1993
+ * @param options - Optional configuration
1994
+ * @param options.apiKey - Optional session key for authorized access (from sessions.create + authorize)
1995
+ */
1996
+ retrieve: async (id, options2) => {
1997
+ log2("tokens.retrieve called", {
1998
+ tokenId: id,
1999
+ hasSessionKey: !!(options2 == null ? void 0 : options2.apiKey)
2000
+ });
2001
+ if (!coordinatorClient) {
2002
+ await initializeCoordinator();
2003
+ }
2004
+ if (!coordinatorClient) {
2005
+ throw new Error("Coordinator failed to initialize");
2006
+ }
2007
+ try {
2008
+ const token = await coordinatorClient.sendRequest(
2009
+ "CMD_TOKENS_RETRIEVE",
2010
+ {
2011
+ id,
2012
+ apiKey: options2 == null ? void 0 : options2.apiKey
2013
+ // Pass session key to coordinator
2014
+ }
2015
+ );
2016
+ log2("tokens.retrieve successful", {
2017
+ tokenId: token.id
2018
+ });
2019
+ return token;
2020
+ } catch (error) {
2021
+ log2("tokens.retrieve failed", {
2022
+ error: error.message
2023
+ });
2024
+ throw error;
2025
+ }
2026
+ },
2027
+ /**
2028
+ * Update a token
2029
+ * Supports Element references in data
2030
+ */
2031
+ update: async (id, request) => {
2032
+ log2("tokens.update called", { tokenId: id });
2033
+ if (!coordinatorClient) {
2034
+ await initializeCoordinator();
2035
+ }
2036
+ if (!coordinatorClient) {
2037
+ throw new Error("Coordinator failed to initialize");
2038
+ }
2039
+ try {
2040
+ const resolvedRequest = request.data ? { ...request, data: resolveElementReferences(request.data) } : request;
2041
+ const token = await coordinatorClient.sendRequest(
2042
+ "CMD_TOKENS_UPDATE",
2043
+ { id, data: resolvedRequest }
2044
+ );
2045
+ log2("tokens.update successful", { tokenId: token.id });
2046
+ return token;
2047
+ } catch (error) {
2048
+ log2("tokens.update failed", {
2049
+ error: error.message
2050
+ });
2051
+ throw error;
2052
+ }
2053
+ },
2054
+ /**
2055
+ * Encrypt token data with client-side JWE
2056
+ * Uses ECDH-ES key agreement and A256GCM authenticated encryption
2057
+ * Supports Element references - automatically resolves to secure values
2058
+ *
2059
+ * @param tokenData - Single token or multiple tokens to encrypt
2060
+ * @param publicKeyPEM - X25519 public key in PEM format
2061
+ * @param keyId - Key identifier for decryption lookup
2062
+ * @returns Encrypted JWE token(s)
2063
+ */
2064
+ encrypt: async (tokenData, publicKeyPEM, keyId) => {
2065
+ log2("tokens.encrypt called", {
2066
+ isSingleToken: "type" in tokenData && "data" in tokenData,
2067
+ keyId
2068
+ });
2069
+ if (!tokenData || typeof tokenData !== "object") {
2070
+ throw ValidationError("tokenData must be an object");
2071
+ }
2072
+ if (!publicKeyPEM || typeof publicKeyPEM !== "string") {
2073
+ throw ValidationError("publicKeyPEM is required");
2074
+ }
2075
+ if (!keyId || typeof keyId !== "string") {
2076
+ throw ValidationError("keyId is required");
2077
+ }
2078
+ if (!coordinatorClient) {
2079
+ await initializeCoordinator();
2080
+ }
2081
+ if (!coordinatorClient) {
2082
+ throw new Error("Coordinator failed to initialize");
2083
+ }
2084
+ try {
2085
+ const resolvedTokenData = resolveElementReferences(tokenData);
2086
+ log2("Element references resolved for tokens.encrypt", {
2087
+ originalData: !!tokenData,
2088
+ resolvedData: !!resolvedTokenData
2089
+ });
2090
+ const encrypted = await coordinatorClient.sendRequest("CMD_TOKENS_ENCRYPT", {
2091
+ tokenRequests: resolvedTokenData,
2092
+ publicKeyPEM,
2093
+ keyId
2094
+ });
2095
+ log2("tokens.encrypt successful", {
2096
+ isSingleToken: "encrypted" in encrypted && "type" in encrypted
2097
+ });
2098
+ return encrypted;
2099
+ } catch (error) {
2100
+ log2("tokens.encrypt failed", {
2101
+ error: error.message
2102
+ });
2103
+ throw error;
2104
+ }
2105
+ }
2106
+ },
2107
+ sessions: {
2108
+ /**
2109
+ * Create a new session for temporary elevated access
2110
+ * Note: Session must be authorized by a Private Application on your backend
2111
+ */
2112
+ create: async () => {
2113
+ log2("sessions.create called");
2114
+ if (!coordinatorClient) {
2115
+ await initializeCoordinator();
2116
+ }
2117
+ if (!coordinatorClient) {
2118
+ throw new Error("Coordinator failed to initialize");
2119
+ }
2120
+ try {
2121
+ const session = await coordinatorClient.sendRequest(
2122
+ "CMD_SESSIONS_CREATE",
2123
+ {}
2124
+ );
2125
+ log2("sessions.create successful", {
2126
+ nonce: session.nonce,
2127
+ expiresAt: session.expires_at
2128
+ });
2129
+ return session;
2130
+ } catch (error) {
2131
+ log2("sessions.create failed", {
2132
+ error: error.message
2133
+ });
2134
+ throw error;
2135
+ }
2136
+ }
2137
+ },
2138
+ tokenIntents: {
2139
+ /**
2140
+ * Create a token intent with element reference support
2141
+ */
2142
+ create: async (data, options2) => {
2143
+ log2("tokenIntents.create called", {
2144
+ type: data.type,
2145
+ hasData: !!data.data,
2146
+ hasOptions: !!options2
2147
+ });
2148
+ if (!coordinatorClient) {
2149
+ await initializeCoordinator();
2150
+ }
2151
+ if (!coordinatorClient) {
2152
+ throw new Error("Coordinator failed to initialize");
2153
+ }
2154
+ try {
2155
+ const resolvedData = resolveElementReferences(data.data);
2156
+ log2("Element references resolved for token intent");
2157
+ const intent = await coordinatorClient.sendRequest(
2158
+ "CMD_TOKEN_INTENTS_CREATE",
2159
+ {
2160
+ type: data.type,
2161
+ data: resolvedData,
2162
+ options: options2
2163
+ }
2164
+ );
2165
+ log2("tokenIntents.create successful", {
2166
+ intentId: intent.id
2167
+ });
2168
+ return intent;
2169
+ } catch (error) {
2170
+ log2("tokenIntents.create failed", {
2171
+ error: error.message
2172
+ });
2173
+ throw error;
2174
+ }
2175
+ },
2176
+ /**
2177
+ * Get a token intent by ID
2178
+ */
2179
+ get: async (id, options2) => {
2180
+ log2("tokenIntents.get called", {
2181
+ intentId: id
2182
+ });
2183
+ if (!coordinatorClient) {
2184
+ await initializeCoordinator();
2185
+ }
2186
+ if (!coordinatorClient) {
2187
+ throw new Error("Coordinator failed to initialize");
2188
+ }
2189
+ try {
2190
+ const intent = await coordinatorClient.sendRequest(
2191
+ "CMD_TOKEN_INTENTS_GET",
2192
+ {
2193
+ id,
2194
+ options: options2
2195
+ }
2196
+ );
2197
+ log2("tokenIntents.get successful", {
2198
+ intentId: intent.id
2199
+ });
2200
+ return intent;
2201
+ } catch (error) {
2202
+ log2("tokenIntents.get failed", {
2203
+ error: error.message
2204
+ });
2205
+ throw error;
2206
+ }
2207
+ }
2208
+ },
2209
+ updateThemeMode: async (mode) => {
2210
+ log2("updateThemeMode called", {
2211
+ oldMode: currentThemeMode,
2212
+ newMode: mode,
2213
+ mountedElements: Array.from(elements.values()).filter(
2214
+ (el) => el.mounted
2215
+ ).length
2216
+ });
2217
+ currentThemeMode = mode;
2218
+ const updatePromises = [];
2219
+ for (const [id, element] of elements) {
2220
+ if (element.mounted) {
2221
+ log2("Sending theme mode update to element", {
2222
+ elementId: id,
2223
+ elementType: element.type,
2224
+ mode
2225
+ });
2226
+ const iframe = document.querySelector(
2227
+ `iframe[data-bt-element-id="${element.id}"]`
2228
+ );
2229
+ if (iframe) {
2230
+ iframe.setAttribute("data-theme-mode", mode);
2231
+ }
2232
+ const updatePromise = element.update({
2233
+ themeMode: mode
2234
+ }).catch((error) => {
2235
+ log2("Failed to update theme mode for element", {
2236
+ elementId: id,
2237
+ error: error.message
2238
+ });
2239
+ });
2240
+ updatePromises.push(updatePromise);
2241
+ }
2242
+ }
2243
+ await Promise.all(updatePromises);
2244
+ log2("Theme mode updated for all mounted elements", {
2245
+ mode,
2246
+ updatedCount: Array.from(elements.values()).filter((el) => el.mounted).length
2247
+ });
2248
+ }
2249
+ };
2250
+ if (queuedCommands && queuedCommands.length > 0) {
2251
+ log2("Draining command queue from loader", {
2252
+ queueLength: queuedCommands.length,
2253
+ commands: queuedCommands.map((cmd) => cmd.method)
2254
+ });
2255
+ for (const cmd of queuedCommands) {
2256
+ try {
2257
+ const method = sdk[cmd.method];
2258
+ if (!method) {
2259
+ throw new Error(`Unknown method: ${cmd.method}`);
2260
+ }
2261
+ log2("Replaying queued command", {
2262
+ method: cmd.method,
2263
+ argsCount: cmd.args.length
2264
+ });
2265
+ const result = method(...cmd.args);
2266
+ if (result && typeof result.then === "function") {
2267
+ result.then((value) => {
2268
+ log2("Queued command completed (async)", {
2269
+ method: cmd.method
2270
+ });
2271
+ cmd.resolve(value);
2272
+ }).catch((error) => {
2273
+ log2("Queued command failed (async)", {
2274
+ method: cmd.method,
2275
+ error: error.message
2276
+ });
2277
+ cmd.reject(error);
2278
+ });
2279
+ } else {
2280
+ log2("Queued command completed (sync)", {
2281
+ method: cmd.method
2282
+ });
2283
+ cmd.resolve(result);
2284
+ }
2285
+ } catch (error) {
2286
+ log2("Queued command failed (sync)", {
2287
+ method: cmd.method,
2288
+ error: error.message
2289
+ });
2290
+ cmd.reject(error);
2291
+ }
2292
+ }
2293
+ log2("Command queue drain complete", {
2294
+ processedCount: queuedCommands.length
2295
+ });
2296
+ }
2297
+ return sdk;
2298
+ };
2299
+ if (typeof window !== "undefined" && typeof document !== "undefined" && document.currentScript && !window.BasisTheory) {
2300
+ window.BasisTheory = BasisTheory;
2301
+ }
2302
+ const version = "3.0.0-beta.2";
2303
+ const buildDate = "2026-04-13T17:05:21.283Z";
2304
+ if (typeof window !== "undefined") {
2305
+ window.__BasisTheorySDK__ = BasisTheory;
2306
+ }
2307
+ export {
2308
+ BasisTheory as B,
2309
+ ElementError as E,
2310
+ createPostMessageClient as a,
2311
+ createEventDispatcher as b,
2312
+ createIframe as c,
2313
+ createMounter as d,
2314
+ buildDate as e,
2315
+ log as l,
2316
+ version as v
2317
+ };