@explorins/pers-sdk-react-native 2.1.20 → 2.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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
  *
@@ -9144,6 +9236,19 @@ exports.AuthStatus = void 0;
9144
9236
  function isExtendedProvider(provider) {
9145
9237
  return 'setAuthType' in provider && typeof provider.setAuthType === 'function';
9146
9238
  }
9239
+ /**
9240
+ * Fatal auth error codes that require immediate logout.
9241
+ * These indicate the session is completely invalid and cannot be recovered.
9242
+ * TOKEN_EXPIRED is NOT in this list - it's the normal "please refresh" case.
9243
+ *
9244
+ * Values validated against CommonErrorCodes from @explorins/pers-shared at compile time.
9245
+ */
9246
+ const FATAL_AUTH_CODES = [
9247
+ 'REFRESH_TOKEN_EXPIRED',
9248
+ 'REFRESH_TOKEN_REVOKED',
9249
+ 'INVALID_TOKEN',
9250
+ 'TOKEN_REVOKED'
9251
+ ];
9147
9252
  /**
9148
9253
  * Platform-agnostic authentication service
9149
9254
  * Handles login, token refresh, and storage operations
@@ -9153,6 +9258,7 @@ class AuthService {
9153
9258
  this.authApi = authApi;
9154
9259
  this.authProvider = authProvider;
9155
9260
  this.activeRefreshPromise = null;
9261
+ this.activeAuthFailurePromise = null;
9156
9262
  this.currentAuthStatus = exports.AuthStatus.UNAUTHENTICATED;
9157
9263
  }
9158
9264
  /**
@@ -9291,13 +9397,42 @@ class AuthService {
9291
9397
  }
9292
9398
  }
9293
9399
  /**
9294
- * Handle authentication failure with proper cleanup
9295
- * Centralized method to avoid redundant token cleanup logic
9400
+ * Check if an error code indicates a fatal auth failure.
9401
+ * Fatal errors require immediate logout - the session cannot be recovered.
9402
+ */
9403
+ isFatalAuthError(errorCode) {
9404
+ return FATAL_AUTH_CODES.includes(errorCode);
9405
+ }
9406
+ /**
9407
+ * Handle authentication failure with proper cleanup.
9408
+ * Called when backend returns fatal auth error codes or refresh fails.
9296
9409
  */
9297
9410
  async handleAuthFailure() {
9411
+ // Already failed - nothing to do
9298
9412
  if (this.currentAuthStatus === exports.AuthStatus.AUTH_FAILED) {
9299
9413
  return;
9300
9414
  }
9415
+ // If refresh is currently in progress, don't fail - wait for refresh result
9416
+ if (this.activeRefreshPromise) {
9417
+ return;
9418
+ }
9419
+ // If auth failure is already in progress, wait for it
9420
+ if (this.activeAuthFailurePromise) {
9421
+ return await this.activeAuthFailurePromise;
9422
+ }
9423
+ // Create the failure promise to deduplicate concurrent calls
9424
+ this.activeAuthFailurePromise = this.performAuthFailure();
9425
+ try {
9426
+ await this.activeAuthFailurePromise;
9427
+ }
9428
+ finally {
9429
+ this.activeAuthFailurePromise = null;
9430
+ }
9431
+ }
9432
+ /**
9433
+ * Performs the actual auth failure cleanup
9434
+ */
9435
+ async performAuthFailure() {
9301
9436
  // Clear active refresh operations to prevent hanging promises
9302
9437
  this.activeRefreshPromise = null;
9303
9438
  // Clear all stored tokens
@@ -9419,10 +9554,8 @@ class TokenRefreshManager {
9419
9554
  return true;
9420
9555
  }
9421
9556
  catch (error) {
9422
- const isAuthError = error && typeof error === 'object' && 'status' in error && error.status === 401;
9423
- if (isAuthError) {
9424
- await this.authService.handleAuthFailure();
9425
- }
9557
+ // Don't call handleAuthFailure here - let the caller decide
9558
+ // This prevents duplicate auth failure handling in race conditions
9426
9559
  return false;
9427
9560
  }
9428
9561
  }
@@ -9955,15 +10088,6 @@ class DefaultAuthProvider {
9955
10088
  const hasIndexedDB = this.canEnableDPoP();
9956
10089
  const canUseDPoP = hasCustomCryptoProvider || hasIndexedDB;
9957
10090
  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
10091
  if (config.dpop?.enabled && !canUseDPoP) {
9968
10092
  console.warn('[DefaultAuthProvider] DPoP disabled: IndexedDB not available and no custom crypto provider supplied. CryptoKey persistence requires IndexedDB or a custom provider.');
9969
10093
  }
@@ -10134,7 +10258,7 @@ class DefaultAuthProvider {
10134
10258
  /** SDK package name */
10135
10259
  const SDK_NAME = "@explorins/pers-sdk";
10136
10260
  /** SDK version - injected from package.json at build time */
10137
- const SDK_VERSION = "2.1.27";
10261
+ const SDK_VERSION = "2.1.31";
10138
10262
  /** Full SDK identifier for headers */
10139
10263
  const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
10140
10264
 
@@ -10283,12 +10407,21 @@ class PersApiClient {
10283
10407
  // Handle 401 errors centrally through AuthService
10284
10408
  if (status === 401) {
10285
10409
  const backendError = ErrorUtils.extractBackendErrorDetails(error);
10410
+ // Check for fatal auth errors that require immediate logout
10411
+ if (this.authService.isFatalAuthError(backendError.code)) {
10412
+ // Definitive auth failure - no retry, session is invalid
10413
+ await this.authService.handleAuthFailure();
10414
+ this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
10415
+ throw new AuthenticationError(errorMessage, endpoint, method, backendError);
10416
+ }
10286
10417
  // If this IS a refresh request that failed with 401, don't retry to avoid infinite loop
10287
10418
  if (options?.isRefreshRequest) {
10419
+ // Refresh failed - this is a real auth failure
10420
+ await this.authService.handleAuthFailure();
10288
10421
  this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
10289
10422
  throw new AuthenticationError(errorMessage, endpoint, method, backendError);
10290
10423
  }
10291
- // For regular requests: try refresh once, then fail
10424
+ // TOKEN_EXPIRED or unknown 401: try refresh once, then retry
10292
10425
  if (retryCount === 0 && this.mergedConfig.authProvider) {
10293
10426
  try {
10294
10427
  const refreshSuccessful = await this.refreshManager.attemptInternalRefresh();
@@ -10299,13 +10432,15 @@ class PersApiClient {
10299
10432
  domain: 'authentication',
10300
10433
  userMessage: 'Authentication token refreshed',
10301
10434
  });
10435
+ // Retry with new token
10302
10436
  return await this.request(method, endpoint, body, { ...options, retryCount: 1 });
10303
10437
  }
10304
10438
  }
10305
10439
  catch (refreshError) {
10306
- // Silent refresh failure
10440
+ // Refresh failed - fall through to handleAuthFailure
10307
10441
  }
10308
10442
  }
10443
+ // All recovery attempts failed
10309
10444
  await this.authService.handleAuthFailure();
10310
10445
  this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
10311
10446
  throw new AuthenticationError(errorMessage, endpoint, method, backendError);
@@ -12266,6 +12401,69 @@ class BusinessManager {
12266
12401
  async getBusinessTypes(options) {
12267
12402
  return this.businessService.getBusinessTypes();
12268
12403
  }
12404
+ /**
12405
+ * Admin: Create a new business type
12406
+ *
12407
+ * Creates a new business type category in the system.
12408
+ * Requires administrator privileges.
12409
+ *
12410
+ * @param data - Business type data (name, description, code, iconUrl)
12411
+ * @returns Promise resolving to the created business type
12412
+ * @throws {PersApiError} When not authenticated as admin or validation fails
12413
+ *
12414
+ * @example
12415
+ * ```typescript
12416
+ * const newType = await sdk.business.createBusinessType({
12417
+ * name: 'Restaurant',
12418
+ * description: 'Food service establishments',
12419
+ * code: 'RESTAURANT',
12420
+ * iconUrl: 'https://example.com/restaurant-icon.png'
12421
+ * });
12422
+ * ```
12423
+ */
12424
+ async createBusinessType(data) {
12425
+ return this.businessApi.createBusinessType(data);
12426
+ }
12427
+ /**
12428
+ * Admin: Update an existing business type
12429
+ *
12430
+ * Updates a business type's properties.
12431
+ * Requires administrator privileges.
12432
+ *
12433
+ * @param data - Updated business type data (must include id)
12434
+ * @returns Promise resolving to the updated business type
12435
+ * @throws {PersApiError} When not authenticated as admin or type not found
12436
+ *
12437
+ * @example
12438
+ * ```typescript
12439
+ * const updated = await sdk.business.updateBusinessType({
12440
+ * id: 123,
12441
+ * name: 'Fine Dining Restaurant',
12442
+ * description: 'Upscale dining establishments'
12443
+ * });
12444
+ * ```
12445
+ */
12446
+ async updateBusinessType(data) {
12447
+ return this.businessApi.updateBusinessType(data);
12448
+ }
12449
+ /**
12450
+ * Admin: Delete a business type
12451
+ *
12452
+ * Deletes a business type. Will fail if type is in use by businesses.
12453
+ * Requires administrator privileges.
12454
+ *
12455
+ * @param id - Business type ID to delete
12456
+ * @returns Promise resolving to true if deleted successfully
12457
+ * @throws {PersApiError} When not authenticated as admin or type not found/in use
12458
+ *
12459
+ * @example
12460
+ * ```typescript
12461
+ * const success = await sdk.business.deleteBusinessType(123);
12462
+ * ```
12463
+ */
12464
+ async deleteBusinessType(id) {
12465
+ return this.businessApi.deleteBusinessType(id);
12466
+ }
12269
12467
  /**
12270
12468
  * Get businesses by type
12271
12469
  *
@@ -14274,6 +14472,77 @@ class RedemptionManager {
14274
14472
  async deleteRedemptionTokenUnit(redemptionId, tokenUnitId) {
14275
14473
  return this.redemptionService.deleteRedemptionTokenUnit(redemptionId, tokenUnitId);
14276
14474
  }
14475
+ // ==========================================
14476
+ // REDEMPTION TYPE MANAGEMENT (Admin)
14477
+ // ==========================================
14478
+ /**
14479
+ * Admin: Create redemption type
14480
+ *
14481
+ * Creates a new redemption type category for organizing redemption offers.
14482
+ * Requires administrator privileges.
14483
+ *
14484
+ * @param data - Redemption type data (name, description, imageUrl)
14485
+ * @returns Promise resolving to created redemption type
14486
+ * @throws {PersApiError} When not authenticated as admin or validation fails
14487
+ *
14488
+ * @example
14489
+ * ```typescript
14490
+ * const newType = await sdk.redemptions.createRedemptionType({
14491
+ * name: 'Premium Rewards',
14492
+ * description: 'High-value exclusive rewards',
14493
+ * imageUrl: 'https://example.com/premium-icon.png'
14494
+ * });
14495
+ *
14496
+ * console.log('Created type:', newType.name);
14497
+ * ```
14498
+ */
14499
+ async createRedemptionType(data) {
14500
+ return this.redemptionService.createRedemptionType(data);
14501
+ }
14502
+ /**
14503
+ * Admin: Update redemption type
14504
+ *
14505
+ * Updates an existing redemption type's properties.
14506
+ * Requires administrator privileges.
14507
+ *
14508
+ * @param data - Updated redemption type data (must include id)
14509
+ * @returns Promise resolving to updated redemption type
14510
+ * @throws {PersApiError} When not authenticated as admin or type not found
14511
+ *
14512
+ * @example
14513
+ * ```typescript
14514
+ * const updated = await sdk.redemptions.updateRedemptionType({
14515
+ * id: 123,
14516
+ * name: 'VIP Rewards',
14517
+ * description: 'Updated description',
14518
+ * imageUrl: 'https://example.com/vip-icon.png'
14519
+ * });
14520
+ *
14521
+ * console.log('Updated type:', updated.name);
14522
+ * ```
14523
+ */
14524
+ async updateRedemptionType(data) {
14525
+ return this.redemptionService.updateRedemptionType(data);
14526
+ }
14527
+ /**
14528
+ * Admin: Delete redemption type
14529
+ *
14530
+ * Deletes a redemption type. Will fail if type is in use by redemptions.
14531
+ * Requires administrator privileges.
14532
+ *
14533
+ * @param id - Redemption type ID to delete
14534
+ * @returns Promise resolving to true if deleted successfully
14535
+ * @throws {PersApiError} When not authenticated as admin or type not found/in use
14536
+ *
14537
+ * @example
14538
+ * ```typescript
14539
+ * const success = await sdk.redemptions.deleteRedemptionType(123);
14540
+ * console.log('Deleted:', success);
14541
+ * ```
14542
+ */
14543
+ async deleteRedemptionType(id) {
14544
+ return this.redemptionService.deleteRedemptionType(id);
14545
+ }
14277
14546
  /**
14278
14547
  * Get the full redemption service for advanced operations
14279
14548
  *
@@ -19429,15 +19698,6 @@ function createReactNativeAuthProvider(projectKey, config = {}) {
19429
19698
  enabled: dpop.enabled ?? true,
19430
19699
  cryptoProvider: dpop.cryptoProvider
19431
19700
  } : 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
19701
  // Return DefaultAuthProvider configured with platform-appropriate storage
19442
19702
  return new DefaultAuthProvider({
19443
19703
  authType,
@@ -19764,13 +20024,6 @@ const PersSDKProvider = ({ children, config }) => {
19764
20024
  if (reactNative.Platform.OS !== 'web' && isDpopEnabled && !dpopSettings.cryptoProvider) {
19765
20025
  dpopSettings.cryptoProvider = new ReactNativeDPoPProvider();
19766
20026
  }
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
20027
  // Create platform-aware auth provider if not provided
19775
20028
  let authProvider;
19776
20029
  if (config.authProvider) {
@@ -40279,6 +40532,13 @@ class MetadataDomainService {
40279
40532
  const cacheKey = `resolve:${url}`;
40280
40533
  return this.cache.getOrSet(cacheKey, () => this.ipfsApi.resolveIPFSUrl(url), CacheTTL.GATEWAY);
40281
40534
  }
40535
+ /**
40536
+ * Get IPFS gateway domain from tenant configuration.
40537
+ * Useful for pipes that need to resolve URLs synchronously after initial fetch.
40538
+ */
40539
+ async getIpfsGatewayDomain() {
40540
+ return this.ipfsApi.getIpfsGatewayDomain();
40541
+ }
40282
40542
  /**
40283
40543
  * Clear all cached metadata and URLs for this service
40284
40544
  */
@@ -40384,6 +40644,15 @@ class Web3ApplicationService {
40384
40644
  async resolveIPFSUrl(url) {
40385
40645
  return this.metadataDomainService.resolveIPFSUrl(url);
40386
40646
  }
40647
+ /**
40648
+ * Get IPFS gateway domain from tenant configuration.
40649
+ * Useful for pipes that need to resolve URLs synchronously after initial fetch.
40650
+ *
40651
+ * @returns Gateway domain (e.g., 'pers.mypinata.cloud')
40652
+ */
40653
+ async getIpfsGatewayDomain() {
40654
+ return this.metadataDomainService.getIpfsGatewayDomain();
40655
+ }
40387
40656
  /**
40388
40657
  * Fetch and process metadata from any URI with IPFS conversion.
40389
40658
  *
@@ -40504,25 +40773,26 @@ class IPFSInfrastructureApi {
40504
40773
  throw new Error(`HTTP error! status: ${response.status}`);
40505
40774
  }
40506
40775
  const rawMetadata = await response.json();
40507
- // Resolve IPFS URLs for media fields
40508
- const resolvedImageUrl = rawMetadata.image
40509
- ? await this.resolveIPFSUrl(rawMetadata.image)
40510
- : '';
40511
- const resolvedAnimationUrl = rawMetadata.animation_url
40512
- ? await this.resolveIPFSUrl(rawMetadata.animation_url)
40513
- : undefined;
40776
+ // Resolve any IPFS URLs in string fields
40777
+ for (const key of Object.keys(rawMetadata)) {
40778
+ const value = rawMetadata[key];
40779
+ if (typeof value === 'string' && value.startsWith('ipfs://')) {
40780
+ rawMetadata[key] = await this.resolveIPFSUrl(value);
40781
+ }
40782
+ }
40514
40783
  // Extract custom properties (anything not in ERC standard)
40515
40784
  const customProperties = Object.fromEntries(Object.entries(rawMetadata).filter(([key]) => !['name', 'description', 'image', 'animation_url', 'external_url', 'attributes'].includes(key)));
40516
- // Return normalized SDK format (typed as TokenMetadata)
40517
- return {
40518
- name: rawMetadata.name || '',
40519
- description: rawMetadata.description || '',
40520
- imageUrl: resolvedImageUrl,
40521
- animationUrl: resolvedAnimationUrl,
40522
- externalUrl: rawMetadata.external_url || undefined,
40523
- attributes: rawMetadata.attributes || [],
40785
+ // Return normalized SDK format
40786
+ const result = {
40787
+ name: String(rawMetadata.name ?? ''),
40788
+ description: String(rawMetadata.description ?? ''),
40789
+ imageUrl: String(rawMetadata.image ?? ''),
40790
+ animationUrl: rawMetadata.animation_url ? String(rawMetadata.animation_url) : undefined,
40791
+ externalUrl: rawMetadata.external_url ? String(rawMetadata.external_url) : undefined,
40792
+ attributes: Array.isArray(rawMetadata.attributes) ? rawMetadata.attributes : [],
40524
40793
  ...customProperties
40525
40794
  };
40795
+ return result;
40526
40796
  }
40527
40797
  catch (error) {
40528
40798
  console.error('Error fetching metadata:', error);
@@ -40645,8 +40915,20 @@ async function getExplorerUrlByChainId(getChainData, chainId, address, type) {
40645
40915
  *
40646
40916
  * ## IPFS Resolution
40647
40917
  *
40648
- * For IPFS URL resolution, use `sdk.tenant.resolveIPFSUrl()` instead.
40649
- * IPFS is chain-agnostic and configured at the tenant level.
40918
+ * Use `sdk.web3.resolveIPFSUrl()` to resolve IPFS URLs to HTTPS:
40919
+ *
40920
+ * ```typescript
40921
+ * const httpsUrl = await sdk.web3.resolveIPFSUrl('ipfs://QmXxx.../image.png');
40922
+ * // Returns: 'https://pers.mypinata.cloud/ipfs/QmXxx.../image.png'
40923
+ * ```
40924
+ *
40925
+ * Or fetch full metadata with resolved URLs:
40926
+ *
40927
+ * ```typescript
40928
+ * const metadata = await sdk.web3.fetchAndProcessMetadata('ipfs://QmXxx.../metadata.json');
40929
+ * // All IPFS URLs in metadata are resolved to HTTPS
40930
+ * // Returns SDK format: { imageUrl, animationUrl, externalUrl, ... }
40931
+ * ```
40650
40932
  */
40651
40933
  class Web3Manager {
40652
40934
  // TODO: Add PersEventEmitter support for blockchain events
@@ -40704,6 +40986,45 @@ class Web3Manager {
40704
40986
  async fetchAndProcessMetadata(tokenUri) {
40705
40987
  return this.web3ApplicationService.fetchAndProcessMetadata(tokenUri);
40706
40988
  }
40989
+ // ==========================================
40990
+ // IPFS OPERATIONS
40991
+ // ==========================================
40992
+ /**
40993
+ * Resolve IPFS URL to HTTPS URL using tenant's configured gateway.
40994
+ *
40995
+ * @param url - URL to resolve (can be ipfs:// or https://)
40996
+ * @returns Resolved HTTPS URL
40997
+ * @throws Error if tenant's ipfsGatewayDomain is not configured
40998
+ *
40999
+ * @example
41000
+ * ```typescript
41001
+ * const imageUrl = await sdk.web3.resolveIPFSUrl('ipfs://QmXxx.../image.png');
41002
+ * // Returns: 'https://pers.mypinata.cloud/ipfs/QmXxx.../image.png'
41003
+ *
41004
+ * // Non-IPFS URLs pass through unchanged
41005
+ * const httpUrl = await sdk.web3.resolveIPFSUrl('https://example.com/image.png');
41006
+ * // Returns: 'https://example.com/image.png'
41007
+ * ```
41008
+ */
41009
+ async resolveIPFSUrl(url) {
41010
+ return this.web3ApplicationService.resolveIPFSUrl(url);
41011
+ }
41012
+ /**
41013
+ * Get IPFS gateway domain from tenant configuration.
41014
+ * Useful for pipes that need to resolve URLs synchronously after initial fetch.
41015
+ *
41016
+ * @returns Gateway domain (e.g., 'pers.mypinata.cloud')
41017
+ * @throws Error if tenant's ipfsGatewayDomain is not configured
41018
+ *
41019
+ * @example
41020
+ * ```typescript
41021
+ * const gateway = await sdk.web3.getIpfsGatewayDomain();
41022
+ * // Use gateway for sync resolution: `https://${gateway}/ipfs/${cid}`
41023
+ * ```
41024
+ */
41025
+ async getIpfsGatewayDomain() {
41026
+ return this.web3ApplicationService.getIpfsGatewayDomain();
41027
+ }
40707
41028
  /**
40708
41029
  * Get blockchain chain data by chain ID
40709
41030
  *
@@ -41010,23 +41331,32 @@ const useWeb3 = () => {
41010
41331
  /**
41011
41332
  * Resolve IPFS URL to HTTP gateway URL
41012
41333
  *
41013
- * @deprecated Use `sdk.tenant.resolveIPFSUrl()` directly - IPFS is chain-agnostic
41334
+ * Converts an ipfs:// URL to an HTTPS URL using the tenant's configured IPFS gateway.
41335
+ * Non-IPFS URLs are returned unchanged.
41336
+ *
41014
41337
  * @param url - IPFS URL to resolve (ipfs://...)
41015
41338
  * @returns Promise resolving to HTTP gateway URL
41339
+ *
41340
+ * @example
41341
+ * ```typescript
41342
+ * const { resolveIPFSUrl } = useWeb3();
41343
+ * const httpsUrl = await resolveIPFSUrl('ipfs://QmHash...');
41344
+ * // Returns: 'https://gateway.pinata.cloud/ipfs/QmHash...'
41345
+ * ```
41016
41346
  */
41017
41347
  const resolveIPFSUrl = react.useCallback(async (url) => {
41018
- if (!isInitialized || !sdk) {
41348
+ if (!isInitialized || !web3) {
41019
41349
  throw new Error('SDK not initialized. Call initialize() first.');
41020
41350
  }
41021
41351
  try {
41022
- const result = await sdk.tenants.resolveIPFSUrl(url);
41352
+ const result = await web3.resolveIPFSUrl(url);
41023
41353
  return result;
41024
41354
  }
41025
41355
  catch (error) {
41026
41356
  console.error('Failed to resolve IPFS URL:', error);
41027
41357
  throw error;
41028
41358
  }
41029
- }, [sdk, isInitialized]);
41359
+ }, [web3, isInitialized]);
41030
41360
  /**
41031
41361
  * Fetch and process token metadata from a URI
41032
41362
  *
@@ -42680,8 +43010,7 @@ const useTransactions = () => {
42680
43010
  throw new Error('SDK not initialized. Call initialize() first.');
42681
43011
  }
42682
43012
  try {
42683
- const result = await sdk.transactions.getTransactionById(transactionId, include);
42684
- return result;
43013
+ return await sdk.transactions.getTransactionById(transactionId, include);
42685
43014
  }
42686
43015
  catch (error) {
42687
43016
  console.error('Failed to fetch transaction:', error);
@@ -42730,8 +43059,7 @@ const useTransactions = () => {
42730
43059
  throw new Error('SDK not initialized. Call initialize() first.');
42731
43060
  }
42732
43061
  try {
42733
- const result = await sdk.transactions.getUserTransactionHistory(options);
42734
- return result;
43062
+ return await sdk.transactions.getUserTransactionHistory(options);
42735
43063
  }
42736
43064
  catch (error) {
42737
43065
  console.error('Failed to fetch transaction history:', error);
@@ -45201,6 +45529,7 @@ exports.CampaignManager = CampaignManager;
45201
45529
  exports.CampaignService = CampaignService;
45202
45530
  exports.ChainTypes = ChainTypes;
45203
45531
  exports.ClientTransactionType = ClientTransactionType;
45532
+ exports.CommonErrorCodes = CommonErrorCodes;
45204
45533
  exports.DEFAULT_PERS_CONFIG = DEFAULT_PERS_CONFIG;
45205
45534
  exports.DPOP_STORAGE_KEYS = DPOP_STORAGE_KEYS;
45206
45535
  exports.DPoPManager = DPoPManager;