@explorins/pers-sdk-react-native 1.5.31 → 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.
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useAnalytics.d.ts.map +1 -1
- package/dist/hooks/useAnalytics.js +0 -1
- package/dist/hooks/useAuth.d.ts +0 -1
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +5 -18
- package/dist/hooks/useBusiness.d.ts.map +1 -1
- package/dist/hooks/useBusiness.js +0 -9
- package/dist/hooks/useCampaigns.d.ts.map +1 -1
- package/dist/hooks/useCampaigns.js +0 -10
- package/dist/hooks/useDonations.d.ts.map +1 -1
- package/dist/hooks/useDonations.js +0 -1
- package/dist/hooks/useFiles.d.ts.map +1 -1
- package/dist/hooks/useFiles.js +0 -4
- package/dist/hooks/usePurchases.d.ts.map +1 -1
- package/dist/hooks/usePurchases.js +0 -3
- package/dist/hooks/useRedemptions.d.ts +4 -1
- package/dist/hooks/useRedemptions.d.ts.map +1 -1
- package/dist/hooks/useRedemptions.js +6 -17
- package/dist/hooks/useTenants.d.ts.map +1 -1
- package/dist/hooks/useTenants.js +0 -3
- package/dist/hooks/useTokens.d.ts.map +1 -1
- package/dist/hooks/useTokens.js +0 -6
- package/dist/hooks/useTransactionSigner.d.ts +13 -1
- package/dist/hooks/useTransactionSigner.d.ts.map +1 -1
- package/dist/hooks/useTransactionSigner.js +59 -2
- package/dist/hooks/useTransactions.d.ts +4 -1
- package/dist/hooks/useTransactions.d.ts.map +1 -1
- package/dist/hooks/useTransactions.js +9 -10
- package/dist/hooks/useUserStatus.d.ts.map +1 -1
- package/dist/hooks/useUserStatus.js +0 -3
- package/dist/hooks/useUsers.d.ts.map +1 -1
- package/dist/hooks/useUsers.js +0 -7
- package/dist/hooks/useWeb3.d.ts +26 -42
- package/dist/hooks/useWeb3.d.ts.map +1 -1
- package/dist/hooks/useWeb3.js +27 -53
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +325 -302
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +1 -3
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +13 -9
- package/dist/providers/rn-dpop-provider.d.ts +2 -4
- package/dist/providers/rn-dpop-provider.d.ts.map +1 -1
- package/dist/providers/rn-dpop-provider.js +50 -23
- package/dist/storage/rn-secure-storage.d.ts +1 -0
- package/dist/storage/rn-secure-storage.d.ts.map +1 -1
- package/dist/storage/rn-secure-storage.js +9 -12
- package/package.json +2 -2
- package/src/hooks/index.ts +10 -2
- package/src/hooks/useAnalytics.ts +0 -1
- package/src/hooks/useAuth.ts +4 -25
- package/src/hooks/useBusiness.ts +0 -9
- package/src/hooks/useCampaigns.ts +0 -10
- package/src/hooks/useDonations.ts +0 -1
- package/src/hooks/useFiles.ts +0 -4
- package/src/hooks/usePurchases.ts +0 -3
- package/src/hooks/useRedemptions.ts +7 -21
- package/src/hooks/useTenants.ts +0 -3
- package/src/hooks/useTokens.ts +0 -6
- package/src/hooks/useTransactionSigner.ts +74 -4
- package/src/hooks/useTransactions.ts +10 -12
- package/src/hooks/useUserStatus.ts +0 -3
- package/src/hooks/useUsers.ts +0 -7
- package/src/hooks/useWeb3.ts +28 -68
- package/src/index.ts +4 -0
- package/src/providers/PersSDKProvider.tsx +16 -17
- package/src/providers/rn-dpop-provider.ts +85 -45
- package/src/storage/rn-secure-storage.ts +13 -13
package/src/hooks/useWeb3.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
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
|
-
* <
|
|
64
|
-
* {
|
|
65
|
-
* <
|
|
66
|
-
* <
|
|
67
|
-
* <
|
|
68
|
-
* </
|
|
48
|
+
* <View>
|
|
49
|
+
* {walletAddress ? (
|
|
50
|
+
* <View>
|
|
51
|
+
* <Text>Wallet: {walletAddress}</Text>
|
|
52
|
+
* <Button onPress={loadTokenData} title="Load Tokens" />
|
|
53
|
+
* </View>
|
|
69
54
|
* ) : (
|
|
70
|
-
* <
|
|
55
|
+
* <Text>No wallet connected</Text>
|
|
71
56
|
* )}
|
|
72
|
-
* </
|
|
57
|
+
* </View>
|
|
73
58
|
* );
|
|
74
59
|
* }
|
|
75
60
|
* ```
|
|
76
61
|
*/
|
|
77
62
|
export const useWeb3 = () => {
|
|
78
|
-
const { sdk, isInitialized, isAuthenticated
|
|
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
|
|
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
|
-
//
|
|
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,
|
|
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
|
|
@@ -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,
|
|
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}>
|
|
@@ -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
|
-
*
|
|
26
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
return this.base64UrlBuffer(Buffer.from(str));
|
|
94
135
|
}
|
|
95
136
|
|
|
96
137
|
private base64UrlBuffer(buf: Buffer | Uint8Array): string {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
|
59
|
+
await this.fallbackStorage.set(key, stringValue);
|
|
56
60
|
}
|
|
57
61
|
} else {
|
|
58
62
|
// Store standard config/metadata in AsyncStorage
|
|
59
|
-
await
|
|
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
|
|
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
|
|
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
|
|
113
|
+
await this.fallbackStorage.remove(key);
|
|
110
114
|
} else {
|
|
111
|
-
await
|
|
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
|
-
|
|
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
|
}
|