@ozura/elements 1.1.0 → 1.2.0-next.27

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.
Files changed (45) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +73 -71
  3. package/dist/frame/element-frame.js +48 -3
  4. package/dist/frame/element-frame.js.map +1 -1
  5. package/dist/frame/tokenizer-frame.html +1 -1
  6. package/dist/frame/tokenizer-frame.js +113 -52
  7. package/dist/frame/tokenizer-frame.js.map +1 -1
  8. package/dist/oz-elements.esm.js +363 -311
  9. package/dist/oz-elements.esm.js.map +1 -1
  10. package/dist/oz-elements.umd.js +364 -311
  11. package/dist/oz-elements.umd.js.map +1 -1
  12. package/dist/react/frame/elementFrame.d.ts +7 -0
  13. package/dist/react/frame/tokenizerFrame.d.ts +4 -1
  14. package/dist/react/index.cjs.js +239 -180
  15. package/dist/react/index.cjs.js.map +1 -1
  16. package/dist/react/index.esm.js +238 -180
  17. package/dist/react/index.esm.js.map +1 -1
  18. package/dist/react/react/index.d.ts +50 -29
  19. package/dist/react/sdk/OzElement.d.ts +3 -1
  20. package/dist/react/sdk/OzVault.d.ts +9 -14
  21. package/dist/react/sdk/createSessionFetcher.d.ts +29 -0
  22. package/dist/react/sdk/index.d.ts +6 -26
  23. package/dist/react/server/index.d.ts +126 -74
  24. package/dist/react/types/index.d.ts +70 -41
  25. package/dist/server/frame/elementFrame.d.ts +7 -0
  26. package/dist/server/frame/tokenizerFrame.d.ts +4 -1
  27. package/dist/server/index.cjs.js +188 -90
  28. package/dist/server/index.cjs.js.map +1 -1
  29. package/dist/server/index.esm.js +187 -91
  30. package/dist/server/index.esm.js.map +1 -1
  31. package/dist/server/sdk/OzElement.d.ts +3 -1
  32. package/dist/server/sdk/OzVault.d.ts +9 -14
  33. package/dist/server/sdk/createSessionFetcher.d.ts +29 -0
  34. package/dist/server/sdk/index.d.ts +6 -26
  35. package/dist/server/server/index.d.ts +126 -74
  36. package/dist/server/types/index.d.ts +70 -41
  37. package/dist/types/frame/elementFrame.d.ts +7 -0
  38. package/dist/types/frame/tokenizerFrame.d.ts +4 -1
  39. package/dist/types/sdk/OzElement.d.ts +3 -1
  40. package/dist/types/sdk/OzVault.d.ts +9 -14
  41. package/dist/types/sdk/createSessionFetcher.d.ts +29 -0
  42. package/dist/types/sdk/index.d.ts +6 -26
  43. package/dist/types/server/index.d.ts +126 -74
  44. package/dist/types/types/index.d.ts +70 -41
  45. package/package.json +1 -1
@@ -1,144 +1,3 @@
1
- /**
2
- * errors.ts — error types and normalisation for OzElements.
3
- *
4
- * Three normalisation functions:
5
- * - normalizeVaultError — maps raw vault /tokenize errors to user-facing messages (card flows)
6
- * - normalizeBankVaultError — maps raw vault /tokenize errors to user-facing messages (bank/ACH flows)
7
- * - normalizeCardSaleError — maps raw cardSale API errors to user-facing messages
8
- *
9
- * Error keys in normalizeCardSaleError are taken directly from checkout's
10
- * errorMapping.ts so the same error strings produce the same user-facing copy.
11
- */
12
- const OZ_ERROR_CODES = new Set(['network', 'timeout', 'auth', 'validation', 'server', 'config', 'unknown']);
13
- /** Returns true and narrows to OzErrorCode when `value` is a valid member of the union. */
14
- function isOzErrorCode(value) {
15
- return typeof value === 'string' && OZ_ERROR_CODES.has(value);
16
- }
17
- class OzError extends Error {
18
- constructor(message, raw, errorCode) {
19
- super(message);
20
- this.name = 'OzError';
21
- this.raw = raw !== null && raw !== void 0 ? raw : message;
22
- this.errorCode = errorCode !== null && errorCode !== void 0 ? errorCode : 'unknown';
23
- this.retryable = this.errorCode === 'network' || this.errorCode === 'timeout' || this.errorCode === 'server';
24
- }
25
- }
26
- /** Shared patterns that apply to both card and bank vault errors. */
27
- function normalizeCommonVaultError(msg) {
28
- if (msg.includes('api key') || msg.includes('unauthorized') || msg.includes('authentication') || msg.includes('forbidden')) {
29
- return 'Authentication failed. Check your vault API key configuration.';
30
- }
31
- if (msg.includes('network') || msg.includes('failed to fetch') || msg.includes('networkerror')) {
32
- return 'A network error occurred. Please check your connection and try again.';
33
- }
34
- if (msg.includes('timeout') || msg.includes('timed out')) {
35
- return 'The request timed out. Please try again.';
36
- }
37
- if (msg.includes('http 5') || msg.includes('500') || msg.includes('502') || msg.includes('503')) {
38
- return 'A server error occurred. Please try again shortly.';
39
- }
40
- return null;
41
- }
42
- /**
43
- * Maps a raw vault /tokenize error string to a user-facing message for card flows.
44
- * Falls back to the original string if no pattern matches.
45
- */
46
- function normalizeVaultError(raw) {
47
- const msg = raw.toLowerCase();
48
- if (msg.includes('card number') || msg.includes('invalid card') || msg.includes('luhn')) {
49
- return 'The card number is invalid. Please check and try again.';
50
- }
51
- if (msg.includes('expir')) {
52
- return 'The card expiration date is invalid or the card has expired.';
53
- }
54
- if (msg.includes('cvv') || msg.includes('cvc') || msg.includes('security code')) {
55
- return 'The CVV code is invalid. Please check and try again.';
56
- }
57
- if (msg.includes('insufficient') || msg.includes('funds')) {
58
- return 'Your card has insufficient funds. Please use a different card.';
59
- }
60
- if (msg.includes('declined') || msg.includes('do not honor')) {
61
- return 'Your card was declined. Please try a different card or contact your bank.';
62
- }
63
- const common = normalizeCommonVaultError(msg);
64
- if (common)
65
- return common;
66
- return raw;
67
- }
68
- /**
69
- * Maps a raw vault /tokenize error string to a user-facing message for bank (ACH) flows.
70
- * Uses bank-specific pattern matching so card-specific messages are never shown for
71
- * bank errors. Falls back to the original string if no pattern matches.
72
- */
73
- function normalizeBankVaultError(raw) {
74
- const msg = raw.toLowerCase();
75
- if (msg.includes('account number') || msg.includes('account_number') || msg.includes('invalid account')) {
76
- return 'The bank account number is invalid. Please check and try again.';
77
- }
78
- if (msg.includes('routing number') || msg.includes('routing_number') || msg.includes('invalid routing') || /\baba\b/.test(msg)) {
79
- return 'The routing number is invalid. Please check and try again.';
80
- }
81
- const common = normalizeCommonVaultError(msg);
82
- if (common)
83
- return common;
84
- return raw;
85
- }
86
- // ─── cardSale error map (mirrors checkout/src/utils/errorMapping.ts exactly) ─
87
- const CARD_SALE_ERROR_MAP = [
88
- ['Insufficient Funds', 'Your card has insufficient funds. Please use a different payment method.'],
89
- ['Invalid card number', 'The card number you entered is invalid. Please check and try again.'],
90
- ['Card expired', 'Your card has expired. Please use a different card.'],
91
- ['CVV Verification Failed', 'The CVV code you entered is incorrect. Please check and try again.'],
92
- ['Address Verification Failed', 'The billing address does not match your card. Please verify your address.'],
93
- ['Do Not Honor', 'Your card was declined. Please contact your bank or use a different payment method.'],
94
- ['Declined', 'Your card was declined. Please contact your bank or use a different payment method.'],
95
- ['Surcharge is currently not supported', 'Surcharge fees are not supported at this time.'],
96
- ['Surcharge percent must be between', 'Surcharge fees are not supported at this time.'],
97
- ['Forbidden - API key', 'Authentication failed. Please refresh the page.'],
98
- ['Api Key is invalid', 'Authentication failed. Please refresh the page.'],
99
- ['API key not found', 'Authentication failed. Please refresh the page.'],
100
- ['Access token expired', 'Your session has expired. Please refresh the page.'],
101
- ['Access token is invalid', 'Authentication failed. Please refresh the page.'],
102
- ['Unauthorized', 'Authentication failed. Please refresh the page.'],
103
- ['Too Many Requests', 'Too many requests. Please wait a moment and try again.'],
104
- ['Rate limit exceeded', 'System is busy. Please wait a moment and try again.'],
105
- ['No processor integrations found', 'Payment processing is not configured for this merchant. Please contact the merchant for assistance.'],
106
- ['processor integration', 'Payment processing is temporarily unavailable. Please try again later or contact the merchant.'],
107
- ['Invalid zipcode', 'The ZIP code you entered is invalid. Please check and try again.'],
108
- ['failed to save to database', 'Your payment was processed but we encountered an issue. Please contact support.'],
109
- ['CASHBACK UNAVAIL', 'This transaction type is not supported. Please try again or use a different payment method.'],
110
- ];
111
- /**
112
- * Maps a raw Ozura Pay API cardSale error string to a user-facing message.
113
- *
114
- * Uses the exact same error key table as checkout's `getUserFriendlyError()` in
115
- * errorMapping.ts so both surfaces produce identical copy for the same errors.
116
- *
117
- * Falls back to the original string when it's under 100 characters, or to a
118
- * generic message for long/opaque server errors — matching checkout's fallback
119
- * behaviour exactly.
120
- *
121
- * **Trade-off:** Short unrecognised strings (e.g. processor codes like
122
- * `"PROC_TIMEOUT"`) are passed through verbatim. This intentionally mirrors
123
- * checkout so the same raw Pay API errors produce the same user-facing text on
124
- * both surfaces. If the Pay API ever returns internal codes that should never
125
- * reach the UI, the fix belongs in the Pay API error normalisation layer rather
126
- * than here.
127
- */
128
- function normalizeCardSaleError(raw) {
129
- if (!raw)
130
- return 'Payment processing failed. Please try again.';
131
- for (const [key, message] of CARD_SALE_ERROR_MAP) {
132
- if (raw.toLowerCase().includes(key.toLowerCase())) {
133
- return message;
134
- }
135
- }
136
- // Checkout fallback: pass through short errors, genericise long ones
137
- if (raw.length < 100)
138
- return raw;
139
- return 'Payment processing failed. Please try again or contact support.';
140
- }
141
-
142
1
  const THEME_DEFAULT = {
143
2
  base: {
144
3
  color: '#1a1a2e',
@@ -303,6 +162,147 @@ function mergeAppearanceWithElementStyle(appearance, elementStyle) {
303
162
  return mergeStyleConfigs(appearance, elementStyle);
304
163
  }
305
164
 
165
+ /**
166
+ * errors.ts — error types and normalisation for OzElements.
167
+ *
168
+ * Three normalisation functions:
169
+ * - normalizeVaultError — maps raw vault /tokenize errors to user-facing messages (card flows)
170
+ * - normalizeBankVaultError — maps raw vault /tokenize errors to user-facing messages (bank/ACH flows)
171
+ * - normalizeCardSaleError — maps raw cardSale API errors to user-facing messages
172
+ *
173
+ * Error keys in normalizeCardSaleError are taken directly from checkout's
174
+ * errorMapping.ts so the same error strings produce the same user-facing copy.
175
+ */
176
+ const OZ_ERROR_CODES = new Set(['network', 'timeout', 'auth', 'validation', 'server', 'config', 'unknown']);
177
+ /** Returns true and narrows to OzErrorCode when `value` is a valid member of the union. */
178
+ function isOzErrorCode(value) {
179
+ return typeof value === 'string' && OZ_ERROR_CODES.has(value);
180
+ }
181
+ class OzError extends Error {
182
+ constructor(message, raw, errorCode) {
183
+ super(message);
184
+ this.name = 'OzError';
185
+ this.raw = raw !== null && raw !== void 0 ? raw : message;
186
+ this.errorCode = errorCode !== null && errorCode !== void 0 ? errorCode : 'unknown';
187
+ this.retryable = this.errorCode === 'network' || this.errorCode === 'timeout' || this.errorCode === 'server';
188
+ }
189
+ }
190
+ /** Shared patterns that apply to both card and bank vault errors. */
191
+ function normalizeCommonVaultError(msg) {
192
+ if (msg.includes('api key') || msg.includes('unauthorized') || msg.includes('authentication') || msg.includes('forbidden')) {
193
+ return 'Authentication failed. Check your vault API key configuration.';
194
+ }
195
+ if (msg.includes('network') || msg.includes('failed to fetch') || msg.includes('networkerror')) {
196
+ return 'A network error occurred. Please check your connection and try again.';
197
+ }
198
+ if (msg.includes('timeout') || msg.includes('timed out')) {
199
+ return 'The request timed out. Please try again.';
200
+ }
201
+ if (msg.includes('http 5') || msg.includes('500') || msg.includes('502') || msg.includes('503')) {
202
+ return 'A server error occurred. Please try again shortly.';
203
+ }
204
+ return null;
205
+ }
206
+ /**
207
+ * Maps a raw vault /tokenize error string to a user-facing message for card flows.
208
+ * Falls back to the original string if no pattern matches.
209
+ */
210
+ function normalizeVaultError(raw) {
211
+ const msg = raw.toLowerCase();
212
+ if (msg.includes('card number') || msg.includes('invalid card') || msg.includes('luhn')) {
213
+ return 'The card number is invalid. Please check and try again.';
214
+ }
215
+ if (msg.includes('expir')) {
216
+ return 'The card expiration date is invalid or the card has expired.';
217
+ }
218
+ if (msg.includes('cvv') || msg.includes('cvc') || msg.includes('security code')) {
219
+ return 'The CVV code is invalid. Please check and try again.';
220
+ }
221
+ if (msg.includes('insufficient') || msg.includes('funds')) {
222
+ return 'Your card has insufficient funds. Please use a different card.';
223
+ }
224
+ if (msg.includes('declined') || msg.includes('do not honor')) {
225
+ return 'Your card was declined. Please try a different card or contact your bank.';
226
+ }
227
+ const common = normalizeCommonVaultError(msg);
228
+ if (common)
229
+ return common;
230
+ return raw;
231
+ }
232
+ /**
233
+ * Maps a raw vault /tokenize error string to a user-facing message for bank (ACH) flows.
234
+ * Uses bank-specific pattern matching so card-specific messages are never shown for
235
+ * bank errors. Falls back to the original string if no pattern matches.
236
+ */
237
+ function normalizeBankVaultError(raw) {
238
+ const msg = raw.toLowerCase();
239
+ if (msg.includes('account number') || msg.includes('account_number') || msg.includes('invalid account')) {
240
+ return 'The bank account number is invalid. Please check and try again.';
241
+ }
242
+ if (msg.includes('routing number') || msg.includes('routing_number') || msg.includes('invalid routing') || /\baba\b/.test(msg)) {
243
+ return 'The routing number is invalid. Please check and try again.';
244
+ }
245
+ const common = normalizeCommonVaultError(msg);
246
+ if (common)
247
+ return common;
248
+ return raw;
249
+ }
250
+ // ─── cardSale error map (mirrors checkout/src/utils/errorMapping.ts exactly) ─
251
+ const CARD_SALE_ERROR_MAP = [
252
+ ['Insufficient Funds', 'Your card has insufficient funds. Please use a different payment method.'],
253
+ ['Invalid card number', 'The card number you entered is invalid. Please check and try again.'],
254
+ ['Card expired', 'Your card has expired. Please use a different card.'],
255
+ ['CVV Verification Failed', 'The CVV code you entered is incorrect. Please check and try again.'],
256
+ ['Address Verification Failed', 'The billing address does not match your card. Please verify your address.'],
257
+ ['Do Not Honor', 'Your card was declined. Please contact your bank or use a different payment method.'],
258
+ ['Declined', 'Your card was declined. Please contact your bank or use a different payment method.'],
259
+ ['Surcharge is currently not supported', 'Surcharge fees are not supported at this time.'],
260
+ ['Surcharge percent must be between', 'Surcharge fees are not supported at this time.'],
261
+ ['Forbidden - API key', 'Authentication failed. Please refresh the page.'],
262
+ ['Api Key is invalid', 'Authentication failed. Please refresh the page.'],
263
+ ['API key not found', 'Authentication failed. Please refresh the page.'],
264
+ ['Access token expired', 'Your session has expired. Please refresh the page.'],
265
+ ['Access token is invalid', 'Authentication failed. Please refresh the page.'],
266
+ ['Unauthorized', 'Authentication failed. Please refresh the page.'],
267
+ ['Too Many Requests', 'Too many requests. Please wait a moment and try again.'],
268
+ ['Rate limit exceeded', 'System is busy. Please wait a moment and try again.'],
269
+ ['No processor integrations found', 'Payment processing is not configured for this merchant. Please contact the merchant for assistance.'],
270
+ ['processor integration', 'Payment processing is temporarily unavailable. Please try again later or contact the merchant.'],
271
+ ['Invalid zipcode', 'The ZIP code you entered is invalid. Please check and try again.'],
272
+ ['failed to save to database', 'Your payment was processed but we encountered an issue. Please contact support.'],
273
+ ['CASHBACK UNAVAIL', 'This transaction type is not supported. Please try again or use a different payment method.'],
274
+ ];
275
+ /**
276
+ * Maps a raw Ozura Pay API cardSale error string to a user-facing message.
277
+ *
278
+ * Uses the exact same error key table as checkout's `getUserFriendlyError()` in
279
+ * errorMapping.ts so both surfaces produce identical copy for the same errors.
280
+ *
281
+ * Falls back to the original string when it's under 100 characters, or to a
282
+ * generic message for long/opaque server errors — matching checkout's fallback
283
+ * behaviour exactly.
284
+ *
285
+ * **Trade-off:** Short unrecognised strings (e.g. processor codes like
286
+ * `"PROC_TIMEOUT"`) are passed through verbatim. This intentionally mirrors
287
+ * checkout so the same raw Pay API errors produce the same user-facing text on
288
+ * both surfaces. If the Pay API ever returns internal codes that should never
289
+ * reach the UI, the fix belongs in the Pay API error normalisation layer rather
290
+ * than here.
291
+ */
292
+ function normalizeCardSaleError(raw) {
293
+ if (!raw)
294
+ return 'Payment processing failed. Please try again.';
295
+ for (const [key, message] of CARD_SALE_ERROR_MAP) {
296
+ if (raw.toLowerCase().includes(key.toLowerCase())) {
297
+ return message;
298
+ }
299
+ }
300
+ // Checkout fallback: pass through short errors, genericise long ones
301
+ if (raw.length < 100)
302
+ return raw;
303
+ return 'Payment processing failed. Please try again or contact support.';
304
+ }
305
+
306
306
  /**
307
307
  * Generates a RFC 4122 v4 UUID.
308
308
  *
@@ -376,7 +376,7 @@ function sanitizeOptions(options) {
376
376
  * it never holds raw card data — all sensitive values live in the iframe.
377
377
  */
378
378
  class OzElement {
379
- constructor(elementType, options, vaultId, frameBaseUrl, fonts = [], appearanceStyle) {
379
+ constructor(elementType, options, vaultId, frameBaseUrl, fonts = [], appearanceStyle, onDestroy) {
380
380
  this.iframe = null;
381
381
  this._frameWindow = null;
382
382
  this._ready = false;
@@ -392,6 +392,7 @@ class OzElement {
392
392
  this.fonts = fonts;
393
393
  this.appearanceStyle = appearanceStyle;
394
394
  this.frameId = `oz-${elementType}-${uuid()}`;
395
+ this._onDestroy = onDestroy;
395
396
  }
396
397
  /** The element type this proxy represents. */
397
398
  get type() {
@@ -547,9 +548,14 @@ class OzElement {
547
548
  * and prevents future use. Distinct from `unmount()` which allows re-mounting.
548
549
  */
549
550
  destroy() {
551
+ var _a;
550
552
  this.unmount();
551
553
  this.handlers.clear();
552
554
  this._destroyed = true;
555
+ // Notify OzVault so it can prune the stale frameId entry from its elements
556
+ // and completionState maps. Without this, manually calling el.destroy() leaks
557
+ // map entries that grow unboundedly in SPA scenarios with repeated mount/unmount.
558
+ (_a = this._onDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
553
559
  }
554
560
  // ─── Called by OzVault ───────────────────────────────────────────────────
555
561
  /**
@@ -890,13 +896,116 @@ function validateBilling(billing) {
890
896
  */
891
897
  const PROTOCOL_VERSION = 1;
892
898
 
899
+ /**
900
+ * Creates a `getSessionKey` callback for `OzVault.create()` and `<OzElements>`.
901
+ *
902
+ * This is the recommended way to wire the SDK to your backend session endpoint.
903
+ * If you don't need custom headers or auth logic, pass `sessionUrl` directly to
904
+ * `OzVault.create()` or `<OzElements>` — it calls this helper internally.
905
+ *
906
+ * The callback POSTs `{ sessionId }` to `url` and reads `sessionKey` (or the
907
+ * legacy `waxKey`) from the JSON response, so it is compatible with both the
908
+ * new `createSessionMiddleware` and the old `createMintWaxMiddleware` backends.
909
+ *
910
+ * Each call enforces a **10-second timeout**. On pure network failures
911
+ * (offline, DNS, connection refused) the request is retried **once after 750ms**.
912
+ * HTTP 4xx/5xx errors are never retried — they indicate misconfiguration.
913
+ *
914
+ * @param url - Absolute or relative URL of your session endpoint, e.g. `'/api/oz-session'`.
915
+ *
916
+ * @example
917
+ * // Simplest — just pass sessionUrl, no need to call this directly
918
+ * const vault = await OzVault.create({ pubKey: 'pk_live_...', sessionUrl: '/api/oz-session' });
919
+ *
920
+ * @example
921
+ * // Manual — use when you need custom headers
922
+ * const vault = await OzVault.create({
923
+ * pubKey: 'pk_live_...',
924
+ * getSessionKey: createSessionFetcher('/api/oz-session'),
925
+ * });
926
+ */
927
+ function createSessionFetcher(url) {
928
+ const TIMEOUT_MS = 10000;
929
+ // Each attempt gets its own AbortController so a timeout on attempt 1 does
930
+ // not bleed into the retry.
931
+ const attemptFetch = (sessionId) => {
932
+ const controller = new AbortController();
933
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
934
+ return fetch(url, {
935
+ method: 'POST',
936
+ headers: { 'Content-Type': 'application/json' },
937
+ body: JSON.stringify({ sessionId }),
938
+ signal: controller.signal,
939
+ }).finally(() => clearTimeout(timer));
940
+ };
941
+ return async (sessionId) => {
942
+ let res;
943
+ try {
944
+ res = await attemptFetch(sessionId);
945
+ }
946
+ catch (firstErr) {
947
+ // Timeout/abort — don't retry, we already waited the full duration.
948
+ if (firstErr instanceof Error && (firstErr.name === 'AbortError' || firstErr.name === 'TimeoutError')) {
949
+ throw new OzError(`Session endpoint timed out after ${TIMEOUT_MS / 1000}s (${url})`, undefined, 'timeout');
950
+ }
951
+ // Pure network error — retry once after a short pause.
952
+ await new Promise(resolve => setTimeout(resolve, 750));
953
+ try {
954
+ res = await attemptFetch(sessionId);
955
+ }
956
+ catch (retryErr) {
957
+ const msg = retryErr instanceof Error ? retryErr.message : 'Network error';
958
+ throw new OzError(`Could not reach session endpoint (${url}): ${msg}`, undefined, 'network');
959
+ }
960
+ }
961
+ // Parse JSON separately from the ok-check so that a non-JSON error body
962
+ // (HTML error page, WAF block, CDN 503) produces the right error code.
963
+ // Previously res.json() was attempted before res.ok was checked; a parse
964
+ // failure on a 5xx HTML body would fall through as {} and produce a
965
+ // misleading 'validation' code when the real cause is a server/network issue.
966
+ let data = {};
967
+ try {
968
+ data = await res.json();
969
+ }
970
+ catch (_a) {
971
+ if (!res.ok) {
972
+ throw new OzError(`Session endpoint returned HTTP ${res.status} with a non-JSON body`, undefined, res.status >= 500 ? 'server' : res.status === 401 || res.status === 403 ? 'auth' : 'validation');
973
+ }
974
+ // HTTP 200 but body isn't JSON — this is a misconfigured session endpoint.
975
+ throw new OzError('Session endpoint returned HTTP 200 but the response body is not valid JSON. Check your /api/oz-session implementation.', undefined, 'validation');
976
+ }
977
+ if (!res.ok) {
978
+ throw new OzError(typeof data.error === 'string' && data.error
979
+ ? data.error
980
+ : `Session endpoint returned HTTP ${res.status}`, undefined, res.status >= 500 ? 'server' : res.status === 401 || res.status === 403 ? 'auth' : 'validation');
981
+ }
982
+ // Accept both new `sessionKey` and legacy `waxKey` for backward compatibility
983
+ // with backends that haven't migrated to createSessionMiddleware yet.
984
+ const key = (typeof data.sessionKey === 'string' ? data.sessionKey : '') ||
985
+ (typeof data.waxKey === 'string' ? data.waxKey : '');
986
+ if (!key.trim()) {
987
+ throw new OzError('Session endpoint response is missing sessionKey. Check your /api/oz-session implementation.', undefined, 'validation');
988
+ }
989
+ return key;
990
+ };
991
+ }
992
+
893
993
  function isCardMetadata(v) {
894
- return !!v && typeof v === 'object' && typeof v.last4 === 'string';
994
+ if (!v || typeof v !== 'object')
995
+ return false;
996
+ const r = v;
997
+ return (typeof r.last4 === 'string' &&
998
+ typeof r.brand === 'string' &&
999
+ typeof r.expMonth === 'string' &&
1000
+ typeof r.expYear === 'string');
895
1001
  }
896
1002
  function isBankAccountMetadata(v) {
897
- return !!v && typeof v === 'object' && typeof v.last4 === 'string';
1003
+ if (!v || typeof v !== 'object')
1004
+ return false;
1005
+ const r = v;
1006
+ return typeof r.last4 === 'string' && typeof r.routingNumberLast4 === 'string';
898
1007
  }
899
- const DEFAULT_FRAME_BASE_URL = "https://elements.ozura.com";
1008
+ const DEFAULT_FRAME_BASE_URL = "https://lively-hill-097170c0f.4.azurestaticapps.net";
900
1009
  /**
901
1010
  * The main entry point for OzElements. Creates and manages iframe-based
902
1011
  * card input elements that keep raw card data isolated from the merchant page.
@@ -904,16 +1013,10 @@ const DEFAULT_FRAME_BASE_URL = "https://elements.ozura.com";
904
1013
  * Use the static `OzVault.create()` factory — do not call `new OzVault()` directly.
905
1014
  *
906
1015
  * @example
1016
+ * // Recommended — pass sessionUrl and let the SDK call your backend automatically
907
1017
  * const vault = await OzVault.create({
908
- * pubKey: 'pk_live_...',
909
- * fetchWaxKey: async (sessionId) => {
910
- * // Call your backend — which calls ozura.mintWaxKey() from @ozura/elements/server
911
- * const { waxKey } = await fetch('/api/mint-wax', {
912
- * method: 'POST',
913
- * body: JSON.stringify({ sessionId }),
914
- * }).then(r => r.json());
915
- * return waxKey;
916
- * },
1018
+ * pubKey: 'pk_prod_...', // or 'pk_test_...' for test mode
1019
+ * sessionUrl: '/api/oz-session', // backend endpoint that calls ozura.createSession()
917
1020
  * });
918
1021
  * const cardNum = vault.createElement('cardNumber');
919
1022
  * cardNum.mount('#card-number');
@@ -929,7 +1032,7 @@ class OzVault {
929
1032
  * @internal
930
1033
  */
931
1034
  constructor(options, waxKey, tokenizationSessionId) {
932
- var _a, _b, _c, _d;
1035
+ var _a, _b, _c, _d, _e;
933
1036
  this.elements = new Map();
934
1037
  this.elementsByType = new Map();
935
1038
  this.bankElementsByType = new Map();
@@ -963,7 +1066,12 @@ class OzVault {
963
1066
  this.fonts = (_a = options.fonts) !== null && _a !== void 0 ? _a : [];
964
1067
  this.resolvedAppearance = resolveAppearance(options.appearance);
965
1068
  this.vaultId = `vault-${uuid()}`;
966
- this._maxTokenizeCalls = (_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3;
1069
+ // sessionLimit takes precedence over legacy maxTokenizeCalls.
1070
+ // null means unlimited — use Infinity so the ">=" check never triggers.
1071
+ const rawLimit = options.sessionLimit !== undefined
1072
+ ? options.sessionLimit
1073
+ : ((_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3);
1074
+ this._maxTokenizeCalls = rawLimit === null ? Infinity : rawLimit;
967
1075
  this._debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
968
1076
  this.boundHandleMessage = this.handleMessage.bind(this);
969
1077
  window.addEventListener('message', this.boundHandleMessage);
@@ -979,7 +1087,8 @@ class OzVault {
979
1087
  }
980
1088
  }, timeout);
981
1089
  }
982
- this._onWaxRefresh = options.onWaxRefresh;
1090
+ // onSessionRefresh takes precedence over legacy onWaxRefresh
1091
+ this._onWaxRefresh = (_e = options.onSessionRefresh) !== null && _e !== void 0 ? _e : options.onWaxRefresh;
983
1092
  this._onReady = options.onReady;
984
1093
  this.log('vault created', { vaultId: this.vaultId, frameBaseUrl: this.frameBaseUrl, maxTokenizeCalls: this._maxTokenizeCalls });
985
1094
  }
@@ -987,51 +1096,63 @@ class OzVault {
987
1096
  * Creates and returns a ready `OzVault` instance.
988
1097
  *
989
1098
  * Internally this:
990
- * 1. Generates a `tokenizationSessionId` (UUID).
1099
+ * 1. Generates a session UUID.
991
1100
  * 2. Starts loading the hidden tokenizer iframe immediately.
992
- * 3. Calls `options.fetchWaxKey(tokenizationSessionId)` concurrently — your
993
- * backend mints a session-bound wax key from the vault and returns it.
994
- * 4. Resolves with the vault instance once the wax key is stored. The iframe
1101
+ * 3. Fetches a session key from your backend concurrently — either via
1102
+ * `sessionUrl` (simplest), `getSessionKey` (custom headers/auth), or the
1103
+ * deprecated `fetchWaxKey` callback.
1104
+ * 4. Resolves with the vault instance once the session key is stored. The iframe
995
1105
  * has been loading the whole time, so `isReady` may already be true or
996
1106
  * will fire shortly after.
997
1107
  *
998
1108
  * The returned vault is ready to create elements immediately. `createToken()`
999
1109
  * additionally requires `vault.isReady` (tokenizer iframe loaded).
1000
1110
  *
1001
- * @throws {OzError} if `fetchWaxKey` throws, returns a non-string value, or returns an empty/whitespace-only string.
1111
+ * @throws {OzError} if the session fetch fails, times out, or returns an empty string.
1002
1112
  */
1003
1113
  static async create(options, signal) {
1004
1114
  if (!options.pubKey || !options.pubKey.trim()) {
1005
1115
  throw new OzError('pubKey is required in options. Obtain your public key from the Ozura admin.');
1006
1116
  }
1007
- if (typeof options.fetchWaxKey !== 'function') {
1008
- throw new OzError('fetchWaxKey must be a function. See OzVault.create() docs for the expected signature.');
1117
+ // Normalize the session callback. Priority: sessionUrl > getSessionKey > fetchWaxKey (deprecated).
1118
+ // This allows merchants to use the clean new API without touching legacy code.
1119
+ let resolvedFetchKey;
1120
+ if (options.sessionUrl) {
1121
+ resolvedFetchKey = createSessionFetcher(options.sessionUrl);
1122
+ }
1123
+ else if (typeof options.getSessionKey === 'function') {
1124
+ resolvedFetchKey = options.getSessionKey;
1125
+ }
1126
+ else if (typeof options.fetchWaxKey === 'function') {
1127
+ resolvedFetchKey = options.fetchWaxKey;
1128
+ }
1129
+ else {
1130
+ throw new OzError('A session URL or callback is required. Pass sessionUrl, getSessionKey, or fetchWaxKey to OzVault.create().');
1009
1131
  }
1010
1132
  const tokenizationSessionId = uuid();
1011
1133
  // Construct the vault immediately — this mounts the tokenizer iframe so it
1012
- // starts loading while fetchWaxKey is in flight. The waxKey field starts
1013
- // empty and is set below before create() returns.
1134
+ // starts loading while the session fetch is in flight.
1014
1135
  const vault = new OzVault(options, '', tokenizationSessionId);
1015
1136
  // If the caller provides an AbortSignal (e.g. React useEffect cleanup),
1016
1137
  // destroy the vault immediately on abort so the tokenizer iframe and message
1017
- // listener are removed synchronously rather than waiting for fetchWaxKey to
1018
- // settle. This eliminates the brief double-iframe window in React StrictMode.
1138
+ // listener are removed synchronously rather than waiting for the session fetch
1139
+ // to settle. This eliminates the brief double-iframe window in React StrictMode.
1019
1140
  const onAbort = () => vault.destroy();
1020
1141
  signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
1021
1142
  let waxKey;
1022
1143
  try {
1023
- waxKey = await options.fetchWaxKey(tokenizationSessionId);
1144
+ waxKey = await resolvedFetchKey(tokenizationSessionId);
1024
1145
  }
1025
1146
  catch (err) {
1026
1147
  signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
1027
1148
  vault.destroy();
1028
1149
  if (signal === null || signal === void 0 ? void 0 : signal.aborted)
1029
1150
  throw new OzError('OzVault.create() was cancelled.');
1030
- // Preserve errorCode/retryable from OzError (e.g. timeout/network from createFetchWaxKey)
1151
+ // Preserve errorCode/retryable from OzError (e.g. timeout/network from createSessionFetcher)
1031
1152
  // so callers can distinguish transient failures from config errors.
1032
1153
  const originalCode = err instanceof OzError ? err.errorCode : undefined;
1033
1154
  const msg = err instanceof Error ? err.message : 'Unknown error';
1034
- throw new OzError(`fetchWaxKey threw an error: ${msg}`, undefined, originalCode);
1155
+ throw new OzError(`Session fetch threw an error: ${msg}`, undefined, originalCode);
1035
1156
  }
1036
1157
  signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
1037
1158
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
@@ -1040,11 +1161,11 @@ class OzVault {
1040
1161
  }
1041
1162
  if (typeof waxKey !== 'string' || !waxKey.trim()) {
1042
1163
  vault.destroy();
1043
- throw new OzError('fetchWaxKey must return a non-empty wax key string. Check your mint endpoint.');
1164
+ throw new OzError('Session fetch returned an empty key. Check your session endpoint response — it must return { sessionKey: "..." }.');
1044
1165
  }
1045
1166
  // Static methods can access private fields of instances of the same class.
1046
1167
  vault.waxKey = waxKey;
1047
- vault._storedFetchWaxKey = options.fetchWaxKey;
1168
+ vault._storedFetchWaxKey = resolvedFetchKey;
1048
1169
  // If the tokenizer iframe fired OZ_FRAME_READY before fetchWaxKey resolved,
1049
1170
  // the OZ_INIT sent at that point had an empty waxKey. Send a follow-up now
1050
1171
  // so the tokenizer has the key stored before any createToken() call.
@@ -1131,7 +1252,12 @@ class OzVault {
1131
1252
  this.completionState.delete(existing.frameId);
1132
1253
  existing.destroy();
1133
1254
  }
1134
- const el = new OzElement(type, options, this.vaultId, this.frameBaseUrl, this.fonts, this.resolvedAppearance);
1255
+ const el = new OzElement(type, options, this.vaultId, this.frameBaseUrl, this.fonts, this.resolvedAppearance, () => {
1256
+ // Prune vault-level maps when the element is manually destroyed so they
1257
+ // don't grow unboundedly in SPA scenarios with repeated mount/unmount cycles.
1258
+ this.elements.delete(el.frameId);
1259
+ this.completionState.delete(el.frameId);
1260
+ });
1135
1261
  this.elements.set(el.frameId, el);
1136
1262
  typeMap.set(type, el);
1137
1263
  return el;
@@ -1710,9 +1836,9 @@ class OzVault {
1710
1836
  // key without waiting for a vault rejection.
1711
1837
  this._tokenizeSuccessCount++;
1712
1838
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1713
- this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1839
+ this.log('proactive session key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1714
1840
  this.refreshWaxKey().catch((err) => {
1715
- console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1841
+ console.warn('[OzVault] Post-budget session key refresh failed:', err instanceof Error ? err.message : err);
1716
1842
  });
1717
1843
  }
1718
1844
  }
@@ -1788,61 +1914,65 @@ class OzVault {
1788
1914
  }
1789
1915
  pending.reject(new OzError(normalizeVaultError(raw), raw, errorCode));
1790
1916
  }
1791
- // Also check bank resolvers — both card and bank errors use OZ_TOKEN_ERROR
1792
- const bankPending = this.bankTokenizeResolvers.get(msg.requestId);
1793
- if (bankPending) {
1794
- this.bankTokenizeResolvers.delete(msg.requestId);
1795
- if (bankPending.timeoutId != null)
1796
- clearTimeout(bankPending.timeoutId);
1797
- if (this.isRefreshableAuthError(errorCode, raw) && !bankPending.retried && this._storedFetchWaxKey) {
1798
- const resetCountAtRetry = this._resetCount;
1799
- this.refreshWaxKey().then(() => {
1800
- if (this._destroyed) {
1801
- bankPending.reject(new OzError('Vault destroyed during wax key refresh.'));
1802
- return;
1803
- }
1804
- if (this._resetCount !== resetCountAtRetry) {
1805
- bankPending.reject(new OzError('Vault was reset while tokenization was in progress.'));
1806
- return;
1807
- }
1808
- const newRequestId = `req-${uuid()}`;
1809
- this.bankTokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, bankPending), { retried: true }));
1810
- try {
1811
- const retryBankChannels = bankPending.readyElements.map(() => new MessageChannel());
1812
- this.sendToTokenizer({
1813
- type: 'OZ_BANK_TOKENIZE',
1814
- requestId: newRequestId,
1815
- waxKey: this.waxKey,
1816
- tokenizationSessionId: this.tokenizationSessionId,
1817
- pubKey: this.pubKey,
1818
- firstName: bankPending.firstName,
1819
- lastName: bankPending.lastName,
1820
- fieldCount: bankPending.fieldCount,
1821
- }, retryBankChannels.map(ch => ch.port1));
1822
- bankPending.readyElements.forEach((el, i) => el.beginCollect(newRequestId, retryBankChannels[i].port2));
1823
- const retryBankTimeoutId = setTimeout(() => {
1824
- if (this.bankTokenizeResolvers.has(newRequestId)) {
1825
- this.bankTokenizeResolvers.delete(newRequestId);
1826
- this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId: newRequestId });
1827
- bankPending.reject(new OzError('Bank tokenization timed out after wax key refresh.', undefined, 'timeout'));
1828
- }
1829
- }, 30000);
1830
- const retryBankEntry = this.bankTokenizeResolvers.get(newRequestId);
1831
- if (retryBankEntry)
1832
- retryBankEntry.timeoutId = retryBankTimeoutId;
1833
- }
1834
- catch (setupErr) {
1835
- this.bankTokenizeResolvers.delete(newRequestId);
1836
- bankPending.reject(setupErr instanceof OzError ? setupErr : new OzError('Retry bank tokenization failed to start'));
1837
- }
1838
- }).catch((refreshErr) => {
1839
- const msg = refreshErr instanceof Error ? refreshErr.message : 'Wax key refresh failed';
1840
- bankPending.reject(new OzError(msg, undefined, 'auth'));
1841
- });
1842
- break;
1917
+ else {
1918
+ // Also check bank resolvers — both card and bank errors use OZ_TOKEN_ERROR.
1919
+ // Use else-if rather than sequential checks so a UUID collision (however
1920
+ // improbable) can never trigger double-rejection of two unrelated resolvers.
1921
+ const bankPending = this.bankTokenizeResolvers.get(msg.requestId);
1922
+ if (bankPending) {
1923
+ this.bankTokenizeResolvers.delete(msg.requestId);
1924
+ if (bankPending.timeoutId != null)
1925
+ clearTimeout(bankPending.timeoutId);
1926
+ if (this.isRefreshableAuthError(errorCode, raw) && !bankPending.retried && this._storedFetchWaxKey) {
1927
+ const resetCountAtRetry = this._resetCount;
1928
+ this.refreshWaxKey().then(() => {
1929
+ if (this._destroyed) {
1930
+ bankPending.reject(new OzError('Vault destroyed during wax key refresh.'));
1931
+ return;
1932
+ }
1933
+ if (this._resetCount !== resetCountAtRetry) {
1934
+ bankPending.reject(new OzError('Vault was reset while tokenization was in progress.'));
1935
+ return;
1936
+ }
1937
+ const newRequestId = `req-${uuid()}`;
1938
+ this.bankTokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, bankPending), { retried: true }));
1939
+ try {
1940
+ const retryBankChannels = bankPending.readyElements.map(() => new MessageChannel());
1941
+ this.sendToTokenizer({
1942
+ type: 'OZ_BANK_TOKENIZE',
1943
+ requestId: newRequestId,
1944
+ waxKey: this.waxKey,
1945
+ tokenizationSessionId: this.tokenizationSessionId,
1946
+ pubKey: this.pubKey,
1947
+ firstName: bankPending.firstName,
1948
+ lastName: bankPending.lastName,
1949
+ fieldCount: bankPending.fieldCount,
1950
+ }, retryBankChannels.map(ch => ch.port1));
1951
+ bankPending.readyElements.forEach((el, i) => el.beginCollect(newRequestId, retryBankChannels[i].port2));
1952
+ const retryBankTimeoutId = setTimeout(() => {
1953
+ if (this.bankTokenizeResolvers.has(newRequestId)) {
1954
+ this.bankTokenizeResolvers.delete(newRequestId);
1955
+ this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId: newRequestId });
1956
+ bankPending.reject(new OzError('Bank tokenization timed out after wax key refresh.', undefined, 'timeout'));
1957
+ }
1958
+ }, 30000);
1959
+ const retryBankEntry = this.bankTokenizeResolvers.get(newRequestId);
1960
+ if (retryBankEntry)
1961
+ retryBankEntry.timeoutId = retryBankTimeoutId;
1962
+ }
1963
+ catch (setupErr) {
1964
+ this.bankTokenizeResolvers.delete(newRequestId);
1965
+ bankPending.reject(setupErr instanceof OzError ? setupErr : new OzError('Retry bank tokenization failed to start'));
1966
+ }
1967
+ }).catch((refreshErr) => {
1968
+ const msg = refreshErr instanceof Error ? refreshErr.message : 'Wax key refresh failed';
1969
+ bankPending.reject(new OzError(msg, undefined, 'auth'));
1970
+ });
1971
+ break;
1972
+ }
1973
+ bankPending.reject(new OzError(normalizeBankVaultError(raw), raw, errorCode));
1843
1974
  }
1844
- bankPending.reject(new OzError(normalizeBankVaultError(raw), raw, errorCode));
1845
- }
1975
+ } // end else (bank path)
1846
1976
  break;
1847
1977
  }
1848
1978
  case 'OZ_BANK_TOKEN_RESULT': {
@@ -1870,9 +2000,9 @@ class OzVault {
1870
2000
  // Same proactive refresh logic as card tokenization.
1871
2001
  this._tokenizeSuccessCount++;
1872
2002
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1873
- this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
2003
+ this.log('proactive session key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1874
2004
  this.refreshWaxKey().catch((err) => {
1875
- console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
2005
+ console.warn('[OzVault] Post-budget session key refresh failed:', err instanceof Error ? err.message : err);
1876
2006
  });
1877
2007
  }
1878
2008
  }
@@ -1958,83 +2088,5 @@ class OzVault {
1958
2088
  }
1959
2089
  }
1960
2090
 
1961
- /**
1962
- * Creates a ready-to-use `fetchWaxKey` callback for `OzVault.create()` and `<OzElements>`.
1963
- *
1964
- * Calls your backend mint endpoint with `{ sessionId }` and returns the wax key string.
1965
- * Throws on non-OK responses or a missing `waxKey` field so the vault can surface the
1966
- * error through its normal error path.
1967
- *
1968
- * Each call enforces a 10-second per-attempt timeout. On a pure network-level
1969
- * failure (connection refused, DNS failure, etc.) the call is retried once after
1970
- * 750ms before throwing. HTTP errors (4xx/5xx) are never retried — they indicate
1971
- * an endpoint misconfiguration or an invalid key, not a transient failure.
1972
- *
1973
- * The mint endpoint is typically the one-line `createMintWaxHandler` / `createMintWaxMiddleware`
1974
- * from `@ozura/elements/server`.
1975
- *
1976
- * @param mintUrl - Absolute or relative URL of your wax-key mint endpoint, e.g. `'/api/mint-wax'`.
1977
- *
1978
- * @example
1979
- * // Vanilla JS
1980
- * const vault = await OzVault.create({
1981
- * pubKey: 'pk_live_...',
1982
- * fetchWaxKey: createFetchWaxKey('/api/mint-wax'),
1983
- * });
1984
- *
1985
- * @example
1986
- * // React
1987
- * <OzElements pubKey="pk_live_..." fetchWaxKey={createFetchWaxKey('/api/mint-wax')}>
1988
- */
1989
- function createFetchWaxKey(mintUrl) {
1990
- const TIMEOUT_MS = 10000;
1991
- // Each attempt gets its own AbortController so a timeout on attempt 1 does
1992
- // not bleed into the retry. Uses AbortController + setTimeout instead of
1993
- // AbortSignal.timeout() to support environments without that API.
1994
- const attemptFetch = (sessionId) => {
1995
- const controller = new AbortController();
1996
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
1997
- return fetch(mintUrl, {
1998
- method: 'POST',
1999
- headers: { 'Content-Type': 'application/json' },
2000
- body: JSON.stringify({ sessionId }),
2001
- signal: controller.signal,
2002
- }).finally(() => clearTimeout(timer));
2003
- };
2004
- return async (sessionId) => {
2005
- let res;
2006
- try {
2007
- res = await attemptFetch(sessionId);
2008
- }
2009
- catch (firstErr) {
2010
- // Abort/timeout should not be retried — the server received nothing or
2011
- // we already waited the full timeout duration.
2012
- if (firstErr instanceof Error && (firstErr.name === 'AbortError' || firstErr.name === 'TimeoutError')) {
2013
- throw new OzError(`Wax key mint timed out after ${TIMEOUT_MS / 1000}s (${mintUrl})`, undefined, 'timeout');
2014
- }
2015
- // Pure network error (offline, DNS, connection refused) — retry once
2016
- // after a short pause in case of a transient blip.
2017
- await new Promise(resolve => setTimeout(resolve, 750));
2018
- try {
2019
- res = await attemptFetch(sessionId);
2020
- }
2021
- catch (retryErr) {
2022
- const msg = retryErr instanceof Error ? retryErr.message : 'Network error';
2023
- throw new OzError(`Could not reach wax key mint endpoint (${mintUrl}): ${msg}`, undefined, 'network');
2024
- }
2025
- }
2026
- const data = await res.json().catch(() => ({}));
2027
- if (!res.ok) {
2028
- throw new OzError(typeof data.error === 'string' && data.error
2029
- ? data.error
2030
- : `Wax key mint failed (HTTP ${res.status})`, undefined, res.status >= 500 ? 'server' : res.status === 401 || res.status === 403 ? 'auth' : 'validation');
2031
- }
2032
- if (typeof data.waxKey !== 'string' || !data.waxKey.trim()) {
2033
- throw new OzError('Mint endpoint response is missing waxKey. Check your /api/mint-wax implementation.', undefined, 'validation');
2034
- }
2035
- return data.waxKey;
2036
- };
2037
- }
2038
-
2039
- export { OzElement, OzError, OzVault, createFetchWaxKey, normalizeBankVaultError, normalizeCardSaleError, normalizeVaultError };
2091
+ export { OzElement, OzError, OzVault, createSessionFetcher as createFetchWaxKey, createSessionFetcher, normalizeBankVaultError, normalizeCardSaleError, normalizeVaultError };
2040
2092
  //# sourceMappingURL=oz-elements.esm.js.map