@explorins/pers-sdk-react-native 2.1.5 → 2.1.6
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/useEvents.d.ts +17 -5
- package/dist/hooks/useEvents.d.ts.map +1 -1
- package/dist/hooks/useEvents.js +17 -5
- package/dist/hooks/useTokenBalances.d.ts +24 -0
- package/dist/hooks/useTokenBalances.d.ts.map +1 -1
- package/dist/hooks/useTokenBalances.js +42 -2
- package/dist/hooks/useUsers.d.ts +5 -4
- package/dist/hooks/useUsers.d.ts.map +1 -1
- package/dist/hooks/useUsers.js +47 -8
- package/dist/hooks/useWeb3.d.ts +6 -5
- package/dist/hooks/useWeb3.d.ts.map +1 -1
- package/dist/hooks/useWeb3.js +23 -10
- package/dist/index.js +3041 -500
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +38 -0
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +29 -1
- package/package.json +3 -2
- package/src/hooks/useEvents.ts +17 -5
- package/src/hooks/useTokenBalances.ts +60 -2
- package/src/hooks/useUsers.ts +51 -9
- package/src/hooks/useWeb3.ts +28 -13
- package/src/providers/PersSDKProvider.tsx +38 -1
package/dist/index.js
CHANGED
|
@@ -2593,6 +2593,8 @@ exports.TransactionStatus = void 0;
|
|
|
2593
2593
|
TransactionStatus["PROCESSING"] = "processing";
|
|
2594
2594
|
// when transaction is pending to be signed
|
|
2595
2595
|
TransactionStatus["PENDING_SIGNATURE"] = "pending_signature";
|
|
2596
|
+
// when transaction is signed but pending submission (sign-only flow)
|
|
2597
|
+
TransactionStatus["PENDING_SUBMISSION"] = "pending_submission";
|
|
2596
2598
|
// after transaction has been broadcasted
|
|
2597
2599
|
TransactionStatus["BROADCASTED"] = "broadcasted";
|
|
2598
2600
|
// after transaction has succeeded
|
|
@@ -2634,6 +2636,7 @@ exports.ApiKeyType = void 0;
|
|
|
2634
2636
|
ApiKeyType["BLOCKCHAIN_WRITER_JWT"] = "BLOCKCHAIN_WRITER_JWT";
|
|
2635
2637
|
ApiKeyType["BLOCKCHAIN_READER_JWT"] = "BLOCKCHAIN_READER_JWT";
|
|
2636
2638
|
ApiKeyType["TRANSACTION_JWT_ACCESS_TOKEN"] = "TRANSACTION_JWT_ACCESS_TOKEN";
|
|
2639
|
+
ApiKeyType["WEBHOOK_OUTBOUND_JWT"] = "WEBHOOK_OUTBOUND_JWT";
|
|
2637
2640
|
// TODO: this needs to be removed. However, there is still dependency
|
|
2638
2641
|
ApiKeyType["TENANT_LEGACY_ADMIN"] = "TENANT_ADMIN_JWT";
|
|
2639
2642
|
// TENANT_SYSTEM_API_SECRET = 'TENANT_SYSTEM_API_SECRET',
|
|
@@ -2850,6 +2853,66 @@ exports.ProcessRecordStatus = void 0;
|
|
|
2850
2853
|
ProcessRecordStatus["FAILED"] = "FAILED"; // Transaction processing failed (blockchain or validation error)
|
|
2851
2854
|
})(exports.ProcessRecordStatus || (exports.ProcessRecordStatus = {}));
|
|
2852
2855
|
|
|
2856
|
+
/**
|
|
2857
|
+
* Entity types that support file uploads in the storage system.
|
|
2858
|
+
* Used for organizing uploaded files by entity category.
|
|
2859
|
+
*/
|
|
2860
|
+
exports.FileUploadEntityType = void 0;
|
|
2861
|
+
(function (FileUploadEntityType) {
|
|
2862
|
+
FileUploadEntityType["TOKEN"] = "token";
|
|
2863
|
+
FileUploadEntityType["CAMPAIGN"] = "campaign";
|
|
2864
|
+
FileUploadEntityType["REDEMPTION"] = "redemption";
|
|
2865
|
+
FileUploadEntityType["BUSINESS"] = "business";
|
|
2866
|
+
FileUploadEntityType["TENANT"] = "tenant";
|
|
2867
|
+
FileUploadEntityType["USER"] = "user";
|
|
2868
|
+
})(exports.FileUploadEntityType || (exports.FileUploadEntityType = {}));
|
|
2869
|
+
|
|
2870
|
+
/**
|
|
2871
|
+
* Types of signed URLs for storage operations.
|
|
2872
|
+
* GET: For downloading/reading files
|
|
2873
|
+
* PUT: For uploading/writing files
|
|
2874
|
+
*/
|
|
2875
|
+
exports.SignedUrlType = void 0;
|
|
2876
|
+
(function (SignedUrlType) {
|
|
2877
|
+
SignedUrlType["GET"] = "GET";
|
|
2878
|
+
SignedUrlType["PUT"] = "PUT";
|
|
2879
|
+
})(exports.SignedUrlType || (exports.SignedUrlType = {}));
|
|
2880
|
+
|
|
2881
|
+
/**
|
|
2882
|
+
* Token Validity Type Enum
|
|
2883
|
+
*
|
|
2884
|
+
* Defines how token expiry is calculated:
|
|
2885
|
+
* - FIXED_DATE: Uses expiryDate directly as absolute expiration
|
|
2886
|
+
* - DAYS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration days
|
|
2887
|
+
* - HOURS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration hours
|
|
2888
|
+
* - MONTHS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration months
|
|
2889
|
+
* - END_OF_MONTH: Token expires at end of the month when issued
|
|
2890
|
+
* - END_OF_YEAR: Token expires at end of the year when issued
|
|
2891
|
+
*/
|
|
2892
|
+
const TokenValidityTypes = {
|
|
2893
|
+
FIXED_DATE: 'fixed_date',
|
|
2894
|
+
DAYS_FROM_ISSUANCE: 'days_from_issuance',
|
|
2895
|
+
HOURS_FROM_ISSUANCE: 'hours_from_issuance',
|
|
2896
|
+
MONTHS_FROM_ISSUANCE: 'months_from_issuance',
|
|
2897
|
+
END_OF_MONTH: 'end_of_month',
|
|
2898
|
+
END_OF_YEAR: 'end_of_year',
|
|
2899
|
+
};
|
|
2900
|
+
const TOKEN_VALIDITY_TYPE_VALUES = Object.values(TokenValidityTypes);
|
|
2901
|
+
|
|
2902
|
+
/**
|
|
2903
|
+
* Balance Migration Type
|
|
2904
|
+
*
|
|
2905
|
+
* Defines how token balances should be migrated between accounts.
|
|
2906
|
+
* Used during user merge operations and account balance transfers.
|
|
2907
|
+
*/
|
|
2908
|
+
exports.BalanceMigrationType = void 0;
|
|
2909
|
+
(function (BalanceMigrationType) {
|
|
2910
|
+
/** Replace: New balance = from balance - to balance (diff only) */
|
|
2911
|
+
BalanceMigrationType["REPLACE"] = "replace";
|
|
2912
|
+
/** Merge: New balance = from balance (full transfer) */
|
|
2913
|
+
BalanceMigrationType["MERGE"] = "merge";
|
|
2914
|
+
})(exports.BalanceMigrationType || (exports.BalanceMigrationType = {}));
|
|
2915
|
+
|
|
2853
2916
|
exports.PurchaseStatus = void 0;
|
|
2854
2917
|
(function (PurchaseStatus) {
|
|
2855
2918
|
// after creation of payment and before payment is done by user
|
|
@@ -2911,6 +2974,208 @@ exports.CampaignTriggerType = void 0;
|
|
|
2911
2974
|
CampaignTriggerType["CLAIM_BY_BUSINESS"] = "CLAIM_BY_BUSINESS";
|
|
2912
2975
|
})(exports.CampaignTriggerType || (exports.CampaignTriggerType = {}));
|
|
2913
2976
|
|
|
2977
|
+
/**
|
|
2978
|
+
* AI Operation Types
|
|
2979
|
+
*
|
|
2980
|
+
* Categorizes AI generation operations for analytics and billing.
|
|
2981
|
+
* Used across AI module and Analytics module.
|
|
2982
|
+
*/
|
|
2983
|
+
exports.AiOperationType = void 0;
|
|
2984
|
+
(function (AiOperationType) {
|
|
2985
|
+
/** Text generation using Gemini models */
|
|
2986
|
+
AiOperationType["TEXT_GENERATION"] = "TEXT_GENERATION";
|
|
2987
|
+
/** Text generation with streaming response */
|
|
2988
|
+
AiOperationType["TEXT_GENERATION_STREAM"] = "TEXT_GENERATION_STREAM";
|
|
2989
|
+
/** Text generation with reasoning/thinking (Gemini 2.5+) */
|
|
2990
|
+
AiOperationType["TEXT_WITH_REASONING"] = "TEXT_WITH_REASONING";
|
|
2991
|
+
/** Image generation using Imagen models */
|
|
2992
|
+
AiOperationType["IMAGE_GENERATION"] = "IMAGE_GENERATION";
|
|
2993
|
+
/** Structured output generation with schema */
|
|
2994
|
+
AiOperationType["STRUCTURED_OUTPUT"] = "STRUCTURED_OUTPUT";
|
|
2995
|
+
/** Multimodal input processing (text + images) */
|
|
2996
|
+
AiOperationType["MULTIMODAL"] = "MULTIMODAL";
|
|
2997
|
+
})(exports.AiOperationType || (exports.AiOperationType = {}));
|
|
2998
|
+
|
|
2999
|
+
/**
|
|
3000
|
+
* AI Trigger Process Types
|
|
3001
|
+
*
|
|
3002
|
+
* Identifies which business process triggered an AI generation.
|
|
3003
|
+
* Used for cost allocation and analytics grouping.
|
|
3004
|
+
*
|
|
3005
|
+
* NOTE: This enum will grow as AI is integrated into more processes.
|
|
3006
|
+
*/
|
|
3007
|
+
exports.AiTriggerProcessType = void 0;
|
|
3008
|
+
(function (AiTriggerProcessType) {
|
|
3009
|
+
/** AI triggered during transaction processing (e.g., dynamic token image/description) */
|
|
3010
|
+
AiTriggerProcessType["TRANSACTION"] = "TRANSACTION";
|
|
3011
|
+
})(exports.AiTriggerProcessType || (exports.AiTriggerProcessType = {}));
|
|
3012
|
+
|
|
3013
|
+
/**
|
|
3014
|
+
* AI Analytics Metric Enum
|
|
3015
|
+
*
|
|
3016
|
+
* Available metrics for AI analytics aggregation and sorting.
|
|
3017
|
+
*/
|
|
3018
|
+
exports.AiAnalyticsMetric = void 0;
|
|
3019
|
+
(function (AiAnalyticsMetric) {
|
|
3020
|
+
/** Count of generations */
|
|
3021
|
+
AiAnalyticsMetric["COUNT"] = "count";
|
|
3022
|
+
/** Total tokens (input + output) */
|
|
3023
|
+
AiAnalyticsMetric["TOTAL_TOKENS"] = "totalTokens";
|
|
3024
|
+
/** Input tokens */
|
|
3025
|
+
AiAnalyticsMetric["INPUT_TOKENS"] = "inputTokens";
|
|
3026
|
+
/** Output tokens */
|
|
3027
|
+
AiAnalyticsMetric["OUTPUT_TOKENS"] = "outputTokens";
|
|
3028
|
+
/** Total estimated cost in USD */
|
|
3029
|
+
AiAnalyticsMetric["TOTAL_COST_USD"] = "totalCostUsd";
|
|
3030
|
+
/** Average response time in milliseconds */
|
|
3031
|
+
AiAnalyticsMetric["AVG_RESPONSE_TIME_MS"] = "avgResponseTimeMs";
|
|
3032
|
+
})(exports.AiAnalyticsMetric || (exports.AiAnalyticsMetric = {}));
|
|
3033
|
+
/**
|
|
3034
|
+
* AI Analytics Group By Enum
|
|
3035
|
+
*
|
|
3036
|
+
* Available fields for grouping AI analytics results.
|
|
3037
|
+
*/
|
|
3038
|
+
exports.AiAnalyticsGroupBy = void 0;
|
|
3039
|
+
(function (AiAnalyticsGroupBy) {
|
|
3040
|
+
/** Group by day */
|
|
3041
|
+
AiAnalyticsGroupBy["DAY"] = "day";
|
|
3042
|
+
/** Group by week */
|
|
3043
|
+
AiAnalyticsGroupBy["WEEK"] = "week";
|
|
3044
|
+
/** Group by month */
|
|
3045
|
+
AiAnalyticsGroupBy["MONTH"] = "month";
|
|
3046
|
+
/** Group by AI model */
|
|
3047
|
+
AiAnalyticsGroupBy["MODEL"] = "model";
|
|
3048
|
+
/** Group by operation type */
|
|
3049
|
+
AiAnalyticsGroupBy["OPERATION_TYPE"] = "operationType";
|
|
3050
|
+
/** Group by trigger process type */
|
|
3051
|
+
AiAnalyticsGroupBy["TRIGGER_PROCESS_TYPE"] = "triggerProcessType";
|
|
3052
|
+
/** Group by user ID */
|
|
3053
|
+
AiAnalyticsGroupBy["USER_ID"] = "userId";
|
|
3054
|
+
/** Group by success status */
|
|
3055
|
+
AiAnalyticsGroupBy["SUCCESS"] = "success";
|
|
3056
|
+
})(exports.AiAnalyticsGroupBy || (exports.AiAnalyticsGroupBy = {}));
|
|
3057
|
+
|
|
3058
|
+
/**
|
|
3059
|
+
* Webhook-related enums
|
|
3060
|
+
* Single source of truth for webhook types across the application
|
|
3061
|
+
*/
|
|
3062
|
+
/**
|
|
3063
|
+
* HTTP methods allowed for webhook forwarding
|
|
3064
|
+
*/
|
|
3065
|
+
exports.WebhookMethod = void 0;
|
|
3066
|
+
(function (WebhookMethod) {
|
|
3067
|
+
WebhookMethod["GET"] = "GET";
|
|
3068
|
+
WebhookMethod["POST"] = "POST";
|
|
3069
|
+
WebhookMethod["PUT"] = "PUT";
|
|
3070
|
+
WebhookMethod["PATCH"] = "PATCH";
|
|
3071
|
+
WebhookMethod["DELETE"] = "DELETE";
|
|
3072
|
+
})(exports.WebhookMethod || (exports.WebhookMethod = {}));
|
|
3073
|
+
/**
|
|
3074
|
+
* Authentication types for outbound webhook forwarding
|
|
3075
|
+
*/
|
|
3076
|
+
exports.WebhookAuthType = void 0;
|
|
3077
|
+
(function (WebhookAuthType) {
|
|
3078
|
+
WebhookAuthType["NONE"] = "NONE";
|
|
3079
|
+
WebhookAuthType["BEARER"] = "BEARER";
|
|
3080
|
+
WebhookAuthType["JWT"] = "JWT";
|
|
3081
|
+
})(exports.WebhookAuthType || (exports.WebhookAuthType = {}));
|
|
3082
|
+
/**
|
|
3083
|
+
* Sources that can invoke a webhook
|
|
3084
|
+
*/
|
|
3085
|
+
exports.WebhookSource = void 0;
|
|
3086
|
+
(function (WebhookSource) {
|
|
3087
|
+
// Internal authenticated sources
|
|
3088
|
+
WebhookSource["USER"] = "USER";
|
|
3089
|
+
WebhookSource["BUSINESS"] = "BUSINESS";
|
|
3090
|
+
WebhookSource["TENANT"] = "TENANT";
|
|
3091
|
+
// External verified sources
|
|
3092
|
+
WebhookSource["STRIPE"] = "STRIPE";
|
|
3093
|
+
// Allow any external traffic (no verification)
|
|
3094
|
+
WebhookSource["ANY"] = "ANY";
|
|
3095
|
+
})(exports.WebhookSource || (exports.WebhookSource = {}));
|
|
3096
|
+
/**
|
|
3097
|
+
* Webhook execution status
|
|
3098
|
+
*/
|
|
3099
|
+
exports.WebhookExecutionStatus = void 0;
|
|
3100
|
+
(function (WebhookExecutionStatus) {
|
|
3101
|
+
WebhookExecutionStatus["PENDING"] = "PENDING";
|
|
3102
|
+
WebhookExecutionStatus["SUCCESS"] = "SUCCESS";
|
|
3103
|
+
WebhookExecutionStatus["FAILED"] = "FAILED";
|
|
3104
|
+
WebhookExecutionStatus["BLOCKED"] = "BLOCKED";
|
|
3105
|
+
})(exports.WebhookExecutionStatus || (exports.WebhookExecutionStatus = {}));
|
|
3106
|
+
/**
|
|
3107
|
+
* Webhook callback status (from external workflow engine)
|
|
3108
|
+
*/
|
|
3109
|
+
exports.WebhookCallbackStatus = void 0;
|
|
3110
|
+
(function (WebhookCallbackStatus) {
|
|
3111
|
+
/** No callback received yet */
|
|
3112
|
+
WebhookCallbackStatus["NONE"] = "NONE";
|
|
3113
|
+
/** Workflow completed successfully */
|
|
3114
|
+
WebhookCallbackStatus["SUCCESS"] = "SUCCESS";
|
|
3115
|
+
/** Workflow failed */
|
|
3116
|
+
WebhookCallbackStatus["FAILED"] = "FAILED";
|
|
3117
|
+
/** Workflow was cancelled */
|
|
3118
|
+
WebhookCallbackStatus["CANCELLED"] = "CANCELLED";
|
|
3119
|
+
})(exports.WebhookCallbackStatus || (exports.WebhookCallbackStatus = {}));
|
|
3120
|
+
|
|
3121
|
+
/**
|
|
3122
|
+
* Core Entity Types
|
|
3123
|
+
*
|
|
3124
|
+
* Single source of truth for entity type identifiers used across the system.
|
|
3125
|
+
* Other type consts (ErrorDomains, AuditEntityTypes) extend from this base.
|
|
3126
|
+
*/
|
|
3127
|
+
const EntityTypes = {
|
|
3128
|
+
// Core entities
|
|
3129
|
+
USER: 'user',
|
|
3130
|
+
ADMIN: 'admin',
|
|
3131
|
+
TENANT: 'tenant',
|
|
3132
|
+
// Token entities
|
|
3133
|
+
TOKEN: 'token',
|
|
3134
|
+
TOKEN_METADATA: 'token_metadata',
|
|
3135
|
+
TOKEN_TYPE: 'token_type',
|
|
3136
|
+
// Financial entities
|
|
3137
|
+
WALLET: 'wallet',
|
|
3138
|
+
BALANCE: 'balance',
|
|
3139
|
+
TRANSACTION: 'transaction',
|
|
3140
|
+
PURCHASE: 'purchase',
|
|
3141
|
+
// Business entities
|
|
3142
|
+
BUSINESS: 'business',
|
|
3143
|
+
BUSINESS_TYPE: 'business_type',
|
|
3144
|
+
// Campaign entities
|
|
3145
|
+
CAMPAIGN: 'campaign',
|
|
3146
|
+
CAMPAIGN_TRIGGER: 'campaign_trigger',
|
|
3147
|
+
// Redemption entities
|
|
3148
|
+
REDEMPTION: 'redemption',
|
|
3149
|
+
REDEMPTION_TYPE: 'redemption_type',
|
|
3150
|
+
// Infrastructure entities
|
|
3151
|
+
CONTRACT: 'contract',
|
|
3152
|
+
SIGNING_ACCOUNT: 'signing_account',
|
|
3153
|
+
API_KEY: 'api_key',
|
|
3154
|
+
WEBHOOK: 'webhook',
|
|
3155
|
+
};
|
|
3156
|
+
/**
|
|
3157
|
+
* Error-specific domains (extends EntityTypes)
|
|
3158
|
+
*
|
|
3159
|
+
* Includes entity types plus error-specific classification domains.
|
|
3160
|
+
*/
|
|
3161
|
+
const ErrorDomains = {
|
|
3162
|
+
...EntityTypes,
|
|
3163
|
+
// Error-specific domains (not entities, but error classifications)
|
|
3164
|
+
VALIDATION: 'validation',
|
|
3165
|
+
SYSTEM: 'system',
|
|
3166
|
+
AUTHENTICATION: 'authentication',
|
|
3167
|
+
EXTERNAL: 'external',
|
|
3168
|
+
};
|
|
3169
|
+
/**
|
|
3170
|
+
* Auditable Entity Types (extends EntityTypes)
|
|
3171
|
+
*
|
|
3172
|
+
* All entities that can be audited. Currently same as EntityTypes,
|
|
3173
|
+
* but can be extended with audit-specific entities if needed.
|
|
3174
|
+
*/
|
|
3175
|
+
const AuditEntityTypes = {
|
|
3176
|
+
...EntityTypes,
|
|
3177
|
+
};
|
|
3178
|
+
|
|
2914
3179
|
/**
|
|
2915
3180
|
* Error categories for classification and handling
|
|
2916
3181
|
*
|
|
@@ -2953,6 +3218,62 @@ exports.ErrorCategory = void 0;
|
|
|
2953
3218
|
ErrorCategory["UNKNOWN"] = "UNKNOWN";
|
|
2954
3219
|
})(exports.ErrorCategory || (exports.ErrorCategory = {}));
|
|
2955
3220
|
|
|
3221
|
+
/**
|
|
3222
|
+
* PERS WS-Relay Shared Types
|
|
3223
|
+
*
|
|
3224
|
+
* Type-safe WebSocket communication types for the PERS event relay system.
|
|
3225
|
+
*
|
|
3226
|
+
* @version 1.2.0
|
|
3227
|
+
*/
|
|
3228
|
+
// =============================================================================
|
|
3229
|
+
// HELPER FUNCTIONS (TYPE GUARDS)
|
|
3230
|
+
// =============================================================================
|
|
3231
|
+
/**
|
|
3232
|
+
* Type guard for server messages
|
|
3233
|
+
*/
|
|
3234
|
+
function isServerMessage(data) {
|
|
3235
|
+
return (typeof data === 'object' &&
|
|
3236
|
+
data !== null &&
|
|
3237
|
+
'type' in data &&
|
|
3238
|
+
'timestamp' in data);
|
|
3239
|
+
}
|
|
3240
|
+
/**
|
|
3241
|
+
* Type guard for event messages
|
|
3242
|
+
*/
|
|
3243
|
+
function isEventMessage(msg) {
|
|
3244
|
+
return msg.type === 'event';
|
|
3245
|
+
}
|
|
3246
|
+
/**
|
|
3247
|
+
* Type guard for connected messages
|
|
3248
|
+
*/
|
|
3249
|
+
function isConnectedMessage(msg) {
|
|
3250
|
+
return msg.type === 'connected';
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Type guard for subscribed messages
|
|
3254
|
+
*/
|
|
3255
|
+
function isSubscribedMessage(msg) {
|
|
3256
|
+
return msg.type === 'subscribed';
|
|
3257
|
+
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Type guard for unsubscribed messages
|
|
3260
|
+
*/
|
|
3261
|
+
function isUnsubscribedMessage(msg) {
|
|
3262
|
+
return msg.type === 'unsubscribed';
|
|
3263
|
+
}
|
|
3264
|
+
/**
|
|
3265
|
+
* Type guard for error messages
|
|
3266
|
+
*/
|
|
3267
|
+
function isErrorMessage(msg) {
|
|
3268
|
+
return msg.type === 'error';
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Type guard for ping messages
|
|
3272
|
+
*/
|
|
3273
|
+
function isPingMessage(msg) {
|
|
3274
|
+
return msg.type === 'ping';
|
|
3275
|
+
}
|
|
3276
|
+
|
|
2956
3277
|
const apiPublicKeyTestPrefix = 'pk_test_';
|
|
2957
3278
|
const testnetPrefix = 'testnet-';
|
|
2958
3279
|
const testnetShortPrefix = 'tn-';
|
|
@@ -3218,6 +3539,42 @@ function isValidRedemptionRedeemRelation(value) {
|
|
|
3218
3539
|
return VALID_REDEMPTION_REDEEM_RELATIONS.includes(value);
|
|
3219
3540
|
}
|
|
3220
3541
|
|
|
3542
|
+
/**
|
|
3543
|
+
* Valid business membership relations for runtime validation
|
|
3544
|
+
*/
|
|
3545
|
+
const VALID_BUSINESS_MEMBERSHIP_RELATIONS = [
|
|
3546
|
+
'user',
|
|
3547
|
+
'business'
|
|
3548
|
+
];
|
|
3549
|
+
/**
|
|
3550
|
+
* Type guard to validate business membership relation strings at runtime
|
|
3551
|
+
*/
|
|
3552
|
+
function isValidBusinessMembershipRelation(value) {
|
|
3553
|
+
return VALID_BUSINESS_MEMBERSHIP_RELATIONS.includes(value);
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
/**
|
|
3557
|
+
* Valid include relations for User endpoints.
|
|
3558
|
+
* These relations can be requested via ?include=status,balances query parameter.
|
|
3559
|
+
*/
|
|
3560
|
+
const VALID_USER_RELATIONS = ['status', 'balances'];
|
|
3561
|
+
/**
|
|
3562
|
+
* Type guard to check if a string is a valid UserIncludeRelation
|
|
3563
|
+
*/
|
|
3564
|
+
function isValidUserRelation(value) {
|
|
3565
|
+
return VALID_USER_RELATIONS.includes(value);
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
/**
|
|
3569
|
+
* Chain Data Types
|
|
3570
|
+
*
|
|
3571
|
+
* Types for blockchain chain configuration.
|
|
3572
|
+
*/
|
|
3573
|
+
const ChainTypes = {
|
|
3574
|
+
PRIVATE: "PRIVATE",
|
|
3575
|
+
PUBLIC: "PUBLIC"
|
|
3576
|
+
};
|
|
3577
|
+
|
|
3221
3578
|
/**
|
|
3222
3579
|
* Transaction format constants for Ethereum and EVM-compatible chains
|
|
3223
3580
|
* Using const assertions for zero runtime overhead
|
|
@@ -3504,6 +3861,281 @@ const ApiErrorDetector = {
|
|
|
3504
3861
|
ErrorUtils.isTokenExpiredError(error)
|
|
3505
3862
|
};
|
|
3506
3863
|
|
|
3864
|
+
/**
|
|
3865
|
+
* Centralized Cache Service for PERS SDK
|
|
3866
|
+
* Simple, efficient caching with memory management
|
|
3867
|
+
*/
|
|
3868
|
+
const DEFAULT_CACHE_CONFIG = {
|
|
3869
|
+
defaultTtl: 30 * 60 * 1000, // 30 minutes
|
|
3870
|
+
maxMemoryMB: 50, // 50MB cache limit
|
|
3871
|
+
cleanupInterval: 5 * 60 * 1000, // Cleanup every 5 minutes
|
|
3872
|
+
maxEntries: 10000, // Entry count limit
|
|
3873
|
+
evictionBatchPercent: 0.2, // Remove 20% when evicting
|
|
3874
|
+
};
|
|
3875
|
+
// Constants for memory estimation
|
|
3876
|
+
const ENTRY_OVERHEAD_BYTES = 64;
|
|
3877
|
+
const DEFAULT_OBJECT_SIZE_BYTES = 1024;
|
|
3878
|
+
const MEMORY_CHECK_INTERVAL_MS = 60000; // Lazy check every 60s
|
|
3879
|
+
class CacheService {
|
|
3880
|
+
constructor(config) {
|
|
3881
|
+
this.cache = new Map();
|
|
3882
|
+
this.cleanupTimer = null;
|
|
3883
|
+
this.lastMemoryCheck = 0;
|
|
3884
|
+
this.pendingFetches = new Map();
|
|
3885
|
+
this.accessOrder = new Set();
|
|
3886
|
+
this.stats = {
|
|
3887
|
+
hits: 0,
|
|
3888
|
+
misses: 0,
|
|
3889
|
+
errors: 0
|
|
3890
|
+
};
|
|
3891
|
+
this.config = { ...DEFAULT_CACHE_CONFIG, ...config };
|
|
3892
|
+
this.startCleanupTimer();
|
|
3893
|
+
}
|
|
3894
|
+
/**
|
|
3895
|
+
* Set a value in cache with optional TTL
|
|
3896
|
+
*/
|
|
3897
|
+
set(key, value, ttl) {
|
|
3898
|
+
if (!key || value === undefined)
|
|
3899
|
+
return;
|
|
3900
|
+
const entry = {
|
|
3901
|
+
value,
|
|
3902
|
+
timestamp: Date.now(),
|
|
3903
|
+
ttl: ttl || this.config.defaultTtl,
|
|
3904
|
+
lastAccessed: Date.now()
|
|
3905
|
+
};
|
|
3906
|
+
this.cache.set(key, entry);
|
|
3907
|
+
this.accessOrder.add(key);
|
|
3908
|
+
// Fast entry count check first
|
|
3909
|
+
if (this.cache.size > this.config.maxEntries) {
|
|
3910
|
+
this.evictOldestEntries();
|
|
3911
|
+
}
|
|
3912
|
+
// Lazy memory check (only every 10s)
|
|
3913
|
+
const now = Date.now();
|
|
3914
|
+
if (now - this.lastMemoryCheck > MEMORY_CHECK_INTERVAL_MS) {
|
|
3915
|
+
this.lastMemoryCheck = now;
|
|
3916
|
+
this.enforceMemoryLimit();
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
/**
|
|
3920
|
+
* Get a value from cache
|
|
3921
|
+
*/
|
|
3922
|
+
get(key) {
|
|
3923
|
+
const entry = this.cache.get(key);
|
|
3924
|
+
if (!entry) {
|
|
3925
|
+
this.stats.misses++;
|
|
3926
|
+
return null;
|
|
3927
|
+
}
|
|
3928
|
+
// Check if expired
|
|
3929
|
+
const now = Date.now();
|
|
3930
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
3931
|
+
this.cache.delete(key);
|
|
3932
|
+
this.accessOrder.delete(key);
|
|
3933
|
+
this.stats.misses++;
|
|
3934
|
+
return null;
|
|
3935
|
+
}
|
|
3936
|
+
// Update access order efficiently
|
|
3937
|
+
this.accessOrder.delete(key);
|
|
3938
|
+
this.accessOrder.add(key);
|
|
3939
|
+
entry.lastAccessed = now;
|
|
3940
|
+
this.stats.hits++;
|
|
3941
|
+
return entry.value;
|
|
3942
|
+
}
|
|
3943
|
+
/**
|
|
3944
|
+
* Get or set pattern - common caching pattern with race condition protection
|
|
3945
|
+
*/
|
|
3946
|
+
async getOrSet(key, fetcher, ttl) {
|
|
3947
|
+
const cached = this.get(key);
|
|
3948
|
+
if (cached !== null)
|
|
3949
|
+
return cached;
|
|
3950
|
+
// Prevent duplicate fetches
|
|
3951
|
+
if (this.pendingFetches.has(key)) {
|
|
3952
|
+
return this.pendingFetches.get(key);
|
|
3953
|
+
}
|
|
3954
|
+
const fetchPromise = fetcher()
|
|
3955
|
+
.then(value => {
|
|
3956
|
+
this.pendingFetches.delete(key);
|
|
3957
|
+
if (value !== undefined) {
|
|
3958
|
+
this.set(key, value, ttl);
|
|
3959
|
+
}
|
|
3960
|
+
return value;
|
|
3961
|
+
})
|
|
3962
|
+
.catch(error => {
|
|
3963
|
+
this.pendingFetches.delete(key);
|
|
3964
|
+
this.stats.errors++;
|
|
3965
|
+
throw error;
|
|
3966
|
+
});
|
|
3967
|
+
this.pendingFetches.set(key, fetchPromise);
|
|
3968
|
+
return fetchPromise;
|
|
3969
|
+
}
|
|
3970
|
+
/**
|
|
3971
|
+
* Delete a specific key
|
|
3972
|
+
*/
|
|
3973
|
+
delete(key) {
|
|
3974
|
+
this.accessOrder.delete(key);
|
|
3975
|
+
return this.cache.delete(key);
|
|
3976
|
+
}
|
|
3977
|
+
/**
|
|
3978
|
+
* Clear all keys matching a prefix
|
|
3979
|
+
*/
|
|
3980
|
+
clearByPrefix(prefix) {
|
|
3981
|
+
const keysToDelete = Array.from(this.cache.keys()).filter(key => key.startsWith(prefix));
|
|
3982
|
+
keysToDelete.forEach(key => {
|
|
3983
|
+
this.cache.delete(key);
|
|
3984
|
+
this.accessOrder.delete(key);
|
|
3985
|
+
});
|
|
3986
|
+
return keysToDelete.length;
|
|
3987
|
+
}
|
|
3988
|
+
/**
|
|
3989
|
+
* Clear all cache
|
|
3990
|
+
*/
|
|
3991
|
+
clear() {
|
|
3992
|
+
this.cache.clear();
|
|
3993
|
+
this.accessOrder.clear();
|
|
3994
|
+
this.pendingFetches.clear();
|
|
3995
|
+
this.stats = { hits: 0, misses: 0, errors: 0 };
|
|
3996
|
+
}
|
|
3997
|
+
/**
|
|
3998
|
+
* Force cleanup of expired entries
|
|
3999
|
+
*/
|
|
4000
|
+
cleanup() {
|
|
4001
|
+
const now = Date.now();
|
|
4002
|
+
const keysToDelete = [];
|
|
4003
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
4004
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
4005
|
+
keysToDelete.push(key);
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
keysToDelete.forEach(key => {
|
|
4009
|
+
this.cache.delete(key);
|
|
4010
|
+
this.accessOrder.delete(key);
|
|
4011
|
+
});
|
|
4012
|
+
return keysToDelete.length;
|
|
4013
|
+
}
|
|
4014
|
+
/**
|
|
4015
|
+
* Stop cleanup timer and clear cache
|
|
4016
|
+
*/
|
|
4017
|
+
destroy() {
|
|
4018
|
+
this.stopCleanupTimer();
|
|
4019
|
+
this.clear();
|
|
4020
|
+
}
|
|
4021
|
+
/**
|
|
4022
|
+
* Create a namespaced cache interface
|
|
4023
|
+
*/
|
|
4024
|
+
createNamespace(namespace) {
|
|
4025
|
+
if (!namespace || namespace.includes(':')) {
|
|
4026
|
+
throw new Error('Namespace must be non-empty and cannot contain ":"');
|
|
4027
|
+
}
|
|
4028
|
+
return {
|
|
4029
|
+
set: (key, value, ttl) => this.set(`${namespace}:${key}`, value, ttl),
|
|
4030
|
+
get: (key) => this.get(`${namespace}:${key}`),
|
|
4031
|
+
getOrSet: (key, fetcher, ttl) => this.getOrSet(`${namespace}:${key}`, fetcher, ttl),
|
|
4032
|
+
delete: (key) => this.delete(`${namespace}:${key}`),
|
|
4033
|
+
clear: () => this.clearByPrefix(`${namespace}:`)
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
startCleanupTimer() {
|
|
4037
|
+
this.stopCleanupTimer();
|
|
4038
|
+
this.cleanupTimer = setInterval(() => {
|
|
4039
|
+
this.safeCleanup();
|
|
4040
|
+
}, this.config.cleanupInterval);
|
|
4041
|
+
// Platform-agnostic: Prevent hanging process in Node.js environments
|
|
4042
|
+
if (this.cleanupTimer && typeof this.cleanupTimer.unref === 'function') {
|
|
4043
|
+
this.cleanupTimer.unref();
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
stopCleanupTimer() {
|
|
4047
|
+
if (this.cleanupTimer) {
|
|
4048
|
+
clearInterval(this.cleanupTimer);
|
|
4049
|
+
this.cleanupTimer = null;
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
safeCleanup() {
|
|
4053
|
+
try {
|
|
4054
|
+
this.cleanup();
|
|
4055
|
+
this.enforceMemoryLimit();
|
|
4056
|
+
}
|
|
4057
|
+
catch (error) {
|
|
4058
|
+
this.stats.errors++;
|
|
4059
|
+
// Platform-agnostic error logging
|
|
4060
|
+
if (typeof console !== 'undefined' && console.warn) {
|
|
4061
|
+
console.warn('Cache cleanup error:', error);
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
estimateValueSize(value) {
|
|
4066
|
+
if (typeof value === 'string') {
|
|
4067
|
+
return value.length * 2; // UTF-16 encoding
|
|
4068
|
+
}
|
|
4069
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
4070
|
+
return 8; // Primitive values
|
|
4071
|
+
}
|
|
4072
|
+
if (value === null || value === undefined) {
|
|
4073
|
+
return 4;
|
|
4074
|
+
}
|
|
4075
|
+
try {
|
|
4076
|
+
// More accurate JSON-based estimation for serializable objects
|
|
4077
|
+
const jsonString = JSON.stringify(value);
|
|
4078
|
+
return jsonString.length * 2;
|
|
4079
|
+
}
|
|
4080
|
+
catch {
|
|
4081
|
+
// Fallback for non-serializable objects
|
|
4082
|
+
if (Array.isArray(value)) {
|
|
4083
|
+
return value.length * 100; // Better estimate for arrays
|
|
4084
|
+
}
|
|
4085
|
+
if (value && typeof value === 'object') {
|
|
4086
|
+
return Object.keys(value).length * 50; // Better estimate for objects
|
|
4087
|
+
}
|
|
4088
|
+
return DEFAULT_OBJECT_SIZE_BYTES;
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
estimateMemoryUsage() {
|
|
4092
|
+
let totalSize = 0;
|
|
4093
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
4094
|
+
totalSize += key.length * 2; // Key size
|
|
4095
|
+
totalSize += this.estimateValueSize(entry.value);
|
|
4096
|
+
totalSize += ENTRY_OVERHEAD_BYTES;
|
|
4097
|
+
}
|
|
4098
|
+
return totalSize / (1024 * 1024);
|
|
4099
|
+
}
|
|
4100
|
+
enforceMemoryLimit() {
|
|
4101
|
+
const memoryUsage = this.estimateMemoryUsage();
|
|
4102
|
+
if (memoryUsage <= this.config.maxMemoryMB || this.cache.size === 0)
|
|
4103
|
+
return;
|
|
4104
|
+
this.evictOldestEntries();
|
|
4105
|
+
}
|
|
4106
|
+
evictOldestEntries() {
|
|
4107
|
+
if (this.cache.size === 0)
|
|
4108
|
+
return;
|
|
4109
|
+
const batchSize = Math.floor(this.cache.size * this.config.evictionBatchPercent);
|
|
4110
|
+
const toRemove = Math.max(batchSize, 1);
|
|
4111
|
+
// Efficiently remove oldest entries using access order
|
|
4112
|
+
const iterator = this.accessOrder.values();
|
|
4113
|
+
for (let i = 0; i < toRemove; i++) {
|
|
4114
|
+
const result = iterator.next();
|
|
4115
|
+
if (result.done)
|
|
4116
|
+
break;
|
|
4117
|
+
const key = result.value;
|
|
4118
|
+
this.cache.delete(key);
|
|
4119
|
+
this.accessOrder.delete(key);
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
// Singleton instance
|
|
4124
|
+
const globalCacheService = new CacheService();
|
|
4125
|
+
|
|
4126
|
+
/**
|
|
4127
|
+
* Cache module exports
|
|
4128
|
+
*/
|
|
4129
|
+
// Predefined cache TTL constants
|
|
4130
|
+
const CacheTTL = {
|
|
4131
|
+
SHORT: 5 * 60 * 1000, // 5 minutes
|
|
4132
|
+
MEDIUM: 30 * 60 * 1000, // 30 minutes
|
|
4133
|
+
LONG: 24 * 60 * 60 * 1000, // 24 hours
|
|
4134
|
+
METADATA: 24 * 60 * 60 * 1000, // 24 hours - IPFS metadata is immutable
|
|
4135
|
+
GATEWAY: 30 * 60 * 1000, // 30 minutes - Gateway config can change
|
|
4136
|
+
PROVIDER: 60 * 60 * 1000, // 1 hour - Provider connections with auth
|
|
4137
|
+
};
|
|
4138
|
+
|
|
3507
4139
|
// ==========================================
|
|
3508
4140
|
// UTILITY FUNCTIONS
|
|
3509
4141
|
// ==========================================
|
|
@@ -3713,9 +4345,15 @@ class UserApi {
|
|
|
3713
4345
|
/**
|
|
3714
4346
|
* AUTH: Get current authenticated user
|
|
3715
4347
|
* Uses new RESTful /users/me endpoint
|
|
4348
|
+
*
|
|
4349
|
+
* @param options - Query options. Use `include` to specify relations: 'status' (user status types), 'balances' (token balances)
|
|
3716
4350
|
*/
|
|
3717
|
-
async getRemoteUser() {
|
|
3718
|
-
|
|
4351
|
+
async getRemoteUser(options) {
|
|
4352
|
+
let url = `${this.basePath}/me`;
|
|
4353
|
+
if (options?.include?.length) {
|
|
4354
|
+
url += `?include=${options.include.join(',')}`;
|
|
4355
|
+
}
|
|
4356
|
+
return this.apiClient.get(url);
|
|
3719
4357
|
}
|
|
3720
4358
|
/**
|
|
3721
4359
|
* AUTH: Update current authenticated user
|
|
@@ -3791,19 +4429,66 @@ class UserApi {
|
|
|
3791
4429
|
return this.apiClient.put(`${this.basePath}/${id}`, userData);
|
|
3792
4430
|
}
|
|
3793
4431
|
/**
|
|
3794
|
-
* ADMIN:
|
|
4432
|
+
* ADMIN: Set or toggle user active status
|
|
3795
4433
|
* Uses new consistent /users/{id}/status endpoint
|
|
3796
4434
|
* Enhanced: Follows RESTful status management pattern across all domains
|
|
4435
|
+
*
|
|
4436
|
+
* @param userId - User ID
|
|
4437
|
+
* @param isActive - Optional explicit status. If provided, sets to this value. If omitted, toggles current status.
|
|
4438
|
+
*
|
|
4439
|
+
* @example
|
|
4440
|
+
* ```typescript
|
|
4441
|
+
* // Explicit set
|
|
4442
|
+
* await sdk.users.setUserActiveStatus('user-123', true); // Activate
|
|
4443
|
+
* await sdk.users.setUserActiveStatus('user-123', false); // Deactivate
|
|
4444
|
+
*
|
|
4445
|
+
* // Toggle (current behavior)
|
|
4446
|
+
* await sdk.users.setUserActiveStatus('user-123'); // Flips current status
|
|
4447
|
+
* ```
|
|
3797
4448
|
*/
|
|
3798
|
-
async
|
|
3799
|
-
|
|
4449
|
+
async setUserActiveStatus(userId, isActive) {
|
|
4450
|
+
const body = isActive !== undefined ? { isActive } : {};
|
|
4451
|
+
return this.apiClient.put(`${this.basePath}/${userId}/status`, body);
|
|
3800
4452
|
}
|
|
3801
4453
|
/**
|
|
3802
4454
|
* ADMIN: Get user by unique identifier
|
|
3803
4455
|
* Uses new RESTful /users/{id} endpoint
|
|
4456
|
+
*
|
|
4457
|
+
* @param id - User unique identifier (id, email, externalId, accountAddress, etc.)
|
|
4458
|
+
* @param options - Query options. Use `include` to specify relations: 'status' (user status types), 'balances' (token balances)
|
|
3804
4459
|
*/
|
|
3805
|
-
async getUserByUniqueIdentifier(id) {
|
|
3806
|
-
|
|
4460
|
+
async getUserByUniqueIdentifier(id, options) {
|
|
4461
|
+
let url = `${this.basePath}/${id}`;
|
|
4462
|
+
if (options?.include?.length) {
|
|
4463
|
+
url += `?include=${options.include.join(',')}`;
|
|
4464
|
+
}
|
|
4465
|
+
return this.apiClient.get(url);
|
|
4466
|
+
}
|
|
4467
|
+
/**
|
|
4468
|
+
* ADMIN: Delete user by identifier (soft delete)
|
|
4469
|
+
* Uses RESTful /users/{identifier} DELETE endpoint
|
|
4470
|
+
*
|
|
4471
|
+
* Soft deletes a user. The user data is retained for 30 days before GDPR anonymization.
|
|
4472
|
+
* Use restoreUser() to restore within the grace period.
|
|
4473
|
+
*
|
|
4474
|
+
* @param identifier - User unique identifier (id, email, externalId, accountAddress, etc.)
|
|
4475
|
+
* @returns Promise resolving to success status and message
|
|
4476
|
+
*/
|
|
4477
|
+
async deleteUser(identifier) {
|
|
4478
|
+
return this.apiClient.delete(`${this.basePath}/${identifier}`);
|
|
4479
|
+
}
|
|
4480
|
+
/**
|
|
4481
|
+
* ADMIN: Restore deleted user by identifier
|
|
4482
|
+
* Uses RESTful /users/{identifier}/restore POST endpoint
|
|
4483
|
+
*
|
|
4484
|
+
* Restores a soft-deleted user within the 30-day grace period.
|
|
4485
|
+
* After GDPR anonymization (30 days), restoration is not possible.
|
|
4486
|
+
*
|
|
4487
|
+
* @param identifier - User unique identifier (id, email, externalId, accountAddress, etc.)
|
|
4488
|
+
* @returns Promise resolving to restored user data
|
|
4489
|
+
*/
|
|
4490
|
+
async restoreUser(identifier) {
|
|
4491
|
+
return this.apiClient.post(`${this.basePath}/${identifier}/restore`);
|
|
3807
4492
|
}
|
|
3808
4493
|
}
|
|
3809
4494
|
|
|
@@ -3833,9 +4518,11 @@ class UserService {
|
|
|
3833
4518
|
// ==========================================
|
|
3834
4519
|
/**
|
|
3835
4520
|
* AUTH: Get current authenticated user
|
|
4521
|
+
*
|
|
4522
|
+
* @param options - Query options. Use `include` to specify relations: 'status' (user status types), 'balances' (token balances)
|
|
3836
4523
|
*/
|
|
3837
|
-
async getRemoteUser() {
|
|
3838
|
-
return this.userApi.getRemoteUser();
|
|
4524
|
+
async getRemoteUser(options) {
|
|
4525
|
+
return this.userApi.getRemoteUser(options);
|
|
3839
4526
|
}
|
|
3840
4527
|
/**
|
|
3841
4528
|
* AUTH: Update current authenticated user
|
|
@@ -3886,15 +4573,47 @@ class UserService {
|
|
|
3886
4573
|
async updateUserAsAdmin(id, userData) {
|
|
3887
4574
|
return this.userApi.updateUserAsAdmin(id, userData);
|
|
3888
4575
|
}
|
|
3889
|
-
|
|
3890
|
-
|
|
4576
|
+
/**
|
|
4577
|
+
* ADMIN: Get user by unique identifier
|
|
4578
|
+
*
|
|
4579
|
+
* @param id - User unique identifier (id, email, externalId, accountAddress, etc.)
|
|
4580
|
+
* @param options - Query options. Use `include` to specify relations: 'status' (user status types), 'balances' (token balances)
|
|
4581
|
+
*/
|
|
4582
|
+
async getUserByUniqueIdentifier(id, options) {
|
|
4583
|
+
return this.userApi.getUserByUniqueIdentifier(id, options);
|
|
4584
|
+
}
|
|
4585
|
+
/**
|
|
4586
|
+
* ADMIN: Set or toggle user active status
|
|
4587
|
+
*
|
|
4588
|
+
* @param userId - User ID
|
|
4589
|
+
* @param isActive - Optional explicit status. If provided, sets to this value. If omitted, toggles current status.
|
|
4590
|
+
*/
|
|
4591
|
+
async setUserActiveStatus(userId, isActive) {
|
|
4592
|
+
return this.userApi.setUserActiveStatus(userId, isActive);
|
|
3891
4593
|
}
|
|
3892
4594
|
/**
|
|
3893
|
-
* ADMIN:
|
|
3894
|
-
*
|
|
4595
|
+
* ADMIN: Delete user by identifier (soft delete)
|
|
4596
|
+
*
|
|
4597
|
+
* Soft deletes a user. The user data is retained for 30 days before GDPR anonymization.
|
|
4598
|
+
* Use restoreUser() to restore within the grace period.
|
|
4599
|
+
*
|
|
4600
|
+
* @param identifier - User unique identifier (id, email, externalId, accountAddress, etc.)
|
|
4601
|
+
* @returns Promise resolving to success status and message
|
|
3895
4602
|
*/
|
|
3896
|
-
async
|
|
3897
|
-
return this.userApi.
|
|
4603
|
+
async deleteUser(identifier) {
|
|
4604
|
+
return this.userApi.deleteUser(identifier);
|
|
4605
|
+
}
|
|
4606
|
+
/**
|
|
4607
|
+
* ADMIN: Restore deleted user by identifier
|
|
4608
|
+
*
|
|
4609
|
+
* Restores a soft-deleted user within the 30-day grace period.
|
|
4610
|
+
* After GDPR anonymization (30 days), restoration is not possible.
|
|
4611
|
+
*
|
|
4612
|
+
* @param identifier - User unique identifier (id, email, externalId, accountAddress, etc.)
|
|
4613
|
+
* @returns Promise resolving to restored user data
|
|
4614
|
+
*/
|
|
4615
|
+
async restoreUser(identifier) {
|
|
4616
|
+
return this.userApi.restoreUser(identifier);
|
|
3898
4617
|
}
|
|
3899
4618
|
}
|
|
3900
4619
|
|
|
@@ -3973,6 +4692,32 @@ class UserStatusApi {
|
|
|
3973
4692
|
throw error;
|
|
3974
4693
|
}
|
|
3975
4694
|
}
|
|
4695
|
+
/**
|
|
4696
|
+
* ADMIN: Update user status type
|
|
4697
|
+
* PUT /users/status-types/:id
|
|
4698
|
+
*/
|
|
4699
|
+
async updateUserStatusType(id, userStatusType) {
|
|
4700
|
+
try {
|
|
4701
|
+
return await this.apiClient.put(`${this.basePath}/status-types/${id}`, userStatusType);
|
|
4702
|
+
}
|
|
4703
|
+
catch (error) {
|
|
4704
|
+
console.error('Error updating user status type', error);
|
|
4705
|
+
throw error;
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
/**
|
|
4709
|
+
* ADMIN: Delete user status type
|
|
4710
|
+
* DELETE /users/status-types/:id
|
|
4711
|
+
*/
|
|
4712
|
+
async deleteUserStatusType(id) {
|
|
4713
|
+
try {
|
|
4714
|
+
await this.apiClient.delete(`${this.basePath}/status-types/${id}`);
|
|
4715
|
+
}
|
|
4716
|
+
catch (error) {
|
|
4717
|
+
console.error('Error deleting user status type', error);
|
|
4718
|
+
throw error;
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
3976
4721
|
}
|
|
3977
4722
|
|
|
3978
4723
|
/**
|
|
@@ -4013,6 +4758,18 @@ class UserStatusService {
|
|
|
4013
4758
|
async createUserStatusType(userStatusType) {
|
|
4014
4759
|
return this.userStatusApi.createUserStatusType(userStatusType);
|
|
4015
4760
|
}
|
|
4761
|
+
/**
|
|
4762
|
+
* ADMIN: Update user status type
|
|
4763
|
+
*/
|
|
4764
|
+
async updateUserStatusType(id, userStatusType) {
|
|
4765
|
+
return this.userStatusApi.updateUserStatusType(id, userStatusType);
|
|
4766
|
+
}
|
|
4767
|
+
/**
|
|
4768
|
+
* ADMIN: Delete user status type
|
|
4769
|
+
*/
|
|
4770
|
+
async deleteUserStatusType(id) {
|
|
4771
|
+
return this.userStatusApi.deleteUserStatusType(id);
|
|
4772
|
+
}
|
|
4016
4773
|
}
|
|
4017
4774
|
|
|
4018
4775
|
/**
|
|
@@ -4040,6 +4797,8 @@ function createUserStatusSDK(apiClient) {
|
|
|
4040
4797
|
getRemoteEarnedUserStatus: (options) => userStatusService.getRemoteEarnedUserStatus(options),
|
|
4041
4798
|
// Admin methods
|
|
4042
4799
|
createUserStatusType: (userStatusType) => userStatusService.createUserStatusType(userStatusType),
|
|
4800
|
+
updateUserStatusType: (id, userStatusType) => userStatusService.updateUserStatusType(id, userStatusType),
|
|
4801
|
+
deleteUserStatusType: (id) => userStatusService.deleteUserStatusType(id),
|
|
4043
4802
|
// Advanced access for edge cases
|
|
4044
4803
|
api: userStatusApi,
|
|
4045
4804
|
service: userStatusService
|
|
@@ -4166,6 +4925,13 @@ class TokenApi {
|
|
|
4166
4925
|
async createTokenMetadata(tokenId, tokenData) {
|
|
4167
4926
|
return this.apiClient.post(`${this.basePath}/${tokenId}/metadata`, tokenData);
|
|
4168
4927
|
}
|
|
4928
|
+
/**
|
|
4929
|
+
* ADMIN: Update token metadata (ERC721 only)
|
|
4930
|
+
* Note: Existing minted NFTs retain their original metadata - this only affects future mints
|
|
4931
|
+
*/
|
|
4932
|
+
async updateTokenMetadata(metadataId, tokenData) {
|
|
4933
|
+
return this.apiClient.put(`${this.basePath}/metadata/${metadataId}`, tokenData);
|
|
4934
|
+
}
|
|
4169
4935
|
/**
|
|
4170
4936
|
* ADMIN: Toggle token metadata status (separate from token status)
|
|
4171
4937
|
*/
|
|
@@ -4240,6 +5006,13 @@ class TokenService {
|
|
|
4240
5006
|
async createTokenMetadata(tokenId, tokenData) {
|
|
4241
5007
|
return this.tokenApi.createTokenMetadata(tokenId, tokenData);
|
|
4242
5008
|
}
|
|
5009
|
+
/**
|
|
5010
|
+
* ADMIN: Update token metadata (ERC721 only)
|
|
5011
|
+
* Note: Existing minted NFTs retain their original metadata - this only affects future mints
|
|
5012
|
+
*/
|
|
5013
|
+
async updateTokenMetadata(metadataId, tokenData) {
|
|
5014
|
+
return this.tokenApi.updateTokenMetadata(metadataId, tokenData);
|
|
5015
|
+
}
|
|
4243
5016
|
/**
|
|
4244
5017
|
* ADMIN: Toggle token active status
|
|
4245
5018
|
*/
|
|
@@ -4508,7 +5281,7 @@ class BusinessMembershipApi {
|
|
|
4508
5281
|
* Min Role: VIEWER (any member can view)
|
|
4509
5282
|
*
|
|
4510
5283
|
* @param businessId - The business UUID
|
|
4511
|
-
* @param options - Pagination and
|
|
5284
|
+
* @param options - Pagination, filter, and include options
|
|
4512
5285
|
* @returns Paginated array of business memberships with user and role details
|
|
4513
5286
|
*
|
|
4514
5287
|
* @throws {AuthenticationError} 401 - Not authenticated
|
|
@@ -4520,15 +5293,19 @@ class BusinessMembershipApi {
|
|
|
4520
5293
|
* const page1 = await membershipApi.getMembers('business-123');
|
|
4521
5294
|
* page1.data.forEach(m => console.log(`${m.userId}: ${m.role}`));
|
|
4522
5295
|
*
|
|
4523
|
-
* //
|
|
4524
|
-
* const
|
|
4525
|
-
*
|
|
5296
|
+
* // Include user data for display
|
|
5297
|
+
* const withUsers = await membershipApi.getMembers('business-123', {
|
|
5298
|
+
* include: ['user'],
|
|
4526
5299
|
* page: 1,
|
|
4527
5300
|
* limit: 50
|
|
4528
5301
|
* });
|
|
5302
|
+
* withUsers.data.forEach(m => console.log(m.included?.user?.email));
|
|
4529
5303
|
*
|
|
4530
|
-
* //
|
|
4531
|
-
* const
|
|
5304
|
+
* // Filter by role with user data
|
|
5305
|
+
* const admins = await membershipApi.getMembers('business-123', {
|
|
5306
|
+
* role: MembershipRole.ADMIN,
|
|
5307
|
+
* include: ['user']
|
|
5308
|
+
* });
|
|
4532
5309
|
* ```
|
|
4533
5310
|
*/
|
|
4534
5311
|
async getMembers(businessId, options) {
|
|
@@ -4536,6 +5313,9 @@ class BusinessMembershipApi {
|
|
|
4536
5313
|
if (options?.role) {
|
|
4537
5314
|
params.set('role', options.role);
|
|
4538
5315
|
}
|
|
5316
|
+
if (options?.include?.length) {
|
|
5317
|
+
params.set('include', options.include.join(','));
|
|
5318
|
+
}
|
|
4539
5319
|
const response = await this.apiClient.get(`${this.getMembersPath(businessId)}?${params.toString()}`);
|
|
4540
5320
|
return normalizeToPaginated(response);
|
|
4541
5321
|
}
|
|
@@ -4795,8 +5575,15 @@ class BusinessMembershipService {
|
|
|
4795
5575
|
* Get all members of a business with pagination
|
|
4796
5576
|
*
|
|
4797
5577
|
* @param businessId - The business UUID
|
|
4798
|
-
* @param options - Pagination options
|
|
5578
|
+
* @param options - Pagination and include options
|
|
4799
5579
|
* @returns Paginated response with business memberships
|
|
5580
|
+
*
|
|
5581
|
+
* @example
|
|
5582
|
+
* ```typescript
|
|
5583
|
+
* // Get members with user data for display
|
|
5584
|
+
* const members = await service.getMembers('biz-123', { include: ['user'] });
|
|
5585
|
+
* members.data.forEach(m => console.log(m.included?.user?.email));
|
|
5586
|
+
* ```
|
|
4800
5587
|
*/
|
|
4801
5588
|
async getMembers(businessId, options) {
|
|
4802
5589
|
return this.membershipApi.getMembers(businessId, options);
|
|
@@ -6346,7 +7133,9 @@ class TransactionService {
|
|
|
6346
7133
|
return this.transactionApi.getTransactionAnalytics(analyticsRequest);
|
|
6347
7134
|
}
|
|
6348
7135
|
}
|
|
6349
|
-
|
|
7136
|
+
// ============================================================================
|
|
7137
|
+
// CLIENT TRANSACTION TYPES
|
|
7138
|
+
// ============================================================================
|
|
6350
7139
|
/**
|
|
6351
7140
|
* Client-side transaction types extending backend Web3TransactionType
|
|
6352
7141
|
* Includes client-specific flows like pending submissions (POS flow)
|
|
@@ -7054,6 +7843,156 @@ class TenantService {
|
|
|
7054
7843
|
}
|
|
7055
7844
|
}
|
|
7056
7845
|
|
|
7846
|
+
/**
|
|
7847
|
+
* Tenant Manager - Clean, high-level interface for tenant operations
|
|
7848
|
+
*
|
|
7849
|
+
* Provides a simplified API for common tenant management tasks while maintaining
|
|
7850
|
+
* access to the full tenant SDK for advanced use cases.
|
|
7851
|
+
*
|
|
7852
|
+
* Also provides chain-agnostic IPFS URL resolution using tenant configuration.
|
|
7853
|
+
*/
|
|
7854
|
+
class TenantManager {
|
|
7855
|
+
constructor(apiClient) {
|
|
7856
|
+
this.apiClient = apiClient;
|
|
7857
|
+
this.cache = globalCacheService.createNamespace('tenant');
|
|
7858
|
+
const tenantApi = new TenantApi(apiClient);
|
|
7859
|
+
this.tenantService = new TenantService(tenantApi);
|
|
7860
|
+
}
|
|
7861
|
+
/**
|
|
7862
|
+
* Get current tenant information
|
|
7863
|
+
*
|
|
7864
|
+
* @returns Promise resolving to tenant data
|
|
7865
|
+
*/
|
|
7866
|
+
async getTenantInfo() {
|
|
7867
|
+
return this.tenantService.getRemoteTenant();
|
|
7868
|
+
}
|
|
7869
|
+
/**
|
|
7870
|
+
* Get tenant login token
|
|
7871
|
+
*
|
|
7872
|
+
* @returns Promise resolving to login token
|
|
7873
|
+
*/
|
|
7874
|
+
async getLoginToken() {
|
|
7875
|
+
return this.tenantService.getRemoteLoginToken();
|
|
7876
|
+
}
|
|
7877
|
+
/**
|
|
7878
|
+
* Get tenant client configuration (cached)
|
|
7879
|
+
*
|
|
7880
|
+
* @returns Promise resolving to client config
|
|
7881
|
+
*/
|
|
7882
|
+
async getClientConfig() {
|
|
7883
|
+
return this.cache.getOrSet('clientConfig', () => this.tenantService.getRemoteClientConfig(), CacheTTL.LONG);
|
|
7884
|
+
}
|
|
7885
|
+
// ==========================================
|
|
7886
|
+
// IPFS OPERATIONS (Chain-agnostic)
|
|
7887
|
+
// ==========================================
|
|
7888
|
+
/**
|
|
7889
|
+
* Resolve IPFS URL to HTTPS URL using tenant's configured gateway.
|
|
7890
|
+
*
|
|
7891
|
+
* This is chain-agnostic - IPFS gateway is configured at the tenant level,
|
|
7892
|
+
* not per-chain. Use this for resolving any ipfs:// URLs (images, metadata, etc.).
|
|
7893
|
+
*
|
|
7894
|
+
* @param url - URL to resolve (can be ipfs:// or https://)
|
|
7895
|
+
* @returns Resolved HTTPS URL
|
|
7896
|
+
* @throws Error if tenant's ipfsGatewayDomain is not configured
|
|
7897
|
+
*
|
|
7898
|
+
* @example
|
|
7899
|
+
* ```typescript
|
|
7900
|
+
* const imageUrl = await sdk.tenant.resolveIPFSUrl('ipfs://QmXxx.../image.png');
|
|
7901
|
+
* // Returns: 'https://pers.mypinata.cloud/ipfs/QmXxx.../image.png'
|
|
7902
|
+
*
|
|
7903
|
+
* // Non-IPFS URLs pass through unchanged
|
|
7904
|
+
* const httpUrl = await sdk.tenant.resolveIPFSUrl('https://example.com/image.png');
|
|
7905
|
+
* // Returns: 'https://example.com/image.png'
|
|
7906
|
+
* ```
|
|
7907
|
+
*/
|
|
7908
|
+
async resolveIPFSUrl(url) {
|
|
7909
|
+
if (!url || !url.startsWith('ipfs://')) {
|
|
7910
|
+
return url;
|
|
7911
|
+
}
|
|
7912
|
+
const gateway = await this.getIpfsGatewayDomain();
|
|
7913
|
+
return url.replace('ipfs://', `https://${gateway}/ipfs/`);
|
|
7914
|
+
}
|
|
7915
|
+
/**
|
|
7916
|
+
* Get IPFS gateway domain from tenant configuration (cached).
|
|
7917
|
+
*
|
|
7918
|
+
* @returns IPFS gateway domain (e.g., 'pers.mypinata.cloud')
|
|
7919
|
+
* @throws Error if ipfsGatewayDomain is not configured for this tenant
|
|
7920
|
+
*/
|
|
7921
|
+
async getIpfsGatewayDomain() {
|
|
7922
|
+
return this.cache.getOrSet('ipfsGateway', async () => {
|
|
7923
|
+
const config = await this.getClientConfig();
|
|
7924
|
+
if (!config.ipfsGatewayDomain) {
|
|
7925
|
+
throw new Error('IPFS gateway domain not configured for tenant. ' +
|
|
7926
|
+
'Please configure ipfsGatewayDomain in tenant settings.');
|
|
7927
|
+
}
|
|
7928
|
+
return config.ipfsGatewayDomain;
|
|
7929
|
+
}, CacheTTL.GATEWAY);
|
|
7930
|
+
}
|
|
7931
|
+
/**
|
|
7932
|
+
* Get Google API key from tenant configuration (cached).
|
|
7933
|
+
*
|
|
7934
|
+
* @returns Google API key or undefined if not configured
|
|
7935
|
+
*/
|
|
7936
|
+
async getGoogleApiKey() {
|
|
7937
|
+
const config = await this.getClientConfig();
|
|
7938
|
+
return config.googleApiKey;
|
|
7939
|
+
}
|
|
7940
|
+
// ==========================================
|
|
7941
|
+
// ADMIN OPERATIONS
|
|
7942
|
+
// ==========================================
|
|
7943
|
+
/**
|
|
7944
|
+
* Admin: Update tenant data
|
|
7945
|
+
*
|
|
7946
|
+
* @param tenantData - Updated tenant data
|
|
7947
|
+
* @returns Promise resolving to updated tenant
|
|
7948
|
+
*/
|
|
7949
|
+
async updateTenant(tenantData) {
|
|
7950
|
+
return this.tenantService.updateRemoteTenant(tenantData);
|
|
7951
|
+
}
|
|
7952
|
+
/**
|
|
7953
|
+
* Admin: Get all admins
|
|
7954
|
+
*
|
|
7955
|
+
* @param options - Pagination options
|
|
7956
|
+
* @returns Promise resolving to paginated admins
|
|
7957
|
+
*/
|
|
7958
|
+
async getAdmins(options) {
|
|
7959
|
+
return this.tenantService.getAdmins(options);
|
|
7960
|
+
}
|
|
7961
|
+
/**
|
|
7962
|
+
* Admin: Create new admin
|
|
7963
|
+
*
|
|
7964
|
+
* @param adminData - Admin data
|
|
7965
|
+
* @returns Promise resolving to created admin
|
|
7966
|
+
*/
|
|
7967
|
+
async createAdmin(adminData) {
|
|
7968
|
+
return this.tenantService.postAdmin(adminData);
|
|
7969
|
+
}
|
|
7970
|
+
/**
|
|
7971
|
+
* Admin: Update existing admin
|
|
7972
|
+
*
|
|
7973
|
+
* @param adminId - ID of the admin to update
|
|
7974
|
+
* @param adminData - Updated admin data
|
|
7975
|
+
* @returns Promise resolving to updated admin
|
|
7976
|
+
*/
|
|
7977
|
+
async updateAdmin(adminId, adminData) {
|
|
7978
|
+
return this.tenantService.putAdmin(adminId, adminData);
|
|
7979
|
+
}
|
|
7980
|
+
/**
|
|
7981
|
+
* Get the tenant service for advanced operations
|
|
7982
|
+
*
|
|
7983
|
+
* @returns TenantService instance
|
|
7984
|
+
*/
|
|
7985
|
+
getTenantService() {
|
|
7986
|
+
return this.tenantService;
|
|
7987
|
+
}
|
|
7988
|
+
/**
|
|
7989
|
+
* Clear tenant cache (useful after config changes)
|
|
7990
|
+
*/
|
|
7991
|
+
clearCache() {
|
|
7992
|
+
this.cache.clear();
|
|
7993
|
+
}
|
|
7994
|
+
}
|
|
7995
|
+
|
|
7057
7996
|
/**
|
|
7058
7997
|
* Platform-Agnostic Analytics API Client
|
|
7059
7998
|
*
|
|
@@ -7257,6 +8196,57 @@ class AnalyticsApi {
|
|
|
7257
8196
|
async getRetentionAnalytics(request = {}) {
|
|
7258
8197
|
return this.apiClient.post('/analytics/users/retention', request);
|
|
7259
8198
|
}
|
|
8199
|
+
// ==========================================
|
|
8200
|
+
// TAG ANALYTICS
|
|
8201
|
+
// ==========================================
|
|
8202
|
+
/**
|
|
8203
|
+
* ADMIN: Get tag usage analytics across entities
|
|
8204
|
+
*
|
|
8205
|
+
* Aggregates tag usage across all taggable entities (campaigns, redemptions,
|
|
8206
|
+
* businesses, token metadata, user status types). Perfect for:
|
|
8207
|
+
* - Tag autocomplete/suggestions
|
|
8208
|
+
* - Tag management dashboards
|
|
8209
|
+
* - Usage analytics
|
|
8210
|
+
*
|
|
8211
|
+
* @param request - Optional filters for entity types
|
|
8212
|
+
* @returns Aggregated tag usage with per-entity-type breakdown
|
|
8213
|
+
*
|
|
8214
|
+
* @example Get all tags across all entities
|
|
8215
|
+
* ```typescript
|
|
8216
|
+
* const allTags = await analyticsApi.getTagAnalytics();
|
|
8217
|
+
* console.log(`${allTags.totalTags} unique tags, ${allTags.totalUsage} total usages`);
|
|
8218
|
+
*
|
|
8219
|
+
* // Use for autocomplete suggestions
|
|
8220
|
+
* const suggestions = allTags.tags.map(t => t.tag);
|
|
8221
|
+
* ```
|
|
8222
|
+
*
|
|
8223
|
+
* @example Get tags only from campaigns and businesses
|
|
8224
|
+
* ```typescript
|
|
8225
|
+
* const filteredTags = await analyticsApi.getTagAnalytics({
|
|
8226
|
+
* entityTypes: [TaggableEntityType.CAMPAIGN, TaggableEntityType.BUSINESS]
|
|
8227
|
+
* });
|
|
8228
|
+
* ```
|
|
8229
|
+
*
|
|
8230
|
+
* @example Tag usage breakdown
|
|
8231
|
+
* ```typescript
|
|
8232
|
+
* const analytics = await analyticsApi.getTagAnalytics();
|
|
8233
|
+
* analytics.tags.forEach(({ tag, count, usageByEntityType }) => {
|
|
8234
|
+
* console.log(`${tag}: ${count} total`);
|
|
8235
|
+
* usageByEntityType.forEach(({ entityType, count }) => {
|
|
8236
|
+
* console.log(` - ${entityType}: ${count}`);
|
|
8237
|
+
* });
|
|
8238
|
+
* });
|
|
8239
|
+
* ```
|
|
8240
|
+
*/
|
|
8241
|
+
async getTagAnalytics(request = {}) {
|
|
8242
|
+
const params = new URLSearchParams();
|
|
8243
|
+
if (request.entityTypes?.length) {
|
|
8244
|
+
params.set('entityTypes', request.entityTypes.join(','));
|
|
8245
|
+
}
|
|
8246
|
+
const queryString = params.toString();
|
|
8247
|
+
const url = queryString ? `/analytics/tags?${queryString}` : '/analytics/tags';
|
|
8248
|
+
return this.apiClient.get(url);
|
|
8249
|
+
}
|
|
7260
8250
|
}
|
|
7261
8251
|
|
|
7262
8252
|
/**
|
|
@@ -7321,6 +8311,18 @@ class AnalyticsService {
|
|
|
7321
8311
|
async getRetentionAnalytics(request = {}) {
|
|
7322
8312
|
return this.analyticsApi.getRetentionAnalytics(request);
|
|
7323
8313
|
}
|
|
8314
|
+
// ==========================================
|
|
8315
|
+
// TAG ANALYTICS
|
|
8316
|
+
// ==========================================
|
|
8317
|
+
/**
|
|
8318
|
+
* ADMIN: Get tag usage analytics across entities
|
|
8319
|
+
*
|
|
8320
|
+
* Aggregates tag usage across all taggable entities for autocomplete,
|
|
8321
|
+
* tag management dashboards, and usage analytics.
|
|
8322
|
+
*/
|
|
8323
|
+
async getTagAnalytics(request = {}) {
|
|
8324
|
+
return this.analyticsApi.getTagAnalytics(request);
|
|
8325
|
+
}
|
|
7324
8326
|
}
|
|
7325
8327
|
|
|
7326
8328
|
/**
|
|
@@ -7523,19 +8525,40 @@ const DEFAULT_PERS_CONFIG = {
|
|
|
7523
8525
|
timeout: 30000,
|
|
7524
8526
|
retries: 3,
|
|
7525
8527
|
tokenRefreshMargin: 60, // Refresh tokens 60 seconds before expiry
|
|
7526
|
-
backgroundRefreshThreshold: 30 // Use background refresh if >30s remaining
|
|
8528
|
+
backgroundRefreshThreshold: 30, // Use background refresh if >30s remaining
|
|
8529
|
+
captureWalletEvents: true // Auto-connect to wallet events after auth
|
|
7527
8530
|
};
|
|
7528
8531
|
/**
|
|
7529
|
-
*
|
|
7530
|
-
*
|
|
8532
|
+
* Build the API root URL based on config
|
|
8533
|
+
*
|
|
8534
|
+
* Priority:
|
|
8535
|
+
* 1. customApiUrl (if provided)
|
|
8536
|
+
* 2. Environment-based URL (staging/production)
|
|
8537
|
+
*
|
|
8538
|
+
* @internal
|
|
7531
8539
|
*/
|
|
7532
|
-
function buildApiRoot(environment = 'production', version = 'v2') {
|
|
8540
|
+
function buildApiRoot(environment = 'production', version = 'v2', customApiUrl) {
|
|
8541
|
+
// Custom URL takes priority
|
|
8542
|
+
if (customApiUrl) {
|
|
8543
|
+
return customApiUrl;
|
|
8544
|
+
}
|
|
7533
8545
|
const baseUrls = {
|
|
7534
|
-
development: 'https://explorins-loyalty.ngrok.io',
|
|
7535
8546
|
staging: `https://dev.api.pers.ninja/${version}`,
|
|
7536
8547
|
production: `https://api.pers.ninja/${version}`
|
|
7537
8548
|
};
|
|
7538
|
-
return
|
|
8549
|
+
return baseUrls[environment];
|
|
8550
|
+
}
|
|
8551
|
+
/**
|
|
8552
|
+
* Build wallet events WebSocket URL based on config
|
|
8553
|
+
*
|
|
8554
|
+
* @internal
|
|
8555
|
+
*/
|
|
8556
|
+
function buildWalletEventsWsUrl(environment = 'production', customWsUrl) {
|
|
8557
|
+
const wsUrls = {
|
|
8558
|
+
staging: 'wss://events-staging.pers.ninja',
|
|
8559
|
+
production: 'wss://events.pers.ninja'
|
|
8560
|
+
};
|
|
8561
|
+
return wsUrls[environment];
|
|
7539
8562
|
}
|
|
7540
8563
|
/**
|
|
7541
8564
|
* Merge user config with defaults
|
|
@@ -8454,8 +9477,8 @@ class PersApiClient {
|
|
|
8454
9477
|
this.initializationPromise = null;
|
|
8455
9478
|
// Merge user config with defaults (production + v2)
|
|
8456
9479
|
this.mergedConfig = mergeWithDefaults(config);
|
|
8457
|
-
// Build API root
|
|
8458
|
-
this.apiRoot = buildApiRoot(this.mergedConfig.environment, this.mergedConfig.apiVersion);
|
|
9480
|
+
// Build API root - customApiUrl takes priority over environment
|
|
9481
|
+
this.apiRoot = buildApiRoot(this.mergedConfig.environment, this.mergedConfig.apiVersion, this.mergedConfig.customApiUrl);
|
|
8459
9482
|
// Initialize auth services for direct authentication
|
|
8460
9483
|
this.authApi = new AuthApi(this);
|
|
8461
9484
|
// Auto-create auth provider if none provided
|
|
@@ -8795,7 +9818,6 @@ class PersEventEmitter {
|
|
|
8795
9818
|
constructor() {
|
|
8796
9819
|
this.handlers = new Set();
|
|
8797
9820
|
this._instanceId = ++emitterInstanceCounter;
|
|
8798
|
-
console.log(`[PersEventEmitter] Instance #${this._instanceId} created`);
|
|
8799
9821
|
}
|
|
8800
9822
|
get instanceId() {
|
|
8801
9823
|
return this._instanceId;
|
|
@@ -9340,23 +10362,32 @@ class UserManager {
|
|
|
9340
10362
|
* Retrieves the complete profile of the currently authenticated user.
|
|
9341
10363
|
* Requires valid authentication tokens.
|
|
9342
10364
|
*
|
|
10365
|
+
* @param options - Query options. Use `include` to specify relations: 'status' (user status types), 'balances' (token balances)
|
|
9343
10366
|
* @returns Promise resolving to current user data with full profile information
|
|
9344
10367
|
* @throws {PersApiError} When user is not authenticated
|
|
9345
10368
|
*
|
|
9346
|
-
* @example
|
|
10369
|
+
* @example Basic Usage
|
|
9347
10370
|
* ```typescript
|
|
9348
10371
|
* try {
|
|
9349
10372
|
* const user = await sdk.users.getCurrentUser();
|
|
9350
|
-
* console.log('Current user:', user.
|
|
9351
|
-
* console.log('User ID:', user.id);
|
|
9352
|
-
* console.log('Registration date:', user.createdAt);
|
|
10373
|
+
* console.log('Current user:', user.firstName, user.email);
|
|
9353
10374
|
* } catch (error) {
|
|
9354
10375
|
* console.log('User not authenticated');
|
|
9355
10376
|
* }
|
|
9356
10377
|
* ```
|
|
10378
|
+
*
|
|
10379
|
+
* @example With Status Types and Balances
|
|
10380
|
+
* ```typescript
|
|
10381
|
+
* // Include status types and token balances
|
|
10382
|
+
* const user = await sdk.users.getCurrentUser({
|
|
10383
|
+
* include: ['status', 'balances']
|
|
10384
|
+
* });
|
|
10385
|
+
* console.log('Status types:', user.included?.statusTypes);
|
|
10386
|
+
* console.log('Token balances:', user.included?.tokenBalances);
|
|
10387
|
+
* ```
|
|
9357
10388
|
*/
|
|
9358
|
-
async getCurrentUser() {
|
|
9359
|
-
return this.userService.getRemoteUser();
|
|
10389
|
+
async getCurrentUser(options) {
|
|
10390
|
+
return this.userService.getRemoteUser(options);
|
|
9360
10391
|
}
|
|
9361
10392
|
/**
|
|
9362
10393
|
* Update current user profile
|
|
@@ -9402,25 +10433,34 @@ class UserManager {
|
|
|
9402
10433
|
* requires appropriate permissions (admin or specific access rights).
|
|
9403
10434
|
*
|
|
9404
10435
|
* @param identifier - Unique identifier for the user (user ID, email, etc.)
|
|
10436
|
+
* @param options - Query options. Use `include` to specify relations: 'status' (user status types), 'balances' (token balances)
|
|
9405
10437
|
* @returns Promise resolving to user data
|
|
9406
10438
|
* @throws {PersApiError} When user not found or insufficient permissions
|
|
9407
10439
|
*
|
|
9408
|
-
* @example
|
|
10440
|
+
* @example Basic Usage
|
|
9409
10441
|
* ```typescript
|
|
9410
10442
|
* try {
|
|
9411
10443
|
* const user = await sdk.users.getUserById('user-123');
|
|
9412
|
-
* console.log('Found user:', user.
|
|
10444
|
+
* console.log('Found user:', user.firstName);
|
|
9413
10445
|
* } catch (error) {
|
|
9414
10446
|
* if (error.statusCode === 404) {
|
|
9415
10447
|
* console.log('User not found');
|
|
9416
|
-
* } else if (error.statusCode === 403) {
|
|
9417
|
-
* console.log('Access denied');
|
|
9418
10448
|
* }
|
|
9419
10449
|
* }
|
|
9420
10450
|
* ```
|
|
10451
|
+
*
|
|
10452
|
+
* @example With Status Types and Balances
|
|
10453
|
+
* ```typescript
|
|
10454
|
+
* // Get user with status types and token balances
|
|
10455
|
+
* const user = await sdk.users.getUserById('user-123', {
|
|
10456
|
+
* include: ['status', 'balances']
|
|
10457
|
+
* });
|
|
10458
|
+
* console.log('Status types:', user.included?.statusTypes);
|
|
10459
|
+
* console.log('Token balances:', user.included?.tokenBalances);
|
|
10460
|
+
* ```
|
|
9421
10461
|
*/
|
|
9422
|
-
async getUserById(identifier) {
|
|
9423
|
-
return this.userService.getUserByUniqueIdentifier(identifier);
|
|
10462
|
+
async getUserById(identifier, options) {
|
|
10463
|
+
return this.userService.getUserByUniqueIdentifier(identifier, options);
|
|
9424
10464
|
}
|
|
9425
10465
|
/**
|
|
9426
10466
|
* Get all users public profiles with optional filtering
|
|
@@ -9600,34 +10640,104 @@ class UserManager {
|
|
|
9600
10640
|
return this.userService.updateUserAsAdmin(userId, userData);
|
|
9601
10641
|
}
|
|
9602
10642
|
/**
|
|
9603
|
-
* Admin:
|
|
10643
|
+
* Admin: Set or toggle user active status
|
|
9604
10644
|
*
|
|
9605
|
-
*
|
|
9606
|
-
*
|
|
10645
|
+
* Sets the active/inactive status of a user account explicitly, or toggles
|
|
10646
|
+
* if no explicit value is provided. This is typically used for account
|
|
10647
|
+
* suspension or reactivation. Requires administrator privileges.
|
|
9607
10648
|
*
|
|
9608
|
-
* @param
|
|
10649
|
+
* @param userId - User ID to update
|
|
10650
|
+
* @param isActive - Optional explicit status. If provided, sets to this value. If omitted, toggles current status.
|
|
9609
10651
|
* @returns Promise resolving to updated user data
|
|
9610
10652
|
* @throws {PersApiError} When not authenticated as admin
|
|
9611
10653
|
*
|
|
9612
|
-
* @example
|
|
10654
|
+
* @example Explicit Status
|
|
9613
10655
|
* ```typescript
|
|
9614
|
-
* // Admin operation -
|
|
10656
|
+
* // Admin operation - explicitly activate user
|
|
10657
|
+
* const activated = await sdk.users.setUserActiveStatus('user-123', true);
|
|
10658
|
+
* console.log('User activated:', activated.isActive); // true
|
|
10659
|
+
*
|
|
10660
|
+
* // Explicitly deactivate user
|
|
10661
|
+
* const deactivated = await sdk.users.setUserActiveStatus('user-123', false);
|
|
10662
|
+
* console.log('User deactivated:', deactivated.isActive); // false
|
|
10663
|
+
* ```
|
|
10664
|
+
*
|
|
10665
|
+
* @example Toggle Status
|
|
10666
|
+
* ```typescript
|
|
10667
|
+
* // Admin operation - toggle current status
|
|
10668
|
+
* const toggled = await sdk.users.setUserActiveStatus('user-123');
|
|
10669
|
+
* console.log('User status toggled:', toggled.isActive);
|
|
10670
|
+
* ```
|
|
10671
|
+
*/
|
|
10672
|
+
async setUserActiveStatus(userId, isActive) {
|
|
10673
|
+
return this.userService.setUserActiveStatus(userId, isActive);
|
|
10674
|
+
}
|
|
10675
|
+
/**
|
|
10676
|
+
* Admin: Delete user (soft delete)
|
|
10677
|
+
*
|
|
10678
|
+
* Soft deletes a user account. The user data is retained for 30 days before
|
|
10679
|
+
* GDPR anonymization. Use restoreUser() to restore within the grace period.
|
|
10680
|
+
*
|
|
10681
|
+
* ⚠️ This operation is irreversible after 30 days. Consider using toggleUserStatus()
|
|
10682
|
+
* for temporary deactivation instead.
|
|
10683
|
+
*
|
|
10684
|
+
* @param identifier - User unique identifier (id, email, externalId, accountAddress, etc.)
|
|
10685
|
+
* @returns Promise resolving to success status and message
|
|
10686
|
+
* @throws {PersApiError} When not authenticated as admin or user not found
|
|
10687
|
+
*
|
|
10688
|
+
* @example Delete User
|
|
10689
|
+
* ```typescript
|
|
10690
|
+
* // Admin operation - soft delete a user
|
|
9615
10691
|
* try {
|
|
9616
|
-
* const
|
|
9617
|
-
*
|
|
10692
|
+
* const result = await sdk.users.deleteUser('user-123');
|
|
10693
|
+
* console.log(result.message); // "User user-123 has been deleted"
|
|
10694
|
+
* } catch (error) {
|
|
10695
|
+
* console.log('Failed to delete user:', error.message);
|
|
10696
|
+
* }
|
|
10697
|
+
* ```
|
|
10698
|
+
*/
|
|
10699
|
+
async deleteUser(identifier) {
|
|
10700
|
+
const result = await this.userService.deleteUser(identifier);
|
|
10701
|
+
this.events?.emitSuccess({
|
|
10702
|
+
domain: 'user',
|
|
10703
|
+
type: 'USER_DELETED',
|
|
10704
|
+
userMessage: 'User deleted successfully',
|
|
10705
|
+
details: { identifier }
|
|
10706
|
+
});
|
|
10707
|
+
return result;
|
|
10708
|
+
}
|
|
10709
|
+
/**
|
|
10710
|
+
* Admin: Restore deleted user
|
|
9618
10711
|
*
|
|
9619
|
-
*
|
|
9620
|
-
*
|
|
9621
|
-
*
|
|
9622
|
-
*
|
|
9623
|
-
*
|
|
10712
|
+
* Restores a soft-deleted user within the 30-day grace period.
|
|
10713
|
+
* After GDPR anonymization (30 days), restoration is not possible.
|
|
10714
|
+
*
|
|
10715
|
+
* @param identifier - User unique identifier (id, email, externalId, accountAddress, etc.)
|
|
10716
|
+
* @returns Promise resolving to restored user data
|
|
10717
|
+
* @throws {PersApiError} When not authenticated as admin, user not found, or already anonymized
|
|
10718
|
+
*
|
|
10719
|
+
* @example Restore Deleted User
|
|
10720
|
+
* ```typescript
|
|
10721
|
+
* // Admin operation - restore a deleted user
|
|
10722
|
+
* try {
|
|
10723
|
+
* const user = await sdk.users.restoreUser('user-123');
|
|
10724
|
+
* console.log('User restored:', user.firstName);
|
|
9624
10725
|
* } catch (error) {
|
|
9625
|
-
*
|
|
10726
|
+
* if (error.message.includes('anonymized')) {
|
|
10727
|
+
* console.log('User cannot be restored - already anonymized');
|
|
10728
|
+
* }
|
|
9626
10729
|
* }
|
|
9627
10730
|
* ```
|
|
9628
10731
|
*/
|
|
9629
|
-
async
|
|
9630
|
-
|
|
10732
|
+
async restoreUser(identifier) {
|
|
10733
|
+
const result = await this.userService.restoreUser(identifier);
|
|
10734
|
+
this.events?.emitSuccess({
|
|
10735
|
+
domain: 'user',
|
|
10736
|
+
type: 'USER_RESTORED',
|
|
10737
|
+
userMessage: 'User restored successfully',
|
|
10738
|
+
details: { identifier, userId: result.id }
|
|
10739
|
+
});
|
|
10740
|
+
return result;
|
|
9631
10741
|
}
|
|
9632
10742
|
/**
|
|
9633
10743
|
* Get the user service for advanced operations
|
|
@@ -9686,6 +10796,25 @@ class UserStatusManager {
|
|
|
9686
10796
|
async createUserStatusType(userStatusType) {
|
|
9687
10797
|
return this.userStatusSDK.createUserStatusType(userStatusType);
|
|
9688
10798
|
}
|
|
10799
|
+
/**
|
|
10800
|
+
* Admin: Update existing user status type
|
|
10801
|
+
*
|
|
10802
|
+
* @param id - User status type ID
|
|
10803
|
+
* @param userStatusType - User status type data
|
|
10804
|
+
* @returns Promise resolving to updated user status type
|
|
10805
|
+
*/
|
|
10806
|
+
async updateUserStatusType(id, userStatusType) {
|
|
10807
|
+
return this.userStatusSDK.updateUserStatusType(id, userStatusType);
|
|
10808
|
+
}
|
|
10809
|
+
/**
|
|
10810
|
+
* Admin: Delete user status type
|
|
10811
|
+
*
|
|
10812
|
+
* @param id - User status type ID
|
|
10813
|
+
* @returns Promise resolving when deleted
|
|
10814
|
+
*/
|
|
10815
|
+
async deleteUserStatusType(id) {
|
|
10816
|
+
return this.userStatusSDK.deleteUserStatusType(id);
|
|
10817
|
+
}
|
|
9689
10818
|
/**
|
|
9690
10819
|
* Get the full user status SDK for advanced operations
|
|
9691
10820
|
*
|
|
@@ -10401,22 +11530,23 @@ class BusinessManager {
|
|
|
10401
11530
|
* Any member of the business can view the member list.
|
|
10402
11531
|
*
|
|
10403
11532
|
* @param businessId - The business UUID
|
|
10404
|
-
* @
|
|
11533
|
+
* @param options - Pagination and include options
|
|
11534
|
+
* @returns Promise resolving to paginated business memberships
|
|
10405
11535
|
* @throws {PersApiError} 401 - Not authenticated
|
|
10406
11536
|
* @throws {PersApiError} 403 - Not a member of this business
|
|
10407
11537
|
*
|
|
10408
11538
|
* @example
|
|
10409
11539
|
* ```typescript
|
|
10410
|
-
*
|
|
11540
|
+
* // Get members with user data for display
|
|
11541
|
+
* const { data: members } = await sdk.business.getMembers('business-123', {
|
|
11542
|
+
* include: ['user']
|
|
11543
|
+
* });
|
|
10411
11544
|
*
|
|
10412
11545
|
* console.log('Business Members:');
|
|
10413
11546
|
* members.forEach(member => {
|
|
10414
|
-
*
|
|
11547
|
+
* const email = member.included?.user?.email || member.userId;
|
|
11548
|
+
* console.log(`- ${email}: ${member.role}`);
|
|
10415
11549
|
* });
|
|
10416
|
-
*
|
|
10417
|
-
* // Count members by role
|
|
10418
|
-
* const admins = members.filter(m => m.role === 'ADMIN' || m.role === 'OWNER');
|
|
10419
|
-
* console.log(`Administrators: ${admins.length}`);
|
|
10420
11550
|
* ```
|
|
10421
11551
|
*/
|
|
10422
11552
|
async getMembers(businessId, options) {
|
|
@@ -13656,89 +14786,6 @@ class FileManager {
|
|
|
13656
14786
|
}
|
|
13657
14787
|
}
|
|
13658
14788
|
|
|
13659
|
-
/**
|
|
13660
|
-
* Tenant Manager - Clean, high-level interface for tenant operations
|
|
13661
|
-
*
|
|
13662
|
-
* Provides a simplified API for common tenant management tasks while maintaining
|
|
13663
|
-
* access to the full tenant SDK for advanced use cases.
|
|
13664
|
-
*/
|
|
13665
|
-
class TenantManager {
|
|
13666
|
-
constructor(apiClient) {
|
|
13667
|
-
this.apiClient = apiClient;
|
|
13668
|
-
const tenantApi = new TenantApi(apiClient);
|
|
13669
|
-
this.tenantService = new TenantService(tenantApi);
|
|
13670
|
-
}
|
|
13671
|
-
/**
|
|
13672
|
-
* Get current tenant information
|
|
13673
|
-
*
|
|
13674
|
-
* @returns Promise resolving to tenant data
|
|
13675
|
-
*/
|
|
13676
|
-
async getTenantInfo() {
|
|
13677
|
-
return this.tenantService.getRemoteTenant();
|
|
13678
|
-
}
|
|
13679
|
-
/**
|
|
13680
|
-
* Get tenant login token
|
|
13681
|
-
*
|
|
13682
|
-
* @returns Promise resolving to login token
|
|
13683
|
-
*/
|
|
13684
|
-
async getLoginToken() {
|
|
13685
|
-
return this.tenantService.getRemoteLoginToken();
|
|
13686
|
-
}
|
|
13687
|
-
/**
|
|
13688
|
-
* Get tenant client configuration
|
|
13689
|
-
*
|
|
13690
|
-
* @returns Promise resolving to client config
|
|
13691
|
-
*/
|
|
13692
|
-
async getClientConfig() {
|
|
13693
|
-
return this.tenantService.getRemoteClientConfig();
|
|
13694
|
-
}
|
|
13695
|
-
/**
|
|
13696
|
-
* Admin: Update tenant data
|
|
13697
|
-
*
|
|
13698
|
-
* @param tenantData - Updated tenant data
|
|
13699
|
-
* @returns Promise resolving to updated tenant
|
|
13700
|
-
*/
|
|
13701
|
-
async updateTenant(tenantData) {
|
|
13702
|
-
return this.tenantService.updateRemoteTenant(tenantData);
|
|
13703
|
-
}
|
|
13704
|
-
/**
|
|
13705
|
-
* Admin: Get all admins
|
|
13706
|
-
*
|
|
13707
|
-
* @param options - Pagination options
|
|
13708
|
-
* @returns Promise resolving to paginated admins
|
|
13709
|
-
*/
|
|
13710
|
-
async getAdmins(options) {
|
|
13711
|
-
return this.tenantService.getAdmins(options);
|
|
13712
|
-
}
|
|
13713
|
-
/**
|
|
13714
|
-
* Admin: Create new admin
|
|
13715
|
-
*
|
|
13716
|
-
* @param adminData - Admin data
|
|
13717
|
-
* @returns Promise resolving to created admin
|
|
13718
|
-
*/
|
|
13719
|
-
async createAdmin(adminData) {
|
|
13720
|
-
return this.tenantService.postAdmin(adminData);
|
|
13721
|
-
}
|
|
13722
|
-
/**
|
|
13723
|
-
* Admin: Update existing admin
|
|
13724
|
-
*
|
|
13725
|
-
* @param adminId - ID of the admin to update
|
|
13726
|
-
* @param adminData - Updated admin data
|
|
13727
|
-
* @returns Promise resolving to updated admin
|
|
13728
|
-
*/
|
|
13729
|
-
async updateAdmin(adminId, adminData) {
|
|
13730
|
-
return this.tenantService.putAdmin(adminId, adminData);
|
|
13731
|
-
}
|
|
13732
|
-
/**
|
|
13733
|
-
* Get the tenant service for advanced operations
|
|
13734
|
-
*
|
|
13735
|
-
* @returns TenantService instance
|
|
13736
|
-
*/
|
|
13737
|
-
getTenantService() {
|
|
13738
|
-
return this.tenantService;
|
|
13739
|
-
}
|
|
13740
|
-
}
|
|
13741
|
-
|
|
13742
14789
|
/**
|
|
13743
14790
|
* Platform-Agnostic API Key API Client
|
|
13744
14791
|
*
|
|
@@ -14333,6 +15380,36 @@ class AnalyticsManager {
|
|
|
14333
15380
|
async getRetentionAnalytics(request = {}) {
|
|
14334
15381
|
return this.analyticsService.getRetentionAnalytics(request);
|
|
14335
15382
|
}
|
|
15383
|
+
/**
|
|
15384
|
+
* Get tag usage analytics across entities
|
|
15385
|
+
*
|
|
15386
|
+
* Aggregates tag usage across all taggable entities (campaigns, redemptions,
|
|
15387
|
+
* businesses, token metadata, user status types). Perfect for:
|
|
15388
|
+
* - Tag autocomplete/suggestions
|
|
15389
|
+
* - Tag management dashboards
|
|
15390
|
+
* - Usage analytics
|
|
15391
|
+
*
|
|
15392
|
+
* @param request - Optional filters for entity types
|
|
15393
|
+
* @returns Promise resolving to aggregated tag usage data
|
|
15394
|
+
*
|
|
15395
|
+
* @example Get all tags for autocomplete
|
|
15396
|
+
* ```typescript
|
|
15397
|
+
* const allTags = await sdk.analytics.getTagAnalytics();
|
|
15398
|
+
* const suggestions = allTags.tags.map(t => t.tag);
|
|
15399
|
+
* ```
|
|
15400
|
+
*
|
|
15401
|
+
* @example Get tags only from campaigns
|
|
15402
|
+
* ```typescript
|
|
15403
|
+
* import { TaggableEntityType } from '@explorins/pers-sdk';
|
|
15404
|
+
*
|
|
15405
|
+
* const campaignTags = await sdk.analytics.getTagAnalytics({
|
|
15406
|
+
* entityTypes: [TaggableEntityType.CAMPAIGN]
|
|
15407
|
+
* });
|
|
15408
|
+
* ```
|
|
15409
|
+
*/
|
|
15410
|
+
async getTagAnalytics(request = {}) {
|
|
15411
|
+
return this.analyticsService.getTagAnalytics(request);
|
|
15412
|
+
}
|
|
14336
15413
|
/**
|
|
14337
15414
|
* Get the full analytics service for advanced operations
|
|
14338
15415
|
*
|
|
@@ -14643,6 +15720,1411 @@ class TriggerSourceManager {
|
|
|
14643
15720
|
}
|
|
14644
15721
|
}
|
|
14645
15722
|
|
|
15723
|
+
/**
|
|
15724
|
+
* Webhook API - Low-level API client for webhook operations
|
|
15725
|
+
*
|
|
15726
|
+
* Backend routes:
|
|
15727
|
+
* - Admin CRUD: /hooks (requires tenant auth)
|
|
15728
|
+
* - Proxy/Trigger: /hooks/:projectKey/:hookId (public, identified by projectKey)
|
|
15729
|
+
* - Callback: /hooks/:projectKey/executions/:executionId/callback (public)
|
|
15730
|
+
*
|
|
15731
|
+
* @internal Use WebhookService or WebhookManager for higher-level operations
|
|
15732
|
+
*/
|
|
15733
|
+
class WebhookApi {
|
|
15734
|
+
constructor(apiClient) {
|
|
15735
|
+
this.apiClient = apiClient;
|
|
15736
|
+
this.basePath = '/hooks';
|
|
15737
|
+
}
|
|
15738
|
+
// ================================
|
|
15739
|
+
// ADMIN: Webhook Configuration CRUD
|
|
15740
|
+
// All require tenant auth (handled by apiClient)
|
|
15741
|
+
// ================================
|
|
15742
|
+
/**
|
|
15743
|
+
* List all webhooks (Admin)
|
|
15744
|
+
* GET /hooks
|
|
15745
|
+
*/
|
|
15746
|
+
async listWebhooks(options) {
|
|
15747
|
+
const params = new URLSearchParams();
|
|
15748
|
+
if (options?.active !== undefined)
|
|
15749
|
+
params.append('active', String(options.active));
|
|
15750
|
+
if (options?.page)
|
|
15751
|
+
params.append('page', String(options.page));
|
|
15752
|
+
if (options?.limit)
|
|
15753
|
+
params.append('limit', String(options.limit));
|
|
15754
|
+
if (options?.search)
|
|
15755
|
+
params.append('search', options.search);
|
|
15756
|
+
const queryString = params.toString();
|
|
15757
|
+
const url = queryString ? `${this.basePath}?${queryString}` : this.basePath;
|
|
15758
|
+
return this.apiClient.get(url);
|
|
15759
|
+
}
|
|
15760
|
+
/**
|
|
15761
|
+
* Get webhook by ID (Admin)
|
|
15762
|
+
* GET /hooks/:hookId
|
|
15763
|
+
*/
|
|
15764
|
+
async getWebhookById(webhookId) {
|
|
15765
|
+
return this.apiClient.get(`${this.basePath}/${webhookId}`);
|
|
15766
|
+
}
|
|
15767
|
+
/**
|
|
15768
|
+
* Create a new webhook (Admin)
|
|
15769
|
+
* POST /hooks
|
|
15770
|
+
*/
|
|
15771
|
+
async createWebhook(webhook) {
|
|
15772
|
+
return this.apiClient.post(this.basePath, webhook);
|
|
15773
|
+
}
|
|
15774
|
+
/**
|
|
15775
|
+
* Update a webhook (Admin)
|
|
15776
|
+
* PUT /hooks/:hookId
|
|
15777
|
+
*/
|
|
15778
|
+
async updateWebhook(webhookId, webhook) {
|
|
15779
|
+
return this.apiClient.put(`${this.basePath}/${webhookId}`, webhook);
|
|
15780
|
+
}
|
|
15781
|
+
/**
|
|
15782
|
+
* Delete a webhook (Admin)
|
|
15783
|
+
* DELETE /hooks/:hookId
|
|
15784
|
+
*/
|
|
15785
|
+
async deleteWebhook(webhookId) {
|
|
15786
|
+
return this.apiClient.delete(`${this.basePath}/${webhookId}`);
|
|
15787
|
+
}
|
|
15788
|
+
// ================================
|
|
15789
|
+
// Webhook Triggering via Proxy
|
|
15790
|
+
// Route: /hooks/:projectKey/:hookId
|
|
15791
|
+
// Public endpoint identified by projectKey in URL
|
|
15792
|
+
// ================================
|
|
15793
|
+
/**
|
|
15794
|
+
* Trigger a webhook programmatically
|
|
15795
|
+
*
|
|
15796
|
+
* Uses the proxy endpoint which requires projectKey in the URL path.
|
|
15797
|
+
* The projectKey is obtained from SDK config.
|
|
15798
|
+
*
|
|
15799
|
+
* ALL /hooks/:projectKey/:hookId
|
|
15800
|
+
*/
|
|
15801
|
+
async triggerWebhook(request, projectKey) {
|
|
15802
|
+
const { hookId, method = 'POST', body, queryParams, waitForCallback, callbackTimeoutMs } = request;
|
|
15803
|
+
// Build query string from queryParams
|
|
15804
|
+
const params = new URLSearchParams();
|
|
15805
|
+
if (queryParams) {
|
|
15806
|
+
Object.entries(queryParams).forEach(([key, value]) => params.append(key, value));
|
|
15807
|
+
}
|
|
15808
|
+
if (waitForCallback)
|
|
15809
|
+
params.append('waitForCallback', 'true');
|
|
15810
|
+
if (callbackTimeoutMs)
|
|
15811
|
+
params.append('callbackTimeoutMs', String(callbackTimeoutMs));
|
|
15812
|
+
const queryString = params.toString();
|
|
15813
|
+
// Use proxy path with projectKey
|
|
15814
|
+
const proxyPath = `${this.basePath}/${projectKey}/${hookId}`;
|
|
15815
|
+
const url = queryString ? `${proxyPath}?${queryString}` : proxyPath;
|
|
15816
|
+
// Use appropriate HTTP method - backend returns WebhookTriggerResponseDTO
|
|
15817
|
+
switch (method) {
|
|
15818
|
+
case 'GET':
|
|
15819
|
+
return this.apiClient.get(url);
|
|
15820
|
+
case 'PUT':
|
|
15821
|
+
return this.apiClient.put(url, body);
|
|
15822
|
+
case 'DELETE':
|
|
15823
|
+
return this.apiClient.delete(url);
|
|
15824
|
+
case 'POST':
|
|
15825
|
+
default:
|
|
15826
|
+
return this.apiClient.post(url, body);
|
|
15827
|
+
}
|
|
15828
|
+
}
|
|
15829
|
+
// ================================
|
|
15830
|
+
// Execution History (Admin)
|
|
15831
|
+
// GET /hooks/executions
|
|
15832
|
+
// ================================
|
|
15833
|
+
/**
|
|
15834
|
+
* List webhook executions (Admin)
|
|
15835
|
+
* GET /hooks/executions
|
|
15836
|
+
*/
|
|
15837
|
+
async listExecutions(options) {
|
|
15838
|
+
const params = new URLSearchParams();
|
|
15839
|
+
if (options?.webhookId)
|
|
15840
|
+
params.append('hookId', options.webhookId);
|
|
15841
|
+
if (options?.status)
|
|
15842
|
+
params.append('status', options.status);
|
|
15843
|
+
if (options?.fromDate)
|
|
15844
|
+
params.append('dateFrom', options.fromDate);
|
|
15845
|
+
if (options?.toDate)
|
|
15846
|
+
params.append('dateTo', options.toDate);
|
|
15847
|
+
if (options?.page)
|
|
15848
|
+
params.append('page', String(options.page));
|
|
15849
|
+
if (options?.limit)
|
|
15850
|
+
params.append('limit', String(options.limit));
|
|
15851
|
+
const queryString = params.toString();
|
|
15852
|
+
const url = queryString ? `${this.basePath}/executions?${queryString}` : `${this.basePath}/executions`;
|
|
15853
|
+
return this.apiClient.get(url);
|
|
15854
|
+
}
|
|
15855
|
+
/**
|
|
15856
|
+
* Get execution by ID (Admin)
|
|
15857
|
+
* Note: This endpoint doesn't exist in current backend - would need to filter by ID
|
|
15858
|
+
*/
|
|
15859
|
+
async getExecutionById(executionId) {
|
|
15860
|
+
// Filter executions by ID since direct endpoint doesn't exist
|
|
15861
|
+
const result = await this.listExecutions({ page: 1, limit: 1 });
|
|
15862
|
+
const execution = result.data.find(e => e.id === executionId);
|
|
15863
|
+
if (!execution) {
|
|
15864
|
+
throw new Error(`Execution ${executionId} not found`);
|
|
15865
|
+
}
|
|
15866
|
+
return execution;
|
|
15867
|
+
}
|
|
15868
|
+
// ================================
|
|
15869
|
+
// Callback (Public - called by external systems)
|
|
15870
|
+
// POST /hooks/:projectKey/executions/:executionId/callback
|
|
15871
|
+
// ================================
|
|
15872
|
+
/**
|
|
15873
|
+
* Send callback result (for testing or manual callback trigger)
|
|
15874
|
+
* This is normally called by external systems like n8n
|
|
15875
|
+
*
|
|
15876
|
+
* POST /hooks/:projectKey/executions/:executionId/callback
|
|
15877
|
+
*/
|
|
15878
|
+
async sendCallback(executionId, callback, projectKey) {
|
|
15879
|
+
return this.apiClient.post(`${this.basePath}/${projectKey}/executions/${executionId}/callback`, callback);
|
|
15880
|
+
}
|
|
15881
|
+
}
|
|
15882
|
+
|
|
15883
|
+
/**
|
|
15884
|
+
* Webhook Service - Business logic layer for webhook operations
|
|
15885
|
+
*
|
|
15886
|
+
* Provides higher-level operations on top of WebhookApi including:
|
|
15887
|
+
* - Validation and error handling
|
|
15888
|
+
* - Convenience methods for common patterns
|
|
15889
|
+
* - Async workflow support with callbacks
|
|
15890
|
+
*
|
|
15891
|
+
* Note: Trigger operations require projectKey which is obtained from SDK config.
|
|
15892
|
+
*
|
|
15893
|
+
* @group Services
|
|
15894
|
+
* @category Webhook
|
|
15895
|
+
*/
|
|
15896
|
+
class WebhookService {
|
|
15897
|
+
constructor(webhookApi, projectKey) {
|
|
15898
|
+
this.webhookApi = webhookApi;
|
|
15899
|
+
this.projectKey = projectKey;
|
|
15900
|
+
}
|
|
15901
|
+
// ================================
|
|
15902
|
+
// Admin: Webhook Configuration
|
|
15903
|
+
// ================================
|
|
15904
|
+
/**
|
|
15905
|
+
* Get all webhooks with optional filters
|
|
15906
|
+
*/
|
|
15907
|
+
async getWebhooks(options) {
|
|
15908
|
+
return this.webhookApi.listWebhooks(options);
|
|
15909
|
+
}
|
|
15910
|
+
/**
|
|
15911
|
+
* Get active webhooks only
|
|
15912
|
+
*/
|
|
15913
|
+
async getActiveWebhooks() {
|
|
15914
|
+
return this.webhookApi.listWebhooks({ active: true });
|
|
15915
|
+
}
|
|
15916
|
+
/**
|
|
15917
|
+
* Get webhook by ID
|
|
15918
|
+
*/
|
|
15919
|
+
async getWebhookById(webhookId) {
|
|
15920
|
+
return this.webhookApi.getWebhookById(webhookId);
|
|
15921
|
+
}
|
|
15922
|
+
/**
|
|
15923
|
+
* Create a new webhook
|
|
15924
|
+
*/
|
|
15925
|
+
async createWebhook(webhook) {
|
|
15926
|
+
// Validate required fields
|
|
15927
|
+
if (!webhook.name?.trim()) {
|
|
15928
|
+
throw new Error('Webhook name is required');
|
|
15929
|
+
}
|
|
15930
|
+
if (!webhook.targetUrl?.trim()) {
|
|
15931
|
+
throw new Error('Target URL is required');
|
|
15932
|
+
}
|
|
15933
|
+
// Validate URL format
|
|
15934
|
+
try {
|
|
15935
|
+
new URL(webhook.targetUrl);
|
|
15936
|
+
}
|
|
15937
|
+
catch {
|
|
15938
|
+
throw new Error('Invalid target URL format');
|
|
15939
|
+
}
|
|
15940
|
+
return this.webhookApi.createWebhook(webhook);
|
|
15941
|
+
}
|
|
15942
|
+
/**
|
|
15943
|
+
* Update a webhook
|
|
15944
|
+
*/
|
|
15945
|
+
async updateWebhook(webhookId, webhook) {
|
|
15946
|
+
// Validate URL if provided (cast to access optional property from PartialType)
|
|
15947
|
+
const update = webhook;
|
|
15948
|
+
if (update.targetUrl) {
|
|
15949
|
+
try {
|
|
15950
|
+
new URL(update.targetUrl);
|
|
15951
|
+
}
|
|
15952
|
+
catch {
|
|
15953
|
+
throw new Error('Invalid target URL format');
|
|
15954
|
+
}
|
|
15955
|
+
}
|
|
15956
|
+
return this.webhookApi.updateWebhook(webhookId, webhook);
|
|
15957
|
+
}
|
|
15958
|
+
/**
|
|
15959
|
+
* Enable a webhook
|
|
15960
|
+
*/
|
|
15961
|
+
async enableWebhook(webhookId) {
|
|
15962
|
+
return this.webhookApi.updateWebhook(webhookId, { isActive: true });
|
|
15963
|
+
}
|
|
15964
|
+
/**
|
|
15965
|
+
* Disable a webhook
|
|
15966
|
+
*/
|
|
15967
|
+
async disableWebhook(webhookId) {
|
|
15968
|
+
return this.webhookApi.updateWebhook(webhookId, { isActive: false });
|
|
15969
|
+
}
|
|
15970
|
+
/**
|
|
15971
|
+
* Delete a webhook
|
|
15972
|
+
*/
|
|
15973
|
+
async deleteWebhook(webhookId) {
|
|
15974
|
+
return this.webhookApi.deleteWebhook(webhookId);
|
|
15975
|
+
}
|
|
15976
|
+
// ================================
|
|
15977
|
+
// Webhook Triggering (via Proxy)
|
|
15978
|
+
// Uses /hooks/:projectKey/:hookId
|
|
15979
|
+
// ================================
|
|
15980
|
+
/**
|
|
15981
|
+
* Trigger a webhook with full control over request parameters
|
|
15982
|
+
*/
|
|
15983
|
+
async triggerWebhook(request) {
|
|
15984
|
+
return this.webhookApi.triggerWebhook(request, this.projectKey);
|
|
15985
|
+
}
|
|
15986
|
+
/**
|
|
15987
|
+
* Simple POST trigger with JSON body
|
|
15988
|
+
*/
|
|
15989
|
+
async post(hookId, body) {
|
|
15990
|
+
return this.webhookApi.triggerWebhook({
|
|
15991
|
+
hookId,
|
|
15992
|
+
method: exports.WebhookMethod.POST,
|
|
15993
|
+
body
|
|
15994
|
+
}, this.projectKey);
|
|
15995
|
+
}
|
|
15996
|
+
/**
|
|
15997
|
+
* Simple GET trigger
|
|
15998
|
+
*/
|
|
15999
|
+
async get(hookId, queryParams) {
|
|
16000
|
+
return this.webhookApi.triggerWebhook({
|
|
16001
|
+
hookId,
|
|
16002
|
+
method: exports.WebhookMethod.GET,
|
|
16003
|
+
queryParams
|
|
16004
|
+
}, this.projectKey);
|
|
16005
|
+
}
|
|
16006
|
+
/**
|
|
16007
|
+
* Trigger and wait for async callback (for workflow systems like n8n)
|
|
16008
|
+
*
|
|
16009
|
+
* @param hookId - Webhook ID to trigger
|
|
16010
|
+
* @param body - Request body
|
|
16011
|
+
* @param timeoutMs - How long to wait for callback (default: 30000ms)
|
|
16012
|
+
*/
|
|
16013
|
+
async triggerAndWait(hookId, body, timeoutMs = 30000) {
|
|
16014
|
+
return this.webhookApi.triggerWebhook({
|
|
16015
|
+
hookId,
|
|
16016
|
+
method: exports.WebhookMethod.POST,
|
|
16017
|
+
body,
|
|
16018
|
+
waitForCallback: true,
|
|
16019
|
+
callbackTimeoutMs: timeoutMs
|
|
16020
|
+
}, this.projectKey);
|
|
16021
|
+
}
|
|
16022
|
+
// ================================
|
|
16023
|
+
// Execution History (Admin)
|
|
16024
|
+
// ================================
|
|
16025
|
+
/**
|
|
16026
|
+
* Get execution history with filters
|
|
16027
|
+
*/
|
|
16028
|
+
async getExecutions(options) {
|
|
16029
|
+
return this.webhookApi.listExecutions(options);
|
|
16030
|
+
}
|
|
16031
|
+
/**
|
|
16032
|
+
* Get executions for a specific webhook
|
|
16033
|
+
*/
|
|
16034
|
+
async getWebhookExecutions(webhookId, options) {
|
|
16035
|
+
return this.webhookApi.listExecutions({ ...options, webhookId });
|
|
16036
|
+
}
|
|
16037
|
+
/**
|
|
16038
|
+
* Get failed executions
|
|
16039
|
+
*/
|
|
16040
|
+
async getFailedExecutions(options) {
|
|
16041
|
+
return this.webhookApi.listExecutions({ ...options, status: exports.WebhookExecutionStatus.FAILED });
|
|
16042
|
+
}
|
|
16043
|
+
/**
|
|
16044
|
+
* Get execution details by ID
|
|
16045
|
+
*/
|
|
16046
|
+
async getExecutionById(executionId) {
|
|
16047
|
+
return this.webhookApi.getExecutionById(executionId);
|
|
16048
|
+
}
|
|
16049
|
+
// ================================
|
|
16050
|
+
// Callback (for testing/manual triggers)
|
|
16051
|
+
// ================================
|
|
16052
|
+
/**
|
|
16053
|
+
* Send a callback result for an execution
|
|
16054
|
+
* Normally this is called by external systems like n8n
|
|
16055
|
+
*/
|
|
16056
|
+
async sendCallback(executionId, callback) {
|
|
16057
|
+
return this.webhookApi.sendCallback(executionId, callback, this.projectKey);
|
|
16058
|
+
}
|
|
16059
|
+
}
|
|
16060
|
+
|
|
16061
|
+
/**
|
|
16062
|
+
* Webhook Manager - Clean, high-level interface for webhook operations
|
|
16063
|
+
*
|
|
16064
|
+
* Webhooks enable integration with external systems (n8n, Zapier, custom backends):
|
|
16065
|
+
* - **Admin operations**: Create, configure, and manage webhook endpoints
|
|
16066
|
+
* - **Trigger operations**: Programmatically trigger webhooks with payloads
|
|
16067
|
+
* - **Async workflows**: Wait for callbacks from workflow systems
|
|
16068
|
+
* - **Monitoring**: Track execution history
|
|
16069
|
+
*
|
|
16070
|
+
* ## Security Model
|
|
16071
|
+
* - Admin endpoints require tenant authentication
|
|
16072
|
+
* - Trigger endpoints work with user/business/tenant auth
|
|
16073
|
+
* - Caller identity is passed to webhook target
|
|
16074
|
+
*
|
|
16075
|
+
* @group Managers
|
|
16076
|
+
* @category Webhook Management
|
|
16077
|
+
*
|
|
16078
|
+
* @example Basic Webhook Operations
|
|
16079
|
+
* ```typescript
|
|
16080
|
+
* // Admin: Create a webhook for order processing
|
|
16081
|
+
* const webhook = await sdk.webhooks.create({
|
|
16082
|
+
* name: 'Process Order',
|
|
16083
|
+
* targetUrl: 'https://n8n.example.com/webhook/orders',
|
|
16084
|
+
* method: 'POST',
|
|
16085
|
+
* headers: { 'X-Custom-Header': 'value' }
|
|
16086
|
+
* });
|
|
16087
|
+
*
|
|
16088
|
+
* // Trigger webhook with order data
|
|
16089
|
+
* const result = await sdk.webhooks.trigger(webhook.id, {
|
|
16090
|
+
* orderId: 'order-123',
|
|
16091
|
+
* items: [{ productId: 'prod-1', quantity: 2 }]
|
|
16092
|
+
* });
|
|
16093
|
+
*
|
|
16094
|
+
* console.log('Execution ID:', result.executionId);
|
|
16095
|
+
* ```
|
|
16096
|
+
*
|
|
16097
|
+
* @example Async Workflow with Callback
|
|
16098
|
+
* ```typescript
|
|
16099
|
+
* // Trigger and wait for workflow completion (up to 30s)
|
|
16100
|
+
* const result = await sdk.webhooks.triggerAndWait(
|
|
16101
|
+
* 'ai-processing-webhook',
|
|
16102
|
+
* { prompt: 'Generate report for Q1' },
|
|
16103
|
+
* 30000
|
|
16104
|
+
* );
|
|
16105
|
+
*
|
|
16106
|
+
* if (result.status === 'COMPLETED') {
|
|
16107
|
+
* console.log('AI Response:', result.body);
|
|
16108
|
+
* }
|
|
16109
|
+
* ```
|
|
16110
|
+
*/
|
|
16111
|
+
class WebhookManager {
|
|
16112
|
+
constructor(apiClient, projectKey, events) {
|
|
16113
|
+
this.apiClient = apiClient;
|
|
16114
|
+
this.projectKey = projectKey;
|
|
16115
|
+
this.events = events;
|
|
16116
|
+
const webhookApi = new WebhookApi(apiClient);
|
|
16117
|
+
this.webhookService = new WebhookService(webhookApi, projectKey);
|
|
16118
|
+
}
|
|
16119
|
+
// ================================
|
|
16120
|
+
// Admin: Webhook Configuration
|
|
16121
|
+
// ================================
|
|
16122
|
+
/**
|
|
16123
|
+
* Get all webhooks with optional filters (Admin)
|
|
16124
|
+
*
|
|
16125
|
+
* @param options - Filter and pagination options
|
|
16126
|
+
* @returns Promise resolving to paginated webhooks
|
|
16127
|
+
*
|
|
16128
|
+
* @example
|
|
16129
|
+
* ```typescript
|
|
16130
|
+
* // Get all webhooks
|
|
16131
|
+
* const webhooks = await sdk.webhooks.getAll();
|
|
16132
|
+
*
|
|
16133
|
+
* // Get only active webhooks
|
|
16134
|
+
* const active = await sdk.webhooks.getAll({ active: true });
|
|
16135
|
+
*
|
|
16136
|
+
* // Search by name
|
|
16137
|
+
* const found = await sdk.webhooks.getAll({ search: 'order' });
|
|
16138
|
+
* ```
|
|
16139
|
+
*/
|
|
16140
|
+
async getAll(options) {
|
|
16141
|
+
return this.webhookService.getWebhooks(options);
|
|
16142
|
+
}
|
|
16143
|
+
/**
|
|
16144
|
+
* Get active webhooks only (Admin)
|
|
16145
|
+
*/
|
|
16146
|
+
async getActive() {
|
|
16147
|
+
return this.webhookService.getActiveWebhooks();
|
|
16148
|
+
}
|
|
16149
|
+
/**
|
|
16150
|
+
* Get webhook by ID (Admin)
|
|
16151
|
+
*
|
|
16152
|
+
* @param webhookId - UUID of the webhook
|
|
16153
|
+
* @returns Promise resolving to webhook details
|
|
16154
|
+
*/
|
|
16155
|
+
async getById(webhookId) {
|
|
16156
|
+
return this.webhookService.getWebhookById(webhookId);
|
|
16157
|
+
}
|
|
16158
|
+
/**
|
|
16159
|
+
* Create a new webhook (Admin)
|
|
16160
|
+
*
|
|
16161
|
+
* @param webhook - Webhook configuration
|
|
16162
|
+
* @returns Promise resolving to created webhook
|
|
16163
|
+
*
|
|
16164
|
+
* @example
|
|
16165
|
+
* ```typescript
|
|
16166
|
+
* const webhook = await sdk.webhooks.create({
|
|
16167
|
+
* name: 'Order Notifications',
|
|
16168
|
+
* targetUrl: 'https://api.example.com/webhooks/orders',
|
|
16169
|
+
* method: 'POST',
|
|
16170
|
+
* headers: {
|
|
16171
|
+
* 'Authorization': 'Bearer secret-token'
|
|
16172
|
+
* },
|
|
16173
|
+
* rateLimitPerMinute: 60
|
|
16174
|
+
* });
|
|
16175
|
+
* ```
|
|
16176
|
+
*/
|
|
16177
|
+
async create(webhook) {
|
|
16178
|
+
const result = await this.webhookService.createWebhook(webhook);
|
|
16179
|
+
this.events?.emitSuccess({
|
|
16180
|
+
domain: 'webhook',
|
|
16181
|
+
type: 'WEBHOOK_CREATED',
|
|
16182
|
+
userMessage: 'Webhook created successfully',
|
|
16183
|
+
details: { webhookId: result.id, name: result.name }
|
|
16184
|
+
});
|
|
16185
|
+
return result;
|
|
16186
|
+
}
|
|
16187
|
+
/**
|
|
16188
|
+
* Update a webhook (Admin)
|
|
16189
|
+
*
|
|
16190
|
+
* @param webhookId - UUID of the webhook to update
|
|
16191
|
+
* @param webhook - Updated configuration (partial)
|
|
16192
|
+
* @returns Promise resolving to updated webhook
|
|
16193
|
+
*/
|
|
16194
|
+
async update(webhookId, webhook) {
|
|
16195
|
+
const result = await this.webhookService.updateWebhook(webhookId, webhook);
|
|
16196
|
+
this.events?.emitSuccess({
|
|
16197
|
+
domain: 'webhook',
|
|
16198
|
+
type: 'WEBHOOK_UPDATED',
|
|
16199
|
+
userMessage: 'Webhook updated successfully',
|
|
16200
|
+
details: { webhookId }
|
|
16201
|
+
});
|
|
16202
|
+
return result;
|
|
16203
|
+
}
|
|
16204
|
+
/**
|
|
16205
|
+
* Enable a webhook (Admin)
|
|
16206
|
+
*/
|
|
16207
|
+
async enable(webhookId) {
|
|
16208
|
+
return this.webhookService.enableWebhook(webhookId);
|
|
16209
|
+
}
|
|
16210
|
+
/**
|
|
16211
|
+
* Disable a webhook (Admin)
|
|
16212
|
+
*/
|
|
16213
|
+
async disable(webhookId) {
|
|
16214
|
+
return this.webhookService.disableWebhook(webhookId);
|
|
16215
|
+
}
|
|
16216
|
+
/**
|
|
16217
|
+
* Delete a webhook (Admin)
|
|
16218
|
+
*
|
|
16219
|
+
* @param webhookId - UUID of the webhook to delete
|
|
16220
|
+
* @returns Promise resolving to deletion confirmation
|
|
16221
|
+
*/
|
|
16222
|
+
async delete(webhookId) {
|
|
16223
|
+
const result = await this.webhookService.deleteWebhook(webhookId);
|
|
16224
|
+
this.events?.emitSuccess({
|
|
16225
|
+
domain: 'webhook',
|
|
16226
|
+
type: 'WEBHOOK_DELETED',
|
|
16227
|
+
userMessage: 'Webhook deleted successfully',
|
|
16228
|
+
details: { webhookId }
|
|
16229
|
+
});
|
|
16230
|
+
return result;
|
|
16231
|
+
}
|
|
16232
|
+
// ================================
|
|
16233
|
+
// Webhook Triggering
|
|
16234
|
+
// ================================
|
|
16235
|
+
/**
|
|
16236
|
+
* Trigger a webhook with GET method
|
|
16237
|
+
*
|
|
16238
|
+
* Sends a GET request through the webhook proxy. Returns the full response
|
|
16239
|
+
* including execution metadata and raw data from the target.
|
|
16240
|
+
*
|
|
16241
|
+
* @param hookId - Webhook ID to trigger
|
|
16242
|
+
* @param queryParams - Optional query parameters
|
|
16243
|
+
* @returns Promise resolving to trigger response with metadata and data
|
|
16244
|
+
*
|
|
16245
|
+
* @example
|
|
16246
|
+
* ```typescript
|
|
16247
|
+
* // Fetch data from external API via webhook proxy
|
|
16248
|
+
* const result = await sdk.webhooks.get('user-directory-webhook');
|
|
16249
|
+
* console.log('Success:', result.success);
|
|
16250
|
+
* console.log('Data:', result.data); // Raw data from target
|
|
16251
|
+
* ```
|
|
16252
|
+
*/
|
|
16253
|
+
async get(hookId, queryParams) {
|
|
16254
|
+
return this.webhookService.get(hookId, queryParams);
|
|
16255
|
+
}
|
|
16256
|
+
/**
|
|
16257
|
+
* Trigger a webhook with POST method
|
|
16258
|
+
*
|
|
16259
|
+
* Sends data to the configured webhook target. The caller's identity
|
|
16260
|
+
* (user/business/tenant) is included in the request context.
|
|
16261
|
+
*
|
|
16262
|
+
* @param hookId - Webhook ID to trigger
|
|
16263
|
+
* @param body - Payload to send
|
|
16264
|
+
* @returns Promise resolving to trigger response with execution metadata
|
|
16265
|
+
*
|
|
16266
|
+
* @example
|
|
16267
|
+
* ```typescript
|
|
16268
|
+
* const result = await sdk.webhooks.trigger('order-webhook', {
|
|
16269
|
+
* orderId: 'order-123',
|
|
16270
|
+
* action: 'created',
|
|
16271
|
+
* items: [...]
|
|
16272
|
+
* });
|
|
16273
|
+
*
|
|
16274
|
+
* console.log('Success:', result.success);
|
|
16275
|
+
* console.log('Execution ID:', result.executionId);
|
|
16276
|
+
* console.log('Response:', result.data);
|
|
16277
|
+
* ```
|
|
16278
|
+
*/
|
|
16279
|
+
async trigger(hookId, body) {
|
|
16280
|
+
const result = await this.webhookService.post(hookId, body);
|
|
16281
|
+
this.events?.emitSuccess({
|
|
16282
|
+
domain: 'webhook',
|
|
16283
|
+
type: 'WEBHOOK_TRIGGERED',
|
|
16284
|
+
userMessage: 'Webhook triggered',
|
|
16285
|
+
details: { hookId, executionId: result.executionId }
|
|
16286
|
+
});
|
|
16287
|
+
return result;
|
|
16288
|
+
}
|
|
16289
|
+
/**
|
|
16290
|
+
* Trigger a webhook and wait for async callback
|
|
16291
|
+
*
|
|
16292
|
+
* Use this for workflow systems (n8n, Zapier) that need time to process
|
|
16293
|
+
* and return results via callback. The SDK will wait for the callback
|
|
16294
|
+
* up to the specified timeout.
|
|
16295
|
+
*
|
|
16296
|
+
* @param hookId - Webhook ID to trigger
|
|
16297
|
+
* @param body - Payload to send
|
|
16298
|
+
* @param timeoutMs - Max time to wait for callback (default: 30s)
|
|
16299
|
+
* @returns Promise resolving when callback received or timeout
|
|
16300
|
+
*
|
|
16301
|
+
* @example
|
|
16302
|
+
* ```typescript
|
|
16303
|
+
* // Trigger AI workflow and wait for result
|
|
16304
|
+
* const result = await sdk.webhooks.triggerAndWait(
|
|
16305
|
+
* 'ai-analysis-webhook',
|
|
16306
|
+
* { documentId: 'doc-123', analysisType: 'sentiment' },
|
|
16307
|
+
* 60000 // Wait up to 60 seconds
|
|
16308
|
+
* );
|
|
16309
|
+
*
|
|
16310
|
+
* if (result.success) {
|
|
16311
|
+
* console.log('Analysis:', result.data);
|
|
16312
|
+
* }
|
|
16313
|
+
* ```
|
|
16314
|
+
*/
|
|
16315
|
+
async triggerAndWait(hookId, body, timeoutMs = 30000) {
|
|
16316
|
+
return this.webhookService.triggerAndWait(hookId, body, timeoutMs);
|
|
16317
|
+
}
|
|
16318
|
+
// ================================
|
|
16319
|
+
// Execution History & Monitoring
|
|
16320
|
+
// ================================
|
|
16321
|
+
/**
|
|
16322
|
+
* Get webhook execution history (Admin)
|
|
16323
|
+
*
|
|
16324
|
+
* @param options - Filter and pagination options
|
|
16325
|
+
* @returns Promise resolving to paginated executions
|
|
16326
|
+
*
|
|
16327
|
+
* @example
|
|
16328
|
+
* ```typescript
|
|
16329
|
+
* // Get recent failed executions
|
|
16330
|
+
* const failed = await sdk.webhooks.getExecutions({ status: 'FAILED' });
|
|
16331
|
+
*
|
|
16332
|
+
* // Get executions for specific webhook
|
|
16333
|
+
* const executions = await sdk.webhooks.getExecutions({
|
|
16334
|
+
* webhookId: 'webhook-123',
|
|
16335
|
+
* fromDate: '2024-01-01'
|
|
16336
|
+
* });
|
|
16337
|
+
* ```
|
|
16338
|
+
*/
|
|
16339
|
+
async getExecutions(options) {
|
|
16340
|
+
return this.webhookService.getExecutions(options);
|
|
16341
|
+
}
|
|
16342
|
+
/**
|
|
16343
|
+
* Get execution details by ID (Admin)
|
|
16344
|
+
*/
|
|
16345
|
+
async getExecutionById(executionId) {
|
|
16346
|
+
return this.webhookService.getExecutionById(executionId);
|
|
16347
|
+
}
|
|
16348
|
+
/**
|
|
16349
|
+
* Get the full webhook service for advanced operations
|
|
16350
|
+
*
|
|
16351
|
+
* @returns WebhookService instance with full API access
|
|
16352
|
+
*/
|
|
16353
|
+
getWebhookService() {
|
|
16354
|
+
return this.webhookService;
|
|
16355
|
+
}
|
|
16356
|
+
}
|
|
16357
|
+
|
|
16358
|
+
/**
|
|
16359
|
+
* PERS Events Client
|
|
16360
|
+
*
|
|
16361
|
+
* Lightweight WebSocket client for real-time blockchain event streaming.
|
|
16362
|
+
* Connects to the PERS WS Relay server to receive events for user's wallets.
|
|
16363
|
+
*
|
|
16364
|
+
* ## v1.2.0 Subscription Model
|
|
16365
|
+
*
|
|
16366
|
+
* JWT is used for authentication only. After connecting, you must explicitly
|
|
16367
|
+
* subscribe to wallets (user SDK) or chains (admin dashboard).
|
|
16368
|
+
*
|
|
16369
|
+
* @example User SDK - Subscribe to specific wallets
|
|
16370
|
+
* ```typescript
|
|
16371
|
+
* const client = new PersEventsClient({
|
|
16372
|
+
* wsUrl: 'wss://events.pers.ninja',
|
|
16373
|
+
* autoReconnect: true
|
|
16374
|
+
* });
|
|
16375
|
+
*
|
|
16376
|
+
* await client.connect(jwtToken);
|
|
16377
|
+
*
|
|
16378
|
+
* // Subscribe to user's wallets
|
|
16379
|
+
* await client.subscribeWallets([
|
|
16380
|
+
* { address: '0x123...', chainId: 39123 }
|
|
16381
|
+
* ]);
|
|
16382
|
+
*
|
|
16383
|
+
* client.on('Transfer', (event) => {
|
|
16384
|
+
* console.log(`Received ${event.data.value} tokens`);
|
|
16385
|
+
* });
|
|
16386
|
+
* ```
|
|
16387
|
+
*
|
|
16388
|
+
* @example Admin Dashboard - Subscribe to all events on chains
|
|
16389
|
+
* ```typescript
|
|
16390
|
+
* await client.connect(adminJwtToken);
|
|
16391
|
+
*
|
|
16392
|
+
* // Subscribe to all events on specific chains
|
|
16393
|
+
* await client.subscribeChains([39123, 137]);
|
|
16394
|
+
*
|
|
16395
|
+
* client.on('*', (event) => {
|
|
16396
|
+
* console.log(`Chain ${event.chainId}: ${event.type}`);
|
|
16397
|
+
* });
|
|
16398
|
+
* ```
|
|
16399
|
+
*/
|
|
16400
|
+
const DEFAULT_CONFIG = {
|
|
16401
|
+
autoReconnect: true,
|
|
16402
|
+
maxReconnectAttempts: 10,
|
|
16403
|
+
reconnectDelay: 1000,
|
|
16404
|
+
connectionTimeout: 30000,
|
|
16405
|
+
debug: false,
|
|
16406
|
+
tokenRefresher: undefined,
|
|
16407
|
+
};
|
|
16408
|
+
class PersEventsClient {
|
|
16409
|
+
constructor(config) {
|
|
16410
|
+
this.ws = null;
|
|
16411
|
+
this.state = 'disconnected';
|
|
16412
|
+
this.reconnectAttempts = 0;
|
|
16413
|
+
this.reconnectTimeout = null;
|
|
16414
|
+
this.token = null;
|
|
16415
|
+
// Event handlers by type
|
|
16416
|
+
this.handlers = new Map();
|
|
16417
|
+
this.stateHandlers = new Set();
|
|
16418
|
+
// Connection info from server
|
|
16419
|
+
this.connectionInfo = null;
|
|
16420
|
+
// Current subscription state
|
|
16421
|
+
this.subscriptionState = { wallets: [], chains: [], activeChains: [] };
|
|
16422
|
+
// Pending subscription promise resolver
|
|
16423
|
+
this.pendingSubscription = null;
|
|
16424
|
+
// Subscriptions to restore on reconnect
|
|
16425
|
+
this.savedSubscriptions = { wallets: [], chains: [] };
|
|
16426
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
16427
|
+
}
|
|
16428
|
+
/**
|
|
16429
|
+
* Connect to the WS relay server
|
|
16430
|
+
* @param token - JWT token for authentication (wallets no longer required in JWT)
|
|
16431
|
+
*/
|
|
16432
|
+
async connect(token) {
|
|
16433
|
+
if (this.state === 'connected' || this.state === 'connecting') {
|
|
16434
|
+
this.log('Already connected or connecting');
|
|
16435
|
+
return;
|
|
16436
|
+
}
|
|
16437
|
+
this.token = token;
|
|
16438
|
+
this.setState('connecting');
|
|
16439
|
+
return new Promise((resolve, reject) => {
|
|
16440
|
+
// Connection timeout
|
|
16441
|
+
const connectionTimeout = setTimeout(() => {
|
|
16442
|
+
this.log('Connection timeout');
|
|
16443
|
+
this.cleanup();
|
|
16444
|
+
this.setState('error');
|
|
16445
|
+
reject(new Error(`Connection timeout after ${this.config.connectionTimeout}ms`));
|
|
16446
|
+
}, this.config.connectionTimeout);
|
|
16447
|
+
const clearTimeoutAndResolve = () => {
|
|
16448
|
+
clearTimeout(connectionTimeout);
|
|
16449
|
+
resolve();
|
|
16450
|
+
};
|
|
16451
|
+
const clearTimeoutAndReject = (err) => {
|
|
16452
|
+
clearTimeout(connectionTimeout);
|
|
16453
|
+
reject(err);
|
|
16454
|
+
};
|
|
16455
|
+
try {
|
|
16456
|
+
const url = new URL(this.config.wsUrl);
|
|
16457
|
+
url.searchParams.set('token', token);
|
|
16458
|
+
this.ws = new WebSocket(url.toString());
|
|
16459
|
+
this.ws.onopen = () => {
|
|
16460
|
+
// Wait for server 'connected' message
|
|
16461
|
+
};
|
|
16462
|
+
this.ws.onmessage = (event) => {
|
|
16463
|
+
this.handleMessage(event.data, clearTimeoutAndResolve);
|
|
16464
|
+
};
|
|
16465
|
+
this.ws.onerror = (error) => {
|
|
16466
|
+
this.log('WebSocket error:', error);
|
|
16467
|
+
this.setState('error');
|
|
16468
|
+
clearTimeoutAndReject(new Error('Connection failed'));
|
|
16469
|
+
};
|
|
16470
|
+
this.ws.onclose = (event) => {
|
|
16471
|
+
this.log(`WebSocket closed: ${event.code} ${event.reason}`);
|
|
16472
|
+
// Only reject if we were still connecting
|
|
16473
|
+
if (this.state === 'connecting') {
|
|
16474
|
+
clearTimeoutAndReject(new Error(`Connection closed during handshake: ${event.code} ${event.reason}`));
|
|
16475
|
+
}
|
|
16476
|
+
this.handleDisconnect();
|
|
16477
|
+
};
|
|
16478
|
+
}
|
|
16479
|
+
catch (err) {
|
|
16480
|
+
this.log('Error creating WebSocket:', err);
|
|
16481
|
+
this.setState('error');
|
|
16482
|
+
clearTimeoutAndReject(err instanceof Error ? err : new Error(String(err)));
|
|
16483
|
+
}
|
|
16484
|
+
});
|
|
16485
|
+
}
|
|
16486
|
+
/**
|
|
16487
|
+
* Disconnect from the server
|
|
16488
|
+
*/
|
|
16489
|
+
disconnect() {
|
|
16490
|
+
this.config.autoReconnect = false; // Prevent reconnect
|
|
16491
|
+
this.cleanup();
|
|
16492
|
+
this.setState('disconnected');
|
|
16493
|
+
}
|
|
16494
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16495
|
+
// Subscription Methods (v1.2.0)
|
|
16496
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16497
|
+
/**
|
|
16498
|
+
* Subscribe to wallet events (User SDK)
|
|
16499
|
+
*
|
|
16500
|
+
* Receives events only for the specified wallet addresses.
|
|
16501
|
+
* Can be called multiple times to add more wallets.
|
|
16502
|
+
*
|
|
16503
|
+
* @param wallets - Array of wallet info (address + chainId)
|
|
16504
|
+
* @returns Promise that resolves when subscription is confirmed
|
|
16505
|
+
*/
|
|
16506
|
+
async subscribeWallets(wallets) {
|
|
16507
|
+
return this.sendSubscription({ type: 'subscribe', wallets });
|
|
16508
|
+
}
|
|
16509
|
+
/**
|
|
16510
|
+
* Subscribe to chain events (Admin Dashboard)
|
|
16511
|
+
*
|
|
16512
|
+
* Receives ALL events on the specified chains.
|
|
16513
|
+
* Use for admin dashboards that need to monitor all activity.
|
|
16514
|
+
*
|
|
16515
|
+
* @param chains - Array of chain IDs to subscribe to
|
|
16516
|
+
* @returns Promise that resolves when subscription is confirmed
|
|
16517
|
+
*/
|
|
16518
|
+
async subscribeChains(chains) {
|
|
16519
|
+
return this.sendSubscription({ type: 'subscribe', chains });
|
|
16520
|
+
}
|
|
16521
|
+
/**
|
|
16522
|
+
* Unsubscribe from wallet events
|
|
16523
|
+
*
|
|
16524
|
+
* @param wallets - Array of wallet info to unsubscribe from
|
|
16525
|
+
* @returns Promise that resolves when unsubscription is confirmed
|
|
16526
|
+
*/
|
|
16527
|
+
async unsubscribeWallets(wallets) {
|
|
16528
|
+
return this.sendSubscription({ type: 'unsubscribe', wallets });
|
|
16529
|
+
}
|
|
16530
|
+
/**
|
|
16531
|
+
* Unsubscribe from chain events
|
|
16532
|
+
*
|
|
16533
|
+
* @param chains - Array of chain IDs to unsubscribe from
|
|
16534
|
+
* @returns Promise that resolves when unsubscription is confirmed
|
|
16535
|
+
*/
|
|
16536
|
+
async unsubscribeChains(chains) {
|
|
16537
|
+
return this.sendSubscription({ type: 'unsubscribe', chains });
|
|
16538
|
+
}
|
|
16539
|
+
/**
|
|
16540
|
+
* Get current subscription state
|
|
16541
|
+
*/
|
|
16542
|
+
getSubscriptionState() {
|
|
16543
|
+
return { ...this.subscriptionState };
|
|
16544
|
+
}
|
|
16545
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16546
|
+
// Event Handlers
|
|
16547
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16548
|
+
/**
|
|
16549
|
+
* Subscribe to blockchain events
|
|
16550
|
+
* @param eventType - Event type to listen for, or '*' for all events
|
|
16551
|
+
* @param handler - Event handler function
|
|
16552
|
+
* @returns Unsubscribe function
|
|
16553
|
+
*/
|
|
16554
|
+
on(eventType, handler) {
|
|
16555
|
+
if (!this.handlers.has(eventType)) {
|
|
16556
|
+
this.handlers.set(eventType, new Set());
|
|
16557
|
+
}
|
|
16558
|
+
this.handlers.get(eventType).add(handler);
|
|
16559
|
+
return () => {
|
|
16560
|
+
this.handlers.get(eventType)?.delete(handler);
|
|
16561
|
+
};
|
|
16562
|
+
}
|
|
16563
|
+
/**
|
|
16564
|
+
* Subscribe to connection state changes
|
|
16565
|
+
*/
|
|
16566
|
+
onStateChange(handler) {
|
|
16567
|
+
this.stateHandlers.add(handler);
|
|
16568
|
+
return () => this.stateHandlers.delete(handler);
|
|
16569
|
+
}
|
|
16570
|
+
/**
|
|
16571
|
+
* Get current connection state
|
|
16572
|
+
*/
|
|
16573
|
+
getState() {
|
|
16574
|
+
return this.state;
|
|
16575
|
+
}
|
|
16576
|
+
/**
|
|
16577
|
+
* Get connection info (userId, initial wallets, activeChains)
|
|
16578
|
+
*/
|
|
16579
|
+
getConnectionInfo() {
|
|
16580
|
+
return this.connectionInfo;
|
|
16581
|
+
}
|
|
16582
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16583
|
+
// Private methods
|
|
16584
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16585
|
+
async sendSubscription(message) {
|
|
16586
|
+
if (this.state !== 'connected' || !this.ws) {
|
|
16587
|
+
throw new Error('Not connected to server');
|
|
16588
|
+
}
|
|
16589
|
+
// Save subscriptions for reconnect
|
|
16590
|
+
if (message.type === 'subscribe') {
|
|
16591
|
+
if (message.wallets) {
|
|
16592
|
+
this.savedSubscriptions.wallets = [
|
|
16593
|
+
...this.savedSubscriptions.wallets,
|
|
16594
|
+
...message.wallets.filter(w => !this.savedSubscriptions.wallets.some(sw => sw.address === w.address && sw.chainId === w.chainId))
|
|
16595
|
+
];
|
|
16596
|
+
}
|
|
16597
|
+
if (message.chains) {
|
|
16598
|
+
this.savedSubscriptions.chains = [
|
|
16599
|
+
...new Set([...this.savedSubscriptions.chains, ...message.chains])
|
|
16600
|
+
];
|
|
16601
|
+
}
|
|
16602
|
+
}
|
|
16603
|
+
else if (message.type === 'unsubscribe') {
|
|
16604
|
+
if (message.wallets) {
|
|
16605
|
+
this.savedSubscriptions.wallets = this.savedSubscriptions.wallets.filter(sw => !message.wallets.some(w => w.address === sw.address && w.chainId === sw.chainId));
|
|
16606
|
+
}
|
|
16607
|
+
if (message.chains) {
|
|
16608
|
+
this.savedSubscriptions.chains = this.savedSubscriptions.chains.filter(c => !message.chains.includes(c));
|
|
16609
|
+
}
|
|
16610
|
+
}
|
|
16611
|
+
return new Promise((resolve, reject) => {
|
|
16612
|
+
// Set up timeout
|
|
16613
|
+
const timeout = setTimeout(() => {
|
|
16614
|
+
this.pendingSubscription = null;
|
|
16615
|
+
reject(new Error('Subscription timeout'));
|
|
16616
|
+
}, 10000);
|
|
16617
|
+
this.pendingSubscription = {
|
|
16618
|
+
resolve: () => {
|
|
16619
|
+
clearTimeout(timeout);
|
|
16620
|
+
this.pendingSubscription = null;
|
|
16621
|
+
resolve(this.subscriptionState);
|
|
16622
|
+
},
|
|
16623
|
+
reject: (err) => {
|
|
16624
|
+
clearTimeout(timeout);
|
|
16625
|
+
this.pendingSubscription = null;
|
|
16626
|
+
reject(err);
|
|
16627
|
+
}
|
|
16628
|
+
};
|
|
16629
|
+
this.ws.send(JSON.stringify(message));
|
|
16630
|
+
this.log('Sent subscription message:', message);
|
|
16631
|
+
});
|
|
16632
|
+
}
|
|
16633
|
+
handleMessage(data, onConnected) {
|
|
16634
|
+
try {
|
|
16635
|
+
const message = JSON.parse(data);
|
|
16636
|
+
switch (message.type) {
|
|
16637
|
+
case 'connected':
|
|
16638
|
+
this.connectionInfo = message.payload;
|
|
16639
|
+
this.reconnectAttempts = 0;
|
|
16640
|
+
this.setState('connected');
|
|
16641
|
+
this.log('Connected:', message.payload);
|
|
16642
|
+
onConnected?.();
|
|
16643
|
+
// Restore subscriptions on reconnect
|
|
16644
|
+
this.restoreSubscriptions();
|
|
16645
|
+
break;
|
|
16646
|
+
case 'subscribed':
|
|
16647
|
+
this.subscriptionState = message.payload;
|
|
16648
|
+
this.log('Subscription confirmed:', message.payload);
|
|
16649
|
+
this.pendingSubscription?.resolve();
|
|
16650
|
+
break;
|
|
16651
|
+
case 'unsubscribed':
|
|
16652
|
+
this.subscriptionState = message.payload;
|
|
16653
|
+
this.log('Unsubscription confirmed:', message.payload);
|
|
16654
|
+
this.pendingSubscription?.resolve();
|
|
16655
|
+
break;
|
|
16656
|
+
case 'event':
|
|
16657
|
+
this.routeEvent(message.payload);
|
|
16658
|
+
break;
|
|
16659
|
+
case 'ping':
|
|
16660
|
+
// Respond to server ping with pong
|
|
16661
|
+
this.sendPong();
|
|
16662
|
+
break;
|
|
16663
|
+
case 'error':
|
|
16664
|
+
this.log('Server error:', message.payload);
|
|
16665
|
+
this.pendingSubscription?.reject(new Error(message.payload.message));
|
|
16666
|
+
break;
|
|
16667
|
+
}
|
|
16668
|
+
}
|
|
16669
|
+
catch (err) {
|
|
16670
|
+
this.log('Failed to parse message:', err);
|
|
16671
|
+
}
|
|
16672
|
+
}
|
|
16673
|
+
sendPong() {
|
|
16674
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
16675
|
+
this.ws.send(JSON.stringify({ type: 'pong' }));
|
|
16676
|
+
this.log('Sent pong');
|
|
16677
|
+
}
|
|
16678
|
+
}
|
|
16679
|
+
async restoreSubscriptions() {
|
|
16680
|
+
// Restore wallet subscriptions
|
|
16681
|
+
if (this.savedSubscriptions.wallets.length > 0) {
|
|
16682
|
+
this.log('Restoring wallet subscriptions:', this.savedSubscriptions.wallets);
|
|
16683
|
+
try {
|
|
16684
|
+
await this.subscribeWallets(this.savedSubscriptions.wallets);
|
|
16685
|
+
}
|
|
16686
|
+
catch (err) {
|
|
16687
|
+
this.log('Failed to restore wallet subscriptions:', err);
|
|
16688
|
+
}
|
|
16689
|
+
}
|
|
16690
|
+
// Restore chain subscriptions
|
|
16691
|
+
if (this.savedSubscriptions.chains.length > 0) {
|
|
16692
|
+
this.log('Restoring chain subscriptions:', this.savedSubscriptions.chains);
|
|
16693
|
+
try {
|
|
16694
|
+
await this.subscribeChains(this.savedSubscriptions.chains);
|
|
16695
|
+
}
|
|
16696
|
+
catch (err) {
|
|
16697
|
+
this.log('Failed to restore chain subscriptions:', err);
|
|
16698
|
+
}
|
|
16699
|
+
}
|
|
16700
|
+
}
|
|
16701
|
+
routeEvent(event) {
|
|
16702
|
+
this.log('Event received:', event.type, event);
|
|
16703
|
+
// Call specific type handlers
|
|
16704
|
+
const typeHandlers = this.handlers.get(event.type);
|
|
16705
|
+
typeHandlers?.forEach(handler => {
|
|
16706
|
+
try {
|
|
16707
|
+
handler(event);
|
|
16708
|
+
}
|
|
16709
|
+
catch (err) {
|
|
16710
|
+
console.error('[PERS-Events] Handler error:', err);
|
|
16711
|
+
}
|
|
16712
|
+
});
|
|
16713
|
+
// Call wildcard handlers
|
|
16714
|
+
const wildcardHandlers = this.handlers.get('*');
|
|
16715
|
+
wildcardHandlers?.forEach(handler => {
|
|
16716
|
+
try {
|
|
16717
|
+
handler(event);
|
|
16718
|
+
}
|
|
16719
|
+
catch (err) {
|
|
16720
|
+
console.error('[PERS-Events] Handler error:', err);
|
|
16721
|
+
}
|
|
16722
|
+
});
|
|
16723
|
+
}
|
|
16724
|
+
handleDisconnect() {
|
|
16725
|
+
this.cleanup();
|
|
16726
|
+
this.setState('disconnected');
|
|
16727
|
+
if (this.config.autoReconnect && this.token) {
|
|
16728
|
+
if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
16729
|
+
this.attemptReconnect();
|
|
16730
|
+
}
|
|
16731
|
+
else {
|
|
16732
|
+
this.log(`Max reconnect attempts (${this.config.maxReconnectAttempts}) reached. Call connect() manually to retry.`);
|
|
16733
|
+
// Reset counter so manual connect() will work
|
|
16734
|
+
this.reconnectAttempts = 0;
|
|
16735
|
+
}
|
|
16736
|
+
}
|
|
16737
|
+
}
|
|
16738
|
+
attemptReconnect() {
|
|
16739
|
+
const delay = Math.min(this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts), 30000 // Max 30 second delay
|
|
16740
|
+
);
|
|
16741
|
+
this.reconnectAttempts++;
|
|
16742
|
+
this.log(`🔄 Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`);
|
|
16743
|
+
this.setState('reconnecting');
|
|
16744
|
+
this.reconnectTimeout = setTimeout(async () => {
|
|
16745
|
+
try {
|
|
16746
|
+
// Try to get fresh token if tokenRefresher is provided
|
|
16747
|
+
let tokenToUse = this.token;
|
|
16748
|
+
if (this.config.tokenRefresher) {
|
|
16749
|
+
try {
|
|
16750
|
+
this.log('🔄 Refreshing token before reconnect...');
|
|
16751
|
+
tokenToUse = await this.config.tokenRefresher();
|
|
16752
|
+
this.token = tokenToUse; // Update stored token
|
|
16753
|
+
}
|
|
16754
|
+
catch (refreshErr) {
|
|
16755
|
+
this.log('⚠️ Token refresh failed, using existing token:', refreshErr);
|
|
16756
|
+
}
|
|
16757
|
+
}
|
|
16758
|
+
if (tokenToUse) {
|
|
16759
|
+
this.log(`🔄 Attempting reconnection...`);
|
|
16760
|
+
await this.connect(tokenToUse);
|
|
16761
|
+
}
|
|
16762
|
+
}
|
|
16763
|
+
catch (err) {
|
|
16764
|
+
this.log('❌ Reconnect failed:', err);
|
|
16765
|
+
}
|
|
16766
|
+
}, delay);
|
|
16767
|
+
}
|
|
16768
|
+
cleanup() {
|
|
16769
|
+
if (this.reconnectTimeout) {
|
|
16770
|
+
clearTimeout(this.reconnectTimeout);
|
|
16771
|
+
this.reconnectTimeout = null;
|
|
16772
|
+
}
|
|
16773
|
+
if (this.ws) {
|
|
16774
|
+
this.ws.onopen = null;
|
|
16775
|
+
this.ws.onmessage = null;
|
|
16776
|
+
this.ws.onerror = null;
|
|
16777
|
+
this.ws.onclose = null;
|
|
16778
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
16779
|
+
this.ws.close();
|
|
16780
|
+
}
|
|
16781
|
+
this.ws = null;
|
|
16782
|
+
}
|
|
16783
|
+
}
|
|
16784
|
+
setState(state) {
|
|
16785
|
+
if (this.state !== state) {
|
|
16786
|
+
this.state = state;
|
|
16787
|
+
this.stateHandlers.forEach(handler => handler(state));
|
|
16788
|
+
}
|
|
16789
|
+
}
|
|
16790
|
+
log(...args) {
|
|
16791
|
+
if (this.config.debug) {
|
|
16792
|
+
console.log('[PERS-Events]', ...args);
|
|
16793
|
+
}
|
|
16794
|
+
}
|
|
16795
|
+
}
|
|
16796
|
+
|
|
16797
|
+
/**
|
|
16798
|
+
* Wallet Events Manager - Real-time blockchain events for user's wallets
|
|
16799
|
+
*
|
|
16800
|
+
* Provides automatic connection management and event routing integrated
|
|
16801
|
+
* with the SDK's authentication flow and event system.
|
|
16802
|
+
*
|
|
16803
|
+
* Events are routed through the SDK's PersEventEmitter, so you can either:
|
|
16804
|
+
* 1. Subscribe via `sdk.walletEvents.on()` for wallet-specific handling
|
|
16805
|
+
* 2. Subscribe via `sdk.events.subscribe()` for unified event stream
|
|
16806
|
+
*
|
|
16807
|
+
* ## v1.2.0 - Subscription Model
|
|
16808
|
+
*
|
|
16809
|
+
* After connecting, you must explicitly subscribe to wallets or chains:
|
|
16810
|
+
* - User SDK: `subscribeWallets([{ address, chainId }])`
|
|
16811
|
+
* - Admin Dashboard: `subscribeChains([chainId1, chainId2])`
|
|
16812
|
+
*
|
|
16813
|
+
* For convenience, use `sdk.connectWalletEvents()` which auto-subscribes
|
|
16814
|
+
* based on auth type.
|
|
16815
|
+
*
|
|
16816
|
+
* @example Manual subscription
|
|
16817
|
+
* ```typescript
|
|
16818
|
+
* await sdk.walletEvents.connect();
|
|
16819
|
+
* await sdk.walletEvents.subscribeWallets([
|
|
16820
|
+
* { address: user.wallets[0].address, chainId: 39123 }
|
|
16821
|
+
* ]);
|
|
16822
|
+
*
|
|
16823
|
+
* sdk.walletEvents.on('Transfer', (event) => {
|
|
16824
|
+
* console.log(`Received ${event.data.value} tokens`);
|
|
16825
|
+
* });
|
|
16826
|
+
* ```
|
|
16827
|
+
*
|
|
16828
|
+
* @example Auto-subscription (recommended)
|
|
16829
|
+
* ```typescript
|
|
16830
|
+
* // Auto-subscribes based on auth type (user/business/admin)
|
|
16831
|
+
* await sdk.connectWalletEvents();
|
|
16832
|
+
* ```
|
|
16833
|
+
*/
|
|
16834
|
+
/**
|
|
16835
|
+
* Wallet Events Manager - Manages real-time blockchain event subscriptions
|
|
16836
|
+
*
|
|
16837
|
+
* Low-level WS client wrapper. For auto-subscription based on auth type,
|
|
16838
|
+
* use `sdk.connectWalletEvents()` which handles subscription automatically.
|
|
16839
|
+
*/
|
|
16840
|
+
class WalletEventsManager {
|
|
16841
|
+
constructor(apiClient, eventEmitter, config) {
|
|
16842
|
+
this.apiClient = apiClient;
|
|
16843
|
+
this.eventEmitter = eventEmitter;
|
|
16844
|
+
this.client = null;
|
|
16845
|
+
this.pendingHandlers = [];
|
|
16846
|
+
this.unsubscribes = [];
|
|
16847
|
+
this.config = {
|
|
16848
|
+
autoReconnect: true,
|
|
16849
|
+
connectionTimeout: 30000,
|
|
16850
|
+
debug: false,
|
|
16851
|
+
...config,
|
|
16852
|
+
};
|
|
16853
|
+
}
|
|
16854
|
+
/**
|
|
16855
|
+
* Connect to real-time wallet events
|
|
16856
|
+
*
|
|
16857
|
+
* Establishes WebSocket connection to the WS relay server.
|
|
16858
|
+
* After connecting, call subscribeWallets() or subscribeChains() to start
|
|
16859
|
+
* receiving events.
|
|
16860
|
+
*
|
|
16861
|
+
* For auto-subscription, use `sdk.connectWalletEvents()` instead.
|
|
16862
|
+
*/
|
|
16863
|
+
async connect() {
|
|
16864
|
+
// Already connected?
|
|
16865
|
+
if (this.client?.getState() === 'connected') {
|
|
16866
|
+
return;
|
|
16867
|
+
}
|
|
16868
|
+
const sdkConfig = this.apiClient.getConfig();
|
|
16869
|
+
const authProvider = sdkConfig.authProvider;
|
|
16870
|
+
if (!authProvider) {
|
|
16871
|
+
throw new Error('Not authenticated. Call sdk.auth.login() first.');
|
|
16872
|
+
}
|
|
16873
|
+
const token = await authProvider.getToken();
|
|
16874
|
+
if (!token) {
|
|
16875
|
+
throw new Error('No authentication token available. Call sdk.auth.login() first.');
|
|
16876
|
+
}
|
|
16877
|
+
// Resolve wsUrl: config override > SDK config > environment default
|
|
16878
|
+
const wsUrl = this.config.wsUrl
|
|
16879
|
+
|| sdkConfig.walletEventsWsUrl
|
|
16880
|
+
|| buildWalletEventsWsUrl(sdkConfig.environment);
|
|
16881
|
+
// Create token refresher that fetches fresh token from auth provider
|
|
16882
|
+
const tokenRefresher = async () => {
|
|
16883
|
+
const freshToken = await authProvider.getToken();
|
|
16884
|
+
if (!freshToken) {
|
|
16885
|
+
throw new Error('Failed to refresh token');
|
|
16886
|
+
}
|
|
16887
|
+
return freshToken;
|
|
16888
|
+
};
|
|
16889
|
+
this.client = new PersEventsClient({
|
|
16890
|
+
wsUrl,
|
|
16891
|
+
autoReconnect: this.config.autoReconnect,
|
|
16892
|
+
connectionTimeout: this.config.connectionTimeout,
|
|
16893
|
+
debug: this.config.debug,
|
|
16894
|
+
tokenRefresher,
|
|
16895
|
+
});
|
|
16896
|
+
await this.client.connect(token);
|
|
16897
|
+
// Clear previous unsubscribes on new connection (prevent memory leak)
|
|
16898
|
+
this.unsubscribes.forEach(unsub => unsub());
|
|
16899
|
+
this.unsubscribes = [];
|
|
16900
|
+
// Route all events through PersEventEmitter for unified stream
|
|
16901
|
+
// Note: Cast needed because web3-types uses string for event.type while pers-shared uses strict union
|
|
16902
|
+
this.client.on('*', (event) => this.emitToPersEvents(event));
|
|
16903
|
+
// Re-attach any handlers registered before connect
|
|
16904
|
+
for (const { type, handler } of this.pendingHandlers) {
|
|
16905
|
+
this.unsubscribes.push(this.client.on(type, handler));
|
|
16906
|
+
}
|
|
16907
|
+
this.pendingHandlers = [];
|
|
16908
|
+
}
|
|
16909
|
+
/**
|
|
16910
|
+
* Disconnect from real-time events
|
|
16911
|
+
*/
|
|
16912
|
+
disconnect() {
|
|
16913
|
+
this.unsubscribes.forEach(unsub => unsub());
|
|
16914
|
+
this.unsubscribes = [];
|
|
16915
|
+
this.client?.disconnect();
|
|
16916
|
+
this.client = null;
|
|
16917
|
+
}
|
|
16918
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16919
|
+
// Subscription Methods (v1.2.0)
|
|
16920
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16921
|
+
/**
|
|
16922
|
+
* Subscribe to wallet events
|
|
16923
|
+
*
|
|
16924
|
+
* Receives events only for the specified wallet addresses.
|
|
16925
|
+
* Use this for user-facing SDK where you want to monitor specific wallets.
|
|
16926
|
+
*
|
|
16927
|
+
* @param wallets - Array of wallet info (address + chainId)
|
|
16928
|
+
* @returns Promise that resolves when subscription is confirmed
|
|
16929
|
+
*
|
|
16930
|
+
* @example
|
|
16931
|
+
* ```typescript
|
|
16932
|
+
* await sdk.walletEvents.connect();
|
|
16933
|
+
* await sdk.walletEvents.subscribeWallets([
|
|
16934
|
+
* { address: '0x123...', chainId: 39123 }
|
|
16935
|
+
* ]);
|
|
16936
|
+
* ```
|
|
16937
|
+
*/
|
|
16938
|
+
async subscribeWallets(wallets) {
|
|
16939
|
+
if (!this.client) {
|
|
16940
|
+
throw new Error('Not connected. Call connect() first.');
|
|
16941
|
+
}
|
|
16942
|
+
return this.client.subscribeWallets(wallets);
|
|
16943
|
+
}
|
|
16944
|
+
/**
|
|
16945
|
+
* Subscribe to chain events (Admin Dashboard)
|
|
16946
|
+
*
|
|
16947
|
+
* Receives ALL events on the specified chains.
|
|
16948
|
+
* Use for admin dashboards that need to monitor all activity.
|
|
16949
|
+
*
|
|
16950
|
+
* @param chains - Array of chain IDs to subscribe to
|
|
16951
|
+
* @returns Promise that resolves when subscription is confirmed
|
|
16952
|
+
*
|
|
16953
|
+
* @example
|
|
16954
|
+
* ```typescript
|
|
16955
|
+
* await sdk.walletEvents.connect();
|
|
16956
|
+
* await sdk.walletEvents.subscribeChains([39123, 137]);
|
|
16957
|
+
* ```
|
|
16958
|
+
*/
|
|
16959
|
+
async subscribeChains(chains) {
|
|
16960
|
+
if (!this.client) {
|
|
16961
|
+
throw new Error('Not connected. Call connect() first.');
|
|
16962
|
+
}
|
|
16963
|
+
return this.client.subscribeChains(chains);
|
|
16964
|
+
}
|
|
16965
|
+
/**
|
|
16966
|
+
* Unsubscribe from wallet events
|
|
16967
|
+
*
|
|
16968
|
+
* @param wallets - Array of wallet info to unsubscribe from
|
|
16969
|
+
* @returns Promise that resolves when unsubscription is confirmed
|
|
16970
|
+
*/
|
|
16971
|
+
async unsubscribeWallets(wallets) {
|
|
16972
|
+
if (!this.client) {
|
|
16973
|
+
throw new Error('Not connected. Call connect() first.');
|
|
16974
|
+
}
|
|
16975
|
+
return this.client.unsubscribeWallets(wallets);
|
|
16976
|
+
}
|
|
16977
|
+
/**
|
|
16978
|
+
* Unsubscribe from chain events
|
|
16979
|
+
*
|
|
16980
|
+
* @param chains - Array of chain IDs to unsubscribe from
|
|
16981
|
+
* @returns Promise that resolves when unsubscription is confirmed
|
|
16982
|
+
*/
|
|
16983
|
+
async unsubscribeChains(chains) {
|
|
16984
|
+
if (!this.client) {
|
|
16985
|
+
throw new Error('Not connected. Call connect() first.');
|
|
16986
|
+
}
|
|
16987
|
+
return this.client.unsubscribeChains(chains);
|
|
16988
|
+
}
|
|
16989
|
+
/**
|
|
16990
|
+
* Get current subscription state
|
|
16991
|
+
*/
|
|
16992
|
+
getSubscriptionState() {
|
|
16993
|
+
return this.client?.getSubscriptionState() ?? { wallets: [], chains: [], activeChains: [] };
|
|
16994
|
+
}
|
|
16995
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16996
|
+
// Event Handlers
|
|
16997
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16998
|
+
/**
|
|
16999
|
+
* Subscribe to blockchain events
|
|
17000
|
+
*
|
|
17001
|
+
* @param eventType - Event type ('Transfer', 'Approval', etc.) or '*' for all
|
|
17002
|
+
* @param handler - Callback function when event is received
|
|
17003
|
+
* @returns Unsubscribe function
|
|
17004
|
+
*
|
|
17005
|
+
* @example
|
|
17006
|
+
* ```typescript
|
|
17007
|
+
* const unsub = sdk.walletEvents.on('Transfer', (event) => {
|
|
17008
|
+
* console.log(`Transfer: ${event.data.from} -> ${event.data.to}`);
|
|
17009
|
+
* });
|
|
17010
|
+
*
|
|
17011
|
+
* // Later: stop listening
|
|
17012
|
+
* unsub();
|
|
17013
|
+
* ```
|
|
17014
|
+
*/
|
|
17015
|
+
on(eventType, handler) {
|
|
17016
|
+
if (this.client) {
|
|
17017
|
+
const unsub = this.client.on(eventType, handler);
|
|
17018
|
+
this.unsubscribes.push(unsub);
|
|
17019
|
+
return unsub;
|
|
17020
|
+
}
|
|
17021
|
+
else {
|
|
17022
|
+
// Store for when connect() is called
|
|
17023
|
+
this.pendingHandlers.push({ type: eventType, handler });
|
|
17024
|
+
return () => {
|
|
17025
|
+
const idx = this.pendingHandlers.findIndex(h => h.handler === handler);
|
|
17026
|
+
if (idx !== -1)
|
|
17027
|
+
this.pendingHandlers.splice(idx, 1);
|
|
17028
|
+
};
|
|
17029
|
+
}
|
|
17030
|
+
}
|
|
17031
|
+
/**
|
|
17032
|
+
* Subscribe to connection state changes
|
|
17033
|
+
*/
|
|
17034
|
+
onStateChange(handler) {
|
|
17035
|
+
if (this.client) {
|
|
17036
|
+
return this.client.onStateChange(handler);
|
|
17037
|
+
}
|
|
17038
|
+
return () => { };
|
|
17039
|
+
}
|
|
17040
|
+
/**
|
|
17041
|
+
* Get current connection state
|
|
17042
|
+
*/
|
|
17043
|
+
getState() {
|
|
17044
|
+
return this.client?.getState() ?? 'disconnected';
|
|
17045
|
+
}
|
|
17046
|
+
/**
|
|
17047
|
+
* Check if connected
|
|
17048
|
+
*/
|
|
17049
|
+
isConnected() {
|
|
17050
|
+
return this.getState() === 'connected';
|
|
17051
|
+
}
|
|
17052
|
+
/**
|
|
17053
|
+
* Get connection info (wallets, active chains)
|
|
17054
|
+
*/
|
|
17055
|
+
getConnectionInfo() {
|
|
17056
|
+
return this.client?.getConnectionInfo();
|
|
17057
|
+
}
|
|
17058
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17059
|
+
// Private Methods
|
|
17060
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17061
|
+
/**
|
|
17062
|
+
* Build a descriptive message for blockchain events
|
|
17063
|
+
* Neutral tone - frontend will handle user-facing presentation
|
|
17064
|
+
*/
|
|
17065
|
+
buildUserMessage(event) {
|
|
17066
|
+
const { type, data } = event;
|
|
17067
|
+
// Use string for switch since event types may extend beyond strict BlockchainEventType union
|
|
17068
|
+
const eventType = type;
|
|
17069
|
+
const from = data.from?.toLowerCase();
|
|
17070
|
+
const to = data.to?.toLowerCase();
|
|
17071
|
+
const shortFrom = from ? `${from.slice(0, 6)}...${from.slice(-4)}` : 'unknown';
|
|
17072
|
+
const shortTo = to ? `${to.slice(0, 6)}...${to.slice(-4)}` : 'unknown';
|
|
17073
|
+
// Get user's wallets to determine direction
|
|
17074
|
+
const subscriptionState = this.getSubscriptionState();
|
|
17075
|
+
const userWallets = subscriptionState.wallets.map(w => w.address.toLowerCase());
|
|
17076
|
+
const isIncoming = to && userWallets.includes(to);
|
|
17077
|
+
const isOutgoing = from && userWallets.includes(from);
|
|
17078
|
+
const isMint = from === '0x0000000000000000000000000000000000000000';
|
|
17079
|
+
const isBurn = to === '0x0000000000000000000000000000000000000000';
|
|
17080
|
+
switch (eventType) {
|
|
17081
|
+
case 'Transfer':
|
|
17082
|
+
if (isMint) {
|
|
17083
|
+
return `Token minted to ${shortTo}`;
|
|
17084
|
+
}
|
|
17085
|
+
else if (isBurn) {
|
|
17086
|
+
return `Token burned from ${shortFrom}`;
|
|
17087
|
+
}
|
|
17088
|
+
else if (isIncoming) {
|
|
17089
|
+
return `Transfer received from ${shortFrom}`;
|
|
17090
|
+
}
|
|
17091
|
+
else if (isOutgoing) {
|
|
17092
|
+
return `Transfer sent to ${shortTo}`;
|
|
17093
|
+
}
|
|
17094
|
+
return `Transfer from ${shortFrom} to ${shortTo}`;
|
|
17095
|
+
case 'Approval':
|
|
17096
|
+
return `Approval from ${shortFrom}`;
|
|
17097
|
+
case 'SmartWalletCreated':
|
|
17098
|
+
return `Smart wallet created: ${shortTo}`;
|
|
17099
|
+
case 'TransactionExecuted':
|
|
17100
|
+
return `Transaction executed by ${shortFrom}`;
|
|
17101
|
+
case 'OwnershipTransferred':
|
|
17102
|
+
return `Ownership transferred from ${shortFrom} to ${shortTo}`;
|
|
17103
|
+
default:
|
|
17104
|
+
return `${eventType} event`;
|
|
17105
|
+
}
|
|
17106
|
+
}
|
|
17107
|
+
/**
|
|
17108
|
+
* Route blockchain event to PersEventEmitter for unified event stream
|
|
17109
|
+
*/
|
|
17110
|
+
emitToPersEvents(event) {
|
|
17111
|
+
const userMessage = this.buildUserMessage(event);
|
|
17112
|
+
this.eventEmitter.emitSuccess({
|
|
17113
|
+
type: `wallet_${event.type.toLowerCase()}`,
|
|
17114
|
+
domain: 'wallet',
|
|
17115
|
+
userMessage,
|
|
17116
|
+
details: {
|
|
17117
|
+
chainId: event.chainId,
|
|
17118
|
+
txHash: event.txHash,
|
|
17119
|
+
blockNumber: event.blockNumber,
|
|
17120
|
+
timestamp: event.timestamp,
|
|
17121
|
+
contractAddress: event.contractAddress,
|
|
17122
|
+
...event.data,
|
|
17123
|
+
},
|
|
17124
|
+
});
|
|
17125
|
+
}
|
|
17126
|
+
}
|
|
17127
|
+
|
|
14646
17128
|
/**
|
|
14647
17129
|
* @fileoverview PERS SDK - Platform-agnostic TypeScript SDK with High-Level Managers
|
|
14648
17130
|
*
|
|
@@ -14756,6 +17238,81 @@ class PersSDK {
|
|
|
14756
17238
|
// Initialize event emitter and wire to API client for error events
|
|
14757
17239
|
this._events = new PersEventEmitter();
|
|
14758
17240
|
this.apiClient.setEvents(this._events);
|
|
17241
|
+
// Auto-connect to wallet events on successful login (if enabled)
|
|
17242
|
+
this.setupWalletEventsAutoConnect();
|
|
17243
|
+
}
|
|
17244
|
+
/**
|
|
17245
|
+
* Setup auto-connect for wallet events on authentication
|
|
17246
|
+
* @internal
|
|
17247
|
+
*/
|
|
17248
|
+
setupWalletEventsAutoConnect() {
|
|
17249
|
+
const config = this.apiClient.getConfig();
|
|
17250
|
+
// Check if captureWalletEvents is enabled (default: true)
|
|
17251
|
+
if (config.captureWalletEvents === false) {
|
|
17252
|
+
return;
|
|
17253
|
+
}
|
|
17254
|
+
// Listen for login success events (both fresh login and session restoration)
|
|
17255
|
+
this._events.subscribe((event) => {
|
|
17256
|
+
if (event.level === 'success' && (event.type === 'LOGIN_SUCCESS' || event.type === 'session_restored')) {
|
|
17257
|
+
// Auto-connect and subscribe to wallet events (fire and forget, log errors)
|
|
17258
|
+
this.connectWalletEvents().catch((err) => {
|
|
17259
|
+
console.warn('[PersSDK] Failed to auto-connect wallet events:', err.message);
|
|
17260
|
+
});
|
|
17261
|
+
}
|
|
17262
|
+
// Disconnect on logout
|
|
17263
|
+
if (event.level === 'success' && event.type === 'logout_success') {
|
|
17264
|
+
this.walletEvents.disconnect();
|
|
17265
|
+
}
|
|
17266
|
+
});
|
|
17267
|
+
}
|
|
17268
|
+
/**
|
|
17269
|
+
* Connect to wallet events and auto-subscribe based on auth type
|
|
17270
|
+
*
|
|
17271
|
+
* Connects to the WebSocket relay and automatically subscribes to relevant
|
|
17272
|
+
* blockchain events based on the current authentication type:
|
|
17273
|
+
* - **USER**: Subscribes to all user's wallets
|
|
17274
|
+
* - **BUSINESS**: Subscribes to all business's wallets
|
|
17275
|
+
* - **TENANT**: Subscribes to all chains where tokens are deployed
|
|
17276
|
+
*
|
|
17277
|
+
* This method is called automatically on login when `captureWalletEvents` is enabled.
|
|
17278
|
+
* Call manually if you need to reconnect or refresh subscriptions.
|
|
17279
|
+
*
|
|
17280
|
+
* @example Manual connection
|
|
17281
|
+
* ```typescript
|
|
17282
|
+
* await sdk.connectWalletEvents();
|
|
17283
|
+
* ```
|
|
17284
|
+
*/
|
|
17285
|
+
async connectWalletEvents() {
|
|
17286
|
+
await this.walletEvents.connect();
|
|
17287
|
+
// Get authType from auth provider (where it's stored during login)
|
|
17288
|
+
const authProvider = this.apiClient.getConfig().authProvider;
|
|
17289
|
+
const authType = authProvider?.getAuthType ? await authProvider.getAuthType() : undefined;
|
|
17290
|
+
switch (authType) {
|
|
17291
|
+
case exports.AccountOwnerType.USER: {
|
|
17292
|
+
const user = await this.users.getCurrentUser();
|
|
17293
|
+
const wallets = user.wallets?.map(w => ({ address: w.address, chainId: w.chainId })) || [];
|
|
17294
|
+
if (wallets.length > 0) {
|
|
17295
|
+
await this.walletEvents.subscribeWallets(wallets);
|
|
17296
|
+
}
|
|
17297
|
+
break;
|
|
17298
|
+
}
|
|
17299
|
+
case exports.AccountOwnerType.BUSINESS: {
|
|
17300
|
+
const business = await this.auth.getCurrentBusiness();
|
|
17301
|
+
const wallets = business.wallets?.map(w => ({ address: w.address, chainId: w.chainId })) || [];
|
|
17302
|
+
if (wallets.length > 0) {
|
|
17303
|
+
await this.walletEvents.subscribeWallets(wallets);
|
|
17304
|
+
}
|
|
17305
|
+
break;
|
|
17306
|
+
}
|
|
17307
|
+
case exports.AccountOwnerType.TENANT: {
|
|
17308
|
+
const tokens = await this.tokens.getTokens();
|
|
17309
|
+
const chains = [...new Set(tokens.data.map(t => t.chainId))];
|
|
17310
|
+
if (chains.length > 0) {
|
|
17311
|
+
await this.walletEvents.subscribeChains(chains);
|
|
17312
|
+
}
|
|
17313
|
+
break;
|
|
17314
|
+
}
|
|
17315
|
+
}
|
|
14759
17316
|
}
|
|
14760
17317
|
/**
|
|
14761
17318
|
* Restore user session from stored tokens
|
|
@@ -15163,6 +17720,103 @@ class PersSDK {
|
|
|
15163
17720
|
}
|
|
15164
17721
|
return this._triggerSources;
|
|
15165
17722
|
}
|
|
17723
|
+
/**
|
|
17724
|
+
* Webhook manager - High-level webhook operations
|
|
17725
|
+
*
|
|
17726
|
+
* Provides methods for creating webhooks, triggering them programmatically,
|
|
17727
|
+
* and monitoring execution history. Supports async workflows with callbacks.
|
|
17728
|
+
*
|
|
17729
|
+
* @returns WebhookManager instance
|
|
17730
|
+
*
|
|
17731
|
+
* @example Webhook Operations
|
|
17732
|
+
* ```typescript
|
|
17733
|
+
* // Admin: Create a webhook
|
|
17734
|
+
* const webhook = await sdk.webhooks.create({
|
|
17735
|
+
* name: 'Order Processing',
|
|
17736
|
+
* targetUrl: 'https://n8n.example.com/webhook/orders',
|
|
17737
|
+
* method: 'POST'
|
|
17738
|
+
* });
|
|
17739
|
+
*
|
|
17740
|
+
* // Trigger webhook with payload
|
|
17741
|
+
* const result = await sdk.webhooks.trigger(webhook.id, {
|
|
17742
|
+
* orderId: 'order-123',
|
|
17743
|
+
* action: 'created'
|
|
17744
|
+
* });
|
|
17745
|
+
*
|
|
17746
|
+
* // Trigger and wait for async workflow completion
|
|
17747
|
+
* const asyncResult = await sdk.webhooks.triggerAndWait(
|
|
17748
|
+
* 'ai-webhook',
|
|
17749
|
+
* { prompt: 'Analyze this data' },
|
|
17750
|
+
* 30000 // Wait up to 30s
|
|
17751
|
+
* );
|
|
17752
|
+
* ```
|
|
17753
|
+
*
|
|
17754
|
+
* @see {@link WebhookManager} for detailed documentation
|
|
17755
|
+
*/
|
|
17756
|
+
get webhooks() {
|
|
17757
|
+
if (!this._webhooks) {
|
|
17758
|
+
const projectKey = this.apiClient.getConfig().apiProjectKey || '';
|
|
17759
|
+
this._webhooks = new WebhookManager(this.apiClient, projectKey, this._events);
|
|
17760
|
+
}
|
|
17761
|
+
return this._webhooks;
|
|
17762
|
+
}
|
|
17763
|
+
/**
|
|
17764
|
+
* Wallet Events Manager - Real-time blockchain events for user's wallets
|
|
17765
|
+
*
|
|
17766
|
+
* Provides real-time WebSocket connection to receive blockchain events
|
|
17767
|
+
* for the user's wallets (transfers, approvals, NFT mints, etc.).
|
|
17768
|
+
*
|
|
17769
|
+
* Events are also routed through `sdk.events` for unified event handling.
|
|
17770
|
+
*
|
|
17771
|
+
* **Important:** Requires `walletEventsWsUrl` configuration and authentication.
|
|
17772
|
+
*
|
|
17773
|
+
* @returns WalletEventsManager instance
|
|
17774
|
+
*
|
|
17775
|
+
* @example Basic Usage
|
|
17776
|
+
* ```typescript
|
|
17777
|
+
* // Configure SDK with events URL
|
|
17778
|
+
* sdk.configureWalletEvents({ wsUrl: 'wss://events.pers.ninja' });
|
|
17779
|
+
*
|
|
17780
|
+
* // Connect after authentication
|
|
17781
|
+
* await sdk.auth.loginWithToken(jwt, 'user');
|
|
17782
|
+
* await sdk.walletEvents.connect();
|
|
17783
|
+
*
|
|
17784
|
+
* // Listen for token transfers
|
|
17785
|
+
* sdk.walletEvents.on('Transfer', (event) => {
|
|
17786
|
+
* if (event.data.to === myWallet) {
|
|
17787
|
+
* showNotification(`Received ${event.data.value} tokens!`);
|
|
17788
|
+
* }
|
|
17789
|
+
* });
|
|
17790
|
+
* ```
|
|
17791
|
+
*
|
|
17792
|
+
* @example Unified Event Stream
|
|
17793
|
+
* ```typescript
|
|
17794
|
+
* // Wallet events also flow through sdk.events
|
|
17795
|
+
* sdk.events.subscribe((event) => {
|
|
17796
|
+
* if (event.domain === 'wallet') {
|
|
17797
|
+
* console.log('Wallet event:', event.type);
|
|
17798
|
+
* }
|
|
17799
|
+
* });
|
|
17800
|
+
* ```
|
|
17801
|
+
*
|
|
17802
|
+
* @see {@link WalletEventsManager} for detailed documentation
|
|
17803
|
+
*/
|
|
17804
|
+
get walletEvents() {
|
|
17805
|
+
if (!this._walletEvents) {
|
|
17806
|
+
this._walletEvents = new WalletEventsManager(this.apiClient, this._events, this.walletEventsConfig);
|
|
17807
|
+
}
|
|
17808
|
+
return this._walletEvents;
|
|
17809
|
+
}
|
|
17810
|
+
/**
|
|
17811
|
+
* Configure wallet events (call before accessing walletEvents)
|
|
17812
|
+
*
|
|
17813
|
+
* @param config - Events configuration including wsUrl
|
|
17814
|
+
*/
|
|
17815
|
+
configureWalletEvents(config) {
|
|
17816
|
+
this.walletEventsConfig = config;
|
|
17817
|
+
// Reset manager so it picks up new config
|
|
17818
|
+
this._walletEvents = undefined;
|
|
17819
|
+
}
|
|
15166
17820
|
/**
|
|
15167
17821
|
* Gets the API client for direct PERS API requests
|
|
15168
17822
|
*
|
|
@@ -15233,281 +17887,6 @@ function detectEnvironment() {
|
|
|
15233
17887
|
*/
|
|
15234
17888
|
detectEnvironment();
|
|
15235
17889
|
|
|
15236
|
-
/**
|
|
15237
|
-
* Centralized Cache Service for PERS SDK
|
|
15238
|
-
* Simple, efficient caching with memory management
|
|
15239
|
-
*/
|
|
15240
|
-
const DEFAULT_CACHE_CONFIG = {
|
|
15241
|
-
defaultTtl: 30 * 60 * 1000, // 30 minutes
|
|
15242
|
-
maxMemoryMB: 50, // 50MB cache limit
|
|
15243
|
-
cleanupInterval: 5 * 60 * 1000, // Cleanup every 5 minutes
|
|
15244
|
-
maxEntries: 10000, // Entry count limit
|
|
15245
|
-
evictionBatchPercent: 0.2, // Remove 20% when evicting
|
|
15246
|
-
};
|
|
15247
|
-
// Constants for memory estimation
|
|
15248
|
-
const ENTRY_OVERHEAD_BYTES = 64;
|
|
15249
|
-
const DEFAULT_OBJECT_SIZE_BYTES = 1024;
|
|
15250
|
-
const MEMORY_CHECK_INTERVAL_MS = 60000; // Lazy check every 60s
|
|
15251
|
-
class CacheService {
|
|
15252
|
-
constructor(config) {
|
|
15253
|
-
this.cache = new Map();
|
|
15254
|
-
this.cleanupTimer = null;
|
|
15255
|
-
this.lastMemoryCheck = 0;
|
|
15256
|
-
this.pendingFetches = new Map();
|
|
15257
|
-
this.accessOrder = new Set();
|
|
15258
|
-
this.stats = {
|
|
15259
|
-
hits: 0,
|
|
15260
|
-
misses: 0,
|
|
15261
|
-
errors: 0
|
|
15262
|
-
};
|
|
15263
|
-
this.config = { ...DEFAULT_CACHE_CONFIG, ...config };
|
|
15264
|
-
this.startCleanupTimer();
|
|
15265
|
-
}
|
|
15266
|
-
/**
|
|
15267
|
-
* Set a value in cache with optional TTL
|
|
15268
|
-
*/
|
|
15269
|
-
set(key, value, ttl) {
|
|
15270
|
-
if (!key || value === undefined)
|
|
15271
|
-
return;
|
|
15272
|
-
const entry = {
|
|
15273
|
-
value,
|
|
15274
|
-
timestamp: Date.now(),
|
|
15275
|
-
ttl: ttl || this.config.defaultTtl,
|
|
15276
|
-
lastAccessed: Date.now()
|
|
15277
|
-
};
|
|
15278
|
-
this.cache.set(key, entry);
|
|
15279
|
-
this.accessOrder.add(key);
|
|
15280
|
-
// Fast entry count check first
|
|
15281
|
-
if (this.cache.size > this.config.maxEntries) {
|
|
15282
|
-
this.evictOldestEntries();
|
|
15283
|
-
}
|
|
15284
|
-
// Lazy memory check (only every 10s)
|
|
15285
|
-
const now = Date.now();
|
|
15286
|
-
if (now - this.lastMemoryCheck > MEMORY_CHECK_INTERVAL_MS) {
|
|
15287
|
-
this.lastMemoryCheck = now;
|
|
15288
|
-
this.enforceMemoryLimit();
|
|
15289
|
-
}
|
|
15290
|
-
}
|
|
15291
|
-
/**
|
|
15292
|
-
* Get a value from cache
|
|
15293
|
-
*/
|
|
15294
|
-
get(key) {
|
|
15295
|
-
const entry = this.cache.get(key);
|
|
15296
|
-
if (!entry) {
|
|
15297
|
-
this.stats.misses++;
|
|
15298
|
-
return null;
|
|
15299
|
-
}
|
|
15300
|
-
// Check if expired
|
|
15301
|
-
const now = Date.now();
|
|
15302
|
-
if (now - entry.timestamp > entry.ttl) {
|
|
15303
|
-
this.cache.delete(key);
|
|
15304
|
-
this.accessOrder.delete(key);
|
|
15305
|
-
this.stats.misses++;
|
|
15306
|
-
return null;
|
|
15307
|
-
}
|
|
15308
|
-
// Update access order efficiently
|
|
15309
|
-
this.accessOrder.delete(key);
|
|
15310
|
-
this.accessOrder.add(key);
|
|
15311
|
-
entry.lastAccessed = now;
|
|
15312
|
-
this.stats.hits++;
|
|
15313
|
-
return entry.value;
|
|
15314
|
-
}
|
|
15315
|
-
/**
|
|
15316
|
-
* Get or set pattern - common caching pattern with race condition protection
|
|
15317
|
-
*/
|
|
15318
|
-
async getOrSet(key, fetcher, ttl) {
|
|
15319
|
-
const cached = this.get(key);
|
|
15320
|
-
if (cached !== null)
|
|
15321
|
-
return cached;
|
|
15322
|
-
// Prevent duplicate fetches
|
|
15323
|
-
if (this.pendingFetches.has(key)) {
|
|
15324
|
-
return this.pendingFetches.get(key);
|
|
15325
|
-
}
|
|
15326
|
-
const fetchPromise = fetcher()
|
|
15327
|
-
.then(value => {
|
|
15328
|
-
this.pendingFetches.delete(key);
|
|
15329
|
-
if (value !== undefined) {
|
|
15330
|
-
this.set(key, value, ttl);
|
|
15331
|
-
}
|
|
15332
|
-
return value;
|
|
15333
|
-
})
|
|
15334
|
-
.catch(error => {
|
|
15335
|
-
this.pendingFetches.delete(key);
|
|
15336
|
-
this.stats.errors++;
|
|
15337
|
-
throw error;
|
|
15338
|
-
});
|
|
15339
|
-
this.pendingFetches.set(key, fetchPromise);
|
|
15340
|
-
return fetchPromise;
|
|
15341
|
-
}
|
|
15342
|
-
/**
|
|
15343
|
-
* Delete a specific key
|
|
15344
|
-
*/
|
|
15345
|
-
delete(key) {
|
|
15346
|
-
this.accessOrder.delete(key);
|
|
15347
|
-
return this.cache.delete(key);
|
|
15348
|
-
}
|
|
15349
|
-
/**
|
|
15350
|
-
* Clear all keys matching a prefix
|
|
15351
|
-
*/
|
|
15352
|
-
clearByPrefix(prefix) {
|
|
15353
|
-
const keysToDelete = Array.from(this.cache.keys()).filter(key => key.startsWith(prefix));
|
|
15354
|
-
keysToDelete.forEach(key => {
|
|
15355
|
-
this.cache.delete(key);
|
|
15356
|
-
this.accessOrder.delete(key);
|
|
15357
|
-
});
|
|
15358
|
-
return keysToDelete.length;
|
|
15359
|
-
}
|
|
15360
|
-
/**
|
|
15361
|
-
* Clear all cache
|
|
15362
|
-
*/
|
|
15363
|
-
clear() {
|
|
15364
|
-
this.cache.clear();
|
|
15365
|
-
this.accessOrder.clear();
|
|
15366
|
-
this.pendingFetches.clear();
|
|
15367
|
-
this.stats = { hits: 0, misses: 0, errors: 0 };
|
|
15368
|
-
}
|
|
15369
|
-
/**
|
|
15370
|
-
* Force cleanup of expired entries
|
|
15371
|
-
*/
|
|
15372
|
-
cleanup() {
|
|
15373
|
-
const now = Date.now();
|
|
15374
|
-
const keysToDelete = [];
|
|
15375
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
15376
|
-
if (now - entry.timestamp > entry.ttl) {
|
|
15377
|
-
keysToDelete.push(key);
|
|
15378
|
-
}
|
|
15379
|
-
}
|
|
15380
|
-
keysToDelete.forEach(key => {
|
|
15381
|
-
this.cache.delete(key);
|
|
15382
|
-
this.accessOrder.delete(key);
|
|
15383
|
-
});
|
|
15384
|
-
return keysToDelete.length;
|
|
15385
|
-
}
|
|
15386
|
-
/**
|
|
15387
|
-
* Stop cleanup timer and clear cache
|
|
15388
|
-
*/
|
|
15389
|
-
destroy() {
|
|
15390
|
-
this.stopCleanupTimer();
|
|
15391
|
-
this.clear();
|
|
15392
|
-
}
|
|
15393
|
-
/**
|
|
15394
|
-
* Create a namespaced cache interface
|
|
15395
|
-
*/
|
|
15396
|
-
createNamespace(namespace) {
|
|
15397
|
-
if (!namespace || namespace.includes(':')) {
|
|
15398
|
-
throw new Error('Namespace must be non-empty and cannot contain ":"');
|
|
15399
|
-
}
|
|
15400
|
-
return {
|
|
15401
|
-
set: (key, value, ttl) => this.set(`${namespace}:${key}`, value, ttl),
|
|
15402
|
-
get: (key) => this.get(`${namespace}:${key}`),
|
|
15403
|
-
getOrSet: (key, fetcher, ttl) => this.getOrSet(`${namespace}:${key}`, fetcher, ttl),
|
|
15404
|
-
delete: (key) => this.delete(`${namespace}:${key}`),
|
|
15405
|
-
clear: () => this.clearByPrefix(`${namespace}:`)
|
|
15406
|
-
};
|
|
15407
|
-
}
|
|
15408
|
-
startCleanupTimer() {
|
|
15409
|
-
this.stopCleanupTimer();
|
|
15410
|
-
this.cleanupTimer = setInterval(() => {
|
|
15411
|
-
this.safeCleanup();
|
|
15412
|
-
}, this.config.cleanupInterval);
|
|
15413
|
-
// Platform-agnostic: Prevent hanging process in Node.js environments
|
|
15414
|
-
if (this.cleanupTimer && typeof this.cleanupTimer.unref === 'function') {
|
|
15415
|
-
this.cleanupTimer.unref();
|
|
15416
|
-
}
|
|
15417
|
-
}
|
|
15418
|
-
stopCleanupTimer() {
|
|
15419
|
-
if (this.cleanupTimer) {
|
|
15420
|
-
clearInterval(this.cleanupTimer);
|
|
15421
|
-
this.cleanupTimer = null;
|
|
15422
|
-
}
|
|
15423
|
-
}
|
|
15424
|
-
safeCleanup() {
|
|
15425
|
-
try {
|
|
15426
|
-
this.cleanup();
|
|
15427
|
-
this.enforceMemoryLimit();
|
|
15428
|
-
}
|
|
15429
|
-
catch (error) {
|
|
15430
|
-
this.stats.errors++;
|
|
15431
|
-
// Platform-agnostic error logging
|
|
15432
|
-
if (typeof console !== 'undefined' && console.warn) {
|
|
15433
|
-
console.warn('Cache cleanup error:', error);
|
|
15434
|
-
}
|
|
15435
|
-
}
|
|
15436
|
-
}
|
|
15437
|
-
estimateValueSize(value) {
|
|
15438
|
-
if (typeof value === 'string') {
|
|
15439
|
-
return value.length * 2; // UTF-16 encoding
|
|
15440
|
-
}
|
|
15441
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
15442
|
-
return 8; // Primitive values
|
|
15443
|
-
}
|
|
15444
|
-
if (value === null || value === undefined) {
|
|
15445
|
-
return 4;
|
|
15446
|
-
}
|
|
15447
|
-
try {
|
|
15448
|
-
// More accurate JSON-based estimation for serializable objects
|
|
15449
|
-
const jsonString = JSON.stringify(value);
|
|
15450
|
-
return jsonString.length * 2;
|
|
15451
|
-
}
|
|
15452
|
-
catch {
|
|
15453
|
-
// Fallback for non-serializable objects
|
|
15454
|
-
if (Array.isArray(value)) {
|
|
15455
|
-
return value.length * 100; // Better estimate for arrays
|
|
15456
|
-
}
|
|
15457
|
-
if (value && typeof value === 'object') {
|
|
15458
|
-
return Object.keys(value).length * 50; // Better estimate for objects
|
|
15459
|
-
}
|
|
15460
|
-
return DEFAULT_OBJECT_SIZE_BYTES;
|
|
15461
|
-
}
|
|
15462
|
-
}
|
|
15463
|
-
estimateMemoryUsage() {
|
|
15464
|
-
let totalSize = 0;
|
|
15465
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
15466
|
-
totalSize += key.length * 2; // Key size
|
|
15467
|
-
totalSize += this.estimateValueSize(entry.value);
|
|
15468
|
-
totalSize += ENTRY_OVERHEAD_BYTES;
|
|
15469
|
-
}
|
|
15470
|
-
return totalSize / (1024 * 1024);
|
|
15471
|
-
}
|
|
15472
|
-
enforceMemoryLimit() {
|
|
15473
|
-
const memoryUsage = this.estimateMemoryUsage();
|
|
15474
|
-
if (memoryUsage <= this.config.maxMemoryMB || this.cache.size === 0)
|
|
15475
|
-
return;
|
|
15476
|
-
this.evictOldestEntries();
|
|
15477
|
-
}
|
|
15478
|
-
evictOldestEntries() {
|
|
15479
|
-
if (this.cache.size === 0)
|
|
15480
|
-
return;
|
|
15481
|
-
const batchSize = Math.floor(this.cache.size * this.config.evictionBatchPercent);
|
|
15482
|
-
const toRemove = Math.max(batchSize, 1);
|
|
15483
|
-
// Efficiently remove oldest entries using access order
|
|
15484
|
-
const iterator = this.accessOrder.values();
|
|
15485
|
-
for (let i = 0; i < toRemove; i++) {
|
|
15486
|
-
const result = iterator.next();
|
|
15487
|
-
if (result.done)
|
|
15488
|
-
break;
|
|
15489
|
-
const key = result.value;
|
|
15490
|
-
this.cache.delete(key);
|
|
15491
|
-
this.accessOrder.delete(key);
|
|
15492
|
-
}
|
|
15493
|
-
}
|
|
15494
|
-
}
|
|
15495
|
-
// Singleton instance
|
|
15496
|
-
const globalCacheService = new CacheService();
|
|
15497
|
-
|
|
15498
|
-
/**
|
|
15499
|
-
* Cache module exports
|
|
15500
|
-
*/
|
|
15501
|
-
// Predefined cache TTL constants
|
|
15502
|
-
const CacheTTL = {
|
|
15503
|
-
SHORT: 5 * 60 * 1000, // 5 minutes
|
|
15504
|
-
MEDIUM: 30 * 60 * 1000, // 30 minutes
|
|
15505
|
-
LONG: 24 * 60 * 60 * 1000, // 24 hours
|
|
15506
|
-
METADATA: 24 * 60 * 60 * 1000, // 24 hours - IPFS metadata is immutable
|
|
15507
|
-
GATEWAY: 30 * 60 * 1000, // 30 minutes - Gateway config can change
|
|
15508
|
-
PROVIDER: 60 * 60 * 1000, // 1 hour - Provider connections with auth
|
|
15509
|
-
};
|
|
15510
|
-
|
|
15511
17890
|
/**
|
|
15512
17891
|
* AsyncStorage Token Storage for React Native Mobile Platforms
|
|
15513
17892
|
*
|
|
@@ -16052,7 +18431,35 @@ class ReactNativeHttpClient {
|
|
|
16052
18431
|
|
|
16053
18432
|
// Create the context
|
|
16054
18433
|
const SDKContext = react.createContext(null);
|
|
16055
|
-
|
|
18434
|
+
/**
|
|
18435
|
+
* PERS SDK Provider for React Native
|
|
18436
|
+
*
|
|
18437
|
+
* Wraps your app to provide SDK context to all child components.
|
|
18438
|
+
* Handles platform-specific initialization (DPoP, storage, etc.).
|
|
18439
|
+
*
|
|
18440
|
+
* @param config - SDK configuration (see PersConfig)
|
|
18441
|
+
* @param config.apiProjectKey - Your PERS project key (required)
|
|
18442
|
+
* @param config.environment - 'staging' | 'production' (default: 'staging')
|
|
18443
|
+
* @param config.captureWalletEvents - Enable real-time blockchain events (default: true)
|
|
18444
|
+
* @param config.dpop - DPoP configuration for enhanced security
|
|
18445
|
+
*
|
|
18446
|
+
* @example Basic usage
|
|
18447
|
+
* ```tsx
|
|
18448
|
+
* <PersSDKProvider config={{ apiProjectKey: 'my-project' }}>
|
|
18449
|
+
* <App />
|
|
18450
|
+
* </PersSDKProvider>
|
|
18451
|
+
* ```
|
|
18452
|
+
*
|
|
18453
|
+
* @example Disable wallet events
|
|
18454
|
+
* ```tsx
|
|
18455
|
+
* <PersSDKProvider config={{
|
|
18456
|
+
* apiProjectKey: 'my-project',
|
|
18457
|
+
* captureWalletEvents: false // Disable auto-connect to blockchain events
|
|
18458
|
+
* }}>
|
|
18459
|
+
* <App />
|
|
18460
|
+
* </PersSDKProvider>
|
|
18461
|
+
* ```
|
|
18462
|
+
*/
|
|
16056
18463
|
const PersSDKProvider = ({ children, config }) => {
|
|
16057
18464
|
const initializingRef = react.useRef(false);
|
|
16058
18465
|
// State refs for stable functions to read current values
|
|
@@ -36196,6 +38603,7 @@ class Web3ChainApi {
|
|
|
36196
38603
|
// ==========================================
|
|
36197
38604
|
/**
|
|
36198
38605
|
* PUBLIC: Get chain data by chain ID
|
|
38606
|
+
* Returns ChainDataDTO from the API (rpcUrl may be optional)
|
|
36199
38607
|
*/
|
|
36200
38608
|
async getChainData(chainId) {
|
|
36201
38609
|
try {
|
|
@@ -36423,7 +38831,7 @@ class TokenDomainService {
|
|
|
36423
38831
|
const abi = convertAbiToInterface(params.abi);
|
|
36424
38832
|
const tokenUri = await this.web3Api.getTokenUri({ ...params, abi });
|
|
36425
38833
|
const metadata = tokenUri
|
|
36426
|
-
? await this.metadataService.fetchAndProcessMetadata(tokenUri
|
|
38834
|
+
? await this.metadataService.fetchAndProcessMetadata(tokenUri)
|
|
36427
38835
|
: null;
|
|
36428
38836
|
return { tokenId: params.tokenId, tokenUri, metadata };
|
|
36429
38837
|
}
|
|
@@ -36614,17 +39022,19 @@ class TokenDomainService {
|
|
|
36614
39022
|
|
|
36615
39023
|
/**
|
|
36616
39024
|
* MetadataDomainService - Clean IPFS metadata resolution with centralized caching
|
|
39025
|
+
*
|
|
39026
|
+
* IPFS gateway is resolved from tenant configuration (chain-agnostic).
|
|
36617
39027
|
*/
|
|
36618
39028
|
class MetadataDomainService {
|
|
36619
39029
|
constructor(ipfsApi) {
|
|
36620
39030
|
this.ipfsApi = ipfsApi;
|
|
36621
39031
|
this.cache = globalCacheService.createNamespace('metadata');
|
|
36622
39032
|
}
|
|
36623
|
-
async fetchAndProcessMetadata(tokenUri
|
|
36624
|
-
const cacheKey = `fetch:${tokenUri}
|
|
39033
|
+
async fetchAndProcessMetadata(tokenUri) {
|
|
39034
|
+
const cacheKey = `fetch:${tokenUri}`;
|
|
36625
39035
|
return this.cache.getOrSet(cacheKey, async () => {
|
|
36626
39036
|
try {
|
|
36627
|
-
return await this.ipfsApi.fetchAndProcessMetadata(tokenUri
|
|
39037
|
+
return await this.ipfsApi.fetchAndProcessMetadata(tokenUri);
|
|
36628
39038
|
}
|
|
36629
39039
|
catch (error) {
|
|
36630
39040
|
console.error(`Error fetching metadata for ${tokenUri}:`, error);
|
|
@@ -36632,9 +39042,9 @@ class MetadataDomainService {
|
|
|
36632
39042
|
}
|
|
36633
39043
|
}, CacheTTL.METADATA);
|
|
36634
39044
|
}
|
|
36635
|
-
async resolveIPFSUrl(url
|
|
36636
|
-
const cacheKey = `resolve:${url}
|
|
36637
|
-
return this.cache.getOrSet(cacheKey, () => this.ipfsApi.resolveIPFSUrl(url
|
|
39045
|
+
async resolveIPFSUrl(url) {
|
|
39046
|
+
const cacheKey = `resolve:${url}`;
|
|
39047
|
+
return this.cache.getOrSet(cacheKey, () => this.ipfsApi.resolveIPFSUrl(url), CacheTTL.GATEWAY);
|
|
36638
39048
|
}
|
|
36639
39049
|
/**
|
|
36640
39050
|
* Clear all cached metadata and URLs for this service
|
|
@@ -36734,19 +39144,22 @@ class Web3ApplicationService {
|
|
|
36734
39144
|
});
|
|
36735
39145
|
}
|
|
36736
39146
|
/**
|
|
36737
|
-
* Resolve IPFS URLs to HTTPS if needed
|
|
39147
|
+
* Resolve IPFS URLs to HTTPS if needed.
|
|
39148
|
+
*
|
|
39149
|
+
* IPFS gateway is resolved from tenant configuration (chain-agnostic).
|
|
36738
39150
|
*/
|
|
36739
|
-
async resolveIPFSUrl(url
|
|
36740
|
-
return this.metadataDomainService.resolveIPFSUrl(url
|
|
39151
|
+
async resolveIPFSUrl(url) {
|
|
39152
|
+
return this.metadataDomainService.resolveIPFSUrl(url);
|
|
36741
39153
|
}
|
|
36742
39154
|
/**
|
|
36743
39155
|
* Fetch and process metadata from any URI with IPFS conversion.
|
|
36744
39156
|
*
|
|
36745
39157
|
* Use this for ad-hoc metadata fetching when you have a tokenURI.
|
|
36746
39158
|
* Normalization happens at infrastructure layer.
|
|
39159
|
+
* IPFS gateway is resolved from tenant configuration (chain-agnostic).
|
|
36747
39160
|
*/
|
|
36748
|
-
async fetchAndProcessMetadata(tokenUri
|
|
36749
|
-
return this.metadataDomainService.fetchAndProcessMetadata(tokenUri
|
|
39161
|
+
async fetchAndProcessMetadata(tokenUri) {
|
|
39162
|
+
return this.metadataDomainService.fetchAndProcessMetadata(tokenUri);
|
|
36750
39163
|
}
|
|
36751
39164
|
}
|
|
36752
39165
|
|
|
@@ -36800,30 +39213,42 @@ class Web3InfrastructureApi {
|
|
|
36800
39213
|
|
|
36801
39214
|
/**
|
|
36802
39215
|
* IPFSInfrastructureApi - Infrastructure implementation for IPFS operations
|
|
36803
|
-
* Uses
|
|
39216
|
+
* Uses TenantManager for IPFS gateway resolution with centralized caching
|
|
39217
|
+
*
|
|
39218
|
+
* IMPORTANT: IPFS gateway domain is now fetched from tenant configuration (TenantClientConfigDTO),
|
|
39219
|
+
* not from chain data. This is the correct architecture since IPFS is chain-agnostic.
|
|
36804
39220
|
*/
|
|
36805
39221
|
class IPFSInfrastructureApi {
|
|
36806
|
-
constructor(
|
|
36807
|
-
this.
|
|
36808
|
-
this.defaultIpfsGatewayDomain = 'pers.mypinata.cloud';
|
|
39222
|
+
constructor(tenantManager) {
|
|
39223
|
+
this.tenantManager = tenantManager;
|
|
36809
39224
|
this.cache = globalCacheService.createNamespace('ipfs');
|
|
36810
39225
|
}
|
|
36811
|
-
|
|
36812
|
-
|
|
39226
|
+
/**
|
|
39227
|
+
* Get IPFS gateway domain from tenant configuration.
|
|
39228
|
+
*
|
|
39229
|
+
* @returns The IPFS gateway domain from tenant configuration
|
|
39230
|
+
* @throws Error if tenant config is not found or ipfsGatewayDomain is not configured
|
|
39231
|
+
*/
|
|
39232
|
+
async getIpfsGatewayDomain() {
|
|
39233
|
+
const cacheKey = 'gateway';
|
|
36813
39234
|
return this.cache.getOrSet(cacheKey, async () => {
|
|
36814
|
-
|
|
36815
|
-
|
|
36816
|
-
|
|
36817
|
-
|
|
36818
|
-
catch (error) {
|
|
36819
|
-
console.warn(`Failed to get chain data for chainId ${chainId}, using default IPFS gateway:`, error);
|
|
36820
|
-
return this.defaultIpfsGatewayDomain;
|
|
39235
|
+
const clientConfig = await this.tenantManager.getClientConfig();
|
|
39236
|
+
if (!clientConfig.ipfsGatewayDomain) {
|
|
39237
|
+
throw new Error(`IPFS gateway domain not configured for tenant. ` +
|
|
39238
|
+
`Please configure ipfsGatewayDomain in the tenant configuration.`);
|
|
36821
39239
|
}
|
|
39240
|
+
return clientConfig.ipfsGatewayDomain;
|
|
36822
39241
|
}, CacheTTL.GATEWAY);
|
|
36823
39242
|
}
|
|
36824
|
-
|
|
39243
|
+
/**
|
|
39244
|
+
* Resolve IPFS URL to HTTPS URL using tenant's configured gateway.
|
|
39245
|
+
*
|
|
39246
|
+
* @param url - The URL to resolve (can be ipfs:// or https://)
|
|
39247
|
+
* @returns Resolved HTTPS URL
|
|
39248
|
+
*/
|
|
39249
|
+
async resolveIPFSUrl(url) {
|
|
36825
39250
|
if (url.startsWith('ipfs://')) {
|
|
36826
|
-
const gateway = await this.getIpfsGatewayDomain(
|
|
39251
|
+
const gateway = await this.getIpfsGatewayDomain();
|
|
36827
39252
|
return url.replace('ipfs://', `https://${gateway}/ipfs/`);
|
|
36828
39253
|
}
|
|
36829
39254
|
return url;
|
|
@@ -36836,12 +39261,11 @@ class IPFSInfrastructureApi {
|
|
|
36836
39261
|
* All downstream consumers receive normalized SDK format with resolved HTTPS URLs.
|
|
36837
39262
|
*
|
|
36838
39263
|
* @param tokenUri - Token URI (can be ipfs:// or https://)
|
|
36839
|
-
* @param chainId - Chain ID for IPFS gateway selection
|
|
36840
39264
|
* @returns Normalized TokenMetadata with resolved HTTPS URLs, or null on error
|
|
36841
39265
|
*/
|
|
36842
|
-
async fetchAndProcessMetadata(tokenUri
|
|
39266
|
+
async fetchAndProcessMetadata(tokenUri) {
|
|
36843
39267
|
try {
|
|
36844
|
-
const resolvedUri = await this.resolveIPFSUrl(tokenUri
|
|
39268
|
+
const resolvedUri = await this.resolveIPFSUrl(tokenUri);
|
|
36845
39269
|
const response = await fetch(resolvedUri);
|
|
36846
39270
|
if (!response.ok) {
|
|
36847
39271
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
@@ -36849,10 +39273,10 @@ class IPFSInfrastructureApi {
|
|
|
36849
39273
|
const rawMetadata = await response.json();
|
|
36850
39274
|
// Resolve IPFS URLs for media fields
|
|
36851
39275
|
const resolvedImageUrl = rawMetadata.image
|
|
36852
|
-
? await this.resolveIPFSUrl(rawMetadata.image
|
|
39276
|
+
? await this.resolveIPFSUrl(rawMetadata.image)
|
|
36853
39277
|
: '';
|
|
36854
39278
|
const resolvedAnimationUrl = rawMetadata.animation_url
|
|
36855
|
-
? await this.resolveIPFSUrl(rawMetadata.animation_url
|
|
39279
|
+
? await this.resolveIPFSUrl(rawMetadata.animation_url)
|
|
36856
39280
|
: undefined;
|
|
36857
39281
|
// Extract custom properties (anything not in ERC standard)
|
|
36858
39282
|
const customProperties = Object.fromEntries(Object.entries(rawMetadata).filter(([key]) => !['name', 'description', 'image', 'animation_url', 'external_url', 'attributes'].includes(key)));
|
|
@@ -36985,6 +39409,11 @@ async function getExplorerUrlByChainId(getChainData, chainId, address, type) {
|
|
|
36985
39409
|
* tokenIds // Required for ERC-1155, optional for ERC-721
|
|
36986
39410
|
* });
|
|
36987
39411
|
* ```
|
|
39412
|
+
*
|
|
39413
|
+
* ## IPFS Resolution
|
|
39414
|
+
*
|
|
39415
|
+
* For IPFS URL resolution, use `sdk.tenant.resolveIPFSUrl()` instead.
|
|
39416
|
+
* IPFS is chain-agnostic and configured at the tenant level.
|
|
36988
39417
|
*/
|
|
36989
39418
|
class Web3Manager {
|
|
36990
39419
|
// TODO: Add PersEventEmitter support for blockchain events
|
|
@@ -36994,14 +39423,16 @@ class Web3Manager {
|
|
|
36994
39423
|
// 3. Emit via: this.events?.emitSuccess({ domain: 'web3', type: 'NFT_TRANSFERRED', ... })
|
|
36995
39424
|
// 4. Filter to only user's address (not all transfers)
|
|
36996
39425
|
// 5. Add cleanup for event listeners on SDK destroy
|
|
36997
|
-
constructor(apiClient) {
|
|
39426
|
+
constructor(apiClient, tenantManager) {
|
|
36998
39427
|
this.apiClient = apiClient;
|
|
39428
|
+
// Use provided TenantManager or create one
|
|
39429
|
+
this.tenantManager = tenantManager || new TenantManager(apiClient);
|
|
36999
39430
|
// Initialize Web3 Chain service
|
|
37000
39431
|
const web3ChainApi = new Web3ChainApi(apiClient);
|
|
37001
39432
|
this.web3ChainService = new Web3ChainService(web3ChainApi);
|
|
37002
|
-
// Initialize Web3 Application service
|
|
39433
|
+
// Initialize Web3 Application service with TenantManager for IPFS
|
|
37003
39434
|
const web3InfrastructureApi = new Web3InfrastructureApi(this.web3ChainService);
|
|
37004
|
-
const ipfsInfrastructureApi = new IPFSInfrastructureApi(this.
|
|
39435
|
+
const ipfsInfrastructureApi = new IPFSInfrastructureApi(this.tenantManager);
|
|
37005
39436
|
this.web3ApplicationService = new Web3ApplicationService(web3InfrastructureApi, ipfsInfrastructureApi);
|
|
37006
39437
|
}
|
|
37007
39438
|
/**
|
|
@@ -37031,25 +39462,14 @@ class Web3Manager {
|
|
|
37031
39462
|
async getTokenCollection(request) {
|
|
37032
39463
|
return this.web3ApplicationService.getTokenCollection(request);
|
|
37033
39464
|
}
|
|
37034
|
-
/**
|
|
37035
|
-
* Resolve IPFS URL to accessible URL
|
|
37036
|
-
*
|
|
37037
|
-
* @param url - IPFS URL to resolve
|
|
37038
|
-
* @param chainId - Chain ID for context
|
|
37039
|
-
* @returns Promise resolving to accessible URL
|
|
37040
|
-
*/
|
|
37041
|
-
async resolveIPFSUrl(url, chainId) {
|
|
37042
|
-
return this.web3ApplicationService.resolveIPFSUrl(url, chainId);
|
|
37043
|
-
}
|
|
37044
39465
|
/**
|
|
37045
39466
|
* Fetch and process token metadata
|
|
37046
39467
|
*
|
|
37047
39468
|
* @param tokenUri - Token URI to fetch metadata from
|
|
37048
|
-
* @param chainId - Chain ID for context
|
|
37049
39469
|
* @returns Promise resolving to processed metadata or null if not found
|
|
37050
39470
|
*/
|
|
37051
|
-
async fetchAndProcessMetadata(tokenUri
|
|
37052
|
-
return this.web3ApplicationService.fetchAndProcessMetadata(tokenUri
|
|
39471
|
+
async fetchAndProcessMetadata(tokenUri) {
|
|
39472
|
+
return this.web3ApplicationService.fetchAndProcessMetadata(tokenUri);
|
|
37053
39473
|
}
|
|
37054
39474
|
/**
|
|
37055
39475
|
* Get blockchain chain data by chain ID
|
|
@@ -37133,7 +39553,7 @@ class Web3Manager {
|
|
|
37133
39553
|
*
|
|
37134
39554
|
* @param accountAddress - Any valid blockchain address (wallet, contract, etc.)
|
|
37135
39555
|
* @param token - Token definition (from getRewardTokens, getStatusTokens, etc.)
|
|
37136
|
-
* @param maxTokens - Maximum tokens to retrieve (default:
|
|
39556
|
+
* @param maxTokens - Maximum tokens to retrieve (default: 100, max: 100)
|
|
37137
39557
|
* @returns Promise resolving to collection result with owned tokens
|
|
37138
39558
|
*
|
|
37139
39559
|
* @example Query user's wallet
|
|
@@ -37153,7 +39573,7 @@ class Web3Manager {
|
|
|
37153
39573
|
* @see {@link extractTokenIds} - Low-level helper used internally for ERC-1155
|
|
37154
39574
|
* @see {@link buildCollectionRequest} - For manual request building
|
|
37155
39575
|
*/
|
|
37156
|
-
async getAccountOwnedTokensFromContract(accountAddress, token, maxTokens =
|
|
39576
|
+
async getAccountOwnedTokensFromContract(accountAddress, token, maxTokens = 100) {
|
|
37157
39577
|
// For ERC-1155, extract tokenIds from metadata
|
|
37158
39578
|
const tokenIds = token.type === NativeTokenTypes.ERC1155 ? this.extractTokenIds(token) : undefined;
|
|
37159
39579
|
const collection = await this.getTokenCollection({
|
|
@@ -37354,25 +39774,38 @@ const useWeb3 = () => {
|
|
|
37354
39774
|
throw error;
|
|
37355
39775
|
}
|
|
37356
39776
|
}, [web3, isInitialized]);
|
|
37357
|
-
|
|
37358
|
-
|
|
39777
|
+
/**
|
|
39778
|
+
* Resolve IPFS URL to HTTP gateway URL
|
|
39779
|
+
*
|
|
39780
|
+
* @deprecated Use `sdk.tenant.resolveIPFSUrl()` directly - IPFS is chain-agnostic
|
|
39781
|
+
* @param url - IPFS URL to resolve (ipfs://...)
|
|
39782
|
+
* @returns Promise resolving to HTTP gateway URL
|
|
39783
|
+
*/
|
|
39784
|
+
const resolveIPFSUrl = react.useCallback(async (url) => {
|
|
39785
|
+
if (!isInitialized || !sdk) {
|
|
37359
39786
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
37360
39787
|
}
|
|
37361
39788
|
try {
|
|
37362
|
-
const result = await
|
|
39789
|
+
const result = await sdk.tenants.resolveIPFSUrl(url);
|
|
37363
39790
|
return result;
|
|
37364
39791
|
}
|
|
37365
39792
|
catch (error) {
|
|
37366
39793
|
console.error('Failed to resolve IPFS URL:', error);
|
|
37367
39794
|
throw error;
|
|
37368
39795
|
}
|
|
37369
|
-
}, [
|
|
37370
|
-
|
|
39796
|
+
}, [sdk, isInitialized]);
|
|
39797
|
+
/**
|
|
39798
|
+
* Fetch and process token metadata from a URI
|
|
39799
|
+
*
|
|
39800
|
+
* @param tokenUri - Token URI to fetch metadata from
|
|
39801
|
+
* @returns Promise resolving to processed metadata or null
|
|
39802
|
+
*/
|
|
39803
|
+
const fetchAndProcessMetadata = react.useCallback(async (tokenUri) => {
|
|
37371
39804
|
if (!isInitialized || !web3) {
|
|
37372
39805
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
37373
39806
|
}
|
|
37374
39807
|
try {
|
|
37375
|
-
const result = await web3.fetchAndProcessMetadata(tokenUri
|
|
39808
|
+
const result = await web3.fetchAndProcessMetadata(tokenUri);
|
|
37376
39809
|
return result;
|
|
37377
39810
|
}
|
|
37378
39811
|
catch (error) {
|
|
@@ -37442,7 +39875,7 @@ const useWeb3 = () => {
|
|
|
37442
39875
|
*
|
|
37443
39876
|
* @param accountAddress - Any valid blockchain address (wallet, contract, etc.)
|
|
37444
39877
|
* @param token - Token definition (from getRewardTokens, getStatusTokens, etc.)
|
|
37445
|
-
* @param maxTokens - Maximum tokens to retrieve (default:
|
|
39878
|
+
* @param maxTokens - Maximum tokens to retrieve (default: 100, max: 100)
|
|
37446
39879
|
* @returns Promise resolving to result with owned tokens
|
|
37447
39880
|
* @throws Error if SDK is not initialized
|
|
37448
39881
|
*
|
|
@@ -37461,7 +39894,7 @@ const useWeb3 = () => {
|
|
|
37461
39894
|
* @see {@link extractTokenIds} - Low-level helper used internally for ERC-1155
|
|
37462
39895
|
* @see {@link buildCollectionRequest} - For manual request building
|
|
37463
39896
|
*/
|
|
37464
|
-
const getAccountOwnedTokensFromContract = react.useCallback(async (accountAddress, token, maxTokens =
|
|
39897
|
+
const getAccountOwnedTokensFromContract = react.useCallback(async (accountAddress, token, maxTokens = 100) => {
|
|
37465
39898
|
if (!isInitialized || !web3) {
|
|
37466
39899
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
37467
39900
|
}
|
|
@@ -37476,10 +39909,10 @@ const useWeb3 = () => {
|
|
|
37476
39909
|
*
|
|
37477
39910
|
* @param accountAddress - Any valid blockchain address (wallet, contract, etc.)
|
|
37478
39911
|
* @param token - Token definition
|
|
37479
|
-
* @param maxTokens - Maximum tokens to retrieve (default:
|
|
39912
|
+
* @param maxTokens - Maximum tokens to retrieve (default: 100, max: 100)
|
|
37480
39913
|
* @returns TokenCollectionRequest ready for getTokenCollection()
|
|
37481
39914
|
*/
|
|
37482
|
-
const buildCollectionRequest = react.useCallback((accountAddress, token, maxTokens =
|
|
39915
|
+
const buildCollectionRequest = react.useCallback((accountAddress, token, maxTokens = 100) => {
|
|
37483
39916
|
if (!web3) {
|
|
37484
39917
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
37485
39918
|
}
|
|
@@ -37571,6 +40004,18 @@ const useWeb3 = () => {
|
|
|
37571
40004
|
* ```
|
|
37572
40005
|
*
|
|
37573
40006
|
* @example
|
|
40007
|
+
* **With Wallet Events (Real-time):**
|
|
40008
|
+
* ```typescript
|
|
40009
|
+
* // Auto-refresh on Transfer, Approval, and other blockchain events
|
|
40010
|
+
* const { tokenBalances } = useTokenBalances({
|
|
40011
|
+
* accountAddress: walletAddress!,
|
|
40012
|
+
* availableTokens,
|
|
40013
|
+
* refreshOnWalletEvents: true, // Enable real-time refresh (default: true)
|
|
40014
|
+
* walletEventDebounceMs: 1000 // Debounce rapid events (default: 1000ms)
|
|
40015
|
+
* });
|
|
40016
|
+
* ```
|
|
40017
|
+
*
|
|
40018
|
+
* @example
|
|
37574
40019
|
* **Multi-Wallet Support:**
|
|
37575
40020
|
* ```typescript
|
|
37576
40021
|
* function MultiWalletBalances() {
|
|
@@ -37589,12 +40034,14 @@ const useWeb3 = () => {
|
|
|
37589
40034
|
* ```
|
|
37590
40035
|
*/
|
|
37591
40036
|
function useTokenBalances(options) {
|
|
37592
|
-
const { accountAddress, availableTokens = [], autoLoad = true, refreshInterval = 0 } = options;
|
|
40037
|
+
const { accountAddress, availableTokens = [], autoLoad = true, refreshInterval = 0, refreshOnWalletEvents = true, walletEventDebounceMs = 1000 } = options;
|
|
37593
40038
|
const { isAuthenticated, sdk } = usePersSDK();
|
|
37594
40039
|
const web3 = useWeb3();
|
|
37595
40040
|
const [tokenBalances, setTokenBalances] = react.useState([]);
|
|
37596
40041
|
const [isLoading, setIsLoading] = react.useState(false);
|
|
37597
40042
|
const [error, setError] = react.useState(null);
|
|
40043
|
+
// Debounce ref for wallet events
|
|
40044
|
+
const walletEventDebounceRef = react.useRef(null);
|
|
37598
40045
|
// Check if the hook is available for use
|
|
37599
40046
|
const isAvailable = web3.isAvailable && isAuthenticated && !!accountAddress;
|
|
37600
40047
|
/**
|
|
@@ -37714,6 +40161,32 @@ function useTokenBalances(options) {
|
|
|
37714
40161
|
clearInterval(intervalId);
|
|
37715
40162
|
};
|
|
37716
40163
|
}, [sdk, refreshInterval, isAvailable, loadBalances]);
|
|
40164
|
+
// Wallet events refresh: listen for real-time blockchain events
|
|
40165
|
+
// Refreshes on ANY wallet domain event (Transfer, TransferSingle, etc.)
|
|
40166
|
+
// Debouncing prevents excessive API calls during rapid events
|
|
40167
|
+
react.useEffect(() => {
|
|
40168
|
+
if (!sdk || !refreshOnWalletEvents || !isAvailable)
|
|
40169
|
+
return;
|
|
40170
|
+
const unsubscribe = sdk.events.subscribe((event) => {
|
|
40171
|
+
if (event.domain === 'wallet') {
|
|
40172
|
+
// Debounce rapid events (batch transfers, etc.)
|
|
40173
|
+
if (walletEventDebounceRef.current) {
|
|
40174
|
+
clearTimeout(walletEventDebounceRef.current);
|
|
40175
|
+
}
|
|
40176
|
+
walletEventDebounceRef.current = setTimeout(() => {
|
|
40177
|
+
console.log(`[useTokenBalances] Wallet event (${event.type}), refreshing balances...`);
|
|
40178
|
+
loadBalances();
|
|
40179
|
+
walletEventDebounceRef.current = null;
|
|
40180
|
+
}, walletEventDebounceMs);
|
|
40181
|
+
}
|
|
40182
|
+
}, { domains: ['wallet'] });
|
|
40183
|
+
return () => {
|
|
40184
|
+
unsubscribe();
|
|
40185
|
+
if (walletEventDebounceRef.current) {
|
|
40186
|
+
clearTimeout(walletEventDebounceRef.current);
|
|
40187
|
+
}
|
|
40188
|
+
};
|
|
40189
|
+
}, [sdk, refreshOnWalletEvents, walletEventDebounceMs, isAvailable, loadBalances]);
|
|
37717
40190
|
return {
|
|
37718
40191
|
tokenBalances,
|
|
37719
40192
|
isLoading,
|
|
@@ -39315,7 +41788,23 @@ const useTenants = () => {
|
|
|
39315
41788
|
|
|
39316
41789
|
const useUsers = () => {
|
|
39317
41790
|
const { sdk, isInitialized, isAuthenticated } = usePersSDK();
|
|
39318
|
-
|
|
41791
|
+
/**
|
|
41792
|
+
* Get the current authenticated user with optional include relations
|
|
41793
|
+
*
|
|
41794
|
+
* @param options - Query options including include relations
|
|
41795
|
+
* @returns Current user data
|
|
41796
|
+
*
|
|
41797
|
+
* @example
|
|
41798
|
+
* ```typescript
|
|
41799
|
+
* // Basic
|
|
41800
|
+
* const user = await getCurrentUser();
|
|
41801
|
+
*
|
|
41802
|
+
* // With wallets included
|
|
41803
|
+
* const userWithWallets = await getCurrentUser({ include: ['wallets'] });
|
|
41804
|
+
* console.log('Wallets:', userWithWallets.included?.wallets);
|
|
41805
|
+
* ```
|
|
41806
|
+
*/
|
|
41807
|
+
const getCurrentUser = react.useCallback(async (options) => {
|
|
39319
41808
|
if (!isInitialized || !sdk) {
|
|
39320
41809
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
39321
41810
|
}
|
|
@@ -39323,7 +41812,7 @@ const useUsers = () => {
|
|
|
39323
41812
|
throw new Error('SDK not authenticated. getCurrentUser requires authentication.');
|
|
39324
41813
|
}
|
|
39325
41814
|
try {
|
|
39326
|
-
const result = await sdk.users.getCurrentUser();
|
|
41815
|
+
const result = await sdk.users.getCurrentUser(options);
|
|
39327
41816
|
return result;
|
|
39328
41817
|
}
|
|
39329
41818
|
catch (error) {
|
|
@@ -39347,12 +41836,28 @@ const useUsers = () => {
|
|
|
39347
41836
|
throw error;
|
|
39348
41837
|
}
|
|
39349
41838
|
}, [sdk, isInitialized, isAuthenticated]);
|
|
39350
|
-
|
|
41839
|
+
/**
|
|
41840
|
+
* Get user by ID with optional include relations
|
|
41841
|
+
*
|
|
41842
|
+
* @param userId - User identifier (id, email, externalId, etc.)
|
|
41843
|
+
* @param options - Query options including include relations
|
|
41844
|
+
* @returns User data
|
|
41845
|
+
*
|
|
41846
|
+
* @example
|
|
41847
|
+
* ```typescript
|
|
41848
|
+
* // Basic
|
|
41849
|
+
* const user = await getUserById('user-123');
|
|
41850
|
+
*
|
|
41851
|
+
* // With wallets included
|
|
41852
|
+
* const userWithWallets = await getUserById('user-123', { include: ['wallets'] });
|
|
41853
|
+
* ```
|
|
41854
|
+
*/
|
|
41855
|
+
const getUserById = react.useCallback(async (userId, options) => {
|
|
39351
41856
|
if (!isInitialized || !sdk) {
|
|
39352
41857
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
39353
41858
|
}
|
|
39354
41859
|
try {
|
|
39355
|
-
const result = await sdk.users.getUserById(userId);
|
|
41860
|
+
const result = await sdk.users.getUserById(userId, options);
|
|
39356
41861
|
return result;
|
|
39357
41862
|
}
|
|
39358
41863
|
catch (error) {
|
|
@@ -39400,16 +41905,23 @@ const useUsers = () => {
|
|
|
39400
41905
|
throw error;
|
|
39401
41906
|
}
|
|
39402
41907
|
}, [sdk, isInitialized]);
|
|
39403
|
-
|
|
41908
|
+
/**
|
|
41909
|
+
* Toggle or set a user's active status (admin only)
|
|
41910
|
+
*
|
|
41911
|
+
* @param userId - User ID to update
|
|
41912
|
+
* @param isActive - Optional explicit status. If omitted, toggles current status.
|
|
41913
|
+
* @returns Updated user
|
|
41914
|
+
*/
|
|
41915
|
+
const setUserActiveStatus = react.useCallback(async (userId, isActive) => {
|
|
39404
41916
|
if (!isInitialized || !sdk) {
|
|
39405
41917
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
39406
41918
|
}
|
|
39407
41919
|
try {
|
|
39408
|
-
const result = await sdk.users.
|
|
41920
|
+
const result = await sdk.users.setUserActiveStatus(userId, isActive);
|
|
39409
41921
|
return result;
|
|
39410
41922
|
}
|
|
39411
41923
|
catch (error) {
|
|
39412
|
-
console.error('Failed to
|
|
41924
|
+
console.error('Failed to set user active status:', error);
|
|
39413
41925
|
throw error;
|
|
39414
41926
|
}
|
|
39415
41927
|
}, [sdk, isInitialized]);
|
|
@@ -39420,7 +41932,7 @@ const useUsers = () => {
|
|
|
39420
41932
|
getAllUsersPublic,
|
|
39421
41933
|
getAllUsers,
|
|
39422
41934
|
updateUser,
|
|
39423
|
-
|
|
41935
|
+
setUserActiveStatus,
|
|
39424
41936
|
isAvailable: isInitialized && !!sdk?.users,
|
|
39425
41937
|
};
|
|
39426
41938
|
};
|
|
@@ -39968,23 +42480,35 @@ const useDonations = () => {
|
|
|
39968
42480
|
* React Native hook for PERS SDK event system
|
|
39969
42481
|
*
|
|
39970
42482
|
* This hook provides access to the platform-agnostic event system for subscribing to
|
|
39971
|
-
* transaction, authentication, campaign, and system events. All events
|
|
39972
|
-
* `userMessage` field ready for display to end users.
|
|
42483
|
+
* transaction, authentication, campaign, blockchain, and system events. All events
|
|
42484
|
+
* include a `userMessage` field ready for display to end users.
|
|
39973
42485
|
*
|
|
39974
42486
|
* **Event Domains:**
|
|
39975
42487
|
* - `auth` - Authentication events (login, logout, token refresh)
|
|
39976
42488
|
* - `user` - User profile events (update, create)
|
|
39977
|
-
* - `transaction` - Transaction events (created,
|
|
42489
|
+
* - `transaction` - Transaction events (created, submitted, confirmed)
|
|
39978
42490
|
* - `campaign` - Campaign events (claimed, activated)
|
|
39979
42491
|
* - `redemption` - Redemption events (redeemed, expired)
|
|
39980
42492
|
* - `business` - Business events (created, updated, membership)
|
|
42493
|
+
* - `wallet` - Real-time blockchain events (Transfer, Approval, etc.) - Auto-enabled on auth
|
|
39981
42494
|
* - `api` - API error events (network, validation, server errors)
|
|
39982
42495
|
*
|
|
42496
|
+
* **Blockchain Events (Wallet Domain):**
|
|
42497
|
+
* When `sdk.connectWalletEvents()` is called (or auto-enabled via `captureWalletEvents: true`),
|
|
42498
|
+
* real-time blockchain events are automatically routed through the same event system:
|
|
42499
|
+
* ```typescript
|
|
42500
|
+
* const { subscribe } = useEvents();
|
|
42501
|
+
*
|
|
42502
|
+
* subscribe((event) => {
|
|
42503
|
+
* if (event.domain === 'wallet') {
|
|
42504
|
+
* console.log('Blockchain event:', event.type); // wallet_transfer, wallet_approval, etc.
|
|
42505
|
+
* }
|
|
42506
|
+
* });
|
|
42507
|
+
* ```
|
|
42508
|
+
*
|
|
39983
42509
|
* **Notification Levels:**
|
|
39984
42510
|
* - `success` - Operation completed successfully
|
|
39985
42511
|
* - `error` - Operation failed
|
|
39986
|
-
* - `warning` - Operation completed with warnings
|
|
39987
|
-
* - `info` - Informational event
|
|
39988
42512
|
*
|
|
39989
42513
|
* **Cleanup:**
|
|
39990
42514
|
* All subscriptions created through this hook are automatically cleaned up when
|
|
@@ -40619,9 +43143,13 @@ function getMetadataFromTokenUnitResponse(tokenUnit, incrementalId) {
|
|
|
40619
43143
|
exports.ANALYTICS_GROUP_BY_TYPES = ANALYTICS_GROUP_BY_TYPES;
|
|
40620
43144
|
exports.ANALYTICS_METRIC_TYPES = ANALYTICS_METRIC_TYPES;
|
|
40621
43145
|
exports.AsyncStorageTokenStorage = AsyncStorageTokenStorage;
|
|
43146
|
+
exports.AuditEntityTypes = AuditEntityTypes;
|
|
40622
43147
|
exports.AuthenticationError = AuthenticationError;
|
|
43148
|
+
exports.ChainTypes = ChainTypes;
|
|
40623
43149
|
exports.ClientTransactionType = ClientTransactionType;
|
|
40624
43150
|
exports.Domains = Domains;
|
|
43151
|
+
exports.EntityTypes = EntityTypes;
|
|
43152
|
+
exports.ErrorDomains = ErrorDomains;
|
|
40625
43153
|
exports.ErrorUtils = ErrorUtils;
|
|
40626
43154
|
exports.MEMBERSHIP_ROLE_HIERARCHY = MEMBERSHIP_ROLE_HIERARCHY;
|
|
40627
43155
|
exports.NativeTokenTypes = NativeTokenTypes;
|
|
@@ -40634,15 +43162,19 @@ exports.ReactNativeSecureStorage = ReactNativeSecureStorage;
|
|
|
40634
43162
|
exports.SOURCE_LOGIC_TYPES = SOURCE_LOGIC_TYPES;
|
|
40635
43163
|
exports.SOURCE_LOGIC_TYPE_VALUES = SOURCE_LOGIC_TYPE_VALUES;
|
|
40636
43164
|
exports.SigningStatus = SigningStatus;
|
|
43165
|
+
exports.TOKEN_VALIDITY_TYPE_VALUES = TOKEN_VALIDITY_TYPE_VALUES;
|
|
40637
43166
|
exports.TRANSACTION_FORMATS = TRANSACTION_FORMATS;
|
|
40638
43167
|
exports.TRANSACTION_FORMAT_DESCRIPTIONS = TRANSACTION_FORMAT_DESCRIPTIONS;
|
|
40639
43168
|
exports.TRIGGER_SOURCE_TYPES = TRIGGER_SOURCE_TYPES;
|
|
40640
43169
|
exports.TRIGGER_SOURCE_TYPE_VALUES = TRIGGER_SOURCE_TYPE_VALUES;
|
|
43170
|
+
exports.TokenValidityTypes = TokenValidityTypes;
|
|
43171
|
+
exports.VALID_BUSINESS_MEMBERSHIP_RELATIONS = VALID_BUSINESS_MEMBERSHIP_RELATIONS;
|
|
40641
43172
|
exports.VALID_CAMPAIGN_CLAIM_RELATIONS = VALID_CAMPAIGN_CLAIM_RELATIONS;
|
|
40642
43173
|
exports.VALID_CAMPAIGN_RELATIONS = VALID_CAMPAIGN_RELATIONS;
|
|
40643
43174
|
exports.VALID_REDEMPTION_REDEEM_RELATIONS = VALID_REDEMPTION_REDEEM_RELATIONS;
|
|
40644
43175
|
exports.VALID_RELATIONS = VALID_RELATIONS;
|
|
40645
43176
|
exports.VALID_TRANSACTION_RELATIONS = VALID_TRANSACTION_RELATIONS;
|
|
43177
|
+
exports.VALID_USER_RELATIONS = VALID_USER_RELATIONS;
|
|
40646
43178
|
exports.apiPublicKeyTestPrefix = apiPublicKeyTestPrefix;
|
|
40647
43179
|
exports.buildBurnRequest = buildBurnRequest;
|
|
40648
43180
|
exports.buildMintRequest = buildMintRequest;
|
|
@@ -40662,13 +43194,22 @@ exports.fetchAllPages = fetchAllPages;
|
|
|
40662
43194
|
exports.getMetadataFromTokenUnitResponse = getMetadataFromTokenUnitResponse;
|
|
40663
43195
|
exports.hasMinimumRole = hasMinimumRole;
|
|
40664
43196
|
exports.initializeReactNativePolyfills = initializeReactNativePolyfills;
|
|
43197
|
+
exports.isConnectedMessage = isConnectedMessage;
|
|
43198
|
+
exports.isErrorMessage = isErrorMessage;
|
|
43199
|
+
exports.isEventMessage = isEventMessage;
|
|
40665
43200
|
exports.isPaginatedResponse = isPaginatedResponse;
|
|
43201
|
+
exports.isPingMessage = isPingMessage;
|
|
43202
|
+
exports.isServerMessage = isServerMessage;
|
|
43203
|
+
exports.isSubscribedMessage = isSubscribedMessage;
|
|
43204
|
+
exports.isUnsubscribedMessage = isUnsubscribedMessage;
|
|
40666
43205
|
exports.isUserIdentifierObject = isUserIdentifierObject;
|
|
43206
|
+
exports.isValidBusinessMembershipRelation = isValidBusinessMembershipRelation;
|
|
40667
43207
|
exports.isValidCampaignClaimRelation = isValidCampaignClaimRelation;
|
|
40668
43208
|
exports.isValidCampaignRelation = isValidCampaignRelation;
|
|
40669
43209
|
exports.isValidRedemptionRedeemRelation = isValidRedemptionRedeemRelation;
|
|
40670
43210
|
exports.isValidRelation = isValidRelation;
|
|
40671
43211
|
exports.isValidTransactionRelation = isValidTransactionRelation;
|
|
43212
|
+
exports.isValidUserRelation = isValidUserRelation;
|
|
40672
43213
|
exports.normalizeToPaginated = normalizeToPaginated;
|
|
40673
43214
|
exports.registerIdentifierType = registerIdentifierType;
|
|
40674
43215
|
exports.testnetPrefix = testnetPrefix;
|