@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.
package/dist/index.js CHANGED
@@ -5938,6 +5938,8 @@ class CampaignApi {
5938
5938
  params.push(`tag=${encodeURIComponent(options.tag)}`);
5939
5939
  if (options?.businessId)
5940
5940
  params.push(`businessId=${encodeURIComponent(options.businessId)}`);
5941
+ if (options?.search)
5942
+ params.push(`search=${encodeURIComponent(options.search)}`);
5941
5943
  if (options?.sortBy)
5942
5944
  params.push(`sortBy=${options.sortBy}`);
5943
5945
  if (options?.sortOrder)
@@ -6304,7 +6306,9 @@ class CampaignService {
6304
6306
  /**
6305
6307
  * Get campaigns with pagination support
6306
6308
  * Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all
6307
- * @param options.active - Filter by active status
6309
+ * @param options.active - Filter by active status (Admin only)
6310
+ * @param options.tag - Filter by tag
6311
+ * @param options.search - Free text search across name, description, and tags (case-insensitive)
6308
6312
  * @param options.businessId - Filter by business engagement
6309
6313
  * @param options.page - Page number (1-based, default: 1)
6310
6314
  * @param options.limit - Items per page (default: 50)
@@ -6480,15 +6484,23 @@ class RedemptionApi {
6480
6484
  * - Public users: Get active redemptions only
6481
6485
  * - Admin users: Get all redemptions with optional filtering
6482
6486
  *
6483
- * @param options - Filter and pagination options (active, adminAccess, page, limit, sortBy, sortOrder)
6487
+ * @param options - Filter and pagination options (active, search, adminAccess, page, limit, sortBy, sortOrder)
6484
6488
  * @returns Paginated response with redemptions
6485
6489
  */
6486
6490
  async getRedemptions(options) {
6487
- const params = buildPaginationParams(options);
6488
- if (options?.active !== undefined) {
6489
- params.append('active', String(options.active));
6490
- }
6491
- const response = await this.apiClient.get(`${this.basePath}?${params.toString()}`);
6491
+ const params = [];
6492
+ // Always include pagination params to ensure paginated response
6493
+ params.push(`page=${options?.page || 1}`);
6494
+ params.push(`limit=${options?.limit || 50}`);
6495
+ if (options?.active !== undefined)
6496
+ params.push(`active=${options.active}`);
6497
+ if (options?.search)
6498
+ params.push(`search=${encodeURIComponent(options.search)}`);
6499
+ if (options?.sortBy)
6500
+ params.push(`sortBy=${options.sortBy}`);
6501
+ if (options?.sortOrder)
6502
+ params.push(`sortOrder=${options.sortOrder}`);
6503
+ const response = await this.apiClient.get(`${this.basePath}?${params.join('&')}`);
6492
6504
  return normalizeToPaginated(response);
6493
6505
  }
6494
6506
  /**
@@ -6717,7 +6729,12 @@ class RedemptionService {
6717
6729
  /**
6718
6730
  * UNIFIED: Get redemptions with intelligent access control and pagination
6719
6731
  * @param options.active - Filter by active status
6732
+ * @param options.search - Free text search across name, description, tags (case-insensitive)
6720
6733
  * @param options.adminAccess - Force admin access
6734
+ * @param options.page - Page number (1-indexed)
6735
+ * @param options.limit - Results per page
6736
+ * @param options.sortBy - Field to sort by ('name' | 'createdAt')
6737
+ * @param options.sortOrder - Sort direction ('ASC' | 'DESC')
6721
6738
  */
6722
6739
  async getRedemptions(options) {
6723
6740
  return this.redemptionApi.getRedemptions(options);
@@ -6770,10 +6787,11 @@ class RedemptionService {
6770
6787
  // ADMIN OPERATIONS
6771
6788
  // ==========================================
6772
6789
  /**
6773
- * Convenience: Get redemptions as admin
6790
+ * Convenience: Get redemptions as admin with filtering and search
6791
+ * @param options - Filter options (active, search, pagination)
6774
6792
  */
6775
- async getRedemptionsAsAdmin(active, options) {
6776
- return this.redemptionApi.getRedemptions({ active, adminAccess: true, ...options });
6793
+ async getRedemptionsAsAdmin(options) {
6794
+ return this.redemptionApi.getRedemptions({ ...options, adminAccess: true });
6777
6795
  }
6778
6796
  /**
6779
6797
  * ADMIN: Create redemption
@@ -11890,7 +11908,7 @@ class BusinessManager {
11890
11908
  *
11891
11909
  * // Get specific campaign details
11892
11910
  * const campaign = await sdk.campaigns.getCampaignById('summer-promo-2024');
11893
- * console.log('Campaign:', campaign.title);
11911
+ * console.log('Campaign:', campaign.name);
11894
11912
  *
11895
11913
  * // User claims campaign reward
11896
11914
  * const claim = await sdk.campaigns.claimCampaign({
@@ -11906,7 +11924,7 @@ class BusinessManager {
11906
11924
  *
11907
11925
  * console.log('My Campaign Claims:');
11908
11926
  * userClaims.forEach(claim => {
11909
- * console.log(`- ${claim.campaign.title}`);
11927
+ * console.log(`- ${claim.campaign.name}`);
11910
11928
  * console.log(` Rewards: ${claim.rewards?.length || 0} tokens`);
11911
11929
  * console.log(` Date: ${claim.claimedAt}`);
11912
11930
  * });
@@ -11922,7 +11940,7 @@ class BusinessManager {
11922
11940
  * ```typescript
11923
11941
  * // Admin: Create seasonal campaign
11924
11942
  * const newCampaign = await sdk.campaigns.createCampaign({
11925
- * title: 'Holiday Rewards',
11943
+ * name: 'Holiday Rewards',
11926
11944
  * description: 'Special holiday loyalty bonuses',
11927
11945
  * startDate: '2024-12-01',
11928
11946
  * endDate: '2024-12-31',
@@ -11961,7 +11979,7 @@ class CampaignManager {
11961
11979
  * const campaign = await sdk.campaigns.getCampaignById('summer-promo-2024');
11962
11980
  *
11963
11981
  * console.log('Campaign Details:');
11964
- * console.log('Title:', campaign.title);
11982
+ * console.log('Name:', campaign.name);
11965
11983
  * console.log('Description:', campaign.description);
11966
11984
  * console.log('Active:', campaign.isActive);
11967
11985
  * console.log('Period:', `${campaign.startDate} to ${campaign.endDate}`);
@@ -12075,7 +12093,7 @@ class CampaignManager {
12075
12093
  * console.log(`Campaign History (${userClaims.length} claims):`);
12076
12094
  *
12077
12095
  * userClaims.forEach((claim, index) => {
12078
- * console.log(`\n${index + 1}. ${claim.campaign.title}`);
12096
+ * console.log(`\n${index + 1}. ${claim.campaign.name}`);
12079
12097
  * console.log(` Claimed: ${new Date(claim.claimedAt).toLocaleDateString()}`);
12080
12098
  * console.log(` Business: ${claim.business?.displayName || 'N/A'}`);
12081
12099
  *
@@ -12105,7 +12123,7 @@ class CampaignManager {
12105
12123
  * Returns campaigns with pagination metadata for efficient data loading.
12106
12124
  * Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all.
12107
12125
  *
12108
- * @param options - Pagination and filter options (page, limit, sortBy, sortOrder, active, businessId, include)
12126
+ * @param options - Pagination and filter options (page, limit, sortBy, sortOrder, active, tag, search, businessId, include)
12109
12127
  * @returns Promise resolving to paginated campaigns with metadata
12110
12128
  *
12111
12129
  * @example
@@ -12122,6 +12140,19 @@ class CampaignManager {
12122
12140
  * limit: 20
12123
12141
  * });
12124
12142
  *
12143
+ * // Search campaigns by name or description
12144
+ * const searchResults = await sdk.campaigns.getCampaigns({
12145
+ * search: 'summer rewards',
12146
+ * page: 1,
12147
+ * limit: 20
12148
+ * });
12149
+ *
12150
+ * // Filter by tag
12151
+ * const taggedCampaigns = await sdk.campaigns.getCampaigns({
12152
+ * tag: 'seasonal',
12153
+ * active: true
12154
+ * });
12155
+ *
12125
12156
  * // Get active campaigns sorted by creation date
12126
12157
  * const activeCampaigns = await sdk.campaigns.getCampaigns({
12127
12158
  * active: true,
@@ -12155,7 +12186,7 @@ class CampaignManager {
12155
12186
  * ```typescript
12156
12187
  * // Admin operation - create seasonal campaign
12157
12188
  * const campaign = await sdk.campaigns.createCampaign({
12158
- * title: 'Summer Travel Rewards',
12189
+ * name: 'Summer Travel Rewards',
12159
12190
  * description: 'Earn bonus points for summer bookings',
12160
12191
  * startDate: '2024-06-01T00:00:00Z',
12161
12192
  * endDate: '2024-08-31T23:59:59Z',
@@ -12164,7 +12195,7 @@ class CampaignManager {
12164
12195
  * campaignType: 'SEASONAL'
12165
12196
  * });
12166
12197
  *
12167
- * console.log('Campaign created:', campaign.title);
12198
+ * console.log('Campaign created:', campaign.name);
12168
12199
  * console.log('Campaign ID:', campaign.id);
12169
12200
  *
12170
12201
  * // Set up campaign rewards
@@ -12194,13 +12225,13 @@ class CampaignManager {
12194
12225
  * ```typescript
12195
12226
  * // Admin operation - extend campaign duration
12196
12227
  * const updated = await sdk.campaigns.updateCampaign('summer-2024', {
12197
- * title: 'Extended Summer Travel Rewards',
12228
+ * name: 'Extended Summer Travel Rewards',
12198
12229
  * description: 'Earn bonus points for summer and early fall bookings',
12199
12230
  * endDate: '2024-09-30T23:59:59Z', // Extended end date
12200
12231
  * isActive: true
12201
12232
  * });
12202
12233
  *
12203
- * console.log('Campaign updated:', updated.title);
12234
+ * console.log('Campaign updated:', updated.name);
12204
12235
  * console.log('New end date:', updated.endDate);
12205
12236
  * ```
12206
12237
  */
@@ -12665,7 +12696,7 @@ class CampaignManager {
12665
12696
  * console.log(`Total campaigns claimed: ${userClaims.length}`);
12666
12697
  *
12667
12698
  * userClaims.forEach((claim, index) => {
12668
- * console.log(`\n${index + 1}. ${claim.campaign.title}`);
12699
+ * console.log(`\n${index + 1}. ${claim.campaign.name}`);
12669
12700
  * console.log(` Date: ${claim.claimedAt}`);
12670
12701
  * console.log(` Business: ${claim.business?.displayName || 'Direct'}`);
12671
12702
  * console.log(` Rewards: ${claim.rewards?.length || 0} items`);
@@ -12702,8 +12733,8 @@ class CampaignManager {
12702
12733
  *
12703
12734
  * // Analyze claims by campaign
12704
12735
  * const claimsByCampaign = businessClaims.reduce((acc, claim) => {
12705
- * const title = claim.campaign.title;
12706
- * acc[title] = (acc[title] || 0) + 1;
12736
+ * const campaignName = claim.campaign.name;
12737
+ * acc[campaignName] = (acc[campaignName] || 0) + 1;
12707
12738
  * return acc;
12708
12739
  * }, {});
12709
12740
  *
@@ -12907,6 +12938,7 @@ class RedemptionManager {
12907
12938
  *
12908
12939
  * @param options - Optional filters and pagination:
12909
12940
  * - `active`: Filter by active status (true/false). Admins only - regular users always see active offers
12941
+ * - `search`: Free text search across name, description, tags (case-insensitive)
12910
12942
  * - Pagination options: `page`, `limit`, `sortBy`, `sortOrder`
12911
12943
  * @returns Promise resolving to paginated redemption offers
12912
12944
  *
@@ -12919,9 +12951,9 @@ class RedemptionManager {
12919
12951
  * console.log(`Showing ${offers.length} of ${pagination.total} offers`);
12920
12952
  *
12921
12953
  * offers.forEach(offer => {
12922
- * console.log(`\n${offer.title}`);
12954
+ * console.log(`\n${offer.name}`);
12923
12955
  * console.log(`${offer.description}`);
12924
- * console.log(`Type: ${offer.redemptionType?.name || 'General'}`);
12956
+ * console.log(`Type: ${offer.type?.name || 'General'}`);
12925
12957
  *
12926
12958
  * if (offer.tokenUnits?.length) {
12927
12959
  * console.log('Cost:');
@@ -12944,6 +12976,23 @@ class RedemptionManager {
12944
12976
  * console.log(`\n${affordableOffers.length} offers within your budget`);
12945
12977
  * ```
12946
12978
  *
12979
+ * @example Search Redemptions
12980
+ * ```typescript
12981
+ * // Search by text (matches name, description, tags)
12982
+ * const { data: coffeeOffers } = await sdk.redemptions.getRedemptions({
12983
+ * search: 'coffee'
12984
+ * });
12985
+ *
12986
+ * console.log(`Found ${coffeeOffers.length} coffee-related offers`);
12987
+ *
12988
+ * // Search with sorting
12989
+ * const { data: sortedResults } = await sdk.redemptions.getRedemptions({
12990
+ * search: 'discount',
12991
+ * sortBy: 'name',
12992
+ * sortOrder: 'ASC'
12993
+ * });
12994
+ * ```
12995
+ *
12947
12996
  * @example Admin - Manage All Redemptions
12948
12997
  * ```typescript
12949
12998
  * // Admins can see all redemptions and filter by status
@@ -12956,14 +13005,11 @@ class RedemptionManager {
12956
13005
  * console.log(`Active offers: ${activeOnly.length}`);
12957
13006
  * console.log(`Inactive offers: ${inactiveOnly.length}`);
12958
13007
  *
12959
- * // Analyze by type
12960
- * const offersByType = allRedemptions.reduce((acc, offer) => {
12961
- * const type = offer.redemptionType?.name || 'Unspecified';
12962
- * acc[type] = (acc[type] || 0) + 1;
12963
- * return acc;
12964
- * }, {});
12965
- *
12966
- * console.log('\nBy type:', offersByType);
13008
+ * // Search inactive redemptions
13009
+ * const { data: inactiveVouchers } = await sdk.redemptions.getRedemptions({
13010
+ * active: false,
13011
+ * search: 'voucher'
13012
+ * });
12967
13013
  * ```
12968
13014
  *
12969
13015
  * @example Pagination
@@ -13163,9 +13209,9 @@ class RedemptionManager {
13163
13209
  * console.log(`Redemption History (${userRedemptions.length} of ${pagination.total} items):`);
13164
13210
  *
13165
13211
  * userRedemptions.forEach((redemption, index) => {
13166
- * console.log(`\n${index + 1}. ${redemption.redemption.title}`);
13212
+ * console.log(`\n${index + 1}. ${redemption.redemption.name}`);
13167
13213
  * console.log(` Date: ${new Date(redemption.redeemedAt).toLocaleDateString()}`);
13168
- * console.log(` Type: ${redemption.redemption.redemptionType?.name || 'General'}`);
13214
+ * console.log(` Type: ${redemption.redemption.type?.name || 'General'}`);
13169
13215
  *
13170
13216
  * if (redemption.voucherCode) {
13171
13217
  * console.log(` Voucher: ${redemption.voucherCode}`);
@@ -13284,7 +13330,7 @@ class RedemptionManager {
13284
13330
  * ```typescript
13285
13331
  * // Admin operation - create discount redemption
13286
13332
  * const newRedemption = await sdk.redemptions.createRedemption({
13287
- * title: '20% Off Weekend Stay',
13333
+ * name: '20% Off Weekend Stay',
13288
13334
  * description: 'Get 20% off your next weekend booking at partner hotels',
13289
13335
  * redemptionTypeId: 'discount-type-id',
13290
13336
  * isActive: true,
@@ -13293,7 +13339,7 @@ class RedemptionManager {
13293
13339
  * validUntil: '2024-08-31T23:59:59Z'
13294
13340
  * });
13295
13341
  *
13296
- * console.log('New redemption created:', newRedemption.title);
13342
+ * console.log('New redemption created:', newRedemption.name);
13297
13343
  * console.log('Redemption ID:', newRedemption.id);
13298
13344
  *
13299
13345
  * // Set redemption cost
@@ -13325,13 +13371,13 @@ class RedemptionManager {
13325
13371
  * ```typescript
13326
13372
  * // Admin operation - update redemption details
13327
13373
  * const updated = await sdk.redemptions.updateRedemption('weekend-discount', {
13328
- * title: '25% Off Weekend Stay - Extended!',
13374
+ * name: '25% Off Weekend Stay - Extended!',
13329
13375
  * description: 'Enhanced discount - now 25% off weekend bookings',
13330
13376
  * availableQuantity: 150, // Increased availability
13331
13377
  * validUntil: '2024-09-30T23:59:59Z' // Extended validity
13332
13378
  * });
13333
13379
  *
13334
- * console.log('Redemption updated:', updated.title);
13380
+ * console.log('Redemption updated:', updated.name);
13335
13381
  * console.log('New quantity available:', updated.availableQuantity);
13336
13382
  * ```
13337
13383
  */
@@ -17630,7 +17676,9 @@ class PersSDK {
17630
17676
  }
17631
17677
  }
17632
17678
  try {
17633
- // Fetch user data to validate tokens and restore session
17679
+ // Ensure token is valid (refresh if expired) before fetching user
17680
+ await this.auth.ensureValidToken();
17681
+ // Fetch user data to restore session
17634
17682
  const userData = await this.auth.getCurrentUser();
17635
17683
  // Emit event through proper event emitter
17636
17684
  this._events.emitSuccess({
@@ -18490,21 +18538,45 @@ function createReactNativeAuthProvider(projectKey, config = {}) {
18490
18538
  });
18491
18539
  }
18492
18540
 
18493
- // Conditionally require quick-crypto for Native platforms only
18494
- let crypto$1;
18495
- if (reactNative.Platform.OS !== 'web') {
18541
+ // Lazy-loaded crypto module - initialized on first use to avoid native module timing issues
18542
+ let _crypto = null;
18543
+ let _cryptoInitialized = false;
18544
+ /**
18545
+ * Lazily load the crypto module.
18546
+ *
18547
+ * Supports customer pre-initialization via global.__QuickCrypto for environments
18548
+ * where native module timing is unpredictable (e.g., certain React Native setups).
18549
+ *
18550
+ * @returns The crypto module or null if unavailable
18551
+ */
18552
+ function getCrypto() {
18553
+ if (_cryptoInitialized) {
18554
+ return _crypto;
18555
+ }
18556
+ _cryptoInitialized = true;
18557
+ if (reactNative.Platform.OS === 'web') {
18558
+ // On Web, we shouldn't be using this provider (Core SDK has WebDPoPProvider)
18559
+ _crypto = {
18560
+ generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); },
18561
+ randomUUID: () => globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`
18562
+ };
18563
+ return _crypto;
18564
+ }
18565
+ // Check for customer pre-initialized global (for native module timing issues)
18566
+ if (typeof global !== 'undefined' && global.__QuickCrypto) {
18567
+ _crypto = global.__QuickCrypto;
18568
+ return _crypto;
18569
+ }
18570
+ // Standard lazy load
18496
18571
  try {
18497
- crypto$1 = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
18572
+ _crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
18498
18573
  }
18499
18574
  catch (e) {
18500
- console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto', e);
18575
+ console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto.', e);
18576
+ console.warn('Tip: If you encounter timing issues, pre-initialize in index.js: global.__QuickCrypto = require("react-native-quick-crypto")');
18577
+ _crypto = null;
18501
18578
  }
18502
- }
18503
- else {
18504
- // on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
18505
- crypto$1 = {
18506
- generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
18507
- };
18579
+ return _crypto;
18508
18580
  }
18509
18581
  class ReactNativeDPoPProvider {
18510
18582
  /**
@@ -18514,13 +18586,17 @@ class ReactNativeDPoPProvider {
18514
18586
  * Falls back to Node.js crypto API on iOS if needed.
18515
18587
  */
18516
18588
  async generateKeyPair(options) {
18589
+ const crypto = getCrypto();
18590
+ if (!crypto) {
18591
+ throw new Error('Crypto module not available. Install react-native-quick-crypto or pre-initialize global.__QuickCrypto');
18592
+ }
18517
18593
  // Try WebCrypto API first (works on both iOS and Android)
18518
- if (crypto$1.subtle) {
18594
+ if (crypto.subtle) {
18519
18595
  try {
18520
- const keyPair = await crypto$1.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, // extractable - required for JWK export
18596
+ const keyPair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, // extractable - required for JWK export
18521
18597
  ['sign', 'verify']);
18522
- const publicKeyJwk = await crypto$1.subtle.exportKey('jwk', keyPair.publicKey);
18523
- const privateKeyJwk = await crypto$1.subtle.exportKey('jwk', keyPair.privateKey);
18598
+ const publicKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
18599
+ const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
18524
18600
  return {
18525
18601
  publicKey: publicKeyJwk,
18526
18602
  privateKey: privateKeyJwk
@@ -18532,7 +18608,7 @@ class ReactNativeDPoPProvider {
18532
18608
  }
18533
18609
  // Fallback: Node.js crypto API (works on iOS)
18534
18610
  return new Promise((resolve, reject) => {
18535
- crypto$1.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
18611
+ crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
18536
18612
  if (err)
18537
18613
  return reject(err);
18538
18614
  try {
@@ -18547,6 +18623,10 @@ class ReactNativeDPoPProvider {
18547
18623
  });
18548
18624
  }
18549
18625
  async signProof(payload, keyPair) {
18626
+ const crypto = getCrypto();
18627
+ if (!crypto) {
18628
+ throw new Error('Crypto module not available for signing');
18629
+ }
18550
18630
  // 1. Construct Header
18551
18631
  const header = {
18552
18632
  typ: 'dpop+jwt',
@@ -18557,17 +18637,17 @@ class ReactNativeDPoPProvider {
18557
18637
  const finalPayload = {
18558
18638
  ...payload,
18559
18639
  iat: payload.iat || Math.floor(Date.now() / 1000),
18560
- jti: payload.jti || crypto$1.randomUUID()
18640
+ jti: payload.jti || (crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`)
18561
18641
  };
18562
18642
  // 3. Encode
18563
18643
  const b64Header = this.base64Url(JSON.stringify(header));
18564
18644
  const b64Payload = this.base64Url(JSON.stringify(finalPayload));
18565
18645
  const signingInput = `${b64Header}.${b64Payload}`;
18566
18646
  // 4. Sign - try WebCrypto first, fallback to Node.js crypto
18567
- if (crypto$1.subtle) {
18647
+ if (crypto.subtle) {
18568
18648
  try {
18569
- const privateKey = await crypto$1.subtle.importKey('jwk', keyPair.privateKey, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
18570
- const signatureBuffer = await crypto$1.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, privateKey, buffer.Buffer.from(signingInput));
18649
+ const privateKey = await crypto.subtle.importKey('jwk', keyPair.privateKey, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
18650
+ const signatureBuffer = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, privateKey, buffer.Buffer.from(signingInput));
18571
18651
  // WebCrypto returns signature in IEEE P1363 format (r || s), which is what we need for JWT
18572
18652
  return `${signingInput}.${this.base64UrlBuffer(buffer.Buffer.from(signatureBuffer))}`;
18573
18653
  }
@@ -18576,24 +18656,28 @@ class ReactNativeDPoPProvider {
18576
18656
  }
18577
18657
  }
18578
18658
  // Fallback: Node.js crypto API
18579
- const sign = crypto$1.createSign('SHA256');
18659
+ const sign = crypto.createSign('SHA256');
18580
18660
  sign.update(signingInput);
18581
- const privateKeyObj = crypto$1.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
18661
+ const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
18582
18662
  const signature = sign.sign(privateKeyObj);
18583
18663
  return `${signingInput}.${this.base64UrlBuffer(signature)}`;
18584
18664
  }
18585
18665
  async hashAccessToken(accessToken) {
18666
+ const crypto = getCrypto();
18667
+ if (!crypto) {
18668
+ throw new Error('Crypto module not available for hashing');
18669
+ }
18586
18670
  // Try WebCrypto first
18587
- if (crypto$1.subtle) {
18671
+ if (crypto.subtle) {
18588
18672
  try {
18589
- const hashBuffer = await crypto$1.subtle.digest('SHA-256', buffer.Buffer.from(accessToken));
18673
+ const hashBuffer = await crypto.subtle.digest('SHA-256', buffer.Buffer.from(accessToken));
18590
18674
  return this.base64UrlBuffer(buffer.Buffer.from(hashBuffer));
18591
18675
  }
18592
18676
  catch (err) {
18593
18677
  // Fallback to Node.js crypto
18594
18678
  }
18595
18679
  }
18596
- const hash = crypto$1.createHash('sha256').update(accessToken).digest();
18680
+ const hash = crypto.createHash('sha256').update(accessToken).digest();
18597
18681
  return this.base64UrlBuffer(hash);
18598
18682
  }
18599
18683
  // --- Helpers ---
@@ -40479,15 +40563,41 @@ const SigningStatus = {
40479
40563
  EXPIRED: 'expired'
40480
40564
  };
40481
40565
 
40482
- // Dynamic import the signer SDK to avoid build issues with static dependencies
40483
- let createPersSignerSDK = null;
40484
- try {
40485
- const signerModule = require('@explorins/pers-signer/react-native');
40486
- createPersSignerSDK = signerModule.createPersSignerSDK;
40487
- }
40488
- catch (error) {
40489
- console.warn('[useTransactionSigner] PERS Signer SDK not available:', error.message);
40490
- console.warn('[useTransactionSigner] Real blockchain signing will not be available');
40566
+ // Lazy-loaded signer SDK factory - initialized on first use to avoid native module timing issues
40567
+ let _createPersSignerSDK = null;
40568
+ let _signerInitAttempted = false;
40569
+ /**
40570
+ * Lazily load the PERS Signer SDK.
40571
+ *
40572
+ * Supports customer pre-initialization via global.__PersSigner for environments
40573
+ * where native module timing is unpredictable (e.g., certain React Native setups).
40574
+ *
40575
+ * @returns The createPersSignerSDK function or null if unavailable
40576
+ */
40577
+ function getSignerSDKFactory() {
40578
+ if (_signerInitAttempted) {
40579
+ // Already tried - return cached result (could be null if failed)
40580
+ return _createPersSignerSDK;
40581
+ }
40582
+ _signerInitAttempted = true;
40583
+ // Check for customer pre-initialized global (for native module timing issues)
40584
+ if (typeof global !== 'undefined' && global.__PersSigner?.createPersSignerSDK) {
40585
+ _createPersSignerSDK = global.__PersSigner.createPersSignerSDK;
40586
+ return _createPersSignerSDK;
40587
+ }
40588
+ // Standard lazy load
40589
+ try {
40590
+ const signerModule = require('@explorins/pers-signer/react-native');
40591
+ _createPersSignerSDK = signerModule.createPersSignerSDK;
40592
+ return _createPersSignerSDK;
40593
+ }
40594
+ catch (error) {
40595
+ console.warn('[useTransactionSigner] PERS Signer SDK not available:', error.message);
40596
+ console.warn('[useTransactionSigner] Real blockchain signing will not be available');
40597
+ console.warn('[useTransactionSigner] Tip: If timing issues, pre-initialize in index.js: global.__PersSigner = require("@explorins/pers-signer/react-native")');
40598
+ _createPersSignerSDK = null; // Mark as unavailable
40599
+ return null;
40600
+ }
40491
40601
  }
40492
40602
  // Constants - TODO: Move to environment config later
40493
40603
  const DEFAULT_ETHERS_PROVIDER = "https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e";
@@ -40677,6 +40787,7 @@ const useTransactionSigner = () => {
40677
40787
  // Auto-initialize signer SDK when PERS SDK is ready
40678
40788
  react.useEffect(() => {
40679
40789
  const initializeSignerSDK = async () => {
40790
+ const createPersSignerSDK = getSignerSDKFactory();
40680
40791
  if (!createPersSignerSDK) {
40681
40792
  console.warn('[useTransactionSigner] Signer SDK not available');
40682
40793
  return;
@@ -40791,7 +40902,7 @@ const useTransactionSigner = () => {
40791
40902
  if (!isSignerInitialized || !signerSDKRef.current) {
40792
40903
  throw new Error('Transaction signer not initialized');
40793
40904
  }
40794
- if (!createPersSignerSDK) {
40905
+ if (!getSignerSDKFactory()) {
40795
40906
  throw new Error('PERS Signer SDK not available. Blockchain signing is not supported.');
40796
40907
  }
40797
40908
  // Create status callback wrapper that updates both hook state and calls user callback
@@ -40935,7 +41046,7 @@ const useTransactionSigner = () => {
40935
41046
  * );
40936
41047
  * ```
40937
41048
  */
40938
- isSignerAvailable: isSignerInitialized && !!createPersSignerSDK,
41049
+ isSignerAvailable: isSignerInitialized && !!getSignerSDKFactory(),
40939
41050
  /**
40940
41051
  * Current signing status for UI feedback
40941
41052
  *
@@ -41830,6 +41941,35 @@ const useRedemptions = () => {
41830
41941
  * **Administrators:** Can see all redemptions and filter by active/inactive status
41831
41942
  *
41832
41943
  * The backend enforces permission-based filtering automatically.
41944
+ *
41945
+ * @param options - Filter and pagination options
41946
+ * @param options.active - Filter by active status (Admin only)
41947
+ * @param options.search - Free text search across name, description, tags
41948
+ * @param options.page - Page number (1-indexed)
41949
+ * @param options.limit - Results per page
41950
+ * @param options.sortBy - Field to sort by ('name' | 'createdAt')
41951
+ * @param options.sortOrder - Sort direction ('ASC' | 'DESC')
41952
+ *
41953
+ * @example Basic usage
41954
+ * ```typescript
41955
+ * const { data: offers } = await getRedemptions();
41956
+ * ```
41957
+ *
41958
+ * @example Search redemptions
41959
+ * ```typescript
41960
+ * const { data: coffeeOffers } = await getRedemptions({ search: 'coffee' });
41961
+ * ```
41962
+ *
41963
+ * @example With pagination and sorting
41964
+ * ```typescript
41965
+ * const { data, pagination } = await getRedemptions({
41966
+ * search: 'discount',
41967
+ * page: 1,
41968
+ * limit: 10,
41969
+ * sortBy: 'name',
41970
+ * sortOrder: 'ASC'
41971
+ * });
41972
+ * ```
41833
41973
  */
41834
41974
  const getRedemptions = react.useCallback(async (options) => {
41835
41975
  if (!isInitialized || !sdk) {