@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.
- package/README.md +668 -2
- package/dist/.vite/manifest.json +27 -0
- package/dist/api-client/src/api-client.d.ts +11 -0
- package/dist/api-client/src/api-client.d.ts.map +1 -0
- package/dist/api-client/src/http-client.d.ts +24 -0
- package/dist/api-client/src/http-client.d.ts.map +1 -0
- package/dist/api-client/src/index.d.ts +9 -0
- package/dist/api-client/src/index.d.ts.map +1 -0
- package/dist/api-client/src/services/sessions.d.ts +19 -0
- package/dist/api-client/src/services/sessions.d.ts.map +1 -0
- package/dist/api-client/src/services/token-intents.d.ts +17 -0
- package/dist/api-client/src/services/token-intents.d.ts.map +1 -0
- package/dist/api-client/src/services/tokens.d.ts +19 -0
- package/dist/api-client/src/services/tokens.d.ts.map +1 -0
- package/dist/api-client/src/types.d.ts +313 -0
- package/dist/api-client/src/types.d.ts.map +1 -0
- package/dist/basis-theory-sdk.es.js +6 -0
- package/dist/basis-theory-sdk.es.js.map +1 -0
- package/dist/basis-theory-sdk.umd.js +1 -0
- package/dist/basis-theory-sdk.umd.js.map +1 -0
- package/dist/create-card-BuOvimjH.js +232 -0
- package/dist/create-card-BuOvimjH.js.map +1 -0
- package/dist/index-oREjBjga.js +2317 -0
- package/dist/index-oREjBjga.js.map +1 -0
- package/dist/loader/basis-theory.min.js +1 -0
- package/dist/postmessage/src/client.d.ts +7 -0
- package/dist/postmessage/src/client.d.ts.map +1 -0
- package/dist/postmessage/src/event-map.d.ts +135 -0
- package/dist/postmessage/src/event-map.d.ts.map +1 -0
- package/dist/postmessage/src/index.d.ts +9 -0
- package/dist/postmessage/src/index.d.ts.map +1 -0
- package/dist/postmessage/src/server.d.ts +11 -0
- package/dist/postmessage/src/server.d.ts.map +1 -0
- package/dist/postmessage/src/types.d.ts +71 -0
- package/dist/postmessage/src/types.d.ts.map +1 -0
- package/dist/shared/src/api-url-resolver.d.ts +22 -0
- package/dist/shared/src/api-url-resolver.d.ts.map +1 -0
- package/dist/shared/src/debug-logger.d.ts +16 -0
- package/dist/shared/src/debug-logger.d.ts.map +1 -0
- package/dist/shared/src/deep-sanitization.d.ts +24 -0
- package/dist/shared/src/deep-sanitization.d.ts.map +1 -0
- package/dist/shared/src/errors.d.ts +93 -0
- package/dist/shared/src/errors.d.ts.map +1 -0
- package/dist/shared/src/generate-id.d.ts +13 -0
- package/dist/shared/src/generate-id.d.ts.map +1 -0
- package/dist/shared/src/index.d.ts +13 -0
- package/dist/shared/src/index.d.ts.map +1 -0
- package/dist/shared/src/logger-context.d.ts +25 -0
- package/dist/shared/src/logger-context.d.ts.map +1 -0
- package/dist/sri.json +22 -0
- package/dist/stats.html +4949 -0
- package/dist/web-elements/src/BasisTheory.d.ts +47 -0
- package/dist/web-elements/src/BasisTheory.d.ts.map +1 -0
- package/dist/web-elements/src/basis-theory.d.ts +62 -0
- package/dist/web-elements/src/basis-theory.d.ts.map +1 -0
- package/dist/web-elements/src/elements/create-card-number.d.ts +14 -0
- package/dist/web-elements/src/elements/create-card-number.d.ts.map +1 -0
- package/dist/web-elements/src/elements/create-card.d.ts +37 -0
- package/dist/web-elements/src/elements/create-card.d.ts.map +1 -0
- package/dist/web-elements/src/elements/create-cvv.d.ts +10 -0
- package/dist/web-elements/src/elements/create-cvv.d.ts.map +1 -0
- package/dist/web-elements/src/elements/create-expiry.d.ts +10 -0
- package/dist/web-elements/src/elements/create-expiry.d.ts.map +1 -0
- package/dist/web-elements/src/elements/create-text.d.ts +13 -0
- package/dist/web-elements/src/elements/create-text.d.ts.map +1 -0
- package/dist/web-elements/src/index.d.ts +27 -0
- package/dist/web-elements/src/index.d.ts.map +1 -0
- package/dist/web-elements/src/mounter/create-mounter.d.ts +11 -0
- package/dist/web-elements/src/mounter/create-mounter.d.ts.map +1 -0
- package/dist/web-elements/src/theme/default-theme.d.ts +22 -0
- package/dist/web-elements/src/theme/default-theme.d.ts.map +1 -0
- package/dist/web-elements/src/theme/inject-theme-dark.d.ts +13 -0
- package/dist/web-elements/src/theme/inject-theme-dark.d.ts.map +1 -0
- package/dist/web-elements/src/theme/inject-theme.d.ts +20 -0
- package/dist/web-elements/src/theme/inject-theme.d.ts.map +1 -0
- package/dist/web-elements/src/types/events.d.ts +137 -0
- package/dist/web-elements/src/types/events.d.ts.map +1 -0
- package/dist/web-elements/src/types/theme.d.ts +91 -0
- package/dist/web-elements/src/types/theme.d.ts.map +1 -0
- package/dist/web-elements/src/types.d.ts +537 -0
- package/dist/web-elements/src/types.d.ts.map +1 -0
- package/dist/web-elements/src/utils/create-event-dispatcher.d.ts +47 -0
- package/dist/web-elements/src/utils/create-event-dispatcher.d.ts.map +1 -0
- package/dist/web-elements/src/utils/create-iframe.d.ts +10 -0
- package/dist/web-elements/src/utils/create-iframe.d.ts.map +1 -0
- package/dist/web-elements/src/utils/resolve-element-references.d.ts +20 -0
- package/dist/web-elements/src/utils/resolve-element-references.d.ts.map +1 -0
- package/dist/web-elements/src/utils/validate-element-options.d.ts +11 -0
- package/dist/web-elements/src/utils/validate-element-options.d.ts.map +1 -0
- package/dist/web-elements/src/utils/validate-sdk-config.d.ts +15 -0
- package/dist/web-elements/src/utils/validate-sdk-config.d.ts.map +1 -0
- package/dist/web-elements/src/utils/warn-unknown-keys.d.ts +15 -0
- package/dist/web-elements/src/utils/warn-unknown-keys.d.ts.map +1 -0
- package/dist/web-elements/src/version.d.ts +25 -0
- package/dist/web-elements/src/version.d.ts.map +1 -0
- package/package.json +53 -72
- package/LICENSE +0 -202
- package/dist/LICENSE +0 -202
- package/dist/README.md +0 -3
- package/dist/bundle/index.js +0 -1
- package/dist/main/index.js +0 -1160
- package/dist/module/module.js +0 -1158
- package/dist/package.json +0 -37
- 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
|
+
};
|