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