@ozura/elements 1.2.4-next.53 → 1.2.4-next.55

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.
@@ -1,6 +1,41 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { createContext, useContext, useRef, useCallback, useState, useMemo, useEffect } from 'react';
3
3
 
4
+ /******************************************************************************
5
+ Copyright (c) Microsoft Corporation.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
+ PERFORMANCE OF THIS SOFTWARE.
17
+ ***************************************************************************** */
18
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
19
+
20
+
21
+ function __classPrivateFieldGet(receiver, state, kind, f) {
22
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
23
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
24
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
25
+ }
26
+
27
+ function __classPrivateFieldSet(receiver, state, value, kind, f) {
28
+ if (kind === "m") throw new TypeError("Private method is not writable");
29
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
30
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
31
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
32
+ }
33
+
34
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
35
+ var e = new Error(message);
36
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
37
+ };
38
+
4
39
  const THEME_DEFAULT = {
5
40
  base: {
6
41
  color: '#1a1a2e',
@@ -181,11 +216,24 @@ const OZ_ERROR_CODES = new Set(['network', 'timeout', 'auth', 'validation', 'ser
181
216
  function isOzErrorCode(value) {
182
217
  return typeof value === 'string' && OZ_ERROR_CODES.has(value);
183
218
  }
219
+ /**
220
+ * Strips PAN-shaped digit sequences from raw vault / Pay API error strings
221
+ * before they are stored on OzError.raw.
222
+ *
223
+ * The vault and Pay API should never echo card data in error messages — but
224
+ * this is a defense-in-depth layer. 13–19 digit runs (optionally separated
225
+ * by spaces or dashes) match every major card number format. Shorter digit
226
+ * runs (CVV, ZIP, HTTP status codes) are left untouched.
227
+ */
228
+ const RAW_PAN_RE = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{1,7}\b|\b\d{13,19}\b/g;
229
+ function redactRawString(s) {
230
+ return s.replace(RAW_PAN_RE, '[REDACTED]');
231
+ }
184
232
  class OzError extends Error {
185
233
  constructor(message, raw, errorCode) {
186
234
  super(message);
187
235
  this.name = 'OzError';
188
- this.raw = raw !== null && raw !== void 0 ? raw : message;
236
+ this.raw = redactRawString(raw !== null && raw !== void 0 ? raw : message);
189
237
  this.errorCode = errorCode !== null && errorCode !== void 0 ? errorCode : 'unknown';
190
238
  this.retryable = this.errorCode === 'network' || this.errorCode === 'timeout' || this.errorCode === 'server';
191
239
  }
@@ -952,6 +1000,7 @@ function createSessionFetcher(url) {
952
1000
  };
953
1001
  }
954
1002
 
1003
+ var _OzVault_waxKey;
955
1004
  function isCardMetadata(v) {
956
1005
  if (!v || typeof v !== 'object')
957
1006
  return false;
@@ -995,6 +1044,11 @@ class OzVault {
995
1044
  */
996
1045
  constructor(options, waxKey, tokenizationSessionId) {
997
1046
  var _a, _b, _c, _d, _e;
1047
+ // Hard-private: JavaScript WeakMap-based enforcement (not just TypeScript
1048
+ // compile-time). Runtime code cannot read this via vault['waxKey'] or
1049
+ // (vault as any).waxKey — prevents wax key exfiltration if merchant-page
1050
+ // JS were somehow inspected at runtime (e.g. after an XSS).
1051
+ _OzVault_waxKey.set(this, '');
998
1052
  this.elements = new Map();
999
1053
  this.elementsByType = new Map();
1000
1054
  this.bankElementsByType = new Map();
@@ -1020,7 +1074,7 @@ class OzVault {
1020
1074
  this.loadErrorTimeoutId = null;
1021
1075
  // Proactive wax refresh on visibility restore after long idle
1022
1076
  this._hiddenAt = null;
1023
- this.waxKey = waxKey;
1077
+ __classPrivateFieldSet(this, _OzVault_waxKey, waxKey, "f");
1024
1078
  this.tokenizationSessionId = tokenizationSessionId;
1025
1079
  this.pubKey = options.pubKey;
1026
1080
  // Strip trailing slash so URL construction never produces double-slash paths
@@ -1132,8 +1186,8 @@ class OzVault {
1132
1186
  vault.destroy();
1133
1187
  throw new OzError('Session fetch returned an empty key. Check your session endpoint response — it must return { sessionKey: "..." }.');
1134
1188
  }
1135
- // Static methods can access private fields of instances of the same class.
1136
- vault.waxKey = waxKey;
1189
+ // Static methods can access hard-private fields of instances of the same class.
1190
+ __classPrivateFieldSet(vault, _OzVault_waxKey, waxKey, "f");
1137
1191
  vault._storedFetchWaxKey = resolvedFetchKey;
1138
1192
  // If the tokenizer iframe fired OZ_FRAME_READY before fetchWaxKey resolved,
1139
1193
  // the OZ_INIT sent at that point had an empty waxKey. Send a follow-up now
@@ -1650,7 +1704,7 @@ class OzVault {
1650
1704
  isReady: this.tokenizerReady,
1651
1705
  tokenizing: this._tokenizing,
1652
1706
  destroyed: this._destroyed,
1653
- waxKeyPresent: Boolean(this.waxKey),
1707
+ waxKeyPresent: Boolean(__classPrivateFieldGet(this, _OzVault_waxKey, "f")),
1654
1708
  tokenizeSuccessCount: this._tokenizeSuccessCount,
1655
1709
  maxTokenizeCalls: this._maxTokenizeCalls,
1656
1710
  resetCount: this._resetCount,
@@ -1668,6 +1722,17 @@ class OzVault {
1668
1722
  iframe.style.cssText = 'position:absolute;top:-9999px;left:-9999px;width:1px;height:1px;';
1669
1723
  iframe.setAttribute('aria-hidden', 'true');
1670
1724
  iframe.tabIndex = -1;
1725
+ // allow-scripts: JS runs. allow-same-origin: frame keeps its actual origin
1726
+ // (elements.ozura.com) so fetch() CORS requests carry the correct Origin
1727
+ // header. Without allow-same-origin the frame gets a null opaque origin and
1728
+ // the vault API's CORS policy would reject it.
1729
+ // NOT included: allow-top-navigation, allow-popups, allow-forms — prevents
1730
+ // a compromised tokenizer frame from navigating the merchant page or opening
1731
+ // popups even if the CDN bundle were somehow replaced.
1732
+ // Note: allow-scripts + allow-same-origin on a cross-origin iframe does NOT
1733
+ // expose window.parent — Same Origin Policy still applies between
1734
+ // elements.ozura.com and the merchant domain.
1735
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
1671
1736
  const parentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
1672
1737
  iframe.src = `${this.frameBaseUrl}/frame/tokenizer-frame.html#vaultId=${encodeURIComponent(this.vaultId)}${parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : ''}`;
1673
1738
  document.body.appendChild(iframe);
@@ -1803,7 +1868,7 @@ class OzVault {
1803
1868
  // Deliver the wax key via OZ_INIT so the tokenizer stores it internally.
1804
1869
  // If waxKey is still empty (fetchWaxKey hasn't resolved yet), it will be
1805
1870
  // sent again from create() once the key is available.
1806
- this.sendToTokenizer(Object.assign(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (this.waxKey ? { waxKey: this.waxKey } : {})), { debug: this._debug }));
1871
+ this.sendToTokenizer(Object.assign(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (__classPrivateFieldGet(this, _OzVault_waxKey, "f") ? { waxKey: __classPrivateFieldGet(this, _OzVault_waxKey, "f") } : {})), { debug: this._debug }));
1807
1872
  (_c = this._onReady) === null || _c === void 0 ? void 0 : _c.call(this);
1808
1873
  this.log('tokenizer iframe ready', { protocolVersion: (_d = msg.__ozVersion) !== null && _d !== void 0 ? _d : null });
1809
1874
  this.log('vault state', this.debugState());
@@ -2109,7 +2174,7 @@ class OzVault {
2109
2174
  throw new OzError('fetchWaxKey returned an empty string during auto-refresh.', undefined, 'auth');
2110
2175
  }
2111
2176
  if (!this._destroyed) {
2112
- this.waxKey = newWaxKey;
2177
+ __classPrivateFieldSet(this, _OzVault_waxKey, newWaxKey, "f");
2113
2178
  this.tokenizationSessionId = newSessionId;
2114
2179
  this._tokenizeSuccessCount = 0;
2115
2180
  }
@@ -2133,6 +2198,7 @@ class OzVault {
2133
2198
  (_a = this.tokenizerWindow) === null || _a === void 0 ? void 0 : _a.postMessage(msg, this.frameOrigin, transfer !== null && transfer !== void 0 ? transfer : []);
2134
2199
  }
2135
2200
  }
2201
+ _OzVault_waxKey = new WeakMap();
2136
2202
 
2137
2203
  const OzContext = createContext({
2138
2204
  vault: null,