@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.
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +11 -12
- package/dist/index.js +264 -152
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +26 -27
- package/dist/providers/react-native-auth-provider.d.ts +1 -1
- package/dist/providers/react-native-auth-provider.d.ts.map +1 -1
- package/dist/providers/react-native-auth-provider.js +33 -5
- package/package.json +2 -2
- package/src/hooks/useAuth.ts +12 -14
- package/src/providers/PersSDKProvider.tsx +29 -31
- package/src/providers/react-native-auth-provider.ts +33 -8
|
@@ -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;;;;
|
|
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"}
|
package/dist/hooks/useAuth.js
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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 (
|
|
9820
|
-
this.dpopManager = new DPoPManager(storage, config.dpop
|
|
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
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
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 =
|
|
9951
|
-
/** SDK version -
|
|
9952
|
-
const SDK_VERSION =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
19298
|
-
|
|
19299
|
-
|
|
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]);
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
19914
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
19998
|
-
}, [sdk, setAuthenticationState]);
|
|
20110
|
+
}, [sdk]);
|
|
19999
20111
|
/**
|
|
20000
20112
|
* Checks if the current authentication is valid (non-expired)
|
|
20001
20113
|
*
|