@explorins/pers-sdk-react-native 1.5.1 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorins/pers-sdk-react-native",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Manager Pattern Architecture",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,7 +15,9 @@
15
15
  "docs:serve": "npx http-server docs -p 8080",
16
16
  "prepublishOnly": "npm run clean && npm run build",
17
17
  "publish-version": "npm run clean && npm run build && npm version patch && npm publish",
18
- "publish-with-docs": "npm run clean && npm run build && npm run docs && npm version patch && npm publish"
18
+ "publish-with-docs": "npm run clean && npm run build && npm run docs && npm version patch && npm publish",
19
+ "yalc:publish": "npm version patch --no-git-tag-version && npm run build && yalc publish",
20
+ "yalc:push": "npm version patch --no-git-tag-version && npm run build && yalc push"
19
21
  },
20
22
  "keywords": [
21
23
  "pers",
@@ -29,8 +31,11 @@
29
31
  "author": "eXplorins",
30
32
  "license": "MIT",
31
33
  "dependencies": {
34
+ "@dfns/sdk": "^0.8.1",
35
+ "@dfns/sdk-react-native": "^0.8.1",
32
36
  "@explorins/pers-sdk": "^1.6.3",
33
37
  "@explorins/pers-shared": "^2.1.40",
38
+ "@explorins/pers-signer": "^1.0.9",
34
39
  "@explorins/web3-ts": "^0.3.75",
35
40
  "@react-native-async-storage/async-storage": "^2.2.0",
36
41
  "buffer": "^6.0.3",
@@ -150,6 +155,7 @@
150
155
  "devDependencies": {
151
156
  "@react-native-async-storage/async-storage": "^1.19.0",
152
157
  "@rollup/plugin-commonjs": "^25.0.7",
158
+ "@rollup/plugin-json": "^6.1.0",
153
159
  "@rollup/plugin-node-resolve": "^15.2.3",
154
160
  "@types/react": "^19.1.1",
155
161
  "@types/react-native": "^0.72.8",
@@ -2,6 +2,7 @@
2
2
  export { useAuth } from './useAuth';
3
3
  export { useTokens } from './useTokens';
4
4
  export { useTransactions } from './useTransactions';
5
+ export { useTransactionSigner } from './useTransactionSigner';
5
6
  export { useBusiness } from './useBusiness';
6
7
  export { useCampaigns } from './useCampaigns';
7
8
  export { useRedemptions } from './useRedemptions';
@@ -15,4 +16,5 @@ export { useAnalytics } from './useAnalytics';
15
16
  export { useDonations } from './useDonations';
16
17
 
17
18
  // Re-export auth-related types for convenience
18
- export type { RawUserData } from './useAuth';
19
+ export type { RawUserData } from './useAuth';
20
+ export type { TransactionSignerHook, TransactionSigningResult } from './useTransactionSigner';
@@ -1,5 +1,7 @@
1
1
  import { useCallback } from 'react';
2
2
  import { usePersSDK } from '../providers/PersSDKProvider';
3
+ import { useTransactionSigner } from './useTransactionSigner';
4
+ import type { TransactionSigningResult } from './useTransactionSigner';
3
5
  import type {
4
6
  RedemptionCreateRequestDTO,
5
7
  RedemptionDTO,
@@ -11,6 +13,7 @@ import type {
11
13
 
12
14
  export const useRedemptions = () => {
13
15
  const { sdk, isInitialized, isAuthenticated } = usePersSDK();
16
+ const { signTransaction, isSignerAvailable } = useTransactionSigner();
14
17
 
15
18
  const getActiveRedemptions = useCallback(async (): Promise<RedemptionDTO[]> => {
16
19
  if (!isInitialized || !sdk) {
@@ -58,24 +61,47 @@ export const useRedemptions = () => {
58
61
  console.log('Redeeming redemption:', redemptionId);
59
62
  const result = await sdk.redemptions.redeemOffer(redemptionId);
60
63
 
61
- // Cross-platform: Handle signature URLs for redemptions
62
- if (result?.senderTransaction?.actionable?.actionUrl) {
64
+ // Check if result has signing fields and sign transaction if required and signer is available
65
+ console.log('Redemption processed successfully:', result);
66
+
67
+
68
+ const txToken = result.senderTransaction?.actionable?.authToken;
69
+ if (txToken && isSignerAvailable) {
70
+ console.log('Transaction requires blockchain signing, processing with WebAuthn signer...');
71
+
63
72
  try {
64
- const { Linking } = require('react-native');
65
- console.log('Opening signature URL:', result.senderTransaction.actionable.actionUrl);
66
- await Linking.openURL(result.senderTransaction.actionable.actionUrl);
67
- } catch (linkingError) {
68
- console.error('Failed to open signature URL:', linkingError);
73
+ const signingResult: TransactionSigningResult = await signTransaction(txToken);
74
+ console.log('Blockchain signing result:', signingResult);
75
+ if (signingResult.success) {
76
+ console.log('Transaction signed successfully:', signingResult.transactionHash);
77
+ // Return enhanced result with signing information
78
+ return {
79
+ ...result,
80
+ transactionHash: signingResult.transactionHash,
81
+ signature: signingResult.signature,
82
+ isSigned: true,
83
+ signedAt: new Date().toISOString()
84
+ } as RedemptionRedeemRequestResponseDTO;
85
+ } else {
86
+ console.error('Transaction signing failed:', signingResult.error);
87
+ throw new Error(signingResult.error || 'Transaction signing failed');
88
+ }
89
+ } catch (signingError) {
90
+ console.error('Blockchain signing error:', signingError);
91
+ throw new Error(`Transaction signing failed: ${signingError}`);
69
92
  }
93
+ } else if (txToken && !isSignerAvailable) {
94
+ console.warn('Transaction requires signature but signer is not available');
95
+ throw new Error('Transaction requires signature but blockchain signer is not initialized');
70
96
  }
71
97
 
72
- console.log('Redemption processed successfully:', result);
98
+ // Return original result if no signing was required
73
99
  return result;
74
100
  } catch (error) {
75
101
  console.error('Failed to redeem redemption:', error);
76
102
  throw error;
77
103
  }
78
- }, [sdk, isInitialized, isAuthenticated]);
104
+ }, [sdk, isInitialized, isAuthenticated, signTransaction, isSignerAvailable]);
79
105
 
80
106
  // Admin methods
81
107
  const createRedemption = useCallback(async (redemptionData: RedemptionCreateRequestDTO): Promise<RedemptionDTO> => {
@@ -0,0 +1,287 @@
1
+ import { useCallback, useState, useRef, useEffect } from 'react';
2
+ import { usePersSDK } from '../providers/PersSDKProvider';
3
+ import type { UserDTO, AdminDTO } from '@explorins/pers-shared';
4
+
5
+ // Dynamic import the signer SDK to avoid build issues with static dependencies
6
+ let createPersSignerSDK: ((config: any) => Promise<any>) | null = null;
7
+ try {
8
+ const signerModule = require('@explorins/pers-signer/react-native');
9
+ createPersSignerSDK = signerModule.createPersSignerSDK;
10
+ console.log('[useTransactionSigner] PERS Signer SDK loaded successfully');
11
+ } catch (error: unknown) {
12
+ console.warn('[useTransactionSigner] PERS Signer SDK not available:', (error as Error).message);
13
+ console.warn('[useTransactionSigner] Real blockchain signing will not be available');
14
+ }
15
+
16
+ interface PersSignerSDKInstance {
17
+ authenticateUser: (userInfo: SignerUserInfo) => Promise<SignerAuthenticatedUser>;
18
+ signPersTransaction: (user: SignerAuthenticatedUser, jwt: string) => Promise<SigningResult>;
19
+ }
20
+
21
+ interface SignerUserInfo {
22
+ identifier: string;
23
+ email?: string;
24
+ id?: string;
25
+ }
26
+
27
+ interface SignerAuthenticatedUser {
28
+ identifier: string;
29
+ signerAuthToken: string;
30
+ persAccessToken: string;
31
+ }
32
+
33
+ interface SigningResult {
34
+ success: boolean;
35
+ transactionHash?: string;
36
+ signature?: string;
37
+ error?: string;
38
+ }
39
+
40
+ /**
41
+ * Utility to decode JWT and extract user information
42
+ * This will be used to extract user data for the signer
43
+ */
44
+ const extractUserInfoFromJWT = async (jwt: string): Promise<{ userId?: string; email?: string; sub?: string }> => {
45
+ try {
46
+ // Dynamically import jwt-decode to avoid bundling issues
47
+ const { jwtDecode } = await import('jwt-decode');
48
+ const decoded = jwtDecode<{
49
+ sub?: string;
50
+ email?: string;
51
+ user_id?: string;
52
+ userId?: string;
53
+ uid?: string;
54
+ }>(jwt);
55
+
56
+ return {
57
+ userId: decoded.user_id || decoded.userId || decoded.uid || decoded.sub,
58
+ email: decoded.email,
59
+ sub: decoded.sub
60
+ };
61
+ } catch (error) {
62
+ console.warn('[useTransactionSigner] Failed to decode JWT:', error);
63
+ return {};
64
+ }
65
+ };
66
+
67
+ // Define our own result type that's compatible with both SDK and our usage
68
+ interface TransactionSigningResult {
69
+ success: boolean;
70
+ transactionHash?: string;
71
+ signature?: string;
72
+ error?: string;
73
+ }
74
+
75
+ // Constants - TODO: Move to environment config later
76
+ const DEFAULT_ETHERS_PROVIDER = "https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e";
77
+
78
+ /**
79
+ * React Native hook for blockchain transaction signing using PERS Signer SDK
80
+ *
81
+ * Provides WebAuthn-based transaction signing capabilities integrated with PERS ecosystem.
82
+ * Automatically handles user authentication and transaction signing workflows.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * function TransactionComponent() {
87
+ * const { signTransaction, initializeSigner, isSignerAvailable } = useTransactionSigner();
88
+ *
89
+ * useEffect(() => {
90
+ * initializeSigner({
91
+ * tenantId: 'my-tenant',
92
+ * ethersProviderUrl: 'https://sepolia.infura.io/v3/...'
93
+ * });
94
+ * }, []);
95
+ *
96
+ * const handleSign = async () => {
97
+ * try {
98
+ * const result = await signTransaction('transaction-id-123');
99
+ * if (result.success) {
100
+ * console.log('Transaction signed:', result.transactionHash);
101
+ * }
102
+ * } catch (error) {
103
+ * console.error('Signing failed:', error);
104
+ * }
105
+ * };
106
+ *
107
+ * return (
108
+ * <button onClick={handleSign} disabled={!isSignerAvailable}>
109
+ * Sign Transaction
110
+ * </button>
111
+ * );
112
+ * }
113
+ * ```
114
+ */
115
+ export const useTransactionSigner = () => {
116
+ const { sdk, isInitialized, isAuthenticated, user } = usePersSDK();
117
+ const [isSignerInitialized, setIsSignerInitialized] = useState(false);
118
+ const signerSDKRef = useRef<PersSignerSDKInstance | null>(null);
119
+
120
+ // Auto-initialize signer when user is authenticated and real SDK is available
121
+ useEffect(() => {
122
+ if (isInitialized && isAuthenticated && user && !isSignerInitialized) {
123
+ console.log('[useTransactionSigner] Auto-initializing PERS transaction signer...');
124
+ initializeSigner({
125
+ tenantId: 'auto-tenant', // TODO: Get from SDK config or environment
126
+ ethersProviderUrl: DEFAULT_ETHERS_PROVIDER
127
+ }).catch((error) => {
128
+ console.error('[useTransactionSigner] Auto-initialization failed:', error);
129
+ });
130
+ }
131
+ }, [isInitialized, isAuthenticated, user, isSignerInitialized, createPersSignerSDK]);
132
+
133
+ /**
134
+ * Initialize the blockchain signer with configuration
135
+ *
136
+ * @param config - Signer configuration options
137
+ * @param config.tenantId - Multi-tenant identifier for the signer
138
+ * @param config.ethersProviderUrl - Custom blockchain provider URL
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * await initializeSigner({
143
+ * tenantId: 'my-tenant-id',
144
+ * ethersProviderUrl: 'https://sepolia.infura.io/v3/your-key'
145
+ * });
146
+ * ```
147
+ */
148
+ const initializeSigner = useCallback(async (config?: {
149
+ tenantId?: string;
150
+ ethersProviderUrl?: string;
151
+ }) => {
152
+ if (!createPersSignerSDK) {
153
+ throw new Error('PERS Signer SDK not available. Please ensure dependencies are properly installed.');
154
+ }
155
+
156
+ try {
157
+ console.log('[useTransactionSigner] Initializing PERS transaction signer...');
158
+
159
+ const signerSDK = await createPersSignerSDK({
160
+ tenantId: config?.tenantId,
161
+ ethersProviderUrl: config?.ethersProviderUrl || DEFAULT_ETHERS_PROVIDER
162
+ });
163
+
164
+ signerSDKRef.current = signerSDK;
165
+ setIsSignerInitialized(true);
166
+ console.log('[useTransactionSigner] PERS Signer SDK initialized successfully');
167
+
168
+ } catch (error) {
169
+ console.error('[useTransactionSigner] Failed to initialize transaction signer:', error);
170
+ throw new Error(`Signer initialization failed: ${error}`);
171
+ }
172
+ }, []);
173
+
174
+ /**
175
+ * Sign a blockchain transaction using WebAuthn authentication
176
+ *
177
+ * @param jwt - JWT token containing transaction information and user context
178
+ * @returns Promise resolving to signing result with transaction hash
179
+ *
180
+ * @throws {Error} When SDK not initialized, user not authenticated, or signer not initialized
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * try {
185
+ * const result = await signTransaction(jwtToken);
186
+ * if (result.success) {
187
+ * console.log('Signed transaction hash:', result.transactionHash);
188
+ * } else {
189
+ * console.error('Signing failed:', result.error);
190
+ * }
191
+ * } catch (error) {
192
+ * console.error('Signing error:', error);
193
+ * }
194
+ * ```
195
+ */
196
+ const signTransaction = useCallback(async (jwt: string): Promise<TransactionSigningResult> => {
197
+ if (!isInitialized || !sdk) {
198
+ throw new Error('SDK not initialized. Call initialize() first.');
199
+ }
200
+ if (!isAuthenticated || !user) {
201
+ throw new Error('User must be authenticated to sign transactions.');
202
+ }
203
+ if (!isSignerInitialized || !signerSDKRef.current) {
204
+ throw new Error('Transaction signer not initialized. Call initializeSigner() first.');
205
+ }
206
+ if (!createPersSignerSDK) {
207
+ throw new Error('PERS Signer SDK not available. Blockchain signing is not supported.');
208
+ }
209
+
210
+ try {
211
+ console.log('[useTransactionSigner] Extracting user info from JWT for transaction signing...');
212
+
213
+ // Extract user information from JWT
214
+ const jwtUserInfo = await extractUserInfoFromJWT(jwt);
215
+
216
+ // Create user info for signer, prioritizing current user data
217
+ const currentUser = user as UserDTO;
218
+ const signerUserInfo: SignerUserInfo = {
219
+ identifier: currentUser.email || currentUser.id || jwtUserInfo.userId || jwtUserInfo.sub || `user-${Date.now()}`,
220
+ email: currentUser.email || jwtUserInfo.email,
221
+ id: currentUser.id || jwtUserInfo.userId
222
+ };
223
+
224
+ // Authenticate user with blockchain signer
225
+ console.log('[useTransactionSigner] Authenticating user with signer:', signerUserInfo.identifier);
226
+ const signerUser = await signerSDKRef.current.authenticateUser(signerUserInfo);
227
+
228
+ // Sign the PERS transaction using JWT
229
+ console.log('[useTransactionSigner] Signing PERS transaction with JWT containing transaction data');
230
+ const result = await signerSDKRef.current.signPersTransaction(signerUser, jwt);
231
+
232
+ // Convert PERS SDK result to our format
233
+ const convertedResult: TransactionSigningResult = {
234
+ success: result.success || false,
235
+ transactionHash: result.transactionHash,
236
+ signature: result.signature,
237
+ error: result.error
238
+ };
239
+
240
+ if (convertedResult.success) {
241
+ console.log('[useTransactionSigner] Transaction signed successfully:', convertedResult.transactionHash);
242
+ } else {
243
+ console.warn('[useTransactionSigner] Transaction signing completed with warnings:', convertedResult.error);
244
+ }
245
+
246
+ return convertedResult;
247
+ } catch (error) {
248
+ console.error('[useTransactionSigner] Failed to sign transaction:', error);
249
+ return {
250
+ success: false,
251
+ error: `Transaction signing failed: ${error}`
252
+ };
253
+ }
254
+ }, [sdk, isInitialized, isAuthenticated, user, isSignerInitialized]);
255
+
256
+ return {
257
+ /**
258
+ * Sign a blockchain transaction with WebAuthn authentication
259
+ */
260
+ signTransaction,
261
+
262
+ /**
263
+ * Initialize the transaction signer with configuration
264
+ */
265
+ initializeSigner,
266
+
267
+ /**
268
+ * Whether the transaction signer has been initialized
269
+ */
270
+ isSignerInitialized,
271
+
272
+ /**
273
+ * Whether transaction signing is available (all requirements met)
274
+ */
275
+ isSignerAvailable: isInitialized && isAuthenticated && isSignerInitialized,
276
+ };
277
+ };
278
+
279
+ /**
280
+ * Type definition for the transaction signer hook
281
+ */
282
+ export type TransactionSignerHook = ReturnType<typeof useTransactionSigner>;
283
+
284
+ /**
285
+ * Export the transaction signing result type for external usage
286
+ */
287
+ export type { TransactionSigningResult };
@@ -7,10 +7,6 @@ import type {
7
7
  } from '@explorins/pers-shared';
8
8
  import type { TransactionPaginationParams } from '@explorins/pers-sdk/transaction';
9
9
 
10
- interface TransactionFilters {
11
- type?: string;
12
- }
13
-
14
10
  /**
15
11
  * React hook for transaction operations in the PERS SDK
16
12
  *
@@ -82,19 +78,9 @@ export const useTransactions = () => {
82
78
  }
83
79
 
84
80
  try {
85
- console.log('Creating transaction with request:', request);
86
81
  const result = await sdk.transactions.createTransaction(request);
87
82
 
88
- // Cross-platform: Handle signature URLs
89
- if (result?.actionable?.actionUrl) {
90
- try {
91
- const { Linking } = require('react-native');
92
- console.log('Opening signature URL:', result.actionable.actionUrl);
93
- await Linking.openURL(result.actionable.actionUrl);
94
- } catch (linkingError) {
95
- console.error('Failed to open signature URL:', linkingError);
96
- }
97
- }
83
+ // Cross-platform: Dont handle signature URLs here, leave to caller
98
84
 
99
85
  console.log('Transaction created successfully:', result);
100
86
  return result;
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ export {
20
20
  useAuth,
21
21
  useTokens,
22
22
  useTransactions,
23
+ useTransactionSigner,
23
24
  useBusiness,
24
25
  useCampaigns,
25
26
  useRedemptions,