@ozura/elements 1.0.2-next.17 → 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 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.
@@ -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();
@@ -964,13 +964,14 @@ class OzVault {
964
964
  this.resolvedAppearance = resolveAppearance(options.appearance);
965
965
  this.vaultId = `vault-${uuid()}`;
966
966
  this._maxTokenizeCalls = (_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3;
967
+ this._debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
967
968
  this.boundHandleMessage = this.handleMessage.bind(this);
968
969
  window.addEventListener('message', this.boundHandleMessage);
969
970
  this.boundHandleVisibility = this.handleVisibilityChange.bind(this);
970
971
  document.addEventListener('visibilitychange', this.boundHandleVisibility);
971
972
  this.mountTokenizerFrame();
972
973
  if (options.onLoadError) {
973
- const timeout = (_c = options.loadTimeoutMs) !== null && _c !== void 0 ? _c : 10000;
974
+ const timeout = (_d = options.loadTimeoutMs) !== null && _d !== void 0 ? _d : 10000;
974
975
  this.loadErrorTimeoutId = setTimeout(() => {
975
976
  this.loadErrorTimeoutId = null;
976
977
  if (!this._destroyed && !this.tokenizerReady) {
@@ -980,6 +981,7 @@ class OzVault {
980
981
  }
981
982
  this._onWaxRefresh = options.onWaxRefresh;
982
983
  this._onReady = options.onReady;
984
+ this.log('vault created', { vaultId: this.vaultId, frameBaseUrl: this.frameBaseUrl, maxTokenizeCalls: this._maxTokenizeCalls });
983
985
  }
984
986
  /**
985
987
  * Creates and returns a ready `OzVault` instance.
@@ -1049,6 +1051,7 @@ class OzVault {
1049
1051
  if (vault.tokenizerReady) {
1050
1052
  vault.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey });
1051
1053
  }
1054
+ vault.log('wax key received — vault ready');
1052
1055
  return vault;
1053
1056
  }
1054
1057
  /**
@@ -1190,6 +1193,7 @@ class OzVault {
1190
1193
  const readyBankElements = [accountEl, routingEl];
1191
1194
  this._tokenizing = 'bank';
1192
1195
  const requestId = `req-${uuid()}`;
1196
+ this.log('createBankToken() called');
1193
1197
  return new Promise((resolve, reject) => {
1194
1198
  const resetCountAtStart = this._resetCount;
1195
1199
  const cleanup = () => {
@@ -1206,6 +1210,7 @@ class OzVault {
1206
1210
  });
1207
1211
  try {
1208
1212
  const bankChannels = readyBankElements.map(() => new MessageChannel());
1213
+ const bankTokenizeStartMs = Date.now();
1209
1214
  this.sendToTokenizer({
1210
1215
  type: 'OZ_BANK_TOKENIZE',
1211
1216
  requestId,
@@ -1216,6 +1221,7 @@ class OzVault {
1216
1221
  lastName: options.lastName.trim(),
1217
1222
  fieldCount: readyBankElements.length,
1218
1223
  }, bankChannels.map(ch => ch.port1));
1224
+ this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
1219
1225
  readyBankElements.forEach((el, i) => el.beginCollect(requestId, bankChannels[i].port2));
1220
1226
  const bankTimeoutId = setTimeout(() => {
1221
1227
  if (this.bankTokenizeResolvers.has(requestId)) {
@@ -1226,8 +1232,10 @@ class OzVault {
1226
1232
  }
1227
1233
  }, 30000);
1228
1234
  const bankPendingEntry = this.bankTokenizeResolvers.get(requestId);
1229
- if (bankPendingEntry)
1235
+ if (bankPendingEntry) {
1230
1236
  bankPendingEntry.timeoutId = bankTimeoutId;
1237
+ bankPendingEntry.tokenizeStartMs = bankTokenizeStartMs;
1238
+ }
1231
1239
  }
1232
1240
  catch (err) {
1233
1241
  this.bankTokenizeResolvers.delete(requestId);
@@ -1298,6 +1306,7 @@ class OzVault {
1298
1306
  }
1299
1307
  this._tokenizing = 'card';
1300
1308
  const requestId = `req-${uuid()}`;
1309
+ this.log('createToken() called');
1301
1310
  return new Promise((resolve, reject) => {
1302
1311
  // Capture the reset generation so cleanup() only zeros _tokenizing when it
1303
1312
  // still belongs to this invocation — not a newer one that started after a reset.
@@ -1318,6 +1327,7 @@ class OzVault {
1318
1327
  try {
1319
1328
  // Tell tokenizer frame to expect N field values, then tokenize
1320
1329
  const cardChannels = readyElements.map(() => new MessageChannel());
1330
+ const tokenizeStartMs = Date.now();
1321
1331
  this.sendToTokenizer({
1322
1332
  type: 'OZ_TOKENIZE',
1323
1333
  requestId,
@@ -1328,6 +1338,11 @@ class OzVault {
1328
1338
  lastName,
1329
1339
  fieldCount: readyElements.length,
1330
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;
1331
1346
  // Tell each ready element frame to send its raw value to the tokenizer
1332
1347
  readyElements.forEach((el, i) => el.beginCollect(requestId, cardChannels[i].port2));
1333
1348
  const cardTimeoutId = setTimeout(() => {
@@ -1374,6 +1389,8 @@ class OzVault {
1374
1389
  reset() {
1375
1390
  if (this._destroyed)
1376
1391
  return;
1392
+ const cancelling = Boolean(this._tokenizing);
1393
+ this.log('reset() called', { tokenizing: this._tokenizing, cancelling });
1377
1394
  if (this._tokenizing) {
1378
1395
  this._tokenizing = null;
1379
1396
  this._resetCount++;
@@ -1414,6 +1431,7 @@ class OzVault {
1414
1431
  if (this._destroyed)
1415
1432
  return;
1416
1433
  this._destroyed = true;
1434
+ this.log('destroy() called');
1417
1435
  window.removeEventListener('message', this.boundHandleMessage);
1418
1436
  document.removeEventListener('visibilitychange', this.boundHandleVisibility);
1419
1437
  if (this._pendingMount) {
@@ -1468,13 +1486,17 @@ class OzVault {
1468
1486
  const REFRESH_THRESHOLD_MS = 20 * 60 * 1000; // 20 minutes
1469
1487
  if (document.hidden) {
1470
1488
  this._hiddenAt = Date.now();
1489
+ this.log('tab hidden');
1471
1490
  }
1472
1491
  else {
1473
- if (this._hiddenAt !== null &&
1474
- Date.now() - this._hiddenAt >= REFRESH_THRESHOLD_MS &&
1475
- this._storedFetchWaxKey &&
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) &&
1476
1496
  !this._tokenizing &&
1477
- !this._waxRefreshing) {
1497
+ !this._waxRefreshing);
1498
+ this.log('tab visible', { hiddenMs, willRefresh });
1499
+ if (willRefresh) {
1478
1500
  this.refreshWaxKey().catch((err) => {
1479
1501
  // Proactive refresh failure is non-fatal — the reactive path on the
1480
1502
  // next createToken() call will handle it, including the auth retry.
@@ -1484,6 +1506,56 @@ class OzVault {
1484
1506
  this._hiddenAt = null;
1485
1507
  }
1486
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
+ }
1487
1559
  mountTokenizerFrame() {
1488
1560
  const mount = () => {
1489
1561
  this._pendingMount = null;
@@ -1495,6 +1567,7 @@ class OzVault {
1495
1567
  iframe.src = `${this.frameBaseUrl}/frame/tokenizer-frame.html#vaultId=${encodeURIComponent(this.vaultId)}${parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : ''}`;
1496
1568
  document.body.appendChild(iframe);
1497
1569
  this.tokenizerFrame = iframe;
1570
+ this.log('mounting tokenizer iframe');
1498
1571
  };
1499
1572
  if (document.readyState === 'loading') {
1500
1573
  this._pendingMount = mount;
@@ -1535,6 +1608,7 @@ class OzVault {
1535
1608
  `SDK expects v${PROTOCOL_VERSION}, frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
1536
1609
  'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
1537
1610
  }
1611
+ this.log('element iframe ready', { type: el.type, frameIdPrefix: frameId.slice(0, 8) });
1538
1612
  }
1539
1613
  // Intercept OZ_CHANGE before forwarding — handle auto-advance and CVV sync
1540
1614
  if (msg.type === 'OZ_CHANGE') {
@@ -1558,6 +1632,7 @@ class OzVault {
1558
1632
  // Require valid too — avoids advancing at 13 digits for unknown-brand cards
1559
1633
  // where isComplete() fires before the user has finished typing.
1560
1634
  const justCompleted = complete && valid && !wasComplete;
1635
+ this.log('field changed', { type: el.type, complete, valid, justCompleted });
1561
1636
  // Sync CVV length when card brand changes
1562
1637
  if (el.type === 'cardNumber') {
1563
1638
  const brand = msg.cardBrand;
@@ -1569,15 +1644,17 @@ class OzVault {
1569
1644
  // Auto-advance focus on completion
1570
1645
  if (justCompleted) {
1571
1646
  if (el.type === 'cardNumber') {
1647
+ this.log('auto-advance', { from: 'cardNumber', to: 'expirationDate' });
1572
1648
  (_b = this.elementsByType.get('expirationDate')) === null || _b === void 0 ? void 0 : _b.focus();
1573
1649
  }
1574
1650
  else if (el.type === 'expirationDate') {
1651
+ this.log('auto-advance', { from: 'expirationDate', to: 'cvv' });
1575
1652
  (_c = this.elementsByType.get('cvv')) === null || _c === void 0 ? void 0 : _c.focus();
1576
1653
  }
1577
1654
  }
1578
1655
  }
1579
1656
  handleTokenizerMessage(msg) {
1580
- var _a, _b, _c;
1657
+ var _a, _b, _c, _d;
1581
1658
  switch (msg.type) {
1582
1659
  case 'OZ_FRAME_READY':
1583
1660
  if (msg.__ozVersion !== PROTOCOL_VERSION) {
@@ -1597,6 +1674,7 @@ class OzVault {
1597
1674
  // sent again from create() once the key is available.
1598
1675
  this.sendToTokenizer(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (this.waxKey ? { waxKey: this.waxKey } : {})));
1599
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 });
1600
1678
  break;
1601
1679
  case 'OZ_TOKEN_RESULT': {
1602
1680
  if (typeof msg.requestId !== 'string' || !msg.requestId) {
@@ -1621,11 +1699,18 @@ class OzVault {
1621
1699
  }
1622
1700
  pending.resolve(Object.assign(Object.assign({ token,
1623
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
+ });
1624
1708
  // Increment the per-key success counter and proactively refresh once
1625
1709
  // the budget is exhausted so the next createToken() call uses a fresh
1626
1710
  // key without waiting for a vault rejection.
1627
1711
  this._tokenizeSuccessCount++;
1628
1712
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1713
+ this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1629
1714
  this.refreshWaxKey().catch((err) => {
1630
1715
  console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1631
1716
  });
@@ -1645,9 +1730,11 @@ class OzVault {
1645
1730
  this.tokenizeResolvers.delete(msg.requestId);
1646
1731
  if (pending.timeoutId != null)
1647
1732
  clearTimeout(pending.timeoutId);
1733
+ const willRefresh = this.isRefreshableAuthError(errorCode, raw) && !pending.retried && Boolean(this._storedFetchWaxKey);
1734
+ this.log('token error', { errorCode, willRefresh });
1648
1735
  // Auto-refresh: if the wax key expired or was consumed and we haven't
1649
1736
  // already retried for this request, transparently re-mint and retry.
1650
- if (this.isRefreshableAuthError(errorCode, raw) && !pending.retried && this._storedFetchWaxKey) {
1737
+ if (willRefresh) {
1651
1738
  const resetCountAtRetry = this._resetCount;
1652
1739
  this.refreshWaxKey().then(() => {
1653
1740
  if (this._destroyed) {
@@ -1775,9 +1862,15 @@ class OzVault {
1775
1862
  }
1776
1863
  const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
1777
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
+ });
1778
1870
  // Same proactive refresh logic as card tokenization.
1779
1871
  this._tokenizeSuccessCount++;
1780
1872
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1873
+ this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1781
1874
  this.refreshWaxKey().catch((err) => {
1782
1875
  console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1783
1876
  });
@@ -1833,6 +1926,7 @@ class OzVault {
1833
1926
  }
1834
1927
  const newSessionId = uuid();
1835
1928
  (_a = this._onWaxRefresh) === null || _a === void 0 ? void 0 : _a.call(this);
1929
+ this.log('wax key refresh started');
1836
1930
  this._waxRefreshing = this._storedFetchWaxKey(newSessionId)
1837
1931
  .then(newWaxKey => {
1838
1932
  if (typeof newWaxKey !== 'string' || !newWaxKey.trim()) {
@@ -1846,6 +1940,11 @@ class OzVault {
1846
1940
  if (!this._destroyed && this.tokenizerReady) {
1847
1941
  this.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey: newWaxKey });
1848
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;
1849
1948
  })
1850
1949
  .finally(() => {
1851
1950
  this._waxRefreshing = null;