@explorins/pers-sdk-react-native 2.1.12 → 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/README.md +108 -14
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useCampaigns.d.ts +2 -0
- package/dist/hooks/useCampaigns.d.ts.map +1 -1
- package/dist/hooks/useCampaigns.js +58 -1
- package/dist/hooks/useRedemptions.d.ts +28 -7
- package/dist/hooks/useRedemptions.d.ts.map +1 -1
- package/dist/hooks/useRedemptions.js +91 -10
- package/dist/hooks/useTokenBalances.d.ts +8 -1
- package/dist/hooks/useTokenBalances.d.ts.map +1 -1
- package/dist/hooks/useTokenBalances.js +8 -1
- package/dist/hooks/useTransactionSigner.d.ts.map +1 -1
- package/dist/hooks/useTransactionSigner.js +38 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +461 -116
- package/dist/index.js.map +1 -1
- package/dist/providers/rn-dpop-provider.d.ts.map +1 -1
- package/dist/providers/rn-dpop-provider.js +48 -12
- package/package.json +3 -3
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useCampaigns.ts +62 -2
- package/src/hooks/useRedemptions.ts +119 -13
- package/src/hooks/useTokenBalances.ts +8 -1
- package/src/hooks/useTransactionSigner.ts +41 -10
- package/src/index.ts +1 -1
- package/src/providers/rn-dpop-provider.ts +57 -11
package/dist/index.js
CHANGED
|
@@ -2882,12 +2882,21 @@ exports.SignedUrlType = void 0;
|
|
|
2882
2882
|
* Token Validity Type Enum
|
|
2883
2883
|
*
|
|
2884
2884
|
* Defines how token expiry is calculated:
|
|
2885
|
+
*
|
|
2886
|
+
* Static types (computed at issuance from template):
|
|
2885
2887
|
* - FIXED_DATE: Uses expiryDate directly as absolute expiration
|
|
2886
2888
|
* - DAYS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration days
|
|
2887
2889
|
* - HOURS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration hours
|
|
2888
2890
|
* - MONTHS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration months
|
|
2889
2891
|
* - END_OF_MONTH: Token expires at end of the month when issued
|
|
2890
2892
|
* - END_OF_YEAR: Token expires at end of the year when issued
|
|
2893
|
+
*
|
|
2894
|
+
* Trigger-based types (computed from runtime context):
|
|
2895
|
+
* - TRIGGER_DATE: Uses date from context.validityDate (e.g., hotel check-out date)
|
|
2896
|
+
* - TRIGGER_DATE_RANGE: Uses context.validityEndDate directly (for explicit start/end scenarios)
|
|
2897
|
+
* - DAYS_FROM_TRIGGER: Computes expiry as context.validityDate + duration days
|
|
2898
|
+
* Duration from context.validityDuration OR template.validityDuration
|
|
2899
|
+
* - HOURS_FROM_TRIGGER: Same as above but hours
|
|
2891
2900
|
*/
|
|
2892
2901
|
const TokenValidityTypes = {
|
|
2893
2902
|
FIXED_DATE: 'fixed_date',
|
|
@@ -2896,6 +2905,11 @@ const TokenValidityTypes = {
|
|
|
2896
2905
|
MONTHS_FROM_ISSUANCE: 'months_from_issuance',
|
|
2897
2906
|
END_OF_MONTH: 'end_of_month',
|
|
2898
2907
|
END_OF_YEAR: 'end_of_year',
|
|
2908
|
+
// Trigger-based types - require context at runtime
|
|
2909
|
+
TRIGGER_DATE: 'trigger_date',
|
|
2910
|
+
TRIGGER_DATE_RANGE: 'trigger_date_range',
|
|
2911
|
+
DAYS_FROM_TRIGGER: 'days_from_trigger',
|
|
2912
|
+
HOURS_FROM_TRIGGER: 'hours_from_trigger',
|
|
2899
2913
|
};
|
|
2900
2914
|
const TOKEN_VALIDITY_TYPE_VALUES = Object.values(TokenValidityTypes);
|
|
2901
2915
|
|
|
@@ -5924,6 +5938,8 @@ class CampaignApi {
|
|
|
5924
5938
|
params.push(`tag=${encodeURIComponent(options.tag)}`);
|
|
5925
5939
|
if (options?.businessId)
|
|
5926
5940
|
params.push(`businessId=${encodeURIComponent(options.businessId)}`);
|
|
5941
|
+
if (options?.search)
|
|
5942
|
+
params.push(`search=${encodeURIComponent(options.search)}`);
|
|
5927
5943
|
if (options?.sortBy)
|
|
5928
5944
|
params.push(`sortBy=${options.sortBy}`);
|
|
5929
5945
|
if (options?.sortOrder)
|
|
@@ -6290,7 +6306,9 @@ class CampaignService {
|
|
|
6290
6306
|
/**
|
|
6291
6307
|
* Get campaigns with pagination support
|
|
6292
6308
|
* Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all
|
|
6293
|
-
* @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)
|
|
6294
6312
|
* @param options.businessId - Filter by business engagement
|
|
6295
6313
|
* @param options.page - Page number (1-based, default: 1)
|
|
6296
6314
|
* @param options.limit - Items per page (default: 50)
|
|
@@ -6466,15 +6484,23 @@ class RedemptionApi {
|
|
|
6466
6484
|
* - Public users: Get active redemptions only
|
|
6467
6485
|
* - Admin users: Get all redemptions with optional filtering
|
|
6468
6486
|
*
|
|
6469
|
-
* @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)
|
|
6470
6488
|
* @returns Paginated response with redemptions
|
|
6471
6489
|
*/
|
|
6472
6490
|
async getRedemptions(options) {
|
|
6473
|
-
const params =
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
}
|
|
6477
|
-
|
|
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('&')}`);
|
|
6478
6504
|
return normalizeToPaginated(response);
|
|
6479
6505
|
}
|
|
6480
6506
|
/**
|
|
@@ -6516,12 +6542,11 @@ class RedemptionApi {
|
|
|
6516
6542
|
* - USER: Direct user redemption processing
|
|
6517
6543
|
* - ADMIN: Can process redemptions for any account type
|
|
6518
6544
|
* - BUSINESS: Process redemptions for customers
|
|
6545
|
+
*
|
|
6546
|
+
* @param request - Redemption request with optional context for dynamic NFT generation
|
|
6519
6547
|
*/
|
|
6520
|
-
async redeemRedemption(
|
|
6521
|
-
|
|
6522
|
-
redemptionId: redemptionId,
|
|
6523
|
-
};
|
|
6524
|
-
return this.apiClient.post(this.basePath + '/redeems', body);
|
|
6548
|
+
async redeemRedemption(request) {
|
|
6549
|
+
return this.apiClient.post(this.basePath + '/redeems', request);
|
|
6525
6550
|
}
|
|
6526
6551
|
// ==========================================
|
|
6527
6552
|
// REDEMPTION REDEEMS QUERIES (NEW ENDPOINTS)
|
|
@@ -6531,10 +6556,10 @@ class RedemptionApi {
|
|
|
6531
6556
|
*
|
|
6532
6557
|
* Role-based access with unified filtering:
|
|
6533
6558
|
* - Users: See only their own redeems (userId/businessId filters ignored)
|
|
6534
|
-
* - Admins: Can filter by userId, businessId, or
|
|
6559
|
+
* - Admins: Can filter by userId, businessId, redemptionId, or status
|
|
6535
6560
|
*
|
|
6536
|
-
* @param filters - Filter and pagination options (redemptionId, userId, businessId, myRedeems, page, limit, sortBy, sortOrder)
|
|
6537
|
-
* @param include - Optional relations to include (redemption, user, business)
|
|
6561
|
+
* @param filters - Filter and pagination options (redemptionId, userId, businessId, status, myRedeems, page, limit, sortBy, sortOrder)
|
|
6562
|
+
* @param include - Optional relations to include (redemption, user, business, transactions)
|
|
6538
6563
|
* @returns Paginated response with redemption redeems
|
|
6539
6564
|
*/
|
|
6540
6565
|
async getRedemptionRedeems(filters, include) {
|
|
@@ -6546,6 +6571,9 @@ class RedemptionApi {
|
|
|
6546
6571
|
if (filters?.redemptionId) {
|
|
6547
6572
|
params.append('redemptionId', filters.redemptionId);
|
|
6548
6573
|
}
|
|
6574
|
+
if (filters?.status) {
|
|
6575
|
+
params.append('status', filters.status);
|
|
6576
|
+
}
|
|
6549
6577
|
// Add include parameter
|
|
6550
6578
|
if (include && include.length > 0) {
|
|
6551
6579
|
include.forEach(relation => params.append('include', relation));
|
|
@@ -6560,6 +6588,8 @@ class RedemptionApi {
|
|
|
6560
6588
|
params.append('userId', filters.userId);
|
|
6561
6589
|
if (filters?.businessId)
|
|
6562
6590
|
params.append('businessId', filters.businessId);
|
|
6591
|
+
if (filters?.status)
|
|
6592
|
+
params.append('status', filters.status);
|
|
6563
6593
|
// Add include parameter
|
|
6564
6594
|
if (include && include.length > 0) {
|
|
6565
6595
|
include.forEach(relation => params.append('include', relation));
|
|
@@ -6588,13 +6618,13 @@ class RedemptionApi {
|
|
|
6588
6618
|
* UNIFIED: Get user redemption history
|
|
6589
6619
|
*
|
|
6590
6620
|
* Uses convenience endpoint /redemption-redeems/me with optional filtering
|
|
6591
|
-
* @param
|
|
6621
|
+
* @param filters - Optional filters (redemptionId, status)
|
|
6592
6622
|
* @param options - Pagination options (page, limit, sortBy, sortOrder)
|
|
6593
|
-
* @param include - Optional relations to include (redemption, user, business)
|
|
6623
|
+
* @param include - Optional relations to include (redemption, user, business, transactions)
|
|
6594
6624
|
* @returns Paginated response with user's redemption redeems
|
|
6595
6625
|
*/
|
|
6596
|
-
async getUserRedeems(
|
|
6597
|
-
return this.getRedemptionRedeems({ myRedeems: true,
|
|
6626
|
+
async getUserRedeems(filters, options, include) {
|
|
6627
|
+
return this.getRedemptionRedeems({ myRedeems: true, ...filters, ...options }, include);
|
|
6598
6628
|
}
|
|
6599
6629
|
// ==========================================
|
|
6600
6630
|
// ADMIN OPERATIONS (Tenant Admin JWT)
|
|
@@ -6699,7 +6729,12 @@ class RedemptionService {
|
|
|
6699
6729
|
/**
|
|
6700
6730
|
* UNIFIED: Get redemptions with intelligent access control and pagination
|
|
6701
6731
|
* @param options.active - Filter by active status
|
|
6732
|
+
* @param options.search - Free text search across name, description, tags (case-insensitive)
|
|
6702
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')
|
|
6703
6738
|
*/
|
|
6704
6739
|
async getRedemptions(options) {
|
|
6705
6740
|
return this.redemptionApi.getRedemptions(options);
|
|
@@ -6720,35 +6755,43 @@ class RedemptionService {
|
|
|
6720
6755
|
// AUTHENTICATED OPERATIONS
|
|
6721
6756
|
// ==========================================
|
|
6722
6757
|
/**
|
|
6723
|
-
* AUTH: Redeem a redemption
|
|
6758
|
+
* AUTH: Redeem a redemption with optional dynamic context
|
|
6759
|
+
*
|
|
6760
|
+
* @param request - Redemption request (can be just redemptionId string or full request with context)
|
|
6724
6761
|
*/
|
|
6725
|
-
async redeemRedemption(
|
|
6726
|
-
|
|
6762
|
+
async redeemRedemption(request) {
|
|
6763
|
+
// Support both simple string and full request object
|
|
6764
|
+
const requestBody = typeof request === 'string'
|
|
6765
|
+
? { redemptionId: request }
|
|
6766
|
+
: request;
|
|
6767
|
+
return this.redemptionApi.redeemRedemption(requestBody);
|
|
6727
6768
|
}
|
|
6728
6769
|
/**
|
|
6729
6770
|
* UNIFIED: Get redemption redeems with filtering and pagination
|
|
6730
|
-
* @param filters - Filter options
|
|
6731
|
-
* @param include - Optional relations to include (redemption, user, business)
|
|
6771
|
+
* @param filters - Filter options (redemptionId, userId, businessId, status, myRedeems)
|
|
6772
|
+
* @param include - Optional relations to include (redemption, user, business, transactions)
|
|
6732
6773
|
*/
|
|
6733
6774
|
async getRedemptionRedeems(filters, include) {
|
|
6734
6775
|
return this.redemptionApi.getRedemptionRedeems(filters, include);
|
|
6735
6776
|
}
|
|
6736
6777
|
/**
|
|
6737
|
-
* Convenience: Get user redemptions with pagination
|
|
6778
|
+
* Convenience: Get user redemptions with pagination and filtering
|
|
6779
|
+
* @param filters - Optional filters (redemptionId, status)
|
|
6738
6780
|
* @param options - Pagination options
|
|
6739
|
-
* @param include - Optional relations to include (redemption, user, business)
|
|
6781
|
+
* @param include - Optional relations to include (redemption, user, business, transactions)
|
|
6740
6782
|
*/
|
|
6741
|
-
async getUserRedeems(options, include) {
|
|
6742
|
-
return this.redemptionApi.getUserRedeems(
|
|
6783
|
+
async getUserRedeems(filters, options, include) {
|
|
6784
|
+
return this.redemptionApi.getUserRedeems(filters, options, include);
|
|
6743
6785
|
}
|
|
6744
6786
|
// ==========================================
|
|
6745
6787
|
// ADMIN OPERATIONS
|
|
6746
6788
|
// ==========================================
|
|
6747
6789
|
/**
|
|
6748
|
-
* Convenience: Get redemptions as admin
|
|
6790
|
+
* Convenience: Get redemptions as admin with filtering and search
|
|
6791
|
+
* @param options - Filter options (active, search, pagination)
|
|
6749
6792
|
*/
|
|
6750
|
-
async getRedemptionsAsAdmin(
|
|
6751
|
-
return this.redemptionApi.getRedemptions({
|
|
6793
|
+
async getRedemptionsAsAdmin(options) {
|
|
6794
|
+
return this.redemptionApi.getRedemptions({ ...options, adminAccess: true });
|
|
6752
6795
|
}
|
|
6753
6796
|
/**
|
|
6754
6797
|
* ADMIN: Create redemption
|
|
@@ -7281,7 +7324,7 @@ function buildAccountSelector(data) {
|
|
|
7281
7324
|
* 🎯 Build a MINT transaction request (admin/business endpoint)
|
|
7282
7325
|
* Creates tokens and assigns them to a recipient account
|
|
7283
7326
|
*
|
|
7284
|
-
* @example
|
|
7327
|
+
* @example Basic Mint
|
|
7285
7328
|
* ```typescript
|
|
7286
7329
|
* const request = buildMintRequest({
|
|
7287
7330
|
* amount: 100,
|
|
@@ -7292,6 +7335,27 @@ function buildAccountSelector(data) {
|
|
|
7292
7335
|
* engagedBusinessId: 'business-456' // optional: for tracking which business triggered the mint
|
|
7293
7336
|
* });
|
|
7294
7337
|
* ```
|
|
7338
|
+
*
|
|
7339
|
+
* @example ERC721 Mint with Dynamic Context
|
|
7340
|
+
* ```typescript
|
|
7341
|
+
* const request = buildMintRequest({
|
|
7342
|
+
* amount: 1,
|
|
7343
|
+
* contractAddress: '0x...',
|
|
7344
|
+
* contractTokenId: '123', // TokenMetadata ID
|
|
7345
|
+
* chainId: 137,
|
|
7346
|
+
* recipientAccountId: 'user-123',
|
|
7347
|
+
* recipientAccountType: AccountOwnerType.USER,
|
|
7348
|
+
* context: {
|
|
7349
|
+
* // Validity control
|
|
7350
|
+
* validityDate: '2026-04-15T14:00:00Z',
|
|
7351
|
+
* validityEndDate: '2026-04-20T11:00:00Z',
|
|
7352
|
+
* // AI prompt placeholders (available as {{context.xxx}})
|
|
7353
|
+
* guestName: 'John Doe',
|
|
7354
|
+
* roomNumber: '305',
|
|
7355
|
+
* awardReason: 'Employee of the Month'
|
|
7356
|
+
* }
|
|
7357
|
+
* });
|
|
7358
|
+
* ```
|
|
7295
7359
|
*/
|
|
7296
7360
|
function buildMintRequest(data) {
|
|
7297
7361
|
return {
|
|
@@ -7305,7 +7369,8 @@ function buildMintRequest(data) {
|
|
|
7305
7369
|
accountAddress: data.recipientAccountAddress
|
|
7306
7370
|
})
|
|
7307
7371
|
},
|
|
7308
|
-
...(data.engagedBusinessId && { engagedBusinessId: data.engagedBusinessId })
|
|
7372
|
+
...(data.engagedBusinessId && { engagedBusinessId: data.engagedBusinessId }),
|
|
7373
|
+
...(data.context && { context: data.context })
|
|
7309
7374
|
};
|
|
7310
7375
|
}
|
|
7311
7376
|
/**
|
|
@@ -11843,7 +11908,7 @@ class BusinessManager {
|
|
|
11843
11908
|
*
|
|
11844
11909
|
* // Get specific campaign details
|
|
11845
11910
|
* const campaign = await sdk.campaigns.getCampaignById('summer-promo-2024');
|
|
11846
|
-
* console.log('Campaign:', campaign.
|
|
11911
|
+
* console.log('Campaign:', campaign.name);
|
|
11847
11912
|
*
|
|
11848
11913
|
* // User claims campaign reward
|
|
11849
11914
|
* const claim = await sdk.campaigns.claimCampaign({
|
|
@@ -11859,7 +11924,7 @@ class BusinessManager {
|
|
|
11859
11924
|
*
|
|
11860
11925
|
* console.log('My Campaign Claims:');
|
|
11861
11926
|
* userClaims.forEach(claim => {
|
|
11862
|
-
* console.log(`- ${claim.campaign.
|
|
11927
|
+
* console.log(`- ${claim.campaign.name}`);
|
|
11863
11928
|
* console.log(` Rewards: ${claim.rewards?.length || 0} tokens`);
|
|
11864
11929
|
* console.log(` Date: ${claim.claimedAt}`);
|
|
11865
11930
|
* });
|
|
@@ -11875,7 +11940,7 @@ class BusinessManager {
|
|
|
11875
11940
|
* ```typescript
|
|
11876
11941
|
* // Admin: Create seasonal campaign
|
|
11877
11942
|
* const newCampaign = await sdk.campaigns.createCampaign({
|
|
11878
|
-
*
|
|
11943
|
+
* name: 'Holiday Rewards',
|
|
11879
11944
|
* description: 'Special holiday loyalty bonuses',
|
|
11880
11945
|
* startDate: '2024-12-01',
|
|
11881
11946
|
* endDate: '2024-12-31',
|
|
@@ -11914,7 +11979,7 @@ class CampaignManager {
|
|
|
11914
11979
|
* const campaign = await sdk.campaigns.getCampaignById('summer-promo-2024');
|
|
11915
11980
|
*
|
|
11916
11981
|
* console.log('Campaign Details:');
|
|
11917
|
-
* console.log('
|
|
11982
|
+
* console.log('Name:', campaign.name);
|
|
11918
11983
|
* console.log('Description:', campaign.description);
|
|
11919
11984
|
* console.log('Active:', campaign.isActive);
|
|
11920
11985
|
* console.log('Period:', `${campaign.startDate} to ${campaign.endDate}`);
|
|
@@ -12028,7 +12093,7 @@ class CampaignManager {
|
|
|
12028
12093
|
* console.log(`Campaign History (${userClaims.length} claims):`);
|
|
12029
12094
|
*
|
|
12030
12095
|
* userClaims.forEach((claim, index) => {
|
|
12031
|
-
* console.log(`\n${index + 1}. ${claim.campaign.
|
|
12096
|
+
* console.log(`\n${index + 1}. ${claim.campaign.name}`);
|
|
12032
12097
|
* console.log(` Claimed: ${new Date(claim.claimedAt).toLocaleDateString()}`);
|
|
12033
12098
|
* console.log(` Business: ${claim.business?.displayName || 'N/A'}`);
|
|
12034
12099
|
*
|
|
@@ -12058,7 +12123,7 @@ class CampaignManager {
|
|
|
12058
12123
|
* Returns campaigns with pagination metadata for efficient data loading.
|
|
12059
12124
|
* Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all.
|
|
12060
12125
|
*
|
|
12061
|
-
* @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)
|
|
12062
12127
|
* @returns Promise resolving to paginated campaigns with metadata
|
|
12063
12128
|
*
|
|
12064
12129
|
* @example
|
|
@@ -12075,6 +12140,19 @@ class CampaignManager {
|
|
|
12075
12140
|
* limit: 20
|
|
12076
12141
|
* });
|
|
12077
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
|
+
*
|
|
12078
12156
|
* // Get active campaigns sorted by creation date
|
|
12079
12157
|
* const activeCampaigns = await sdk.campaigns.getCampaigns({
|
|
12080
12158
|
* active: true,
|
|
@@ -12108,7 +12186,7 @@ class CampaignManager {
|
|
|
12108
12186
|
* ```typescript
|
|
12109
12187
|
* // Admin operation - create seasonal campaign
|
|
12110
12188
|
* const campaign = await sdk.campaigns.createCampaign({
|
|
12111
|
-
*
|
|
12189
|
+
* name: 'Summer Travel Rewards',
|
|
12112
12190
|
* description: 'Earn bonus points for summer bookings',
|
|
12113
12191
|
* startDate: '2024-06-01T00:00:00Z',
|
|
12114
12192
|
* endDate: '2024-08-31T23:59:59Z',
|
|
@@ -12117,7 +12195,7 @@ class CampaignManager {
|
|
|
12117
12195
|
* campaignType: 'SEASONAL'
|
|
12118
12196
|
* });
|
|
12119
12197
|
*
|
|
12120
|
-
* console.log('Campaign created:', campaign.
|
|
12198
|
+
* console.log('Campaign created:', campaign.name);
|
|
12121
12199
|
* console.log('Campaign ID:', campaign.id);
|
|
12122
12200
|
*
|
|
12123
12201
|
* // Set up campaign rewards
|
|
@@ -12147,13 +12225,13 @@ class CampaignManager {
|
|
|
12147
12225
|
* ```typescript
|
|
12148
12226
|
* // Admin operation - extend campaign duration
|
|
12149
12227
|
* const updated = await sdk.campaigns.updateCampaign('summer-2024', {
|
|
12150
|
-
*
|
|
12228
|
+
* name: 'Extended Summer Travel Rewards',
|
|
12151
12229
|
* description: 'Earn bonus points for summer and early fall bookings',
|
|
12152
12230
|
* endDate: '2024-09-30T23:59:59Z', // Extended end date
|
|
12153
12231
|
* isActive: true
|
|
12154
12232
|
* });
|
|
12155
12233
|
*
|
|
12156
|
-
* console.log('Campaign updated:', updated.
|
|
12234
|
+
* console.log('Campaign updated:', updated.name);
|
|
12157
12235
|
* console.log('New end date:', updated.endDate);
|
|
12158
12236
|
* ```
|
|
12159
12237
|
*/
|
|
@@ -12618,7 +12696,7 @@ class CampaignManager {
|
|
|
12618
12696
|
* console.log(`Total campaigns claimed: ${userClaims.length}`);
|
|
12619
12697
|
*
|
|
12620
12698
|
* userClaims.forEach((claim, index) => {
|
|
12621
|
-
* console.log(`\n${index + 1}. ${claim.campaign.
|
|
12699
|
+
* console.log(`\n${index + 1}. ${claim.campaign.name}`);
|
|
12622
12700
|
* console.log(` Date: ${claim.claimedAt}`);
|
|
12623
12701
|
* console.log(` Business: ${claim.business?.displayName || 'Direct'}`);
|
|
12624
12702
|
* console.log(` Rewards: ${claim.rewards?.length || 0} items`);
|
|
@@ -12655,8 +12733,8 @@ class CampaignManager {
|
|
|
12655
12733
|
*
|
|
12656
12734
|
* // Analyze claims by campaign
|
|
12657
12735
|
* const claimsByCampaign = businessClaims.reduce((acc, claim) => {
|
|
12658
|
-
* const
|
|
12659
|
-
* acc[
|
|
12736
|
+
* const campaignName = claim.campaign.name;
|
|
12737
|
+
* acc[campaignName] = (acc[campaignName] || 0) + 1;
|
|
12660
12738
|
* return acc;
|
|
12661
12739
|
* }, {});
|
|
12662
12740
|
*
|
|
@@ -12860,6 +12938,7 @@ class RedemptionManager {
|
|
|
12860
12938
|
*
|
|
12861
12939
|
* @param options - Optional filters and pagination:
|
|
12862
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)
|
|
12863
12942
|
* - Pagination options: `page`, `limit`, `sortBy`, `sortOrder`
|
|
12864
12943
|
* @returns Promise resolving to paginated redemption offers
|
|
12865
12944
|
*
|
|
@@ -12872,9 +12951,9 @@ class RedemptionManager {
|
|
|
12872
12951
|
* console.log(`Showing ${offers.length} of ${pagination.total} offers`);
|
|
12873
12952
|
*
|
|
12874
12953
|
* offers.forEach(offer => {
|
|
12875
|
-
* console.log(`\n${offer.
|
|
12954
|
+
* console.log(`\n${offer.name}`);
|
|
12876
12955
|
* console.log(`${offer.description}`);
|
|
12877
|
-
* console.log(`Type: ${offer.
|
|
12956
|
+
* console.log(`Type: ${offer.type?.name || 'General'}`);
|
|
12878
12957
|
*
|
|
12879
12958
|
* if (offer.tokenUnits?.length) {
|
|
12880
12959
|
* console.log('Cost:');
|
|
@@ -12897,6 +12976,23 @@ class RedemptionManager {
|
|
|
12897
12976
|
* console.log(`\n${affordableOffers.length} offers within your budget`);
|
|
12898
12977
|
* ```
|
|
12899
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
|
+
*
|
|
12900
12996
|
* @example Admin - Manage All Redemptions
|
|
12901
12997
|
* ```typescript
|
|
12902
12998
|
* // Admins can see all redemptions and filter by status
|
|
@@ -12909,14 +13005,11 @@ class RedemptionManager {
|
|
|
12909
13005
|
* console.log(`Active offers: ${activeOnly.length}`);
|
|
12910
13006
|
* console.log(`Inactive offers: ${inactiveOnly.length}`);
|
|
12911
13007
|
*
|
|
12912
|
-
* //
|
|
12913
|
-
* const
|
|
12914
|
-
*
|
|
12915
|
-
*
|
|
12916
|
-
*
|
|
12917
|
-
* }, {});
|
|
12918
|
-
*
|
|
12919
|
-
* console.log('\nBy type:', offersByType);
|
|
13008
|
+
* // Search inactive redemptions
|
|
13009
|
+
* const { data: inactiveVouchers } = await sdk.redemptions.getRedemptions({
|
|
13010
|
+
* active: false,
|
|
13011
|
+
* search: 'voucher'
|
|
13012
|
+
* });
|
|
12920
13013
|
* ```
|
|
12921
13014
|
*
|
|
12922
13015
|
* @example Pagination
|
|
@@ -12987,7 +13080,12 @@ class RedemptionManager {
|
|
|
12987
13080
|
* redemption confirmation details. Creates a permanent redemption record
|
|
12988
13081
|
* and may generate voucher codes or instructions.
|
|
12989
13082
|
*
|
|
13083
|
+
* Supports dynamic context for personalized NFT generation when the redemption
|
|
13084
|
+
* issues ERC721 tokens. Context can include validity dates, AI prompt placeholders,
|
|
13085
|
+
* and custom metadata.
|
|
13086
|
+
*
|
|
12990
13087
|
* @param redemptionId - ID of the redemption offer to redeem
|
|
13088
|
+
* @param context - Optional dynamic context for NFT personalization (validity dates, AI prompts)
|
|
12991
13089
|
* @returns Promise resolving to redemption result with confirmation details
|
|
12992
13090
|
* @throws {PersApiError} When redemption is not available or user lacks required tokens
|
|
12993
13091
|
*
|
|
@@ -13035,6 +13133,30 @@ class RedemptionManager {
|
|
|
13035
13133
|
* }
|
|
13036
13134
|
* ```
|
|
13037
13135
|
*
|
|
13136
|
+
* @example Redemption with Dynamic Context (Personalized NFT)
|
|
13137
|
+
* ```typescript
|
|
13138
|
+
* // Event ticket with dynamic validity and personalization
|
|
13139
|
+
* const result = await sdk.redemptions.redeem('event-ticket-123', {
|
|
13140
|
+
* // Validity - token valid for event duration + 2 hours
|
|
13141
|
+
* validityDate: new Date().toISOString(),
|
|
13142
|
+
* validityDuration: 8, // 8 hours
|
|
13143
|
+
*
|
|
13144
|
+
* // AI prompt context - available as {{context.xxx}} in prompts
|
|
13145
|
+
* attendeeName: 'John Doe',
|
|
13146
|
+
* ticketTier: 'VIP',
|
|
13147
|
+
* seatSection: 'A12',
|
|
13148
|
+
* });
|
|
13149
|
+
*
|
|
13150
|
+
* // Hotel stay NFT with check-in/check-out dates
|
|
13151
|
+
* const hotelResult = await sdk.redemptions.redeem('hotel-nft-456', {
|
|
13152
|
+
* validityDate: '2026-04-15T14:00:00Z', // Check-in
|
|
13153
|
+
* validityEndDate: '2026-04-20T11:00:00Z', // Check-out
|
|
13154
|
+
* guestName: 'Jane Smith',
|
|
13155
|
+
* roomNumber: '305',
|
|
13156
|
+
* roomType: 'Suite',
|
|
13157
|
+
* });
|
|
13158
|
+
* ```
|
|
13159
|
+
*
|
|
13038
13160
|
* @example Redemption with Pre-Validation
|
|
13039
13161
|
* ```typescript
|
|
13040
13162
|
* // Get offer details first
|
|
@@ -13058,13 +13180,16 @@ class RedemptionManager {
|
|
|
13058
13180
|
* }
|
|
13059
13181
|
* ```
|
|
13060
13182
|
*/
|
|
13061
|
-
async redeem(redemptionId) {
|
|
13062
|
-
const
|
|
13183
|
+
async redeem(redemptionId, context) {
|
|
13184
|
+
const request = context
|
|
13185
|
+
? { redemptionId, context }
|
|
13186
|
+
: { redemptionId };
|
|
13187
|
+
const result = await this.redemptionService.redeemRedemption(request);
|
|
13063
13188
|
this.events?.emitSuccess({
|
|
13064
13189
|
domain: 'redemption',
|
|
13065
13190
|
type: 'redeem_success',
|
|
13066
13191
|
userMessage: 'Reward redeemed successfully',
|
|
13067
|
-
details: { redemptionId }
|
|
13192
|
+
details: { redemptionId, hasContext: !!context }
|
|
13068
13193
|
});
|
|
13069
13194
|
return result;
|
|
13070
13195
|
}
|
|
@@ -13084,9 +13209,9 @@ class RedemptionManager {
|
|
|
13084
13209
|
* console.log(`Redemption History (${userRedemptions.length} of ${pagination.total} items):`);
|
|
13085
13210
|
*
|
|
13086
13211
|
* userRedemptions.forEach((redemption, index) => {
|
|
13087
|
-
* console.log(`\n${index + 1}. ${redemption.redemption.
|
|
13212
|
+
* console.log(`\n${index + 1}. ${redemption.redemption.name}`);
|
|
13088
13213
|
* console.log(` Date: ${new Date(redemption.redeemedAt).toLocaleDateString()}`);
|
|
13089
|
-
* console.log(` Type: ${redemption.redemption.
|
|
13214
|
+
* console.log(` Type: ${redemption.redemption.type?.name || 'General'}`);
|
|
13090
13215
|
*
|
|
13091
13216
|
* if (redemption.voucherCode) {
|
|
13092
13217
|
* console.log(` Voucher: ${redemption.voucherCode}`);
|
|
@@ -13127,9 +13252,19 @@ class RedemptionManager {
|
|
|
13127
13252
|
* console.log(`${type}: ${count} redemptions`);
|
|
13128
13253
|
* });
|
|
13129
13254
|
* ```
|
|
13255
|
+
*
|
|
13256
|
+
* @example Filter by Status
|
|
13257
|
+
* ```typescript
|
|
13258
|
+
* import { ProcessRecordStatus } from '@explorins/pers-sdk';
|
|
13259
|
+
*
|
|
13260
|
+
* // Get only completed redemptions
|
|
13261
|
+
* const { data: completed } = await sdk.redemptions.getUserRedemptions(
|
|
13262
|
+
* { status: ProcessRecordStatus.COMPLETED }
|
|
13263
|
+
* );
|
|
13264
|
+
* ```
|
|
13130
13265
|
*/
|
|
13131
|
-
async getUserRedemptions(options, include) {
|
|
13132
|
-
return this.redemptionService.getUserRedeems(options, include);
|
|
13266
|
+
async getUserRedemptions(filters, options, include) {
|
|
13267
|
+
return this.redemptionService.getUserRedeems(filters, options, include);
|
|
13133
13268
|
}
|
|
13134
13269
|
/**
|
|
13135
13270
|
* Admin: Get all redemption redeems with filtering and enriched data
|
|
@@ -13195,7 +13330,7 @@ class RedemptionManager {
|
|
|
13195
13330
|
* ```typescript
|
|
13196
13331
|
* // Admin operation - create discount redemption
|
|
13197
13332
|
* const newRedemption = await sdk.redemptions.createRedemption({
|
|
13198
|
-
*
|
|
13333
|
+
* name: '20% Off Weekend Stay',
|
|
13199
13334
|
* description: 'Get 20% off your next weekend booking at partner hotels',
|
|
13200
13335
|
* redemptionTypeId: 'discount-type-id',
|
|
13201
13336
|
* isActive: true,
|
|
@@ -13204,7 +13339,7 @@ class RedemptionManager {
|
|
|
13204
13339
|
* validUntil: '2024-08-31T23:59:59Z'
|
|
13205
13340
|
* });
|
|
13206
13341
|
*
|
|
13207
|
-
* console.log('New redemption created:', newRedemption.
|
|
13342
|
+
* console.log('New redemption created:', newRedemption.name);
|
|
13208
13343
|
* console.log('Redemption ID:', newRedemption.id);
|
|
13209
13344
|
*
|
|
13210
13345
|
* // Set redemption cost
|
|
@@ -13236,13 +13371,13 @@ class RedemptionManager {
|
|
|
13236
13371
|
* ```typescript
|
|
13237
13372
|
* // Admin operation - update redemption details
|
|
13238
13373
|
* const updated = await sdk.redemptions.updateRedemption('weekend-discount', {
|
|
13239
|
-
*
|
|
13374
|
+
* name: '25% Off Weekend Stay - Extended!',
|
|
13240
13375
|
* description: 'Enhanced discount - now 25% off weekend bookings',
|
|
13241
13376
|
* availableQuantity: 150, // Increased availability
|
|
13242
13377
|
* validUntil: '2024-09-30T23:59:59Z' // Extended validity
|
|
13243
13378
|
* });
|
|
13244
13379
|
*
|
|
13245
|
-
* console.log('Redemption updated:', updated.
|
|
13380
|
+
* console.log('Redemption updated:', updated.name);
|
|
13246
13381
|
* console.log('New quantity available:', updated.availableQuantity);
|
|
13247
13382
|
* ```
|
|
13248
13383
|
*/
|
|
@@ -17541,7 +17676,9 @@ class PersSDK {
|
|
|
17541
17676
|
}
|
|
17542
17677
|
}
|
|
17543
17678
|
try {
|
|
17544
|
-
//
|
|
17679
|
+
// Ensure token is valid (refresh if expired) before fetching user
|
|
17680
|
+
await this.auth.ensureValidToken();
|
|
17681
|
+
// Fetch user data to restore session
|
|
17545
17682
|
const userData = await this.auth.getCurrentUser();
|
|
17546
17683
|
// Emit event through proper event emitter
|
|
17547
17684
|
this._events.emitSuccess({
|
|
@@ -18401,21 +18538,45 @@ function createReactNativeAuthProvider(projectKey, config = {}) {
|
|
|
18401
18538
|
});
|
|
18402
18539
|
}
|
|
18403
18540
|
|
|
18404
|
-
//
|
|
18405
|
-
let
|
|
18406
|
-
|
|
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
|
|
18407
18571
|
try {
|
|
18408
|
-
|
|
18572
|
+
_crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
|
|
18409
18573
|
}
|
|
18410
18574
|
catch (e) {
|
|
18411
|
-
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;
|
|
18412
18578
|
}
|
|
18413
|
-
|
|
18414
|
-
else {
|
|
18415
|
-
// on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
|
|
18416
|
-
crypto$1 = {
|
|
18417
|
-
generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
|
|
18418
|
-
};
|
|
18579
|
+
return _crypto;
|
|
18419
18580
|
}
|
|
18420
18581
|
class ReactNativeDPoPProvider {
|
|
18421
18582
|
/**
|
|
@@ -18425,13 +18586,17 @@ class ReactNativeDPoPProvider {
|
|
|
18425
18586
|
* Falls back to Node.js crypto API on iOS if needed.
|
|
18426
18587
|
*/
|
|
18427
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
|
+
}
|
|
18428
18593
|
// Try WebCrypto API first (works on both iOS and Android)
|
|
18429
|
-
if (crypto
|
|
18594
|
+
if (crypto.subtle) {
|
|
18430
18595
|
try {
|
|
18431
|
-
const keyPair = await crypto
|
|
18596
|
+
const keyPair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, // extractable - required for JWK export
|
|
18432
18597
|
['sign', 'verify']);
|
|
18433
|
-
const publicKeyJwk = await crypto
|
|
18434
|
-
const privateKeyJwk = await crypto
|
|
18598
|
+
const publicKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
|
18599
|
+
const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
|
|
18435
18600
|
return {
|
|
18436
18601
|
publicKey: publicKeyJwk,
|
|
18437
18602
|
privateKey: privateKeyJwk
|
|
@@ -18443,7 +18608,7 @@ class ReactNativeDPoPProvider {
|
|
|
18443
18608
|
}
|
|
18444
18609
|
// Fallback: Node.js crypto API (works on iOS)
|
|
18445
18610
|
return new Promise((resolve, reject) => {
|
|
18446
|
-
crypto
|
|
18611
|
+
crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
|
|
18447
18612
|
if (err)
|
|
18448
18613
|
return reject(err);
|
|
18449
18614
|
try {
|
|
@@ -18458,6 +18623,10 @@ class ReactNativeDPoPProvider {
|
|
|
18458
18623
|
});
|
|
18459
18624
|
}
|
|
18460
18625
|
async signProof(payload, keyPair) {
|
|
18626
|
+
const crypto = getCrypto();
|
|
18627
|
+
if (!crypto) {
|
|
18628
|
+
throw new Error('Crypto module not available for signing');
|
|
18629
|
+
}
|
|
18461
18630
|
// 1. Construct Header
|
|
18462
18631
|
const header = {
|
|
18463
18632
|
typ: 'dpop+jwt',
|
|
@@ -18468,17 +18637,17 @@ class ReactNativeDPoPProvider {
|
|
|
18468
18637
|
const finalPayload = {
|
|
18469
18638
|
...payload,
|
|
18470
18639
|
iat: payload.iat || Math.floor(Date.now() / 1000),
|
|
18471
|
-
jti: payload.jti || crypto
|
|
18640
|
+
jti: payload.jti || (crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`)
|
|
18472
18641
|
};
|
|
18473
18642
|
// 3. Encode
|
|
18474
18643
|
const b64Header = this.base64Url(JSON.stringify(header));
|
|
18475
18644
|
const b64Payload = this.base64Url(JSON.stringify(finalPayload));
|
|
18476
18645
|
const signingInput = `${b64Header}.${b64Payload}`;
|
|
18477
18646
|
// 4. Sign - try WebCrypto first, fallback to Node.js crypto
|
|
18478
|
-
if (crypto
|
|
18647
|
+
if (crypto.subtle) {
|
|
18479
18648
|
try {
|
|
18480
|
-
const privateKey = await crypto
|
|
18481
|
-
const signatureBuffer = await crypto
|
|
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));
|
|
18482
18651
|
// WebCrypto returns signature in IEEE P1363 format (r || s), which is what we need for JWT
|
|
18483
18652
|
return `${signingInput}.${this.base64UrlBuffer(buffer.Buffer.from(signatureBuffer))}`;
|
|
18484
18653
|
}
|
|
@@ -18487,24 +18656,28 @@ class ReactNativeDPoPProvider {
|
|
|
18487
18656
|
}
|
|
18488
18657
|
}
|
|
18489
18658
|
// Fallback: Node.js crypto API
|
|
18490
|
-
const sign = crypto
|
|
18659
|
+
const sign = crypto.createSign('SHA256');
|
|
18491
18660
|
sign.update(signingInput);
|
|
18492
|
-
const privateKeyObj = crypto
|
|
18661
|
+
const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
|
|
18493
18662
|
const signature = sign.sign(privateKeyObj);
|
|
18494
18663
|
return `${signingInput}.${this.base64UrlBuffer(signature)}`;
|
|
18495
18664
|
}
|
|
18496
18665
|
async hashAccessToken(accessToken) {
|
|
18666
|
+
const crypto = getCrypto();
|
|
18667
|
+
if (!crypto) {
|
|
18668
|
+
throw new Error('Crypto module not available for hashing');
|
|
18669
|
+
}
|
|
18497
18670
|
// Try WebCrypto first
|
|
18498
|
-
if (crypto
|
|
18671
|
+
if (crypto.subtle) {
|
|
18499
18672
|
try {
|
|
18500
|
-
const hashBuffer = await crypto
|
|
18673
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer.Buffer.from(accessToken));
|
|
18501
18674
|
return this.base64UrlBuffer(buffer.Buffer.from(hashBuffer));
|
|
18502
18675
|
}
|
|
18503
18676
|
catch (err) {
|
|
18504
18677
|
// Fallback to Node.js crypto
|
|
18505
18678
|
}
|
|
18506
18679
|
}
|
|
18507
|
-
const hash = crypto
|
|
18680
|
+
const hash = crypto.createHash('sha256').update(accessToken).digest();
|
|
18508
18681
|
return this.base64UrlBuffer(hash);
|
|
18509
18682
|
}
|
|
18510
18683
|
// --- Helpers ---
|
|
@@ -40125,10 +40298,17 @@ const useWeb3 = () => {
|
|
|
40125
40298
|
* **Basic Usage:**
|
|
40126
40299
|
* ```typescript
|
|
40127
40300
|
* import { useTokenBalances, useTokens, usePersSDK } from '@explorins/pers-sdk-react-native';
|
|
40301
|
+
* import { useState, useEffect } from 'react';
|
|
40128
40302
|
*
|
|
40129
40303
|
* function TokenBalancesScreen() {
|
|
40130
40304
|
* const { user } = usePersSDK();
|
|
40131
|
-
* const {
|
|
40305
|
+
* const { getTokens } = useTokens();
|
|
40306
|
+
* const [availableTokens, setAvailableTokens] = useState([]);
|
|
40307
|
+
*
|
|
40308
|
+
* // Load tokens on mount
|
|
40309
|
+
* useEffect(() => {
|
|
40310
|
+
* getTokens().then(result => setAvailableTokens(result.data));
|
|
40311
|
+
* }, []);
|
|
40132
40312
|
*
|
|
40133
40313
|
* // App explicitly selects which wallet to use
|
|
40134
40314
|
* const walletAddress = user?.wallets?.[0]?.address;
|
|
@@ -40383,15 +40563,41 @@ const SigningStatus = {
|
|
|
40383
40563
|
EXPIRED: 'expired'
|
|
40384
40564
|
};
|
|
40385
40565
|
|
|
40386
|
-
//
|
|
40387
|
-
let
|
|
40388
|
-
|
|
40389
|
-
|
|
40390
|
-
|
|
40391
|
-
|
|
40392
|
-
|
|
40393
|
-
|
|
40394
|
-
|
|
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
|
+
}
|
|
40395
40601
|
}
|
|
40396
40602
|
// Constants - TODO: Move to environment config later
|
|
40397
40603
|
const DEFAULT_ETHERS_PROVIDER = "https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e";
|
|
@@ -40581,6 +40787,7 @@ const useTransactionSigner = () => {
|
|
|
40581
40787
|
// Auto-initialize signer SDK when PERS SDK is ready
|
|
40582
40788
|
react.useEffect(() => {
|
|
40583
40789
|
const initializeSignerSDK = async () => {
|
|
40790
|
+
const createPersSignerSDK = getSignerSDKFactory();
|
|
40584
40791
|
if (!createPersSignerSDK) {
|
|
40585
40792
|
console.warn('[useTransactionSigner] Signer SDK not available');
|
|
40586
40793
|
return;
|
|
@@ -40695,7 +40902,7 @@ const useTransactionSigner = () => {
|
|
|
40695
40902
|
if (!isSignerInitialized || !signerSDKRef.current) {
|
|
40696
40903
|
throw new Error('Transaction signer not initialized');
|
|
40697
40904
|
}
|
|
40698
|
-
if (!
|
|
40905
|
+
if (!getSignerSDKFactory()) {
|
|
40699
40906
|
throw new Error('PERS Signer SDK not available. Blockchain signing is not supported.');
|
|
40700
40907
|
}
|
|
40701
40908
|
// Create status callback wrapper that updates both hook state and calls user callback
|
|
@@ -40839,7 +41046,7 @@ const useTransactionSigner = () => {
|
|
|
40839
41046
|
* );
|
|
40840
41047
|
* ```
|
|
40841
41048
|
*/
|
|
40842
|
-
isSignerAvailable: isSignerInitialized && !!
|
|
41049
|
+
isSignerAvailable: isSignerInitialized && !!getSignerSDKFactory(),
|
|
40843
41050
|
/**
|
|
40844
41051
|
* Current signing status for UI feedback
|
|
40845
41052
|
*
|
|
@@ -41408,6 +41615,63 @@ const useCampaigns = () => {
|
|
|
41408
41615
|
throw error;
|
|
41409
41616
|
}
|
|
41410
41617
|
}, [sdk, isInitialized]);
|
|
41618
|
+
/**
|
|
41619
|
+
* Claim a campaign reward with optional dynamic context
|
|
41620
|
+
*
|
|
41621
|
+
* Triggers a campaign claim for the authenticated user. The campaign must be active
|
|
41622
|
+
* and the user must meet all eligibility requirements. Supports dynamic context for
|
|
41623
|
+
* personalized NFT generation when the campaign issues ERC721 tokens.
|
|
41624
|
+
*
|
|
41625
|
+
* @param request - Campaign claim request with optional context
|
|
41626
|
+
* @param request.campaignId - ID of the campaign to claim (optional if using triggerSourceId)
|
|
41627
|
+
* @param request.triggerSourceId - ID of trigger source (QR, NFC, etc.)
|
|
41628
|
+
* @param request.context - Dynamic context for NFT personalization
|
|
41629
|
+
* @param request.metadata - Additional metadata for the claim
|
|
41630
|
+
* @param request.latitude - User's latitude for geo-validated claims
|
|
41631
|
+
* @param request.longitude - User's longitude for geo-validated claims
|
|
41632
|
+
* @returns Promise resolving to campaign claim result
|
|
41633
|
+
*
|
|
41634
|
+
* @example Basic campaign claim
|
|
41635
|
+
* ```typescript
|
|
41636
|
+
* const claim = await claimCampaign({
|
|
41637
|
+
* triggerSourceId: 'qr-code-123',
|
|
41638
|
+
* });
|
|
41639
|
+
* ```
|
|
41640
|
+
*
|
|
41641
|
+
* @example Campaign claim with dynamic context (personalized NFT)
|
|
41642
|
+
* ```typescript
|
|
41643
|
+
* import { DynamicContext } from '@explorins/pers-sdk-react-native';
|
|
41644
|
+
*
|
|
41645
|
+
* const context: DynamicContext = {
|
|
41646
|
+
* // Validity - token valid for 30 days
|
|
41647
|
+
* validityDate: new Date().toISOString(),
|
|
41648
|
+
* validityDuration: 30,
|
|
41649
|
+
*
|
|
41650
|
+
* // AI prompt context - available as {{context.xxx}} in prompts
|
|
41651
|
+
* visitorName: 'John Doe',
|
|
41652
|
+
* visitLocation: 'Grand Hotel Lobby',
|
|
41653
|
+
* membershipTier: 'Gold',
|
|
41654
|
+
* };
|
|
41655
|
+
*
|
|
41656
|
+
* const claim = await claimCampaign({
|
|
41657
|
+
* triggerSourceId: 'hotel-checkin-qr',
|
|
41658
|
+
* context,
|
|
41659
|
+
* });
|
|
41660
|
+
* ```
|
|
41661
|
+
*
|
|
41662
|
+
* @example Geo-validated claim with context
|
|
41663
|
+
* ```typescript
|
|
41664
|
+
* const claim = await claimCampaign({
|
|
41665
|
+
* triggerSourceId: 'museum-entrance-nfc',
|
|
41666
|
+
* latitude: 48.8606,
|
|
41667
|
+
* longitude: 2.3376,
|
|
41668
|
+
* context: {
|
|
41669
|
+
* visitorType: 'Adult',
|
|
41670
|
+
* ticketCategory: 'General Admission',
|
|
41671
|
+
* }
|
|
41672
|
+
* });
|
|
41673
|
+
* ```
|
|
41674
|
+
*/
|
|
41411
41675
|
const claimCampaign = react.useCallback(async (request) => {
|
|
41412
41676
|
if (!isInitialized || !sdk) {
|
|
41413
41677
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
@@ -41435,7 +41699,7 @@ const useCampaigns = () => {
|
|
|
41435
41699
|
* // Get user's claims with campaign details
|
|
41436
41700
|
* const { data: claims } = await getUserClaims({ include: ['campaign', 'business'] });
|
|
41437
41701
|
* claims.forEach(claim => {
|
|
41438
|
-
* console.log('Campaign:', claim.included?.campaign?.
|
|
41702
|
+
* console.log('Campaign:', claim.included?.campaign?.name);
|
|
41439
41703
|
* });
|
|
41440
41704
|
*
|
|
41441
41705
|
* // With pagination
|
|
@@ -41677,6 +41941,35 @@ const useRedemptions = () => {
|
|
|
41677
41941
|
* **Administrators:** Can see all redemptions and filter by active/inactive status
|
|
41678
41942
|
*
|
|
41679
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
|
+
* ```
|
|
41680
41973
|
*/
|
|
41681
41974
|
const getRedemptions = react.useCallback(async (options) => {
|
|
41682
41975
|
if (!isInitialized || !sdk) {
|
|
@@ -41692,27 +41985,32 @@ const useRedemptions = () => {
|
|
|
41692
41985
|
}
|
|
41693
41986
|
}, [sdk, isInitialized]);
|
|
41694
41987
|
/**
|
|
41695
|
-
* Get user's redemption history with optional pagination and include relations
|
|
41988
|
+
* Get user's redemption history with optional filters, pagination and include relations
|
|
41696
41989
|
*
|
|
41990
|
+
* @param filters - Optional filters (redemptionId, status)
|
|
41697
41991
|
* @param options - Pagination options (page, limit, sortBy, sortOrder)
|
|
41698
|
-
* @param include - Relations to include: 'redemption', 'user', 'business'
|
|
41992
|
+
* @param include - Relations to include: 'redemption', 'user', 'business', 'transactions'
|
|
41699
41993
|
* @returns Promise resolving to paginated redemption history
|
|
41700
41994
|
*
|
|
41701
41995
|
* @example
|
|
41702
41996
|
* ```typescript
|
|
41703
41997
|
* // Get first page of user redemptions
|
|
41704
|
-
* const { data: redeems, pagination } = await getUserRedemptions({ page: 1, limit: 10 });
|
|
41998
|
+
* const { data: redeems, pagination } = await getUserRedemptions({}, { page: 1, limit: 10 });
|
|
41705
41999
|
* console.log(`Page ${pagination.page} of ${pagination.pages}`);
|
|
41706
42000
|
*
|
|
42001
|
+
* // Get only completed redemptions
|
|
42002
|
+
* import { ProcessRecordStatus } from '@explorins/pers-shared';
|
|
42003
|
+
* const { data: completed } = await getUserRedemptions({ status: ProcessRecordStatus.COMPLETED });
|
|
42004
|
+
*
|
|
41707
42005
|
* // Get user redemptions with full redemption details
|
|
41708
|
-
* const { data: redeems } = await getUserRedemptions({}, ['redemption', 'business']);
|
|
42006
|
+
* const { data: redeems } = await getUserRedemptions({}, {}, ['redemption', 'business']);
|
|
41709
42007
|
* redeems.forEach(redeem => {
|
|
41710
|
-
* console.log('Redemption:', redeem.included?.redemption?.
|
|
42008
|
+
* console.log('Redemption:', redeem.included?.redemption?.name);
|
|
41711
42009
|
* console.log('Business:', redeem.included?.business?.displayName);
|
|
41712
42010
|
* });
|
|
41713
42011
|
* ```
|
|
41714
42012
|
*/
|
|
41715
|
-
const getUserRedemptions = react.useCallback(async (options, include) => {
|
|
42013
|
+
const getUserRedemptions = react.useCallback(async (filters, options, include) => {
|
|
41716
42014
|
if (!isInitialized || !sdk) {
|
|
41717
42015
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
41718
42016
|
}
|
|
@@ -41721,7 +42019,7 @@ const useRedemptions = () => {
|
|
|
41721
42019
|
return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
|
|
41722
42020
|
}
|
|
41723
42021
|
try {
|
|
41724
|
-
const result = await sdk.redemptions.getUserRedemptions(options, include);
|
|
42022
|
+
const result = await sdk.redemptions.getUserRedemptions(filters, options, include);
|
|
41725
42023
|
return result;
|
|
41726
42024
|
}
|
|
41727
42025
|
catch (error) {
|
|
@@ -41729,7 +42027,54 @@ const useRedemptions = () => {
|
|
|
41729
42027
|
throw error;
|
|
41730
42028
|
}
|
|
41731
42029
|
}, [sdk, isInitialized, isAuthenticated]);
|
|
41732
|
-
|
|
42030
|
+
/**
|
|
42031
|
+
* Redeem a redemption offer with optional dynamic context
|
|
42032
|
+
*
|
|
42033
|
+
* Supports dynamic context for personalized NFT generation when the redemption
|
|
42034
|
+
* issues ERC721 tokens. Context can include validity dates, AI prompt placeholders,
|
|
42035
|
+
* and custom metadata.
|
|
42036
|
+
*
|
|
42037
|
+
* @param redemptionId - ID of the redemption to redeem
|
|
42038
|
+
* @param options - Optional settings
|
|
42039
|
+
* @param options.context - Dynamic context for NFT personalization (validity dates, AI prompts)
|
|
42040
|
+
* @param options.onStatusUpdate - Callback for transaction signing status updates
|
|
42041
|
+
* @returns Promise resolving to redemption result
|
|
42042
|
+
*
|
|
42043
|
+
* @example Basic redemption
|
|
42044
|
+
* ```typescript
|
|
42045
|
+
* const result = await redeem('redemption-123');
|
|
42046
|
+
* ```
|
|
42047
|
+
*
|
|
42048
|
+
* @example Redemption with dynamic context (personalized NFT)
|
|
42049
|
+
* ```typescript
|
|
42050
|
+
* const result = await redeem('event-ticket-123', {
|
|
42051
|
+
* context: {
|
|
42052
|
+
* // Validity - token valid for event duration
|
|
42053
|
+
* validityDate: new Date().toISOString(),
|
|
42054
|
+
* validityDuration: 8, // 8 hours
|
|
42055
|
+
*
|
|
42056
|
+
* // AI prompt context - available as {{context.xxx}} in prompts
|
|
42057
|
+
* attendeeName: 'John Doe',
|
|
42058
|
+
* ticketTier: 'VIP',
|
|
42059
|
+
* seatSection: 'A12',
|
|
42060
|
+
* }
|
|
42061
|
+
* });
|
|
42062
|
+
* ```
|
|
42063
|
+
*
|
|
42064
|
+
* @example Hotel stay NFT with check-in/check-out dates
|
|
42065
|
+
* ```typescript
|
|
42066
|
+
* const result = await redeem('hotel-nft-456', {
|
|
42067
|
+
* context: {
|
|
42068
|
+
* validityDate: '2026-04-15T14:00:00Z', // Check-in
|
|
42069
|
+
* validityEndDate: '2026-04-20T11:00:00Z', // Check-out
|
|
42070
|
+
* guestName: 'Jane Smith',
|
|
42071
|
+
* roomNumber: '305',
|
|
42072
|
+
* roomType: 'Suite',
|
|
42073
|
+
* }
|
|
42074
|
+
* });
|
|
42075
|
+
* ```
|
|
42076
|
+
*/
|
|
42077
|
+
const redeem = react.useCallback(async (redemptionId, options) => {
|
|
41733
42078
|
if (!isInitialized || !sdk) {
|
|
41734
42079
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
41735
42080
|
}
|
|
@@ -41737,11 +42082,11 @@ const useRedemptions = () => {
|
|
|
41737
42082
|
throw new Error('SDK not authenticated. redeem requires authentication.');
|
|
41738
42083
|
}
|
|
41739
42084
|
try {
|
|
41740
|
-
const result = await sdk.redemptions.redeem(redemptionId);
|
|
42085
|
+
const result = await sdk.redemptions.redeem(redemptionId, options?.context);
|
|
41741
42086
|
const txToken = result.senderTransaction?.actionable?.authToken;
|
|
41742
42087
|
if (txToken && isSignerAvailable) {
|
|
41743
42088
|
try {
|
|
41744
|
-
const signingResult = await signAndSubmitTransactionWithJWT(txToken, onStatusUpdate);
|
|
42089
|
+
const signingResult = await signAndSubmitTransactionWithJWT(txToken, options?.onStatusUpdate);
|
|
41745
42090
|
if (signingResult.success) {
|
|
41746
42091
|
// Return enhanced result with signing information
|
|
41747
42092
|
return {
|