@explorins/pers-sdk-react-native 2.1.19 → 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
  }
@@ -9948,11 +10081,15 @@ class DefaultAuthProvider {
9948
10081
  this.config = config;
9949
10082
  this.authTypeCache = null;
9950
10083
  this.authType = config.authType || exports.AccountOwnerType.USER;
9951
- // Determine if DPoP can be enabled (requires IndexedDB for key persistence)
9952
- const canUseDPoP = this.canEnableDPoP();
10084
+ // Determine if DPoP can be enabled:
10085
+ // 1. Custom crypto provider is supplied (e.g., React Native), OR
10086
+ // 2. IndexedDB is available for browser CryptoKey persistence
10087
+ const hasCustomCryptoProvider = !!config.dpop?.cryptoProvider;
10088
+ const hasIndexedDB = this.canEnableDPoP();
10089
+ const canUseDPoP = hasCustomCryptoProvider || hasIndexedDB;
9953
10090
  this.dpopEnabled = !!(config.dpop?.enabled && canUseDPoP);
9954
10091
  if (config.dpop?.enabled && !canUseDPoP) {
9955
- console.warn('[DefaultAuthProvider] DPoP disabled: IndexedDB not available. CryptoKey persistence requires IndexedDB.');
10092
+ console.warn('[DefaultAuthProvider] DPoP disabled: IndexedDB not available and no custom crypto provider supplied. CryptoKey persistence requires IndexedDB or a custom provider.');
9956
10093
  }
9957
10094
  const storage = config.storage || this.createStorage();
9958
10095
  this.tokenManager = new AuthTokenManager(storage);
@@ -10121,7 +10258,7 @@ class DefaultAuthProvider {
10121
10258
  /** SDK package name */
10122
10259
  const SDK_NAME = "@explorins/pers-sdk";
10123
10260
  /** SDK version - injected from package.json at build time */
10124
- const SDK_VERSION = "2.1.27";
10261
+ const SDK_VERSION = "2.1.31";
10125
10262
  /** Full SDK identifier for headers */
10126
10263
  const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
10127
10264
 
@@ -10270,12 +10407,21 @@ class PersApiClient {
10270
10407
  // Handle 401 errors centrally through AuthService
10271
10408
  if (status === 401) {
10272
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
+ }
10273
10417
  // If this IS a refresh request that failed with 401, don't retry to avoid infinite loop
10274
10418
  if (options?.isRefreshRequest) {
10419
+ // Refresh failed - this is a real auth failure
10420
+ await this.authService.handleAuthFailure();
10275
10421
  this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
10276
10422
  throw new AuthenticationError(errorMessage, endpoint, method, backendError);
10277
10423
  }
10278
- // For regular requests: try refresh once, then fail
10424
+ // TOKEN_EXPIRED or unknown 401: try refresh once, then retry
10279
10425
  if (retryCount === 0 && this.mergedConfig.authProvider) {
10280
10426
  try {
10281
10427
  const refreshSuccessful = await this.refreshManager.attemptInternalRefresh();
@@ -10286,13 +10432,15 @@ class PersApiClient {
10286
10432
  domain: 'authentication',
10287
10433
  userMessage: 'Authentication token refreshed',
10288
10434
  });
10435
+ // Retry with new token
10289
10436
  return await this.request(method, endpoint, body, { ...options, retryCount: 1 });
10290
10437
  }
10291
10438
  }
10292
10439
  catch (refreshError) {
10293
- // Silent refresh failure
10440
+ // Refresh failed - fall through to handleAuthFailure
10294
10441
  }
10295
10442
  }
10443
+ // All recovery attempts failed
10296
10444
  await this.authService.handleAuthFailure();
10297
10445
  this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
10298
10446
  throw new AuthenticationError(errorMessage, endpoint, method, backendError);
@@ -12253,6 +12401,69 @@ class BusinessManager {
12253
12401
  async getBusinessTypes(options) {
12254
12402
  return this.businessService.getBusinessTypes();
12255
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
+ }
12256
12467
  /**
12257
12468
  * Get businesses by type
12258
12469
  *
@@ -14261,6 +14472,77 @@ class RedemptionManager {
14261
14472
  async deleteRedemptionTokenUnit(redemptionId, tokenUnitId) {
14262
14473
  return this.redemptionService.deleteRedemptionTokenUnit(redemptionId, tokenUnitId);
14263
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
+ }
14264
14546
  /**
14265
14547
  * Get the full redemption service for advanced operations
14266
14548
  *
@@ -19416,10 +19698,6 @@ function createReactNativeAuthProvider(projectKey, config = {}) {
19416
19698
  enabled: dpop.enabled ?? true,
19417
19699
  cryptoProvider: dpop.cryptoProvider
19418
19700
  } : undefined;
19419
- // DEBUG LOGGING
19420
- if (debug) {
19421
- console.log('[ReactNativeAuthProvider] Creating provider with DPoP config:', JSON.stringify(dpopConfig));
19422
- }
19423
19701
  // Return DefaultAuthProvider configured with platform-appropriate storage
19424
19702
  return new DefaultAuthProvider({
19425
19703
  authType,
@@ -40254,6 +40532,13 @@ class MetadataDomainService {
40254
40532
  const cacheKey = `resolve:${url}`;
40255
40533
  return this.cache.getOrSet(cacheKey, () => this.ipfsApi.resolveIPFSUrl(url), CacheTTL.GATEWAY);
40256
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
+ }
40257
40542
  /**
40258
40543
  * Clear all cached metadata and URLs for this service
40259
40544
  */
@@ -40359,6 +40644,15 @@ class Web3ApplicationService {
40359
40644
  async resolveIPFSUrl(url) {
40360
40645
  return this.metadataDomainService.resolveIPFSUrl(url);
40361
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
+ }
40362
40656
  /**
40363
40657
  * Fetch and process metadata from any URI with IPFS conversion.
40364
40658
  *
@@ -40479,25 +40773,26 @@ class IPFSInfrastructureApi {
40479
40773
  throw new Error(`HTTP error! status: ${response.status}`);
40480
40774
  }
40481
40775
  const rawMetadata = await response.json();
40482
- // Resolve IPFS URLs for media fields
40483
- const resolvedImageUrl = rawMetadata.image
40484
- ? await this.resolveIPFSUrl(rawMetadata.image)
40485
- : '';
40486
- const resolvedAnimationUrl = rawMetadata.animation_url
40487
- ? await this.resolveIPFSUrl(rawMetadata.animation_url)
40488
- : 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
+ }
40489
40783
  // Extract custom properties (anything not in ERC standard)
40490
40784
  const customProperties = Object.fromEntries(Object.entries(rawMetadata).filter(([key]) => !['name', 'description', 'image', 'animation_url', 'external_url', 'attributes'].includes(key)));
40491
- // Return normalized SDK format (typed as TokenMetadata)
40492
- return {
40493
- name: rawMetadata.name || '',
40494
- description: rawMetadata.description || '',
40495
- imageUrl: resolvedImageUrl,
40496
- animationUrl: resolvedAnimationUrl,
40497
- externalUrl: rawMetadata.external_url || undefined,
40498
- 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 : [],
40499
40793
  ...customProperties
40500
40794
  };
40795
+ return result;
40501
40796
  }
40502
40797
  catch (error) {
40503
40798
  console.error('Error fetching metadata:', error);
@@ -40620,8 +40915,20 @@ async function getExplorerUrlByChainId(getChainData, chainId, address, type) {
40620
40915
  *
40621
40916
  * ## IPFS Resolution
40622
40917
  *
40623
- * For IPFS URL resolution, use `sdk.tenant.resolveIPFSUrl()` instead.
40624
- * 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
+ * ```
40625
40932
  */
40626
40933
  class Web3Manager {
40627
40934
  // TODO: Add PersEventEmitter support for blockchain events
@@ -40679,6 +40986,45 @@ class Web3Manager {
40679
40986
  async fetchAndProcessMetadata(tokenUri) {
40680
40987
  return this.web3ApplicationService.fetchAndProcessMetadata(tokenUri);
40681
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
+ }
40682
41028
  /**
40683
41029
  * Get blockchain chain data by chain ID
40684
41030
  *
@@ -40985,23 +41331,32 @@ const useWeb3 = () => {
40985
41331
  /**
40986
41332
  * Resolve IPFS URL to HTTP gateway URL
40987
41333
  *
40988
- * @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
+ *
40989
41337
  * @param url - IPFS URL to resolve (ipfs://...)
40990
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
+ * ```
40991
41346
  */
40992
41347
  const resolveIPFSUrl = react.useCallback(async (url) => {
40993
- if (!isInitialized || !sdk) {
41348
+ if (!isInitialized || !web3) {
40994
41349
  throw new Error('SDK not initialized. Call initialize() first.');
40995
41350
  }
40996
41351
  try {
40997
- const result = await sdk.tenants.resolveIPFSUrl(url);
41352
+ const result = await web3.resolveIPFSUrl(url);
40998
41353
  return result;
40999
41354
  }
41000
41355
  catch (error) {
41001
41356
  console.error('Failed to resolve IPFS URL:', error);
41002
41357
  throw error;
41003
41358
  }
41004
- }, [sdk, isInitialized]);
41359
+ }, [web3, isInitialized]);
41005
41360
  /**
41006
41361
  * Fetch and process token metadata from a URI
41007
41362
  *
@@ -42655,8 +43010,7 @@ const useTransactions = () => {
42655
43010
  throw new Error('SDK not initialized. Call initialize() first.');
42656
43011
  }
42657
43012
  try {
42658
- const result = await sdk.transactions.getTransactionById(transactionId, include);
42659
- return result;
43013
+ return await sdk.transactions.getTransactionById(transactionId, include);
42660
43014
  }
42661
43015
  catch (error) {
42662
43016
  console.error('Failed to fetch transaction:', error);
@@ -42705,8 +43059,7 @@ const useTransactions = () => {
42705
43059
  throw new Error('SDK not initialized. Call initialize() first.');
42706
43060
  }
42707
43061
  try {
42708
- const result = await sdk.transactions.getUserTransactionHistory(options);
42709
- return result;
43062
+ return await sdk.transactions.getUserTransactionHistory(options);
42710
43063
  }
42711
43064
  catch (error) {
42712
43065
  console.error('Failed to fetch transaction history:', error);
@@ -45176,6 +45529,7 @@ exports.CampaignManager = CampaignManager;
45176
45529
  exports.CampaignService = CampaignService;
45177
45530
  exports.ChainTypes = ChainTypes;
45178
45531
  exports.ClientTransactionType = ClientTransactionType;
45532
+ exports.CommonErrorCodes = CommonErrorCodes;
45179
45533
  exports.DEFAULT_PERS_CONFIG = DEFAULT_PERS_CONFIG;
45180
45534
  exports.DPOP_STORAGE_KEYS = DPOP_STORAGE_KEYS;
45181
45535
  exports.DPoPManager = DPoPManager;