@explorins/pers-sdk-react-native 2.0.5 → 2.1.2
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/README.md +117 -4
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useTokenBalances.d.ts +140 -0
- package/dist/hooks/useTokenBalances.d.ts.map +1 -0
- package/dist/hooks/useTokenBalances.js +213 -0
- package/dist/hooks/useWeb3.d.ts +2 -3
- package/dist/hooks/useWeb3.d.ts.map +1 -1
- package/dist/hooks/useWeb3.js +42 -32
- package/dist/index.d.ts +81 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29780 -29395
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +27 -38
- package/dist/storage/rn-secure-storage.d.ts.map +1 -1
- package/dist/storage/rn-secure-storage.js +24 -10
- package/package.json +2 -4
- package/src/hooks/index.ts +7 -1
- package/src/hooks/useTokenBalances.ts +290 -0
- package/src/hooks/useWeb3.ts +45 -35
- package/src/index.ts +90 -9
- package/src/providers/PersSDKProvider.tsx +29 -44
- package/src/storage/rn-secure-storage.ts +27 -10
package/src/hooks/useWeb3.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
2
|
import { usePersSDK } from '../providers/PersSDKProvider';
|
|
3
|
+
import { Web3Manager } from '@explorins/pers-sdk/web3';
|
|
3
4
|
import type {
|
|
4
5
|
TokenBalance,
|
|
5
6
|
TokenBalanceRequest,
|
|
6
7
|
TokenCollectionRequest,
|
|
7
8
|
TokenCollection,
|
|
8
|
-
TokenMetadata
|
|
9
|
+
TokenMetadata,
|
|
10
|
+
AccountOwnedTokensResult
|
|
9
11
|
} from '@explorins/pers-sdk/web3';
|
|
10
12
|
import type { ChainData } from '@explorins/pers-sdk/web3-chain';
|
|
11
13
|
import type { TokenDTO } from '@explorins/pers-shared';
|
|
12
|
-
import type { AccountOwnedTokensResult } from '@explorins/pers-sdk';
|
|
13
14
|
|
|
14
15
|
// Re-export for convenience
|
|
15
|
-
export type { AccountOwnedTokensResult } from '@explorins/pers-sdk';
|
|
16
|
+
export type { AccountOwnedTokensResult } from '@explorins/pers-sdk/web3';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* React hook for Web3 operations in the PERS SDK
|
|
@@ -71,6 +72,15 @@ export const useWeb3 = () => {
|
|
|
71
72
|
console.warn('SDK not authenticated. Some web3 operations may fail.');
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Create Web3Manager instance lazily
|
|
77
|
+
* Web3Manager is now separate from PersSDK since v2.0.11+
|
|
78
|
+
*/
|
|
79
|
+
const web3 = useMemo(() => {
|
|
80
|
+
if (!sdk) return null;
|
|
81
|
+
return new Web3Manager(sdk.api());
|
|
82
|
+
}, [sdk]);
|
|
83
|
+
|
|
74
84
|
/**
|
|
75
85
|
* Retrieves token balance for a specific wallet and contract
|
|
76
86
|
*
|
|
@@ -91,18 +101,18 @@ export const useWeb3 = () => {
|
|
|
91
101
|
* ```
|
|
92
102
|
*/
|
|
93
103
|
const getTokenBalance = useCallback(async (request: TokenBalanceRequest): Promise<TokenBalance> => {
|
|
94
|
-
if (!isInitialized || !
|
|
104
|
+
if (!isInitialized || !web3) {
|
|
95
105
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
try {
|
|
99
|
-
const result = await
|
|
109
|
+
const result = await web3.getTokenBalance(request);
|
|
100
110
|
return result;
|
|
101
111
|
} catch (error) {
|
|
102
112
|
console.error('Failed to fetch token balance:', error);
|
|
103
113
|
throw error;
|
|
104
114
|
}
|
|
105
|
-
}, [
|
|
115
|
+
}, [web3, isInitialized]);
|
|
106
116
|
|
|
107
117
|
/**
|
|
108
118
|
* Retrieves metadata for a specific token (useful for NFTs)
|
|
@@ -124,88 +134,88 @@ export const useWeb3 = () => {
|
|
|
124
134
|
* ```
|
|
125
135
|
*/
|
|
126
136
|
const getTokenMetadata = useCallback(async (request: TokenBalanceRequest): Promise<TokenMetadata | null> => {
|
|
127
|
-
if (!isInitialized || !
|
|
137
|
+
if (!isInitialized || !web3) {
|
|
128
138
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
try {
|
|
132
|
-
const result = await
|
|
142
|
+
const result = await web3.getTokenMetadata(request);
|
|
133
143
|
return result;
|
|
134
144
|
} catch (error) {
|
|
135
145
|
console.error('Failed to fetch token metadata:', error);
|
|
136
146
|
throw error;
|
|
137
147
|
}
|
|
138
|
-
}, [
|
|
148
|
+
}, [web3, isInitialized]);
|
|
139
149
|
|
|
140
150
|
const getTokenCollection = useCallback(async (request: TokenCollectionRequest): Promise<TokenCollection> => {
|
|
141
|
-
if (!isInitialized || !
|
|
151
|
+
if (!isInitialized || !web3) {
|
|
142
152
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
try {
|
|
146
|
-
const result = await
|
|
156
|
+
const result = await web3.getTokenCollection(request);
|
|
147
157
|
return result;
|
|
148
158
|
} catch (error) {
|
|
149
159
|
console.error('Failed to fetch token collection:', error);
|
|
150
160
|
throw error;
|
|
151
161
|
}
|
|
152
|
-
}, [
|
|
162
|
+
}, [web3, isInitialized]);
|
|
153
163
|
|
|
154
164
|
const resolveIPFSUrl = useCallback(async (url: string, chainId: number): Promise<string> => {
|
|
155
|
-
if (!isInitialized || !
|
|
165
|
+
if (!isInitialized || !web3) {
|
|
156
166
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
try {
|
|
160
|
-
const result = await
|
|
170
|
+
const result = await web3.resolveIPFSUrl(url, chainId);
|
|
161
171
|
return result;
|
|
162
172
|
} catch (error) {
|
|
163
173
|
console.error('Failed to resolve IPFS URL:', error);
|
|
164
174
|
throw error;
|
|
165
175
|
}
|
|
166
|
-
}, [
|
|
176
|
+
}, [web3, isInitialized]);
|
|
167
177
|
|
|
168
178
|
const fetchAndProcessMetadata = useCallback(async (tokenUri: string, chainId: number): Promise<TokenMetadata | null> => {
|
|
169
|
-
if (!isInitialized || !
|
|
179
|
+
if (!isInitialized || !web3) {
|
|
170
180
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
171
181
|
}
|
|
172
182
|
|
|
173
183
|
try {
|
|
174
|
-
const result = await
|
|
184
|
+
const result = await web3.fetchAndProcessMetadata(tokenUri, chainId);
|
|
175
185
|
return result;
|
|
176
186
|
} catch (error) {
|
|
177
187
|
console.error('Failed to fetch and process metadata:', error);
|
|
178
188
|
throw error;
|
|
179
189
|
}
|
|
180
|
-
}, [
|
|
190
|
+
}, [web3, isInitialized]);
|
|
181
191
|
|
|
182
192
|
const getChainDataById = useCallback(async (chainId: number): Promise<ChainData | null> => {
|
|
183
|
-
if (!isInitialized || !
|
|
193
|
+
if (!isInitialized || !web3) {
|
|
184
194
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
try {
|
|
188
|
-
const result = await
|
|
198
|
+
const result = await web3.getChainDataById(chainId);
|
|
189
199
|
return result;
|
|
190
200
|
} catch (error) {
|
|
191
201
|
console.error('Failed to fetch chain data:', error);
|
|
192
202
|
throw error;
|
|
193
203
|
}
|
|
194
|
-
}, [
|
|
204
|
+
}, [web3, isInitialized]);
|
|
195
205
|
|
|
196
206
|
const getExplorerUrl = useCallback(async (chainId: number, address: string, type: 'address' | 'tx'): Promise<string> => {
|
|
197
|
-
if (!isInitialized || !
|
|
207
|
+
if (!isInitialized || !web3) {
|
|
198
208
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
199
209
|
}
|
|
200
210
|
|
|
201
211
|
try {
|
|
202
|
-
const result = await
|
|
212
|
+
const result = await web3.getExplorerUrl(chainId, address, type);
|
|
203
213
|
return result;
|
|
204
214
|
} catch (error) {
|
|
205
215
|
console.error('Failed to generate explorer URL:', error);
|
|
206
216
|
throw error;
|
|
207
217
|
}
|
|
208
|
-
}, [
|
|
218
|
+
}, [web3, isInitialized]);
|
|
209
219
|
|
|
210
220
|
// ==========================================
|
|
211
221
|
// HELPER METHODS (delegating to core SDK)
|
|
@@ -227,9 +237,9 @@ export const useWeb3 = () => {
|
|
|
227
237
|
* @see {@link getAccountOwnedTokensFromContract} - Recommended helper that handles this automatically
|
|
228
238
|
*/
|
|
229
239
|
const extractTokenIds = useCallback((token: TokenDTO): string[] | undefined => {
|
|
230
|
-
// Pure function - delegates to
|
|
231
|
-
return
|
|
232
|
-
}, [
|
|
240
|
+
// Pure function - delegates to Web3Manager (no initialization required)
|
|
241
|
+
return web3?.extractTokenIds(token);
|
|
242
|
+
}, [web3]);
|
|
233
243
|
|
|
234
244
|
/**
|
|
235
245
|
* Get owned tokens from a specific token contract for any blockchain address.
|
|
@@ -269,12 +279,12 @@ export const useWeb3 = () => {
|
|
|
269
279
|
token: TokenDTO,
|
|
270
280
|
maxTokens: number = 50
|
|
271
281
|
): Promise<AccountOwnedTokensResult> => {
|
|
272
|
-
if (!isInitialized || !
|
|
282
|
+
if (!isInitialized || !web3) {
|
|
273
283
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
274
284
|
}
|
|
275
285
|
|
|
276
|
-
return
|
|
277
|
-
}, [
|
|
286
|
+
return web3.getAccountOwnedTokensFromContract(accountAddress, token, maxTokens);
|
|
287
|
+
}, [web3, isInitialized]);
|
|
278
288
|
|
|
279
289
|
/**
|
|
280
290
|
* Build a TokenCollectionRequest from a TokenDTO.
|
|
@@ -293,11 +303,11 @@ export const useWeb3 = () => {
|
|
|
293
303
|
token: TokenDTO,
|
|
294
304
|
maxTokens: number = 50
|
|
295
305
|
): TokenCollectionRequest => {
|
|
296
|
-
if (!
|
|
306
|
+
if (!web3) {
|
|
297
307
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
298
308
|
}
|
|
299
|
-
return
|
|
300
|
-
}, [
|
|
309
|
+
return web3.buildCollectionRequest(accountAddress, token, maxTokens);
|
|
310
|
+
}, [web3]);
|
|
301
311
|
|
|
302
312
|
return {
|
|
303
313
|
getTokenBalance,
|
|
@@ -311,7 +321,7 @@ export const useWeb3 = () => {
|
|
|
311
321
|
extractTokenIds,
|
|
312
322
|
getAccountOwnedTokensFromContract,
|
|
313
323
|
buildCollectionRequest,
|
|
314
|
-
isAvailable: isInitialized && !!
|
|
324
|
+
isAvailable: isInitialized && !!web3,
|
|
315
325
|
};
|
|
316
326
|
};
|
|
317
327
|
|
package/src/index.ts
CHANGED
|
@@ -247,6 +247,7 @@ export {
|
|
|
247
247
|
export {
|
|
248
248
|
useAuth,
|
|
249
249
|
useTokens,
|
|
250
|
+
useTokenBalances,
|
|
250
251
|
useTransactions,
|
|
251
252
|
useTransactionSigner,
|
|
252
253
|
SigningStatus,
|
|
@@ -265,11 +266,14 @@ export {
|
|
|
265
266
|
} from './hooks';
|
|
266
267
|
|
|
267
268
|
// Re-export signing status types for convenience
|
|
268
|
-
export type { OnStatusUpdateFn, StatusUpdateData, SigningStatusType } from './hooks';
|
|
269
|
+
export type { OnStatusUpdateFn, StatusUpdateData, SigningStatusType, TransactionSigningResult, SubmissionResult, AuthenticatedUser } from './hooks';
|
|
269
270
|
|
|
270
271
|
// Re-export event types for convenience
|
|
271
272
|
export type { EventsHook, PersEvent, EventHandler, EventFilter, Unsubscribe } from './hooks';
|
|
272
273
|
|
|
274
|
+
// Re-export token balance types for convenience
|
|
275
|
+
export type { TokenBalanceWithToken, UseTokenBalancesOptions, UseTokenBalancesResult } from './hooks';
|
|
276
|
+
|
|
273
277
|
// ==============================================================================
|
|
274
278
|
// PLATFORM ADAPTERS
|
|
275
279
|
// ==============================================================================
|
|
@@ -420,31 +424,108 @@ export {
|
|
|
420
424
|
buildTransferRequest,
|
|
421
425
|
buildPOSTransferRequest,
|
|
422
426
|
buildPOSBurnRequest,
|
|
423
|
-
buildSubmissionRequest
|
|
427
|
+
buildSubmissionRequest,
|
|
428
|
+
ClientTransactionType,
|
|
429
|
+
extractDeadlineFromSigningData,
|
|
430
|
+
buildPendingTransactionData
|
|
424
431
|
} from '@explorins/pers-sdk/transaction';
|
|
425
432
|
|
|
426
|
-
export type { POSAuthorizationOptions } from '@explorins/pers-sdk/transaction';
|
|
433
|
+
export type { POSAuthorizationOptions, PendingTransactionParams } from '@explorins/pers-sdk/transaction';
|
|
427
434
|
|
|
428
435
|
// ==============================================================================
|
|
429
436
|
// ERROR CLASSES (for instanceof checks)
|
|
430
437
|
// ==============================================================================
|
|
431
438
|
|
|
432
439
|
/**
|
|
433
|
-
*
|
|
440
|
+
* Structured error handling for PERS SDK
|
|
441
|
+
*
|
|
442
|
+
* The SDK provides comprehensive error classes and utilities for consistent error handling
|
|
443
|
+
* across all operations. Errors follow the StructuredError pattern from @explorins/pers-shared.
|
|
444
|
+
*
|
|
445
|
+
* **Error Classes:**
|
|
446
|
+
* - `PersApiError` - API errors with structured backend details
|
|
447
|
+
* - `AuthenticationError` - Authentication/authorization failures (401)
|
|
448
|
+
*
|
|
449
|
+
* **Error Utilities:**
|
|
450
|
+
* - `ErrorUtils.getMessage(error)` - Extract user-friendly message
|
|
451
|
+
* - `ErrorUtils.getStatus(error)` - Get HTTP status code
|
|
452
|
+
* - `ErrorUtils.isRetryable(error)` - Check if operation should be retried
|
|
453
|
+
* - `ErrorUtils.isTokenExpiredError(error)` - Detect token expiration
|
|
434
454
|
*
|
|
435
455
|
* @example
|
|
456
|
+
* **Basic Error Handling:**
|
|
436
457
|
* ```typescript
|
|
437
|
-
* import { PersApiError } from '@explorins/pers-sdk-react-native';
|
|
458
|
+
* import { PersApiError, ErrorUtils } from '@explorins/pers-sdk-react-native';
|
|
438
459
|
*
|
|
439
460
|
* try {
|
|
440
461
|
* await sdk.campaigns.claimCampaign({ campaignId });
|
|
441
462
|
* } catch (error) {
|
|
442
463
|
* if (error instanceof PersApiError) {
|
|
443
|
-
*
|
|
444
|
-
* console.log(error.code);
|
|
445
|
-
* console.log(error.status);
|
|
464
|
+
* // Structured error with backend details
|
|
465
|
+
* console.log('Error code:', error.code); // 'CAMPAIGN_BUSINESS_REQUIRED'
|
|
466
|
+
* console.log('Status:', error.status); // 400
|
|
467
|
+
* console.log('Message:', error.message); // Backend error message
|
|
468
|
+
* console.log('User message:', error.userMessage); // User-friendly message
|
|
469
|
+
* console.log('Retryable:', error.retryable); // false
|
|
470
|
+
* } else {
|
|
471
|
+
* // Generic error fallback
|
|
472
|
+
* const message = ErrorUtils.getMessage(error);
|
|
473
|
+
* console.error('Operation failed:', message);
|
|
446
474
|
* }
|
|
447
475
|
* }
|
|
448
476
|
* ```
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* **Error Utilities:**
|
|
480
|
+
* ```typescript
|
|
481
|
+
* import { ErrorUtils } from '@explorins/pers-sdk-react-native';
|
|
482
|
+
*
|
|
483
|
+
* try {
|
|
484
|
+
* await someOperation();
|
|
485
|
+
* } catch (error) {
|
|
486
|
+
* const status = ErrorUtils.getStatus(error); // Extract status code
|
|
487
|
+
* const message = ErrorUtils.getMessage(error); // Extract message
|
|
488
|
+
* const retryable = ErrorUtils.isRetryable(error); // Check if retryable
|
|
489
|
+
*
|
|
490
|
+
* if (ErrorUtils.isTokenExpiredError(error)) {
|
|
491
|
+
* // Handle token expiration
|
|
492
|
+
* await refreshToken();
|
|
493
|
+
* } else if (retryable) {
|
|
494
|
+
* // Retry operation
|
|
495
|
+
* await retry(someOperation);
|
|
496
|
+
* } else {
|
|
497
|
+
* // Show error to user
|
|
498
|
+
* showError(message);
|
|
499
|
+
* }
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* **React Native Error Display:**
|
|
505
|
+
* ```typescript
|
|
506
|
+
* import { PersApiError, ErrorUtils } from '@explorins/pers-sdk-react-native';
|
|
507
|
+
* import { Alert } from 'react-native';
|
|
508
|
+
*
|
|
509
|
+
* const handleError = (error: unknown) => {
|
|
510
|
+
* if (error instanceof PersApiError) {
|
|
511
|
+
* // Show structured error
|
|
512
|
+
* Alert.alert(
|
|
513
|
+
* 'Error',
|
|
514
|
+
* error.userMessage || error.message,
|
|
515
|
+
* [
|
|
516
|
+
* { text: 'OK' },
|
|
517
|
+
* error.retryable && { text: 'Retry', onPress: retry }
|
|
518
|
+
* ].filter(Boolean)
|
|
519
|
+
* );
|
|
520
|
+
* } else {
|
|
521
|
+
* // Show generic error
|
|
522
|
+
* Alert.alert('Error', ErrorUtils.getMessage(error));
|
|
523
|
+
* }
|
|
524
|
+
* };
|
|
525
|
+
* ```
|
|
449
526
|
*/
|
|
450
|
-
export {
|
|
527
|
+
export {
|
|
528
|
+
PersApiError,
|
|
529
|
+
AuthenticationError,
|
|
530
|
+
ErrorUtils
|
|
531
|
+
} from '@explorins/pers-sdk/core';
|
|
@@ -132,6 +132,11 @@ export const PersSDKProvider: React.FC<{
|
|
|
132
132
|
}
|
|
133
133
|
}, [isInitialized]);
|
|
134
134
|
|
|
135
|
+
const setAuthenticationState = useCallback((user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => {
|
|
136
|
+
setUser(user);
|
|
137
|
+
setIsAuthenticated(isAuthenticated);
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
135
140
|
// Auto-initialize if config is provided
|
|
136
141
|
useEffect(() => {
|
|
137
142
|
if (config && !isInitialized && !initializingRef.current) {
|
|
@@ -141,11 +146,6 @@ export const PersSDKProvider: React.FC<{
|
|
|
141
146
|
}
|
|
142
147
|
}, [config, isInitialized, initialize]);
|
|
143
148
|
|
|
144
|
-
const setAuthenticationState = useCallback((user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => {
|
|
145
|
-
setUser(user);
|
|
146
|
-
setIsAuthenticated(isAuthenticated);
|
|
147
|
-
}, []);
|
|
148
|
-
|
|
149
149
|
const refreshUserData = useCallback(async (): Promise<void> => {
|
|
150
150
|
if (!sdk || !isAuthenticated || !isInitialized) {
|
|
151
151
|
throw new Error('SDK not initialized or not authenticated. Cannot refresh user data.');
|
|
@@ -159,53 +159,38 @@ export const PersSDKProvider: React.FC<{
|
|
|
159
159
|
throw error;
|
|
160
160
|
}
|
|
161
161
|
}, [sdk, isAuthenticated, isInitialized]);
|
|
162
|
-
|
|
163
|
-
//
|
|
162
|
+
// Listen for authentication events from core SDK
|
|
163
|
+
// Set up immediately when SDK is created (don't wait for isInitialized)
|
|
164
|
+
// to catch session_restored events that fire during SDK initialization
|
|
164
165
|
useEffect(() => {
|
|
165
|
-
if (!
|
|
166
|
+
if (!sdk) return;
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (!providerConfig) return;
|
|
168
|
+
const unsubscribe = sdk.events.subscribe((event) => {
|
|
169
|
+
if (event.domain !== 'authentication') return;
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
// Session restored successfully - sync React state
|
|
172
|
+
if (event.type === 'session_restored') {
|
|
173
|
+
console.log('[PersSDK] Session restoration event received, syncing state...');
|
|
174
|
+
sdk.users.getCurrentUser()
|
|
175
|
+
.then(userData => {
|
|
176
|
+
setAuthenticationState(userData, true);
|
|
177
|
+
})
|
|
178
|
+
.catch(error => {
|
|
179
|
+
console.error('[PersSDK] Failed to sync restored session:', error);
|
|
180
|
+
});
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
console.log('[PersSDK] Token refreshed, reloading user data...');
|
|
186
|
-
await refreshUserData();
|
|
187
|
-
} catch (error) {
|
|
188
|
-
console.error('[PersSDK] Failed to refresh user data after token renewal:', error);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// If authentication failed, clear state
|
|
193
|
-
if (status === 'auth_failed') {
|
|
194
|
-
console.log('[PersSDK] Authentication failed, clearing state');
|
|
182
|
+
|
|
183
|
+
// Session restoration failed or auth error - clear React state
|
|
184
|
+
if (event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
|
|
185
|
+
console.log('[PersSDK] Authentication failed - clearing session');
|
|
195
186
|
setAuthenticationState(null, false);
|
|
196
187
|
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Inject our handler into the auth provider config
|
|
200
|
-
providerConfig.onAuthStatusChange = authStatusHandler;
|
|
201
|
-
|
|
202
|
-
// Cleanup
|
|
188
|
+
}, { domains: ['authentication'] });
|
|
189
|
+
|
|
203
190
|
return () => {
|
|
204
|
-
|
|
205
|
-
providerConfig.onAuthStatusChange = originalHandler;
|
|
206
|
-
}
|
|
191
|
+
unsubscribe();
|
|
207
192
|
};
|
|
208
|
-
}, [
|
|
193
|
+
}, [sdk, setAuthenticationState]);
|
|
209
194
|
|
|
210
195
|
// iOS/Android: Monitor app state and validate tokens when app becomes active
|
|
211
196
|
useEffect(() => {
|
|
@@ -117,22 +117,39 @@ export class ReactNativeSecureStorage implements TokenStorage {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
async clear(): Promise<void> {
|
|
120
|
-
|
|
120
|
+
const clearPromises: Promise<void>[] = [];
|
|
121
|
+
|
|
122
|
+
// Clear all known secure keys with retry logic
|
|
121
123
|
for (const key of this.SECURE_KEYS) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
clearPromises.push(
|
|
125
|
+
(async () => {
|
|
126
|
+
try {
|
|
127
|
+
if (Keychain) {
|
|
128
|
+
await Keychain.resetGenericPassword({ service: this.getKeyName(key) });
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.warn(`[ReactNativeSecureStorage] Retry clearing ${key}`);
|
|
132
|
+
// Retry once
|
|
133
|
+
try {
|
|
134
|
+
if (Keychain) {
|
|
135
|
+
await Keychain.resetGenericPassword({ service: this.getKeyName(key) });
|
|
136
|
+
}
|
|
137
|
+
} catch (retryError) {
|
|
138
|
+
console.error(`[ReactNativeSecureStorage] Failed to clear ${key} after retry:`, retryError);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
})()
|
|
142
|
+
);
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
//
|
|
145
|
+
// Wait for all Keychain clearing to complete (or fail)
|
|
146
|
+
await Promise.allSettled(clearPromises);
|
|
147
|
+
|
|
148
|
+
// Always clear AsyncStorage fallback
|
|
132
149
|
try {
|
|
133
150
|
await this.fallbackStorage.clear();
|
|
134
151
|
} catch (e) {
|
|
135
|
-
console.
|
|
152
|
+
console.error('[ReactNativeSecureStorage] Failed to clear AsyncStorage:', e);
|
|
136
153
|
}
|
|
137
154
|
}
|
|
138
155
|
|