@explorins/pers-sdk-react-native 1.5.30 → 1.5.32

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.
Files changed (76) hide show
  1. package/README.md +9 -0
  2. package/dist/hooks/index.d.ts +2 -2
  3. package/dist/hooks/index.d.ts.map +1 -1
  4. package/dist/hooks/index.js +1 -1
  5. package/dist/hooks/useAnalytics.d.ts.map +1 -1
  6. package/dist/hooks/useAnalytics.js +0 -1
  7. package/dist/hooks/useAuth.d.ts +0 -1
  8. package/dist/hooks/useAuth.d.ts.map +1 -1
  9. package/dist/hooks/useAuth.js +5 -18
  10. package/dist/hooks/useBusiness.d.ts.map +1 -1
  11. package/dist/hooks/useBusiness.js +0 -9
  12. package/dist/hooks/useCampaigns.d.ts.map +1 -1
  13. package/dist/hooks/useCampaigns.js +0 -10
  14. package/dist/hooks/useDonations.d.ts.map +1 -1
  15. package/dist/hooks/useDonations.js +0 -1
  16. package/dist/hooks/useFiles.d.ts.map +1 -1
  17. package/dist/hooks/useFiles.js +0 -4
  18. package/dist/hooks/usePurchases.d.ts.map +1 -1
  19. package/dist/hooks/usePurchases.js +0 -3
  20. package/dist/hooks/useRedemptions.d.ts +4 -1
  21. package/dist/hooks/useRedemptions.d.ts.map +1 -1
  22. package/dist/hooks/useRedemptions.js +6 -17
  23. package/dist/hooks/useTenants.d.ts.map +1 -1
  24. package/dist/hooks/useTenants.js +0 -3
  25. package/dist/hooks/useTokens.d.ts.map +1 -1
  26. package/dist/hooks/useTokens.js +0 -6
  27. package/dist/hooks/useTransactionSigner.d.ts +13 -1
  28. package/dist/hooks/useTransactionSigner.d.ts.map +1 -1
  29. package/dist/hooks/useTransactionSigner.js +59 -2
  30. package/dist/hooks/useTransactions.d.ts +4 -1
  31. package/dist/hooks/useTransactions.d.ts.map +1 -1
  32. package/dist/hooks/useTransactions.js +9 -10
  33. package/dist/hooks/useUserStatus.d.ts.map +1 -1
  34. package/dist/hooks/useUserStatus.js +0 -3
  35. package/dist/hooks/useUsers.d.ts.map +1 -1
  36. package/dist/hooks/useUsers.js +0 -7
  37. package/dist/hooks/useWeb3.d.ts +26 -42
  38. package/dist/hooks/useWeb3.d.ts.map +1 -1
  39. package/dist/hooks/useWeb3.js +27 -53
  40. package/dist/index.d.ts +2 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +330 -304
  43. package/dist/index.js.map +1 -1
  44. package/dist/providers/PersSDKProvider.d.ts +1 -3
  45. package/dist/providers/PersSDKProvider.d.ts.map +1 -1
  46. package/dist/providers/PersSDKProvider.js +14 -11
  47. package/dist/providers/react-native-auth-provider.d.ts.map +1 -1
  48. package/dist/providers/react-native-auth-provider.js +4 -0
  49. package/dist/providers/rn-dpop-provider.d.ts +2 -4
  50. package/dist/providers/rn-dpop-provider.d.ts.map +1 -1
  51. package/dist/providers/rn-dpop-provider.js +50 -23
  52. package/dist/storage/rn-secure-storage.d.ts +1 -0
  53. package/dist/storage/rn-secure-storage.d.ts.map +1 -1
  54. package/dist/storage/rn-secure-storage.js +9 -12
  55. package/package.json +2 -2
  56. package/src/hooks/index.ts +10 -2
  57. package/src/hooks/useAnalytics.ts +0 -1
  58. package/src/hooks/useAuth.ts +4 -25
  59. package/src/hooks/useBusiness.ts +0 -9
  60. package/src/hooks/useCampaigns.ts +0 -10
  61. package/src/hooks/useDonations.ts +0 -1
  62. package/src/hooks/useFiles.ts +0 -4
  63. package/src/hooks/usePurchases.ts +0 -3
  64. package/src/hooks/useRedemptions.ts +7 -21
  65. package/src/hooks/useTenants.ts +0 -3
  66. package/src/hooks/useTokens.ts +0 -6
  67. package/src/hooks/useTransactionSigner.ts +74 -4
  68. package/src/hooks/useTransactions.ts +10 -12
  69. package/src/hooks/useUserStatus.ts +0 -3
  70. package/src/hooks/useUsers.ts +0 -7
  71. package/src/hooks/useWeb3.ts +28 -68
  72. package/src/index.ts +4 -0
  73. package/src/providers/PersSDKProvider.tsx +19 -20
  74. package/src/providers/react-native-auth-provider.ts +5 -0
  75. package/src/providers/rn-dpop-provider.ts +85 -45
  76. package/src/storage/rn-secure-storage.ts +13 -13
@@ -9,73 +9,58 @@ import type {
9
9
  } from '@explorins/pers-sdk/web3';
10
10
  import type { ChainData } from '@explorins/pers-sdk/web3-chain';
11
11
 
12
- /**
13
- * Wallet information interface
14
- *
15
- * @interface WalletInfo
16
- * @property {string | null} address - Wallet address if connected
17
- * @property {boolean} isConnected - Whether wallet is connected
18
- */
19
- export interface WalletInfo {
20
- address: string | null;
21
- isConnected: boolean;
22
- }
23
-
24
12
  /**
25
13
  * React hook for Web3 operations in the PERS SDK
26
14
  *
27
15
  * Provides comprehensive Web3 functionality including token balance queries,
28
16
  * metadata retrieval, collection management, IPFS resolution, and blockchain
29
- * explorer integration. Supports multi-chain operations and wallet management.
17
+ * explorer integration. Supports multi-chain operations.
18
+ *
19
+ * Note: Wallet addresses should be obtained from `user.wallets` via `usePersSDK()`.
30
20
  *
31
21
  * @returns Web3 hook with methods for blockchain operations
32
22
  *
33
23
  * @example
34
24
  * ```typescript
35
25
  * function Web3Component() {
36
- * const {
37
- * getTokenBalance,
38
- * getTokenMetadata,
39
- * getWalletInfo,
40
- * accountAddress
41
- * } = useWeb3();
26
+ * const { user } = usePersSDK();
27
+ * const { getTokenBalance, getTokenMetadata } = useWeb3();
28
+ *
29
+ * // Get wallet address from user
30
+ * const walletAddress = user?.wallets?.[0]?.address;
42
31
  *
43
32
  * const loadTokenData = async () => {
44
- * try {
45
- * if (!accountAddress) {
46
- * console.log('No wallet connected');
47
- * return;
48
- * }
49
- *
50
- * const balance = await getTokenBalance({
51
- * walletAddress: accountAddress,
52
- * contractAddress: '0x123...',
53
- * chainId: 1
54
- * });
55
- *
56
- * console.log('Token balance:', balance);
57
- * } catch (error) {
58
- * console.error('Failed to load token data:', error);
33
+ * if (!walletAddress) {
34
+ * console.log('No wallet connected');
35
+ * return;
59
36
  * }
37
+ *
38
+ * const balance = await getTokenBalance({
39
+ * walletAddress,
40
+ * contractAddress: '0x123...',
41
+ * chainId: 1
42
+ * });
43
+ *
44
+ * console.log('Token balance:', balance);
60
45
  * };
61
46
  *
62
47
  * return (
63
- * <div>
64
- * {accountAddress ? (
65
- * <div>
66
- * <p>Wallet: {accountAddress}</p>
67
- * <button onClick={loadTokenData}>Load Tokens</button>
68
- * </div>
48
+ * <View>
49
+ * {walletAddress ? (
50
+ * <View>
51
+ * <Text>Wallet: {walletAddress}</Text>
52
+ * <Button onPress={loadTokenData} title="Load Tokens" />
53
+ * </View>
69
54
  * ) : (
70
- * <p>No wallet connected</p>
55
+ * <Text>No wallet connected</Text>
71
56
  * )}
72
- * </div>
57
+ * </View>
73
58
  * );
74
59
  * }
75
60
  * ```
76
61
  */
77
62
  export const useWeb3 = () => {
78
- const { sdk, isInitialized, isAuthenticated, accountAddress } = usePersSDK();
63
+ const { sdk, isInitialized, isAuthenticated } = usePersSDK();
79
64
 
80
65
  if (!isAuthenticated && isInitialized) {
81
66
  console.warn('SDK not authenticated. Some web3 operations may fail.');
@@ -101,14 +86,12 @@ export const useWeb3 = () => {
101
86
  * ```
102
87
  */
103
88
  const getTokenBalance = useCallback(async (request: TokenBalanceRequest): Promise<TokenBalance> => {
104
- console.log('[useWeb3] getTokenBalance called with request:', request);
105
89
  if (!isInitialized || !sdk) {
106
90
  throw new Error('SDK not initialized. Call initialize() first.');
107
91
  }
108
92
 
109
93
  try {
110
94
  const result = await sdk.web3.getTokenBalance(request);
111
- console.log('Token balance fetched successfully:', result);
112
95
  return result;
113
96
  } catch (error) {
114
97
  console.error('Failed to fetch token balance:', error);
@@ -142,7 +125,6 @@ export const useWeb3 = () => {
142
125
 
143
126
  try {
144
127
  const result = await sdk.web3.getTokenMetadata(request);
145
- console.log('Token metadata fetched successfully:', result);
146
128
  return result;
147
129
  } catch (error) {
148
130
  console.error('Failed to fetch token metadata:', error);
@@ -157,7 +139,6 @@ export const useWeb3 = () => {
157
139
 
158
140
  try {
159
141
  const result = await sdk.web3.getTokenCollection(request);
160
- console.log('Token collection fetched successfully:', result);
161
142
  return result;
162
143
  } catch (error) {
163
144
  console.error('Failed to fetch token collection:', error);
@@ -172,7 +153,6 @@ export const useWeb3 = () => {
172
153
 
173
154
  try {
174
155
  const result = await sdk.web3.resolveIPFSUrl(url, chainId);
175
- console.log('IPFS URL resolved successfully:', result);
176
156
  return result;
177
157
  } catch (error) {
178
158
  console.error('Failed to resolve IPFS URL:', error);
@@ -187,7 +167,6 @@ export const useWeb3 = () => {
187
167
 
188
168
  try {
189
169
  const result = await sdk.web3.fetchAndProcessMetadata(tokenUri, chainId);
190
- console.log('Metadata fetched and processed successfully:', result);
191
170
  return result;
192
171
  } catch (error) {
193
172
  console.error('Failed to fetch and process metadata:', error);
@@ -202,7 +181,6 @@ export const useWeb3 = () => {
202
181
 
203
182
  try {
204
183
  const result = await sdk.web3.getChainDataById(chainId);
205
- console.log('Chain data fetched successfully:', result);
206
184
  return result;
207
185
  } catch (error) {
208
186
  console.error('Failed to fetch chain data:', error);
@@ -217,7 +195,6 @@ export const useWeb3 = () => {
217
195
 
218
196
  try {
219
197
  const result = await sdk.web3.getExplorerUrl(chainId, address, type);
220
- console.log('Explorer URL generated successfully:', result);
221
198
  return result;
222
199
  } catch (error) {
223
200
  console.error('Failed to generate explorer URL:', error);
@@ -225,21 +202,6 @@ export const useWeb3 = () => {
225
202
  }
226
203
  }, [sdk, isInitialized]);
227
204
 
228
- const getWalletInfo = useCallback(async (): Promise<WalletInfo | null> => {
229
- if (!isInitialized) {
230
- throw new Error('SDK not initialized. Call initialize() first.');
231
- }
232
- if (!accountAddress) {
233
- console.warn('No account address available');
234
- return null;
235
- }
236
-
237
- return {
238
- address: accountAddress,
239
- isConnected: !!accountAddress,
240
- };
241
- }, [isInitialized, accountAddress]);
242
-
243
205
  return {
244
206
  getTokenBalance,
245
207
  getTokenMetadata,
@@ -248,8 +210,6 @@ export const useWeb3 = () => {
248
210
  fetchAndProcessMetadata,
249
211
  getChainDataById,
250
212
  getExplorerUrl,
251
- getWalletInfo,
252
- accountAddress: isInitialized ? accountAddress : null,
253
213
  isAvailable: isInitialized && !!sdk?.web3,
254
214
  };
255
215
  };
package/src/index.ts CHANGED
@@ -228,6 +228,7 @@ export {
228
228
  useTokens,
229
229
  useTransactions,
230
230
  useTransactionSigner,
231
+ SigningStatus,
231
232
  useBusiness,
232
233
  useCampaigns,
233
234
  useRedemptions,
@@ -241,6 +242,9 @@ export {
241
242
  useDonations
242
243
  } from './hooks';
243
244
 
245
+ // Re-export signing status types for convenience
246
+ export type { OnStatusUpdateFn, StatusUpdateData, SigningStatusType } from './hooks';
247
+
244
248
  // ==============================================================================
245
249
  // PLATFORM ADAPTERS
246
250
  // ==============================================================================
@@ -1,6 +1,6 @@
1
- import React, { createContext, useContext, useState, ReactNode, useCallback, useRef, useEffect } from 'react';
1
+ import React, { createContext, useContext, useState, ReactNode, useCallback, useRef, useEffect, useMemo } from 'react';
2
2
  import { Platform } from 'react-native';
3
- import { PersSDK, PersConfig, DefaultAuthProvider, DPoPCryptoProvider } from '@explorins/pers-sdk/core';
3
+ import { PersSDK, PersConfig, DefaultAuthProvider } from '@explorins/pers-sdk/core';
4
4
  import { ReactNativeHttpClient } from './react-native-http-client';
5
5
  import { createReactNativeAuthProvider } from './react-native-auth-provider';
6
6
  import { ReactNativeDPoPProvider } from './rn-dpop-provider';
@@ -41,21 +41,17 @@ export interface PersSDKContext {
41
41
  analytics: AnalyticsManager | null;
42
42
  donations: DonationManager | null;
43
43
 
44
- // Legacy support (deprecated but kept for backward compatibility)
45
- business: BusinessManager | null;
46
-
47
- // Platform-specific providers - now uses unified auth provider
44
+ // Platform-specific providers
48
45
  authProvider: DefaultAuthProvider | null;
49
46
 
50
47
  // State
51
48
  isInitialized: boolean;
52
49
  isAuthenticated: boolean;
53
50
  user: UserDTO | AdminDTO | null;
54
- accountAddress: string | null;
55
51
 
56
52
  // Methods
57
53
  initialize: (config: PersConfig) => Promise<void>;
58
- setAuthenticationState: (user: UserDTO | AdminDTO | null, accountAddress: string | null, isAuthenticated: boolean) => void;
54
+ setAuthenticationState: (user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => void;
59
55
  refreshUserData: () => Promise<void>;
60
56
  }
61
57
 
@@ -74,7 +70,6 @@ export const PersSDKProvider: React.FC<{
74
70
  const [isInitialized, setIsInitialized] = useState(false);
75
71
  const [isAuthenticated, setIsAuthenticated] = useState(false);
76
72
  const [user, setUser] = useState<UserDTO | AdminDTO | null>(null);
77
- const [accountAddress, setAccountAddress] = useState<string | null>(null);
78
73
 
79
74
  const initialize = useCallback(async (config: PersConfig) => {
80
75
  // Prevent multiple initializations
@@ -88,10 +83,10 @@ export const PersSDKProvider: React.FC<{
88
83
  // Create HTTP client
89
84
  const httpClient = new ReactNativeHttpClient();
90
85
 
91
- // Ensure DPoP is enabled by default for all platforms
92
- const dpopConfig = config.dpop || {};
86
+ const dpopConfig = config.dpop ?? {};
87
+
93
88
  const isDpopEnabled = dpopConfig.enabled ?? true;
94
-
89
+
95
90
  // Prepare DPoP settings for Auth Provider
96
91
  const dpopSettings = {
97
92
  enabled: isDpopEnabled,
@@ -146,9 +141,8 @@ export const PersSDKProvider: React.FC<{
146
141
  }
147
142
  }, [config, isInitialized, initialize]);
148
143
 
149
- const setAuthenticationState = useCallback((user: UserDTO | AdminDTO | null, accountAddress: string | null, isAuthenticated: boolean) => {
144
+ const setAuthenticationState = useCallback((user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => {
150
145
  setUser(user);
151
- setAccountAddress(accountAddress);
152
146
  setIsAuthenticated(isAuthenticated);
153
147
  }, []);
154
148
 
@@ -166,7 +160,7 @@ export const PersSDKProvider: React.FC<{
166
160
  }
167
161
  }, [sdk, isAuthenticated, isInitialized]);
168
162
 
169
- const contextValue: PersSDKContext = {
163
+ const contextValue: PersSDKContext = useMemo(() => ({
170
164
  // Main SDK instance
171
165
  sdk,
172
166
 
@@ -183,9 +177,6 @@ export const PersSDKProvider: React.FC<{
183
177
  analytics: sdk?.analytics || null,
184
178
  donations: sdk?.donations || null,
185
179
 
186
- // Legacy support (deprecated but kept for backward compatibility)
187
- business: sdk?.businesses || null,
188
-
189
180
  // Platform-specific providers
190
181
  authProvider,
191
182
 
@@ -193,13 +184,21 @@ export const PersSDKProvider: React.FC<{
193
184
  isInitialized,
194
185
  isAuthenticated,
195
186
  user,
196
- accountAddress,
197
187
 
198
188
  // Methods
199
189
  initialize,
200
190
  setAuthenticationState,
201
191
  refreshUserData,
202
- };
192
+ }), [
193
+ sdk,
194
+ authProvider,
195
+ isInitialized,
196
+ isAuthenticated,
197
+ user,
198
+ initialize,
199
+ setAuthenticationState,
200
+ refreshUserData
201
+ ]);
203
202
 
204
203
  return (
205
204
  <SDKContext.Provider value={contextValue}>
@@ -73,6 +73,11 @@ export function createReactNativeAuthProvider(
73
73
  enabled: dpop.enabled ?? true,
74
74
  cryptoProvider: dpop.cryptoProvider
75
75
  } : undefined;
76
+
77
+ // DEBUG LOGGING
78
+ if (debug) {
79
+ console.log('[ReactNativeAuthProvider] Creating provider with DPoP config:', JSON.stringify(dpopConfig));
80
+ }
76
81
 
77
82
  // Return DefaultAuthProvider configured with platform-appropriate storage
78
83
  return new DefaultAuthProvider({
@@ -12,7 +12,6 @@ if (Platform.OS !== 'web') {
12
12
  }
13
13
  } else {
14
14
  // on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
15
- // But to be safe, we can mock or throw
16
15
  crypto = {
17
16
  generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
18
17
  };
@@ -22,83 +21,124 @@ export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
22
21
  /**
23
22
  * Generates a new key pair (ES256 recommended)
24
23
  *
25
- * Note: options.extractable is ignored because for React Native Keychain storage,
26
- * we MUST export the keys as JWK/strings. We rely on the Keychain encryption
27
- * for security at rest (High Security), making the key "Extractable" in memory
28
- * but protected by hardware encryption when stored.
24
+ * Uses WebCrypto API (crypto.subtle) for cross-platform compatibility.
25
+ * Falls back to Node.js crypto API on iOS if needed.
29
26
  */
30
27
  async generateKeyPair(options?: { extractable?: boolean }): Promise<DPoPKeyPair> {
31
- // Generate P-256 Key Pair
28
+ // Try WebCrypto API first (works on both iOS and Android)
29
+ if (crypto.subtle) {
30
+ try {
31
+ const keyPair = await crypto.subtle.generateKey(
32
+ { name: 'ECDSA', namedCurve: 'P-256' },
33
+ true, // extractable - required for JWK export
34
+ ['sign', 'verify']
35
+ );
36
+
37
+ const publicKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
38
+ const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
39
+
40
+ return {
41
+ publicKey: publicKeyJwk,
42
+ privateKey: privateKeyJwk
43
+ };
44
+ } catch (err) {
45
+ console.warn('[DPoP] WebCrypto API failed, trying Node.js crypto API', err);
46
+ }
47
+ }
48
+
49
+ // Fallback: Node.js crypto API (works on iOS)
32
50
  return new Promise((resolve, reject) => {
33
- crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err: any, publicKey: any, privateKey: any) => {
34
- if (err) return reject(err);
35
-
36
- // Export to JWK for SDK Compatibility
37
- // We use 'export' because supportsObjects=false in our storage forces the SDK
38
- // to expect serializable keys.
39
- const pubJwk = publicKey.export({ format: 'jwk' });
40
- const privJwk = privateKey.export({ format: 'jwk' });
41
-
42
- resolve({
43
- publicKey: pubJwk,
44
- privateKey: privJwk
45
- });
46
- });
51
+ crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err: any, publicKey: any, privateKey: any) => {
52
+ if (err) return reject(err);
53
+ try {
54
+ const pJwk = publicKey.export({ format: 'jwk' });
55
+ const prJwk = privateKey.export({ format: 'jwk' });
56
+ resolve({ publicKey: pJwk, privateKey: prJwk });
57
+ } catch (exportErr) {
58
+ reject(exportErr);
59
+ }
60
+ });
47
61
  });
48
62
  }
49
63
 
50
64
  async signProof(payload: Record<string, any>, keyPair: DPoPKeyPair): Promise<string> {
51
65
  // 1. Construct Header
52
66
  const header = {
53
- typ: 'dpop+jwt',
54
- alg: 'ES256',
55
- jwk: keyPair.publicKey
67
+ typ: 'dpop+jwt',
68
+ alg: 'ES256',
69
+ jwk: keyPair.publicKey
56
70
  };
57
71
 
58
72
  // 2. Add Claims (iat/jti)
59
73
  const finalPayload = {
60
- ...payload,
61
- iat: payload.iat || Math.floor(Date.now() / 1000),
62
- jti: payload.jti || crypto.randomUUID()
74
+ ...payload,
75
+ iat: payload.iat || Math.floor(Date.now() / 1000),
76
+ jti: payload.jti || crypto.randomUUID()
63
77
  };
64
-
78
+
65
79
  // 3. Encode
66
80
  const b64Header = this.base64Url(JSON.stringify(header));
67
81
  const b64Payload = this.base64Url(JSON.stringify(finalPayload));
68
82
  const signingInput = `${b64Header}.${b64Payload}`;
69
-
70
- // 4. Sign
83
+
84
+ // 4. Sign - try WebCrypto first, fallback to Node.js crypto
85
+ if (crypto.subtle) {
86
+ try {
87
+ const privateKey = await crypto.subtle.importKey(
88
+ 'jwk',
89
+ keyPair.privateKey,
90
+ { name: 'ECDSA', namedCurve: 'P-256' },
91
+ false,
92
+ ['sign']
93
+ );
94
+
95
+ const signatureBuffer = await crypto.subtle.sign(
96
+ { name: 'ECDSA', hash: 'SHA-256' },
97
+ privateKey,
98
+ Buffer.from(signingInput)
99
+ );
100
+
101
+ // WebCrypto returns signature in IEEE P1363 format (r || s), which is what we need for JWT
102
+ return `${signingInput}.${this.base64UrlBuffer(Buffer.from(signatureBuffer))}`;
103
+ } catch (err) {
104
+ console.warn('[DPoP] WebCrypto sign failed, trying Node.js crypto', err);
105
+ }
106
+ }
107
+
108
+ // Fallback: Node.js crypto API
71
109
  const sign = crypto.createSign('SHA256');
72
110
  sign.update(signingInput);
73
- // sign.end() is not required/available in quick-crypto as it doesn't strictly follow stream interface
74
-
75
- // Import private key back from JWK to sign
76
- // The keyPair.privateKey is a JsonWebKey object because we exported it earlier.
77
- // quick-crypto createPrivateKey expects jwk object if format is jwk
78
111
  const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey as any, format: 'jwk' });
79
- const signature = sign.sign(privateKeyObj);
80
- // signature is a Buffer in quick-crypto
81
-
112
+ const signature = sign.sign(privateKeyObj);
113
+
82
114
  return `${signingInput}.${this.base64UrlBuffer(signature)}`;
83
115
  }
84
116
 
85
117
  async hashAccessToken(accessToken: string): Promise<string> {
118
+ // Try WebCrypto first
119
+ if (crypto.subtle) {
120
+ try {
121
+ const hashBuffer = await crypto.subtle.digest('SHA-256', Buffer.from(accessToken));
122
+ return this.base64UrlBuffer(Buffer.from(hashBuffer));
123
+ } catch (err) {
124
+ // Fallback to Node.js crypto
125
+ }
126
+ }
127
+
86
128
  const hash = crypto.createHash('sha256').update(accessToken).digest();
87
- // digest returns Buffer in quick-crypto
88
129
  return this.base64UrlBuffer(hash);
89
130
  }
90
131
 
91
132
  // --- Helpers ---
92
133
  private base64Url(str: string): string {
93
- return this.base64UrlBuffer(Buffer.from(str));
134
+ return this.base64UrlBuffer(Buffer.from(str));
94
135
  }
95
136
 
96
137
  private base64UrlBuffer(buf: Buffer | Uint8Array): string {
97
- // Ensure we have a Buffer
98
- const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
99
- return buffer.toString('base64')
100
- .replace(/=/g, '')
101
- .replace(/\+/g, '-')
102
- .replace(/\//g, '_');
138
+ const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
139
+ return buffer.toString('base64')
140
+ .replace(/=/g, '')
141
+ .replace(/\+/g, '-')
142
+ .replace(/\//g, '_');
103
143
  }
104
144
  }
@@ -1,10 +1,10 @@
1
1
  import { Platform } from 'react-native';
2
- import AsyncStorage from '@react-native-async-storage/async-storage';
3
2
  import {
4
3
  TokenStorage,
5
4
  DPOP_STORAGE_KEYS,
6
5
  AUTH_STORAGE_KEYS
7
6
  } from '@explorins/pers-sdk/core';
7
+ import { AsyncStorageTokenStorage } from './async-storage-token-storage';
8
8
 
9
9
  // Conditionally require Keychain to avoid Web bundler errors
10
10
  let Keychain: any;
@@ -32,7 +32,11 @@ export class ReactNativeSecureStorage implements TokenStorage {
32
32
  AUTH_STORAGE_KEYS.PROVIDER_TOKEN
33
33
  ]);
34
34
 
35
- constructor(private keyPrefix: string = 'pers_tokens_') {}
35
+ private fallbackStorage: AsyncStorageTokenStorage;
36
+
37
+ constructor(private keyPrefix: string = 'pers_tokens_') {
38
+ this.fallbackStorage = new AsyncStorageTokenStorage(keyPrefix);
39
+ }
36
40
 
37
41
  async set(key: string, value: unknown): Promise<void> {
38
42
  const prefixedKey = this.getKeyName(key);
@@ -52,11 +56,11 @@ export class ReactNativeSecureStorage implements TokenStorage {
52
56
  } catch (e) {
53
57
  console.warn(`[ReactNativeSecureStorage] Keychain set failed for ${key}, falling back to AsyncStorage`, e);
54
58
  // Fallback to AsyncStorage if Keychain fails
55
- await AsyncStorage.setItem(prefixedKey, stringValue);
59
+ await this.fallbackStorage.set(key, stringValue);
56
60
  }
57
61
  } else {
58
62
  // Store standard config/metadata in AsyncStorage
59
- await AsyncStorage.setItem(prefixedKey, stringValue);
63
+ await this.fallbackStorage.set(key, stringValue);
60
64
  }
61
65
  }
62
66
 
@@ -78,14 +82,14 @@ export class ReactNativeSecureStorage implements TokenStorage {
78
82
 
79
83
  // Fallback: Check AsyncStorage if not found in Keychain or Keychain failed
80
84
  try {
81
- const val = await AsyncStorage.getItem(prefixedKey);
85
+ const val = await this.fallbackStorage.get(key);
82
86
  return val ? this.tryParse(val) : null;
83
87
  } catch (e) {
84
88
  return null;
85
89
  }
86
90
  } else {
87
91
  try {
88
- const val = await AsyncStorage.getItem(prefixedKey);
92
+ const val = await this.fallbackStorage.get(key);
89
93
  return val ? this.tryParse(val) : null;
90
94
  } catch (e) {
91
95
  console.warn(`[ReactNativeSecureStorage] Failed to access AsyncStorage for ${key}`, e);
@@ -106,9 +110,9 @@ export class ReactNativeSecureStorage implements TokenStorage {
106
110
  console.warn(`[ReactNativeSecureStorage] Failed to reset keychain for ${key}`, e);
107
111
  }
108
112
  // Always remove from fallback storage too, just in case
109
- await AsyncStorage.removeItem(prefixedKey);
113
+ await this.fallbackStorage.remove(key);
110
114
  } else {
111
- await AsyncStorage.removeItem(prefixedKey);
115
+ await this.fallbackStorage.remove(key);
112
116
  }
113
117
  }
114
118
 
@@ -126,11 +130,7 @@ export class ReactNativeSecureStorage implements TokenStorage {
126
130
 
127
131
  // Clear AsyncStorage keys related to PERS
128
132
  try {
129
- const allKeys = await AsyncStorage.getAllKeys();
130
- const ourKeys = allKeys.filter(k => k.startsWith(this.keyPrefix));
131
- if (ourKeys.length > 0) {
132
- await AsyncStorage.multiRemove(ourKeys);
133
- }
133
+ await this.fallbackStorage.clear();
134
134
  } catch (e) {
135
135
  console.warn('[ReactNativeSecureStorage] Failed to clear AsyncStorage', e);
136
136
  }