@explorins/pers-sdk-react-native 2.1.17 → 2.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/hooks/useAuth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAGlF,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,OAAO;;;;sBA0ByB,MAAM,aAAY,gBAAgB,KAA2B,QAAQ,6BAA6B,CAAC;oCAgCrF,WAAW,KAAG,QAAQ,IAAI,CAAC;kBAgC/C,QAAQ,IAAI,CAAC;;0BA0BL,QAAQ,OAAO,CAAC;gCAmBV,QAAQ,OAAO,CAAC;mCAqBX,MAAM;qBAmBtB,QAAQ,IAAI,CAAC;wBAoBV,QAAQ,OAAO,CAAC;CAwB5D,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/hooks/useAuth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAGlF,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,OAAO;;;;sBAyByB,MAAM,aAAY,gBAAgB,KAA2B,QAAQ,6BAA6B,CAAC;oCA+BrF,WAAW,KAAG,QAAQ,IAAI,CAAC;kBA+B/C,QAAQ,IAAI,CAAC;;0BA2BL,QAAQ,OAAO,CAAC;gCAmBV,QAAQ,OAAO,CAAC;mCAqBX,MAAM;qBAmBtB,QAAQ,IAAI,CAAC;wBAoBV,QAAQ,OAAO,CAAC;CAwB5D,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC"}
@@ -39,7 +39,7 @@ import { AccountOwnerType } from '@explorins/pers-sdk/core';
39
39
  * ```
40
40
  */
41
41
  export const useAuth = () => {
42
- const { sdk, authProvider, isInitialized, isAuthenticated, user, setAuthenticationState, refreshUserData } = usePersSDK();
42
+ const { sdk, authProvider, isInitialized, isAuthenticated, user, refreshUserData } = usePersSDK();
43
43
  /**
44
44
  * Authenticates a user with a JWT token
45
45
  *
@@ -62,8 +62,7 @@ export const useAuth = () => {
62
62
  try {
63
63
  // Set token in auth provider
64
64
  await authProvider.setAccessToken(jwtToken);
65
- // Perform login using the manager
66
- // SDK emits login_success event with user data - PersSDKProvider handles state sync
65
+ // Perform login - emits login_success event which Provider handles
67
66
  const result = await sdk.auth.loginWithToken(jwtToken, userType);
68
67
  return result;
69
68
  }
@@ -89,8 +88,7 @@ export const useAuth = () => {
89
88
  throw new Error('SDK not initialized. Call initialize() first.');
90
89
  }
91
90
  try {
92
- // Use the raw data login from the auth manager
93
- // SDK emits login_success event with user data - PersSDKProvider handles state sync
91
+ // Use the raw data login - emits login_success event which Provider handles
94
92
  const result = await sdk.auth.loginWithRawData(rawUserData);
95
93
  // Set token from result
96
94
  if (result.accessToken) {
@@ -115,17 +113,18 @@ export const useAuth = () => {
115
113
  * ```
116
114
  */
117
115
  const logout = useCallback(async () => {
116
+ if (!sdk) {
117
+ throw new Error('SDK not initialized. Call initialize() first.');
118
+ }
118
119
  try {
119
- if (authProvider) {
120
- await authProvider.clearTokens();
121
- }
122
- setAuthenticationState(null, false);
120
+ // clearAuth emits logout_success event which Provider handles
121
+ await sdk.auth.clearAuth();
123
122
  }
124
123
  catch (error) {
125
124
  console.error('Logout failed:', error);
126
125
  throw error;
127
126
  }
128
- }, [authProvider, setAuthenticationState]);
127
+ }, [sdk]);
129
128
  /**
130
129
  * Retrieves the current authenticated user data
131
130
  *
@@ -199,9 +198,9 @@ export const useAuth = () => {
199
198
  if (!sdk) {
200
199
  throw new Error('SDK not initialized. Call initialize() first.');
201
200
  }
201
+ // clearAuth emits logout_success event which Provider handles
202
202
  await sdk.auth.clearAuth();
203
- setAuthenticationState(null, false);
204
- }, [sdk, setAuthenticationState]);
203
+ }, [sdk]);
205
204
  /**
206
205
  * Checks if the current authentication is valid (non-expired)
207
206
  *
package/dist/index.js CHANGED
@@ -9588,7 +9588,113 @@ class AuthTokenManager {
9588
9588
  const token = await this.getRefreshToken();
9589
9589
  return !!token;
9590
9590
  }
9591
+ /**
9592
+ * Clears only the refresh token.
9593
+ * Used when DPoP keys are regenerated, invalidating the token binding.
9594
+ */
9595
+ async clearRefreshToken() {
9596
+ this.cache.refreshToken = null;
9597
+ await this.storage.remove(AUTH_STORAGE_KEYS.REFRESH_TOKEN);
9598
+ }
9599
+ }
9600
+
9601
+ class IndexedDBTokenStorage {
9602
+ constructor() {
9603
+ this.supportsObjects = true;
9604
+ this.dbPromise = null;
9605
+ if (typeof indexedDB === 'undefined') {
9606
+ console.warn('IndexedDB is not available in this environment');
9607
+ }
9608
+ }
9609
+ getDB() {
9610
+ if (this.dbPromise)
9611
+ return this.dbPromise;
9612
+ this.dbPromise = new Promise((resolve, reject) => {
9613
+ if (typeof indexedDB === 'undefined') {
9614
+ return reject(new Error('IndexedDB not supported'));
9615
+ }
9616
+ const request = indexedDB.open(IndexedDBTokenStorage.DB_NAME, IndexedDBTokenStorage.DB_VERSION);
9617
+ request.onupgradeneeded = (event) => {
9618
+ const db = event.target.result;
9619
+ if (!db.objectStoreNames.contains(IndexedDBTokenStorage.STORE_NAME)) {
9620
+ db.createObjectStore(IndexedDBTokenStorage.STORE_NAME);
9621
+ }
9622
+ };
9623
+ request.onsuccess = (event) => {
9624
+ resolve(event.target.result);
9625
+ };
9626
+ request.onerror = (event) => {
9627
+ reject(event.target.error);
9628
+ };
9629
+ });
9630
+ return this.dbPromise;
9631
+ }
9632
+ async get(key) {
9633
+ try {
9634
+ const db = await this.getDB();
9635
+ return new Promise((resolve, reject) => {
9636
+ const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readonly');
9637
+ const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
9638
+ const request = store.get(key);
9639
+ request.onsuccess = () => resolve(request.result || null);
9640
+ request.onerror = () => reject(request.error);
9641
+ });
9642
+ }
9643
+ catch (e) {
9644
+ console.warn('[IndexedDBTokenStorage] Failed to get key', key, e);
9645
+ return null;
9646
+ }
9647
+ }
9648
+ async set(key, value) {
9649
+ try {
9650
+ const db = await this.getDB();
9651
+ return new Promise((resolve, reject) => {
9652
+ const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
9653
+ const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
9654
+ const request = store.put(value, key);
9655
+ request.onsuccess = () => resolve();
9656
+ request.onerror = () => reject(request.error);
9657
+ });
9658
+ }
9659
+ catch (e) {
9660
+ console.error('[IndexedDBTokenStorage] Failed to set key', key, e);
9661
+ throw e;
9662
+ }
9663
+ }
9664
+ async remove(key) {
9665
+ try {
9666
+ const db = await this.getDB();
9667
+ return new Promise((resolve, reject) => {
9668
+ const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
9669
+ const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
9670
+ const request = store.delete(key);
9671
+ request.onsuccess = () => resolve();
9672
+ request.onerror = () => reject(request.error);
9673
+ });
9674
+ }
9675
+ catch (e) {
9676
+ console.warn('[IndexedDBTokenStorage] Failed to remove key', key, e);
9677
+ }
9678
+ }
9679
+ async clear() {
9680
+ try {
9681
+ const db = await this.getDB();
9682
+ return new Promise((resolve, reject) => {
9683
+ const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
9684
+ const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
9685
+ const request = store.clear();
9686
+ request.onsuccess = () => resolve();
9687
+ request.onerror = () => reject(request.error);
9688
+ });
9689
+ }
9690
+ catch (e) {
9691
+ console.warn('[IndexedDBTokenStorage] Failed to clear storage', e);
9692
+ }
9693
+ }
9591
9694
  }
9695
+ IndexedDBTokenStorage.DB_NAME = 'pers-sdk-storage';
9696
+ IndexedDBTokenStorage.STORE_NAME = 'auth-tokens';
9697
+ IndexedDBTokenStorage.DB_VERSION = 1;
9592
9698
 
9593
9699
  class WebDPoPCryptoProvider {
9594
9700
  get crypto() {
@@ -9724,13 +9830,30 @@ const DPOP_STORAGE_KEYS = {
9724
9830
  PRIVATE: 'pers_dpop_private_key'
9725
9831
  };
9726
9832
  class DPoPManager {
9727
- constructor(storage, cryptoProvider) {
9833
+ constructor(storage, cryptoProvider, callbacks) {
9728
9834
  this.storage = storage;
9729
9835
  this.memoryKeyPair = null;
9730
9836
  this.cryptoProvider = cryptoProvider || new WebDPoPCryptoProvider();
9837
+ this.callbacks = callbacks || {};
9838
+ }
9839
+ /**
9840
+ * Checks if DPoP keys exist in storage without generating new ones.
9841
+ * Useful for detecting if a key regeneration would be needed.
9842
+ */
9843
+ async hasStoredKeys() {
9844
+ if (this.memoryKeyPair)
9845
+ return true;
9846
+ const storedPublic = await this.storage.get(DPOP_STORAGE_KEYS.PUBLIC);
9847
+ const storedPrivate = await this.storage.get(DPOP_STORAGE_KEYS.PRIVATE);
9848
+ return !!(storedPublic && storedPrivate);
9731
9849
  }
9732
9850
  /**
9733
9851
  * Ensures a DPoP key pair exists, loading from storage or generating new one.
9852
+ *
9853
+ * IMPORTANT: If keys are regenerated (not loaded from storage), this will
9854
+ * invalidate any refresh tokens bound to the old keys. The onKeysRegenerated
9855
+ * callback should clear the refresh token to prevent "DPoP proof does not
9856
+ * match refresh token binding" errors.
9734
9857
  */
9735
9858
  async ensureKeyPair() {
9736
9859
  if (this.memoryKeyPair)
@@ -9746,10 +9869,12 @@ class DPoPManager {
9746
9869
  return this.memoryKeyPair;
9747
9870
  }
9748
9871
  catch (e) {
9749
- console.warn('Corrupted DPoP keys in storage, regenerating.');
9872
+ console.warn('[DPoPManager] Corrupted DPoP keys in storage, regenerating.');
9750
9873
  }
9751
9874
  }
9752
9875
  // Generate new key pair
9876
+ // IMPORTANT: This invalidates any existing refresh tokens bound to the old keys
9877
+ console.info('[DPoPManager] Generating new DPoP key pair');
9753
9878
  // Adaptation: If storage supports raw objects (like IndexedDB or Native Keychain),
9754
9879
  // we can generate Non-Extractable keys for maximum security.
9755
9880
  // If storage is text-only (LocalStorage), we must use Extractable keys to serialize them.
@@ -9761,6 +9886,15 @@ class DPoPManager {
9761
9886
  await this.storage.set(DPOP_STORAGE_KEYS.PUBLIC, keyPair.publicKey);
9762
9887
  await this.storage.set(DPOP_STORAGE_KEYS.PRIVATE, keyPair.privateKey);
9763
9888
  this.memoryKeyPair = keyPair;
9889
+ // Notify that keys were regenerated - refresh tokens bound to old keys are now invalid
9890
+ if (this.callbacks.onKeysRegenerated) {
9891
+ try {
9892
+ this.callbacks.onKeysRegenerated();
9893
+ }
9894
+ catch (e) {
9895
+ console.warn('[DPoPManager] onKeysRegenerated callback failed:', e);
9896
+ }
9897
+ }
9764
9898
  return keyPair;
9765
9899
  }
9766
9900
  /**
@@ -9814,16 +9948,48 @@ class DefaultAuthProvider {
9814
9948
  this.config = config;
9815
9949
  this.authTypeCache = null;
9816
9950
  this.authType = config.authType || exports.AccountOwnerType.USER;
9951
+ // Determine if DPoP can be enabled (requires IndexedDB for key persistence)
9952
+ const canUseDPoP = this.canEnableDPoP();
9953
+ this.dpopEnabled = !!(config.dpop?.enabled && canUseDPoP);
9954
+ if (config.dpop?.enabled && !canUseDPoP) {
9955
+ console.warn('[DefaultAuthProvider] DPoP disabled: IndexedDB not available. CryptoKey persistence requires IndexedDB.');
9956
+ }
9817
9957
  const storage = config.storage || this.createStorage();
9818
9958
  this.tokenManager = new AuthTokenManager(storage);
9819
- if (config.dpop?.enabled) {
9820
- this.dpopManager = new DPoPManager(storage, config.dpop.cryptoProvider);
9959
+ if (this.dpopEnabled) {
9960
+ this.dpopManager = new DPoPManager(storage, config.dpop?.cryptoProvider, {
9961
+ // When DPoP keys are regenerated (corrupted/missing), the refresh token
9962
+ // bound to the old keys becomes invalid. Clear it to prevent
9963
+ // "DPoP proof does not match refresh token binding" errors.
9964
+ onKeysRegenerated: () => {
9965
+ console.info('[DefaultAuthProvider] DPoP keys regenerated, clearing invalid refresh token');
9966
+ this.tokenManager.clearRefreshToken().catch(err => {
9967
+ console.warn('[DefaultAuthProvider] Failed to clear refresh token:', err);
9968
+ });
9969
+ }
9970
+ });
9821
9971
  }
9822
9972
  }
9973
+ canEnableDPoP() {
9974
+ // DPoP requires IndexedDB for proper CryptoKey persistence
9975
+ // LocalStorage cannot serialize CryptoKey objects correctly
9976
+ return typeof window !== 'undefined' && typeof indexedDB !== 'undefined';
9977
+ }
9823
9978
  createStorage() {
9824
- return typeof window === 'undefined' || typeof localStorage === 'undefined'
9825
- ? new MemoryTokenStorage()
9826
- : new LocalStorageTokenStorage();
9979
+ // Server-side: use memory storage
9980
+ if (typeof window === 'undefined') {
9981
+ return new MemoryTokenStorage();
9982
+ }
9983
+ // Browser with DPoP enabled: use IndexedDB for CryptoKey persistence
9984
+ if (this.dpopEnabled) {
9985
+ return new IndexedDBTokenStorage();
9986
+ }
9987
+ // Browser without DPoP: use LocalStorage
9988
+ if (typeof localStorage !== 'undefined') {
9989
+ return new LocalStorageTokenStorage();
9990
+ }
9991
+ // Fallback
9992
+ return new MemoryTokenStorage();
9827
9993
  }
9828
9994
  getToken() {
9829
9995
  return this.tokenManager.getAccessToken();
@@ -9905,6 +10071,10 @@ class DefaultAuthProvider {
9905
10071
  async hasValidToken() {
9906
10072
  return this.tokenManager.hasAccessToken();
9907
10073
  }
10074
+ async setAuthType(_authType) {
10075
+ // No-op: authType is derived from JWT payload via getAuthType()
10076
+ // @see getAuthType() - reads accountType directly from JWT
10077
+ }
9908
10078
  async getAuthType() {
9909
10079
  // Read from JWT instead of separate storage for single source of truth
9910
10080
  const token = await this.tokenManager.getAccessToken();
@@ -9944,12 +10114,14 @@ class DefaultAuthProvider {
9944
10114
  * Exported for consumers who need version info.
9945
10115
  * Used internally for X-SDK-Version header.
9946
10116
  *
10117
+ * Version is injected at build time via @rollup/plugin-replace
10118
+ *
9947
10119
  * @module @explorins/pers-sdk/core
9948
10120
  */
9949
10121
  /** SDK package name */
9950
- const SDK_NAME = '@explorins/pers-sdk';
9951
- /** SDK version - TODO: Load dynamically from package.json */
9952
- const SDK_VERSION = '2.1.1';
10122
+ const SDK_NAME = "@explorins/pers-sdk";
10123
+ /** SDK version - injected from package.json at build time */
10124
+ const SDK_VERSION = "2.1.27";
9953
10125
  /** Full SDK identifier for headers */
9954
10126
  const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
9955
10127
 
@@ -10879,7 +11051,14 @@ class AuthManager {
10879
11051
  */
10880
11052
  async loginWithRawData(rawUserData) {
10881
11053
  const authService = this.apiClient.getAuthService();
10882
- return authService.loginUserWithRawData(rawUserData);
11054
+ const result = await authService.loginUserWithRawData(rawUserData);
11055
+ this.events?.emitSuccess({
11056
+ domain: 'authentication',
11057
+ type: 'login_success',
11058
+ userMessage: 'Successfully logged in',
11059
+ details: { user: result.user }
11060
+ });
11061
+ return result;
10883
11062
  }
10884
11063
  /**
10885
11064
  * Get current authenticated user
@@ -10980,6 +11159,11 @@ class AuthManager {
10980
11159
  async clearAuth() {
10981
11160
  const authService = this.apiClient.getAuthService();
10982
11161
  await authService.clearTokens();
11162
+ this.events?.emitSuccess({
11163
+ domain: 'authentication',
11164
+ type: 'logout_success',
11165
+ userMessage: 'Successfully logged out'
11166
+ });
10983
11167
  }
10984
11168
  /**
10985
11169
  * Ensure authentication token is valid
@@ -18849,104 +19033,6 @@ function createPersSDK(httpClient, config) {
18849
19033
  return new PersSDK(httpClient, config);
18850
19034
  }
18851
19035
 
18852
- class IndexedDBTokenStorage {
18853
- constructor() {
18854
- this.supportsObjects = true;
18855
- this.dbPromise = null;
18856
- if (typeof indexedDB === 'undefined') {
18857
- console.warn('IndexedDB is not available in this environment');
18858
- }
18859
- }
18860
- getDB() {
18861
- if (this.dbPromise)
18862
- return this.dbPromise;
18863
- this.dbPromise = new Promise((resolve, reject) => {
18864
- if (typeof indexedDB === 'undefined') {
18865
- return reject(new Error('IndexedDB not supported'));
18866
- }
18867
- const request = indexedDB.open(IndexedDBTokenStorage.DB_NAME, IndexedDBTokenStorage.DB_VERSION);
18868
- request.onupgradeneeded = (event) => {
18869
- const db = event.target.result;
18870
- if (!db.objectStoreNames.contains(IndexedDBTokenStorage.STORE_NAME)) {
18871
- db.createObjectStore(IndexedDBTokenStorage.STORE_NAME);
18872
- }
18873
- };
18874
- request.onsuccess = (event) => {
18875
- resolve(event.target.result);
18876
- };
18877
- request.onerror = (event) => {
18878
- reject(event.target.error);
18879
- };
18880
- });
18881
- return this.dbPromise;
18882
- }
18883
- async get(key) {
18884
- try {
18885
- const db = await this.getDB();
18886
- return new Promise((resolve, reject) => {
18887
- const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readonly');
18888
- const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
18889
- const request = store.get(key);
18890
- request.onsuccess = () => resolve(request.result || null);
18891
- request.onerror = () => reject(request.error);
18892
- });
18893
- }
18894
- catch (e) {
18895
- console.warn('[IndexedDBTokenStorage] Failed to get key', key, e);
18896
- return null;
18897
- }
18898
- }
18899
- async set(key, value) {
18900
- try {
18901
- const db = await this.getDB();
18902
- return new Promise((resolve, reject) => {
18903
- const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
18904
- const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
18905
- const request = store.put(value, key);
18906
- request.onsuccess = () => resolve();
18907
- request.onerror = () => reject(request.error);
18908
- });
18909
- }
18910
- catch (e) {
18911
- console.error('[IndexedDBTokenStorage] Failed to set key', key, e);
18912
- throw e;
18913
- }
18914
- }
18915
- async remove(key) {
18916
- try {
18917
- const db = await this.getDB();
18918
- return new Promise((resolve, reject) => {
18919
- const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
18920
- const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
18921
- const request = store.delete(key);
18922
- request.onsuccess = () => resolve();
18923
- request.onerror = () => reject(request.error);
18924
- });
18925
- }
18926
- catch (e) {
18927
- console.warn('[IndexedDBTokenStorage] Failed to remove key', key, e);
18928
- }
18929
- }
18930
- async clear() {
18931
- try {
18932
- const db = await this.getDB();
18933
- return new Promise((resolve, reject) => {
18934
- const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
18935
- const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
18936
- const request = store.clear();
18937
- request.onsuccess = () => resolve();
18938
- request.onerror = () => reject(request.error);
18939
- });
18940
- }
18941
- catch (e) {
18942
- console.warn('[IndexedDBTokenStorage] Failed to clear storage', e);
18943
- }
18944
- }
18945
- }
18946
- IndexedDBTokenStorage.DB_NAME = 'pers-sdk-storage';
18947
- IndexedDBTokenStorage.STORE_NAME = 'auth-tokens';
18948
- IndexedDBTokenStorage.DB_VERSION = 1;
18949
-
18950
19036
  /**
18951
19037
  * Environment Detection Utility
18952
19038
  * Detects runtime environment for platform-specific behaviors
@@ -19272,11 +19358,19 @@ class ReactNativeSecureStorage {
19272
19358
  * React Native Unified Auth Provider
19273
19359
  *
19274
19360
  * Creates a platform-specific auth provider that automatically selects the appropriate storage:
19275
- * - Web: Uses LocalStorageTokenStorage from core SDK
19361
+ * - Web: Uses IndexedDBTokenStorage for DPoP (CryptoKey persistence) or LocalStorage
19276
19362
  * - Mobile: Uses AsyncStorage-based storage
19277
19363
  */
19278
19364
  // Use React Native's built-in platform detection
19279
19365
  const isWebPlatform = reactNative.Platform.OS === 'web';
19366
+ /**
19367
+ * Check if IndexedDB is available (web environments only)
19368
+ * Uses globalThis to avoid TypeScript errors in React Native context
19369
+ */
19370
+ function hasIndexedDB() {
19371
+ return typeof globalThis !== 'undefined' &&
19372
+ typeof globalThis.indexedDB !== 'undefined';
19373
+ }
19280
19374
  /**
19281
19375
  * Create a React Native auth provider with platform-appropriate storage
19282
19376
  *
@@ -19294,9 +19388,29 @@ function createReactNativeAuthProvider(projectKey, config = {}) {
19294
19388
  }
19295
19389
  const { keyPrefix = `pers_${projectKey.slice(0, 8)}_`, debug = false, customStorage, authType = exports.AccountOwnerType.USER, dpop } = config;
19296
19390
  // Platform-specific storage selection
19297
- const tokenStorage = customStorage || (isWebPlatform
19298
- ? new LocalStorageTokenStorage()
19299
- : new ReactNativeSecureStorage(keyPrefix));
19391
+ // Web with DPoP: Use IndexedDB for CryptoKey persistence
19392
+ // Web without DPoP or no IndexedDB: Use LocalStorage
19393
+ // Mobile: Use ReactNativeSecureStorage
19394
+ let tokenStorage;
19395
+ if (customStorage) {
19396
+ tokenStorage = customStorage;
19397
+ }
19398
+ else if (isWebPlatform) {
19399
+ // On web, prefer IndexedDB when DPoP is enabled for proper CryptoKey persistence
19400
+ const dpopEnabled = dpop?.enabled ?? true;
19401
+ if (dpopEnabled && hasIndexedDB()) {
19402
+ tokenStorage = new IndexedDBTokenStorage();
19403
+ }
19404
+ else {
19405
+ if (dpopEnabled && !hasIndexedDB()) {
19406
+ console.warn('[ReactNativeAuthProvider] DPoP requested but IndexedDB not available. Falling back to LocalStorage (DPoP will be disabled by DefaultAuthProvider).');
19407
+ }
19408
+ tokenStorage = new LocalStorageTokenStorage();
19409
+ }
19410
+ }
19411
+ else {
19412
+ tokenStorage = new ReactNativeSecureStorage(keyPrefix);
19413
+ }
19300
19414
  // Prepare DPoP config ensuring enabled is boolean (default true)
19301
19415
  const dpopConfig = dpop ? {
19302
19416
  enabled: dpop.enabled ?? true,
@@ -19598,6 +19712,7 @@ const SDKContext = react.createContext(null);
19598
19712
  */
19599
19713
  const PersSDKProvider = ({ children, config }) => {
19600
19714
  const initializingRef = react.useRef(false);
19715
+ const eventUnsubscribeRef = react.useRef(null);
19601
19716
  // State refs for stable functions to read current values
19602
19717
  const sdkRef = react.useRef(null);
19603
19718
  const isInitializedRef = react.useRef(false);
@@ -19651,6 +19766,21 @@ const PersSDKProvider = ({ children, config }) => {
19651
19766
  };
19652
19767
  // Initialize PersSDK with platform-aware auth provider
19653
19768
  const sdkInstance = new PersSDK(httpClient, sdkConfig);
19769
+ // Subscribe to auth events BEFORE exposing SDK to consumers
19770
+ // This ensures no events are missed due to React timing
19771
+ eventUnsubscribeRef.current = sdkInstance.events.subscribe((event) => {
19772
+ if (event.type === 'session_restored' || event.type === 'login_success') {
19773
+ const details = event.details;
19774
+ if (details?.user) {
19775
+ setUser(details.user);
19776
+ setIsAuthenticated(true);
19777
+ }
19778
+ }
19779
+ if (event.type === 'logout_success' || event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
19780
+ setUser(null);
19781
+ setIsAuthenticated(false);
19782
+ }
19783
+ }, { domains: ['authentication'], replay: true });
19654
19784
  setAuthProvider(authProvider);
19655
19785
  setSdk(sdkInstance);
19656
19786
  setIsInitialized(true);
@@ -19664,6 +19794,15 @@ const PersSDKProvider = ({ children, config }) => {
19664
19794
  initializingRef.current = false;
19665
19795
  }
19666
19796
  }, [isInitialized]);
19797
+ // Cleanup subscription on unmount
19798
+ react.useEffect(() => {
19799
+ return () => {
19800
+ if (eventUnsubscribeRef.current) {
19801
+ eventUnsubscribeRef.current();
19802
+ eventUnsubscribeRef.current = null;
19803
+ }
19804
+ };
19805
+ }, []);
19667
19806
  const setAuthenticationState = react.useCallback((user, isAuthenticated) => {
19668
19807
  setUser(user);
19669
19808
  setIsAuthenticated(isAuthenticated);
@@ -19710,32 +19849,7 @@ const PersSDKProvider = ({ children, config }) => {
19710
19849
  console.error('[PersSDK] Failed to restore session:', error);
19711
19850
  throw error;
19712
19851
  }
19713
- }, [setAuthenticationState]); // Depends on setAuthenticationState
19714
- // Listen for authentication events from core SDK
19715
- // Set up immediately when SDK is created (don't wait for isInitialized)
19716
- // to catch session_restored events that fire during SDK initialization
19717
- react.useEffect(() => {
19718
- if (!sdk)
19719
- return;
19720
- const unsubscribe = sdk.events.subscribe((event) => {
19721
- // Handle both session restoration and fresh login
19722
- if (event.type === 'session_restored' || event.type === 'login_success') {
19723
- const details = event.details;
19724
- if (details?.user) {
19725
- console.log(`[PersSDK] ${event.type}: syncing user state`);
19726
- setAuthenticationState(details.user, true);
19727
- }
19728
- }
19729
- // Session restoration failed or auth error - clear React state
19730
- if (event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
19731
- console.log('[PersSDK] Authentication failed - clearing session');
19732
- setAuthenticationState(null, false);
19733
- }
19734
- }, { domains: ['authentication'], replay: true });
19735
- return () => {
19736
- unsubscribe();
19737
- };
19738
- }, [sdk, setAuthenticationState]);
19852
+ }, [setAuthenticationState]);
19739
19853
  // iOS/Android: Monitor app state and validate tokens when app becomes active
19740
19854
  react.useEffect(() => {
19741
19855
  if (!sdk || reactNative.Platform.OS === 'web') {
@@ -19743,7 +19857,6 @@ const PersSDKProvider = ({ children, config }) => {
19743
19857
  }
19744
19858
  const handleAppStateChange = async (nextAppState) => {
19745
19859
  if (nextAppState === 'active' && isAuthenticated) {
19746
- console.log('[PersSDK] App became active - validating tokens');
19747
19860
  try {
19748
19861
  // Trigger token validation when app resumes from background
19749
19862
  // This ensures tokens are checked after extended inactivity
@@ -19833,7 +19946,7 @@ const usePersSDK = () => {
19833
19946
  * ```
19834
19947
  */
19835
19948
  const useAuth = () => {
19836
- const { sdk, authProvider, isInitialized, isAuthenticated, user, setAuthenticationState, refreshUserData } = usePersSDK();
19949
+ const { sdk, authProvider, isInitialized, isAuthenticated, user, refreshUserData } = usePersSDK();
19837
19950
  /**
19838
19951
  * Authenticates a user with a JWT token
19839
19952
  *
@@ -19856,8 +19969,7 @@ const useAuth = () => {
19856
19969
  try {
19857
19970
  // Set token in auth provider
19858
19971
  await authProvider.setAccessToken(jwtToken);
19859
- // Perform login using the manager
19860
- // SDK emits login_success event with user data - PersSDKProvider handles state sync
19972
+ // Perform login - emits login_success event which Provider handles
19861
19973
  const result = await sdk.auth.loginWithToken(jwtToken, userType);
19862
19974
  return result;
19863
19975
  }
@@ -19883,8 +19995,7 @@ const useAuth = () => {
19883
19995
  throw new Error('SDK not initialized. Call initialize() first.');
19884
19996
  }
19885
19997
  try {
19886
- // Use the raw data login from the auth manager
19887
- // SDK emits login_success event with user data - PersSDKProvider handles state sync
19998
+ // Use the raw data login - emits login_success event which Provider handles
19888
19999
  const result = await sdk.auth.loginWithRawData(rawUserData);
19889
20000
  // Set token from result
19890
20001
  if (result.accessToken) {
@@ -19909,17 +20020,18 @@ const useAuth = () => {
19909
20020
  * ```
19910
20021
  */
19911
20022
  const logout = react.useCallback(async () => {
20023
+ if (!sdk) {
20024
+ throw new Error('SDK not initialized. Call initialize() first.');
20025
+ }
19912
20026
  try {
19913
- if (authProvider) {
19914
- await authProvider.clearTokens();
19915
- }
19916
- setAuthenticationState(null, false);
20027
+ // clearAuth emits logout_success event which Provider handles
20028
+ await sdk.auth.clearAuth();
19917
20029
  }
19918
20030
  catch (error) {
19919
20031
  console.error('Logout failed:', error);
19920
20032
  throw error;
19921
20033
  }
19922
- }, [authProvider, setAuthenticationState]);
20034
+ }, [sdk]);
19923
20035
  /**
19924
20036
  * Retrieves the current authenticated user data
19925
20037
  *
@@ -19993,9 +20105,9 @@ const useAuth = () => {
19993
20105
  if (!sdk) {
19994
20106
  throw new Error('SDK not initialized. Call initialize() first.');
19995
20107
  }
20108
+ // clearAuth emits logout_success event which Provider handles
19996
20109
  await sdk.auth.clearAuth();
19997
- setAuthenticationState(null, false);
19998
- }, [sdk, setAuthenticationState]);
20110
+ }, [sdk]);
19999
20111
  /**
20000
20112
  * Checks if the current authentication is valid (non-expired)
20001
20113
  *