@explorins/pers-sdk-react-native 2.1.1 → 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
@@ -6955,6 +6955,10 @@ var AuthStatus;
6955
6955
  AuthStatus["REFRESHING"] = "refreshing";
6956
6956
  /** No authentication present */
6957
6957
  AuthStatus["UNAUTHENTICATED"] = "unauthenticated";
6958
+ /** Session restoration completed (fired once on SDK initialization) */
6959
+ AuthStatus["SESSION_RESTORED"] = "session_restored";
6960
+ /** Session restoration determined no valid session exists */
6961
+ AuthStatus["SESSION_RESTORATION_FAILED"] = "session_restoration_failed";
6958
6962
  })(AuthStatus || (AuthStatus = {}));
6959
6963
 
6960
6964
  /**
@@ -7134,8 +7138,12 @@ class AuthService {
7134
7138
  await extendedProvider.setProviderToken(providerToken);
7135
7139
  }
7136
7140
  if (authType && extendedProvider.setAuthType) {
7141
+ console.log('[AuthService] Storing auth type:', authType);
7137
7142
  await extendedProvider.setAuthType(authType);
7138
7143
  }
7144
+ else {
7145
+ console.warn('[AuthService] Auth type not stored - authType:', authType, 'has setAuthType:', !!extendedProvider.setAuthType);
7146
+ }
7139
7147
  // Emit authenticated status on successful token storage
7140
7148
  await this.emitAuthStatus(AuthStatus.AUTHENTICATED);
7141
7149
  }
@@ -7154,8 +7162,15 @@ class TokenRefreshManager {
7154
7162
  this.authService = authService;
7155
7163
  this.authProvider = authProvider;
7156
7164
  this.tokenRefreshMarginSeconds = 120; // 2 minutes
7165
+ this.lastValidationTime = 0;
7166
+ this.validationCacheDurationMs = 30000; // 30 seconds
7157
7167
  }
7158
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
+ }
7159
7174
  try {
7160
7175
  const token = await this.authProvider.getToken();
7161
7176
  if (!token) {
@@ -7166,6 +7181,14 @@ class TokenRefreshManager {
7166
7181
  if (!refreshSuccess) {
7167
7182
  await this.authService.handleAuthFailure();
7168
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;
7169
7192
  }
7170
7193
  }
7171
7194
  catch (error) {
@@ -7249,42 +7272,77 @@ class MemoryTokenStorage {
7249
7272
  }
7250
7273
  }
7251
7274
  /**
7252
- * Token manager for authentication tokens
7275
+ * Token manager for authentication tokens with memory caching
7253
7276
  */
7254
7277
  class AuthTokenManager {
7255
7278
  constructor(storage = new LocalStorageTokenStorage()) {
7256
7279
  this.storage = storage;
7280
+ this.cache = {};
7257
7281
  }
7258
7282
  async getAccessToken() {
7283
+ // Return cached value if available
7284
+ if (this.cache.accessToken !== undefined) {
7285
+ return this.cache.accessToken;
7286
+ }
7259
7287
  const val = await this.storage.get(AUTH_STORAGE_KEYS.ACCESS_TOKEN);
7260
7288
  // Ensure we return string for tokens
7261
- return typeof val === 'string' ? val : null;
7289
+ const token = typeof val === 'string' ? val : null;
7290
+ this.cache.accessToken = token;
7291
+ return token;
7262
7292
  }
7263
7293
  async setAccessToken(token) {
7294
+ this.cache.accessToken = token;
7264
7295
  await this.storage.set(AUTH_STORAGE_KEYS.ACCESS_TOKEN, token);
7265
7296
  }
7266
7297
  async getRefreshToken() {
7298
+ // Return cached value if available
7299
+ if (this.cache.refreshToken !== undefined) {
7300
+ return this.cache.refreshToken;
7301
+ }
7267
7302
  const val = await this.storage.get(AUTH_STORAGE_KEYS.REFRESH_TOKEN);
7268
- return typeof val === 'string' ? val : null;
7303
+ const token = typeof val === 'string' ? val : null;
7304
+ this.cache.refreshToken = token;
7305
+ return token;
7269
7306
  }
7270
7307
  async setRefreshToken(token) {
7308
+ this.cache.refreshToken = token;
7271
7309
  await this.storage.set(AUTH_STORAGE_KEYS.REFRESH_TOKEN, token);
7272
7310
  }
7273
7311
  async getProviderToken() {
7312
+ // Return cached value if available
7313
+ if (this.cache.providerToken !== undefined) {
7314
+ return this.cache.providerToken;
7315
+ }
7274
7316
  const val = await this.storage.get(AUTH_STORAGE_KEYS.PROVIDER_TOKEN);
7275
- return typeof val === 'string' ? val : null;
7317
+ const token = typeof val === 'string' ? val : null;
7318
+ this.cache.providerToken = token;
7319
+ return token;
7276
7320
  }
7277
7321
  async setProviderToken(token) {
7322
+ this.cache.providerToken = token;
7278
7323
  await this.storage.set(AUTH_STORAGE_KEYS.PROVIDER_TOKEN, token);
7279
7324
  }
7280
7325
  async getAuthType() {
7326
+ // Return cached value if available
7327
+ if (this.cache.authType !== undefined) {
7328
+ return this.cache.authType;
7329
+ }
7281
7330
  const authType = await this.storage.get(AUTH_STORAGE_KEYS.AUTH_TYPE);
7282
- return typeof authType === 'string' ? authType : null;
7331
+ const type = typeof authType === 'string' ? authType : null;
7332
+ this.cache.authType = type;
7333
+ return type;
7283
7334
  }
7284
7335
  async setAuthType(authType) {
7336
+ this.cache.authType = authType;
7285
7337
  await this.storage.set(AUTH_STORAGE_KEYS.AUTH_TYPE, authType);
7286
7338
  }
7287
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;
7288
7346
  await this.setAccessToken(accessToken);
7289
7347
  if (refreshToken)
7290
7348
  await this.setRefreshToken(refreshToken);
@@ -7292,13 +7350,23 @@ class AuthTokenManager {
7292
7350
  await this.setProviderToken(providerToken);
7293
7351
  }
7294
7352
  async clearAllTokens() {
7353
+ // Clear cache
7354
+ this.cache = {};
7295
7355
  await this.storage.clear();
7296
7356
  }
7297
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
+ }
7298
7362
  const token = await this.getAccessToken();
7299
7363
  return !!token;
7300
7364
  }
7301
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
+ }
7302
7370
  const token = await this.getRefreshToken();
7303
7371
  return !!token;
7304
7372
  }
@@ -7526,6 +7594,7 @@ class DPoPManager {
7526
7594
  class DefaultAuthProvider {
7527
7595
  constructor(config = {}) {
7528
7596
  this.config = config;
7597
+ this.authTypeCache = null;
7529
7598
  this.authType = config.authType || exports.AccountOwnerType.USER;
7530
7599
  const storage = config.storage || this.createStorage();
7531
7600
  this.tokenManager = new AuthTokenManager(storage);
@@ -7586,6 +7655,15 @@ class DefaultAuthProvider {
7586
7655
  }
7587
7656
  async setAccessToken(token) {
7588
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
+ }
7589
7667
  }
7590
7668
  async setRefreshToken(token) {
7591
7669
  await this.tokenManager.setRefreshToken(token);
@@ -7595,16 +7673,51 @@ class DefaultAuthProvider {
7595
7673
  }
7596
7674
  async clearTokens() {
7597
7675
  await this.tokenManager.clearAllTokens();
7676
+ // Invalidate cache on clear
7677
+ this.authTypeCache = null;
7598
7678
  if (this.dpopManager) {
7599
7679
  await this.dpopManager.clearKeys();
7600
7680
  }
7601
7681
  }
7602
7682
  async setTokens(accessToken, refreshToken, providerToken) {
7603
7683
  await this.tokenManager.setTokens(accessToken, refreshToken, providerToken);
7684
+ // Invalidate cache when tokens change
7685
+ this.authTypeCache = null;
7604
7686
  }
7605
7687
  async hasValidToken() {
7606
7688
  return this.tokenManager.hasAccessToken();
7607
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
+ }
7608
7721
  }
7609
7722
 
7610
7723
  /**
@@ -7618,7 +7731,7 @@ class DefaultAuthProvider {
7618
7731
  /** SDK package name */
7619
7732
  const SDK_NAME = '@explorins/pers-sdk';
7620
7733
  /** SDK version - TODO: Load dynamically from package.json */
7621
- const SDK_VERSION = '2.1.0';
7734
+ const SDK_VERSION = '2.1.1';
7622
7735
  /** Full SDK identifier for headers */
7623
7736
  const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
7624
7737
 
@@ -13346,21 +13459,72 @@ class PersSDK {
13346
13459
  // Initialize event emitter and wire to API client for error events
13347
13460
  this._events = new PersEventEmitter();
13348
13461
  this.apiClient.setEvents(this._events);
13349
- // Initialize domain managers (pass events to managers that emit success events)
13350
- this._auth = new AuthManager(this.apiClient, this._events);
13351
- this._users = new UserManager(this.apiClient, this._events);
13352
- this._userStatus = new UserStatusManager(this.apiClient);
13353
- this._tokens = new TokenManager(this.apiClient);
13354
- this._businesses = new BusinessManager(this.apiClient, this._events);
13355
- this._campaigns = new CampaignManager(this.apiClient, this._events);
13356
- this._redemptions = new RedemptionManager(this.apiClient, this._events);
13357
- this._transactions = new TransactionManager(this.apiClient, this._events);
13358
- this._purchases = new PurchaseManager(this.apiClient);
13359
- this._files = new FileManager(this.apiClient);
13360
- this._tenants = new TenantManager(this.apiClient);
13361
- this._apiKeys = new ApiKeyManager(this.apiClient);
13362
- this._analytics = new AnalyticsManager(this.apiClient);
13363
- this._donations = new DonationManager(this.apiClient);
13462
+ }
13463
+ /**
13464
+ * Restore user session from stored tokens
13465
+ *
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).
13473
+ *
13474
+ * Emits auth domain success event when session is restored.
13475
+ *
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
+ * ```
13487
+ */
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;
13501
+ }
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
+ }
13515
+ catch (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;
13527
+ }
13364
13528
  }
13365
13529
  /**
13366
13530
  * Event emitter - Subscribe to SDK-wide events
@@ -13448,6 +13612,9 @@ class PersSDK {
13448
13612
  * @see {@link AuthManager} for detailed documentation
13449
13613
  */
13450
13614
  get auth() {
13615
+ if (!this._auth) {
13616
+ this._auth = new AuthManager(this.apiClient, this._events);
13617
+ }
13451
13618
  return this._auth;
13452
13619
  }
13453
13620
  /**
@@ -13461,6 +13628,9 @@ class PersSDK {
13461
13628
  * ```
13462
13629
  */
13463
13630
  get users() {
13631
+ if (!this._users) {
13632
+ this._users = new UserManager(this.apiClient, this._events);
13633
+ }
13464
13634
  return this._users;
13465
13635
  }
13466
13636
  /**
@@ -13474,6 +13644,9 @@ class PersSDK {
13474
13644
  * ```
13475
13645
  */
13476
13646
  get userStatus() {
13647
+ if (!this._userStatus) {
13648
+ this._userStatus = new UserStatusManager(this.apiClient);
13649
+ }
13477
13650
  return this._userStatus;
13478
13651
  }
13479
13652
  /**
@@ -13487,6 +13660,9 @@ class PersSDK {
13487
13660
  * ```
13488
13661
  */
13489
13662
  get tokens() {
13663
+ if (!this._tokens) {
13664
+ this._tokens = new TokenManager(this.apiClient);
13665
+ }
13490
13666
  return this._tokens;
13491
13667
  }
13492
13668
  /**
@@ -13500,6 +13676,9 @@ class PersSDK {
13500
13676
  * ```
13501
13677
  */
13502
13678
  get businesses() {
13679
+ if (!this._businesses) {
13680
+ this._businesses = new BusinessManager(this.apiClient, this._events);
13681
+ }
13503
13682
  return this._businesses;
13504
13683
  }
13505
13684
  /**
@@ -13513,6 +13692,9 @@ class PersSDK {
13513
13692
  * ```
13514
13693
  */
13515
13694
  get campaigns() {
13695
+ if (!this._campaigns) {
13696
+ this._campaigns = new CampaignManager(this.apiClient, this._events);
13697
+ }
13516
13698
  return this._campaigns;
13517
13699
  }
13518
13700
  /**
@@ -13526,6 +13708,9 @@ class PersSDK {
13526
13708
  * ```
13527
13709
  */
13528
13710
  get redemptions() {
13711
+ if (!this._redemptions) {
13712
+ this._redemptions = new RedemptionManager(this.apiClient, this._events);
13713
+ }
13529
13714
  return this._redemptions;
13530
13715
  }
13531
13716
  /**
@@ -13539,6 +13724,9 @@ class PersSDK {
13539
13724
  * ```
13540
13725
  */
13541
13726
  get transactions() {
13727
+ if (!this._transactions) {
13728
+ this._transactions = new TransactionManager(this.apiClient, this._events);
13729
+ }
13542
13730
  return this._transactions;
13543
13731
  }
13544
13732
  /**
@@ -13552,6 +13740,9 @@ class PersSDK {
13552
13740
  * ```
13553
13741
  */
13554
13742
  get purchases() {
13743
+ if (!this._purchases) {
13744
+ this._purchases = new PurchaseManager(this.apiClient);
13745
+ }
13555
13746
  return this._purchases;
13556
13747
  }
13557
13748
  /**
@@ -13565,6 +13756,9 @@ class PersSDK {
13565
13756
  * ```
13566
13757
  */
13567
13758
  get files() {
13759
+ if (!this._files) {
13760
+ this._files = new FileManager(this.apiClient);
13761
+ }
13568
13762
  return this._files;
13569
13763
  }
13570
13764
  /**
@@ -13578,6 +13772,9 @@ class PersSDK {
13578
13772
  * ```
13579
13773
  */
13580
13774
  get tenants() {
13775
+ if (!this._tenants) {
13776
+ this._tenants = new TenantManager(this.apiClient);
13777
+ }
13581
13778
  return this._tenants;
13582
13779
  }
13583
13780
  /**
@@ -13602,6 +13799,9 @@ class PersSDK {
13602
13799
  * ```
13603
13800
  */
13604
13801
  get apiKeys() {
13802
+ if (!this._apiKeys) {
13803
+ this._apiKeys = new ApiKeyManager(this.apiClient);
13804
+ }
13605
13805
  return this._apiKeys;
13606
13806
  }
13607
13807
  /**
@@ -13613,6 +13813,9 @@ class PersSDK {
13613
13813
  * ```
13614
13814
  */
13615
13815
  get analytics() {
13816
+ if (!this._analytics) {
13817
+ this._analytics = new AnalyticsManager(this.apiClient);
13818
+ }
13616
13819
  return this._analytics;
13617
13820
  }
13618
13821
  /**
@@ -13624,6 +13827,9 @@ class PersSDK {
13624
13827
  * ```
13625
13828
  */
13626
13829
  get donations() {
13830
+ if (!this._donations) {
13831
+ this._donations = new DonationManager(this.apiClient);
13832
+ }
13627
13833
  return this._donations;
13628
13834
  }
13629
13835
  /**
@@ -14518,11 +14724,19 @@ const SDKContext = react.createContext(null);
14518
14724
  // Provider component
14519
14725
  const PersSDKProvider = ({ children, config }) => {
14520
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);
14521
14731
  const [sdk, setSdk] = react.useState(null);
14522
14732
  const [authProvider, setAuthProvider] = react.useState(null);
14523
14733
  const [isInitialized, setIsInitialized] = react.useState(false);
14524
14734
  const [isAuthenticated, setIsAuthenticated] = react.useState(false);
14525
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;
14526
14740
  const initialize = react.useCallback(async (config) => {
14527
14741
  // Prevent multiple initializations
14528
14742
  if (isInitialized || initializingRef.current) {
@@ -14583,89 +14797,81 @@ const PersSDKProvider = ({ children, config }) => {
14583
14797
  // Auto-initialize if config is provided
14584
14798
  react.useEffect(() => {
14585
14799
  if (config && !isInitialized && !initializingRef.current) {
14586
- initialize(config).then(async () => {
14587
- // Validate stored tokens on startup
14588
- // SDK's initialize() already calls ensureValidToken() which handles expired tokens
14589
- // This provides an additional safety layer for missing/corrupted tokens
14590
- if (authProvider && sdk) {
14591
- try {
14592
- const hasToken = await sdk.auth.hasValidAuth();
14593
- if (!hasToken) {
14594
- console.log('[PersSDK] No tokens found on startup, ensuring clean state');
14595
- await authProvider.clearTokens();
14596
- setAuthenticationState(null, false);
14597
- }
14598
- // Note: Token expiration validation happens automatically in SDK's initialize()
14599
- // which calls ensureValidToken() → checks expiration → triggers AUTH_FAILED if needed
14600
- }
14601
- catch (error) {
14602
- console.warn('[PersSDK] Token validation on startup failed:', error);
14603
- }
14604
- }
14605
- }).catch(err => {
14800
+ initialize(config).catch(err => {
14606
14801
  console.error('Auto-initialization failed:', err);
14607
14802
  });
14608
14803
  }
14609
- }, [config, isInitialized, initialize, authProvider, sdk, setAuthenticationState]);
14804
+ }, [config, isInitialized, initialize]);
14610
14805
  const refreshUserData = react.useCallback(async () => {
14611
- if (!sdk || !isAuthenticated || !isInitialized) {
14612
- 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.');
14613
14811
  }
14614
14812
  try {
14615
- const freshUserData = await sdk.users.getCurrentUser();
14813
+ const freshUserData = await currentSdk.users.getCurrentUser();
14616
14814
  setUser(freshUserData);
14617
14815
  }
14618
14816
  catch (error) {
14619
14817
  console.error('Failed to refresh user data:', error);
14620
14818
  throw error;
14621
14819
  }
14622
- }, [sdk, isAuthenticated, isInitialized]);
14623
- // Listen for authentication status changes and refresh user data when tokens are renewed
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
14840
+ // Listen for authentication events from core SDK
14841
+ // Set up immediately when SDK is created (don't wait for isInitialized)
14842
+ // to catch session_restored events that fire during SDK initialization
14624
14843
  react.useEffect(() => {
14625
- if (!authProvider || !isInitialized)
14626
- return;
14627
- // Access the config object with proper type safety
14628
- const providerConfig = authProvider.config;
14629
- if (!providerConfig)
14844
+ if (!sdk)
14630
14845
  return;
14631
- // Set up auth status change handler
14632
- const originalHandler = providerConfig.onAuthStatusChange;
14633
- const authStatusHandler = async (status) => {
14634
- console.log('[PersSDK] Auth status changed:', status);
14635
- // Call original handler first if it exists
14636
- if (originalHandler) {
14637
- await originalHandler(status);
14638
- }
14639
- // If token was refreshed successfully and user is authenticated, reload user data
14640
- if (status === 'authenticated' && isAuthenticated && sdk) {
14641
- try {
14642
- console.log('[PersSDK] Token refreshed, reloading user data...');
14643
- await refreshUserData();
14644
- }
14645
- catch (error) {
14646
- console.error('[PersSDK] Failed to refresh user data after token renewal:', error);
14846
+ const unsubscribe = sdk.events.subscribe((event) => {
14847
+ if (event.domain !== 'authentication')
14848
+ return;
14849
+ // Session restored successfully - sync React state
14850
+ if (event.type === 'session_restored') {
14851
+ console.log('[PersSDK] Session restoration event received, syncing state...');
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
+ });
14647
14863
  }
14648
14864
  }
14649
- // If authentication failed, clear state
14650
- // Frontend app can observe isAuthenticated state change to show custom UI
14651
- if (status === 'auth_failed') {
14652
- console.log('[PersSDK] Authentication failed - session expired');
14653
- // Note: Token clearing already handled by SDK's handleAuthFailure()
14654
- // which calls authProvider.clearTokens() with robust retry logic
14655
- // Clear React state to sync with SDK
14656
- // This triggers re-render, allowing app to show login screen
14865
+ // Session restoration failed or auth error - clear React state
14866
+ if (event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
14867
+ console.log('[PersSDK] Authentication failed - clearing session');
14657
14868
  setAuthenticationState(null, false);
14658
14869
  }
14659
- };
14660
- // Inject our handler into the auth provider config
14661
- providerConfig.onAuthStatusChange = authStatusHandler;
14662
- // Cleanup
14870
+ }, { domains: ['authentication'] });
14663
14871
  return () => {
14664
- if (originalHandler) {
14665
- providerConfig.onAuthStatusChange = originalHandler;
14666
- }
14872
+ unsubscribe();
14667
14873
  };
14668
- }, [authProvider, isInitialized, isAuthenticated, sdk, refreshUserData, setAuthenticationState]);
14874
+ }, [sdk, setAuthenticationState]);
14669
14875
  // iOS/Android: Monitor app state and validate tokens when app becomes active
14670
14876
  react.useEffect(() => {
14671
14877
  if (!sdk || reactNative.Platform.OS === 'web') {
@@ -14692,28 +14898,17 @@ const PersSDKProvider = ({ children, config }) => {
14692
14898
  const contextValue = react.useMemo(() => ({
14693
14899
  // Main SDK instance
14694
14900
  sdk,
14695
- // Manager shortcuts for convenience
14696
- auth: sdk?.auth || null,
14697
- users: sdk?.users || null,
14698
- tokens: sdk?.tokens || null,
14699
- businesses: sdk?.businesses || null,
14700
- campaigns: sdk?.campaigns || null,
14701
- redemptions: sdk?.redemptions || null,
14702
- transactions: sdk?.transactions || null,
14703
- purchases: sdk?.purchases || null,
14704
- tenants: sdk?.tenants || null,
14705
- analytics: sdk?.analytics || null,
14706
- donations: sdk?.donations || null,
14707
14901
  // Platform-specific providers
14708
14902
  authProvider,
14709
14903
  // State
14710
14904
  isInitialized,
14711
14905
  isAuthenticated,
14712
14906
  user,
14713
- // Methods
14907
+ // Methods - expose functions directly, not through refs
14714
14908
  initialize,
14715
14909
  setAuthenticationState,
14716
14910
  refreshUserData,
14911
+ restoreSession,
14717
14912
  }), [
14718
14913
  sdk,
14719
14914
  authProvider,
@@ -14722,7 +14917,8 @@ const PersSDKProvider = ({ children, config }) => {
14722
14917
  user,
14723
14918
  initialize,
14724
14919
  setAuthenticationState,
14725
- refreshUserData
14920
+ refreshUserData,
14921
+ restoreSession
14726
14922
  ]);
14727
14923
  return (jsxRuntime.jsx(SDKContext.Provider, { value: contextValue, children: children }));
14728
14924
  };
@@ -36063,7 +36259,7 @@ const useWeb3 = () => {
36063
36259
  */
36064
36260
  function useTokenBalances(options) {
36065
36261
  const { accountAddress, availableTokens = [], autoLoad = true, refreshInterval = 0 } = options;
36066
- const { isAuthenticated } = usePersSDK();
36262
+ const { isAuthenticated, sdk } = usePersSDK();
36067
36263
  const web3 = useWeb3();
36068
36264
  const [tokenBalances, setTokenBalances] = react.useState([]);
36069
36265
  const [isLoading, setIsLoading] = react.useState(false);
@@ -36165,15 +36361,28 @@ function useTokenBalances(options) {
36165
36361
  loadBalances();
36166
36362
  }
36167
36363
  }, [autoLoad, isAvailable, availableTokens.length, loadBalances]);
36168
- // Optional auto-refresh interval
36364
+ // Event-driven refresh: listen for transaction events instead of polling
36169
36365
  react.useEffect(() => {
36170
- if (refreshInterval > 0 && isAvailable && availableTokens.length > 0) {
36171
- 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...');
36172
36372
  loadBalances();
36173
- }, refreshInterval);
36174
- return () => clearInterval(intervalId);
36175
- }
36176
- }, [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]);
36177
36386
  return {
36178
36387
  tokenBalances,
36179
36388
  isLoading,