@explorins/pers-sdk 2.1.33-alpha.0 → 2.1.37

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 (124) hide show
  1. package/README.md +7 -0
  2. package/dist/analytics/api/analytics-api.d.ts +42 -1
  3. package/dist/analytics/api/analytics-api.d.ts.map +1 -1
  4. package/dist/analytics/models/index.d.ts +2 -2
  5. package/dist/analytics/models/index.d.ts.map +1 -1
  6. package/dist/analytics/services/analytics-service.d.ts +8 -1
  7. package/dist/analytics/services/analytics-service.d.ts.map +1 -1
  8. package/dist/analytics.cjs +1 -1
  9. package/dist/analytics.js +1 -1
  10. package/dist/business/api/business-api.d.ts +6 -3
  11. package/dist/business/api/business-api.d.ts.map +1 -1
  12. package/dist/business/services/business-service.d.ts +2 -2
  13. package/dist/business/services/business-service.d.ts.map +1 -1
  14. package/dist/business.cjs +1 -1
  15. package/dist/business.js +1 -1
  16. package/dist/chunks/{analytics-service-vm7B7LhS.js → analytics-service-CF7hSwhy.js} +53 -1
  17. package/dist/chunks/analytics-service-CF7hSwhy.js.map +1 -0
  18. package/dist/chunks/{analytics-service-CF9AsMQH.cjs → analytics-service-CRs9cpkg.cjs} +53 -1
  19. package/dist/chunks/analytics-service-CRs9cpkg.cjs.map +1 -0
  20. package/dist/chunks/{base-token-service-BiSXWqOy.js → base-token-service-BGuuZX4b.js} +2 -2
  21. package/dist/chunks/{base-token-service-BiSXWqOy.js.map → base-token-service-BGuuZX4b.js.map} +1 -1
  22. package/dist/chunks/{base-token-service-CReAW_nz.cjs → base-token-service-DSye0WD2.cjs} +2 -2
  23. package/dist/chunks/{base-token-service-CReAW_nz.cjs.map → base-token-service-DSye0WD2.cjs.map} +1 -1
  24. package/dist/chunks/{business-membership-service-DXLG5fP9.cjs → business-membership-service-B9ItWZ2_.cjs} +18 -16
  25. package/dist/chunks/business-membership-service-B9ItWZ2_.cjs.map +1 -0
  26. package/dist/chunks/{business-membership-service-IyY5CkL0.js → business-membership-service-CPcE-AW0.js} +18 -16
  27. package/dist/chunks/business-membership-service-CPcE-AW0.js.map +1 -0
  28. package/dist/chunks/{index-8y63MFOX.js → index--OssIds0.js} +48 -22
  29. package/dist/chunks/index--OssIds0.js.map +1 -0
  30. package/dist/chunks/{index-CVuttuU8.cjs → index-C4K-jkRO.cjs} +48 -20
  31. package/dist/chunks/index-C4K-jkRO.cjs.map +1 -0
  32. package/dist/chunks/{pers-sdk-DAllRtm3.js → pers-sdk-Dds2lB27.js} +560 -51
  33. package/dist/chunks/pers-sdk-Dds2lB27.js.map +1 -0
  34. package/dist/chunks/{pers-sdk-CEAAI2U0.cjs → pers-sdk-DemghJ3a.cjs} +561 -50
  35. package/dist/chunks/pers-sdk-DemghJ3a.cjs.map +1 -0
  36. package/dist/chunks/{redemption-service-czBfCP-3.js → redemption-service-DWhZgrZT.js} +9 -1
  37. package/dist/chunks/{redemption-service-czBfCP-3.js.map → redemption-service-DWhZgrZT.js.map} +1 -1
  38. package/dist/chunks/{redemption-service-CVD2PzBO.cjs → redemption-service-Dc_0Kzd0.cjs} +9 -1
  39. package/dist/chunks/{redemption-service-CVD2PzBO.cjs.map → redemption-service-Dc_0Kzd0.cjs.map} +1 -1
  40. package/dist/chunks/{tenant-manager-BdJYwIgL.cjs → tenant-manager-DR5eSEJw.cjs} +2 -2
  41. package/dist/chunks/{tenant-manager-BdJYwIgL.cjs.map → tenant-manager-DR5eSEJw.cjs.map} +1 -1
  42. package/dist/chunks/{tenant-manager-D9ihQLhf.js → tenant-manager-xmYKBFGu.js} +2 -2
  43. package/dist/chunks/{tenant-manager-D9ihQLhf.js.map → tenant-manager-xmYKBFGu.js.map} +1 -1
  44. package/dist/chunks/{token-service-C1xe11OX.cjs → token-service-BJqu5Xap.cjs} +88 -2
  45. package/dist/chunks/token-service-BJqu5Xap.cjs.map +1 -0
  46. package/dist/chunks/{token-service-BxEO5YVN.js → token-service-qRSYG9uT.js} +88 -2
  47. package/dist/chunks/token-service-qRSYG9uT.js.map +1 -0
  48. package/dist/chunks/{web3-chain-service-DRoykR1u.js → web3-chain-service-BWRmwmmJ.js} +2 -2
  49. package/dist/chunks/{web3-chain-service-DRoykR1u.js.map → web3-chain-service-BWRmwmmJ.js.map} +1 -1
  50. package/dist/chunks/{web3-chain-service-CSxlvjMg.cjs → web3-chain-service-D6tBL0F7.cjs} +2 -2
  51. package/dist/chunks/{web3-chain-service-CSxlvjMg.cjs.map → web3-chain-service-D6tBL0F7.cjs.map} +1 -1
  52. package/dist/chunks/{web3-manager-DKHJrBYE.cjs → web3-manager-C-JflQ86.cjs} +4 -4
  53. package/dist/chunks/{web3-manager-DKHJrBYE.cjs.map → web3-manager-C-JflQ86.cjs.map} +1 -1
  54. package/dist/chunks/{web3-manager-NMLZ3pu7.js → web3-manager-Dvcq4xmn.js} +4 -4
  55. package/dist/chunks/{web3-manager-NMLZ3pu7.js.map → web3-manager-Dvcq4xmn.js.map} +1 -1
  56. package/dist/core/auth/default-auth-provider.d.ts +31 -0
  57. package/dist/core/auth/default-auth-provider.d.ts.map +1 -1
  58. package/dist/core/auth/dpop/dpop-manager.d.ts +11 -0
  59. package/dist/core/auth/dpop/dpop-manager.d.ts.map +1 -1
  60. package/dist/core/auth/indexed-db-storage.d.ts.map +1 -1
  61. package/dist/core/auth/refresh-manager.d.ts +34 -2
  62. package/dist/core/auth/refresh-manager.d.ts.map +1 -1
  63. package/dist/core/auth/services/auth-service.d.ts +58 -2
  64. package/dist/core/auth/services/auth-service.d.ts.map +1 -1
  65. package/dist/core/auth/token-storage.d.ts +6 -0
  66. package/dist/core/auth/token-storage.d.ts.map +1 -1
  67. package/dist/core/errors/index.d.ts +6 -6
  68. package/dist/core/errors/index.d.ts.map +1 -1
  69. package/dist/core/events/event-types.d.ts +5 -1
  70. package/dist/core/events/event-types.d.ts.map +1 -1
  71. package/dist/core/pers-api-client.d.ts.map +1 -1
  72. package/dist/core/utils/jwt.function.d.ts +14 -0
  73. package/dist/core/utils/jwt.function.d.ts.map +1 -1
  74. package/dist/core.cjs +9 -7
  75. package/dist/core.cjs.map +1 -1
  76. package/dist/core.js +7 -7
  77. package/dist/index.cjs +10 -8
  78. package/dist/index.cjs.map +1 -1
  79. package/dist/index.d.ts +1 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +8 -8
  82. package/dist/managers/analytics-manager.d.ts +48 -1
  83. package/dist/managers/analytics-manager.d.ts.map +1 -1
  84. package/dist/managers/redemption-manager.d.ts +45 -1
  85. package/dist/managers/redemption-manager.d.ts.map +1 -1
  86. package/dist/managers/token-manager.d.ts +97 -2
  87. package/dist/managers/token-manager.d.ts.map +1 -1
  88. package/dist/node.cjs +7 -7
  89. package/dist/node.js +7 -7
  90. package/dist/package.json +4 -2
  91. package/dist/redemption/services/redemption-service.d.ts +6 -0
  92. package/dist/redemption/services/redemption-service.d.ts.map +1 -1
  93. package/dist/redemption.cjs +1 -1
  94. package/dist/redemption.js +1 -1
  95. package/dist/token/api/token-api.d.ts +41 -1
  96. package/dist/token/api/token-api.d.ts.map +1 -1
  97. package/dist/token/index.d.ts +1 -1
  98. package/dist/token/index.d.ts.map +1 -1
  99. package/dist/token/services/token-service.d.ts +30 -1
  100. package/dist/token/services/token-service.d.ts.map +1 -1
  101. package/dist/token.cjs +2 -2
  102. package/dist/token.js +2 -2
  103. package/dist/user/api/user-api.d.ts.map +1 -1
  104. package/dist/user.cjs +6 -11
  105. package/dist/user.cjs.map +1 -1
  106. package/dist/user.js +6 -11
  107. package/dist/user.js.map +1 -1
  108. package/dist/web3-chain.cjs +2 -2
  109. package/dist/web3-chain.js +2 -2
  110. package/dist/web3-manager.cjs +4 -4
  111. package/dist/web3-manager.js +4 -4
  112. package/dist/web3.cjs +4 -4
  113. package/dist/web3.js +4 -4
  114. package/package.json +4 -2
  115. package/dist/chunks/analytics-service-CF9AsMQH.cjs.map +0 -1
  116. package/dist/chunks/analytics-service-vm7B7LhS.js.map +0 -1
  117. package/dist/chunks/business-membership-service-DXLG5fP9.cjs.map +0 -1
  118. package/dist/chunks/business-membership-service-IyY5CkL0.js.map +0 -1
  119. package/dist/chunks/index-8y63MFOX.js.map +0 -1
  120. package/dist/chunks/index-CVuttuU8.cjs.map +0 -1
  121. package/dist/chunks/pers-sdk-CEAAI2U0.cjs.map +0 -1
  122. package/dist/chunks/pers-sdk-DAllRtm3.js.map +0 -1
  123. package/dist/chunks/token-service-BxEO5YVN.js.map +0 -1
  124. package/dist/chunks/token-service-C1xe11OX.cjs.map +0 -1
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  var persShared = require('@explorins/pers-shared');
4
- var index = require('./index-CVuttuU8.cjs');
4
+ var index = require('./index-C4K-jkRO.cjs');
5
5
  var user = require('../user.cjs');
6
6
  var userStatus = require('../user-status.cjs');
7
- var tokenService = require('./token-service-C1xe11OX.cjs');
8
- var businessMembershipService = require('./business-membership-service-DXLG5fP9.cjs');
7
+ var tokenService = require('./token-service-BJqu5Xap.cjs');
8
+ var businessMembershipService = require('./business-membership-service-B9ItWZ2_.cjs');
9
9
  var campaign = require('../campaign.cjs');
10
- var redemptionService = require('./redemption-service-CVD2PzBO.cjs');
10
+ var redemptionService = require('./redemption-service-Dc_0Kzd0.cjs');
11
11
  var transactionRequest_builder = require('./transaction-request.builder-D8pIzjYD.cjs');
12
12
  var paymentService = require('./payment-service-Bkw7ZXev.cjs');
13
- var tenantManager = require('./tenant-manager-BdJYwIgL.cjs');
13
+ var tenantManager = require('./tenant-manager-DR5eSEJw.cjs');
14
14
  var paginationUtils = require('./pagination-utils-B2jRHMSO.cjs');
15
- var analyticsService = require('./analytics-service-CF9AsMQH.cjs');
15
+ var analyticsService = require('./analytics-service-CRs9cpkg.cjs');
16
16
  var donation = require('../donation.cjs');
17
17
  var triggerSource = require('../trigger-source.cjs');
18
18
 
@@ -239,6 +239,8 @@ function isExtendedProvider(provider) {
239
239
  * TOKEN_EXPIRED is NOT in this list - it's the normal "please refresh" case.
240
240
  *
241
241
  * Values validated against CommonErrorCodes from @explorins/pers-shared at compile time.
242
+ *
243
+ * SINGLE SOURCE OF TRUTH - use this constant everywhere, never duplicate these values.
242
244
  */
243
245
  const FATAL_AUTH_CODES = [
244
246
  'REFRESH_TOKEN_EXPIRED',
@@ -246,6 +248,13 @@ const FATAL_AUTH_CODES = [
246
248
  'INVALID_TOKEN',
247
249
  'TOKEN_REVOKED'
248
250
  ];
251
+ /**
252
+ * Check if an error message contains any fatal auth error code.
253
+ * Useful for detecting fatal errors in wrapped/stringified errors.
254
+ */
255
+ function isFatalAuthErrorInMessage(message) {
256
+ return FATAL_AUTH_CODES.some(code => message.includes(code));
257
+ }
249
258
  /**
250
259
  * Platform-agnostic authentication service
251
260
  * Handles login, token refresh, and storage operations
@@ -401,8 +410,58 @@ class AuthService {
401
410
  return FATAL_AUTH_CODES.includes(errorCode);
402
411
  }
403
412
  /**
404
- * Handle authentication failure with proper cleanup.
405
- * Called when backend returns fatal auth error codes or refresh fails.
413
+ * Comprehensive check if an error represents a DEFINITIVE auth failure.
414
+ * This is the SINGLE SOURCE OF TRUTH for determining if logout is required.
415
+ *
416
+ * Returns true for:
417
+ * - Known fatal error codes (REFRESH_TOKEN_EXPIRED, TOKEN_REVOKED, etc.)
418
+ * - Fatal error codes in error messages (wrapped errors)
419
+ *
420
+ * Returns false for (retryable/recoverable):
421
+ * - Network errors
422
+ * - Timeouts
423
+ * - 5xx server errors
424
+ * - TOKEN_EXPIRED (normal refresh trigger)
425
+ * - Raw 401/403 without fatal code (should try refresh first)
426
+ *
427
+ * NOTE: We intentionally DO NOT treat raw 401/403 status as fatal.
428
+ * The HTTP client handles 401 by attempting token refresh first.
429
+ * Only if refresh fails WITH a fatal code do we logout.
430
+ */
431
+ isDefinitiveAuthFailure(error) {
432
+ if (!error)
433
+ return false;
434
+ const errorAny = error;
435
+ // Check error code property against canonical list
436
+ if (errorAny?.code && this.isFatalAuthError(errorAny.code)) {
437
+ return true;
438
+ }
439
+ // Check if error message contains fatal keywords (fallback for wrapped errors)
440
+ const errorMessage = error instanceof Error ? error.message : String(error);
441
+ if (isFatalAuthErrorInMessage(errorMessage)) {
442
+ return true;
443
+ }
444
+ // NOTE: We intentionally DO NOT check HTTP status codes here.
445
+ // A raw 401/403 without a fatal error code should trigger a refresh attempt,
446
+ // not an immediate logout. The HTTP client (pers-api-client.ts) handles this:
447
+ // 1. 401 received → try refresh token
448
+ // 2. Refresh succeeds → retry original request
449
+ // 3. Refresh fails with fatal CODE → then logout
450
+ // Everything else is considered retryable
451
+ return false;
452
+ }
453
+ /**
454
+ * Handle authentication failure with provider token recovery.
455
+ *
456
+ * Before logging out, attempts to recover the session using the stored
457
+ * provider token (Firebase JWT, etc.) if it's still valid.
458
+ *
459
+ * Recovery Flow:
460
+ * 1. Check if provider token exists and is not expired
461
+ * 2. Get the auth type (USER, BUSINESS, TENANT) to know which login to use
462
+ * 3. Attempt re-authentication with provider token
463
+ * 4. If successful: Session recovered, stay logged in
464
+ * 5. If failed: Clear tokens and emit AUTH_FAILED
406
465
  */
407
466
  async handleAuthFailure() {
408
467
  // Already failed - nothing to do
@@ -418,7 +477,7 @@ class AuthService {
418
477
  return await this.activeAuthFailurePromise;
419
478
  }
420
479
  // Create the failure promise to deduplicate concurrent calls
421
- this.activeAuthFailurePromise = this.performAuthFailure();
480
+ this.activeAuthFailurePromise = this.performAuthFailureWithRecovery();
422
481
  try {
423
482
  await this.activeAuthFailurePromise;
424
483
  }
@@ -426,6 +485,77 @@ class AuthService {
426
485
  this.activeAuthFailurePromise = null;
427
486
  }
428
487
  }
488
+ /**
489
+ * Attempts to recover session using provider token before failing.
490
+ */
491
+ async performAuthFailureWithRecovery() {
492
+ // Clear active refresh operations
493
+ this.activeRefreshPromise = null;
494
+ // Try to recover using provider token
495
+ const recovered = await this.attemptProviderTokenRecovery();
496
+ if (recovered) {
497
+ return; // Session recovered, don't fail
498
+ }
499
+ // Recovery failed - perform actual logout
500
+ await this.performAuthFailure();
501
+ }
502
+ /**
503
+ * Attempt to recover session using stored provider token.
504
+ * Extracts context (tenantId/businessId) from the current refresh token to ensure
505
+ * the recovered session maintains the same context.
506
+ *
507
+ * @returns true if session was successfully recovered
508
+ */
509
+ async attemptProviderTokenRecovery() {
510
+ const extended = this.extendedProvider;
511
+ if (!extended?.getProviderToken || !extended?.getAuthType) {
512
+ return false; // Provider doesn't support recovery
513
+ }
514
+ try {
515
+ const providerToken = await extended.getProviderToken();
516
+ if (!providerToken) {
517
+ return false;
518
+ }
519
+ // Check if provider token is still valid (with small margin)
520
+ if (index.isTokenExpired(providerToken, 30)) {
521
+ return false;
522
+ }
523
+ const authType = await extended.getAuthType();
524
+ if (!authType) {
525
+ return false;
526
+ }
527
+ // Extract context (tenantId/businessId) from the current refresh token
528
+ // This ensures multi-tenant/multi-business users recover to the same context
529
+ const refreshToken = await this.authProvider?.getRefreshToken?.();
530
+ const contextClaims = refreshToken ? index.decodeJwtPayload(refreshToken) : null;
531
+ // Re-authenticate using provider token based on auth type
532
+ // Pass the context from the previous session to avoid MULTIPLE_CONTEXT_SELECTION_REQUIRED
533
+ // Note: In AuthJWTPayload, when accountType=BUSINESS, sub contains the businessId
534
+ switch (authType) {
535
+ case persShared.AccountOwnerType.USER:
536
+ await this.loginUser(providerToken);
537
+ break;
538
+ case persShared.AccountOwnerType.BUSINESS:
539
+ // For BUSINESS auth, sub contains the businessId when accountType is BUSINESS
540
+ const businessId = contextClaims?.accountType === persShared.AccountOwnerType.BUSINESS
541
+ ? contextClaims.sub
542
+ : undefined;
543
+ await this.loginBusiness(providerToken, businessId ? { businessId } : undefined);
544
+ break;
545
+ case persShared.AccountOwnerType.TENANT:
546
+ await this.loginTenantAdmin(providerToken, contextClaims?.tenantId ? { tenantId: contextClaims.tenantId } : undefined);
547
+ break;
548
+ default:
549
+ console.warn(`[AuthService] Unknown auth type for recovery: ${authType}`);
550
+ return false;
551
+ }
552
+ return true; // Recovery successful
553
+ }
554
+ catch (error) {
555
+ console.warn('[AuthService] Provider token recovery failed:', error);
556
+ return false;
557
+ }
558
+ }
429
559
  /**
430
560
  * Performs the actual auth failure cleanup
431
561
  */
@@ -505,16 +635,32 @@ class AuthService {
505
635
 
506
636
  /**
507
637
  * Token Refresh Manager
508
- * Simple container for token refresh logic with better organization
638
+ * Industry-standard token lifecycle management with:
639
+ * - Dynamic refresh margin based on token lifetime
640
+ * - Retry logic for transient failures
641
+ * - Only logout on definitive auth failures (not network errors)
509
642
  */
510
643
  class TokenRefreshManager {
511
644
  constructor(authService, authProvider) {
512
645
  this.authService = authService;
513
646
  this.authProvider = authProvider;
514
- this.tokenRefreshMarginSeconds = 120; // 2 minutes
647
+ // Industry standard: refresh when 25% of token lifetime remains
648
+ this.REFRESH_THRESHOLD_PERCENT = 0.25;
649
+ // Minimum margin regardless of token lifetime
650
+ this.MIN_REFRESH_MARGIN_SECONDS = 30;
651
+ // Maximum margin (cap for very long-lived tokens)
652
+ this.MAX_REFRESH_MARGIN_SECONDS = 300; // 5 minutes
653
+ // Validation cache to avoid redundant checks
515
654
  this.lastValidationTime = 0;
516
655
  this.validationCacheDurationMs = 30000; // 30 seconds
656
+ // Retry configuration for transient failures
657
+ this.MAX_RETRY_ATTEMPTS = 3;
658
+ this.RETRY_DELAY_MS = 1000;
517
659
  }
660
+ /**
661
+ * Ensures the access token is valid, refreshing if needed.
662
+ * Uses dynamic margin based on token lifetime (industry standard).
663
+ */
518
664
  async ensureValidToken() {
519
665
  const now = Date.now();
520
666
  // Skip validation if we checked recently (within cache duration)
@@ -526,35 +672,95 @@ class TokenRefreshManager {
526
672
  if (!token) {
527
673
  return;
528
674
  }
529
- if (index.isTokenExpired(token, this.tokenRefreshMarginSeconds)) {
530
- const refreshSuccess = await this.attemptInternalRefresh();
531
- if (!refreshSuccess) {
675
+ // Calculate dynamic refresh margin based on token lifetime
676
+ const marginSeconds = this.calculateRefreshMargin(token);
677
+ if (index.isTokenExpired(token, marginSeconds)) {
678
+ const result = await this.attemptRefreshWithRetry();
679
+ if (!result.success) {
680
+ if (result.retryable) {
681
+ // Transient error - don't logout, let next request retry
682
+ console.warn('[TokenRefreshManager] Refresh failed with retryable error, will retry on next request');
683
+ return;
684
+ }
685
+ // Definitive auth failure - logout required
532
686
  await this.authService.handleAuthFailure();
533
687
  }
534
688
  else {
535
- // Update validation time on successful refresh
689
+ // Refresh successful
536
690
  this.lastValidationTime = now;
537
691
  }
538
692
  }
539
693
  else {
540
- // Token is valid, update validation time
694
+ // Token is valid
541
695
  this.lastValidationTime = now;
542
696
  }
543
697
  }
544
698
  catch (error) {
545
- await this.authService.handleAuthFailure();
699
+ // Unexpected error - log but don't logout (conservative approach)
700
+ console.error('[TokenRefreshManager] Unexpected error during token validation:', error);
546
701
  }
547
702
  }
548
- async attemptInternalRefresh() {
549
- try {
550
- await this.authService.refreshAccessToken();
551
- return true;
703
+ /**
704
+ * Calculate refresh margin dynamically based on token lifetime.
705
+ * Industry standard: refresh when 25% of lifetime remains.
706
+ */
707
+ calculateRefreshMargin(token) {
708
+ const ttlSeconds = index.getTokenTimeToLive(token);
709
+ if (ttlSeconds === null || ttlSeconds <= 0) {
710
+ // Can't determine TTL, use minimum margin
711
+ return this.MIN_REFRESH_MARGIN_SECONDS;
552
712
  }
553
- catch (error) {
554
- // Don't call handleAuthFailure here - let the caller decide
555
- // This prevents duplicate auth failure handling in race conditions
556
- return false;
713
+ // Calculate 25% of remaining lifetime
714
+ const dynamicMargin = Math.floor(ttlSeconds * this.REFRESH_THRESHOLD_PERCENT);
715
+ // Clamp between min and max
716
+ return Math.max(this.MIN_REFRESH_MARGIN_SECONDS, Math.min(dynamicMargin, this.MAX_REFRESH_MARGIN_SECONDS));
717
+ }
718
+ /**
719
+ * Attempt token refresh with retry logic for transient failures.
720
+ * Only gives up on definitive auth errors.
721
+ */
722
+ async attemptRefreshWithRetry() {
723
+ let lastError;
724
+ for (let attempt = 1; attempt <= this.MAX_RETRY_ATTEMPTS; attempt++) {
725
+ try {
726
+ await this.authService.refreshAccessToken();
727
+ return { success: true, retryable: false };
728
+ }
729
+ catch (error) {
730
+ lastError = error instanceof Error ? error : new Error(String(error));
731
+ // Check if this is a definitive auth failure (non-retryable)
732
+ if (this.isDefinitiveAuthFailure(error)) {
733
+ console.warn(`[TokenRefreshManager] Definitive auth failure: ${lastError.message}`);
734
+ return { success: false, retryable: false, error: lastError };
735
+ }
736
+ // Retryable error - wait and try again
737
+ if (attempt < this.MAX_RETRY_ATTEMPTS) {
738
+ await this.delay(this.RETRY_DELAY_MS * attempt); // Exponential backoff
739
+ }
740
+ }
557
741
  }
742
+ // All retries exhausted - return as retryable so we don't logout
743
+ console.warn(`[TokenRefreshManager] All ${this.MAX_RETRY_ATTEMPTS} refresh attempts failed`);
744
+ return { success: false, retryable: true, error: lastError };
745
+ }
746
+ /**
747
+ * Determine if an error is a definitive auth failure that requires logout.
748
+ * Network errors, timeouts, 5xx errors are NOT definitive - they're retryable.
749
+ *
750
+ * Delegates to AuthService.isDefinitiveAuthFailure() - the SINGLE SOURCE OF TRUTH.
751
+ */
752
+ isDefinitiveAuthFailure(error) {
753
+ return this.authService.isDefinitiveAuthFailure(error);
754
+ }
755
+ delay(ms) {
756
+ return new Promise(resolve => setTimeout(resolve, ms));
757
+ }
758
+ /**
759
+ * @deprecated Use ensureValidToken() instead
760
+ */
761
+ async attemptInternalRefresh() {
762
+ const result = await this.attemptRefreshWithRetry();
763
+ return result.success;
558
764
  }
559
765
  }
560
766
 
@@ -702,6 +908,22 @@ class AuthTokenManager {
702
908
  this.cache = {};
703
909
  await this.storage.clear();
704
910
  }
911
+ /**
912
+ * Clears only auth tokens (access, refresh, provider, authType), preserving DPoP keys.
913
+ * Use this when DPoP keys are regenerated - we want to invalidate auth tokens
914
+ * that were bound to old keys, but keep the newly generated DPoP keys.
915
+ */
916
+ async clearAuthTokens() {
917
+ // Clear cache
918
+ this.cache = {};
919
+ // Remove all auth tokens and auth type, NOT DPoP keys
920
+ await Promise.all([
921
+ this.storage.remove(AUTH_STORAGE_KEYS.ACCESS_TOKEN),
922
+ this.storage.remove(AUTH_STORAGE_KEYS.REFRESH_TOKEN),
923
+ this.storage.remove(AUTH_STORAGE_KEYS.PROVIDER_TOKEN),
924
+ this.storage.remove(AUTH_STORAGE_KEYS.AUTH_TYPE),
925
+ ]);
926
+ }
705
927
  async hasAccessToken() {
706
928
  // Use cached value if available to avoid storage read
707
929
  if (this.cache.accessToken !== undefined) {
@@ -732,13 +954,12 @@ class IndexedDBTokenStorage {
732
954
  constructor() {
733
955
  this.supportsObjects = true;
734
956
  this.dbPromise = null;
735
- if (typeof indexedDB === 'undefined') {
736
- console.warn('IndexedDB is not available in this environment');
737
- }
957
+ // IndexedDB availability is checked lazily on first operation
738
958
  }
739
959
  getDB() {
740
- if (this.dbPromise)
960
+ if (this.dbPromise) {
741
961
  return this.dbPromise;
962
+ }
742
963
  this.dbPromise = new Promise((resolve, reject) => {
743
964
  if (typeof indexedDB === 'undefined') {
744
965
  return reject(new Error('IndexedDB not supported'));
@@ -766,8 +987,12 @@ class IndexedDBTokenStorage {
766
987
  const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readonly');
767
988
  const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
768
989
  const request = store.get(key);
769
- request.onsuccess = () => resolve(request.result || null);
770
- request.onerror = () => reject(request.error);
990
+ request.onsuccess = () => {
991
+ resolve(request.result || null);
992
+ };
993
+ request.onerror = () => {
994
+ reject(request.error);
995
+ };
771
996
  });
772
997
  }
773
998
  catch (e) {
@@ -963,6 +1188,8 @@ class DPoPManager {
963
1188
  constructor(storage, cryptoProvider, callbacks) {
964
1189
  this.storage = storage;
965
1190
  this.memoryKeyPair = null;
1191
+ /** Pending key pair promise to prevent concurrent generation race conditions */
1192
+ this.pendingKeyPair = null;
966
1193
  this.cryptoProvider = cryptoProvider || new WebDPoPCryptoProvider();
967
1194
  this.callbacks = callbacks || {};
968
1195
  }
@@ -971,8 +1198,9 @@ class DPoPManager {
971
1198
  * Useful for detecting if a key regeneration would be needed.
972
1199
  */
973
1200
  async hasStoredKeys() {
974
- if (this.memoryKeyPair)
1201
+ if (this.memoryKeyPair) {
975
1202
  return true;
1203
+ }
976
1204
  const storedPublic = await this.storage.get(DPOP_STORAGE_KEYS.PUBLIC);
977
1205
  const storedPrivate = await this.storage.get(DPOP_STORAGE_KEYS.PRIVATE);
978
1206
  return !!(storedPublic && storedPrivate);
@@ -986,8 +1214,35 @@ class DPoPManager {
986
1214
  * match refresh token binding" errors.
987
1215
  */
988
1216
  async ensureKeyPair() {
989
- if (this.memoryKeyPair)
1217
+ // Fast path: already have keys in memory
1218
+ if (this.memoryKeyPair) {
1219
+ return this.memoryKeyPair;
1220
+ }
1221
+ // Prevent concurrent key generation race condition:
1222
+ // If another call is already generating keys, wait for it instead of starting a new one
1223
+ if (this.pendingKeyPair) {
1224
+ return this.pendingKeyPair;
1225
+ }
1226
+ // Create a promise that subsequent callers will wait on
1227
+ this.pendingKeyPair = this.loadOrGenerateKeyPair();
1228
+ try {
1229
+ const keyPair = await this.pendingKeyPair;
1230
+ return keyPair;
1231
+ }
1232
+ finally {
1233
+ // Clear pending promise so future calls can proceed
1234
+ this.pendingKeyPair = null;
1235
+ }
1236
+ }
1237
+ /**
1238
+ * Internal method that actually loads or generates keys.
1239
+ * Called only once per concurrent batch of ensureKeyPair() calls.
1240
+ */
1241
+ async loadOrGenerateKeyPair() {
1242
+ // Double-check in case keys were set while waiting
1243
+ if (this.memoryKeyPair) {
990
1244
  return this.memoryKeyPair;
1245
+ }
991
1246
  const storedPublic = await this.storage.get(DPOP_STORAGE_KEYS.PUBLIC);
992
1247
  const storedPrivate = await this.storage.get(DPOP_STORAGE_KEYS.PRIVATE);
993
1248
  if (storedPublic && storedPrivate) {
@@ -999,18 +1254,23 @@ class DPoPManager {
999
1254
  return this.memoryKeyPair;
1000
1255
  }
1001
1256
  catch (e) {
1002
- console.warn('[DPoPManager] Corrupted DPoP keys in storage, regenerating.');
1257
+ console.warn('[DPoPManager] Corrupted DPoP keys in storage, regenerating');
1003
1258
  }
1004
1259
  }
1005
1260
  // Generate new key pair
1006
1261
  // IMPORTANT: This invalidates any existing refresh tokens bound to the old keys
1007
- console.info('[DPoPManager] Generating new DPoP key pair');
1008
- // Adaptation: If storage supports raw objects (like IndexedDB or Native Keychain),
1262
+ // Adaptation: If storage supports raw objects (like Native Keychain on iOS/Android),
1009
1263
  // we can generate Non-Extractable keys for maximum security.
1010
- // If storage is text-only (LocalStorage), we must use Extractable keys to serialize them.
1264
+ // If storage is text-only (LocalStorage) OR IndexedDB (CryptoKey doesn't survive reload),
1265
+ // we must use Extractable keys to serialize them as JWK.
1266
+ //
1267
+ // NOTE: IndexedDB CAN store CryptoKey objects, but they don't survive page reloads -
1268
+ // they come back as empty/corrupted objects. Only native secure storage can persist
1269
+ // non-extractable keys. Frontend should set supportsObjects: false for web storage.
1011
1270
  const useHighSecurity = !!this.storage.supportsObjects;
1271
+ const extractable = !useHighSecurity;
1012
1272
  const keyPair = await this.cryptoProvider.generateKeyPair({
1013
- extractable: !useHighSecurity
1273
+ extractable
1014
1274
  });
1015
1275
  // Save to storage
1016
1276
  await this.storage.set(DPOP_STORAGE_KEYS.PUBLIC, keyPair.publicKey);
@@ -1044,6 +1304,20 @@ class DPoPManager {
1044
1304
  }
1045
1305
  return this.cryptoProvider.signProof(payload, keyPair);
1046
1306
  }
1307
+ /**
1308
+ * Generate a short fingerprint of the public key for debugging
1309
+ * This helps identify if the same key is being used across operations
1310
+ */
1311
+ /* private getPublicKeyFingerprint(publicKey: JsonWebKey): string {
1312
+ try {
1313
+ // Use the 'x' coordinate of the EC key as a fingerprint
1314
+ const x = publicKey.x || '';
1315
+ const y = publicKey.y || '';
1316
+ return `${x.substring(0, 8)}...${y.substring(0, 4)}`;
1317
+ } catch {
1318
+ return 'unknown';
1319
+ }
1320
+ } */
1047
1321
  /**
1048
1322
  * Clears DPoP keys (e.g. on logout)
1049
1323
  */
@@ -1092,13 +1366,15 @@ class DefaultAuthProvider {
1092
1366
  this.tokenManager = new AuthTokenManager(storage);
1093
1367
  if (this.dpopEnabled) {
1094
1368
  this.dpopManager = new DPoPManager(storage, config.dpop?.cryptoProvider, {
1095
- // When DPoP keys are regenerated (corrupted/missing), the refresh token
1096
- // bound to the old keys becomes invalid. Clear it to prevent
1097
- // "DPoP proof does not match refresh token binding" errors.
1369
+ // When DPoP keys are regenerated (corrupted/missing), auth tokens
1370
+ // bound to the old keys become invalid. Clear only auth tokens, NOT DPoP keys.
1371
+ // Access token has cnf claim binding it to the old public key.
1372
+ // Refresh token has DPoP binding to the old key pair.
1373
+ // NOTE: Do NOT call clearAllTokens() here - it would wipe the freshly saved DPoP keys!
1098
1374
  onKeysRegenerated: () => {
1099
- console.info('[DefaultAuthProvider] DPoP keys regenerated, clearing invalid refresh token');
1100
- this.tokenManager.clearRefreshToken().catch(err => {
1101
- console.warn('[DefaultAuthProvider] Failed to clear refresh token:', err);
1375
+ // Clear only auth tokens, preserve DPoP keys (they were just regenerated and saved)
1376
+ this.tokenManager.clearAuthTokens().catch(err => {
1377
+ console.warn('[DefaultAuthProvider] Failed to clear tokens after key regeneration:', err);
1102
1378
  });
1103
1379
  }
1104
1380
  });
@@ -1240,6 +1516,41 @@ class DefaultAuthProvider {
1240
1516
  return null;
1241
1517
  }
1242
1518
  }
1519
+ /**
1520
+ * Store the provider token (Firebase JWT, Auth0 token, etc.)
1521
+ * Used for session recovery when refresh tokens expire.
1522
+ *
1523
+ * NOTE: This is called AUTOMATICALLY by loginWithToken(), so you typically
1524
+ * don't need to call this manually. The only reason to call this is if:
1525
+ *
1526
+ * 1. Your auth provider (Firebase, Auth0) refreshes tokens in the background
1527
+ * 2. You want to proactively update the stored token for better recovery chances
1528
+ *
1529
+ * The stored provider token is used as a fallback when:
1530
+ * - PERS refresh token expires (e.g., after 30 days of inactivity)
1531
+ * - SDK attempts recovery using the stored provider token
1532
+ * - If provider token is still valid → session recovered automatically
1533
+ * - If provider token is also expired → user must re-authenticate
1534
+ *
1535
+ * @example
1536
+ * // Optional: Update stored token when Firebase refreshes it
1537
+ * firebase.auth().onIdTokenChanged(async (user) => {
1538
+ * if (user) {
1539
+ * const freshToken = await user.getIdToken();
1540
+ * await sdk.auth.setProviderToken(freshToken);
1541
+ * }
1542
+ * });
1543
+ */
1544
+ async setProviderToken(token) {
1545
+ await this.tokenManager.setProviderToken(token);
1546
+ }
1547
+ /**
1548
+ * Get the stored provider token (Firebase JWT, Auth0 token, etc.)
1549
+ * Used internally for session recovery when refresh tokens expire.
1550
+ */
1551
+ async getProviderToken() {
1552
+ return this.tokenManager.getProviderToken();
1553
+ }
1243
1554
  }
1244
1555
 
1245
1556
  /**
@@ -1255,7 +1566,7 @@ class DefaultAuthProvider {
1255
1566
  /** SDK package name */
1256
1567
  const SDK_NAME = "@explorins/pers-sdk";
1257
1568
  /** SDK version - injected from package.json at build time */
1258
- const SDK_VERSION = "2.1.33-alpha.0";
1569
+ const SDK_VERSION = "2.1.37";
1259
1570
  /** Full SDK identifier for headers */
1260
1571
  const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
1261
1572
 
@@ -1404,7 +1715,9 @@ class PersApiClient {
1404
1715
  // Handle 401 errors centrally through AuthService
1405
1716
  if (status === 401) {
1406
1717
  const backendError = index.ErrorUtils.extractBackendErrorDetails(error);
1407
- // Check for fatal auth errors that require immediate logout
1718
+ // Check for FATAL auth error CODES (not just 401 status)
1719
+ // Fatal codes: REFRESH_TOKEN_EXPIRED, TOKEN_REVOKED, etc. → immediate logout
1720
+ // Non-fatal codes: TOKEN_EXPIRED → try refresh first
1408
1721
  if (this.authService.isFatalAuthError(backendError.code)) {
1409
1722
  // Definitive auth failure - no retry, session is invalid
1410
1723
  await this.authService.handleAuthFailure();
@@ -1464,9 +1777,9 @@ class PersApiClient {
1464
1777
  }); */
1465
1778
  this._events?.emitError({
1466
1779
  domain: errorDetails.domain || 'external',
1467
- type: errorDetails.code || 'API_ERROR',
1468
- userMessage: errorDetails.message || errorMessage, // Event system uses userMessage for display
1469
- code: errorDetails.code,
1780
+ type: 'api_error', // Event type discriminator (fixed value)
1781
+ userMessage: errorDetails.message || errorMessage,
1782
+ code: errorDetails.code, // Actual error code from backend
1470
1783
  details: {
1471
1784
  endpoint,
1472
1785
  method,
@@ -3102,6 +3415,107 @@ class TokenManager {
3102
3415
  async getTokenByContract(contractAddress, contractTokenId = null) {
3103
3416
  return this.tokenService.getTokenByContractAddress(contractAddress, contractTokenId);
3104
3417
  }
3418
+ /**
3419
+ * Get all token metadata with filtering and pagination
3420
+ *
3421
+ * Retrieves token metadata (rewards/stamps) with server-side filtering and pagination.
3422
+ * Useful for displaying rewards (ERC1155) or stamps (ERC721) in admin tables.
3423
+ * Non-admin users always get active metadata only.
3424
+ *
3425
+ * @param options - Filter and pagination options
3426
+ * @returns Promise resolving to paginated token metadata
3427
+ *
3428
+ * @example Get paginated rewards
3429
+ * ```typescript
3430
+ * const rewards = await sdk.tokens.getTokenMetadata({
3431
+ * tokenType: 'ERC1155',
3432
+ * active: true,
3433
+ * page: 1,
3434
+ * limit: 10,
3435
+ * sortBy: 'name',
3436
+ * sortOrder: 'ASC'
3437
+ * });
3438
+ *
3439
+ * console.log(`Page 1 of ${rewards.pagination.pages}`);
3440
+ * rewards.data.forEach(r => console.log(r.name));
3441
+ * ```
3442
+ *
3443
+ * @example Get stamps with search
3444
+ * ```typescript
3445
+ * const stamps = await sdk.tokens.getTokenMetadata({
3446
+ * tokenType: 'ERC721',
3447
+ * search: 'gold',
3448
+ * limit: 25
3449
+ * });
3450
+ * ```
3451
+ */
3452
+ async getTokenMetadata(options) {
3453
+ return this.tokenService.getTokenMetadata(options);
3454
+ }
3455
+ /**
3456
+ * Get rewards (ERC1155 token metadata) with filtering and pagination
3457
+ *
3458
+ * Convenience method for fetching reward metadata. Rewards are ERC1155 tokens
3459
+ * that represent collectible items, vouchers, or other redeemable rewards.
3460
+ *
3461
+ * @param options - Filter and pagination options (tokenType is set automatically)
3462
+ * @returns Promise resolving to paginated reward metadata
3463
+ *
3464
+ * @example Get active rewards
3465
+ * ```typescript
3466
+ * const rewards = await sdk.tokens.getRewards({
3467
+ * active: true,
3468
+ * limit: 10
3469
+ * });
3470
+ *
3471
+ * console.log(`Found ${rewards.pagination.total} rewards`);
3472
+ * rewards.data.forEach(r => console.log(`${r.name}: ${r.description}`));
3473
+ * ```
3474
+ *
3475
+ * @example Search rewards
3476
+ * ```typescript
3477
+ * const discountRewards = await sdk.tokens.getRewards({
3478
+ * search: 'discount',
3479
+ * sortBy: 'name',
3480
+ * sortOrder: 'ASC'
3481
+ * });
3482
+ * ```
3483
+ */
3484
+ async getRewards(options) {
3485
+ return this.tokenService.getRewards(options);
3486
+ }
3487
+ /**
3488
+ * Get stamps (ERC721 token metadata) with filtering and pagination
3489
+ *
3490
+ * Convenience method for fetching stamp metadata. Stamps are ERC721 tokens
3491
+ * that represent achievements, badges, or collectible status markers.
3492
+ *
3493
+ * @param options - Filter and pagination options (tokenType is set automatically)
3494
+ * @returns Promise resolving to paginated stamp metadata
3495
+ *
3496
+ * @example Get active stamps
3497
+ * ```typescript
3498
+ * const stamps = await sdk.tokens.getStamps({
3499
+ * active: true,
3500
+ * limit: 10
3501
+ * });
3502
+ *
3503
+ * console.log(`Found ${stamps.pagination.total} stamps`);
3504
+ * stamps.data.forEach(s => console.log(`${s.name}: ${s.description}`));
3505
+ * ```
3506
+ *
3507
+ * @example Filter stamps by tags
3508
+ * ```typescript
3509
+ * const goldStamps = await sdk.tokens.getStamps({
3510
+ * tags: ['gold', 'premium'],
3511
+ * sortBy: 'createdAt',
3512
+ * sortOrder: 'DESC'
3513
+ * });
3514
+ * ```
3515
+ */
3516
+ async getStamps(options) {
3517
+ return this.tokenService.getStamps(options);
3518
+ }
3105
3519
  /**
3106
3520
  * Admin: Create new token
3107
3521
  *
@@ -5006,6 +5420,52 @@ class RedemptionManager {
5006
5420
  async getRedemptions(options) {
5007
5421
  return this.redemptionService.getRedemptions(options);
5008
5422
  }
5423
+ /**
5424
+ * Get a specific redemption by ID
5425
+ *
5426
+ * Retrieves detailed information about a specific redemption offer.
5427
+ * Useful when you already have the redemption ID and need full details.
5428
+ *
5429
+ * @param id - Redemption UUID
5430
+ * @param include - Optional relations to include: 'redeemCount'
5431
+ * @returns Promise resolving to the redemption
5432
+ *
5433
+ * @example Basic usage
5434
+ * ```typescript
5435
+ * const redemption = await sdk.redemptions.getRedemptionById('7baf4d39-0bd6-45ca-9c66-8c0d655767c3');
5436
+ * console.log(redemption.name);
5437
+ * console.log(redemption.description);
5438
+ * ```
5439
+ *
5440
+ * @example With redeem count
5441
+ * ```typescript
5442
+ * const redemption = await sdk.redemptions.getRedemptionById(
5443
+ * 'redemption-123',
5444
+ * ['redeemCount']
5445
+ * );
5446
+ * console.log(`${redemption.name} has been redeemed ${redemption.redeemCount} times`);
5447
+ * ```
5448
+ *
5449
+ * @example Get redemption details for display
5450
+ * ```typescript
5451
+ * const redemption = await sdk.redemptions.getRedemptionById(id);
5452
+ *
5453
+ * console.log('Redemption Details:');
5454
+ * console.log(`Name: ${redemption.name}`);
5455
+ * console.log(`Description: ${redemption.description}`);
5456
+ * console.log(`Active: ${redemption.isActive}`);
5457
+ *
5458
+ * if (redemption.tokenUnits?.length) {
5459
+ * console.log('Cost:');
5460
+ * redemption.tokenUnits.forEach(unit => {
5461
+ * console.log(` ${unit.amount} ${unit.token.symbol}`);
5462
+ * });
5463
+ * }
5464
+ * ```
5465
+ */
5466
+ async getRedemptionById(id, include) {
5467
+ return this.redemptionService.getRedemptionById(id, include);
5468
+ }
5009
5469
  /**
5010
5470
  * Get available redemption types
5011
5471
  *
@@ -7531,6 +7991,55 @@ class AnalyticsManager {
7531
7991
  async getCampaignClaimAnalytics(request) {
7532
7992
  return this.analyticsService.getCampaignClaimAnalytics(request);
7533
7993
  }
7994
+ /**
7995
+ * Get redemption redeem analytics with aggregation
7996
+ *
7997
+ * Retrieves aggregated redemption redeem data with flexible grouping and metrics.
7998
+ * Perfect for charts, dashboards, and redeem pattern analysis across redemptions,
7999
+ * businesses, and time periods.
8000
+ *
8001
+ * @param request - Analytics request with filters, groupBy, and metrics
8002
+ * @returns Promise resolving to redemption redeem analytics data
8003
+ *
8004
+ * @example Redeems per redemption
8005
+ * ```typescript
8006
+ * const analytics = await sdk.analytics.getRedemptionRedeemAnalytics({
8007
+ * groupBy: ['redemptionId'],
8008
+ * metrics: ['count'],
8009
+ * sortBy: 'count',
8010
+ * sortOrder: 'DESC',
8011
+ * limit: 10
8012
+ * });
8013
+ *
8014
+ * console.log('Top redemptions by redeems:', analytics.results);
8015
+ * ```
8016
+ *
8017
+ * @example Redeems by status over time
8018
+ * ```typescript
8019
+ * const analytics = await sdk.analytics.getRedemptionRedeemAnalytics({
8020
+ * groupBy: ['month', 'status'],
8021
+ * metrics: ['count'],
8022
+ * sortBy: 'month',
8023
+ * sortOrder: 'DESC',
8024
+ * startDate: new Date('2026-01-01'),
8025
+ * endDate: new Date('2026-12-31')
8026
+ * });
8027
+ * ```
8028
+ *
8029
+ * @example Redeems by business
8030
+ * ```typescript
8031
+ * const analytics = await sdk.analytics.getRedemptionRedeemAnalytics({
8032
+ * filters: { status: 'COMPLETED' },
8033
+ * groupBy: ['businessId'],
8034
+ * metrics: ['count'],
8035
+ * sortBy: 'count',
8036
+ * sortOrder: 'DESC'
8037
+ * });
8038
+ * ```
8039
+ */
8040
+ async getRedemptionRedeemAnalytics(request) {
8041
+ return this.analyticsService.getRedemptionRedeemAnalytics(request);
8042
+ }
7534
8043
  /**
7535
8044
  * Get user analytics with engagement metrics
7536
8045
  *
@@ -10327,6 +10836,7 @@ exports.DPOP_STORAGE_KEYS = DPOP_STORAGE_KEYS;
10327
10836
  exports.DPoPManager = DPoPManager;
10328
10837
  exports.DefaultAuthProvider = DefaultAuthProvider;
10329
10838
  exports.DonationManager = DonationManager;
10839
+ exports.FATAL_AUTH_CODES = FATAL_AUTH_CODES;
10330
10840
  exports.FileApi = FileApi;
10331
10841
  exports.FileManager = FileManager;
10332
10842
  exports.FileService = FileService;
@@ -10357,5 +10867,6 @@ exports.buildApiRoot = buildApiRoot;
10357
10867
  exports.buildWalletEventsWsUrl = buildWalletEventsWsUrl;
10358
10868
  exports.createPersEventsClient = createPersEventsClient;
10359
10869
  exports.createPersSDK = createPersSDK;
10870
+ exports.isFatalAuthErrorInMessage = isFatalAuthErrorInMessage;
10360
10871
  exports.mergeWithDefaults = mergeWithDefaults;
10361
- //# sourceMappingURL=pers-sdk-CEAAI2U0.cjs.map
10872
+ //# sourceMappingURL=pers-sdk-DemghJ3a.cjs.map