@explorins/pers-sdk-react-native 2.1.20 → 2.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useIPFSUrl.d.ts +118 -0
- package/dist/hooks/useIPFSUrl.d.ts.map +1 -0
- package/dist/hooks/useIPFSUrl.js +178 -0
- package/dist/hooks/useTransactions.d.ts.map +1 -1
- package/dist/hooks/useTransactions.js +2 -4
- package/dist/hooks/useWeb3.d.ts.map +1 -1
- package/dist/hooks/useWeb3.js +18 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +669 -109
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +0 -7
- package/dist/providers/react-native-auth-provider.d.ts.map +1 -1
- package/dist/providers/react-native-auth-provider.js +0 -9
- package/package.json +1 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useIPFSUrl.ts +192 -0
- package/src/hooks/useTransactions.ts +2 -5
- package/src/hooks/useWeb3.ts +18 -5
- package/src/index.ts +6 -1
- package/src/providers/PersSDKProvider.tsx +0 -8
- package/src/providers/react-native-auth-provider.ts +0 -10
package/dist/index.js
CHANGED
|
@@ -3717,6 +3717,109 @@ const TRANSACTION_FORMAT_DESCRIPTIONS = {
|
|
|
3717
3717
|
[TRANSACTION_FORMATS.EIP_712]: 'EIP-712 structured data for smart wallet meta-transactions'
|
|
3718
3718
|
};
|
|
3719
3719
|
|
|
3720
|
+
/**
|
|
3721
|
+
* Error Codes extracted from actual domain error classes
|
|
3722
|
+
*
|
|
3723
|
+
* This object maps all error codes from domain error classes to provide
|
|
3724
|
+
* type-safe constants for API documentation and usage.
|
|
3725
|
+
*
|
|
3726
|
+
* Using object pattern for better performance and developer experience:
|
|
3727
|
+
* - Single export vs 50+ individual exports
|
|
3728
|
+
* - Namespaced access: CommonErrorCodes.USER_NOT_FOUND
|
|
3729
|
+
* - Better tree-shaking and bundle optimization
|
|
3730
|
+
* - Cleaner type definition
|
|
3731
|
+
*/
|
|
3732
|
+
const CommonErrorCodes = {
|
|
3733
|
+
// User domain
|
|
3734
|
+
USER_NOT_FOUND: 'USER_NOT_FOUND',
|
|
3735
|
+
USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',
|
|
3736
|
+
USER_NOT_AUTHORIZED: 'USER_NOT_AUTHORIZED',
|
|
3737
|
+
// Token domain
|
|
3738
|
+
TOKEN_NOT_FOUND: 'TOKEN_NOT_FOUND',
|
|
3739
|
+
TOKEN_NOT_ACTIVE: 'TOKEN_NOT_ACTIVE',
|
|
3740
|
+
TOKEN_ID_REQUIRED: 'TOKEN_ID_REQUIRED',
|
|
3741
|
+
TOKEN_TYPE_NOT_FOUND: 'TOKEN_TYPE_NOT_FOUND',
|
|
3742
|
+
// Wallet domain
|
|
3743
|
+
WALLET_MISSING_SIGNING: 'WALLET_MISSING_SIGNING',
|
|
3744
|
+
WALLET_NOT_FOUND: 'WALLET_NOT_FOUND',
|
|
3745
|
+
SMART_WALLET_PROCESSING_FAILED: 'SMART_WALLET_PROCESSING_FAILED',
|
|
3746
|
+
// Balance domain
|
|
3747
|
+
INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
|
|
3748
|
+
// Contract domain
|
|
3749
|
+
CONTRACT_NOT_FOUND: 'CONTRACT_NOT_FOUND',
|
|
3750
|
+
// Business domain
|
|
3751
|
+
BUSINESS_NOT_FOUND: 'BUSINESS_NOT_FOUND',
|
|
3752
|
+
BUSINESS_NOT_MINTER: 'BUSINESS_NOT_MINTER',
|
|
3753
|
+
// Membership domain
|
|
3754
|
+
MEMBERSHIP_NOT_FOUND: 'MEMBERSHIP_NOT_FOUND',
|
|
3755
|
+
MEMBERSHIP_ALREADY_EXISTS: 'MEMBERSHIP_ALREADY_EXISTS',
|
|
3756
|
+
MEMBERSHIP_LAST_OWNER: 'MEMBERSHIP_LAST_OWNER',
|
|
3757
|
+
// Transaction domain
|
|
3758
|
+
TRANSACTION_NOT_FOUND: 'TRANSACTION_NOT_FOUND',
|
|
3759
|
+
TRANSACTION_SIGNATURE_INVALID: 'TRANSACTION_SIGNATURE_INVALID',
|
|
3760
|
+
TRANSACTION_ALREADY_COMPLETED: 'TRANSACTION_ALREADY_COMPLETED',
|
|
3761
|
+
TRANSACTION_NOT_PENDING: 'TRANSACTION_NOT_PENDING',
|
|
3762
|
+
TRANSACTION_EXPIRED: 'TRANSACTION_EXPIRED',
|
|
3763
|
+
TRANSACTION_VALIDATION_ERROR: 'TRANSACTION_VALIDATION_ERROR',
|
|
3764
|
+
UNEXPECTED_VALIDATION_SCENARIO: 'UNEXPECTED_VALIDATION_SCENARIO',
|
|
3765
|
+
// Campaign domain
|
|
3766
|
+
CAMPAIGN_USER_INFO_REQUIRED: 'CAMPAIGN_USER_INFO_REQUIRED',
|
|
3767
|
+
CAMPAIGN_BUSINESS_REQUIRED: 'CAMPAIGN_BUSINESS_REQUIRED',
|
|
3768
|
+
CAMPAIGN_CONDITION_NOT_MET: 'CAMPAIGN_CONDITION_NOT_MET',
|
|
3769
|
+
CAMPAIGN_ALREADY_CLAIMED: 'CAMPAIGN_ALREADY_CLAIMED',
|
|
3770
|
+
CAMPAIGN_NO_TRIGGER: 'CAMPAIGN_NO_TRIGGER',
|
|
3771
|
+
CAMPAIGN_LOCATION_REQUIRED: 'CAMPAIGN_LOCATION_REQUIRED',
|
|
3772
|
+
CAMPAIGN_DISTANCE_EXCEEDED: 'CAMPAIGN_DISTANCE_EXCEEDED',
|
|
3773
|
+
CAMPAIGN_NOT_ACTIVE: 'CAMPAIGN_NOT_ACTIVE',
|
|
3774
|
+
CAMPAIGN_ENDED: 'CAMPAIGN_ENDED',
|
|
3775
|
+
CAMPAIGN_NOT_STARTED: 'CAMPAIGN_NOT_STARTED',
|
|
3776
|
+
CAMPAIGN_CLAIM_LIMIT_REACHED: 'CAMPAIGN_CLAIM_LIMIT_REACHED',
|
|
3777
|
+
CAMPAIGN_COOLDOWN_ACTIVE: 'CAMPAIGN_COOLDOWN_ACTIVE',
|
|
3778
|
+
CAMPAIGN_DAILY_LIMIT_REACHED: 'CAMPAIGN_DAILY_LIMIT_REACHED',
|
|
3779
|
+
CAMPAIGN_GLOBAL_LIMIT_REACHED: 'CAMPAIGN_GLOBAL_LIMIT_REACHED',
|
|
3780
|
+
CAMPAIGN_GLOBAL_DAILY_LIMIT_REACHED: 'CAMPAIGN_GLOBAL_DAILY_LIMIT_REACHED',
|
|
3781
|
+
// Trigger domain
|
|
3782
|
+
TRIGGER_NOT_FOUND: 'TRIGGER_NOT_FOUND',
|
|
3783
|
+
// Trigger Source domain
|
|
3784
|
+
TRIGGER_SOURCE_NOT_FOUND: 'TRIGGER_SOURCE_NOT_FOUND',
|
|
3785
|
+
// Generic domain errors
|
|
3786
|
+
RESOURCE_ALREADY_EXISTS: 'RESOURCE_ALREADY_EXISTS',
|
|
3787
|
+
RESOURCE_IN_USE: 'RESOURCE_IN_USE',
|
|
3788
|
+
// Technical errors
|
|
3789
|
+
TECHNICAL_ERROR: 'TECHNICAL_ERROR',
|
|
3790
|
+
ORCHESTRATION_ERROR: 'ORCHESTRATION_ERROR',
|
|
3791
|
+
// System errors
|
|
3792
|
+
SYSTEM_ERROR: 'SYSTEM_ERROR',
|
|
3793
|
+
EXTERNAL_SERVICE_ERROR: 'EXTERNAL_SERVICE_ERROR',
|
|
3794
|
+
AUTHENTICATION_REQUIRED: 'AUTHENTICATION_REQUIRED',
|
|
3795
|
+
AUTHORIZATION_FAILED: 'AUTHORIZATION_FAILED',
|
|
3796
|
+
// Authentication errors
|
|
3797
|
+
INVALID_TOKEN: 'INVALID_TOKEN',
|
|
3798
|
+
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
|
|
3799
|
+
TOKEN_REVOKED: 'TOKEN_REVOKED',
|
|
3800
|
+
REFRESH_TOKEN_EXPIRED: 'REFRESH_TOKEN_EXPIRED',
|
|
3801
|
+
REFRESH_TOKEN_REVOKED: 'REFRESH_TOKEN_REVOKED',
|
|
3802
|
+
FIREBASE_AUTH_ERROR: 'FIREBASE_AUTH_ERROR',
|
|
3803
|
+
// Generic fallbacks
|
|
3804
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
3805
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
3806
|
+
BUSINESS_RULE_VIOLATION: 'BUSINESS_RULE_VIOLATION',
|
|
3807
|
+
RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND',
|
|
3808
|
+
// Webhook domain
|
|
3809
|
+
WEBHOOK_NOT_FOUND: 'WEBHOOK_NOT_FOUND',
|
|
3810
|
+
WEBHOOK_INACTIVE: 'WEBHOOK_INACTIVE',
|
|
3811
|
+
WEBHOOK_SOURCE_NOT_ALLOWED: 'WEBHOOK_SOURCE_NOT_ALLOWED',
|
|
3812
|
+
WEBHOOK_SIGNATURE_INVALID: 'WEBHOOK_SIGNATURE_INVALID',
|
|
3813
|
+
WEBHOOK_FORWARD_FAILED: 'WEBHOOK_FORWARD_FAILED',
|
|
3814
|
+
WEBHOOK_TIMEOUT: 'WEBHOOK_TIMEOUT',
|
|
3815
|
+
// Conflict errors (409)
|
|
3816
|
+
RESOURCE_CONFLICT: 'RESOURCE_CONFLICT',
|
|
3817
|
+
CONCURRENT_MODIFICATION: 'CONCURRENT_MODIFICATION',
|
|
3818
|
+
// Timeout errors (504)
|
|
3819
|
+
REQUEST_TIMEOUT: 'REQUEST_TIMEOUT',
|
|
3820
|
+
GATEWAY_TIMEOUT: 'GATEWAY_TIMEOUT'
|
|
3821
|
+
};
|
|
3822
|
+
|
|
3720
3823
|
/**
|
|
3721
3824
|
* JWT token expiration utilities
|
|
3722
3825
|
* Native implementation without external dependencies for optimal performance
|
|
@@ -6817,6 +6920,22 @@ class RedemptionApi {
|
|
|
6817
6920
|
async createRedemptionType(redemptionType) {
|
|
6818
6921
|
return this.apiClient.post(this.typesPath, redemptionType);
|
|
6819
6922
|
}
|
|
6923
|
+
/**
|
|
6924
|
+
* ADMIN: Update redemption type
|
|
6925
|
+
*
|
|
6926
|
+
* @param redemptionType - Updated redemption type data (must include id)
|
|
6927
|
+
*/
|
|
6928
|
+
async updateRedemptionType(redemptionType) {
|
|
6929
|
+
return this.apiClient.put(`${this.typesPath}/${redemptionType.id}`, redemptionType);
|
|
6930
|
+
}
|
|
6931
|
+
/**
|
|
6932
|
+
* ADMIN: Delete redemption type
|
|
6933
|
+
*
|
|
6934
|
+
* @param id - Redemption type ID to delete
|
|
6935
|
+
*/
|
|
6936
|
+
async deleteRedemptionType(id) {
|
|
6937
|
+
return this.apiClient.delete(`${this.typesPath}/${id}`);
|
|
6938
|
+
}
|
|
6820
6939
|
// ==========================================
|
|
6821
6940
|
// TOKEN UNIT MANAGEMENT (Admin)
|
|
6822
6941
|
// ==========================================
|
|
@@ -6974,6 +7093,27 @@ class RedemptionService {
|
|
|
6974
7093
|
async deleteRedemptionTokenUnit(redemptionId, redemptionTokenUnitId) {
|
|
6975
7094
|
return this.redemptionApi.deleteRedemptionTokenUnit(redemptionId, redemptionTokenUnitId);
|
|
6976
7095
|
}
|
|
7096
|
+
// ==========================================
|
|
7097
|
+
// REDEMPTION TYPE MANAGEMENT (Admin)
|
|
7098
|
+
// ==========================================
|
|
7099
|
+
/**
|
|
7100
|
+
* ADMIN: Create redemption type
|
|
7101
|
+
*/
|
|
7102
|
+
async createRedemptionType(data) {
|
|
7103
|
+
return this.redemptionApi.createRedemptionType(data);
|
|
7104
|
+
}
|
|
7105
|
+
/**
|
|
7106
|
+
* ADMIN: Update redemption type
|
|
7107
|
+
*/
|
|
7108
|
+
async updateRedemptionType(data) {
|
|
7109
|
+
return this.redemptionApi.updateRedemptionType(data);
|
|
7110
|
+
}
|
|
7111
|
+
/**
|
|
7112
|
+
* ADMIN: Delete redemption type
|
|
7113
|
+
*/
|
|
7114
|
+
async deleteRedemptionType(id) {
|
|
7115
|
+
return this.redemptionApi.deleteRedemptionType(id);
|
|
7116
|
+
}
|
|
6977
7117
|
}
|
|
6978
7118
|
|
|
6979
7119
|
/**
|
|
@@ -8236,8 +8376,6 @@ class TenantService {
|
|
|
8236
8376
|
*
|
|
8237
8377
|
* Provides a simplified API for common tenant management tasks while maintaining
|
|
8238
8378
|
* access to the full tenant SDK for advanced use cases.
|
|
8239
|
-
*
|
|
8240
|
-
* Also provides chain-agnostic IPFS URL resolution using tenant configuration.
|
|
8241
8379
|
*/
|
|
8242
8380
|
class TenantManager {
|
|
8243
8381
|
constructor(apiClient) {
|
|
@@ -8281,52 +8419,6 @@ class TenantManager {
|
|
|
8281
8419
|
async getClientConfig() {
|
|
8282
8420
|
return this.cache.getOrSet('clientConfig', () => this.tenantService.getRemoteClientConfig(), CacheTTL.LONG);
|
|
8283
8421
|
}
|
|
8284
|
-
// ==========================================
|
|
8285
|
-
// IPFS OPERATIONS (Chain-agnostic)
|
|
8286
|
-
// ==========================================
|
|
8287
|
-
/**
|
|
8288
|
-
* Resolve IPFS URL to HTTPS URL using tenant's configured gateway.
|
|
8289
|
-
*
|
|
8290
|
-
* This is chain-agnostic - IPFS gateway is configured at the tenant level,
|
|
8291
|
-
* not per-chain. Use this for resolving any ipfs:// URLs (images, metadata, etc.).
|
|
8292
|
-
*
|
|
8293
|
-
* @param url - URL to resolve (can be ipfs:// or https://)
|
|
8294
|
-
* @returns Resolved HTTPS URL
|
|
8295
|
-
* @throws Error if tenant's ipfsGatewayDomain is not configured
|
|
8296
|
-
*
|
|
8297
|
-
* @example
|
|
8298
|
-
* ```typescript
|
|
8299
|
-
* const imageUrl = await sdk.tenant.resolveIPFSUrl('ipfs://QmXxx.../image.png');
|
|
8300
|
-
* // Returns: 'https://pers.mypinata.cloud/ipfs/QmXxx.../image.png'
|
|
8301
|
-
*
|
|
8302
|
-
* // Non-IPFS URLs pass through unchanged
|
|
8303
|
-
* const httpUrl = await sdk.tenant.resolveIPFSUrl('https://example.com/image.png');
|
|
8304
|
-
* // Returns: 'https://example.com/image.png'
|
|
8305
|
-
* ```
|
|
8306
|
-
*/
|
|
8307
|
-
async resolveIPFSUrl(url) {
|
|
8308
|
-
if (!url || !url.startsWith('ipfs://')) {
|
|
8309
|
-
return url;
|
|
8310
|
-
}
|
|
8311
|
-
const gateway = await this.getIpfsGatewayDomain();
|
|
8312
|
-
return url.replace('ipfs://', `https://${gateway}/ipfs/`);
|
|
8313
|
-
}
|
|
8314
|
-
/**
|
|
8315
|
-
* Get IPFS gateway domain from tenant configuration (cached).
|
|
8316
|
-
*
|
|
8317
|
-
* @returns IPFS gateway domain (e.g., 'pers.mypinata.cloud')
|
|
8318
|
-
* @throws Error if ipfsGatewayDomain is not configured for this tenant
|
|
8319
|
-
*/
|
|
8320
|
-
async getIpfsGatewayDomain() {
|
|
8321
|
-
return this.cache.getOrSet('ipfsGateway', async () => {
|
|
8322
|
-
const config = await this.getClientConfig();
|
|
8323
|
-
if (!config.ipfsGatewayDomain) {
|
|
8324
|
-
throw new Error('IPFS gateway domain not configured for tenant. ' +
|
|
8325
|
-
'Please configure ipfsGatewayDomain in tenant settings.');
|
|
8326
|
-
}
|
|
8327
|
-
return config.ipfsGatewayDomain;
|
|
8328
|
-
}, CacheTTL.GATEWAY);
|
|
8329
|
-
}
|
|
8330
8422
|
/**
|
|
8331
8423
|
* Get Google API key from tenant configuration (cached).
|
|
8332
8424
|
*
|
|
@@ -8408,6 +8500,100 @@ class TenantManager {
|
|
|
8408
8500
|
clearCache() {
|
|
8409
8501
|
this.cache.clear();
|
|
8410
8502
|
}
|
|
8503
|
+
// ==========================================
|
|
8504
|
+
// IPFS OPERATIONS (Lightweight, no Web3 deps)
|
|
8505
|
+
// ==========================================
|
|
8506
|
+
/**
|
|
8507
|
+
* Get the IPFS gateway domain from tenant config (cached)
|
|
8508
|
+
*
|
|
8509
|
+
* @returns Promise resolving to IPFS gateway domain (e.g., 'gateway.pinata.cloud')
|
|
8510
|
+
* @example
|
|
8511
|
+
* ```typescript
|
|
8512
|
+
* const gateway = await sdk.tenants.getIpfsGatewayDomain();
|
|
8513
|
+
* // Returns 'gateway.pinata.cloud' or tenant-specific gateway
|
|
8514
|
+
* ```
|
|
8515
|
+
*/
|
|
8516
|
+
async getIpfsGatewayDomain() {
|
|
8517
|
+
return this.cache.getOrSet('ipfsGateway', async () => {
|
|
8518
|
+
const config = await this.getClientConfig();
|
|
8519
|
+
return config.ipfsGatewayDomain || 'gateway.pinata.cloud';
|
|
8520
|
+
}, CacheTTL.LONG);
|
|
8521
|
+
}
|
|
8522
|
+
/**
|
|
8523
|
+
* Resolve an IPFS URL to an HTTP gateway URL
|
|
8524
|
+
*
|
|
8525
|
+
* @param url - The URL to resolve (ipfs://, ar://, or HTTP URL)
|
|
8526
|
+
* @returns Promise resolving to HTTP gateway URL
|
|
8527
|
+
*
|
|
8528
|
+
* @example
|
|
8529
|
+
* ```typescript
|
|
8530
|
+
* const httpUrl = await sdk.tenants.resolveIpfsUrl('ipfs://QmXxx...');
|
|
8531
|
+
* // Returns 'https://gateway.pinata.cloud/ipfs/QmXxx...'
|
|
8532
|
+
* ```
|
|
8533
|
+
*/
|
|
8534
|
+
async resolveIpfsUrl(url) {
|
|
8535
|
+
if (!url)
|
|
8536
|
+
return '';
|
|
8537
|
+
// Already an HTTP URL
|
|
8538
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
8539
|
+
return url;
|
|
8540
|
+
}
|
|
8541
|
+
const gateway = await this.getIpfsGatewayDomain();
|
|
8542
|
+
// Handle ipfs:// protocol
|
|
8543
|
+
if (url.startsWith('ipfs://')) {
|
|
8544
|
+
const cid = url.replace('ipfs://', '');
|
|
8545
|
+
return `https://${gateway}/ipfs/${cid}`;
|
|
8546
|
+
}
|
|
8547
|
+
// Handle ar:// (Arweave) protocol
|
|
8548
|
+
if (url.startsWith('ar://')) {
|
|
8549
|
+
const txId = url.replace('ar://', '');
|
|
8550
|
+
return `https://arweave.net/${txId}`;
|
|
8551
|
+
}
|
|
8552
|
+
return url;
|
|
8553
|
+
}
|
|
8554
|
+
/**
|
|
8555
|
+
* Fetch and process token metadata from IPFS/HTTP URI
|
|
8556
|
+
*
|
|
8557
|
+
* @param tokenUri - The token URI (ipfs://, ar://, or HTTP)
|
|
8558
|
+
* @returns Promise resolving to processed metadata or null on error
|
|
8559
|
+
*
|
|
8560
|
+
* @example
|
|
8561
|
+
* ```typescript
|
|
8562
|
+
* const metadata = await sdk.tenants.fetchAndProcessMetadata('ipfs://QmXxx.../metadata.json');
|
|
8563
|
+
* if (metadata) {
|
|
8564
|
+
* console.log(metadata.name, metadata.imageUrl);
|
|
8565
|
+
* }
|
|
8566
|
+
* ```
|
|
8567
|
+
*/
|
|
8568
|
+
async fetchAndProcessMetadata(tokenUri) {
|
|
8569
|
+
try {
|
|
8570
|
+
const resolvedUri = await this.resolveIpfsUrl(tokenUri);
|
|
8571
|
+
const response = await fetch(resolvedUri);
|
|
8572
|
+
if (!response.ok) {
|
|
8573
|
+
console.warn(`Failed to fetch metadata from ${resolvedUri}: ${response.status}`);
|
|
8574
|
+
return null;
|
|
8575
|
+
}
|
|
8576
|
+
const rawMetadata = await response.json();
|
|
8577
|
+
// Normalize the metadata - resolve image URLs
|
|
8578
|
+
const imageUrl = rawMetadata.image || rawMetadata.image_url || rawMetadata.imageUrl || '';
|
|
8579
|
+
const resolvedImageUrl = await this.resolveIpfsUrl(imageUrl);
|
|
8580
|
+
const animationUrl = rawMetadata.animation_url || rawMetadata.animationUrl || '';
|
|
8581
|
+
const resolvedAnimationUrl = animationUrl ? await this.resolveIpfsUrl(animationUrl) : undefined;
|
|
8582
|
+
return {
|
|
8583
|
+
name: rawMetadata.name || 'Untitled',
|
|
8584
|
+
description: rawMetadata.description || '',
|
|
8585
|
+
imageUrl: resolvedImageUrl,
|
|
8586
|
+
animationUrl: resolvedAnimationUrl,
|
|
8587
|
+
externalUrl: rawMetadata.external_url || rawMetadata.externalUrl,
|
|
8588
|
+
attributes: rawMetadata.attributes,
|
|
8589
|
+
...rawMetadata // Include any additional properties
|
|
8590
|
+
};
|
|
8591
|
+
}
|
|
8592
|
+
catch (error) {
|
|
8593
|
+
console.warn(`Error fetching metadata from ${tokenUri}:`, error);
|
|
8594
|
+
return null;
|
|
8595
|
+
}
|
|
8596
|
+
}
|
|
8411
8597
|
}
|
|
8412
8598
|
|
|
8413
8599
|
/**
|
|
@@ -9144,6 +9330,19 @@ exports.AuthStatus = void 0;
|
|
|
9144
9330
|
function isExtendedProvider(provider) {
|
|
9145
9331
|
return 'setAuthType' in provider && typeof provider.setAuthType === 'function';
|
|
9146
9332
|
}
|
|
9333
|
+
/**
|
|
9334
|
+
* Fatal auth error codes that require immediate logout.
|
|
9335
|
+
* These indicate the session is completely invalid and cannot be recovered.
|
|
9336
|
+
* TOKEN_EXPIRED is NOT in this list - it's the normal "please refresh" case.
|
|
9337
|
+
*
|
|
9338
|
+
* Values validated against CommonErrorCodes from @explorins/pers-shared at compile time.
|
|
9339
|
+
*/
|
|
9340
|
+
const FATAL_AUTH_CODES = [
|
|
9341
|
+
'REFRESH_TOKEN_EXPIRED',
|
|
9342
|
+
'REFRESH_TOKEN_REVOKED',
|
|
9343
|
+
'INVALID_TOKEN',
|
|
9344
|
+
'TOKEN_REVOKED'
|
|
9345
|
+
];
|
|
9147
9346
|
/**
|
|
9148
9347
|
* Platform-agnostic authentication service
|
|
9149
9348
|
* Handles login, token refresh, and storage operations
|
|
@@ -9153,6 +9352,7 @@ class AuthService {
|
|
|
9153
9352
|
this.authApi = authApi;
|
|
9154
9353
|
this.authProvider = authProvider;
|
|
9155
9354
|
this.activeRefreshPromise = null;
|
|
9355
|
+
this.activeAuthFailurePromise = null;
|
|
9156
9356
|
this.currentAuthStatus = exports.AuthStatus.UNAUTHENTICATED;
|
|
9157
9357
|
}
|
|
9158
9358
|
/**
|
|
@@ -9291,13 +9491,42 @@ class AuthService {
|
|
|
9291
9491
|
}
|
|
9292
9492
|
}
|
|
9293
9493
|
/**
|
|
9294
|
-
*
|
|
9295
|
-
*
|
|
9494
|
+
* Check if an error code indicates a fatal auth failure.
|
|
9495
|
+
* Fatal errors require immediate logout - the session cannot be recovered.
|
|
9496
|
+
*/
|
|
9497
|
+
isFatalAuthError(errorCode) {
|
|
9498
|
+
return FATAL_AUTH_CODES.includes(errorCode);
|
|
9499
|
+
}
|
|
9500
|
+
/**
|
|
9501
|
+
* Handle authentication failure with proper cleanup.
|
|
9502
|
+
* Called when backend returns fatal auth error codes or refresh fails.
|
|
9296
9503
|
*/
|
|
9297
9504
|
async handleAuthFailure() {
|
|
9505
|
+
// Already failed - nothing to do
|
|
9298
9506
|
if (this.currentAuthStatus === exports.AuthStatus.AUTH_FAILED) {
|
|
9299
9507
|
return;
|
|
9300
9508
|
}
|
|
9509
|
+
// If refresh is currently in progress, don't fail - wait for refresh result
|
|
9510
|
+
if (this.activeRefreshPromise) {
|
|
9511
|
+
return;
|
|
9512
|
+
}
|
|
9513
|
+
// If auth failure is already in progress, wait for it
|
|
9514
|
+
if (this.activeAuthFailurePromise) {
|
|
9515
|
+
return await this.activeAuthFailurePromise;
|
|
9516
|
+
}
|
|
9517
|
+
// Create the failure promise to deduplicate concurrent calls
|
|
9518
|
+
this.activeAuthFailurePromise = this.performAuthFailure();
|
|
9519
|
+
try {
|
|
9520
|
+
await this.activeAuthFailurePromise;
|
|
9521
|
+
}
|
|
9522
|
+
finally {
|
|
9523
|
+
this.activeAuthFailurePromise = null;
|
|
9524
|
+
}
|
|
9525
|
+
}
|
|
9526
|
+
/**
|
|
9527
|
+
* Performs the actual auth failure cleanup
|
|
9528
|
+
*/
|
|
9529
|
+
async performAuthFailure() {
|
|
9301
9530
|
// Clear active refresh operations to prevent hanging promises
|
|
9302
9531
|
this.activeRefreshPromise = null;
|
|
9303
9532
|
// Clear all stored tokens
|
|
@@ -9419,10 +9648,8 @@ class TokenRefreshManager {
|
|
|
9419
9648
|
return true;
|
|
9420
9649
|
}
|
|
9421
9650
|
catch (error) {
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
await this.authService.handleAuthFailure();
|
|
9425
|
-
}
|
|
9651
|
+
// Don't call handleAuthFailure here - let the caller decide
|
|
9652
|
+
// This prevents duplicate auth failure handling in race conditions
|
|
9426
9653
|
return false;
|
|
9427
9654
|
}
|
|
9428
9655
|
}
|
|
@@ -9955,15 +10182,6 @@ class DefaultAuthProvider {
|
|
|
9955
10182
|
const hasIndexedDB = this.canEnableDPoP();
|
|
9956
10183
|
const canUseDPoP = hasCustomCryptoProvider || hasIndexedDB;
|
|
9957
10184
|
this.dpopEnabled = !!(config.dpop?.enabled && canUseDPoP);
|
|
9958
|
-
// Debug logging for DPoP configuration
|
|
9959
|
-
console.log('[DefaultAuthProvider] DPoP config:', {
|
|
9960
|
-
requestedEnabled: config.dpop?.enabled ?? false,
|
|
9961
|
-
hasCustomCryptoProvider,
|
|
9962
|
-
hasIndexedDB,
|
|
9963
|
-
canUseDPoP,
|
|
9964
|
-
finalEnabled: this.dpopEnabled,
|
|
9965
|
-
cryptoProviderType: config.dpop?.cryptoProvider?.constructor?.name || 'none'
|
|
9966
|
-
});
|
|
9967
10185
|
if (config.dpop?.enabled && !canUseDPoP) {
|
|
9968
10186
|
console.warn('[DefaultAuthProvider] DPoP disabled: IndexedDB not available and no custom crypto provider supplied. CryptoKey persistence requires IndexedDB or a custom provider.');
|
|
9969
10187
|
}
|
|
@@ -10134,7 +10352,7 @@ class DefaultAuthProvider {
|
|
|
10134
10352
|
/** SDK package name */
|
|
10135
10353
|
const SDK_NAME = "@explorins/pers-sdk";
|
|
10136
10354
|
/** SDK version - injected from package.json at build time */
|
|
10137
|
-
const SDK_VERSION = "2.1.
|
|
10355
|
+
const SDK_VERSION = "2.1.31";
|
|
10138
10356
|
/** Full SDK identifier for headers */
|
|
10139
10357
|
const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
|
|
10140
10358
|
|
|
@@ -10283,12 +10501,21 @@ class PersApiClient {
|
|
|
10283
10501
|
// Handle 401 errors centrally through AuthService
|
|
10284
10502
|
if (status === 401) {
|
|
10285
10503
|
const backendError = ErrorUtils.extractBackendErrorDetails(error);
|
|
10504
|
+
// Check for fatal auth errors that require immediate logout
|
|
10505
|
+
if (this.authService.isFatalAuthError(backendError.code)) {
|
|
10506
|
+
// Definitive auth failure - no retry, session is invalid
|
|
10507
|
+
await this.authService.handleAuthFailure();
|
|
10508
|
+
this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
|
|
10509
|
+
throw new AuthenticationError(errorMessage, endpoint, method, backendError);
|
|
10510
|
+
}
|
|
10286
10511
|
// If this IS a refresh request that failed with 401, don't retry to avoid infinite loop
|
|
10287
10512
|
if (options?.isRefreshRequest) {
|
|
10513
|
+
// Refresh failed - this is a real auth failure
|
|
10514
|
+
await this.authService.handleAuthFailure();
|
|
10288
10515
|
this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
|
|
10289
10516
|
throw new AuthenticationError(errorMessage, endpoint, method, backendError);
|
|
10290
10517
|
}
|
|
10291
|
-
//
|
|
10518
|
+
// TOKEN_EXPIRED or unknown 401: try refresh once, then retry
|
|
10292
10519
|
if (retryCount === 0 && this.mergedConfig.authProvider) {
|
|
10293
10520
|
try {
|
|
10294
10521
|
const refreshSuccessful = await this.refreshManager.attemptInternalRefresh();
|
|
@@ -10299,13 +10526,15 @@ class PersApiClient {
|
|
|
10299
10526
|
domain: 'authentication',
|
|
10300
10527
|
userMessage: 'Authentication token refreshed',
|
|
10301
10528
|
});
|
|
10529
|
+
// Retry with new token
|
|
10302
10530
|
return await this.request(method, endpoint, body, { ...options, retryCount: 1 });
|
|
10303
10531
|
}
|
|
10304
10532
|
}
|
|
10305
10533
|
catch (refreshError) {
|
|
10306
|
-
//
|
|
10534
|
+
// Refresh failed - fall through to handleAuthFailure
|
|
10307
10535
|
}
|
|
10308
10536
|
}
|
|
10537
|
+
// All recovery attempts failed
|
|
10309
10538
|
await this.authService.handleAuthFailure();
|
|
10310
10539
|
this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
|
|
10311
10540
|
throw new AuthenticationError(errorMessage, endpoint, method, backendError);
|
|
@@ -12266,6 +12495,69 @@ class BusinessManager {
|
|
|
12266
12495
|
async getBusinessTypes(options) {
|
|
12267
12496
|
return this.businessService.getBusinessTypes();
|
|
12268
12497
|
}
|
|
12498
|
+
/**
|
|
12499
|
+
* Admin: Create a new business type
|
|
12500
|
+
*
|
|
12501
|
+
* Creates a new business type category in the system.
|
|
12502
|
+
* Requires administrator privileges.
|
|
12503
|
+
*
|
|
12504
|
+
* @param data - Business type data (name, description, code, iconUrl)
|
|
12505
|
+
* @returns Promise resolving to the created business type
|
|
12506
|
+
* @throws {PersApiError} When not authenticated as admin or validation fails
|
|
12507
|
+
*
|
|
12508
|
+
* @example
|
|
12509
|
+
* ```typescript
|
|
12510
|
+
* const newType = await sdk.business.createBusinessType({
|
|
12511
|
+
* name: 'Restaurant',
|
|
12512
|
+
* description: 'Food service establishments',
|
|
12513
|
+
* code: 'RESTAURANT',
|
|
12514
|
+
* iconUrl: 'https://example.com/restaurant-icon.png'
|
|
12515
|
+
* });
|
|
12516
|
+
* ```
|
|
12517
|
+
*/
|
|
12518
|
+
async createBusinessType(data) {
|
|
12519
|
+
return this.businessApi.createBusinessType(data);
|
|
12520
|
+
}
|
|
12521
|
+
/**
|
|
12522
|
+
* Admin: Update an existing business type
|
|
12523
|
+
*
|
|
12524
|
+
* Updates a business type's properties.
|
|
12525
|
+
* Requires administrator privileges.
|
|
12526
|
+
*
|
|
12527
|
+
* @param data - Updated business type data (must include id)
|
|
12528
|
+
* @returns Promise resolving to the updated business type
|
|
12529
|
+
* @throws {PersApiError} When not authenticated as admin or type not found
|
|
12530
|
+
*
|
|
12531
|
+
* @example
|
|
12532
|
+
* ```typescript
|
|
12533
|
+
* const updated = await sdk.business.updateBusinessType({
|
|
12534
|
+
* id: 123,
|
|
12535
|
+
* name: 'Fine Dining Restaurant',
|
|
12536
|
+
* description: 'Upscale dining establishments'
|
|
12537
|
+
* });
|
|
12538
|
+
* ```
|
|
12539
|
+
*/
|
|
12540
|
+
async updateBusinessType(data) {
|
|
12541
|
+
return this.businessApi.updateBusinessType(data);
|
|
12542
|
+
}
|
|
12543
|
+
/**
|
|
12544
|
+
* Admin: Delete a business type
|
|
12545
|
+
*
|
|
12546
|
+
* Deletes a business type. Will fail if type is in use by businesses.
|
|
12547
|
+
* Requires administrator privileges.
|
|
12548
|
+
*
|
|
12549
|
+
* @param id - Business type ID to delete
|
|
12550
|
+
* @returns Promise resolving to true if deleted successfully
|
|
12551
|
+
* @throws {PersApiError} When not authenticated as admin or type not found/in use
|
|
12552
|
+
*
|
|
12553
|
+
* @example
|
|
12554
|
+
* ```typescript
|
|
12555
|
+
* const success = await sdk.business.deleteBusinessType(123);
|
|
12556
|
+
* ```
|
|
12557
|
+
*/
|
|
12558
|
+
async deleteBusinessType(id) {
|
|
12559
|
+
return this.businessApi.deleteBusinessType(id);
|
|
12560
|
+
}
|
|
12269
12561
|
/**
|
|
12270
12562
|
* Get businesses by type
|
|
12271
12563
|
*
|
|
@@ -14274,6 +14566,77 @@ class RedemptionManager {
|
|
|
14274
14566
|
async deleteRedemptionTokenUnit(redemptionId, tokenUnitId) {
|
|
14275
14567
|
return this.redemptionService.deleteRedemptionTokenUnit(redemptionId, tokenUnitId);
|
|
14276
14568
|
}
|
|
14569
|
+
// ==========================================
|
|
14570
|
+
// REDEMPTION TYPE MANAGEMENT (Admin)
|
|
14571
|
+
// ==========================================
|
|
14572
|
+
/**
|
|
14573
|
+
* Admin: Create redemption type
|
|
14574
|
+
*
|
|
14575
|
+
* Creates a new redemption type category for organizing redemption offers.
|
|
14576
|
+
* Requires administrator privileges.
|
|
14577
|
+
*
|
|
14578
|
+
* @param data - Redemption type data (name, description, imageUrl)
|
|
14579
|
+
* @returns Promise resolving to created redemption type
|
|
14580
|
+
* @throws {PersApiError} When not authenticated as admin or validation fails
|
|
14581
|
+
*
|
|
14582
|
+
* @example
|
|
14583
|
+
* ```typescript
|
|
14584
|
+
* const newType = await sdk.redemptions.createRedemptionType({
|
|
14585
|
+
* name: 'Premium Rewards',
|
|
14586
|
+
* description: 'High-value exclusive rewards',
|
|
14587
|
+
* imageUrl: 'https://example.com/premium-icon.png'
|
|
14588
|
+
* });
|
|
14589
|
+
*
|
|
14590
|
+
* console.log('Created type:', newType.name);
|
|
14591
|
+
* ```
|
|
14592
|
+
*/
|
|
14593
|
+
async createRedemptionType(data) {
|
|
14594
|
+
return this.redemptionService.createRedemptionType(data);
|
|
14595
|
+
}
|
|
14596
|
+
/**
|
|
14597
|
+
* Admin: Update redemption type
|
|
14598
|
+
*
|
|
14599
|
+
* Updates an existing redemption type's properties.
|
|
14600
|
+
* Requires administrator privileges.
|
|
14601
|
+
*
|
|
14602
|
+
* @param data - Updated redemption type data (must include id)
|
|
14603
|
+
* @returns Promise resolving to updated redemption type
|
|
14604
|
+
* @throws {PersApiError} When not authenticated as admin or type not found
|
|
14605
|
+
*
|
|
14606
|
+
* @example
|
|
14607
|
+
* ```typescript
|
|
14608
|
+
* const updated = await sdk.redemptions.updateRedemptionType({
|
|
14609
|
+
* id: 123,
|
|
14610
|
+
* name: 'VIP Rewards',
|
|
14611
|
+
* description: 'Updated description',
|
|
14612
|
+
* imageUrl: 'https://example.com/vip-icon.png'
|
|
14613
|
+
* });
|
|
14614
|
+
*
|
|
14615
|
+
* console.log('Updated type:', updated.name);
|
|
14616
|
+
* ```
|
|
14617
|
+
*/
|
|
14618
|
+
async updateRedemptionType(data) {
|
|
14619
|
+
return this.redemptionService.updateRedemptionType(data);
|
|
14620
|
+
}
|
|
14621
|
+
/**
|
|
14622
|
+
* Admin: Delete redemption type
|
|
14623
|
+
*
|
|
14624
|
+
* Deletes a redemption type. Will fail if type is in use by redemptions.
|
|
14625
|
+
* Requires administrator privileges.
|
|
14626
|
+
*
|
|
14627
|
+
* @param id - Redemption type ID to delete
|
|
14628
|
+
* @returns Promise resolving to true if deleted successfully
|
|
14629
|
+
* @throws {PersApiError} When not authenticated as admin or type not found/in use
|
|
14630
|
+
*
|
|
14631
|
+
* @example
|
|
14632
|
+
* ```typescript
|
|
14633
|
+
* const success = await sdk.redemptions.deleteRedemptionType(123);
|
|
14634
|
+
* console.log('Deleted:', success);
|
|
14635
|
+
* ```
|
|
14636
|
+
*/
|
|
14637
|
+
async deleteRedemptionType(id) {
|
|
14638
|
+
return this.redemptionService.deleteRedemptionType(id);
|
|
14639
|
+
}
|
|
14277
14640
|
/**
|
|
14278
14641
|
* Get the full redemption service for advanced operations
|
|
14279
14642
|
*
|
|
@@ -19429,15 +19792,6 @@ function createReactNativeAuthProvider(projectKey, config = {}) {
|
|
|
19429
19792
|
enabled: dpop.enabled ?? true,
|
|
19430
19793
|
cryptoProvider: dpop.cryptoProvider
|
|
19431
19794
|
} : undefined;
|
|
19432
|
-
// Always log DPoP config for debugging (critical for mobile auth)
|
|
19433
|
-
console.log('[ReactNativeAuthProvider] Config:', {
|
|
19434
|
-
platform: reactNative.Platform.OS,
|
|
19435
|
-
isWebPlatform,
|
|
19436
|
-
dpopEnabled: dpopConfig?.enabled ?? false,
|
|
19437
|
-
hasCryptoProvider: !!dpopConfig?.cryptoProvider,
|
|
19438
|
-
cryptoProviderType: dpopConfig?.cryptoProvider?.constructor?.name || 'none',
|
|
19439
|
-
storageType: tokenStorage.constructor.name
|
|
19440
|
-
});
|
|
19441
19795
|
// Return DefaultAuthProvider configured with platform-appropriate storage
|
|
19442
19796
|
return new DefaultAuthProvider({
|
|
19443
19797
|
authType,
|
|
@@ -19764,13 +20118,6 @@ const PersSDKProvider = ({ children, config }) => {
|
|
|
19764
20118
|
if (reactNative.Platform.OS !== 'web' && isDpopEnabled && !dpopSettings.cryptoProvider) {
|
|
19765
20119
|
dpopSettings.cryptoProvider = new ReactNativeDPoPProvider();
|
|
19766
20120
|
}
|
|
19767
|
-
// Log DPoP settings before creating auth provider
|
|
19768
|
-
console.log('[PersSDKProvider] DPoP settings:', {
|
|
19769
|
-
platform: reactNative.Platform.OS,
|
|
19770
|
-
isDpopEnabled,
|
|
19771
|
-
hasCryptoProvider: !!dpopSettings.cryptoProvider,
|
|
19772
|
-
cryptoProviderType: dpopSettings.cryptoProvider?.constructor?.name || 'none'
|
|
19773
|
-
});
|
|
19774
20121
|
// Create platform-aware auth provider if not provided
|
|
19775
20122
|
let authProvider;
|
|
19776
20123
|
if (config.authProvider) {
|
|
@@ -40279,6 +40626,13 @@ class MetadataDomainService {
|
|
|
40279
40626
|
const cacheKey = `resolve:${url}`;
|
|
40280
40627
|
return this.cache.getOrSet(cacheKey, () => this.ipfsApi.resolveIPFSUrl(url), CacheTTL.GATEWAY);
|
|
40281
40628
|
}
|
|
40629
|
+
/**
|
|
40630
|
+
* Get IPFS gateway domain from tenant configuration.
|
|
40631
|
+
* Useful for pipes that need to resolve URLs synchronously after initial fetch.
|
|
40632
|
+
*/
|
|
40633
|
+
async getIpfsGatewayDomain() {
|
|
40634
|
+
return this.ipfsApi.getIpfsGatewayDomain();
|
|
40635
|
+
}
|
|
40282
40636
|
/**
|
|
40283
40637
|
* Clear all cached metadata and URLs for this service
|
|
40284
40638
|
*/
|
|
@@ -40384,6 +40738,15 @@ class Web3ApplicationService {
|
|
|
40384
40738
|
async resolveIPFSUrl(url) {
|
|
40385
40739
|
return this.metadataDomainService.resolveIPFSUrl(url);
|
|
40386
40740
|
}
|
|
40741
|
+
/**
|
|
40742
|
+
* Get IPFS gateway domain from tenant configuration.
|
|
40743
|
+
* Useful for pipes that need to resolve URLs synchronously after initial fetch.
|
|
40744
|
+
*
|
|
40745
|
+
* @returns Gateway domain (e.g., 'pers.mypinata.cloud')
|
|
40746
|
+
*/
|
|
40747
|
+
async getIpfsGatewayDomain() {
|
|
40748
|
+
return this.metadataDomainService.getIpfsGatewayDomain();
|
|
40749
|
+
}
|
|
40387
40750
|
/**
|
|
40388
40751
|
* Fetch and process metadata from any URI with IPFS conversion.
|
|
40389
40752
|
*
|
|
@@ -40504,25 +40867,26 @@ class IPFSInfrastructureApi {
|
|
|
40504
40867
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
40505
40868
|
}
|
|
40506
40869
|
const rawMetadata = await response.json();
|
|
40507
|
-
// Resolve IPFS URLs
|
|
40508
|
-
const
|
|
40509
|
-
|
|
40510
|
-
|
|
40511
|
-
|
|
40512
|
-
|
|
40513
|
-
|
|
40870
|
+
// Resolve any IPFS URLs in string fields
|
|
40871
|
+
for (const key of Object.keys(rawMetadata)) {
|
|
40872
|
+
const value = rawMetadata[key];
|
|
40873
|
+
if (typeof value === 'string' && value.startsWith('ipfs://')) {
|
|
40874
|
+
rawMetadata[key] = await this.resolveIPFSUrl(value);
|
|
40875
|
+
}
|
|
40876
|
+
}
|
|
40514
40877
|
// Extract custom properties (anything not in ERC standard)
|
|
40515
40878
|
const customProperties = Object.fromEntries(Object.entries(rawMetadata).filter(([key]) => !['name', 'description', 'image', 'animation_url', 'external_url', 'attributes'].includes(key)));
|
|
40516
|
-
// Return normalized SDK format
|
|
40517
|
-
|
|
40518
|
-
name: rawMetadata.name
|
|
40519
|
-
description: rawMetadata.description
|
|
40520
|
-
imageUrl:
|
|
40521
|
-
animationUrl:
|
|
40522
|
-
externalUrl: rawMetadata.external_url
|
|
40523
|
-
attributes: rawMetadata.attributes
|
|
40879
|
+
// Return normalized SDK format
|
|
40880
|
+
const result = {
|
|
40881
|
+
name: String(rawMetadata.name ?? ''),
|
|
40882
|
+
description: String(rawMetadata.description ?? ''),
|
|
40883
|
+
imageUrl: String(rawMetadata.image ?? ''),
|
|
40884
|
+
animationUrl: rawMetadata.animation_url ? String(rawMetadata.animation_url) : undefined,
|
|
40885
|
+
externalUrl: rawMetadata.external_url ? String(rawMetadata.external_url) : undefined,
|
|
40886
|
+
attributes: Array.isArray(rawMetadata.attributes) ? rawMetadata.attributes : [],
|
|
40524
40887
|
...customProperties
|
|
40525
40888
|
};
|
|
40889
|
+
return result;
|
|
40526
40890
|
}
|
|
40527
40891
|
catch (error) {
|
|
40528
40892
|
console.error('Error fetching metadata:', error);
|
|
@@ -40645,8 +41009,12 @@ async function getExplorerUrlByChainId(getChainData, chainId, address, type) {
|
|
|
40645
41009
|
*
|
|
40646
41010
|
* ## IPFS Resolution
|
|
40647
41011
|
*
|
|
40648
|
-
*
|
|
40649
|
-
*
|
|
41012
|
+
* **Note:** IPFS is chain-agnostic. Use `sdk.tenants` for IPFS operations:
|
|
41013
|
+
*
|
|
41014
|
+
* ```typescript
|
|
41015
|
+
* const httpsUrl = await sdk.tenants.resolveIpfsUrl('ipfs://QmXxx.../image.png');
|
|
41016
|
+
* const metadata = await sdk.tenants.fetchAndProcessMetadata('ipfs://QmXxx.../metadata.json');
|
|
41017
|
+
* ```
|
|
40650
41018
|
*/
|
|
40651
41019
|
class Web3Manager {
|
|
40652
41020
|
// TODO: Add PersEventEmitter support for blockchain events
|
|
@@ -40700,9 +41068,10 @@ class Web3Manager {
|
|
|
40700
41068
|
*
|
|
40701
41069
|
* @param tokenUri - Token URI to fetch metadata from
|
|
40702
41070
|
* @returns Promise resolving to processed metadata or null if not found
|
|
41071
|
+
* @deprecated Use sdk.tenants.fetchAndProcessMetadata() instead - IPFS is chain-agnostic
|
|
40703
41072
|
*/
|
|
40704
41073
|
async fetchAndProcessMetadata(tokenUri) {
|
|
40705
|
-
return this.
|
|
41074
|
+
return this.tenantManager.fetchAndProcessMetadata(tokenUri);
|
|
40706
41075
|
}
|
|
40707
41076
|
/**
|
|
40708
41077
|
* Get blockchain chain data by chain ID
|
|
@@ -41010,16 +41379,27 @@ const useWeb3 = () => {
|
|
|
41010
41379
|
/**
|
|
41011
41380
|
* Resolve IPFS URL to HTTP gateway URL
|
|
41012
41381
|
*
|
|
41013
|
-
*
|
|
41382
|
+
* Converts an ipfs:// URL to an HTTPS URL using the tenant's configured IPFS gateway.
|
|
41383
|
+
* Non-IPFS URLs are returned unchanged.
|
|
41384
|
+
*
|
|
41385
|
+
* Note: Uses sdk.tenants for IPFS resolution (chain-agnostic).
|
|
41386
|
+
*
|
|
41014
41387
|
* @param url - IPFS URL to resolve (ipfs://...)
|
|
41015
41388
|
* @returns Promise resolving to HTTP gateway URL
|
|
41389
|
+
*
|
|
41390
|
+
* @example
|
|
41391
|
+
* ```typescript
|
|
41392
|
+
* const { resolveIPFSUrl } = useWeb3();
|
|
41393
|
+
* const httpsUrl = await resolveIPFSUrl('ipfs://QmHash...');
|
|
41394
|
+
* // Returns: 'https://gateway.pinata.cloud/ipfs/QmHash...'
|
|
41395
|
+
* ```
|
|
41016
41396
|
*/
|
|
41017
41397
|
const resolveIPFSUrl = react.useCallback(async (url) => {
|
|
41018
41398
|
if (!isInitialized || !sdk) {
|
|
41019
41399
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
41020
41400
|
}
|
|
41021
41401
|
try {
|
|
41022
|
-
const result = await sdk.tenants.
|
|
41402
|
+
const result = await sdk.tenants.resolveIpfsUrl(url);
|
|
41023
41403
|
return result;
|
|
41024
41404
|
}
|
|
41025
41405
|
catch (error) {
|
|
@@ -41030,22 +41410,24 @@ const useWeb3 = () => {
|
|
|
41030
41410
|
/**
|
|
41031
41411
|
* Fetch and process token metadata from a URI
|
|
41032
41412
|
*
|
|
41413
|
+
* Note: Uses sdk.tenants for metadata fetching (chain-agnostic IPFS).
|
|
41414
|
+
*
|
|
41033
41415
|
* @param tokenUri - Token URI to fetch metadata from
|
|
41034
41416
|
* @returns Promise resolving to processed metadata or null
|
|
41035
41417
|
*/
|
|
41036
41418
|
const fetchAndProcessMetadata = react.useCallback(async (tokenUri) => {
|
|
41037
|
-
if (!isInitialized || !
|
|
41419
|
+
if (!isInitialized || !sdk) {
|
|
41038
41420
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
41039
41421
|
}
|
|
41040
41422
|
try {
|
|
41041
|
-
const result = await
|
|
41423
|
+
const result = await sdk.tenants.fetchAndProcessMetadata(tokenUri);
|
|
41042
41424
|
return result;
|
|
41043
41425
|
}
|
|
41044
41426
|
catch (error) {
|
|
41045
41427
|
console.error('Failed to fetch and process metadata:', error);
|
|
41046
41428
|
throw error;
|
|
41047
41429
|
}
|
|
41048
|
-
}, [
|
|
41430
|
+
}, [sdk, isInitialized]);
|
|
41049
41431
|
const getChainDataById = react.useCallback(async (chainId) => {
|
|
41050
41432
|
if (!isInitialized || !web3) {
|
|
41051
41433
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
@@ -42680,8 +43062,7 @@ const useTransactions = () => {
|
|
|
42680
43062
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
42681
43063
|
}
|
|
42682
43064
|
try {
|
|
42683
|
-
|
|
42684
|
-
return result;
|
|
43065
|
+
return await sdk.transactions.getTransactionById(transactionId, include);
|
|
42685
43066
|
}
|
|
42686
43067
|
catch (error) {
|
|
42687
43068
|
console.error('Failed to fetch transaction:', error);
|
|
@@ -42730,8 +43111,7 @@ const useTransactions = () => {
|
|
|
42730
43111
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
42731
43112
|
}
|
|
42732
43113
|
try {
|
|
42733
|
-
|
|
42734
|
-
return result;
|
|
43114
|
+
return await sdk.transactions.getUserTransactionHistory(options);
|
|
42735
43115
|
}
|
|
42736
43116
|
catch (error) {
|
|
42737
43117
|
console.error('Failed to fetch transaction history:', error);
|
|
@@ -43771,6 +44151,181 @@ const useRedemptions = () => {
|
|
|
43771
44151
|
};
|
|
43772
44152
|
};
|
|
43773
44153
|
|
|
44154
|
+
/**
|
|
44155
|
+
* React hook for resolving media URLs (IPFS or HTTPS).
|
|
44156
|
+
*
|
|
44157
|
+
* Automatically handles IPFS URLs by resolving them to HTTPS via tenant's gateway.
|
|
44158
|
+
* Non-IPFS URLs are returned as-is. Handles loading and error states.
|
|
44159
|
+
*
|
|
44160
|
+
* Uses sdk.tenants for IPFS resolution (lightweight, no Web3 deps).
|
|
44161
|
+
*
|
|
44162
|
+
* @param url - The URL to resolve (can be ipfs://, https://, or undefined)
|
|
44163
|
+
* @returns Object with resolved URL, loading state, and error
|
|
44164
|
+
*
|
|
44165
|
+
* @example
|
|
44166
|
+
* ```tsx
|
|
44167
|
+
* function NFTImage({ metadataUri }: { metadataUri?: string }) {
|
|
44168
|
+
* const { url, isLoading } = useResolvedUrl(metadataUri);
|
|
44169
|
+
*
|
|
44170
|
+
* if (isLoading) return <ActivityIndicator />;
|
|
44171
|
+
* return <Image source={{ uri: url }} />;
|
|
44172
|
+
* }
|
|
44173
|
+
* ```
|
|
44174
|
+
*/
|
|
44175
|
+
function useResolvedUrl(url) {
|
|
44176
|
+
const { sdk, isInitialized } = usePersSDK();
|
|
44177
|
+
const [resolvedUrl, setResolvedUrl] = react.useState(null);
|
|
44178
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
44179
|
+
const [error, setError] = react.useState(null);
|
|
44180
|
+
react.useEffect(() => {
|
|
44181
|
+
if (!url || !isInitialized || !sdk) {
|
|
44182
|
+
setResolvedUrl(null);
|
|
44183
|
+
return;
|
|
44184
|
+
}
|
|
44185
|
+
// Non-IPFS URLs don't need resolution
|
|
44186
|
+
if (!url.startsWith('ipfs://')) {
|
|
44187
|
+
setResolvedUrl(url);
|
|
44188
|
+
return;
|
|
44189
|
+
}
|
|
44190
|
+
setIsLoading(true);
|
|
44191
|
+
setError(null);
|
|
44192
|
+
sdk.tenants.resolveIpfsUrl(url)
|
|
44193
|
+
.then((resolved) => {
|
|
44194
|
+
setResolvedUrl(resolved);
|
|
44195
|
+
})
|
|
44196
|
+
.catch((err) => {
|
|
44197
|
+
setError(err instanceof Error ? err.message : 'Failed to resolve URL');
|
|
44198
|
+
setResolvedUrl(null);
|
|
44199
|
+
})
|
|
44200
|
+
.finally(() => {
|
|
44201
|
+
setIsLoading(false);
|
|
44202
|
+
});
|
|
44203
|
+
}, [url, isInitialized, sdk]);
|
|
44204
|
+
return {
|
|
44205
|
+
/** Resolved HTTPS URL (or original if not IPFS) */
|
|
44206
|
+
url: resolvedUrl,
|
|
44207
|
+
/** Whether resolution is in progress */
|
|
44208
|
+
isLoading,
|
|
44209
|
+
/** Error message if resolution failed */
|
|
44210
|
+
error,
|
|
44211
|
+
};
|
|
44212
|
+
}
|
|
44213
|
+
/**
|
|
44214
|
+
* React hook for fetching NFT/token metadata from a URI.
|
|
44215
|
+
*
|
|
44216
|
+
* Handles IPFS resolution automatically and returns parsed metadata
|
|
44217
|
+
* with all nested IPFS URLs already resolved to HTTPS.
|
|
44218
|
+
*
|
|
44219
|
+
* Uses sdk.tenants for metadata fetching (lightweight, no Web3 deps).
|
|
44220
|
+
*
|
|
44221
|
+
* @param metadataUri - The metadata URI to fetch (can be ipfs:// or https://)
|
|
44222
|
+
* @returns Object with metadata, loading state, error, and refresh function
|
|
44223
|
+
*
|
|
44224
|
+
* @example
|
|
44225
|
+
* ```tsx
|
|
44226
|
+
* function NFTDetails({ metadataUri }: { metadataUri?: string }) {
|
|
44227
|
+
* const { metadata, isLoading, error } = useMetadata(metadataUri);
|
|
44228
|
+
*
|
|
44229
|
+
* if (isLoading) return <ActivityIndicator />;
|
|
44230
|
+
* if (error) return <Text>Error: {error}</Text>;
|
|
44231
|
+
* if (!metadata) return null;
|
|
44232
|
+
*
|
|
44233
|
+
* return (
|
|
44234
|
+
* <View>
|
|
44235
|
+
* <Image source={{ uri: metadata.imageUrl }} />
|
|
44236
|
+
* <Text>{metadata.name}</Text>
|
|
44237
|
+
* <Text>{metadata.description}</Text>
|
|
44238
|
+
* </View>
|
|
44239
|
+
* );
|
|
44240
|
+
* }
|
|
44241
|
+
* ```
|
|
44242
|
+
*/
|
|
44243
|
+
function useMetadata(metadataUri) {
|
|
44244
|
+
const { sdk, isInitialized } = usePersSDK();
|
|
44245
|
+
const [metadata, setMetadata] = react.useState(null);
|
|
44246
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
44247
|
+
const [error, setError] = react.useState(null);
|
|
44248
|
+
const fetchMetadata = react.useCallback(async () => {
|
|
44249
|
+
if (!metadataUri || !isInitialized || !sdk) {
|
|
44250
|
+
setMetadata(null);
|
|
44251
|
+
return;
|
|
44252
|
+
}
|
|
44253
|
+
setIsLoading(true);
|
|
44254
|
+
setError(null);
|
|
44255
|
+
try {
|
|
44256
|
+
const result = await sdk.tenants.fetchAndProcessMetadata(metadataUri);
|
|
44257
|
+
setMetadata(result);
|
|
44258
|
+
}
|
|
44259
|
+
catch (err) {
|
|
44260
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch metadata');
|
|
44261
|
+
setMetadata(null);
|
|
44262
|
+
}
|
|
44263
|
+
finally {
|
|
44264
|
+
setIsLoading(false);
|
|
44265
|
+
}
|
|
44266
|
+
}, [metadataUri, isInitialized, sdk]);
|
|
44267
|
+
react.useEffect(() => {
|
|
44268
|
+
fetchMetadata();
|
|
44269
|
+
}, [fetchMetadata]);
|
|
44270
|
+
return {
|
|
44271
|
+
/** Parsed metadata with resolved IPFS URLs (imageUrl, animationUrl, etc.) */
|
|
44272
|
+
metadata,
|
|
44273
|
+
/** Whether fetch is in progress */
|
|
44274
|
+
isLoading,
|
|
44275
|
+
/** Error message if fetch failed */
|
|
44276
|
+
error,
|
|
44277
|
+
/** Manually refresh metadata */
|
|
44278
|
+
refresh: fetchMetadata,
|
|
44279
|
+
};
|
|
44280
|
+
}
|
|
44281
|
+
/**
|
|
44282
|
+
* React hook for loading transaction/token metadata.
|
|
44283
|
+
*
|
|
44284
|
+
* Convenience wrapper around useMetadata specifically for transaction items.
|
|
44285
|
+
* Pass a transaction's metadataUri to get the NFT image, name, description, etc.
|
|
44286
|
+
*
|
|
44287
|
+
* @param transaction - Transaction object with optional metadataUri field
|
|
44288
|
+
* @returns Object with metadata, loading state, and image URL
|
|
44289
|
+
*
|
|
44290
|
+
* @example
|
|
44291
|
+
* ```tsx
|
|
44292
|
+
* import { useTransactionMetadata } from '@explorins/pers-sdk-react-native';
|
|
44293
|
+
*
|
|
44294
|
+
* function TransactionItem({ tx }: { tx: TransactionDTO }) {
|
|
44295
|
+
* const { metadata, isLoading, imageUrl } = useTransactionMetadata(tx);
|
|
44296
|
+
*
|
|
44297
|
+
* if (isLoading) return <ActivityIndicator />;
|
|
44298
|
+
*
|
|
44299
|
+
* return (
|
|
44300
|
+
* <View>
|
|
44301
|
+
* {imageUrl && <Image source={{ uri: imageUrl }} style={{ width: 60, height: 60 }} />}
|
|
44302
|
+
* <Text>{metadata?.name || tx.type}</Text>
|
|
44303
|
+
* <Text>{tx.amount} tokens</Text>
|
|
44304
|
+
* </View>
|
|
44305
|
+
* );
|
|
44306
|
+
* }
|
|
44307
|
+
* ```
|
|
44308
|
+
*/
|
|
44309
|
+
function useTransactionMetadata(transaction) {
|
|
44310
|
+
const { metadata, isLoading, error, refresh } = useMetadata(transaction?.metadataUri);
|
|
44311
|
+
return {
|
|
44312
|
+
/** Parsed metadata with resolved URLs */
|
|
44313
|
+
metadata,
|
|
44314
|
+
/** Whether metadata is loading */
|
|
44315
|
+
isLoading,
|
|
44316
|
+
/** Error message if fetch failed */
|
|
44317
|
+
error,
|
|
44318
|
+
/** Convenience: resolved image URL (or undefined) */
|
|
44319
|
+
imageUrl: metadata?.imageUrl,
|
|
44320
|
+
/** Convenience: token/NFT name (or undefined) */
|
|
44321
|
+
name: metadata?.name,
|
|
44322
|
+
/** Convenience: token/NFT description (or undefined) */
|
|
44323
|
+
description: metadata?.description,
|
|
44324
|
+
/** Manually refresh metadata */
|
|
44325
|
+
refresh,
|
|
44326
|
+
};
|
|
44327
|
+
}
|
|
44328
|
+
|
|
43774
44329
|
const usePurchases = () => {
|
|
43775
44330
|
const { sdk, isInitialized, isAuthenticated } = usePersSDK();
|
|
43776
44331
|
const createPaymentIntent = react.useCallback(async (amount, currency, receiptEmail, description) => {
|
|
@@ -45201,6 +45756,7 @@ exports.CampaignManager = CampaignManager;
|
|
|
45201
45756
|
exports.CampaignService = CampaignService;
|
|
45202
45757
|
exports.ChainTypes = ChainTypes;
|
|
45203
45758
|
exports.ClientTransactionType = ClientTransactionType;
|
|
45759
|
+
exports.CommonErrorCodes = CommonErrorCodes;
|
|
45204
45760
|
exports.DEFAULT_PERS_CONFIG = DEFAULT_PERS_CONFIG;
|
|
45205
45761
|
exports.DPOP_STORAGE_KEYS = DPOP_STORAGE_KEYS;
|
|
45206
45762
|
exports.DPoPManager = DPoPManager;
|
|
@@ -45348,12 +45904,16 @@ exports.useCampaigns = useCampaigns;
|
|
|
45348
45904
|
exports.useDonations = useDonations;
|
|
45349
45905
|
exports.useEvents = useEvents;
|
|
45350
45906
|
exports.useFiles = useFiles;
|
|
45907
|
+
exports.useIPFSUrl = useResolvedUrl;
|
|
45908
|
+
exports.useMetadata = useMetadata;
|
|
45351
45909
|
exports.usePersSDK = usePersSDK;
|
|
45352
45910
|
exports.usePurchases = usePurchases;
|
|
45353
45911
|
exports.useRedemptions = useRedemptions;
|
|
45912
|
+
exports.useResolvedUrl = useResolvedUrl;
|
|
45354
45913
|
exports.useTenants = useTenants;
|
|
45355
45914
|
exports.useTokenBalances = useTokenBalances;
|
|
45356
45915
|
exports.useTokens = useTokens;
|
|
45916
|
+
exports.useTransactionMetadata = useTransactionMetadata;
|
|
45357
45917
|
exports.useTransactionSigner = useTransactionSigner;
|
|
45358
45918
|
exports.useTransactions = useTransactions;
|
|
45359
45919
|
exports.useTriggerSources = useTriggerSources;
|