@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/hooks/useTokenBalances.d.ts.map +1 -1
- package/dist/hooks/useTokenBalances.js +21 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +315 -106
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +1 -12
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +67 -77
- package/package.json +2 -2
- package/src/hooks/useTokenBalances.ts +23 -9
- package/src/index.ts +1 -1
- package/src/providers/PersSDKProvider.tsx +75 -96
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
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).
|
|
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
|
|
14804
|
+
}, [config, isInitialized, initialize]);
|
|
14610
14805
|
const refreshUserData = react.useCallback(async () => {
|
|
14611
|
-
|
|
14612
|
-
|
|
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
|
|
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
|
-
}, [
|
|
14623
|
-
|
|
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 (!
|
|
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
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
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
|
-
//
|
|
14650
|
-
|
|
14651
|
-
|
|
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
|
-
|
|
14665
|
-
providerConfig.onAuthStatusChange = originalHandler;
|
|
14666
|
-
}
|
|
14872
|
+
unsubscribe();
|
|
14667
14873
|
};
|
|
14668
|
-
}, [
|
|
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
|
-
//
|
|
36364
|
+
// Event-driven refresh: listen for transaction events instead of polling
|
|
36169
36365
|
react.useEffect(() => {
|
|
36170
|
-
if (refreshInterval
|
|
36171
|
-
|
|
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
|
-
}
|
|
36174
|
-
|
|
36175
|
-
|
|
36176
|
-
|
|
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,
|