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

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 (58) hide show
  1. package/dist/.vite/manifest.json +19 -8
  2. package/dist/api-client/src/api-client.d.ts.map +1 -1
  3. package/dist/api-client/src/bin-lookup-transform.d.ts +35 -0
  4. package/dist/api-client/src/bin-lookup-transform.d.ts.map +1 -0
  5. package/dist/api-client/src/http-client.d.ts.map +1 -1
  6. package/dist/api-client/src/index.d.ts +1 -1
  7. package/dist/api-client/src/index.d.ts.map +1 -1
  8. package/dist/api-client/src/types.d.ts +114 -6
  9. package/dist/api-client/src/types.d.ts.map +1 -1
  10. package/dist/basis-theory-sdk.es.js +1 -1
  11. package/dist/basis-theory-sdk.umd.js +1 -1
  12. package/dist/basis-theory-sdk.umd.js.map +1 -1
  13. package/dist/{create-card-DipDWgph.js → create-card-B5zEHyRg.js} +49 -15
  14. package/dist/create-card-B5zEHyRg.js.map +1 -0
  15. package/dist/create-copy-button-DrAz8g0m.js +233 -0
  16. package/dist/create-copy-button-DrAz8g0m.js.map +1 -0
  17. package/dist/{index-BBfuUaLw.js → index-BOedIhUN.js} +406 -54
  18. package/dist/index-BOedIhUN.js.map +1 -0
  19. package/dist/loader/basis-theory.min.js +1 -1
  20. package/dist/postmessage/src/client.d.ts.map +1 -1
  21. package/dist/postmessage/src/event-map.d.ts +4 -0
  22. package/dist/postmessage/src/event-map.d.ts.map +1 -1
  23. package/dist/postmessage/src/server.d.ts.map +1 -1
  24. package/dist/postmessage/src/types.d.ts +7 -3
  25. package/dist/postmessage/src/types.d.ts.map +1 -1
  26. package/dist/shared/src/index.d.ts +1 -0
  27. package/dist/shared/src/index.d.ts.map +1 -1
  28. package/dist/shared/src/proxy-validation.d.ts +93 -0
  29. package/dist/shared/src/proxy-validation.d.ts.map +1 -0
  30. package/dist/sri.json +9 -9
  31. package/dist/stats.html +1 -1
  32. package/dist/web-elements/src/BasisTheory.d.ts +1 -1
  33. package/dist/web-elements/src/BasisTheory.d.ts.map +1 -1
  34. package/dist/web-elements/src/basis-theory.d.ts +23 -0
  35. package/dist/web-elements/src/basis-theory.d.ts.map +1 -1
  36. package/dist/web-elements/src/elements/create-card-number.d.ts +2 -1
  37. package/dist/web-elements/src/elements/create-card-number.d.ts.map +1 -1
  38. package/dist/web-elements/src/elements/create-card.d.ts +14 -1
  39. package/dist/web-elements/src/elements/create-card.d.ts.map +1 -1
  40. package/dist/web-elements/src/elements/create-copy-button.d.ts +19 -0
  41. package/dist/web-elements/src/elements/create-copy-button.d.ts.map +1 -0
  42. package/dist/web-elements/src/elements/create-cvv.d.ts +2 -1
  43. package/dist/web-elements/src/elements/create-cvv.d.ts.map +1 -1
  44. package/dist/web-elements/src/elements/create-expiry.d.ts +2 -1
  45. package/dist/web-elements/src/elements/create-expiry.d.ts.map +1 -1
  46. package/dist/web-elements/src/elements/create-text.d.ts +2 -1
  47. package/dist/web-elements/src/elements/create-text.d.ts.map +1 -1
  48. package/dist/web-elements/src/index.d.ts +1 -1
  49. package/dist/web-elements/src/index.d.ts.map +1 -1
  50. package/dist/web-elements/src/types/events.d.ts +30 -2
  51. package/dist/web-elements/src/types/events.d.ts.map +1 -1
  52. package/dist/web-elements/src/types.d.ts +135 -5
  53. package/dist/web-elements/src/types.d.ts.map +1 -1
  54. package/dist/web-elements/src/utils/create-event-dispatcher.d.ts.map +1 -1
  55. package/dist/web-elements/src/utils/validate-element-options.d.ts.map +1 -1
  56. package/package.json +5 -5
  57. package/dist/create-card-DipDWgph.js.map +0 -1
  58. package/dist/index-BBfuUaLw.js.map +0 -1
@@ -162,7 +162,7 @@ const BasisTheoryError = (code, message, details) => {
162
162
  return error;
163
163
  };
164
164
  const ValidationError = (message, field, details) => {
165
- const error = createBasisTheoryError("VALIDATION_ERROR", message, details);
165
+ const error = createBasisTheoryError("VALIDATION_ERROR", message, field ? { field, ...details } : details);
166
166
  error.name = "ValidationError";
167
167
  error.field = field;
168
168
  return error;
@@ -185,6 +185,202 @@ const ConfigurationError = (message, details) => {
185
185
  };
186
186
  const log = (action, metadata) => {
187
187
  };
188
+ const ALLOWED_PROXY_SCHEMES = /* @__PURE__ */ new Set(["https:"]);
189
+ const ALLOWED_METHODS = /* @__PURE__ */ new Set([
190
+ "GET",
191
+ "POST",
192
+ "PUT",
193
+ "PATCH",
194
+ "DELETE"
195
+ ]);
196
+ const FORBIDDEN_PROXY_HEADERS = /* @__PURE__ */ new Set([
197
+ // Request smuggling prevention
198
+ "host",
199
+ "content-length",
200
+ "transfer-encoding",
201
+ "te",
202
+ "trailer",
203
+ // Connection manipulation prevention
204
+ "connection",
205
+ "upgrade",
206
+ "keep-alive",
207
+ "proxy-connection",
208
+ // Proxy bypass prevention
209
+ "via",
210
+ "proxy-authorization",
211
+ // BasisTheory credential protection
212
+ "bt-api-key",
213
+ "bt-proxy-key",
214
+ "bt-proxy-url",
215
+ "bt-idempotency-key",
216
+ // Forwarded headers
217
+ "x-forwarded-for",
218
+ "x-forwarded-host",
219
+ "x-real-ip",
220
+ // SDK owns serialization format — body is always JSON.stringify'd
221
+ "content-type"
222
+ ]);
223
+ const validateProxyHeaders = (headers) => {
224
+ if (!headers)
225
+ return;
226
+ for (const [key, value] of Object.entries(headers)) {
227
+ if (/[\r\n]/.test(value)) {
228
+ throw ValidationError(`Header '${key}' value contains illegal CRLF`, "headers");
229
+ }
230
+ if (/[\r\n]/.test(key)) {
231
+ throw ValidationError(`Header name '${key}' contains illegal CRLF`, "headers");
232
+ }
233
+ if (FORBIDDEN_PROXY_HEADERS.has(key.toLowerCase().trim())) {
234
+ throw ValidationError(`Header '${key}' is not allowed in proxy requests`, "headers");
235
+ }
236
+ }
237
+ };
238
+ const parseIPv4 = (hostname) => {
239
+ const clean = hostname.replace(/^\[|\]$/g, "");
240
+ if (/^(0x[\da-f]+|\d+)$/i.test(clean)) {
241
+ const num = clean.toLowerCase().startsWith("0x") ? parseInt(clean, 16) : parseInt(clean, 10);
242
+ if (num >= 0 && num <= 4294967295) {
243
+ return [
244
+ num >>> 24 & 255,
245
+ num >>> 16 & 255,
246
+ num >>> 8 & 255,
247
+ num & 255
248
+ ];
249
+ }
250
+ return null;
251
+ }
252
+ const parts = clean.split(".");
253
+ if (parts.length !== 4) {
254
+ return null;
255
+ }
256
+ const octets = [];
257
+ for (const part of parts) {
258
+ let value;
259
+ if (part.toLowerCase().startsWith("0x")) {
260
+ value = parseInt(part, 16);
261
+ } else if (part.startsWith("0") && part.length > 1 && !/[89]/.test(part)) {
262
+ value = parseInt(part, 8);
263
+ } else {
264
+ value = parseInt(part, 10);
265
+ }
266
+ if (isNaN(value) || value < 0 || value > 255) {
267
+ return null;
268
+ }
269
+ octets.push(value);
270
+ }
271
+ return octets;
272
+ };
273
+ const isPrivateIPv4 = (octets) => {
274
+ const a = octets[0];
275
+ const b = octets[1];
276
+ if (a === void 0 || b === void 0)
277
+ return true;
278
+ if (a === 127)
279
+ return true;
280
+ if (a === 10)
281
+ return true;
282
+ if (a === 172 && b >= 16 && b <= 31)
283
+ return true;
284
+ if (a === 192 && b === 168)
285
+ return true;
286
+ if (a === 169 && b === 254)
287
+ return true;
288
+ if (a === 0)
289
+ return true;
290
+ return false;
291
+ };
292
+ const isBlockedIPv6 = (hostname) => {
293
+ const clean = hostname.replace(/^\[|\]$/g, "").toLowerCase();
294
+ if (clean === "::1" || clean === "0:0:0:0:0:0:0:1" || clean === "0000:0000:0000:0000:0000:0000:0000:0001") {
295
+ return true;
296
+ }
297
+ if (clean.startsWith("fe8") || clean.startsWith("fe9") || clean.startsWith("fea") || clean.startsWith("feb")) {
298
+ return true;
299
+ }
300
+ if (clean.startsWith("fc") || clean.startsWith("fd")) {
301
+ return true;
302
+ }
303
+ const ipv4MappedDotted = clean.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
304
+ if (ipv4MappedDotted) {
305
+ const ipv4Part = ipv4MappedDotted[1];
306
+ if (ipv4Part) {
307
+ const ipv4Octets = parseIPv4(ipv4Part);
308
+ if (ipv4Octets && isPrivateIPv4(ipv4Octets)) {
309
+ return true;
310
+ }
311
+ }
312
+ }
313
+ const ipv4MappedHex = clean.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
314
+ if (ipv4MappedHex) {
315
+ const highPart = ipv4MappedHex[1];
316
+ const lowPart = ipv4MappedHex[2];
317
+ if (!highPart || !lowPart) {
318
+ return true;
319
+ }
320
+ const high = parseInt(highPart, 16);
321
+ const low = parseInt(lowPart, 16);
322
+ const octets = [
323
+ high >>> 8 & 255,
324
+ high & 255,
325
+ low >>> 8 & 255,
326
+ low & 255
327
+ ];
328
+ if (isPrivateIPv4(octets)) {
329
+ return true;
330
+ }
331
+ }
332
+ return false;
333
+ };
334
+ const validateSsrfProtection = (parsed) => {
335
+ const hostname = parsed.hostname.toLowerCase();
336
+ if (parsed.username || parsed.password) {
337
+ throw ValidationError("URL must not contain userinfo (username/password)", "url");
338
+ }
339
+ if (hostname === "localhost") {
340
+ throw ValidationError("URL must not target localhost", "url");
341
+ }
342
+ if (hostname.startsWith("[") || isBlockedIPv6(hostname)) {
343
+ if (isBlockedIPv6(hostname)) {
344
+ throw ValidationError("URL must not target loopback or link-local IPv6 addresses", "url");
345
+ }
346
+ }
347
+ const ipv4Octets = parseIPv4(hostname);
348
+ if (ipv4Octets && isPrivateIPv4(ipv4Octets)) {
349
+ throw ValidationError("URL must not target private or internal IP addresses", "url");
350
+ }
351
+ };
352
+ const validateProxyUrl = (url) => {
353
+ let parsed;
354
+ try {
355
+ parsed = new URL(url);
356
+ } catch {
357
+ throw ValidationError("Invalid URL format", "url");
358
+ }
359
+ if (!ALLOWED_PROXY_SCHEMES.has(parsed.protocol)) {
360
+ throw ValidationError(`URL scheme '${parsed.protocol}' is not allowed. Only https: is permitted`, "url");
361
+ }
362
+ validateSsrfProtection(parsed);
363
+ };
364
+ const validateProxyTarget = (url, proxyKey) => {
365
+ const hasUrl = typeof url === "string" && url.length > 0;
366
+ const hasKey = typeof proxyKey === "string" && proxyKey.length > 0;
367
+ if (hasUrl === hasKey) {
368
+ throw ValidationError("Specify exactly one of url or proxyKey", "url/proxyKey");
369
+ }
370
+ if (hasUrl) {
371
+ validateProxyUrl(url);
372
+ }
373
+ };
374
+ const validateProxyOptions = (options) => {
375
+ if (options !== void 0 && (typeof options !== "object" || options === null)) {
376
+ throw ValidationError("Proxy options must be a plain object", "options");
377
+ }
378
+ if ((options == null ? void 0 : options.method) !== void 0 && !ALLOWED_METHODS.has(options.method)) {
379
+ throw ValidationError(`Method '${options.method}' is not allowed`, "method");
380
+ }
381
+ validateProxyTarget(options == null ? void 0 : options.url, options == null ? void 0 : options.proxyKey);
382
+ validateProxyHeaders(options == null ? void 0 : options.headers);
383
+ };
188
384
  const DEFAULT_RETRY_CONFIG = {
189
385
  maxRetries: 2,
190
386
  initialDelay: 500,
@@ -298,9 +494,10 @@ const createPostMessageClient = (iframe, config) => {
298
494
  iframe.contentWindow.postMessage({ type, nonce, timestamp, payload }, config.targetOrigin);
299
495
  });
300
496
  };
301
- const sendRequest = async (type, payload) => {
497
+ const sendRequest = async (type, payload, options) => {
498
+ const maxRetries = (options == null ? void 0 : options.retries) !== void 0 ? options.retries : retryConfig.maxRetries;
302
499
  let lastError = null;
303
- for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
500
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
304
501
  try {
305
502
  return await sendRequestOnce(type, payload);
306
503
  } catch (error) {
@@ -316,11 +513,11 @@ const createPostMessageClient = (iframe, config) => {
316
513
  });
317
514
  throw error;
318
515
  }
319
- if (attempt < retryConfig.maxRetries) {
516
+ if (attempt < maxRetries) {
320
517
  const delay = getRetryDelay(attempt, retryConfig);
321
518
  log2("Retrying request", {
322
519
  attempt: attempt + 1,
323
- maxRetries: retryConfig.maxRetries,
520
+ maxRetries,
324
521
  delayMs: delay,
325
522
  error: errorMessage
326
523
  });
@@ -328,7 +525,7 @@ const createPostMessageClient = (iframe, config) => {
328
525
  }
329
526
  }
330
527
  }
331
- throw lastError || new Error(`Request failed after ${retryConfig.maxRetries} retries: ${type}`);
528
+ throw lastError || new Error(`Request failed after ${maxRetries} retries: ${type}`);
332
529
  };
333
530
  const ping = async (timeout = 1e3) => {
334
531
  const start = Date.now();
@@ -405,7 +602,7 @@ const createIframe = (config) => {
405
602
  iframe.src = config.src;
406
603
  iframe.setAttribute("sandbox", config.sandbox);
407
604
  iframe.title = config.title;
408
- iframe.setAttribute("allow", "payment *");
605
+ iframe.setAttribute("allow", config.allow ?? "payment *");
409
606
  iframe.setAttribute("importance", "high");
410
607
  iframe.style.opacity = "0";
411
608
  return iframe;
@@ -444,7 +641,9 @@ const createEventDispatcher = (config) => {
444
641
  bin: payload.bin,
445
642
  cvvLengths: payload.cvvLengths,
446
643
  potentialBrands: payload.potentialBrands,
447
- matchStrength: payload.matchStrength
644
+ matchStrength: payload.matchStrength,
645
+ networks: payload.networks,
646
+ selectedNetwork: payload.selectedNetwork
448
647
  };
449
648
  dispatch("change", cardDetail);
450
649
  } else if (elementType === "card") {
@@ -453,27 +652,47 @@ const createEventDispatcher = (config) => {
453
652
  cardBrand: payload.cardBrand,
454
653
  last4: payload.last4,
455
654
  bin: payload.bin,
655
+ networks: payload.networks,
656
+ selectedNetwork: payload.selectedNetwork,
456
657
  errors: payload.errors || null
457
658
  });
458
659
  } else {
459
660
  dispatch("change", changeDetail);
460
661
  }
461
662
  });
663
+ client.subscribe(
664
+ "EVENT_NETWORK_CHANGED",
665
+ (payload) => {
666
+ if (elementType !== "cardNumber" && elementType !== "card") {
667
+ return;
668
+ }
669
+ const detail = {
670
+ network: payload.network,
671
+ previousNetwork: payload.previousNetwork,
672
+ availableNetworks: payload.availableNetworks ?? [],
673
+ selectionMode: payload.selectionMode ?? "auto"
674
+ };
675
+ dispatch("networkChanged", detail);
676
+ }
677
+ );
462
678
  client.subscribe("EVENT_FOCUS", () => {
463
679
  dispatch("focus", {});
464
680
  });
465
681
  client.subscribe("EVENT_BLUR", () => {
466
682
  dispatch("blur", {});
467
683
  });
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
- });
684
+ client.subscribe(
685
+ "EVENT_ERROR",
686
+ (payload) => {
687
+ dispatch("error", {
688
+ code: payload.code,
689
+ message: payload.message,
690
+ status: payload.status,
691
+ validationErrors: payload.validationErrors,
692
+ metadata: payload.metadata || {}
693
+ });
694
+ }
695
+ );
477
696
  };
478
697
  const dispatchError = (code, message, error) => {
479
698
  dispatch("error", {
@@ -490,7 +709,7 @@ const createEventDispatcher = (config) => {
490
709
  dispatchError
491
710
  };
492
711
  };
493
- const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
712
+ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount) => {
494
713
  let iframe = null;
495
714
  let client = null;
496
715
  let mounter = null;
@@ -511,6 +730,12 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
511
730
  eventTarget
512
731
  });
513
732
  eventDispatcher.setupSubscriptions();
733
+ client.subscribe("EVENT_CONTENT_HEIGHT", (payload) => {
734
+ const h = payload == null ? void 0 : payload.heightPx;
735
+ if (iframe && typeof h === "number" && h > 0) {
736
+ iframe.style.height = `${Math.ceil(h)}px`;
737
+ }
738
+ });
514
739
  };
515
740
  return {
516
741
  id,
@@ -587,7 +812,9 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
587
812
  placeholder: options.placeholder,
588
813
  disabled: options.disabled,
589
814
  readOnly: options.readOnly,
590
- debug: sdkConfig.debug
815
+ debug: sdkConfig.debug,
816
+ binLookup: options.binLookup,
817
+ coBadge: options.coBadge
591
818
  });
592
819
  if (iframe) iframe.style.opacity = "1";
593
820
  } catch (error) {
@@ -617,16 +844,17 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
617
844
  if (!isMounted) {
618
845
  throw ElementError("Element is not mounted");
619
846
  }
620
- if (client) {
621
- client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
622
- log("Failed to send clear cache command", {
623
- error: error.message
624
- });
847
+ notifyCoordinatorElementUnmount(id).catch((error) => {
848
+ log("Failed to notify coordinator of element unmount", {
849
+ error: error.message
625
850
  });
851
+ });
852
+ if (client) {
626
853
  client.destroy();
627
854
  client = null;
628
855
  }
629
856
  if (mounter && iframe) {
857
+ iframe.style.height = "";
630
858
  mounter.unmount(iframe);
631
859
  iframe = null;
632
860
  mounter = null;
@@ -640,10 +868,23 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
640
868
  throw ElementError("Element must be mounted before updating");
641
869
  }
642
870
  const updatePayload = {
643
- placeholder: newOptions.placeholder,
644
- disabled: newOptions.disabled,
645
- readOnly: newOptions.readOnly
871
+ elementId: id
646
872
  };
873
+ if (newOptions.placeholder !== void 0) {
874
+ updatePayload.placeholder = newOptions.placeholder;
875
+ }
876
+ if (newOptions.disabled !== void 0) {
877
+ updatePayload.disabled = newOptions.disabled;
878
+ }
879
+ if (newOptions.readOnly !== void 0) {
880
+ updatePayload.readOnly = newOptions.readOnly;
881
+ }
882
+ if (newOptions.binLookup !== void 0) {
883
+ updatePayload.binLookup = newOptions.binLookup;
884
+ }
885
+ if (newOptions.coBadge !== void 0) {
886
+ updatePayload.coBadge = newOptions.coBadge;
887
+ }
647
888
  if (newOptions.themeMode !== void 0) {
648
889
  updatePayload.themeMode = newOptions.themeMode;
649
890
  updatePayload.theme = sdkConfig.theme;
@@ -669,6 +910,12 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
669
910
  }
670
911
  client.sendRequest("CMD_CLEAR", {});
671
912
  },
913
+ setNetwork: async (network) => {
914
+ if (!isMounted || !client) {
915
+ throw ElementError("Element must be mounted before setNetwork");
916
+ }
917
+ await client.sendRequest("CMD_SET_NETWORK", { network });
918
+ },
672
919
  on: (eventType, listener) => {
673
920
  eventTarget.addEventListener(
674
921
  eventType,
@@ -681,7 +928,7 @@ const createCardNumberElement = (apiKey, sdkConfig, options = {}, coordinatorEle
681
928
  }
682
929
  };
683
930
  };
684
- const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
931
+ const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount) => {
685
932
  let iframe = null;
686
933
  let client = null;
687
934
  let mounter = null;
@@ -804,12 +1051,12 @@ const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId,
804
1051
  if (!isMounted) {
805
1052
  throw ElementError("Element is not mounted");
806
1053
  }
807
- if (client) {
808
- client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
809
- log("Failed to send clear cache command", {
810
- error: error.message
811
- });
1054
+ notifyCoordinatorElementUnmount(id).catch((error) => {
1055
+ log("Failed to notify coordinator of element unmount", {
1056
+ error: error.message
812
1057
  });
1058
+ });
1059
+ if (client) {
813
1060
  client.destroy();
814
1061
  client = null;
815
1062
  }
@@ -871,7 +1118,7 @@ const createCVVElement = (apiKey, sdkConfig, options = {}, coordinatorElementId,
871
1118
  }
872
1119
  };
873
1120
  };
874
- const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
1121
+ const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount) => {
875
1122
  let iframe = null;
876
1123
  let client = null;
877
1124
  let mounter = null;
@@ -993,12 +1240,12 @@ const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElement
993
1240
  if (!isMounted) {
994
1241
  throw ElementError("Element is not mounted");
995
1242
  }
996
- if (client) {
997
- client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
998
- log("Failed to send clear cache command", {
999
- error: error.message
1000
- });
1243
+ notifyCoordinatorElementUnmount(id).catch((error) => {
1244
+ log("Failed to notify coordinator of element unmount", {
1245
+ error: error.message
1001
1246
  });
1247
+ });
1248
+ if (client) {
1002
1249
  client.destroy();
1003
1250
  client = null;
1004
1251
  }
@@ -1057,7 +1304,7 @@ const createExpiryElement = (apiKey, sdkConfig, options = {}, coordinatorElement
1057
1304
  }
1058
1305
  };
1059
1306
  };
1060
- const createTextElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady) => {
1307
+ const createTextElement = (apiKey, sdkConfig, options = {}, coordinatorElementId, ensureCoordinatorReady, notifyCoordinatorElementUnmount) => {
1061
1308
  let iframe = null;
1062
1309
  let client = null;
1063
1310
  let mounter = null;
@@ -1207,12 +1454,12 @@ const createTextElement = (apiKey, sdkConfig, options = {}, coordinatorElementId
1207
1454
  if (!isMounted) {
1208
1455
  throw ElementError("Element is not mounted");
1209
1456
  }
1210
- if (client) {
1211
- client.sendRequest("CMD_CLEAR_CACHE", {}).catch((error) => {
1212
- log("Failed to send clear cache command", {
1213
- error: error.message
1214
- });
1457
+ notifyCoordinatorElementUnmount(id).catch((error) => {
1458
+ log("Failed to notify coordinator of element unmount", {
1459
+ error: error.message
1215
1460
  });
1461
+ });
1462
+ if (client) {
1216
1463
  client.destroy();
1217
1464
  client = null;
1218
1465
  }
@@ -1540,7 +1787,7 @@ const validateSDKConfig = (options = {}) => {
1540
1787
  if (!["light", "dark", "auto"].includes(themeMode)) {
1541
1788
  throw ConfigurationError('themeMode must be "light", "dark", or "auto"');
1542
1789
  }
1543
- const finalBaseUrl = baseUrl || "https://js.basistheory.com/3.0.0-beta.3/elements";
1790
+ const finalBaseUrl = baseUrl || "https://js.basistheory.com/3.0.0-beta.4/elements";
1544
1791
  const iframeOrigin = new URL(finalBaseUrl).origin;
1545
1792
  return {
1546
1793
  iframeOrigin,
@@ -1566,7 +1813,7 @@ const COMMON_ELEMENT_KEYS = /* @__PURE__ */ new Set([
1566
1813
  "readOnly"
1567
1814
  ]);
1568
1815
  const ELEMENT_TYPE_KEYS = {
1569
- cardNumber: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS]),
1816
+ cardNumber: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS, "binLookup", "coBadge"]),
1570
1817
  cvv: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS, "cardBrand", "cvvLengths"]),
1571
1818
  // Internal coordination props
1572
1819
  expiry: /* @__PURE__ */ new Set([...COMMON_ELEMENT_KEYS]),
@@ -1585,7 +1832,15 @@ const ELEMENT_TYPE_KEYS = {
1585
1832
  "layout",
1586
1833
  "stackAt",
1587
1834
  "iconPosition",
1588
- "cardBrands"
1835
+ "cardBrands",
1836
+ "binLookup",
1837
+ "coBadge"
1838
+ ]),
1839
+ copyButton: /* @__PURE__ */ new Set([
1840
+ "allowClipboard",
1841
+ "value",
1842
+ "text",
1843
+ "disabled"
1589
1844
  ])
1590
1845
  };
1591
1846
  const validateElementOptions = (type, options = {}) => {
@@ -1636,6 +1891,14 @@ const validateElementOptions = (type, options = {}) => {
1636
1891
  }
1637
1892
  }
1638
1893
  }
1894
+ if (type === "copyButton") {
1895
+ const copyOpts = options;
1896
+ if (copyOpts.allowClipboard !== true) {
1897
+ throw ConfigurationError(
1898
+ "CopyButton requires allowClipboard: true. Clipboard access for raw card data may not be PCI compliant and must be explicitly enabled."
1899
+ );
1900
+ }
1901
+ }
1639
1902
  if (type === "card") {
1640
1903
  const cardOptions = options;
1641
1904
  if (cardOptions.layout !== void 0) {
@@ -1679,14 +1942,41 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1679
1942
  let coordinatorReadyPromise = null;
1680
1943
  let coordinatorInitStarted = false;
1681
1944
  let currentThemeMode = options.themeMode || "auto";
1945
+ const notifyCoordinatorElementUnmount = async (elementId) => {
1946
+ if (!coordinatorClient) {
1947
+ log2("Cannot notify coordinator of unmount - coordinator not initialized", {
1948
+ elementId
1949
+ });
1950
+ return;
1951
+ }
1952
+ try {
1953
+ await coordinatorClient.sendRequest("CMD_BROADCAST_ELEMENT_UNMOUNT", {
1954
+ elementId
1955
+ });
1956
+ log2("Notified coordinator of element unmount", { elementId });
1957
+ } catch (error) {
1958
+ log2("Failed to notify coordinator of element unmount", {
1959
+ elementId,
1960
+ error: error.message
1961
+ });
1962
+ }
1963
+ };
1682
1964
  let cardElementFactory = null;
1965
+ let copyButtonElementFactory = null;
1683
1966
  const lazyCardElement = async () => {
1684
1967
  if (!cardElementFactory) {
1685
- const module = await import("./create-card-DipDWgph.js");
1968
+ const module = await import("./create-card-B5zEHyRg.js");
1686
1969
  cardElementFactory = module.createCardElement;
1687
1970
  }
1688
1971
  return cardElementFactory;
1689
1972
  };
1973
+ const lazyCopyButtonElement = async () => {
1974
+ if (!copyButtonElementFactory) {
1975
+ const module = await import("./create-copy-button-DrAz8g0m.js");
1976
+ copyButtonElementFactory = module.createCopyButtonElement;
1977
+ }
1978
+ return copyButtonElementFactory;
1979
+ };
1690
1980
  const syncFactories = {
1691
1981
  cardNumber: createCardNumberElement,
1692
1982
  cvv: createCVVElement,
@@ -1694,7 +1984,8 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1694
1984
  text: createTextElement
1695
1985
  };
1696
1986
  const lazyFactories = {
1697
- card: lazyCardElement
1987
+ card: lazyCardElement,
1988
+ copyButton: lazyCopyButtonElement
1698
1989
  };
1699
1990
  const ensureCoordinatorInitialized = () => {
1700
1991
  if (coordinatorReadyPromise) {
@@ -1802,7 +2093,8 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1802
2093
  config,
1803
2094
  elementOptions,
1804
2095
  localElementId,
1805
- ensureCoordinatorInitialized
2096
+ ensureCoordinatorInitialized,
2097
+ notifyCoordinatorElementUnmount
1806
2098
  );
1807
2099
  } else {
1808
2100
  let realElement = null;
@@ -1816,7 +2108,8 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1816
2108
  config,
1817
2109
  elementOptions,
1818
2110
  localElementId,
1819
- ensureCoordinatorInitialized
2111
+ ensureCoordinatorInitialized,
2112
+ notifyCoordinatorElementUnmount
1820
2113
  );
1821
2114
  })();
1822
2115
  }
@@ -1862,6 +2155,16 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
1862
2155
  };
1863
2156
  }
1864
2157
  return realElement.on(eventType, listener);
2158
+ },
2159
+ // Forward setValueRef for CopyButtonElement
2160
+ setValueRef: async (refElement) => {
2161
+ if (!realElement) {
2162
+ await loadRealElement();
2163
+ }
2164
+ if (realElement && "setValueRef" in realElement && typeof realElement.setValueRef === "function") {
2165
+ return realElement.setValueRef(refElement);
2166
+ }
2167
+ throw ElementError("setValueRef not available on this element type");
1865
2168
  }
1866
2169
  };
1867
2170
  }
@@ -2206,6 +2509,54 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2206
2509
  }
2207
2510
  }
2208
2511
  },
2512
+ proxy: (() => {
2513
+ const invokeProxy = async (method, options2 = {}) => {
2514
+ validateProxyOptions({ ...options2, method });
2515
+ log2("proxy invoked", {
2516
+ method,
2517
+ hasUrl: !!options2.url,
2518
+ hasProxyKey: !!options2.proxyKey
2519
+ });
2520
+ if (!coordinatorClient) {
2521
+ log2("Waiting for coordinator initialization");
2522
+ await initializeCoordinator();
2523
+ }
2524
+ if (!coordinatorClient) {
2525
+ throw CoordinatorError("Coordinator failed to initialize");
2526
+ }
2527
+ const resolvedBody = options2.body !== void 0 ? resolveElementReferences(options2.body) : void 0;
2528
+ try {
2529
+ const result = await coordinatorClient.sendRequest(
2530
+ "CMD_PROXY",
2531
+ {
2532
+ method,
2533
+ url: options2.url,
2534
+ proxyKey: options2.proxyKey,
2535
+ headers: options2.headers,
2536
+ body: resolvedBody,
2537
+ includeResponseHeaders: options2.includeResponseHeaders
2538
+ },
2539
+ { retries: 0 }
2540
+ );
2541
+ log2("proxy successful", { method });
2542
+ return result;
2543
+ } catch (error) {
2544
+ log2("proxy failed", {
2545
+ method,
2546
+ error: error.message
2547
+ });
2548
+ throw error;
2549
+ }
2550
+ };
2551
+ const proxyVerb = (method) => (options2) => invokeProxy(method, options2 ?? {});
2552
+ return {
2553
+ get: proxyVerb("GET"),
2554
+ post: proxyVerb("POST"),
2555
+ put: proxyVerb("PUT"),
2556
+ patch: proxyVerb("PATCH"),
2557
+ delete: proxyVerb("DELETE")
2558
+ };
2559
+ })(),
2209
2560
  updateThemeMode: async (mode) => {
2210
2561
  log2("updateThemeMode called", {
2211
2562
  oldMode: currentThemeMode,
@@ -2299,13 +2650,14 @@ const BasisTheory = (apiKey, options = {}, queuedCommands) => {
2299
2650
  if (typeof window !== "undefined" && typeof document !== "undefined" && document.currentScript && !window.BasisTheory) {
2300
2651
  window.BasisTheory = BasisTheory;
2301
2652
  }
2302
- const version = "3.0.0-beta.3";
2303
- const buildDate = "2026-04-13T19:51:30.877Z";
2653
+ const version = "3.0.0-beta.4";
2654
+ const buildDate = "2026-06-09T15:35:24.276Z";
2304
2655
  if (typeof window !== "undefined") {
2305
2656
  window.__BasisTheorySDK__ = BasisTheory;
2306
2657
  }
2307
2658
  export {
2308
2659
  BasisTheory as B,
2660
+ ConfigurationError as C,
2309
2661
  ElementError as E,
2310
2662
  createPostMessageClient as a,
2311
2663
  createEventDispatcher as b,