@ozura/elements 1.0.2-next.17 → 1.0.2-next.19

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.
@@ -879,7 +879,7 @@ class OzVault {
879
879
  * @internal
880
880
  */
881
881
  constructor(options, waxKey, tokenizationSessionId) {
882
- var _a, _b, _c;
882
+ var _a, _b, _c, _d;
883
883
  this.elements = new Map();
884
884
  this.elementsByType = new Map();
885
885
  this.bankElementsByType = new Map();
@@ -914,13 +914,14 @@ class OzVault {
914
914
  this.resolvedAppearance = resolveAppearance(options.appearance);
915
915
  this.vaultId = `vault-${uuid()}`;
916
916
  this._maxTokenizeCalls = (_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3;
917
+ this._debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
917
918
  this.boundHandleMessage = this.handleMessage.bind(this);
918
919
  window.addEventListener('message', this.boundHandleMessage);
919
920
  this.boundHandleVisibility = this.handleVisibilityChange.bind(this);
920
921
  document.addEventListener('visibilitychange', this.boundHandleVisibility);
921
922
  this.mountTokenizerFrame();
922
923
  if (options.onLoadError) {
923
- const timeout = (_c = options.loadTimeoutMs) !== null && _c !== void 0 ? _c : 10000;
924
+ const timeout = (_d = options.loadTimeoutMs) !== null && _d !== void 0 ? _d : 10000;
924
925
  this.loadErrorTimeoutId = setTimeout(() => {
925
926
  this.loadErrorTimeoutId = null;
926
927
  if (!this._destroyed && !this.tokenizerReady) {
@@ -930,6 +931,7 @@ class OzVault {
930
931
  }
931
932
  this._onWaxRefresh = options.onWaxRefresh;
932
933
  this._onReady = options.onReady;
934
+ this.log('vault created', { vaultId: this.vaultId, frameBaseUrl: this.frameBaseUrl, maxTokenizeCalls: this._maxTokenizeCalls });
933
935
  }
934
936
  /**
935
937
  * Creates and returns a ready `OzVault` instance.
@@ -999,6 +1001,7 @@ class OzVault {
999
1001
  if (vault.tokenizerReady) {
1000
1002
  vault.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey });
1001
1003
  }
1004
+ vault.log('wax key received — vault ready');
1002
1005
  return vault;
1003
1006
  }
1004
1007
  /**
@@ -1140,6 +1143,7 @@ class OzVault {
1140
1143
  const readyBankElements = [accountEl, routingEl];
1141
1144
  this._tokenizing = 'bank';
1142
1145
  const requestId = `req-${uuid()}`;
1146
+ this.log('createBankToken() called');
1143
1147
  return new Promise((resolve, reject) => {
1144
1148
  const resetCountAtStart = this._resetCount;
1145
1149
  const cleanup = () => {
@@ -1156,6 +1160,7 @@ class OzVault {
1156
1160
  });
1157
1161
  try {
1158
1162
  const bankChannels = readyBankElements.map(() => new MessageChannel());
1163
+ const bankTokenizeStartMs = Date.now();
1159
1164
  this.sendToTokenizer({
1160
1165
  type: 'OZ_BANK_TOKENIZE',
1161
1166
  requestId,
@@ -1166,6 +1171,7 @@ class OzVault {
1166
1171
  lastName: options.lastName.trim(),
1167
1172
  fieldCount: readyBankElements.length,
1168
1173
  }, bankChannels.map(ch => ch.port1));
1174
+ this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
1169
1175
  readyBankElements.forEach((el, i) => el.beginCollect(requestId, bankChannels[i].port2));
1170
1176
  const bankTimeoutId = setTimeout(() => {
1171
1177
  if (this.bankTokenizeResolvers.has(requestId)) {
@@ -1176,8 +1182,10 @@ class OzVault {
1176
1182
  }
1177
1183
  }, 30000);
1178
1184
  const bankPendingEntry = this.bankTokenizeResolvers.get(requestId);
1179
- if (bankPendingEntry)
1185
+ if (bankPendingEntry) {
1180
1186
  bankPendingEntry.timeoutId = bankTimeoutId;
1187
+ bankPendingEntry.tokenizeStartMs = bankTokenizeStartMs;
1188
+ }
1181
1189
  }
1182
1190
  catch (err) {
1183
1191
  this.bankTokenizeResolvers.delete(requestId);
@@ -1248,6 +1256,7 @@ class OzVault {
1248
1256
  }
1249
1257
  this._tokenizing = 'card';
1250
1258
  const requestId = `req-${uuid()}`;
1259
+ this.log('createToken() called');
1251
1260
  return new Promise((resolve, reject) => {
1252
1261
  // Capture the reset generation so cleanup() only zeros _tokenizing when it
1253
1262
  // still belongs to this invocation — not a newer one that started after a reset.
@@ -1268,6 +1277,7 @@ class OzVault {
1268
1277
  try {
1269
1278
  // Tell tokenizer frame to expect N field values, then tokenize
1270
1279
  const cardChannels = readyElements.map(() => new MessageChannel());
1280
+ const tokenizeStartMs = Date.now();
1271
1281
  this.sendToTokenizer({
1272
1282
  type: 'OZ_TOKENIZE',
1273
1283
  requestId,
@@ -1278,6 +1288,11 @@ class OzVault {
1278
1288
  lastName,
1279
1289
  fieldCount: readyElements.length,
1280
1290
  }, cardChannels.map(ch => ch.port1));
1291
+ this.log('OZ_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyElements.length });
1292
+ // Store start time for elapsed-ms logging on result
1293
+ const cardEntry = this.tokenizeResolvers.get(requestId);
1294
+ if (cardEntry)
1295
+ cardEntry.tokenizeStartMs = tokenizeStartMs;
1281
1296
  // Tell each ready element frame to send its raw value to the tokenizer
1282
1297
  readyElements.forEach((el, i) => el.beginCollect(requestId, cardChannels[i].port2));
1283
1298
  const cardTimeoutId = setTimeout(() => {
@@ -1324,6 +1339,8 @@ class OzVault {
1324
1339
  reset() {
1325
1340
  if (this._destroyed)
1326
1341
  return;
1342
+ const cancelling = Boolean(this._tokenizing);
1343
+ this.log('reset() called', { tokenizing: this._tokenizing, cancelling });
1327
1344
  if (this._tokenizing) {
1328
1345
  this._tokenizing = null;
1329
1346
  this._resetCount++;
@@ -1364,6 +1381,7 @@ class OzVault {
1364
1381
  if (this._destroyed)
1365
1382
  return;
1366
1383
  this._destroyed = true;
1384
+ this.log('destroy() called');
1367
1385
  window.removeEventListener('message', this.boundHandleMessage);
1368
1386
  document.removeEventListener('visibilitychange', this.boundHandleVisibility);
1369
1387
  if (this._pendingMount) {
@@ -1418,13 +1436,17 @@ class OzVault {
1418
1436
  const REFRESH_THRESHOLD_MS = 20 * 60 * 1000; // 20 minutes
1419
1437
  if (document.hidden) {
1420
1438
  this._hiddenAt = Date.now();
1439
+ this.log('tab hidden');
1421
1440
  }
1422
1441
  else {
1423
- if (this._hiddenAt !== null &&
1424
- Date.now() - this._hiddenAt >= REFRESH_THRESHOLD_MS &&
1425
- this._storedFetchWaxKey &&
1442
+ const hiddenMs = this._hiddenAt !== null ? Date.now() - this._hiddenAt : 0;
1443
+ const willRefresh = (this._hiddenAt !== null &&
1444
+ hiddenMs >= REFRESH_THRESHOLD_MS &&
1445
+ Boolean(this._storedFetchWaxKey) &&
1426
1446
  !this._tokenizing &&
1427
- !this._waxRefreshing) {
1447
+ !this._waxRefreshing);
1448
+ this.log('tab visible', { hiddenMs, willRefresh });
1449
+ if (willRefresh) {
1428
1450
  this.refreshWaxKey().catch((err) => {
1429
1451
  // Proactive refresh failure is non-fatal — the reactive path on the
1430
1452
  // next createToken() call will handle it, including the auth retry.
@@ -1434,6 +1456,56 @@ class OzVault {
1434
1456
  this._hiddenAt = null;
1435
1457
  }
1436
1458
  }
1459
+ // ─── Debug ───────────────────────────────────────────────────────────────
1460
+ /**
1461
+ * Emits a `[OzVault]`-prefixed entry to `console.log`. No-op when `debug` is
1462
+ * not set. Never called with sensitive values — callers use presence flags only.
1463
+ */
1464
+ log(message, data) {
1465
+ if (!this._debug)
1466
+ return;
1467
+ if (data !== undefined) {
1468
+ console.log(`[OzVault] ${message}`, data);
1469
+ }
1470
+ else {
1471
+ console.log(`[OzVault] ${message}`);
1472
+ }
1473
+ }
1474
+ /**
1475
+ * Returns a plain-object snapshot of the vault's current internal state.
1476
+ * Safe to attach to bug reports — no wax keys, tokens, or billing data included.
1477
+ *
1478
+ * Available on all vault instances regardless of whether `debug` was enabled.
1479
+ *
1480
+ * @example
1481
+ * console.log(vault.debugState());
1482
+ * // {
1483
+ * // vaultId: 'vault-abc123',
1484
+ * // isReady: true,
1485
+ * // tokenizing: null,
1486
+ * // destroyed: false,
1487
+ * // waxKeyPresent: true,
1488
+ * // elements: ['cardNumber', 'expirationDate', 'cvv'],
1489
+ * // ...
1490
+ * // }
1491
+ */
1492
+ debugState() {
1493
+ return {
1494
+ vaultId: this.vaultId,
1495
+ isReady: this.tokenizerReady,
1496
+ tokenizing: this._tokenizing,
1497
+ destroyed: this._destroyed,
1498
+ waxKeyPresent: Boolean(this.waxKey),
1499
+ tokenizeSuccessCount: this._tokenizeSuccessCount,
1500
+ maxTokenizeCalls: this._maxTokenizeCalls,
1501
+ resetCount: this._resetCount,
1502
+ elements: [...this.elementsByType.keys()],
1503
+ bankElements: [...this.bankElementsByType.keys()],
1504
+ completionState: Object.fromEntries([...this.completionState.entries()].map(([id, v]) => [id.slice(0, 8), v])),
1505
+ pendingTokenizations: this.tokenizeResolvers.size,
1506
+ pendingBankTokenizations: this.bankTokenizeResolvers.size,
1507
+ };
1508
+ }
1437
1509
  mountTokenizerFrame() {
1438
1510
  const mount = () => {
1439
1511
  this._pendingMount = null;
@@ -1445,6 +1517,7 @@ class OzVault {
1445
1517
  iframe.src = `${this.frameBaseUrl}/frame/tokenizer-frame.html#vaultId=${encodeURIComponent(this.vaultId)}${parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : ''}`;
1446
1518
  document.body.appendChild(iframe);
1447
1519
  this.tokenizerFrame = iframe;
1520
+ this.log('mounting tokenizer iframe');
1448
1521
  };
1449
1522
  if (document.readyState === 'loading') {
1450
1523
  this._pendingMount = mount;
@@ -1485,6 +1558,7 @@ class OzVault {
1485
1558
  `SDK expects v${PROTOCOL_VERSION}, frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
1486
1559
  'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
1487
1560
  }
1561
+ this.log('element iframe ready', { type: el.type, frameIdPrefix: frameId.slice(0, 8) });
1488
1562
  }
1489
1563
  // Intercept OZ_CHANGE before forwarding — handle auto-advance and CVV sync
1490
1564
  if (msg.type === 'OZ_CHANGE') {
@@ -1508,6 +1582,7 @@ class OzVault {
1508
1582
  // Require valid too — avoids advancing at 13 digits for unknown-brand cards
1509
1583
  // where isComplete() fires before the user has finished typing.
1510
1584
  const justCompleted = complete && valid && !wasComplete;
1585
+ this.log('field changed', { type: el.type, complete, valid, justCompleted });
1511
1586
  // Sync CVV length when card brand changes
1512
1587
  if (el.type === 'cardNumber') {
1513
1588
  const brand = msg.cardBrand;
@@ -1519,15 +1594,17 @@ class OzVault {
1519
1594
  // Auto-advance focus on completion
1520
1595
  if (justCompleted) {
1521
1596
  if (el.type === 'cardNumber') {
1597
+ this.log('auto-advance', { from: 'cardNumber', to: 'expirationDate' });
1522
1598
  (_b = this.elementsByType.get('expirationDate')) === null || _b === void 0 ? void 0 : _b.focus();
1523
1599
  }
1524
1600
  else if (el.type === 'expirationDate') {
1601
+ this.log('auto-advance', { from: 'expirationDate', to: 'cvv' });
1525
1602
  (_c = this.elementsByType.get('cvv')) === null || _c === void 0 ? void 0 : _c.focus();
1526
1603
  }
1527
1604
  }
1528
1605
  }
1529
1606
  handleTokenizerMessage(msg) {
1530
- var _a, _b, _c;
1607
+ var _a, _b, _c, _d;
1531
1608
  switch (msg.type) {
1532
1609
  case 'OZ_FRAME_READY':
1533
1610
  if (msg.__ozVersion !== PROTOCOL_VERSION) {
@@ -1547,6 +1624,7 @@ class OzVault {
1547
1624
  // sent again from create() once the key is available.
1548
1625
  this.sendToTokenizer(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (this.waxKey ? { waxKey: this.waxKey } : {})));
1549
1626
  (_c = this._onReady) === null || _c === void 0 ? void 0 : _c.call(this);
1627
+ this.log('tokenizer iframe ready', { protocolVersion: (_d = msg.__ozVersion) !== null && _d !== void 0 ? _d : null });
1550
1628
  break;
1551
1629
  case 'OZ_TOKEN_RESULT': {
1552
1630
  if (typeof msg.requestId !== 'string' || !msg.requestId) {
@@ -1571,11 +1649,18 @@ class OzVault {
1571
1649
  }
1572
1650
  pending.resolve(Object.assign(Object.assign({ token,
1573
1651
  cvcSession }, (card ? { card } : {})), (pending.billing ? { billing: pending.billing } : {})));
1652
+ this.log('token received', {
1653
+ elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
1654
+ tokenPresent: true,
1655
+ cvcSessionPresent: true,
1656
+ cardMetadataPresent: Boolean(card),
1657
+ });
1574
1658
  // Increment the per-key success counter and proactively refresh once
1575
1659
  // the budget is exhausted so the next createToken() call uses a fresh
1576
1660
  // key without waiting for a vault rejection.
1577
1661
  this._tokenizeSuccessCount++;
1578
1662
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1663
+ this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1579
1664
  this.refreshWaxKey().catch((err) => {
1580
1665
  console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1581
1666
  });
@@ -1595,9 +1680,11 @@ class OzVault {
1595
1680
  this.tokenizeResolvers.delete(msg.requestId);
1596
1681
  if (pending.timeoutId != null)
1597
1682
  clearTimeout(pending.timeoutId);
1683
+ const willRefresh = this.isRefreshableAuthError(errorCode, raw) && !pending.retried && Boolean(this._storedFetchWaxKey);
1684
+ this.log('token error', { errorCode, willRefresh });
1598
1685
  // Auto-refresh: if the wax key expired or was consumed and we haven't
1599
1686
  // already retried for this request, transparently re-mint and retry.
1600
- if (this.isRefreshableAuthError(errorCode, raw) && !pending.retried && this._storedFetchWaxKey) {
1687
+ if (willRefresh) {
1601
1688
  const resetCountAtRetry = this._resetCount;
1602
1689
  this.refreshWaxKey().then(() => {
1603
1690
  if (this._destroyed) {
@@ -1725,9 +1812,15 @@ class OzVault {
1725
1812
  }
1726
1813
  const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
1727
1814
  pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
1815
+ this.log('bank token received', {
1816
+ elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
1817
+ tokenPresent: true,
1818
+ bankMetadataPresent: Boolean(bank),
1819
+ });
1728
1820
  // Same proactive refresh logic as card tokenization.
1729
1821
  this._tokenizeSuccessCount++;
1730
1822
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1823
+ this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1731
1824
  this.refreshWaxKey().catch((err) => {
1732
1825
  console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1733
1826
  });
@@ -1783,6 +1876,7 @@ class OzVault {
1783
1876
  }
1784
1877
  const newSessionId = uuid();
1785
1878
  (_a = this._onWaxRefresh) === null || _a === void 0 ? void 0 : _a.call(this);
1879
+ this.log('wax key refresh started');
1786
1880
  this._waxRefreshing = this._storedFetchWaxKey(newSessionId)
1787
1881
  .then(newWaxKey => {
1788
1882
  if (typeof newWaxKey !== 'string' || !newWaxKey.trim()) {
@@ -1796,6 +1890,11 @@ class OzVault {
1796
1890
  if (!this._destroyed && this.tokenizerReady) {
1797
1891
  this.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey: newWaxKey });
1798
1892
  }
1893
+ this.log('wax key refresh succeeded');
1894
+ })
1895
+ .catch((err) => {
1896
+ this.log('wax key refresh failed', { error: err instanceof Error ? err.message : String(err) });
1897
+ throw err;
1799
1898
  })
1800
1899
  .finally(() => {
1801
1900
  this._waxRefreshing = null;
@@ -1903,7 +2002,7 @@ const OzContext = react.createContext({
1903
2002
  * All `<OzCardNumber />`, `<OzExpiry />`, and `<OzCvv />` children must be
1904
2003
  * rendered inside this provider.
1905
2004
  */
1906
- function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loadTimeoutMs, onWaxRefresh, onReady, appearance, maxTokenizeCalls, children }) {
2005
+ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loadTimeoutMs, onWaxRefresh, onReady, appearance, maxTokenizeCalls, debug, children }) {
1907
2006
  const [vault, setVault] = react.useState(null);
1908
2007
  const [initError, setInitError] = react.useState(null);
1909
2008
  const [mountedCount, setMountedCount] = react.useState(0);
@@ -1944,7 +2043,7 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
1944
2043
  // synchronously rather than waiting for the promise to settle. Without this,
1945
2044
  // two hidden iframes and two window listeners briefly coexist.
1946
2045
  const abortController = new AbortController();
1947
- OzVault.create(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ pubKey, fetchWaxKey: (sessionId) => fetchWaxKeyRef.current(sessionId) }, (frameBaseUrl ? { frameBaseUrl } : {})), (parsedFonts ? { fonts: parsedFonts } : {})), (parsedAppearance ? { appearance: parsedAppearance } : {})), (onLoadErrorRef.current ? { onLoadError: fireLoadError, loadTimeoutMs } : {})), {
2046
+ OzVault.create(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ pubKey, fetchWaxKey: (sessionId) => fetchWaxKeyRef.current(sessionId) }, (frameBaseUrl ? { frameBaseUrl } : {})), (parsedFonts ? { fonts: parsedFonts } : {})), (parsedAppearance ? { appearance: parsedAppearance } : {})), (onLoadErrorRef.current ? { onLoadError: fireLoadError, loadTimeoutMs } : {})), {
1948
2047
  // Always install onWaxRefresh internally so we can reset tokenizeCount
1949
2048
  // when any wax key refresh occurs (reactive TTL expiry, post-budget
1950
2049
  // proactive, or visibility-change proactive). Without this the React
@@ -1970,7 +2069,7 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
1970
2069
  var _a;
1971
2070
  Promise.resolve().then(() => setTokenizeCount(0));
1972
2071
  (_a = onWaxRefreshRef.current) === null || _a === void 0 ? void 0 : _a.call(onWaxRefreshRef);
1973
- }, onReady: () => { var _a; return (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef); } }), (maxTokenizeCalls !== undefined ? { maxTokenizeCalls } : {})), abortController.signal).then(v => {
2072
+ }, onReady: () => { var _a; return (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef); } }), (maxTokenizeCalls !== undefined ? { maxTokenizeCalls } : {})), (debug ? { debug: true } : {})), abortController.signal).then(v => {
1974
2073
  if (cancelled) {
1975
2074
  v.destroy();
1976
2075
  return;
@@ -2003,7 +2102,7 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
2003
2102
  setVault(null);
2004
2103
  setInitError(null);
2005
2104
  };
2006
- }, [pubKey, frameBaseUrl, loadTimeoutMs, appearanceKey, fontsKey, maxTokenizeCalls]);
2105
+ }, [pubKey, frameBaseUrl, loadTimeoutMs, appearanceKey, fontsKey, maxTokenizeCalls, debug]);
2007
2106
  const notifyMount = react.useCallback(() => setMountedCount(n => n + 1), []);
2008
2107
  const notifyReady = react.useCallback(() => setReadyCount(n => n + 1), []);
2009
2108
  const notifyUnmount = react.useCallback(() => {