@ozura/elements 1.0.2-next.16 → 1.0.2-next.18
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 +106 -0
- package/dist/oz-elements.esm.js +138 -11
- package/dist/oz-elements.esm.js.map +1 -1
- package/dist/oz-elements.umd.js +138 -11
- package/dist/oz-elements.umd.js.map +1 -1
- package/dist/react/index.cjs.js +141 -14
- package/dist/react/index.cjs.js.map +1 -1
- package/dist/react/index.esm.js +141 -14
- package/dist/react/index.esm.js.map +1 -1
- package/dist/react/react/index.d.ts +12 -1
- package/dist/react/sdk/OzVault.d.ts +26 -0
- package/dist/react/types/index.d.ts +18 -0
- package/dist/server/sdk/OzVault.d.ts +26 -0
- package/dist/server/types/index.d.ts +18 -0
- package/dist/types/sdk/OzVault.d.ts +26 -0
- package/dist/types/types/index.d.ts +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Card data is collected inside Ozura-hosted iframes so raw numbers never touch yo
|
|
|
28
28
|
- [vault.createBankToken()](#vaultcreatebanktokenoptions)
|
|
29
29
|
- [vault.reset()](#vaultreset)
|
|
30
30
|
- [vault.destroy()](#vaultdestroy)
|
|
31
|
+
- [vault.debugState()](#vaultdebugstate)
|
|
31
32
|
- [OzElement events](#ozelement-events)
|
|
32
33
|
- [React API](#react-api)
|
|
33
34
|
- [OzElements provider](#ozelements-provider)
|
|
@@ -41,6 +42,7 @@ Card data is collected inside Ozura-hosted iframes so raw numbers never touch yo
|
|
|
41
42
|
- [Custom fonts](#custom-fonts)
|
|
42
43
|
- [Billing details](#billing-details)
|
|
43
44
|
- [Error handling](#error-handling)
|
|
45
|
+
- [Debug mode](#debug-mode)
|
|
44
46
|
- [Server utilities](#server-utilities)
|
|
45
47
|
- [Ozura class](#ozura-class)
|
|
46
48
|
- [Route handler factories](#route-handler-factories)
|
|
@@ -361,6 +363,7 @@ Mounts the hidden tokenizer iframe and fetches the wax key concurrently. Both ha
|
|
|
361
363
|
| `onWaxRefresh` | `() => void` | — | Called when the SDK silently re-mints an expired wax key mid-tokenization. |
|
|
362
364
|
| `onReady` | `() => void` | — | Called once when the tokenizer iframe has loaded and is ready. Use in vanilla JS to re-check submit-button readiness when the tokenizer becomes ready after all element iframes have already fired. In React, `useOzElements().ready` handles this automatically. |
|
|
363
365
|
| `maxTokenizeCalls` | `number` | — | Maximum successful `createToken` calls per wax key before the key is considered consumed. Default: `3`. Must match `maxTokenizeCalls` in your server-side `mintWaxKey` call. |
|
|
366
|
+
| `debug` | `boolean` | — | Enables structured `[OzVault]`-prefixed `console.log` output at every lifecycle event. Safe to use in production — no sensitive data is ever logged. Default: `false`. See [Debug mode](#debug-mode) for details. |
|
|
364
367
|
|
|
365
368
|
Throws `OzError` if `fetchWaxKey` rejects, returns an empty string, or returns a non-string value.
|
|
366
369
|
|
|
@@ -580,6 +583,37 @@ try {
|
|
|
580
583
|
|
|
581
584
|
---
|
|
582
585
|
|
|
586
|
+
### vault.debugState()
|
|
587
|
+
|
|
588
|
+
```ts
|
|
589
|
+
vault.debugState(): Record<string, unknown>
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
Returns a structured snapshot of the vault's internal state. Always available regardless of whether `debug: true` is set. Useful for attaching to support tickets or dumping on error.
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
console.log(vault.debugState());
|
|
596
|
+
// {
|
|
597
|
+
// vaultId: 'vault_abc12...',
|
|
598
|
+
// isReady: true,
|
|
599
|
+
// tokenizing: null,
|
|
600
|
+
// destroyed: false,
|
|
601
|
+
// waxKeyPresent: true,
|
|
602
|
+
// tokenizeSuccessCount: 1,
|
|
603
|
+
// maxTokenizeCalls: 3,
|
|
604
|
+
// resetCount: 0,
|
|
605
|
+
// elements: ['cardNumber', 'expirationDate', 'cvv'],
|
|
606
|
+
// bankElements: [],
|
|
607
|
+
// completionState: { 'a1b2c3d4': true, 'e5f6a7b8': true, '...' : false },
|
|
608
|
+
// pendingTokenizations: 0,
|
|
609
|
+
// pendingBankTokenizations: 0,
|
|
610
|
+
// }
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
No sensitive data is returned: wax keys, tokens, CVC sessions, and billing fields are never included.
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
583
617
|
### OzElement events
|
|
584
618
|
|
|
585
619
|
```ts
|
|
@@ -940,6 +974,78 @@ const display = normalizeCardSaleError(err.message); // cardSale API errors
|
|
|
940
974
|
|
|
941
975
|
---
|
|
942
976
|
|
|
977
|
+
## Debug mode
|
|
978
|
+
|
|
979
|
+
Pass `debug: true` in `VaultOptions` (or as a prop on `<OzElements>`) to activate structured console logging at every SDK lifecycle event.
|
|
980
|
+
|
|
981
|
+
```ts
|
|
982
|
+
const vault = await OzVault.create({
|
|
983
|
+
pubKey: 'pk_live_...',
|
|
984
|
+
fetchWaxKey: createFetchWaxKey('/api/mint-wax'),
|
|
985
|
+
debug: true, // enables [OzVault] console.log output
|
|
986
|
+
});
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
```tsx
|
|
990
|
+
// React
|
|
991
|
+
<OzElements pubKey="pk_live_..." fetchWaxKey={...} debug>
|
|
992
|
+
...
|
|
993
|
+
</OzElements>
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
Each log entry is a `[OzVault] <message>` prefixed `console.log` call. Events logged include:
|
|
997
|
+
|
|
998
|
+
| Event | When it fires |
|
|
999
|
+
|---|---|
|
|
1000
|
+
| `vault created` | Constructor completes |
|
|
1001
|
+
| `wax key received` | `fetchWaxKey` resolves |
|
|
1002
|
+
| `mounting tokenizer iframe` | Tokenizer iframe creation begins |
|
|
1003
|
+
| `tokenizer iframe ready` | Tokenizer iframe handshake complete |
|
|
1004
|
+
| `element iframe ready` | Each card/bank input iframe loads |
|
|
1005
|
+
| `field changed` | Per-field `change` event (empty/complete/valid/auto-advance state) |
|
|
1006
|
+
| `auto-advance` | Focus moves automatically between card fields |
|
|
1007
|
+
| `createToken() called` | Entry to `createToken()` |
|
|
1008
|
+
| `OZ_TOKENIZE sent` | Tokenize request dispatched to iframe |
|
|
1009
|
+
| `token received` | Token result returned (with elapsed ms) |
|
|
1010
|
+
| `token error` | Vault or network error during tokenize |
|
|
1011
|
+
| `proactive wax key refresh triggered` | Budget exhausted; refresh starting |
|
|
1012
|
+
| `wax key refresh started/succeeded/failed` | Refresh lifecycle |
|
|
1013
|
+
| `tab hidden` / `tab visible` | `visibilitychange` events |
|
|
1014
|
+
| `reset() called` | `vault.reset()` entry |
|
|
1015
|
+
| `destroy() called` | `vault.destroy()` entry |
|
|
1016
|
+
|
|
1017
|
+
**Security:** No sensitive data is ever logged. Wax keys, tokens, CVC sessions, and billing fields appear only as boolean presence flags (`waxKeyPresent: true`). Frame IDs and request IDs are truncated.
|
|
1018
|
+
|
|
1019
|
+
### vault.debugState()
|
|
1020
|
+
|
|
1021
|
+
`vault.debugState()` is always available — regardless of whether `debug: true` was set — and returns a one-time snapshot for attaching to bug reports:
|
|
1022
|
+
|
|
1023
|
+
```ts
|
|
1024
|
+
console.log(vault.debugState());
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
Sample output:
|
|
1028
|
+
|
|
1029
|
+
```json
|
|
1030
|
+
{
|
|
1031
|
+
"vaultId": "vault_abc12...",
|
|
1032
|
+
"isReady": true,
|
|
1033
|
+
"tokenizing": null,
|
|
1034
|
+
"destroyed": false,
|
|
1035
|
+
"waxKeyPresent": true,
|
|
1036
|
+
"tokenizeSuccessCount": 1,
|
|
1037
|
+
"maxTokenizeCalls": 3,
|
|
1038
|
+
"resetCount": 0,
|
|
1039
|
+
"elements": ["cardNumber", "expirationDate", "cvv"],
|
|
1040
|
+
"bankElements": [],
|
|
1041
|
+
"completionState": { "a1b2c3d4": true, "e5f6a7b8": true, "c9d0e1f2": false },
|
|
1042
|
+
"pendingTokenizations": 0,
|
|
1043
|
+
"pendingBankTokenizations": 0
|
|
1044
|
+
}
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
---
|
|
1048
|
+
|
|
943
1049
|
## Server utilities
|
|
944
1050
|
|
|
945
1051
|
> 📖 [Server SDK guide](https://docs.ozura.com/sdks/elements/server) — `Ozura` class methods, route handler factories, `getClientIp`, error types, and rate limits.
|
package/dist/oz-elements.esm.js
CHANGED
|
@@ -929,7 +929,7 @@ class OzVault {
|
|
|
929
929
|
* @internal
|
|
930
930
|
*/
|
|
931
931
|
constructor(options, waxKey, tokenizationSessionId) {
|
|
932
|
-
var _a, _b, _c;
|
|
932
|
+
var _a, _b, _c, _d;
|
|
933
933
|
this.elements = new Map();
|
|
934
934
|
this.elementsByType = new Map();
|
|
935
935
|
this.bankElementsByType = new Map();
|
|
@@ -942,6 +942,9 @@ class OzVault {
|
|
|
942
942
|
this.tokenizerReady = false;
|
|
943
943
|
this._tokenizing = null;
|
|
944
944
|
this._destroyed = false;
|
|
945
|
+
// Incremented every time reset() cancels an active tokenization so that
|
|
946
|
+
// any in-flight wax-key refresh retry can detect it was superseded.
|
|
947
|
+
this._resetCount = 0;
|
|
945
948
|
// Tracks successful tokenizations against the per-key call budget so the SDK
|
|
946
949
|
// can proactively refresh the wax key after it has been consumed rather than
|
|
947
950
|
// waiting for the next createToken() call to fail.
|
|
@@ -961,13 +964,14 @@ class OzVault {
|
|
|
961
964
|
this.resolvedAppearance = resolveAppearance(options.appearance);
|
|
962
965
|
this.vaultId = `vault-${uuid()}`;
|
|
963
966
|
this._maxTokenizeCalls = (_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3;
|
|
967
|
+
this._debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
|
|
964
968
|
this.boundHandleMessage = this.handleMessage.bind(this);
|
|
965
969
|
window.addEventListener('message', this.boundHandleMessage);
|
|
966
970
|
this.boundHandleVisibility = this.handleVisibilityChange.bind(this);
|
|
967
971
|
document.addEventListener('visibilitychange', this.boundHandleVisibility);
|
|
968
972
|
this.mountTokenizerFrame();
|
|
969
973
|
if (options.onLoadError) {
|
|
970
|
-
const timeout = (
|
|
974
|
+
const timeout = (_d = options.loadTimeoutMs) !== null && _d !== void 0 ? _d : 10000;
|
|
971
975
|
this.loadErrorTimeoutId = setTimeout(() => {
|
|
972
976
|
this.loadErrorTimeoutId = null;
|
|
973
977
|
if (!this._destroyed && !this.tokenizerReady) {
|
|
@@ -977,6 +981,7 @@ class OzVault {
|
|
|
977
981
|
}
|
|
978
982
|
this._onWaxRefresh = options.onWaxRefresh;
|
|
979
983
|
this._onReady = options.onReady;
|
|
984
|
+
this.log('vault created', { vaultId: this.vaultId, frameBaseUrl: this.frameBaseUrl, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
980
985
|
}
|
|
981
986
|
/**
|
|
982
987
|
* Creates and returns a ready `OzVault` instance.
|
|
@@ -1046,6 +1051,7 @@ class OzVault {
|
|
|
1046
1051
|
if (vault.tokenizerReady) {
|
|
1047
1052
|
vault.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey });
|
|
1048
1053
|
}
|
|
1054
|
+
vault.log('wax key received — vault ready');
|
|
1049
1055
|
return vault;
|
|
1050
1056
|
}
|
|
1051
1057
|
/**
|
|
@@ -1187,8 +1193,13 @@ class OzVault {
|
|
|
1187
1193
|
const readyBankElements = [accountEl, routingEl];
|
|
1188
1194
|
this._tokenizing = 'bank';
|
|
1189
1195
|
const requestId = `req-${uuid()}`;
|
|
1196
|
+
this.log('createBankToken() called');
|
|
1190
1197
|
return new Promise((resolve, reject) => {
|
|
1191
|
-
const
|
|
1198
|
+
const resetCountAtStart = this._resetCount;
|
|
1199
|
+
const cleanup = () => {
|
|
1200
|
+
if (this._resetCount === resetCountAtStart)
|
|
1201
|
+
this._tokenizing = null;
|
|
1202
|
+
};
|
|
1192
1203
|
this.bankTokenizeResolvers.set(requestId, {
|
|
1193
1204
|
resolve: (v) => { cleanup(); resolve(v); },
|
|
1194
1205
|
reject: (e) => { cleanup(); reject(e); },
|
|
@@ -1199,6 +1210,7 @@ class OzVault {
|
|
|
1199
1210
|
});
|
|
1200
1211
|
try {
|
|
1201
1212
|
const bankChannels = readyBankElements.map(() => new MessageChannel());
|
|
1213
|
+
const bankTokenizeStartMs = Date.now();
|
|
1202
1214
|
this.sendToTokenizer({
|
|
1203
1215
|
type: 'OZ_BANK_TOKENIZE',
|
|
1204
1216
|
requestId,
|
|
@@ -1209,6 +1221,7 @@ class OzVault {
|
|
|
1209
1221
|
lastName: options.lastName.trim(),
|
|
1210
1222
|
fieldCount: readyBankElements.length,
|
|
1211
1223
|
}, bankChannels.map(ch => ch.port1));
|
|
1224
|
+
this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
|
|
1212
1225
|
readyBankElements.forEach((el, i) => el.beginCollect(requestId, bankChannels[i].port2));
|
|
1213
1226
|
const bankTimeoutId = setTimeout(() => {
|
|
1214
1227
|
if (this.bankTokenizeResolvers.has(requestId)) {
|
|
@@ -1219,8 +1232,10 @@ class OzVault {
|
|
|
1219
1232
|
}
|
|
1220
1233
|
}, 30000);
|
|
1221
1234
|
const bankPendingEntry = this.bankTokenizeResolvers.get(requestId);
|
|
1222
|
-
if (bankPendingEntry)
|
|
1235
|
+
if (bankPendingEntry) {
|
|
1223
1236
|
bankPendingEntry.timeoutId = bankTimeoutId;
|
|
1237
|
+
bankPendingEntry.tokenizeStartMs = bankTokenizeStartMs;
|
|
1238
|
+
}
|
|
1224
1239
|
}
|
|
1225
1240
|
catch (err) {
|
|
1226
1241
|
this.bankTokenizeResolvers.delete(requestId);
|
|
@@ -1291,8 +1306,15 @@ class OzVault {
|
|
|
1291
1306
|
}
|
|
1292
1307
|
this._tokenizing = 'card';
|
|
1293
1308
|
const requestId = `req-${uuid()}`;
|
|
1309
|
+
this.log('createToken() called');
|
|
1294
1310
|
return new Promise((resolve, reject) => {
|
|
1295
|
-
|
|
1311
|
+
// Capture the reset generation so cleanup() only zeros _tokenizing when it
|
|
1312
|
+
// still belongs to this invocation — not a newer one that started after a reset.
|
|
1313
|
+
const resetCountAtStart = this._resetCount;
|
|
1314
|
+
const cleanup = () => {
|
|
1315
|
+
if (this._resetCount === resetCountAtStart)
|
|
1316
|
+
this._tokenizing = null;
|
|
1317
|
+
};
|
|
1296
1318
|
this.tokenizeResolvers.set(requestId, {
|
|
1297
1319
|
resolve: (v) => { cleanup(); resolve(v); },
|
|
1298
1320
|
reject: (e) => { cleanup(); reject(e); },
|
|
@@ -1305,6 +1327,7 @@ class OzVault {
|
|
|
1305
1327
|
try {
|
|
1306
1328
|
// Tell tokenizer frame to expect N field values, then tokenize
|
|
1307
1329
|
const cardChannels = readyElements.map(() => new MessageChannel());
|
|
1330
|
+
const tokenizeStartMs = Date.now();
|
|
1308
1331
|
this.sendToTokenizer({
|
|
1309
1332
|
type: 'OZ_TOKENIZE',
|
|
1310
1333
|
requestId,
|
|
@@ -1315,6 +1338,11 @@ class OzVault {
|
|
|
1315
1338
|
lastName,
|
|
1316
1339
|
fieldCount: readyElements.length,
|
|
1317
1340
|
}, cardChannels.map(ch => ch.port1));
|
|
1341
|
+
this.log('OZ_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyElements.length });
|
|
1342
|
+
// Store start time for elapsed-ms logging on result
|
|
1343
|
+
const cardEntry = this.tokenizeResolvers.get(requestId);
|
|
1344
|
+
if (cardEntry)
|
|
1345
|
+
cardEntry.tokenizeStartMs = tokenizeStartMs;
|
|
1318
1346
|
// Tell each ready element frame to send its raw value to the tokenizer
|
|
1319
1347
|
readyElements.forEach((el, i) => el.beginCollect(requestId, cardChannels[i].port2));
|
|
1320
1348
|
const cardTimeoutId = setTimeout(() => {
|
|
@@ -1361,8 +1389,11 @@ class OzVault {
|
|
|
1361
1389
|
reset() {
|
|
1362
1390
|
if (this._destroyed)
|
|
1363
1391
|
return;
|
|
1392
|
+
const cancelling = Boolean(this._tokenizing);
|
|
1393
|
+
this.log('reset() called', { tokenizing: this._tokenizing, cancelling });
|
|
1364
1394
|
if (this._tokenizing) {
|
|
1365
1395
|
this._tokenizing = null;
|
|
1396
|
+
this._resetCount++;
|
|
1366
1397
|
this.tokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
|
|
1367
1398
|
if (timeoutId != null)
|
|
1368
1399
|
clearTimeout(timeoutId);
|
|
@@ -1400,6 +1431,7 @@ class OzVault {
|
|
|
1400
1431
|
if (this._destroyed)
|
|
1401
1432
|
return;
|
|
1402
1433
|
this._destroyed = true;
|
|
1434
|
+
this.log('destroy() called');
|
|
1403
1435
|
window.removeEventListener('message', this.boundHandleMessage);
|
|
1404
1436
|
document.removeEventListener('visibilitychange', this.boundHandleVisibility);
|
|
1405
1437
|
if (this._pendingMount) {
|
|
@@ -1454,13 +1486,17 @@ class OzVault {
|
|
|
1454
1486
|
const REFRESH_THRESHOLD_MS = 20 * 60 * 1000; // 20 minutes
|
|
1455
1487
|
if (document.hidden) {
|
|
1456
1488
|
this._hiddenAt = Date.now();
|
|
1489
|
+
this.log('tab hidden');
|
|
1457
1490
|
}
|
|
1458
1491
|
else {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1492
|
+
const hiddenMs = this._hiddenAt !== null ? Date.now() - this._hiddenAt : 0;
|
|
1493
|
+
const willRefresh = (this._hiddenAt !== null &&
|
|
1494
|
+
hiddenMs >= REFRESH_THRESHOLD_MS &&
|
|
1495
|
+
Boolean(this._storedFetchWaxKey) &&
|
|
1462
1496
|
!this._tokenizing &&
|
|
1463
|
-
!this._waxRefreshing)
|
|
1497
|
+
!this._waxRefreshing);
|
|
1498
|
+
this.log('tab visible', { hiddenMs, willRefresh });
|
|
1499
|
+
if (willRefresh) {
|
|
1464
1500
|
this.refreshWaxKey().catch((err) => {
|
|
1465
1501
|
// Proactive refresh failure is non-fatal — the reactive path on the
|
|
1466
1502
|
// next createToken() call will handle it, including the auth retry.
|
|
@@ -1470,6 +1506,56 @@ class OzVault {
|
|
|
1470
1506
|
this._hiddenAt = null;
|
|
1471
1507
|
}
|
|
1472
1508
|
}
|
|
1509
|
+
// ─── Debug ───────────────────────────────────────────────────────────────
|
|
1510
|
+
/**
|
|
1511
|
+
* Emits a `[OzVault]`-prefixed entry to `console.log`. No-op when `debug` is
|
|
1512
|
+
* not set. Never called with sensitive values — callers use presence flags only.
|
|
1513
|
+
*/
|
|
1514
|
+
log(message, data) {
|
|
1515
|
+
if (!this._debug)
|
|
1516
|
+
return;
|
|
1517
|
+
if (data !== undefined) {
|
|
1518
|
+
console.log(`[OzVault] ${message}`, data);
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
console.log(`[OzVault] ${message}`);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Returns a plain-object snapshot of the vault's current internal state.
|
|
1526
|
+
* Safe to attach to bug reports — no wax keys, tokens, or billing data included.
|
|
1527
|
+
*
|
|
1528
|
+
* Available on all vault instances regardless of whether `debug` was enabled.
|
|
1529
|
+
*
|
|
1530
|
+
* @example
|
|
1531
|
+
* console.log(vault.debugState());
|
|
1532
|
+
* // {
|
|
1533
|
+
* // vaultId: 'vault-abc123',
|
|
1534
|
+
* // isReady: true,
|
|
1535
|
+
* // tokenizing: null,
|
|
1536
|
+
* // destroyed: false,
|
|
1537
|
+
* // waxKeyPresent: true,
|
|
1538
|
+
* // elements: ['cardNumber', 'expirationDate', 'cvv'],
|
|
1539
|
+
* // ...
|
|
1540
|
+
* // }
|
|
1541
|
+
*/
|
|
1542
|
+
debugState() {
|
|
1543
|
+
return {
|
|
1544
|
+
vaultId: this.vaultId,
|
|
1545
|
+
isReady: this.tokenizerReady,
|
|
1546
|
+
tokenizing: this._tokenizing,
|
|
1547
|
+
destroyed: this._destroyed,
|
|
1548
|
+
waxKeyPresent: Boolean(this.waxKey),
|
|
1549
|
+
tokenizeSuccessCount: this._tokenizeSuccessCount,
|
|
1550
|
+
maxTokenizeCalls: this._maxTokenizeCalls,
|
|
1551
|
+
resetCount: this._resetCount,
|
|
1552
|
+
elements: [...this.elementsByType.keys()],
|
|
1553
|
+
bankElements: [...this.bankElementsByType.keys()],
|
|
1554
|
+
completionState: Object.fromEntries([...this.completionState.entries()].map(([id, v]) => [id.slice(0, 8), v])),
|
|
1555
|
+
pendingTokenizations: this.tokenizeResolvers.size,
|
|
1556
|
+
pendingBankTokenizations: this.bankTokenizeResolvers.size,
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1473
1559
|
mountTokenizerFrame() {
|
|
1474
1560
|
const mount = () => {
|
|
1475
1561
|
this._pendingMount = null;
|
|
@@ -1481,6 +1567,7 @@ class OzVault {
|
|
|
1481
1567
|
iframe.src = `${this.frameBaseUrl}/frame/tokenizer-frame.html#vaultId=${encodeURIComponent(this.vaultId)}${parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : ''}`;
|
|
1482
1568
|
document.body.appendChild(iframe);
|
|
1483
1569
|
this.tokenizerFrame = iframe;
|
|
1570
|
+
this.log('mounting tokenizer iframe');
|
|
1484
1571
|
};
|
|
1485
1572
|
if (document.readyState === 'loading') {
|
|
1486
1573
|
this._pendingMount = mount;
|
|
@@ -1521,6 +1608,7 @@ class OzVault {
|
|
|
1521
1608
|
`SDK expects v${PROTOCOL_VERSION}, frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
|
|
1522
1609
|
'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
|
|
1523
1610
|
}
|
|
1611
|
+
this.log('element iframe ready', { type: el.type, frameIdPrefix: frameId.slice(0, 8) });
|
|
1524
1612
|
}
|
|
1525
1613
|
// Intercept OZ_CHANGE before forwarding — handle auto-advance and CVV sync
|
|
1526
1614
|
if (msg.type === 'OZ_CHANGE') {
|
|
@@ -1544,6 +1632,7 @@ class OzVault {
|
|
|
1544
1632
|
// Require valid too — avoids advancing at 13 digits for unknown-brand cards
|
|
1545
1633
|
// where isComplete() fires before the user has finished typing.
|
|
1546
1634
|
const justCompleted = complete && valid && !wasComplete;
|
|
1635
|
+
this.log('field changed', { type: el.type, complete, valid, justCompleted });
|
|
1547
1636
|
// Sync CVV length when card brand changes
|
|
1548
1637
|
if (el.type === 'cardNumber') {
|
|
1549
1638
|
const brand = msg.cardBrand;
|
|
@@ -1555,15 +1644,17 @@ class OzVault {
|
|
|
1555
1644
|
// Auto-advance focus on completion
|
|
1556
1645
|
if (justCompleted) {
|
|
1557
1646
|
if (el.type === 'cardNumber') {
|
|
1647
|
+
this.log('auto-advance', { from: 'cardNumber', to: 'expirationDate' });
|
|
1558
1648
|
(_b = this.elementsByType.get('expirationDate')) === null || _b === void 0 ? void 0 : _b.focus();
|
|
1559
1649
|
}
|
|
1560
1650
|
else if (el.type === 'expirationDate') {
|
|
1651
|
+
this.log('auto-advance', { from: 'expirationDate', to: 'cvv' });
|
|
1561
1652
|
(_c = this.elementsByType.get('cvv')) === null || _c === void 0 ? void 0 : _c.focus();
|
|
1562
1653
|
}
|
|
1563
1654
|
}
|
|
1564
1655
|
}
|
|
1565
1656
|
handleTokenizerMessage(msg) {
|
|
1566
|
-
var _a, _b, _c;
|
|
1657
|
+
var _a, _b, _c, _d;
|
|
1567
1658
|
switch (msg.type) {
|
|
1568
1659
|
case 'OZ_FRAME_READY':
|
|
1569
1660
|
if (msg.__ozVersion !== PROTOCOL_VERSION) {
|
|
@@ -1583,6 +1674,7 @@ class OzVault {
|
|
|
1583
1674
|
// sent again from create() once the key is available.
|
|
1584
1675
|
this.sendToTokenizer(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (this.waxKey ? { waxKey: this.waxKey } : {})));
|
|
1585
1676
|
(_c = this._onReady) === null || _c === void 0 ? void 0 : _c.call(this);
|
|
1677
|
+
this.log('tokenizer iframe ready', { protocolVersion: (_d = msg.__ozVersion) !== null && _d !== void 0 ? _d : null });
|
|
1586
1678
|
break;
|
|
1587
1679
|
case 'OZ_TOKEN_RESULT': {
|
|
1588
1680
|
if (typeof msg.requestId !== 'string' || !msg.requestId) {
|
|
@@ -1607,11 +1699,18 @@ class OzVault {
|
|
|
1607
1699
|
}
|
|
1608
1700
|
pending.resolve(Object.assign(Object.assign({ token,
|
|
1609
1701
|
cvcSession }, (card ? { card } : {})), (pending.billing ? { billing: pending.billing } : {})));
|
|
1702
|
+
this.log('token received', {
|
|
1703
|
+
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
1704
|
+
tokenPresent: true,
|
|
1705
|
+
cvcSessionPresent: true,
|
|
1706
|
+
cardMetadataPresent: Boolean(card),
|
|
1707
|
+
});
|
|
1610
1708
|
// Increment the per-key success counter and proactively refresh once
|
|
1611
1709
|
// the budget is exhausted so the next createToken() call uses a fresh
|
|
1612
1710
|
// key without waiting for a vault rejection.
|
|
1613
1711
|
this._tokenizeSuccessCount++;
|
|
1614
1712
|
if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
|
|
1713
|
+
this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
1615
1714
|
this.refreshWaxKey().catch((err) => {
|
|
1616
1715
|
console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
|
|
1617
1716
|
});
|
|
@@ -1631,14 +1730,25 @@ class OzVault {
|
|
|
1631
1730
|
this.tokenizeResolvers.delete(msg.requestId);
|
|
1632
1731
|
if (pending.timeoutId != null)
|
|
1633
1732
|
clearTimeout(pending.timeoutId);
|
|
1733
|
+
const willRefresh = this.isRefreshableAuthError(errorCode, raw) && !pending.retried && Boolean(this._storedFetchWaxKey);
|
|
1734
|
+
this.log('token error', { errorCode, willRefresh });
|
|
1634
1735
|
// Auto-refresh: if the wax key expired or was consumed and we haven't
|
|
1635
1736
|
// already retried for this request, transparently re-mint and retry.
|
|
1636
|
-
if (
|
|
1737
|
+
if (willRefresh) {
|
|
1738
|
+
const resetCountAtRetry = this._resetCount;
|
|
1637
1739
|
this.refreshWaxKey().then(() => {
|
|
1638
1740
|
if (this._destroyed) {
|
|
1639
1741
|
pending.reject(new OzError('Vault destroyed during wax key refresh.'));
|
|
1640
1742
|
return;
|
|
1641
1743
|
}
|
|
1744
|
+
if (this._resetCount !== resetCountAtRetry) {
|
|
1745
|
+
// reset() was called while the wax key was refreshing — the fields
|
|
1746
|
+
// have been cleared and _tokenizing was zeroed. Reject the original
|
|
1747
|
+
// promise so it doesn't stay pending, and bail out without starting
|
|
1748
|
+
// a new retry (which would tokenize against empty fields).
|
|
1749
|
+
pending.reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1642
1752
|
const newRequestId = `req-${uuid()}`;
|
|
1643
1753
|
// _tokenizing is still 'card' (cleanup() hasn't been called yet)
|
|
1644
1754
|
this.tokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, pending), { retried: true }));
|
|
@@ -1685,11 +1795,16 @@ class OzVault {
|
|
|
1685
1795
|
if (bankPending.timeoutId != null)
|
|
1686
1796
|
clearTimeout(bankPending.timeoutId);
|
|
1687
1797
|
if (this.isRefreshableAuthError(errorCode, raw) && !bankPending.retried && this._storedFetchWaxKey) {
|
|
1798
|
+
const resetCountAtRetry = this._resetCount;
|
|
1688
1799
|
this.refreshWaxKey().then(() => {
|
|
1689
1800
|
if (this._destroyed) {
|
|
1690
1801
|
bankPending.reject(new OzError('Vault destroyed during wax key refresh.'));
|
|
1691
1802
|
return;
|
|
1692
1803
|
}
|
|
1804
|
+
if (this._resetCount !== resetCountAtRetry) {
|
|
1805
|
+
bankPending.reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1693
1808
|
const newRequestId = `req-${uuid()}`;
|
|
1694
1809
|
this.bankTokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, bankPending), { retried: true }));
|
|
1695
1810
|
try {
|
|
@@ -1747,9 +1862,15 @@ class OzVault {
|
|
|
1747
1862
|
}
|
|
1748
1863
|
const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
|
|
1749
1864
|
pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
|
|
1865
|
+
this.log('bank token received', {
|
|
1866
|
+
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
1867
|
+
tokenPresent: true,
|
|
1868
|
+
bankMetadataPresent: Boolean(bank),
|
|
1869
|
+
});
|
|
1750
1870
|
// Same proactive refresh logic as card tokenization.
|
|
1751
1871
|
this._tokenizeSuccessCount++;
|
|
1752
1872
|
if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
|
|
1873
|
+
this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
1753
1874
|
this.refreshWaxKey().catch((err) => {
|
|
1754
1875
|
console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
|
|
1755
1876
|
});
|
|
@@ -1805,6 +1926,7 @@ class OzVault {
|
|
|
1805
1926
|
}
|
|
1806
1927
|
const newSessionId = uuid();
|
|
1807
1928
|
(_a = this._onWaxRefresh) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1929
|
+
this.log('wax key refresh started');
|
|
1808
1930
|
this._waxRefreshing = this._storedFetchWaxKey(newSessionId)
|
|
1809
1931
|
.then(newWaxKey => {
|
|
1810
1932
|
if (typeof newWaxKey !== 'string' || !newWaxKey.trim()) {
|
|
@@ -1818,6 +1940,11 @@ class OzVault {
|
|
|
1818
1940
|
if (!this._destroyed && this.tokenizerReady) {
|
|
1819
1941
|
this.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey: newWaxKey });
|
|
1820
1942
|
}
|
|
1943
|
+
this.log('wax key refresh succeeded');
|
|
1944
|
+
})
|
|
1945
|
+
.catch((err) => {
|
|
1946
|
+
this.log('wax key refresh failed', { error: err instanceof Error ? err.message : String(err) });
|
|
1947
|
+
throw err;
|
|
1821
1948
|
})
|
|
1822
1949
|
.finally(() => {
|
|
1823
1950
|
this._waxRefreshing = null;
|