@explorins/pers-sdk-react-native 2.1.2 → 2.1.3

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.
package/dist/index.js CHANGED
@@ -6828,8 +6828,7 @@ const DEFAULT_PERS_CONFIG = {
6828
6828
  timeout: 30000,
6829
6829
  retries: 3,
6830
6830
  tokenRefreshMargin: 60, // Refresh tokens 60 seconds before expiry
6831
- backgroundRefreshThreshold: 30, // Use background refresh if >30s remaining
6832
- autoRestoreSession: true // Automatically restore session on initialization
6831
+ backgroundRefreshThreshold: 30 // Use background refresh if >30s remaining
6833
6832
  };
6834
6833
  /**
6835
6834
  * Internal function to construct API root from environment
@@ -7139,8 +7138,12 @@ class AuthService {
7139
7138
  await extendedProvider.setProviderToken(providerToken);
7140
7139
  }
7141
7140
  if (authType && extendedProvider.setAuthType) {
7141
+ console.log('[AuthService] Storing auth type:', authType);
7142
7142
  await extendedProvider.setAuthType(authType);
7143
7143
  }
7144
+ else {
7145
+ console.warn('[AuthService] Auth type not stored - authType:', authType, 'has setAuthType:', !!extendedProvider.setAuthType);
7146
+ }
7144
7147
  // Emit authenticated status on successful token storage
7145
7148
  await this.emitAuthStatus(AuthStatus.AUTHENTICATED);
7146
7149
  }
@@ -7159,8 +7162,15 @@ class TokenRefreshManager {
7159
7162
  this.authService = authService;
7160
7163
  this.authProvider = authProvider;
7161
7164
  this.tokenRefreshMarginSeconds = 120; // 2 minutes
7165
+ this.lastValidationTime = 0;
7166
+ this.validationCacheDurationMs = 30000; // 30 seconds
7162
7167
  }
7163
7168
  async ensureValidToken() {
7169
+ const now = Date.now();
7170
+ // Skip validation if we checked recently (within cache duration)
7171
+ if (now - this.lastValidationTime < this.validationCacheDurationMs) {
7172
+ return;
7173
+ }
7164
7174
  try {
7165
7175
  const token = await this.authProvider.getToken();
7166
7176
  if (!token) {
@@ -7171,6 +7181,14 @@ class TokenRefreshManager {
7171
7181
  if (!refreshSuccess) {
7172
7182
  await this.authService.handleAuthFailure();
7173
7183
  }
7184
+ else {
7185
+ // Update validation time on successful refresh
7186
+ this.lastValidationTime = now;
7187
+ }
7188
+ }
7189
+ else {
7190
+ // Token is valid, update validation time
7191
+ this.lastValidationTime = now;
7174
7192
  }
7175
7193
  }
7176
7194
  catch (error) {
@@ -7254,42 +7272,77 @@ class MemoryTokenStorage {
7254
7272
  }
7255
7273
  }
7256
7274
  /**
7257
- * Token manager for authentication tokens
7275
+ * Token manager for authentication tokens with memory caching
7258
7276
  */
7259
7277
  class AuthTokenManager {
7260
7278
  constructor(storage = new LocalStorageTokenStorage()) {
7261
7279
  this.storage = storage;
7280
+ this.cache = {};
7262
7281
  }
7263
7282
  async getAccessToken() {
7283
+ // Return cached value if available
7284
+ if (this.cache.accessToken !== undefined) {
7285
+ return this.cache.accessToken;
7286
+ }
7264
7287
  const val = await this.storage.get(AUTH_STORAGE_KEYS.ACCESS_TOKEN);
7265
7288
  // Ensure we return string for tokens
7266
- return typeof val === 'string' ? val : null;
7289
+ const token = typeof val === 'string' ? val : null;
7290
+ this.cache.accessToken = token;
7291
+ return token;
7267
7292
  }
7268
7293
  async setAccessToken(token) {
7294
+ this.cache.accessToken = token;
7269
7295
  await this.storage.set(AUTH_STORAGE_KEYS.ACCESS_TOKEN, token);
7270
7296
  }
7271
7297
  async getRefreshToken() {
7298
+ // Return cached value if available
7299
+ if (this.cache.refreshToken !== undefined) {
7300
+ return this.cache.refreshToken;
7301
+ }
7272
7302
  const val = await this.storage.get(AUTH_STORAGE_KEYS.REFRESH_TOKEN);
7273
- return typeof val === 'string' ? val : null;
7303
+ const token = typeof val === 'string' ? val : null;
7304
+ this.cache.refreshToken = token;
7305
+ return token;
7274
7306
  }
7275
7307
  async setRefreshToken(token) {
7308
+ this.cache.refreshToken = token;
7276
7309
  await this.storage.set(AUTH_STORAGE_KEYS.REFRESH_TOKEN, token);
7277
7310
  }
7278
7311
  async getProviderToken() {
7312
+ // Return cached value if available
7313
+ if (this.cache.providerToken !== undefined) {
7314
+ return this.cache.providerToken;
7315
+ }
7279
7316
  const val = await this.storage.get(AUTH_STORAGE_KEYS.PROVIDER_TOKEN);
7280
- return typeof val === 'string' ? val : null;
7317
+ const token = typeof val === 'string' ? val : null;
7318
+ this.cache.providerToken = token;
7319
+ return token;
7281
7320
  }
7282
7321
  async setProviderToken(token) {
7322
+ this.cache.providerToken = token;
7283
7323
  await this.storage.set(AUTH_STORAGE_KEYS.PROVIDER_TOKEN, token);
7284
7324
  }
7285
7325
  async getAuthType() {
7326
+ // Return cached value if available
7327
+ if (this.cache.authType !== undefined) {
7328
+ return this.cache.authType;
7329
+ }
7286
7330
  const authType = await this.storage.get(AUTH_STORAGE_KEYS.AUTH_TYPE);
7287
- return typeof authType === 'string' ? authType : null;
7331
+ const type = typeof authType === 'string' ? authType : null;
7332
+ this.cache.authType = type;
7333
+ return type;
7288
7334
  }
7289
7335
  async setAuthType(authType) {
7336
+ this.cache.authType = authType;
7290
7337
  await this.storage.set(AUTH_STORAGE_KEYS.AUTH_TYPE, authType);
7291
7338
  }
7292
7339
  async setTokens(accessToken, refreshToken, providerToken) {
7340
+ // Update cache for all tokens
7341
+ this.cache.accessToken = accessToken;
7342
+ if (refreshToken)
7343
+ this.cache.refreshToken = refreshToken;
7344
+ if (providerToken)
7345
+ this.cache.providerToken = providerToken;
7293
7346
  await this.setAccessToken(accessToken);
7294
7347
  if (refreshToken)
7295
7348
  await this.setRefreshToken(refreshToken);
@@ -7297,13 +7350,23 @@ class AuthTokenManager {
7297
7350
  await this.setProviderToken(providerToken);
7298
7351
  }
7299
7352
  async clearAllTokens() {
7353
+ // Clear cache
7354
+ this.cache = {};
7300
7355
  await this.storage.clear();
7301
7356
  }
7302
7357
  async hasAccessToken() {
7358
+ // Use cached value if available to avoid storage read
7359
+ if (this.cache.accessToken !== undefined) {
7360
+ return !!this.cache.accessToken;
7361
+ }
7303
7362
  const token = await this.getAccessToken();
7304
7363
  return !!token;
7305
7364
  }
7306
7365
  async hasRefreshToken() {
7366
+ // Use cached value if available to avoid storage read
7367
+ if (this.cache.refreshToken !== undefined) {
7368
+ return !!this.cache.refreshToken;
7369
+ }
7307
7370
  const token = await this.getRefreshToken();
7308
7371
  return !!token;
7309
7372
  }
@@ -7531,6 +7594,7 @@ class DPoPManager {
7531
7594
  class DefaultAuthProvider {
7532
7595
  constructor(config = {}) {
7533
7596
  this.config = config;
7597
+ this.authTypeCache = null;
7534
7598
  this.authType = config.authType || exports.AccountOwnerType.USER;
7535
7599
  const storage = config.storage || this.createStorage();
7536
7600
  this.tokenManager = new AuthTokenManager(storage);
@@ -7591,6 +7655,15 @@ class DefaultAuthProvider {
7591
7655
  }
7592
7656
  async setAccessToken(token) {
7593
7657
  await this.tokenManager.setAccessToken(token);
7658
+ // Invalidate cache when token changes
7659
+ this.authTypeCache = null;
7660
+ // Eager DPoP key generation after login
7661
+ if (this.dpopManager) {
7662
+ // Fire and forget - generate keys in background
7663
+ this.dpopManager.ensureKeyPair().catch(err => {
7664
+ console.warn('[DefaultAuthProvider] DPoP key generation failed:', err);
7665
+ });
7666
+ }
7594
7667
  }
7595
7668
  async setRefreshToken(token) {
7596
7669
  await this.tokenManager.setRefreshToken(token);
@@ -7600,16 +7673,51 @@ class DefaultAuthProvider {
7600
7673
  }
7601
7674
  async clearTokens() {
7602
7675
  await this.tokenManager.clearAllTokens();
7676
+ // Invalidate cache on clear
7677
+ this.authTypeCache = null;
7603
7678
  if (this.dpopManager) {
7604
7679
  await this.dpopManager.clearKeys();
7605
7680
  }
7606
7681
  }
7607
7682
  async setTokens(accessToken, refreshToken, providerToken) {
7608
7683
  await this.tokenManager.setTokens(accessToken, refreshToken, providerToken);
7684
+ // Invalidate cache when tokens change
7685
+ this.authTypeCache = null;
7609
7686
  }
7610
7687
  async hasValidToken() {
7611
7688
  return this.tokenManager.hasAccessToken();
7612
7689
  }
7690
+ async getAuthType() {
7691
+ // Read from JWT instead of separate storage for single source of truth
7692
+ const token = await this.tokenManager.getAccessToken();
7693
+ if (!token) {
7694
+ this.authTypeCache = null;
7695
+ return null;
7696
+ }
7697
+ // Return cached result if token hasn't changed
7698
+ if (this.authTypeCache && this.authTypeCache.token === token) {
7699
+ return this.authTypeCache.type;
7700
+ }
7701
+ try {
7702
+ // Decode JWT payload (without verification - we just need to read the claim)
7703
+ const parts = token.split('.');
7704
+ if (parts.length !== 3) {
7705
+ console.warn('[DefaultAuthProvider] Invalid JWT format');
7706
+ return null;
7707
+ }
7708
+ const payloadJson = atob(parts[1]);
7709
+ const payload = JSON.parse(payloadJson);
7710
+ // Use accountOwnerType from AuthJWTPayload interface
7711
+ const authType = payload.accountType || null;
7712
+ // Cache result with token signature
7713
+ this.authTypeCache = { token, type: authType };
7714
+ return authType;
7715
+ }
7716
+ catch (error) {
7717
+ console.warn('[DefaultAuthProvider] Failed to parse auth type from JWT:', error);
7718
+ return null;
7719
+ }
7720
+ }
7613
7721
  }
7614
7722
 
7615
7723
  /**
@@ -13351,78 +13459,71 @@ class PersSDK {
13351
13459
  // Initialize event emitter and wire to API client for error events
13352
13460
  this._events = new PersEventEmitter();
13353
13461
  this.apiClient.setEvents(this._events);
13354
- // Initialize domain managers (pass events to managers that emit success events)
13355
- this._auth = new AuthManager(this.apiClient, this._events);
13356
- this._users = new UserManager(this.apiClient, this._events);
13357
- this._userStatus = new UserStatusManager(this.apiClient);
13358
- this._tokens = new TokenManager(this.apiClient);
13359
- this._businesses = new BusinessManager(this.apiClient, this._events);
13360
- this._campaigns = new CampaignManager(this.apiClient, this._events);
13361
- this._redemptions = new RedemptionManager(this.apiClient, this._events);
13362
- this._transactions = new TransactionManager(this.apiClient, this._events);
13363
- this._purchases = new PurchaseManager(this.apiClient);
13364
- this._files = new FileManager(this.apiClient);
13365
- this._tenants = new TenantManager(this.apiClient);
13366
- this._apiKeys = new ApiKeyManager(this.apiClient);
13367
- this._analytics = new AnalyticsManager(this.apiClient);
13368
- this._donations = new DonationManager(this.apiClient);
13369
- // Automatically restore session if enabled and tokens exist
13370
- if (config.autoRestoreSession !== false) {
13371
- this.restoreSessionIfTokensExist().catch(err => {
13372
- console.warn('[PersSDK] Failed to restore session on initialization:', err);
13373
- });
13374
- }
13375
13462
  }
13376
13463
  /**
13377
- * Restore user session if valid tokens exist in storage
13464
+ * Restore user session from stored tokens
13378
13465
  *
13379
- * This method is called automatically during SDK initialization when
13380
- * `autoRestoreSession` is enabled (default). It checks for stored tokens
13381
- * and fetches user data to restore the authentication state.
13466
+ * Call this method after SDK initialization to restore the user's session
13467
+ * if valid tokens exist. This is useful for maintaining login state across
13468
+ * app restarts.
13469
+ *
13470
+ * **Important:** Only works for USER and BUSINESS accounts. Admin/tenant
13471
+ * accounts don't support user data fetching, so this will return null for them
13472
+ * (though their tokens remain valid for API calls).
13382
13473
  *
13383
13474
  * Emits auth domain success event when session is restored.
13384
13475
  *
13385
- * @internal
13386
- * @returns Promise that resolves when session is restored or determined to be invalid
13476
+ * @returns Promise resolving to User data if session restored, null if no session or admin account
13477
+ * @throws {PersError} If token validation or user fetch fails
13478
+ *
13479
+ * @example
13480
+ * ```typescript
13481
+ * // Call after initial render for better perceived performance
13482
+ * const user = await sdk.restoreSession();
13483
+ * if (user) {
13484
+ * console.log('Welcome back,', user.name);
13485
+ * }
13486
+ * ```
13387
13487
  */
13388
- async restoreSessionIfTokensExist() {
13389
- try {
13390
- const hasToken = await this._auth.hasValidAuth();
13391
- if (hasToken) {
13392
- console.log('[PersSDK] Valid tokens found, restoring session...');
13393
- try {
13394
- // Fetch user data to validate tokens and restore session
13395
- const userData = await this._auth.getCurrentUser();
13396
- console.log('[PersSDK] Session restored successfully');
13397
- // Emit event through proper event emitter
13398
- // Send minimal data (just userId) - consumers can fetch full user if needed
13399
- this._events.emitSuccess({
13400
- type: 'session_restored',
13401
- domain: 'authentication',
13402
- userMessage: 'Session restored successfully',
13403
- details: { userId: userData.id }
13404
- });
13405
- }
13406
- catch (error) {
13407
- // Tokens exist but are invalid (expired, revoked, etc.)
13408
- console.warn('[PersSDK] Failed to restore session, tokens may be invalid:', error);
13409
- await this._auth.clearAuth();
13410
- // Emit restoration failed event
13411
- this._events.emitError({
13412
- type: 'session_restoration_failed',
13413
- domain: 'authentication',
13414
- userMessage: 'Session restoration failed',
13415
- code: 'SESSION_INVALID',
13416
- details: { error }
13417
- });
13418
- }
13419
- }
13420
- else {
13421
- console.log('[PersSDK] No valid tokens found');
13488
+ async restoreSession() {
13489
+ const hasToken = await this.auth.hasValidAuth();
13490
+ if (!hasToken) {
13491
+ return null;
13492
+ }
13493
+ // Check auth type - only restore session for user and business accounts
13494
+ const authProvider = this.apiClient.getConfig().authProvider;
13495
+ if (authProvider?.getAuthType) {
13496
+ const authType = await authProvider.getAuthType();
13497
+ if (authType === exports.AccountOwnerType.TENANT) {
13498
+ // Admin sessions don't support getCurrentUser(), skip restoration
13499
+ // Tokens are valid and will be used for API calls
13500
+ return null;
13422
13501
  }
13423
13502
  }
13503
+ try {
13504
+ // Fetch user data to validate tokens and restore session
13505
+ const userData = await this.auth.getCurrentUser();
13506
+ // Emit event through proper event emitter
13507
+ this._events.emitSuccess({
13508
+ type: 'session_restored',
13509
+ domain: 'authentication',
13510
+ userMessage: 'Session restored successfully',
13511
+ details: { userId: userData.id }
13512
+ });
13513
+ return userData;
13514
+ }
13424
13515
  catch (error) {
13425
- console.warn('[PersSDK] Error during session restoration:', error);
13516
+ // Tokens exist but are invalid (expired, revoked, etc.)
13517
+ await this.auth.clearAuth();
13518
+ // Emit restoration failed event
13519
+ this._events.emitError({
13520
+ type: 'session_restoration_failed',
13521
+ domain: 'authentication',
13522
+ userMessage: 'Session restoration failed',
13523
+ code: 'SESSION_INVALID',
13524
+ details: { error }
13525
+ });
13526
+ throw error;
13426
13527
  }
13427
13528
  }
13428
13529
  /**
@@ -13511,6 +13612,9 @@ class PersSDK {
13511
13612
  * @see {@link AuthManager} for detailed documentation
13512
13613
  */
13513
13614
  get auth() {
13615
+ if (!this._auth) {
13616
+ this._auth = new AuthManager(this.apiClient, this._events);
13617
+ }
13514
13618
  return this._auth;
13515
13619
  }
13516
13620
  /**
@@ -13524,6 +13628,9 @@ class PersSDK {
13524
13628
  * ```
13525
13629
  */
13526
13630
  get users() {
13631
+ if (!this._users) {
13632
+ this._users = new UserManager(this.apiClient, this._events);
13633
+ }
13527
13634
  return this._users;
13528
13635
  }
13529
13636
  /**
@@ -13537,6 +13644,9 @@ class PersSDK {
13537
13644
  * ```
13538
13645
  */
13539
13646
  get userStatus() {
13647
+ if (!this._userStatus) {
13648
+ this._userStatus = new UserStatusManager(this.apiClient);
13649
+ }
13540
13650
  return this._userStatus;
13541
13651
  }
13542
13652
  /**
@@ -13550,6 +13660,9 @@ class PersSDK {
13550
13660
  * ```
13551
13661
  */
13552
13662
  get tokens() {
13663
+ if (!this._tokens) {
13664
+ this._tokens = new TokenManager(this.apiClient);
13665
+ }
13553
13666
  return this._tokens;
13554
13667
  }
13555
13668
  /**
@@ -13563,6 +13676,9 @@ class PersSDK {
13563
13676
  * ```
13564
13677
  */
13565
13678
  get businesses() {
13679
+ if (!this._businesses) {
13680
+ this._businesses = new BusinessManager(this.apiClient, this._events);
13681
+ }
13566
13682
  return this._businesses;
13567
13683
  }
13568
13684
  /**
@@ -13576,6 +13692,9 @@ class PersSDK {
13576
13692
  * ```
13577
13693
  */
13578
13694
  get campaigns() {
13695
+ if (!this._campaigns) {
13696
+ this._campaigns = new CampaignManager(this.apiClient, this._events);
13697
+ }
13579
13698
  return this._campaigns;
13580
13699
  }
13581
13700
  /**
@@ -13589,6 +13708,9 @@ class PersSDK {
13589
13708
  * ```
13590
13709
  */
13591
13710
  get redemptions() {
13711
+ if (!this._redemptions) {
13712
+ this._redemptions = new RedemptionManager(this.apiClient, this._events);
13713
+ }
13592
13714
  return this._redemptions;
13593
13715
  }
13594
13716
  /**
@@ -13602,6 +13724,9 @@ class PersSDK {
13602
13724
  * ```
13603
13725
  */
13604
13726
  get transactions() {
13727
+ if (!this._transactions) {
13728
+ this._transactions = new TransactionManager(this.apiClient, this._events);
13729
+ }
13605
13730
  return this._transactions;
13606
13731
  }
13607
13732
  /**
@@ -13615,6 +13740,9 @@ class PersSDK {
13615
13740
  * ```
13616
13741
  */
13617
13742
  get purchases() {
13743
+ if (!this._purchases) {
13744
+ this._purchases = new PurchaseManager(this.apiClient);
13745
+ }
13618
13746
  return this._purchases;
13619
13747
  }
13620
13748
  /**
@@ -13628,6 +13756,9 @@ class PersSDK {
13628
13756
  * ```
13629
13757
  */
13630
13758
  get files() {
13759
+ if (!this._files) {
13760
+ this._files = new FileManager(this.apiClient);
13761
+ }
13631
13762
  return this._files;
13632
13763
  }
13633
13764
  /**
@@ -13641,6 +13772,9 @@ class PersSDK {
13641
13772
  * ```
13642
13773
  */
13643
13774
  get tenants() {
13775
+ if (!this._tenants) {
13776
+ this._tenants = new TenantManager(this.apiClient);
13777
+ }
13644
13778
  return this._tenants;
13645
13779
  }
13646
13780
  /**
@@ -13665,6 +13799,9 @@ class PersSDK {
13665
13799
  * ```
13666
13800
  */
13667
13801
  get apiKeys() {
13802
+ if (!this._apiKeys) {
13803
+ this._apiKeys = new ApiKeyManager(this.apiClient);
13804
+ }
13668
13805
  return this._apiKeys;
13669
13806
  }
13670
13807
  /**
@@ -13676,6 +13813,9 @@ class PersSDK {
13676
13813
  * ```
13677
13814
  */
13678
13815
  get analytics() {
13816
+ if (!this._analytics) {
13817
+ this._analytics = new AnalyticsManager(this.apiClient);
13818
+ }
13679
13819
  return this._analytics;
13680
13820
  }
13681
13821
  /**
@@ -13687,6 +13827,9 @@ class PersSDK {
13687
13827
  * ```
13688
13828
  */
13689
13829
  get donations() {
13830
+ if (!this._donations) {
13831
+ this._donations = new DonationManager(this.apiClient);
13832
+ }
13690
13833
  return this._donations;
13691
13834
  }
13692
13835
  /**
@@ -14581,11 +14724,19 @@ const SDKContext = react.createContext(null);
14581
14724
  // Provider component
14582
14725
  const PersSDKProvider = ({ children, config }) => {
14583
14726
  const initializingRef = react.useRef(false);
14727
+ // State refs for stable functions to read current values
14728
+ const sdkRef = react.useRef(null);
14729
+ const isInitializedRef = react.useRef(false);
14730
+ const isAuthenticatedRef = react.useRef(false);
14584
14731
  const [sdk, setSdk] = react.useState(null);
14585
14732
  const [authProvider, setAuthProvider] = react.useState(null);
14586
14733
  const [isInitialized, setIsInitialized] = react.useState(false);
14587
14734
  const [isAuthenticated, setIsAuthenticated] = react.useState(false);
14588
14735
  const [user, setUser] = react.useState(null);
14736
+ // Keep state refs in sync immediately (not in useEffect to avoid race conditions)
14737
+ sdkRef.current = sdk;
14738
+ isInitializedRef.current = isInitialized;
14739
+ isAuthenticatedRef.current = isAuthenticated;
14589
14740
  const initialize = react.useCallback(async (config) => {
14590
14741
  // Prevent multiple initializations
14591
14742
  if (isInitialized || initializingRef.current) {
@@ -14652,18 +14803,40 @@ const PersSDKProvider = ({ children, config }) => {
14652
14803
  }
14653
14804
  }, [config, isInitialized, initialize]);
14654
14805
  const refreshUserData = react.useCallback(async () => {
14655
- if (!sdk || !isAuthenticated || !isInitialized) {
14656
- throw new Error('SDK not initialized or not authenticated. Cannot refresh user data.');
14806
+ // Read from refs to get current values
14807
+ const currentSdk = sdkRef.current;
14808
+ const currentIsInitialized = isInitializedRef.current;
14809
+ if (!currentSdk || !currentIsInitialized) {
14810
+ throw new Error('SDK not initialized. Cannot refresh user data.');
14657
14811
  }
14658
14812
  try {
14659
- const freshUserData = await sdk.users.getCurrentUser();
14813
+ const freshUserData = await currentSdk.users.getCurrentUser();
14660
14814
  setUser(freshUserData);
14661
14815
  }
14662
14816
  catch (error) {
14663
14817
  console.error('Failed to refresh user data:', error);
14664
14818
  throw error;
14665
14819
  }
14666
- }, [sdk, isAuthenticated, isInitialized]);
14820
+ }, []); // No dependencies - reads from refs
14821
+ const restoreSession = react.useCallback(async () => {
14822
+ // Read from refs to get current values
14823
+ const currentSdk = sdkRef.current;
14824
+ const currentIsInitialized = isInitializedRef.current;
14825
+ if (!currentSdk || !currentIsInitialized) {
14826
+ throw new Error('SDK not initialized. Call initialize() first.');
14827
+ }
14828
+ try {
14829
+ const userData = await currentSdk.restoreSession();
14830
+ if (userData) {
14831
+ setAuthenticationState(userData, true);
14832
+ }
14833
+ return userData;
14834
+ }
14835
+ catch (error) {
14836
+ console.error('[PersSDK] Failed to restore session:', error);
14837
+ throw error;
14838
+ }
14839
+ }, [setAuthenticationState]); // Depends on setAuthenticationState
14667
14840
  // Listen for authentication events from core SDK
14668
14841
  // Set up immediately when SDK is created (don't wait for isInitialized)
14669
14842
  // to catch session_restored events that fire during SDK initialization
@@ -14676,13 +14849,18 @@ const PersSDKProvider = ({ children, config }) => {
14676
14849
  // Session restored successfully - sync React state
14677
14850
  if (event.type === 'session_restored') {
14678
14851
  console.log('[PersSDK] Session restoration event received, syncing state...');
14679
- sdk.users.getCurrentUser()
14680
- .then(userData => {
14681
- setAuthenticationState(userData, true);
14682
- })
14683
- .catch(error => {
14684
- console.error('[PersSDK] Failed to sync restored session:', error);
14685
- });
14852
+ // Read user from event details if available, otherwise fetch
14853
+ const userId = event.details?.userId;
14854
+ if (userId) {
14855
+ // User ID available, fetch user data
14856
+ sdk.users.getCurrentUser()
14857
+ .then(userData => {
14858
+ setAuthenticationState(userData, true);
14859
+ })
14860
+ .catch(error => {
14861
+ console.error('[PersSDK] Failed to sync restored session:', error);
14862
+ });
14863
+ }
14686
14864
  }
14687
14865
  // Session restoration failed or auth error - clear React state
14688
14866
  if (event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
@@ -14720,28 +14898,17 @@ const PersSDKProvider = ({ children, config }) => {
14720
14898
  const contextValue = react.useMemo(() => ({
14721
14899
  // Main SDK instance
14722
14900
  sdk,
14723
- // Manager shortcuts for convenience
14724
- auth: sdk?.auth || null,
14725
- users: sdk?.users || null,
14726
- tokens: sdk?.tokens || null,
14727
- businesses: sdk?.businesses || null,
14728
- campaigns: sdk?.campaigns || null,
14729
- redemptions: sdk?.redemptions || null,
14730
- transactions: sdk?.transactions || null,
14731
- purchases: sdk?.purchases || null,
14732
- tenants: sdk?.tenants || null,
14733
- analytics: sdk?.analytics || null,
14734
- donations: sdk?.donations || null,
14735
14901
  // Platform-specific providers
14736
14902
  authProvider,
14737
14903
  // State
14738
14904
  isInitialized,
14739
14905
  isAuthenticated,
14740
14906
  user,
14741
- // Methods
14907
+ // Methods - expose functions directly, not through refs
14742
14908
  initialize,
14743
14909
  setAuthenticationState,
14744
14910
  refreshUserData,
14911
+ restoreSession,
14745
14912
  }), [
14746
14913
  sdk,
14747
14914
  authProvider,
@@ -14750,7 +14917,8 @@ const PersSDKProvider = ({ children, config }) => {
14750
14917
  user,
14751
14918
  initialize,
14752
14919
  setAuthenticationState,
14753
- refreshUserData
14920
+ refreshUserData,
14921
+ restoreSession
14754
14922
  ]);
14755
14923
  return (jsxRuntime.jsx(SDKContext.Provider, { value: contextValue, children: children }));
14756
14924
  };
@@ -36091,7 +36259,7 @@ const useWeb3 = () => {
36091
36259
  */
36092
36260
  function useTokenBalances(options) {
36093
36261
  const { accountAddress, availableTokens = [], autoLoad = true, refreshInterval = 0 } = options;
36094
- const { isAuthenticated } = usePersSDK();
36262
+ const { isAuthenticated, sdk } = usePersSDK();
36095
36263
  const web3 = useWeb3();
36096
36264
  const [tokenBalances, setTokenBalances] = react.useState([]);
36097
36265
  const [isLoading, setIsLoading] = react.useState(false);
@@ -36193,15 +36361,28 @@ function useTokenBalances(options) {
36193
36361
  loadBalances();
36194
36362
  }
36195
36363
  }, [autoLoad, isAvailable, availableTokens.length, loadBalances]);
36196
- // Optional auto-refresh interval
36364
+ // Event-driven refresh: listen for transaction events instead of polling
36197
36365
  react.useEffect(() => {
36198
- if (refreshInterval > 0 && isAvailable && availableTokens.length > 0) {
36199
- const intervalId = setInterval(() => {
36366
+ if (!sdk || refreshInterval <= 0 || !isAvailable)
36367
+ return;
36368
+ // Subscribe to transaction domain events
36369
+ const unsubscribe = sdk.events.subscribe((event) => {
36370
+ if (event.domain === 'transaction' && event.type === 'transaction_completed') {
36371
+ console.log('[useTokenBalances] Transaction completed, refreshing balances...');
36200
36372
  loadBalances();
36201
- }, refreshInterval);
36202
- return () => clearInterval(intervalId);
36203
- }
36204
- }, [refreshInterval, isAvailable, availableTokens.length, loadBalances]);
36373
+ }
36374
+ }, { domains: ['transaction'] });
36375
+ // Also set up a fallback polling interval (much longer than before)
36376
+ // This handles cases where events might be missed
36377
+ const fallbackInterval = Math.max(refreshInterval, 60000); // Minimum 1 minute
36378
+ const intervalId = setInterval(() => {
36379
+ loadBalances();
36380
+ }, fallbackInterval);
36381
+ return () => {
36382
+ unsubscribe();
36383
+ clearInterval(intervalId);
36384
+ };
36385
+ }, [sdk, refreshInterval, isAvailable, loadBalances]);
36205
36386
  return {
36206
36387
  tokenBalances,
36207
36388
  isLoading,