@explorins/pers-sdk-react-native 2.1.13 → 2.1.14-beta.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"rn-dpop-provider.d.ts","sourceRoot":"","sources":["../../src/providers/rn-dpop-provider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAiB3E,qBAAa,uBAAwB,YAAW,kBAAkB;IAChE;;;;;OAKG;IACG,eAAe,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAqC1E,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAqD9E,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB3D,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,eAAe;CAOxB"}
1
+ {"version":3,"file":"rn-dpop-provider.d.ts","sourceRoot":"","sources":["../../src/providers/rn-dpop-provider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAgD3E,qBAAa,uBAAwB,YAAW,kBAAkB;IAChE;;;;;OAKG;IACG,eAAe,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IA0C1E,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IA0D9E,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB3D,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,eAAe;CAOxB"}
@@ -1,20 +1,44 @@
1
1
  import { Platform } from 'react-native';
2
2
  import { Buffer } from 'buffer';
3
- // Conditionally require quick-crypto for Native platforms only
4
- let crypto;
5
- if (Platform.OS !== 'web') {
3
+ // Lazy-loaded crypto module - initialized on first use to avoid native module timing issues
4
+ let _crypto = null;
5
+ let _cryptoInitialized = false;
6
+ /**
7
+ * Lazily load the crypto module.
8
+ *
9
+ * Supports customer pre-initialization via global.__QuickCrypto for environments
10
+ * where native module timing is unpredictable (e.g., certain React Native setups).
11
+ *
12
+ * @returns The crypto module or null if unavailable
13
+ */
14
+ function getCrypto() {
15
+ if (_cryptoInitialized) {
16
+ return _crypto;
17
+ }
18
+ _cryptoInitialized = true;
19
+ if (Platform.OS === 'web') {
20
+ // On Web, we shouldn't be using this provider (Core SDK has WebDPoPProvider)
21
+ _crypto = {
22
+ generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); },
23
+ randomUUID: () => globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`
24
+ };
25
+ return _crypto;
26
+ }
27
+ // Check for customer pre-initialized global (for native module timing issues)
28
+ if (typeof global !== 'undefined' && global.__QuickCrypto) {
29
+ _crypto = global.__QuickCrypto;
30
+ return _crypto;
31
+ }
32
+ // Standard lazy load
6
33
  try {
7
- crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
34
+ _crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
8
35
  }
9
36
  catch (e) {
10
- console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto', e);
37
+ console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto.', e);
38
+ console.warn('Tip: If you encounter timing issues, pre-initialize in index.js: global.__QuickCrypto = require("react-native-quick-crypto")');
39
+ _crypto = null;
11
40
  }
12
- }
13
- else {
14
- // on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
15
- crypto = {
16
- generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
17
- };
41
+ return _crypto;
18
42
  }
19
43
  export class ReactNativeDPoPProvider {
20
44
  /**
@@ -24,6 +48,10 @@ export class ReactNativeDPoPProvider {
24
48
  * Falls back to Node.js crypto API on iOS if needed.
25
49
  */
26
50
  async generateKeyPair(options) {
51
+ const crypto = getCrypto();
52
+ if (!crypto) {
53
+ throw new Error('Crypto module not available. Install react-native-quick-crypto or pre-initialize global.__QuickCrypto');
54
+ }
27
55
  // Try WebCrypto API first (works on both iOS and Android)
28
56
  if (crypto.subtle) {
29
57
  try {
@@ -57,6 +85,10 @@ export class ReactNativeDPoPProvider {
57
85
  });
58
86
  }
59
87
  async signProof(payload, keyPair) {
88
+ const crypto = getCrypto();
89
+ if (!crypto) {
90
+ throw new Error('Crypto module not available for signing');
91
+ }
60
92
  // 1. Construct Header
61
93
  const header = {
62
94
  typ: 'dpop+jwt',
@@ -67,7 +99,7 @@ export class ReactNativeDPoPProvider {
67
99
  const finalPayload = {
68
100
  ...payload,
69
101
  iat: payload.iat || Math.floor(Date.now() / 1000),
70
- jti: payload.jti || crypto.randomUUID()
102
+ jti: payload.jti || (crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`)
71
103
  };
72
104
  // 3. Encode
73
105
  const b64Header = this.base64Url(JSON.stringify(header));
@@ -93,6 +125,10 @@ export class ReactNativeDPoPProvider {
93
125
  return `${signingInput}.${this.base64UrlBuffer(signature)}`;
94
126
  }
95
127
  async hashAccessToken(accessToken) {
128
+ const crypto = getCrypto();
129
+ if (!crypto) {
130
+ throw new Error('Crypto module not available for hashing');
131
+ }
96
132
  // Try WebCrypto first
97
133
  if (crypto.subtle) {
98
134
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorins/pers-sdk-react-native",
3
- "version": "2.1.13",
3
+ "version": "2.1.14-beta.0",
4
4
  "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,7 +37,7 @@
37
37
  "author": "eXplorins",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "@explorins/pers-sdk": "^2.1.20",
40
+ "@explorins/pers-sdk": "^2.1.21",
41
41
  "@explorins/pers-signer": "^1.0.33",
42
42
  "buffer": "^6.0.3",
43
43
  "ethers": "^6.15.0",
@@ -22,6 +22,8 @@ export type { CampaignClaimFilters } from '@explorins/pers-sdk/campaign';
22
22
  export interface GetCampaignsOptions {
23
23
  /** Filter by tag */
24
24
  tag?: string;
25
+ /** Free text search across name and description (case-insensitive) */
26
+ search?: string;
25
27
  /** Filter by business ID */
26
28
  businessId?: string;
27
29
  /** Page number (1-indexed) */
@@ -10,13 +10,32 @@ import type {
10
10
  PaginatedResponseDTO,
11
11
  RedemptionRedeemIncludeRelation,
12
12
  ProcessRecordStatus,
13
- DynamicContext
13
+ DynamicContext,
14
+ SortOrder
14
15
  } from '@explorins/pers-shared';
15
- import type { RedemptionRedeemFilters } from '@explorins/pers-sdk/redemption';
16
+ import type { RedemptionRedeemFilters, RedemptionFilterOptions } from '@explorins/pers-sdk/redemption';
16
17
  import type { PaginationOptions } from '@explorins/pers-sdk';
17
18
 
18
19
  // Re-export for consumers
19
- export type { RedemptionRedeemFilters } from '@explorins/pers-sdk/redemption';
20
+ export type { RedemptionRedeemFilters, RedemptionFilterOptions } from '@explorins/pers-sdk/redemption';
21
+
22
+ /**
23
+ * Options for fetching redemptions with filtering and pagination
24
+ */
25
+ export interface GetRedemptionsOptions {
26
+ /** Filter by active status (Admin only - regular users always see active offers) */
27
+ active?: boolean;
28
+ /** Free text search across name, description, tags (case-insensitive) */
29
+ search?: string;
30
+ /** Page number (1-indexed) */
31
+ page?: number;
32
+ /** Results per page */
33
+ limit?: number;
34
+ /** Field to sort by */
35
+ sortBy?: 'name' | 'createdAt';
36
+ /** Sort direction */
37
+ sortOrder?: SortOrder;
38
+ }
20
39
 
21
40
  export const useRedemptions = () => {
22
41
  const { sdk, isInitialized, isAuthenticated } = usePersSDK();
@@ -29,8 +48,37 @@ export const useRedemptions = () => {
29
48
  * **Administrators:** Can see all redemptions and filter by active/inactive status
30
49
  *
31
50
  * The backend enforces permission-based filtering automatically.
51
+ *
52
+ * @param options - Filter and pagination options
53
+ * @param options.active - Filter by active status (Admin only)
54
+ * @param options.search - Free text search across name, description, tags
55
+ * @param options.page - Page number (1-indexed)
56
+ * @param options.limit - Results per page
57
+ * @param options.sortBy - Field to sort by ('name' | 'createdAt')
58
+ * @param options.sortOrder - Sort direction ('ASC' | 'DESC')
59
+ *
60
+ * @example Basic usage
61
+ * ```typescript
62
+ * const { data: offers } = await getRedemptions();
63
+ * ```
64
+ *
65
+ * @example Search redemptions
66
+ * ```typescript
67
+ * const { data: coffeeOffers } = await getRedemptions({ search: 'coffee' });
68
+ * ```
69
+ *
70
+ * @example With pagination and sorting
71
+ * ```typescript
72
+ * const { data, pagination } = await getRedemptions({
73
+ * search: 'discount',
74
+ * page: 1,
75
+ * limit: 10,
76
+ * sortBy: 'name',
77
+ * sortOrder: 'ASC'
78
+ * });
79
+ * ```
32
80
  */
33
- const getRedemptions = useCallback(async (options?: { active?: boolean }): Promise<PaginatedResponseDTO<RedemptionDTO>> => {
81
+ const getRedemptions = useCallback(async (options?: GetRedemptionsOptions): Promise<PaginatedResponseDTO<RedemptionDTO>> => {
34
82
  if (!isInitialized || !sdk) {
35
83
  throw new Error('SDK not initialized. Call initialize() first.');
36
84
  }
@@ -19,14 +19,44 @@ export type { StatusUpdateData };
19
19
  export type OnStatusUpdateFn = (status: SigningStatus, message: string, data?: StatusUpdateData) => void;
20
20
 
21
21
 
22
- // Dynamic import the signer SDK to avoid build issues with static dependencies
23
- let createPersSignerSDK: ((config: PersSignerConfig) => Promise<PersSignerSDK>) | null = null;
24
- try {
25
- const signerModule = require('@explorins/pers-signer/react-native');
26
- createPersSignerSDK = signerModule.createPersSignerSDK;
27
- } catch (error: unknown) {
28
- console.warn('[useTransactionSigner] PERS Signer SDK not available:', (error as Error).message);
29
- console.warn('[useTransactionSigner] Real blockchain signing will not be available');
22
+ // Lazy-loaded signer SDK factory - initialized on first use to avoid native module timing issues
23
+ let _createPersSignerSDK: ((config: PersSignerConfig) => Promise<PersSignerSDK>) | null = null;
24
+ let _signerInitAttempted = false;
25
+
26
+ /**
27
+ * Lazily load the PERS Signer SDK.
28
+ *
29
+ * Supports customer pre-initialization via global.__PersSigner for environments
30
+ * where native module timing is unpredictable (e.g., certain React Native setups).
31
+ *
32
+ * @returns The createPersSignerSDK function or null if unavailable
33
+ */
34
+ function getSignerSDKFactory(): ((config: PersSignerConfig) => Promise<PersSignerSDK>) | null {
35
+ if (_signerInitAttempted) {
36
+ // Already tried - return cached result (could be null if failed)
37
+ return _createPersSignerSDK;
38
+ }
39
+
40
+ _signerInitAttempted = true;
41
+
42
+ // Check for customer pre-initialized global (for native module timing issues)
43
+ if (typeof global !== 'undefined' && (global as any).__PersSigner?.createPersSignerSDK) {
44
+ _createPersSignerSDK = (global as any).__PersSigner.createPersSignerSDK;
45
+ return _createPersSignerSDK;
46
+ }
47
+
48
+ // Standard lazy load
49
+ try {
50
+ const signerModule = require('@explorins/pers-signer/react-native');
51
+ _createPersSignerSDK = signerModule.createPersSignerSDK;
52
+ return _createPersSignerSDK;
53
+ } catch (error: unknown) {
54
+ console.warn('[useTransactionSigner] PERS Signer SDK not available:', (error as Error).message);
55
+ console.warn('[useTransactionSigner] Real blockchain signing will not be available');
56
+ console.warn('[useTransactionSigner] Tip: If timing issues, pre-initialize in index.js: global.__PersSigner = require("@explorins/pers-signer/react-native")');
57
+ _createPersSignerSDK = null; // Mark as unavailable
58
+ return null;
59
+ }
30
60
  }
31
61
 
32
62
 
@@ -243,6 +273,7 @@ export const useTransactionSigner = (): TransactionSignerHook => {
243
273
  // Auto-initialize signer SDK when PERS SDK is ready
244
274
  useEffect(() => {
245
275
  const initializeSignerSDK = async () => {
276
+ const createPersSignerSDK = getSignerSDKFactory();
246
277
  if (!createPersSignerSDK) {
247
278
  console.warn('[useTransactionSigner] Signer SDK not available');
248
279
  return;
@@ -361,7 +392,7 @@ export const useTransactionSigner = (): TransactionSignerHook => {
361
392
  if (!isSignerInitialized || !signerSDKRef.current) {
362
393
  throw new Error('Transaction signer not initialized');
363
394
  }
364
- if (!createPersSignerSDK) {
395
+ if (!getSignerSDKFactory()) {
365
396
  throw new Error('PERS Signer SDK not available. Blockchain signing is not supported.');
366
397
  }
367
398
 
@@ -514,7 +545,7 @@ export const useTransactionSigner = (): TransactionSignerHook => {
514
545
  * );
515
546
  * ```
516
547
  */
517
- isSignerAvailable: isSignerInitialized && !!createPersSignerSDK,
548
+ isSignerAvailable: isSignerInitialized && !!getSignerSDKFactory(),
518
549
 
519
550
  /**
520
551
  * Current signing status for UI feedback
@@ -2,19 +2,50 @@ import { Platform } from 'react-native';
2
2
  import { Buffer } from 'buffer';
3
3
  import { DPoPCryptoProvider, DPoPKeyPair } from '@explorins/pers-sdk/core';
4
4
 
5
- // Conditionally require quick-crypto for Native platforms only
6
- let crypto: any;
7
- if (Platform.OS !== 'web') {
5
+ // Lazy-loaded crypto module - initialized on first use to avoid native module timing issues
6
+ let _crypto: any = null;
7
+ let _cryptoInitialized = false;
8
+
9
+ /**
10
+ * Lazily load the crypto module.
11
+ *
12
+ * Supports customer pre-initialization via global.__QuickCrypto for environments
13
+ * where native module timing is unpredictable (e.g., certain React Native setups).
14
+ *
15
+ * @returns The crypto module or null if unavailable
16
+ */
17
+ function getCrypto(): any {
18
+ if (_cryptoInitialized) {
19
+ return _crypto;
20
+ }
21
+
22
+ _cryptoInitialized = true;
23
+
24
+ if (Platform.OS === 'web') {
25
+ // On Web, we shouldn't be using this provider (Core SDK has WebDPoPProvider)
26
+ _crypto = {
27
+ generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); },
28
+ randomUUID: () => globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`
29
+ };
30
+ return _crypto;
31
+ }
32
+
33
+ // Check for customer pre-initialized global (for native module timing issues)
34
+ if (typeof global !== 'undefined' && (global as any).__QuickCrypto) {
35
+ _crypto = (global as any).__QuickCrypto;
36
+ return _crypto;
37
+ }
38
+
39
+ // Standard lazy load
8
40
  try {
9
- crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
41
+ _crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
10
42
  } catch (e) {
11
- console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto', e);
43
+ console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto.', e);
44
+ console.warn('Tip: If you encounter timing issues, pre-initialize in index.js: global.__QuickCrypto = require("react-native-quick-crypto")');
45
+ _crypto = null;
12
46
  }
13
- } else {
14
- // on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
15
- crypto = {
16
- generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
17
- };
47
+
48
+ return _crypto;
18
49
  }
19
50
 
20
51
  export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
@@ -25,6 +56,11 @@ export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
25
56
  * Falls back to Node.js crypto API on iOS if needed.
26
57
  */
27
58
  async generateKeyPair(options?: { extractable?: boolean }): Promise<DPoPKeyPair> {
59
+ const crypto = getCrypto();
60
+ if (!crypto) {
61
+ throw new Error('Crypto module not available. Install react-native-quick-crypto or pre-initialize global.__QuickCrypto');
62
+ }
63
+
28
64
  // Try WebCrypto API first (works on both iOS and Android)
29
65
  if (crypto.subtle) {
30
66
  try {
@@ -62,6 +98,11 @@ export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
62
98
  }
63
99
 
64
100
  async signProof(payload: Record<string, any>, keyPair: DPoPKeyPair): Promise<string> {
101
+ const crypto = getCrypto();
102
+ if (!crypto) {
103
+ throw new Error('Crypto module not available for signing');
104
+ }
105
+
65
106
  // 1. Construct Header
66
107
  const header = {
67
108
  typ: 'dpop+jwt',
@@ -73,7 +114,7 @@ export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
73
114
  const finalPayload = {
74
115
  ...payload,
75
116
  iat: payload.iat || Math.floor(Date.now() / 1000),
76
- jti: payload.jti || crypto.randomUUID()
117
+ jti: payload.jti || (crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`)
77
118
  };
78
119
 
79
120
  // 3. Encode
@@ -115,6 +156,11 @@ export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
115
156
  }
116
157
 
117
158
  async hashAccessToken(accessToken: string): Promise<string> {
159
+ const crypto = getCrypto();
160
+ if (!crypto) {
161
+ throw new Error('Crypto module not available for hashing');
162
+ }
163
+
118
164
  // Try WebCrypto first
119
165
  if (crypto.subtle) {
120
166
  try {