@basis-theory/web-elements 3.0.0-beta.3 → 3.0.0-beta.5

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 (101) hide show
  1. package/dist/.vite/manifest.json +30 -8
  2. package/dist/basis-theory-sdk.es.js +1 -1
  3. package/dist/basis-theory-sdk.umd.js +1 -1
  4. package/dist/basis-theory-sdk.umd.js.map +1 -1
  5. package/dist/{create-card-DipDWgph.js → create-card-CB_YcP_4.js} +58 -20
  6. package/dist/create-card-CB_YcP_4.js.map +1 -0
  7. package/dist/create-card-display-kBupMJwe.js +280 -0
  8. package/dist/create-card-display-kBupMJwe.js.map +1 -0
  9. package/dist/create-copy-button-2XtKTEo3.js +236 -0
  10. package/dist/create-copy-button-2XtKTEo3.js.map +1 -0
  11. package/dist/{index-BBfuUaLw.js → index-Dyy7tm5Z.js} +709 -140
  12. package/dist/index-Dyy7tm5Z.js.map +1 -0
  13. package/dist/index.d.ts +1119 -0
  14. package/dist/loader/basis-theory.min.js +1 -1
  15. package/dist/sri.json +9 -9
  16. package/dist/stats.html +1 -1
  17. package/package.json +12 -12
  18. package/dist/api-client/src/api-client.d.ts +0 -11
  19. package/dist/api-client/src/api-client.d.ts.map +0 -1
  20. package/dist/api-client/src/http-client.d.ts +0 -24
  21. package/dist/api-client/src/http-client.d.ts.map +0 -1
  22. package/dist/api-client/src/index.d.ts +0 -9
  23. package/dist/api-client/src/index.d.ts.map +0 -1
  24. package/dist/api-client/src/services/sessions.d.ts +0 -19
  25. package/dist/api-client/src/services/sessions.d.ts.map +0 -1
  26. package/dist/api-client/src/services/token-intents.d.ts +0 -17
  27. package/dist/api-client/src/services/token-intents.d.ts.map +0 -1
  28. package/dist/api-client/src/services/tokens.d.ts +0 -19
  29. package/dist/api-client/src/services/tokens.d.ts.map +0 -1
  30. package/dist/api-client/src/types.d.ts +0 -313
  31. package/dist/api-client/src/types.d.ts.map +0 -1
  32. package/dist/create-card-DipDWgph.js.map +0 -1
  33. package/dist/index-BBfuUaLw.js.map +0 -1
  34. package/dist/postmessage/src/client.d.ts +0 -7
  35. package/dist/postmessage/src/client.d.ts.map +0 -1
  36. package/dist/postmessage/src/event-map.d.ts +0 -135
  37. package/dist/postmessage/src/event-map.d.ts.map +0 -1
  38. package/dist/postmessage/src/index.d.ts +0 -9
  39. package/dist/postmessage/src/index.d.ts.map +0 -1
  40. package/dist/postmessage/src/server.d.ts +0 -11
  41. package/dist/postmessage/src/server.d.ts.map +0 -1
  42. package/dist/postmessage/src/types.d.ts +0 -71
  43. package/dist/postmessage/src/types.d.ts.map +0 -1
  44. package/dist/shared/src/api-url-resolver.d.ts +0 -22
  45. package/dist/shared/src/api-url-resolver.d.ts.map +0 -1
  46. package/dist/shared/src/debug-logger.d.ts +0 -16
  47. package/dist/shared/src/debug-logger.d.ts.map +0 -1
  48. package/dist/shared/src/deep-sanitization.d.ts +0 -24
  49. package/dist/shared/src/deep-sanitization.d.ts.map +0 -1
  50. package/dist/shared/src/errors.d.ts +0 -93
  51. package/dist/shared/src/errors.d.ts.map +0 -1
  52. package/dist/shared/src/generate-id.d.ts +0 -13
  53. package/dist/shared/src/generate-id.d.ts.map +0 -1
  54. package/dist/shared/src/index.d.ts +0 -13
  55. package/dist/shared/src/index.d.ts.map +0 -1
  56. package/dist/shared/src/logger-context.d.ts +0 -25
  57. package/dist/shared/src/logger-context.d.ts.map +0 -1
  58. package/dist/web-elements/src/BasisTheory.d.ts +0 -47
  59. package/dist/web-elements/src/BasisTheory.d.ts.map +0 -1
  60. package/dist/web-elements/src/basis-theory.d.ts +0 -62
  61. package/dist/web-elements/src/basis-theory.d.ts.map +0 -1
  62. package/dist/web-elements/src/elements/create-card-number.d.ts +0 -14
  63. package/dist/web-elements/src/elements/create-card-number.d.ts.map +0 -1
  64. package/dist/web-elements/src/elements/create-card.d.ts +0 -37
  65. package/dist/web-elements/src/elements/create-card.d.ts.map +0 -1
  66. package/dist/web-elements/src/elements/create-cvv.d.ts +0 -10
  67. package/dist/web-elements/src/elements/create-cvv.d.ts.map +0 -1
  68. package/dist/web-elements/src/elements/create-expiry.d.ts +0 -10
  69. package/dist/web-elements/src/elements/create-expiry.d.ts.map +0 -1
  70. package/dist/web-elements/src/elements/create-text.d.ts +0 -13
  71. package/dist/web-elements/src/elements/create-text.d.ts.map +0 -1
  72. package/dist/web-elements/src/index.d.ts +0 -27
  73. package/dist/web-elements/src/index.d.ts.map +0 -1
  74. package/dist/web-elements/src/mounter/create-mounter.d.ts +0 -11
  75. package/dist/web-elements/src/mounter/create-mounter.d.ts.map +0 -1
  76. package/dist/web-elements/src/theme/default-theme.d.ts +0 -22
  77. package/dist/web-elements/src/theme/default-theme.d.ts.map +0 -1
  78. package/dist/web-elements/src/theme/inject-theme-dark.d.ts +0 -13
  79. package/dist/web-elements/src/theme/inject-theme-dark.d.ts.map +0 -1
  80. package/dist/web-elements/src/theme/inject-theme.d.ts +0 -20
  81. package/dist/web-elements/src/theme/inject-theme.d.ts.map +0 -1
  82. package/dist/web-elements/src/types/events.d.ts +0 -137
  83. package/dist/web-elements/src/types/events.d.ts.map +0 -1
  84. package/dist/web-elements/src/types/theme.d.ts +0 -91
  85. package/dist/web-elements/src/types/theme.d.ts.map +0 -1
  86. package/dist/web-elements/src/types.d.ts +0 -537
  87. package/dist/web-elements/src/types.d.ts.map +0 -1
  88. package/dist/web-elements/src/utils/create-event-dispatcher.d.ts +0 -47
  89. package/dist/web-elements/src/utils/create-event-dispatcher.d.ts.map +0 -1
  90. package/dist/web-elements/src/utils/create-iframe.d.ts +0 -10
  91. package/dist/web-elements/src/utils/create-iframe.d.ts.map +0 -1
  92. package/dist/web-elements/src/utils/resolve-element-references.d.ts +0 -20
  93. package/dist/web-elements/src/utils/resolve-element-references.d.ts.map +0 -1
  94. package/dist/web-elements/src/utils/validate-element-options.d.ts +0 -11
  95. package/dist/web-elements/src/utils/validate-element-options.d.ts.map +0 -1
  96. package/dist/web-elements/src/utils/validate-sdk-config.d.ts +0 -15
  97. package/dist/web-elements/src/utils/validate-sdk-config.d.ts.map +0 -1
  98. package/dist/web-elements/src/utils/warn-unknown-keys.d.ts +0 -15
  99. package/dist/web-elements/src/utils/warn-unknown-keys.d.ts.map +0 -1
  100. package/dist/web-elements/src/version.d.ts +0 -25
  101. package/dist/web-elements/src/version.d.ts.map +0 -1
@@ -15,7 +15,9 @@ const REDACT_PATTERNS = [
15
15
  /secret/i,
16
16
  /token/i,
17
17
  /ssn/i,
18
- /track/i
18
+ /track/i,
19
+ /session_key/i,
20
+ /^apikey$/i
19
21
  ];
20
22
  const isPANField = (key) => {
21
23
  return PAN_PATTERNS.some((pattern) => pattern.test(key));
@@ -140,6 +142,16 @@ const createDebugLogger = (packageName, enabled) => {
140
142
  console.log(`🔍 [${timestamp}] [${packageName}] ${action}`, sanitized);
141
143
  };
142
144
  };
145
+ const generateId = () => {
146
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
147
+ return crypto.randomUUID();
148
+ }
149
+ const random = () => Math.random().toString(36).substring(2);
150
+ const randomPart = (random() + random() + random() + random()).substring(0, 32);
151
+ return `${randomPart.substring(0, 8)}-${randomPart.substring(8, 12)}-${randomPart.substring(12, 16)}-${randomPart.substring(16, 20)}-${randomPart.substring(20, 32)}`;
152
+ };
153
+ const log = (action, metadata) => {
154
+ };
143
155
  const createBasisTheoryError = (code, message, details) => {
144
156
  const error = new Error(message);
145
157
  error.name = "BasisTheoryError";
@@ -162,7 +174,7 @@ const BasisTheoryError = (code, message, details) => {
162
174
  return error;
163
175
  };
164
176
  const ValidationError = (message, field, details) => {
165
- const error = createBasisTheoryError("VALIDATION_ERROR", message, details);
177
+ const error = createBasisTheoryError("VALIDATION_ERROR", message, field ? { field, ...details } : details);
166
178
  error.name = "ValidationError";
167
179
  error.field = field;
168
180
  return error;
@@ -183,7 +195,201 @@ const ConfigurationError = (message, details) => {
183
195
  error.name = "ConfigurationError";
184
196
  return error;
185
197
  };
186
- const log = (action, metadata) => {
198
+ const ALLOWED_PROXY_SCHEMES = /* @__PURE__ */ new Set(["https:"]);
199
+ const ALLOWED_METHODS = /* @__PURE__ */ new Set([
200
+ "GET",
201
+ "POST",
202
+ "PUT",
203
+ "PATCH",
204
+ "DELETE"
205
+ ]);
206
+ const FORBIDDEN_PROXY_HEADERS = /* @__PURE__ */ new Set([
207
+ // Request smuggling prevention
208
+ "host",
209
+ "content-length",
210
+ "transfer-encoding",
211
+ "te",
212
+ "trailer",
213
+ // Connection manipulation prevention
214
+ "connection",
215
+ "upgrade",
216
+ "keep-alive",
217
+ "proxy-connection",
218
+ // Proxy bypass prevention
219
+ "via",
220
+ "proxy-authorization",
221
+ // BasisTheory credential protection
222
+ "bt-api-key",
223
+ "bt-proxy-key",
224
+ "bt-proxy-url",
225
+ "bt-idempotency-key",
226
+ // Forwarded headers
227
+ "x-forwarded-for",
228
+ "x-forwarded-host",
229
+ "x-real-ip",
230
+ // SDK owns serialization format — body is always JSON.stringify'd
231
+ "content-type"
232
+ ]);
233
+ const validateProxyHeaders = (headers) => {
234
+ if (!headers)
235
+ return;
236
+ for (const [key, value] of Object.entries(headers)) {
237
+ if (/[\r\n]/.test(value)) {
238
+ throw ValidationError(`Header '${key}' value contains illegal CRLF`, "headers");
239
+ }
240
+ if (/[\r\n]/.test(key)) {
241
+ throw ValidationError(`Header name '${key}' contains illegal CRLF`, "headers");
242
+ }
243
+ if (FORBIDDEN_PROXY_HEADERS.has(key.toLowerCase().trim())) {
244
+ throw ValidationError(`Header '${key}' is not allowed in proxy requests`, "headers");
245
+ }
246
+ }
247
+ };
248
+ const parseIPv4 = (hostname) => {
249
+ const clean = hostname.replace(/^\[|\]$/g, "");
250
+ if (/^(0x[\da-f]+|\d+)$/i.test(clean)) {
251
+ const num = clean.toLowerCase().startsWith("0x") ? parseInt(clean, 16) : parseInt(clean, 10);
252
+ if (num >= 0 && num <= 4294967295) {
253
+ return [
254
+ num >>> 24 & 255,
255
+ num >>> 16 & 255,
256
+ num >>> 8 & 255,
257
+ num & 255
258
+ ];
259
+ }
260
+ return null;
261
+ }
262
+ const parts = clean.split(".");
263
+ if (parts.length !== 4) {
264
+ return null;
265
+ }
266
+ const octets = [];
267
+ for (const part of parts) {
268
+ let value;
269
+ if (part.toLowerCase().startsWith("0x")) {
270
+ value = parseInt(part, 16);
271
+ } else if (part.startsWith("0") && part.length > 1 && !/[89]/.test(part)) {
272
+ value = parseInt(part, 8);
273
+ } else {
274
+ value = parseInt(part, 10);
275
+ }
276
+ if (isNaN(value) || value < 0 || value > 255) {
277
+ return null;
278
+ }
279
+ octets.push(value);
280
+ }
281
+ return octets;
282
+ };
283
+ const isPrivateIPv4 = (octets) => {
284
+ const a = octets[0];
285
+ const b = octets[1];
286
+ if (a === void 0 || b === void 0)
287
+ return true;
288
+ if (a === 127)
289
+ return true;
290
+ if (a === 10)
291
+ return true;
292
+ if (a === 172 && b >= 16 && b <= 31)
293
+ return true;
294
+ if (a === 192 && b === 168)
295
+ return true;
296
+ if (a === 169 && b === 254)
297
+ return true;
298
+ if (a === 0)
299
+ return true;
300
+ return false;
301
+ };
302
+ const isBlockedIPv6 = (hostname) => {
303
+ const clean = hostname.replace(/^\[|\]$/g, "").toLowerCase();
304
+ if (clean === "::1" || clean === "0:0:0:0:0:0:0:1" || clean === "0000:0000:0000:0000:0000:0000:0000:0001") {
305
+ return true;
306
+ }
307
+ if (clean.startsWith("fe8") || clean.startsWith("fe9") || clean.startsWith("fea") || clean.startsWith("feb")) {
308
+ return true;
309
+ }
310
+ if (clean.startsWith("fc") || clean.startsWith("fd")) {
311
+ return true;
312
+ }
313
+ const ipv4MappedDotted = clean.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
314
+ if (ipv4MappedDotted) {
315
+ const ipv4Part = ipv4MappedDotted[1];
316
+ if (ipv4Part) {
317
+ const ipv4Octets = parseIPv4(ipv4Part);
318
+ if (ipv4Octets && isPrivateIPv4(ipv4Octets)) {
319
+ return true;
320
+ }
321
+ }
322
+ }
323
+ const ipv4MappedHex = clean.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
324
+ if (ipv4MappedHex) {
325
+ const highPart = ipv4MappedHex[1];
326
+ const lowPart = ipv4MappedHex[2];
327
+ if (!highPart || !lowPart) {
328
+ return true;
329
+ }
330
+ const high = parseInt(highPart, 16);
331
+ const low = parseInt(lowPart, 16);
332
+ const octets = [
333
+ high >>> 8 & 255,
334
+ high & 255,
335
+ low >>> 8 & 255,
336
+ low & 255
337
+ ];
338
+ if (isPrivateIPv4(octets)) {
339
+ return true;
340
+ }
341
+ }
342
+ return false;
343
+ };
344
+ const validateSsrfProtection = (parsed) => {
345
+ const hostname = parsed.hostname.toLowerCase();
346
+ if (parsed.username || parsed.password) {
347
+ throw ValidationError("URL must not contain userinfo (username/password)", "url");
348
+ }
349
+ if (hostname === "localhost") {
350
+ throw ValidationError("URL must not target localhost", "url");
351
+ }
352
+ if (hostname.startsWith("[") || isBlockedIPv6(hostname)) {
353
+ if (isBlockedIPv6(hostname)) {
354
+ throw ValidationError("URL must not target loopback or link-local IPv6 addresses", "url");
355
+ }
356
+ }
357
+ const ipv4Octets = parseIPv4(hostname);
358
+ if (ipv4Octets && isPrivateIPv4(ipv4Octets)) {
359
+ throw ValidationError("URL must not target private or internal IP addresses", "url");
360
+ }
361
+ };
362
+ const validateProxyUrl = (url) => {
363
+ let parsed;
364
+ try {
365
+ parsed = new URL(url);
366
+ } catch {
367
+ throw ValidationError("Invalid URL format", "url");
368
+ }
369
+ if (!ALLOWED_PROXY_SCHEMES.has(parsed.protocol)) {
370
+ throw ValidationError(`URL scheme '${parsed.protocol}' is not allowed. Only https: is permitted`, "url");
371
+ }
372
+ validateSsrfProtection(parsed);
373
+ };
374
+ const validateProxyTarget = (url, proxyKey) => {
375
+ const hasUrl = typeof url === "string" && url.length > 0;
376
+ const hasKey = typeof proxyKey === "string" && proxyKey.length > 0;
377
+ if (hasUrl === hasKey) {
378
+ throw ValidationError("Specify exactly one of url or proxyKey", "url/proxyKey");
379
+ }
380
+ if (hasUrl) {
381
+ validateProxyUrl(url);
382
+ }
383
+ };
384
+ const validateProxyOptions = (options) => {
385
+ if (options !== void 0 && (typeof options !== "object" || options === null)) {
386
+ throw ValidationError("Proxy options must be a plain object", "options");
387
+ }
388
+ if ((options == null ? void 0 : options.method) !== void 0 && !ALLOWED_METHODS.has(options.method)) {
389
+ throw ValidationError(`Method '${options.method}' is not allowed`, "method");
390
+ }
391
+ validateProxyTarget(options == null ? void 0 : options.url, options == null ? void 0 : options.proxyKey);
392
+ validateProxyHeaders(options == null ? void 0 : options.headers);
187
393
  };
188
394
  const DEFAULT_RETRY_CONFIG = {
189
395
  maxRetries: 2,
@@ -267,9 +473,10 @@ const createPostMessageClient = (iframe, config) => {
267
473
  try {
268
474
  callback(payload);
269
475
  } catch (err) {
270
- if (config.debug) {
271
- console.error(`[PostMessage Client] Error in event subscriber for ${type}:`, err);
272
- }
476
+ log2("Error in event subscriber", {
477
+ type,
478
+ error: err.message
479
+ });
273
480
  }
274
481
  });
275
482
  } else if (!nonce) {
@@ -283,7 +490,7 @@ const createPostMessageClient = (iframe, config) => {
283
490
  const retryConfig = typeof config.retries === "number" ? { ...DEFAULT_RETRY_CONFIG, maxRetries: config.retries } : typeof config.retries === "object" ? config.retries : DEFAULT_RETRY_CONFIG;
284
491
  const sendRequestOnce = (type, payload) => {
285
492
  return new Promise((resolve, reject) => {
286
- const nonce = crypto.randomUUID();
493
+ const nonce = generateId();
287
494
  const timestamp = Date.now();
288
495
  if (!iframe.contentWindow) {
289
496
  reject(BasisTheoryError("IFRAME_NOT_READY", "Iframe not ready"));
@@ -298,9 +505,10 @@ const createPostMessageClient = (iframe, config) => {
298
505
  iframe.contentWindow.postMessage({ type, nonce, timestamp, payload }, config.targetOrigin);
299
506
  });
300
507
  };
301
- const sendRequest = async (type, payload) => {
508
+ const sendRequest = async (type, payload, options) => {
509
+ const maxRetries = (options == null ? void 0 : options.retries) !== void 0 ? options.retries : retryConfig.maxRetries;
302
510
  let lastError = null;
303
- for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
511
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
304
512
  try {
305
513
  return await sendRequestOnce(type, payload);
306
514
  } catch (error) {
@@ -316,11 +524,11 @@ const createPostMessageClient = (iframe, config) => {
316
524
  });
317
525
  throw error;
318
526
  }
319
- if (attempt < retryConfig.maxRetries) {
527
+ if (attempt < maxRetries) {
320
528
  const delay = getRetryDelay(attempt, retryConfig);
321
529
  log2("Retrying request", {
322
530
  attempt: attempt + 1,
323
- maxRetries: retryConfig.maxRetries,
531
+ maxRetries,
324
532
  delayMs: delay,
325
533
  error: errorMessage
326
534
  });
@@ -328,7 +536,7 @@ const createPostMessageClient = (iframe, config) => {
328
536
  }
329
537
  }
330
538
  }
331
- throw lastError || new Error(`Request failed after ${retryConfig.maxRetries} retries: ${type}`);
539
+ throw lastError || new Error(`Request failed after ${maxRetries} retries: ${type}`);
332
540
  };
333
541
  const ping = async (timeout = 1e3) => {
334
542
  const start = Date.now();
@@ -405,7 +613,7 @@ const createIframe = (config) => {
405
613
  iframe.src = config.src;
406
614
  iframe.setAttribute("sandbox", config.sandbox);
407
615
  iframe.title = config.title;
408
- iframe.setAttribute("allow", "payment *");
616
+ iframe.setAttribute("allow", config.allow ?? "payment *");
409
617
  iframe.setAttribute("importance", "high");
410
618
  iframe.style.opacity = "0";
411
619
  return iframe;
@@ -444,7 +652,9 @@ const createEventDispatcher = (config) => {
444
652
  bin: payload.bin,
445
653
  cvvLengths: payload.cvvLengths,
446
654
  potentialBrands: payload.potentialBrands,
447
- matchStrength: payload.matchStrength
655
+ matchStrength: payload.matchStrength,
656
+ networks: payload.networks,
657
+ selectedNetwork: payload.selectedNetwork
448
658
  };
449
659
  dispatch("change", cardDetail);
450
660
  } else if (elementType === "card") {
@@ -453,27 +663,47 @@ const createEventDispatcher = (config) => {
453
663
  cardBrand: payload.cardBrand,
454
664
  last4: payload.last4,
455
665
  bin: payload.bin,
666
+ networks: payload.networks,
667
+ selectedNetwork: payload.selectedNetwork,
456
668
  errors: payload.errors || null
457
669
  });
458
670
  } else {
459
671
  dispatch("change", changeDetail);
460
672
  }
461
673
  });
674
+ client.subscribe(
675
+ "EVENT_NETWORK_CHANGED",
676
+ (payload) => {
677
+ if (elementType !== "cardNumber" && elementType !== "card") {
678
+ return;
679
+ }
680
+ const detail = {
681
+ network: payload.network,
682
+ previousNetwork: payload.previousNetwork,
683
+ availableNetworks: payload.availableNetworks ?? [],
684
+ selectionMode: payload.selectionMode ?? "auto"
685
+ };
686
+ dispatch("networkChanged", detail);
687
+ }
688
+ );
462
689
  client.subscribe("EVENT_FOCUS", () => {
463
690
  dispatch("focus", {});
464
691
  });
465
692
  client.subscribe("EVENT_BLUR", () => {
466
693
  dispatch("blur", {});
467
694
  });
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
- });
695
+ client.subscribe(
696
+ "EVENT_ERROR",
697
+ (payload) => {
698
+ dispatch("error", {
699
+ code: payload.code,
700
+ message: payload.message,
701
+ status: payload.status,
702
+ validationErrors: payload.validationErrors,
703
+ metadata: payload.metadata || {}
704
+ });
705
+ }
706
+ );
477
707
  };
478
708
  const dispatchError = (code, message, error) => {
479
709
  dispatch("error", {
@@ -490,7 +720,7 @@ const createEventDispatcher = (config) => {
490
720
  dispatchError
491
721
  };
492
722
  };
493
- const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
723
+ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount) => {
494
724
  let iframe = null;
495
725
  let client = null;
496
726
  let mounter = null;
@@ -511,6 +741,12 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
511
741
  eventTarget
512
742
  });
513
743
  eventDispatcher.setupSubscriptions();
744
+ client.subscribe("EVENT_CONTENT_HEIGHT", (payload) => {
745
+ const h = payload == null ? void 0 : payload.heightPx;
746
+ if (iframe && typeof h === "number" && h > 0) {
747
+ iframe.style.height = `${Math.ceil(h)}px`;
748
+ }
749
+ });
514
750
  };
515
751
  return {
516
752
  id,
@@ -518,6 +754,9 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
518
754
  get mounted() {
519
755
  return isMounted;
520
756
  },
757
+ get loaded() {
758
+ return isMounted;
759
+ },
521
760
  mount: async (selector) => {
522
761
  if (isMounted) {
523
762
  throw ElementError("Element is already mounted");
@@ -587,7 +826,9 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
587
826
  placeholder: options.placeholder,
588
827
  disabled: options.disabled,
589
828
  readOnly: options.readOnly,
590
- debug: sdkConfig.debug
829
+ debug: sdkConfig.debug,
830
+ binLookup: options.binLookup,
831
+ coBadge: options.coBadge
591
832
  });
592
833
  if (iframe) iframe.style.opacity = "1";
593
834
  } catch (error) {
@@ -617,16 +858,17 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
617
858
  if (!isMounted) {
618
859
  throw ElementError("Element is not mounted");
619
860
  }
620
- if (client) {
621
- client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
622
- log("Failed to send clear cache command", {
623
- error: error.message
624
- });
861
+ notifyCoordinatorElementUnmount(id).catch((error) => {
862
+ log("Failed to notify coordinator of element unmount", {
863
+ error: error.message
625
864
  });
865
+ });
866
+ if (client) {
626
867
  client.destroy();
627
868
  client = null;
628
869
  }
629
870
  if (mounter && iframe) {
871
+ iframe.style.height = "";
630
872
  mounter.unmount(iframe);
631
873
  iframe = null;
632
874
  mounter = null;
@@ -640,10 +882,23 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
640
882
  throw ElementError("Element must be mounted before updating");
641
883
  }
642
884
  const updatePayload = {
643
- placeholder: newOptions.placeholder,
644
- disabled: newOptions.disabled,
645
- readOnly: newOptions.readOnly
885
+ elementId: id
646
886
  };
887
+ if (newOptions.placeholder !== void 0) {
888
+ updatePayload.placeholder = newOptions.placeholder;
889
+ }
890
+ if (newOptions.disabled !== void 0) {
891
+ updatePayload.disabled = newOptions.disabled;
892
+ }
893
+ if (newOptions.readOnly !== void 0) {
894
+ updatePayload.readOnly = newOptions.readOnly;
895
+ }
896
+ if (newOptions.binLookup !== void 0) {
897
+ updatePayload.binLookup = newOptions.binLookup;
898
+ }
899
+ if (newOptions.coBadge !== void 0) {
900
+ updatePayload.coBadge = newOptions.coBadge;
901
+ }
647
902
  if (newOptions.themeMode !== void 0) {
648
903
  updatePayload.themeMode = newOptions.themeMode;
649
904
  updatePayload.theme = sdkConfig.theme;
@@ -669,6 +924,12 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
669
924
  }
670
925
  client.sendRequest("CMD_CLEAR", {});
671
926
  },
927
+ setNetwork: async (network) => {
928
+ if (!isMounted || !client) {
929
+ throw ElementError("Element must be mounted before setNetwork");
930
+ }
931
+ await client.sendRequest("CMD_SET_NETWORK", { network });
932
+ },
672
933
  on: (eventType, listener) => {
673
934
  eventTarget.addEventListener(
674
935
  eventType,
@@ -681,7 +942,7 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
681
942
  }
682
943
  };
683
944
  };
684
- const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
945
+ const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount) => {
685
946
  let iframe = null;
686
947
  let client = null;
687
948
  let mounter = null;
@@ -707,6 +968,9 @@ const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId,
707
968
  get mounted() {
708
969
  return isMounted;
709
970
  },
971
+ get loaded() {
972
+ return isMounted;
973
+ },
710
974
  mount: async (selector) => {
711
975
  if (isMounted) {
712
976
  throw ElementError("Element is already mounted", id);
@@ -804,12 +1068,12 @@ const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId,
804
1068
  if (!isMounted) {
805
1069
  throw ElementError("Element is not mounted");
806
1070
  }
807
- if (client) {
808
- client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
809
- log("Failed to send clear cache command", {
810
- error: error.message
811
- });
1071
+ notifyCoordinatorElementUnmount(id).catch((error) => {
1072
+ log("Failed to notify coordinator of element unmount", {
1073
+ error: error.message
812
1074
  });
1075
+ });
1076
+ if (client) {
813
1077
  client.destroy();
814
1078
  client = null;
815
1079
  }
@@ -871,7 +1135,7 @@ const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId,
871
1135
  }
872
1136
  };
873
1137
  };
874
- const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
1138
+ const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount) => {
875
1139
  let iframe = null;
876
1140
  let client = null;
877
1141
  let mounter = null;
@@ -897,6 +1161,9 @@ const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElement
897
1161
  get mounted() {
898
1162
  return isMounted;
899
1163
  },
1164
+ get loaded() {
1165
+ return isMounted;
1166
+ },
900
1167
  mount: async (selector) => {
901
1168
  if (isMounted) {
902
1169
  throw ElementError("Element is already mounted", id);
@@ -993,12 +1260,12 @@ const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElement
993
1260
  if (!isMounted) {
994
1261
  throw ElementError("Element is not mounted");
995
1262
  }
996
- if (client) {
997
- client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
998
- log("Failed to send clear cache command", {
999
- error: error.message
1000
- });
1263
+ notifyCoordinatorElementUnmount(id).catch((error) => {
1264
+ log("Failed to notify coordinator of element unmount", {
1265
+ error: error.message
1001
1266
  });
1267
+ });
1268
+ if (client) {
1002
1269
  client.destroy();
1003
1270
  client = null;
1004
1271
  }
@@ -1057,7 +1324,7 @@ const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElement
1057
1324
  }
1058
1325
  };
1059
1326
  };
1060
- const createTextElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
1327
+ const createTextElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount) => {
1061
1328
  let iframe = null;
1062
1329
  let client = null;
1063
1330
  let mounter = null;
@@ -1090,6 +1357,9 @@ const createTextElement = (apiKey, sdkConfig, options = {}, coordinatorElementId
1090
1357
  get mounted() {
1091
1358
  return isMounted;
1092
1359
  },
1360
+ get loaded() {
1361
+ return isMounted;
1362
+ },
1093
1363
  mount: async (selector) => {
1094
1364
  if (isMounted) {
1095
1365
  throw ElementError("Element is already mounted", id);
@@ -1207,12 +1477,12 @@ const createTextElement = (apiKey, sdkConfig, options = {}, coordinatorElementId
1207
1477
  if (!isMounted) {
1208
1478
  throw ElementError("Element is not mounted");
1209
1479
  }
1210
- if (client) {
1211
- client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
1212
- log("Failed to send clear cache command", {
1213
- error: error.message
1214
- });
1480
+ notifyCoordinatorElementUnmount(id).catch((error) => {
1481
+ log("Failed to notify coordinator of element unmount", {
1482
+ error: error.message
1215
1483
  });
1484
+ });
1485
+ if (client) {
1216
1486
  client.destroy();
1217
1487
  client = null;
1218
1488
  }
@@ -1465,6 +1735,7 @@ const warnUnknownKeys = (label, obj, knownKeys, deprecatedKeys) => {
1465
1735
  };
1466
1736
  const KNOWN_SDK_KEYS = /* @__PURE__ */ new Set([
1467
1737
  "elementsBaseUrl",
1738
+ "whitelabelDomain",
1468
1739
  "apiBaseUrl",
1469
1740
  "debug",
1470
1741
  "measurePerformance",
@@ -1472,7 +1743,8 @@ const KNOWN_SDK_KEYS = /* @__PURE__ */ new Set([
1472
1743
  "retryConfig",
1473
1744
  "themeMode",
1474
1745
  "theme",
1475
- "darkTheme"
1746
+ "darkTheme",
1747
+ "sessionAuthorizationUrl"
1476
1748
  ]);
1477
1749
  const DEPRECATED_SDK_KEYS = /* @__PURE__ */ new Map([
1478
1750
  ["iframeOrigin", 'Use "elementsBaseUrl" instead.']
@@ -1540,7 +1812,44 @@ const validateSDKConfig = (options = {}) => {
1540
1812
  if (!["light", "dark", "auto"].includes(themeMode)) {
1541
1813
  throw ConfigurationError('themeMode must be "light", "dark", or "auto"');
1542
1814
  }
1543
- const finalBaseUrl = baseUrl || "https://js.basistheory.com/3.0.0-beta.3/elements";
1815
+ if (options.sessionAuthorizationUrl !== void 0) {
1816
+ if (typeof options.sessionAuthorizationUrl !== "string" || options.sessionAuthorizationUrl.trim() === "") {
1817
+ throw ConfigurationError("sessionAuthorizationUrl must be a non-empty string");
1818
+ }
1819
+ try {
1820
+ const parsed = new URL(options.sessionAuthorizationUrl, window.location.origin);
1821
+ if (parsed.protocol === "http:" && parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1") {
1822
+ throw ConfigurationError("sessionAuthorizationUrl must use https://.");
1823
+ }
1824
+ } catch (error) {
1825
+ if (error instanceof Error && error.name === "ConfigurationError") {
1826
+ throw error;
1827
+ }
1828
+ }
1829
+ }
1830
+ let resolvedBaseUrl = baseUrl;
1831
+ if (resolvedBaseUrl === void 0 && options.whitelabelDomain !== void 0) {
1832
+ const { whitelabelDomain } = options;
1833
+ if (typeof whitelabelDomain !== "string" || whitelabelDomain.trim().length === 0) {
1834
+ throw ConfigurationError("whitelabelDomain must be a non-empty string");
1835
+ }
1836
+ let domainOrigin;
1837
+ try {
1838
+ const parsed = new URL(whitelabelDomain);
1839
+ if (parsed.protocol !== "https:") {
1840
+ throw ConfigurationError("whitelabelDomain must use https://");
1841
+ }
1842
+ domainOrigin = parsed.origin;
1843
+ } catch (error) {
1844
+ if (error instanceof Error && error.name === "ConfigurationError") {
1845
+ throw error;
1846
+ }
1847
+ throw ConfigurationError("whitelabelDomain must be a valid URL");
1848
+ }
1849
+ const { pathname } = new URL("https://js.basistheory.com/3.0.0-beta.5/elements");
1850
+ resolvedBaseUrl = `${domainOrigin}${pathname}`;
1851
+ }
1852
+ const finalBaseUrl = resolvedBaseUrl || "https://js.basistheory.com/3.0.0-beta.5/elements";
1544
1853
  const iframeOrigin = new URL(finalBaseUrl).origin;
1545
1854
  return {
1546
1855
  iframeOrigin,
@@ -1556,7 +1865,9 @@ const validateSDKConfig = (options = {}) => {
1556
1865
  // Light theme or only theme
1557
1866
  darkTheme: options.darkTheme ? mergeWithDefaultTheme(options.darkTheme) : void 0,
1558
1867
  // Optional dark theme
1559
- retryConfig
1868
+ retryConfig,
1869
+ sessionAuthorizationUrl: options.sessionAuthorizationUrl
1870
+ // Optional - required for cardDisplay
1560
1871
  };
1561
1872
  };
1562
1873
  const COMMON_ELEMENT_KEYS = /* @__PURE__ */ new Set([
@@ -1566,7 +1877,7 @@ const COMMON_ELEMENT_KEYS = /* @__PURE__ */ new Set([
1566
1877
  "readOnly"
1567
1878
  ]);
1568
1879
  const ELEMENT_TYPE_KEYS = {
1569
- cardNumber: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS]),
1880
+ cardNumber: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS, "binLookup", "coBadge"]),
1570
1881
  cvv: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS, "cardBrand", "cvvLengths"]),
1571
1882
  // Internal coordination props
1572
1883
  expiry: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS]),
@@ -1585,7 +1896,21 @@ const ELEMENT_TYPE_KEYS = {
1585
1896
  "layout",
1586
1897
  "stackAt",
1587
1898
  "iconPosition",
1588
- "cardBrands"
1899
+ "cardBrands",
1900
+ "binLookup",
1901
+ "coBadge"
1902
+ ]),
1903
+ copyButton: /* @__PURE__ */ new Set([
1904
+ "allowClipboard",
1905
+ "value",
1906
+ "text",
1907
+ "disabled"
1908
+ ]),
1909
+ cardDisplay: /* @__PURE__ */ new Set([
1910
+ "tokenId",
1911
+ "number",
1912
+ "cvc",
1913
+ "expiration"
1589
1914
  ])
1590
1915
  };
1591
1916
  const validateElementOptions = (type, options = {}) => {
@@ -1636,6 +1961,14 @@ const validateElementOptions = (type, options = {}) => {
1636
1961
  }
1637
1962
  }
1638
1963
  }
1964
+ if (type === "copyButton") {
1965
+ const copyOpts = options;
1966
+ if (copyOpts.allowClipboard !== true) {
1967
+ throw ConfigurationError(
1968
+ "CopyButton requires allowClipboard: true. Clipboard access for raw card data may not be PCI compliant and must be explicitly enabled."
1969
+ );
1970
+ }
1971
+ }
1639
1972
  if (type === "card") {
1640
1973
  const cardOptions = options;
1641
1974
  if (cardOptions.layout !== void 0) {
@@ -1678,15 +2011,158 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1678
2011
  let coordinatorClient = null;
1679
2012
  let coordinatorReadyPromise = null;
1680
2013
  let coordinatorInitStarted = false;
2014
+ let coordinatorLoadTimeout = null;
2015
+ let rejectCoordinatorLoad = null;
2016
+ let destroyed = false;
2017
+ const assertNotDestroyed = () => {
2018
+ if (destroyed) {
2019
+ throw ConfigurationError(
2020
+ "SDK instance has been destroyed. Create a new instance with BasisTheory()"
2021
+ );
2022
+ }
2023
+ };
2024
+ const guardSync = (fn) => (...args) => {
2025
+ assertNotDestroyed();
2026
+ return fn(...args);
2027
+ };
2028
+ const guardAsync = (fn) => async (...args) => {
2029
+ assertNotDestroyed();
2030
+ return fn(...args);
2031
+ };
1681
2032
  let currentThemeMode = options.themeMode || "auto";
2033
+ const createLazyElementWrapper = (lazyFactory, elementType, elementId, elementApiKey, elementConfig, elementOptions, ensureCoordinator, notifyUnmount, coordinatorRequest) => {
2034
+ let realElement = null;
2035
+ let loadPromise = null;
2036
+ const loadRealElement = async () => {
2037
+ if (!loadPromise) {
2038
+ loadPromise = (async () => {
2039
+ const factory = await lazyFactory();
2040
+ realElement = factory(
2041
+ elementApiKey,
2042
+ elementConfig,
2043
+ elementOptions,
2044
+ elementId,
2045
+ ensureCoordinator,
2046
+ notifyUnmount,
2047
+ coordinatorRequest
2048
+ );
2049
+ })();
2050
+ }
2051
+ await loadPromise;
2052
+ };
2053
+ const ensureLoaded = () => {
2054
+ if (!realElement) throw ElementError("Element not initialized");
2055
+ return realElement;
2056
+ };
2057
+ const wrapper = {
2058
+ id: elementId,
2059
+ type: elementType,
2060
+ get mounted() {
2061
+ return (realElement == null ? void 0 : realElement.mounted) ?? false;
2062
+ },
2063
+ get loaded() {
2064
+ return (realElement == null ? void 0 : realElement.loaded) ?? false;
2065
+ },
2066
+ mount: async (selector) => {
2067
+ await loadRealElement();
2068
+ return realElement.mount(selector);
2069
+ },
2070
+ unmount: () => ensureLoaded().unmount(),
2071
+ update: async (opts) => ensureLoaded().update(opts),
2072
+ focus: () => ensureLoaded().focus(),
2073
+ blur: () => ensureLoaded().blur(),
2074
+ clear: () => ensureLoaded().clear(),
2075
+ on: (eventType, listener) => {
2076
+ if (!realElement) {
2077
+ let unsubscribe = null;
2078
+ let cancelled = false;
2079
+ loadRealElement().then(() => {
2080
+ if (!cancelled) unsubscribe = realElement.on(eventType, listener);
2081
+ });
2082
+ return () => {
2083
+ cancelled = true;
2084
+ unsubscribe == null ? void 0 : unsubscribe();
2085
+ };
2086
+ }
2087
+ return realElement.on(eventType, listener);
2088
+ },
2089
+ setValueRef: async (refElement) => {
2090
+ if (!realElement) {
2091
+ await loadRealElement();
2092
+ }
2093
+ if (realElement && "setValueRef" in realElement && typeof realElement.setValueRef === "function") {
2094
+ return realElement.setValueRef(refElement);
2095
+ }
2096
+ throw ElementError("setValueRef not available on this element type");
2097
+ }
2098
+ };
2099
+ if (elementType === "card") {
2100
+ Object.defineProperties(wrapper, {
2101
+ number: {
2102
+ get: () => realElement == null ? void 0 : realElement.number,
2103
+ enumerable: true
2104
+ },
2105
+ expiryDate: {
2106
+ get: () => realElement == null ? void 0 : realElement.expiryDate,
2107
+ enumerable: true
2108
+ },
2109
+ cvc: {
2110
+ get: () => realElement == null ? void 0 : realElement.cvc,
2111
+ enumerable: true
2112
+ }
2113
+ });
2114
+ }
2115
+ return wrapper;
2116
+ };
2117
+ const notifyCoordinatorElementUnmount = async (elementId) => {
2118
+ if (!coordinatorClient) {
2119
+ log2("Cannot notify coordinator of unmount - coordinator not initialized", {
2120
+ elementId
2121
+ });
2122
+ return;
2123
+ }
2124
+ try {
2125
+ await coordinatorClient.sendRequest("CMD_BROADCAST_ELEMENT_UNMOUNT", {
2126
+ elementId
2127
+ });
2128
+ log2("Notified coordinator of element unmount", { elementId });
2129
+ } catch (error) {
2130
+ log2("Failed to notify coordinator of element unmount", {
2131
+ elementId,
2132
+ error: error.message
2133
+ });
2134
+ }
2135
+ };
2136
+ const sendCoordinatorRequest = async (command, payload) => {
2137
+ if (!coordinatorClient) {
2138
+ throw CoordinatorError("Coordinator not initialized");
2139
+ }
2140
+ return coordinatorClient.sendRequest(command, payload);
2141
+ };
1682
2142
  let cardElementFactory = null;
2143
+ let copyButtonElementFactory = null;
2144
+ let cardDisplayElementFactory = null;
1683
2145
  const lazyCardElement = async () => {
1684
2146
  if (!cardElementFactory) {
1685
- const module = await import("./create-card-DipDWgph.js");
2147
+ const module = await import("./create-card-CB_YcP_4.js");
1686
2148
  cardElementFactory = module.createCardElement;
1687
2149
  }
1688
2150
  return cardElementFactory;
1689
2151
  };
2152
+ const lazyCopyButtonElement = async () => {
2153
+ if (!copyButtonElementFactory) {
2154
+ const module = await import("./create-copy-button-2XtKTEo3.js");
2155
+ copyButtonElementFactory = module.createCopyButtonElement;
2156
+ }
2157
+ return copyButtonElementFactory;
2158
+ };
2159
+ const lazyCardDisplayElement = async () => {
2160
+ if (!cardDisplayElementFactory) {
2161
+ const module = await import("./create-card-display-kBupMJwe.js");
2162
+ cardDisplayElementFactory = module.createCardDisplayElement;
2163
+ }
2164
+ return cardDisplayElementFactory;
2165
+ };
1690
2166
  const syncFactories = {
1691
2167
  cardNumber: createCardNumberElement,
1692
2168
  cvv: createCVVElement,
@@ -1694,7 +2170,9 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1694
2170
  text: createTextElement
1695
2171
  };
1696
2172
  const lazyFactories = {
1697
- card: lazyCardElement
2173
+ card: lazyCardElement,
2174
+ copyButton: lazyCopyButtonElement,
2175
+ cardDisplay: lazyCardDisplayElement
1698
2176
  };
1699
2177
  const ensureCoordinatorInitialized = () => {
1700
2178
  if (coordinatorReadyPromise) {
@@ -1736,7 +2214,10 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1736
2214
  url: coordinatorIframe.src
1737
2215
  });
1738
2216
  await new Promise((resolve, reject) => {
1739
- const timeout = setTimeout(() => {
2217
+ rejectCoordinatorLoad = reject;
2218
+ coordinatorLoadTimeout = setTimeout(() => {
2219
+ coordinatorLoadTimeout = null;
2220
+ rejectCoordinatorLoad = null;
1740
2221
  reject(
1741
2222
  new Error(
1742
2223
  `Coordinator load timeout (${config.timeoutMs}ms). Try increasing timeoutMs for slow connections.`
@@ -1746,13 +2227,21 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1746
2227
  coordinatorIframe.addEventListener(
1747
2228
  "load",
1748
2229
  () => {
1749
- clearTimeout(timeout);
2230
+ if (coordinatorLoadTimeout !== null) {
2231
+ clearTimeout(coordinatorLoadTimeout);
2232
+ }
2233
+ coordinatorLoadTimeout = null;
2234
+ rejectCoordinatorLoad = null;
1750
2235
  log2("Coordinator iframe loaded");
1751
2236
  resolve();
1752
2237
  },
1753
2238
  { once: true }
1754
2239
  );
1755
2240
  });
2241
+ if (destroyed) {
2242
+ log2("Coordinator load resolved after destroy — aborting init");
2243
+ return;
2244
+ }
1756
2245
  coordinatorClient = createPostMessageClient(coordinatorIframe, {
1757
2246
  targetOrigin: new URL(config.elementsBaseUrl).origin,
1758
2247
  timeout: 3e4,
@@ -1770,6 +2259,9 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1770
2259
  theme: config.theme,
1771
2260
  darkTheme: config.darkTheme,
1772
2261
  themeMode: config.themeMode
2262
+ // sessionAuthorizationUrl is intentionally NOT sent to the coordinator:
2263
+ // the merchant-authorization fetch now runs in the parent page (see
2264
+ // create-card-display.ts), so the coordinator never needs the URL.
1773
2265
  });
1774
2266
  log2("Coordinator initialized successfully");
1775
2267
  };
@@ -1779,7 +2271,7 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1779
2271
  * createElement is now SYNCHRONOUS - returns Element instantly
1780
2272
  * Coordinator initialization starts on first element (but doesn't block)
1781
2273
  */
1782
- createElement: (type, elementOptions) => {
2274
+ createElement: guardSync((type, elementOptions) => {
1783
2275
  const syncFactory = syncFactories[type];
1784
2276
  const lazyFactory = lazyFactories[type];
1785
2277
  if (!syncFactory && !lazyFactory) {
@@ -1802,68 +2294,22 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1802
2294
  config,
1803
2295
  elementOptions,
1804
2296
  localElementId,
1805
- ensureCoordinatorInitialized
2297
+ ensureCoordinatorInitialized,
2298
+ notifyCoordinatorElementUnmount,
2299
+ sendCoordinatorRequest
1806
2300
  );
1807
2301
  } 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,
2302
+ element = createLazyElementWrapper(
2303
+ lazyFactory,
1827
2304
  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
- };
2305
+ localElementId,
2306
+ apiKey,
2307
+ config,
2308
+ elementOptions,
2309
+ ensureCoordinatorInitialized,
2310
+ notifyCoordinatorElementUnmount,
2311
+ sendCoordinatorRequest
2312
+ );
1867
2313
  }
1868
2314
  elements.set(element.id, element);
1869
2315
  if (type === "cardNumber") {
@@ -1902,8 +2348,8 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1902
2348
  });
1903
2349
  }
1904
2350
  return element;
1905
- },
1906
- tokenize: async (payload, options2) => {
2351
+ }),
2352
+ tokenize: guardAsync(async (payload, options2) => {
1907
2353
  log2("tokenize called", {
1908
2354
  hasPayload: !!payload,
1909
2355
  hasOptions: !!options2,
@@ -1940,7 +2386,7 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1940
2386
  });
1941
2387
  throw error;
1942
2388
  }
1943
- },
2389
+ }),
1944
2390
  /**
1945
2391
  * Token CRUD operations
1946
2392
  * Supports Element references - automatically resolves to secure values
@@ -1950,7 +2396,7 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1950
2396
  * Create a new token
1951
2397
  * Supports Element references in data (same as tokenize)
1952
2398
  */
1953
- create: async (request) => {
2399
+ create: guardAsync(async (request) => {
1954
2400
  if (!request || typeof request !== "object" || Array.isArray(request)) {
1955
2401
  throw ValidationError("Token data must be a non-null object");
1956
2402
  }
@@ -1985,7 +2431,7 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1985
2431
  });
1986
2432
  throw error;
1987
2433
  }
1988
- },
2434
+ }),
1989
2435
  /**
1990
2436
  * Retrieve a token by ID
1991
2437
  *
@@ -1993,7 +2439,7 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1993
2439
  * @param options - Optional configuration
1994
2440
  * @param options.apiKey - Optional session key for authorized access (from sessions.create + authorize)
1995
2441
  */
1996
- retrieve: async (id, options2) => {
2442
+ retrieve: guardAsync(async (id, options2) => {
1997
2443
  log2("tokens.retrieve called", {
1998
2444
  tokenId: id,
1999
2445
  hasSessionKey: !!(options2 == null ? void 0 : options2.apiKey)
@@ -2023,12 +2469,12 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2023
2469
  });
2024
2470
  throw error;
2025
2471
  }
2026
- },
2472
+ }),
2027
2473
  /**
2028
2474
  * Update a token
2029
2475
  * Supports Element references in data
2030
2476
  */
2031
- update: async (id, request) => {
2477
+ update: guardAsync(async (id, request) => {
2032
2478
  log2("tokens.update called", { tokenId: id });
2033
2479
  if (!coordinatorClient) {
2034
2480
  await initializeCoordinator();
@@ -2050,7 +2496,7 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2050
2496
  });
2051
2497
  throw error;
2052
2498
  }
2053
- },
2499
+ }),
2054
2500
  /**
2055
2501
  * Encrypt token data with client-side JWE
2056
2502
  * Uses ECDH-ES key agreement and A256GCM authenticated encryption
@@ -2061,7 +2507,7 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2061
2507
  * @param keyId - Key identifier for decryption lookup
2062
2508
  * @returns Encrypted JWE token(s)
2063
2509
  */
2064
- encrypt: async (tokenData, publicKeyPEM, keyId) => {
2510
+ encrypt: guardAsync(async (tokenData, publicKeyPEM, keyId) => {
2065
2511
  log2("tokens.encrypt called", {
2066
2512
  isSingleToken: "type" in tokenData && "data" in tokenData,
2067
2513
  keyId
@@ -2102,14 +2548,14 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2102
2548
  });
2103
2549
  throw error;
2104
2550
  }
2105
- }
2551
+ })
2106
2552
  },
2107
2553
  sessions: {
2108
2554
  /**
2109
2555
  * Create a new session for temporary elevated access
2110
2556
  * Note: Session must be authorized by a Private Application on your backend
2111
2557
  */
2112
- create: async () => {
2558
+ create: guardAsync(async () => {
2113
2559
  log2("sessions.create called");
2114
2560
  if (!coordinatorClient) {
2115
2561
  await initializeCoordinator();
@@ -2133,13 +2579,13 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2133
2579
  });
2134
2580
  throw error;
2135
2581
  }
2136
- }
2582
+ })
2137
2583
  },
2138
2584
  tokenIntents: {
2139
2585
  /**
2140
2586
  * Create a token intent with element reference support
2141
2587
  */
2142
- create: async (data, options2) => {
2588
+ create: guardAsync(async (data, options2) => {
2143
2589
  log2("tokenIntents.create called", {
2144
2590
  type: data.type,
2145
2591
  hasData: !!data.data,
@@ -2172,11 +2618,11 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2172
2618
  });
2173
2619
  throw error;
2174
2620
  }
2175
- },
2621
+ }),
2176
2622
  /**
2177
2623
  * Get a token intent by ID
2178
2624
  */
2179
- get: async (id, options2) => {
2625
+ get: guardAsync(async (id, options2) => {
2180
2626
  log2("tokenIntents.get called", {
2181
2627
  intentId: id
2182
2628
  });
@@ -2204,9 +2650,57 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2204
2650
  });
2205
2651
  throw error;
2206
2652
  }
2207
- }
2653
+ })
2208
2654
  },
2209
- updateThemeMode: async (mode) => {
2655
+ proxy: (() => {
2656
+ const invokeProxy = guardAsync(async (method, options2 = {}) => {
2657
+ validateProxyOptions({ ...options2, method });
2658
+ log2("proxy invoked", {
2659
+ method,
2660
+ hasUrl: !!options2.url,
2661
+ hasProxyKey: !!options2.proxyKey
2662
+ });
2663
+ if (!coordinatorClient) {
2664
+ log2("Waiting for coordinator initialization");
2665
+ await initializeCoordinator();
2666
+ }
2667
+ if (!coordinatorClient) {
2668
+ throw CoordinatorError("Coordinator failed to initialize");
2669
+ }
2670
+ const resolvedBody = options2.body !== void 0 ? resolveElementReferences(options2.body) : void 0;
2671
+ try {
2672
+ const result = await coordinatorClient.sendRequest(
2673
+ "CMD_PROXY",
2674
+ {
2675
+ method,
2676
+ url: options2.url,
2677
+ proxyKey: options2.proxyKey,
2678
+ headers: options2.headers,
2679
+ body: resolvedBody,
2680
+ includeResponseHeaders: options2.includeResponseHeaders
2681
+ },
2682
+ { retries: 0 }
2683
+ );
2684
+ log2("proxy successful", { method });
2685
+ return result;
2686
+ } catch (error) {
2687
+ log2("proxy failed", {
2688
+ method,
2689
+ error: error.message
2690
+ });
2691
+ throw error;
2692
+ }
2693
+ });
2694
+ const proxyVerb = (method) => (options2) => invokeProxy(method, options2 ?? {});
2695
+ return {
2696
+ get: proxyVerb("GET"),
2697
+ post: proxyVerb("POST"),
2698
+ put: proxyVerb("PUT"),
2699
+ patch: proxyVerb("PATCH"),
2700
+ delete: proxyVerb("DELETE")
2701
+ };
2702
+ })(),
2703
+ updateThemeMode: guardAsync(async (mode) => {
2210
2704
  log2("updateThemeMode called", {
2211
2705
  oldMode: currentThemeMode,
2212
2706
  newMode: mode,
@@ -2245,6 +2739,79 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2245
2739
  mode,
2246
2740
  updatedCount: Array.from(elements.values()).filter((el) => el.mounted).length
2247
2741
  });
2742
+ }),
2743
+ /**
2744
+ * Destroys this instance and removes its elements from the page. Once
2745
+ * destroyed, its other methods throw; create a new instance with
2746
+ * BasisTheory() to recover. Safe to call more than once.
2747
+ */
2748
+ destroy: async () => {
2749
+ if (destroyed) {
2750
+ return;
2751
+ }
2752
+ destroyed = true;
2753
+ log2("Destroying SDK instance", {
2754
+ elementCount: elements.size,
2755
+ hasCoordinator: coordinatorIframe !== null
2756
+ });
2757
+ for (const [id, element] of elements) {
2758
+ if (!element.mounted) {
2759
+ continue;
2760
+ }
2761
+ try {
2762
+ element.unmount();
2763
+ } catch (error) {
2764
+ log2("Failed to unmount element during destroy", {
2765
+ elementId: id,
2766
+ error: error.message
2767
+ });
2768
+ }
2769
+ }
2770
+ elements.clear();
2771
+ if (coordinatorLoadTimeout !== null) {
2772
+ clearTimeout(coordinatorLoadTimeout);
2773
+ coordinatorLoadTimeout = null;
2774
+ }
2775
+ if (rejectCoordinatorLoad) {
2776
+ rejectCoordinatorLoad(
2777
+ ConfigurationError(
2778
+ "SDK instance has been destroyed. Create a new instance with BasisTheory()"
2779
+ )
2780
+ );
2781
+ rejectCoordinatorLoad = null;
2782
+ }
2783
+ if (coordinatorClient) {
2784
+ const CACHE_CLEAR_TIMEOUT_MS = 2e3;
2785
+ const cacheCleared = coordinatorClient.sendRequest("CMD_CLEAR_ALL_CACHE", {}, { retries: 0 }).catch((error) => {
2786
+ log2("Failed to clear coordinator cache during destroy", {
2787
+ error: error.message
2788
+ });
2789
+ });
2790
+ let capTimeout;
2791
+ const cap = new Promise((resolve) => {
2792
+ capTimeout = setTimeout(() => {
2793
+ log2("Coordinator cache-clear timed out during destroy", {
2794
+ timeoutMs: CACHE_CLEAR_TIMEOUT_MS
2795
+ });
2796
+ resolve();
2797
+ }, CACHE_CLEAR_TIMEOUT_MS);
2798
+ });
2799
+ await Promise.race([cacheCleared, cap]);
2800
+ if (capTimeout !== void 0) {
2801
+ clearTimeout(capTimeout);
2802
+ }
2803
+ }
2804
+ if (coordinatorClient) {
2805
+ coordinatorClient.destroy();
2806
+ coordinatorClient = null;
2807
+ }
2808
+ if (coordinatorIframe) {
2809
+ coordinatorIframe.remove();
2810
+ coordinatorIframe = null;
2811
+ }
2812
+ coordinatorReadyPromise = null;
2813
+ coordinatorInitStarted = false;
2814
+ log2("SDK instance destroyed");
2248
2815
  }
2249
2816
  };
2250
2817
  if (queuedCommands && queuedCommands.length > 0) {
@@ -2299,19 +2866,21 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2299
2866
  if (typeof window !== "undefined" && typeof document !== "undefined" && document.currentScript && !window.BasisTheory) {
2300
2867
  window.BasisTheory = BasisTheory;
2301
2868
  }
2302
- const version = "3.0.0-beta.3";
2303
- const buildDate = "2026-04-13T19:51:30.877Z";
2869
+ const version = "3.0.0-beta.5";
2870
+ const buildDate = "2026-07-16T14:24:00.230Z";
2304
2871
  if (typeof window !== "undefined") {
2305
2872
  window.__BasisTheorySDK__ = BasisTheory;
2306
2873
  }
2307
2874
  export {
2308
2875
  BasisTheory as B,
2876
+ ConfigurationError as C,
2309
2877
  ElementError as E,
2310
2878
  createPostMessageClient as a,
2311
2879
  createEventDispatcher as b,
2312
2880
  createIframe as c,
2313
2881
  createMounter as d,
2314
2882
  buildDate as e,
2883
+ generateId as g,
2315
2884
  log as l,
2316
2885
  version as v
2317
2886
  };