@explorins/pers-sdk 2.1.10 → 2.1.14

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.
Files changed (127) hide show
  1. package/dist/business/api/business-membership-api.d.ts +2 -2
  2. package/dist/chunks/{index-B-g2JPVK.cjs → index-CGaKfZNU.cjs} +279 -1
  3. package/dist/chunks/index-CGaKfZNU.cjs.map +1 -0
  4. package/dist/chunks/{index-CKm_V5XE.js → index-DgTEdUgC.js} +277 -2
  5. package/dist/chunks/index-DgTEdUgC.js.map +1 -0
  6. package/dist/chunks/{pers-sdk-Bh-m0x17.js → pers-sdk-BiP7UMJ3.js} +1695 -177
  7. package/dist/chunks/pers-sdk-BiP7UMJ3.js.map +1 -0
  8. package/dist/chunks/{pers-sdk-Co-R9M1O.cjs → pers-sdk-Cv7hM1I7.cjs} +1701 -177
  9. package/dist/chunks/pers-sdk-Cv7hM1I7.cjs.map +1 -0
  10. package/dist/chunks/tenant-manager-BUiFM33X.cjs +157 -0
  11. package/dist/chunks/tenant-manager-BUiFM33X.cjs.map +1 -0
  12. package/dist/chunks/tenant-manager-Bbj0bKoo.js +155 -0
  13. package/dist/chunks/tenant-manager-Bbj0bKoo.js.map +1 -0
  14. package/dist/chunks/{transaction-request.builder-DGTxGvc3.js → transaction-request.builder-C3C19kCx.js} +23 -2
  15. package/dist/chunks/{transaction-request.builder-DGTxGvc3.js.map → transaction-request.builder-C3C19kCx.js.map} +1 -1
  16. package/dist/chunks/{transaction-request.builder-Bjxi0C9F.cjs → transaction-request.builder-CW3Wwdi3.cjs} +23 -1
  17. package/dist/chunks/{transaction-request.builder-Bjxi0C9F.cjs.map → transaction-request.builder-CW3Wwdi3.cjs.map} +1 -1
  18. package/dist/chunks/{web3-chain-service-D68-0WaW.cjs → web3-chain-service-DcLiy3m2.cjs} +7 -7
  19. package/dist/chunks/{web3-chain-service-D68-0WaW.cjs.map → web3-chain-service-DcLiy3m2.cjs.map} +1 -1
  20. package/dist/chunks/{web3-chain-service-DuvxmKSj.js → web3-chain-service-nGntR60S.js} +3 -3
  21. package/dist/chunks/{web3-chain-service-DuvxmKSj.js.map → web3-chain-service-nGntR60S.js.map} +1 -1
  22. package/dist/chunks/{web3-manager-OExwBWB7.js → web3-manager-BbJzGd0y.js} +67 -53
  23. package/dist/chunks/web3-manager-BbJzGd0y.js.map +1 -0
  24. package/dist/chunks/{web3-manager-C_cFaMCm.cjs → web3-manager-fRGFR_pM.cjs} +67 -53
  25. package/dist/chunks/web3-manager-fRGFR_pM.cjs.map +1 -0
  26. package/dist/core/events/event-emitter.d.ts.map +1 -1
  27. package/dist/core/pers-config.d.ts +19 -0
  28. package/dist/core/pers-config.d.ts.map +1 -1
  29. package/dist/core.cjs +17 -14
  30. package/dist/core.cjs.map +1 -1
  31. package/dist/core.js +5 -5
  32. package/dist/events/index.d.ts +13 -0
  33. package/dist/events/index.d.ts.map +1 -0
  34. package/dist/events/pers-events-client.d.ts +143 -0
  35. package/dist/events/pers-events-client.d.ts.map +1 -0
  36. package/dist/events/types.d.ts +37 -0
  37. package/dist/events/types.d.ts.map +1 -0
  38. package/dist/index.cjs +41 -13
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +5 -5
  43. package/dist/managers/events-manager.d.ts +179 -0
  44. package/dist/managers/events-manager.d.ts.map +1 -0
  45. package/dist/managers/index.d.ts +2 -0
  46. package/dist/managers/index.d.ts.map +1 -1
  47. package/dist/managers/tenant-manager.d.ts +42 -1
  48. package/dist/managers/tenant-manager.d.ts.map +1 -1
  49. package/dist/managers/user-manager.d.ts +21 -18
  50. package/dist/managers/user-manager.d.ts.map +1 -1
  51. package/dist/managers/web3-manager.d.ts +12 -14
  52. package/dist/managers/web3-manager.d.ts.map +1 -1
  53. package/dist/managers/webhook-manager.d.ts +237 -0
  54. package/dist/managers/webhook-manager.d.ts.map +1 -0
  55. package/dist/node.cjs +4 -3
  56. package/dist/node.cjs.map +1 -1
  57. package/dist/node.js +4 -3
  58. package/dist/node.js.map +1 -1
  59. package/dist/package.json +3 -2
  60. package/dist/pers-sdk.d.ts +110 -1
  61. package/dist/pers-sdk.d.ts.map +1 -1
  62. package/dist/transaction/index.d.ts +1 -1
  63. package/dist/transaction/index.d.ts.map +1 -1
  64. package/dist/transaction/models/transaction-request.builder.d.ts +16 -0
  65. package/dist/transaction/models/transaction-request.builder.d.ts.map +1 -1
  66. package/dist/transaction.cjs +2 -1
  67. package/dist/transaction.cjs.map +1 -1
  68. package/dist/transaction.js +1 -1
  69. package/dist/user/api/user-api.d.ts +15 -2
  70. package/dist/user/api/user-api.d.ts.map +1 -1
  71. package/dist/user/services/user-service.d.ts +5 -3
  72. package/dist/user/services/user-service.d.ts.map +1 -1
  73. package/dist/user.cjs +23 -7
  74. package/dist/user.cjs.map +1 -1
  75. package/dist/user.js +23 -7
  76. package/dist/user.js.map +1 -1
  77. package/dist/web3/application/web3-application.service.d.ts +6 -3
  78. package/dist/web3/application/web3-application.service.d.ts.map +1 -1
  79. package/dist/web3/domain/services/metadata-domain.service.d.ts +4 -2
  80. package/dist/web3/domain/services/metadata-domain.service.d.ts.map +1 -1
  81. package/dist/web3/infrastructure/api/ipfs-api.d.ts +22 -9
  82. package/dist/web3/infrastructure/api/ipfs-api.d.ts.map +1 -1
  83. package/dist/web3/utils/explorer.utils.d.ts +3 -3
  84. package/dist/web3/utils/explorer.utils.d.ts.map +1 -1
  85. package/dist/web3-chain/api/web3-chain-api.d.ts +3 -2
  86. package/dist/web3-chain/api/web3-chain-api.d.ts.map +1 -1
  87. package/dist/web3-chain/index.d.ts +0 -1
  88. package/dist/web3-chain/index.d.ts.map +1 -1
  89. package/dist/web3-chain/models/index.d.ts +5 -23
  90. package/dist/web3-chain/models/index.d.ts.map +1 -1
  91. package/dist/web3-chain/services/web3-chain-service.d.ts +2 -2
  92. package/dist/web3-chain/services/web3-chain-service.d.ts.map +1 -1
  93. package/dist/web3-chain.cjs +8 -14
  94. package/dist/web3-chain.cjs.map +1 -1
  95. package/dist/web3-chain.js +3 -16
  96. package/dist/web3-chain.js.map +1 -1
  97. package/dist/web3-manager.cjs +7 -4
  98. package/dist/web3-manager.cjs.map +1 -1
  99. package/dist/web3-manager.js +7 -4
  100. package/dist/web3-manager.js.map +1 -1
  101. package/dist/web3.cjs +7 -4
  102. package/dist/web3.cjs.map +1 -1
  103. package/dist/web3.js +7 -4
  104. package/dist/web3.js.map +1 -1
  105. package/dist/webhook/api/index.d.ts +2 -0
  106. package/dist/webhook/api/index.d.ts.map +1 -0
  107. package/dist/webhook/api/webhook-api.d.ts +73 -0
  108. package/dist/webhook/api/webhook-api.d.ts.map +1 -0
  109. package/dist/webhook/index.d.ts +35 -0
  110. package/dist/webhook/index.d.ts.map +1 -0
  111. package/dist/webhook/models/index.d.ts +58 -0
  112. package/dist/webhook/models/index.d.ts.map +1 -0
  113. package/dist/webhook/services/index.d.ts +2 -0
  114. package/dist/webhook/services/index.d.ts.map +1 -0
  115. package/dist/webhook/services/webhook-service.d.ts +98 -0
  116. package/dist/webhook/services/webhook-service.d.ts.map +1 -0
  117. package/package.json +3 -2
  118. package/dist/chunks/index-B-g2JPVK.cjs.map +0 -1
  119. package/dist/chunks/index-B6-bbNnd.cjs +0 -281
  120. package/dist/chunks/index-B6-bbNnd.cjs.map +0 -1
  121. package/dist/chunks/index-CKm_V5XE.js.map +0 -1
  122. package/dist/chunks/index-DBLskLuH.js +0 -277
  123. package/dist/chunks/index-DBLskLuH.js.map +0 -1
  124. package/dist/chunks/pers-sdk-Bh-m0x17.js.map +0 -1
  125. package/dist/chunks/pers-sdk-Co-R9M1O.cjs.map +0 -1
  126. package/dist/chunks/web3-manager-C_cFaMCm.cjs.map +0 -1
  127. package/dist/chunks/web3-manager-OExwBWB7.js.map +0 -1
@@ -1,16 +1,16 @@
1
1
  'use strict';
2
2
 
3
3
  var persShared = require('@explorins/pers-shared');
4
- var index = require('./index-B-g2JPVK.cjs');
4
+ var index = require('./index-CGaKfZNU.cjs');
5
5
  var user = require('../user.cjs');
6
6
  var userStatus = require('../user-status.cjs');
7
7
  var tokenService = require('./token-service-C1xe11OX.cjs');
8
8
  var businessMembershipService = require('./business-membership-service-BfHzIQlc.cjs');
9
9
  var campaign = require('../campaign.cjs');
10
10
  var redemptionService = require('./redemption-service-C61Qr2vI.cjs');
11
- var transactionRequest_builder = require('./transaction-request.builder-Bjxi0C9F.cjs');
11
+ var transactionRequest_builder = require('./transaction-request.builder-CW3Wwdi3.cjs');
12
12
  var paymentService = require('./payment-service-Bkw7ZXev.cjs');
13
- var tenantService = require('./tenant-service-fj-pkXTw.cjs');
13
+ var tenantManager = require('./tenant-manager-BUiFM33X.cjs');
14
14
  var paginationUtils = require('./pagination-utils-B2jRHMSO.cjs');
15
15
  var analyticsService = require('./analytics-service-CF9AsMQH.cjs');
16
16
  var donation = require('../donation.cjs');
@@ -31,7 +31,8 @@ const DEFAULT_PERS_CONFIG = {
31
31
  timeout: 30000,
32
32
  retries: 3,
33
33
  tokenRefreshMargin: 60, // Refresh tokens 60 seconds before expiry
34
- backgroundRefreshThreshold: 30 // Use background refresh if >30s remaining
34
+ backgroundRefreshThreshold: 30, // Use background refresh if >30s remaining
35
+ captureWalletEvents: true // Auto-connect to wallet events after auth
35
36
  };
36
37
  /**
37
38
  * Build the API root URL based on config
@@ -53,6 +54,21 @@ function buildApiRoot(environment = 'production', version = 'v2', customApiUrl)
53
54
  };
54
55
  return baseUrls[environment];
55
56
  }
57
+ /**
58
+ * Build wallet events WebSocket URL based on config
59
+ *
60
+ * @internal
61
+ */
62
+ function buildWalletEventsWsUrl(environment = 'production', customWsUrl) {
63
+ if (customWsUrl) {
64
+ return customWsUrl;
65
+ }
66
+ const wsUrls = {
67
+ staging: 'wss://events-staging.pers.ninja',
68
+ production: 'wss://events.pers.ninja'
69
+ };
70
+ return wsUrls[environment];
71
+ }
56
72
  /**
57
73
  * Merge user config with defaults
58
74
  */
@@ -1413,7 +1429,6 @@ class PersEventEmitter {
1413
1429
  constructor() {
1414
1430
  this.handlers = new Set();
1415
1431
  this._instanceId = ++emitterInstanceCounter;
1416
- console.log(`[PersEventEmitter] Instance #${this._instanceId} created`);
1417
1432
  }
1418
1433
  get instanceId() {
1419
1434
  return this._instanceId;
@@ -2236,34 +2251,37 @@ class UserManager {
2236
2251
  return this.userService.updateUserAsAdmin(userId, userData);
2237
2252
  }
2238
2253
  /**
2239
- * Admin: Toggle user active status
2254
+ * Admin: Set or toggle user active status
2240
2255
  *
2241
- * Toggles the active/inactive status of a user account. This is typically
2242
- * used for account suspension or reactivation. Requires administrator privileges.
2256
+ * Sets the active/inactive status of a user account explicitly, or toggles
2257
+ * if no explicit value is provided. This is typically used for account
2258
+ * suspension or reactivation. Requires administrator privileges.
2243
2259
  *
2244
- * @param user - User object to toggle status for
2260
+ * @param userId - User ID to update
2261
+ * @param isActive - Optional explicit status. If provided, sets to this value. If omitted, toggles current status.
2245
2262
  * @returns Promise resolving to updated user data
2246
2263
  * @throws {PersApiError} When not authenticated as admin
2247
2264
  *
2248
- * @example Suspend User
2265
+ * @example Explicit Status
2249
2266
  * ```typescript
2250
- * // Admin operation - toggle user status
2251
- * try {
2252
- * const user = await sdk.users.getUserById('problematic-user-123');
2253
- * const updated = await sdk.users.toggleUserStatus(user);
2267
+ * // Admin operation - explicitly activate user
2268
+ * const activated = await sdk.users.setUserActiveStatus('user-123', true);
2269
+ * console.log('User activated:', activated.isActive); // true
2254
2270
  *
2255
- * if (updated.isActive) {
2256
- * console.log('User account reactivated');
2257
- * } else {
2258
- * console.log('User account suspended');
2259
- * }
2260
- * } catch (error) {
2261
- * console.log('Failed to update user status');
2262
- * }
2271
+ * // Explicitly deactivate user
2272
+ * const deactivated = await sdk.users.setUserActiveStatus('user-123', false);
2273
+ * console.log('User deactivated:', deactivated.isActive); // false
2274
+ * ```
2275
+ *
2276
+ * @example Toggle Status
2277
+ * ```typescript
2278
+ * // Admin operation - toggle current status
2279
+ * const toggled = await sdk.users.setUserActiveStatus('user-123');
2280
+ * console.log('User status toggled:', toggled.isActive);
2263
2281
  * ```
2264
2282
  */
2265
- async toggleUserStatus(user) {
2266
- return this.userService.toggleUserActiveStatusByUser(user);
2283
+ async setUserActiveStatus(userId, isActive) {
2284
+ return this.userService.setUserActiveStatus(userId, isActive);
2267
2285
  }
2268
2286
  /**
2269
2287
  * Admin: Delete user (soft delete)
@@ -6379,89 +6397,6 @@ class FileManager {
6379
6397
  }
6380
6398
  }
6381
6399
 
6382
- /**
6383
- * Tenant Manager - Clean, high-level interface for tenant operations
6384
- *
6385
- * Provides a simplified API for common tenant management tasks while maintaining
6386
- * access to the full tenant SDK for advanced use cases.
6387
- */
6388
- class TenantManager {
6389
- constructor(apiClient) {
6390
- this.apiClient = apiClient;
6391
- const tenantApi = new tenantService.TenantApi(apiClient);
6392
- this.tenantService = new tenantService.TenantService(tenantApi);
6393
- }
6394
- /**
6395
- * Get current tenant information
6396
- *
6397
- * @returns Promise resolving to tenant data
6398
- */
6399
- async getTenantInfo() {
6400
- return this.tenantService.getRemoteTenant();
6401
- }
6402
- /**
6403
- * Get tenant login token
6404
- *
6405
- * @returns Promise resolving to login token
6406
- */
6407
- async getLoginToken() {
6408
- return this.tenantService.getRemoteLoginToken();
6409
- }
6410
- /**
6411
- * Get tenant client configuration
6412
- *
6413
- * @returns Promise resolving to client config
6414
- */
6415
- async getClientConfig() {
6416
- return this.tenantService.getRemoteClientConfig();
6417
- }
6418
- /**
6419
- * Admin: Update tenant data
6420
- *
6421
- * @param tenantData - Updated tenant data
6422
- * @returns Promise resolving to updated tenant
6423
- */
6424
- async updateTenant(tenantData) {
6425
- return this.tenantService.updateRemoteTenant(tenantData);
6426
- }
6427
- /**
6428
- * Admin: Get all admins
6429
- *
6430
- * @param options - Pagination options
6431
- * @returns Promise resolving to paginated admins
6432
- */
6433
- async getAdmins(options) {
6434
- return this.tenantService.getAdmins(options);
6435
- }
6436
- /**
6437
- * Admin: Create new admin
6438
- *
6439
- * @param adminData - Admin data
6440
- * @returns Promise resolving to created admin
6441
- */
6442
- async createAdmin(adminData) {
6443
- return this.tenantService.postAdmin(adminData);
6444
- }
6445
- /**
6446
- * Admin: Update existing admin
6447
- *
6448
- * @param adminId - ID of the admin to update
6449
- * @param adminData - Updated admin data
6450
- * @returns Promise resolving to updated admin
6451
- */
6452
- async updateAdmin(adminId, adminData) {
6453
- return this.tenantService.putAdmin(adminId, adminData);
6454
- }
6455
- /**
6456
- * Get the tenant service for advanced operations
6457
- *
6458
- * @returns TenantService instance
6459
- */
6460
- getTenantService() {
6461
- return this.tenantService;
6462
- }
6463
- }
6464
-
6465
6400
  /**
6466
6401
  * Platform-Agnostic API Key API Client
6467
6402
  *
@@ -7397,102 +7332,1513 @@ class TriggerSourceManager {
7397
7332
  }
7398
7333
 
7399
7334
  /**
7400
- * @fileoverview PERS SDK - Platform-agnostic TypeScript SDK with High-Level Managers
7335
+ * Webhook API - Low-level API client for webhook operations
7401
7336
  *
7402
- * @module @explorins/pers-sdk
7403
- * @version 1.6.4
7404
- * @author Explorins
7405
- * @license MIT
7337
+ * Backend routes:
7338
+ * - Admin CRUD: /hooks (requires tenant auth)
7339
+ * - Proxy/Trigger: /hooks/:projectKey/:hookId (public, identified by projectKey)
7340
+ * - Callback: /hooks/:projectKey/executions/:executionId/callback (public)
7341
+ *
7342
+ * @internal Use WebhookService or WebhookManager for higher-level operations
7406
7343
  */
7344
+ class WebhookApi {
7345
+ constructor(apiClient) {
7346
+ this.apiClient = apiClient;
7347
+ this.basePath = '/hooks';
7348
+ }
7349
+ // ================================
7350
+ // ADMIN: Webhook Configuration CRUD
7351
+ // All require tenant auth (handled by apiClient)
7352
+ // ================================
7353
+ /**
7354
+ * List all webhooks (Admin)
7355
+ * GET /hooks
7356
+ */
7357
+ async listWebhooks(options) {
7358
+ const params = new URLSearchParams();
7359
+ if (options?.active !== undefined)
7360
+ params.append('active', String(options.active));
7361
+ if (options?.page)
7362
+ params.append('page', String(options.page));
7363
+ if (options?.limit)
7364
+ params.append('limit', String(options.limit));
7365
+ if (options?.search)
7366
+ params.append('search', options.search);
7367
+ const queryString = params.toString();
7368
+ const url = queryString ? `${this.basePath}?${queryString}` : this.basePath;
7369
+ return this.apiClient.get(url);
7370
+ }
7371
+ /**
7372
+ * Get webhook by ID (Admin)
7373
+ * GET /hooks/:hookId
7374
+ */
7375
+ async getWebhookById(webhookId) {
7376
+ return this.apiClient.get(`${this.basePath}/${webhookId}`);
7377
+ }
7378
+ /**
7379
+ * Create a new webhook (Admin)
7380
+ * POST /hooks
7381
+ */
7382
+ async createWebhook(webhook) {
7383
+ return this.apiClient.post(this.basePath, webhook);
7384
+ }
7385
+ /**
7386
+ * Update a webhook (Admin)
7387
+ * PUT /hooks/:hookId
7388
+ */
7389
+ async updateWebhook(webhookId, webhook) {
7390
+ return this.apiClient.put(`${this.basePath}/${webhookId}`, webhook);
7391
+ }
7392
+ /**
7393
+ * Delete a webhook (Admin)
7394
+ * DELETE /hooks/:hookId
7395
+ */
7396
+ async deleteWebhook(webhookId) {
7397
+ return this.apiClient.delete(`${this.basePath}/${webhookId}`);
7398
+ }
7399
+ // ================================
7400
+ // Webhook Triggering via Proxy
7401
+ // Route: /hooks/:projectKey/:hookId
7402
+ // Public endpoint identified by projectKey in URL
7403
+ // ================================
7404
+ /**
7405
+ * Trigger a webhook programmatically
7406
+ *
7407
+ * Uses the proxy endpoint which requires projectKey in the URL path.
7408
+ * The projectKey is obtained from SDK config.
7409
+ *
7410
+ * ALL /hooks/:projectKey/:hookId
7411
+ */
7412
+ async triggerWebhook(request, projectKey) {
7413
+ const { hookId, method = 'POST', body, queryParams, waitForCallback, callbackTimeoutMs } = request;
7414
+ // Build query string from queryParams
7415
+ const params = new URLSearchParams();
7416
+ if (queryParams) {
7417
+ Object.entries(queryParams).forEach(([key, value]) => params.append(key, value));
7418
+ }
7419
+ if (waitForCallback)
7420
+ params.append('waitForCallback', 'true');
7421
+ if (callbackTimeoutMs)
7422
+ params.append('callbackTimeoutMs', String(callbackTimeoutMs));
7423
+ const queryString = params.toString();
7424
+ // Use proxy path with projectKey
7425
+ const proxyPath = `${this.basePath}/${projectKey}/${hookId}`;
7426
+ const url = queryString ? `${proxyPath}?${queryString}` : proxyPath;
7427
+ // Use appropriate HTTP method - backend returns WebhookTriggerResponseDTO
7428
+ switch (method) {
7429
+ case 'GET':
7430
+ return this.apiClient.get(url);
7431
+ case 'PUT':
7432
+ return this.apiClient.put(url, body);
7433
+ case 'DELETE':
7434
+ return this.apiClient.delete(url);
7435
+ case 'POST':
7436
+ default:
7437
+ return this.apiClient.post(url, body);
7438
+ }
7439
+ }
7440
+ // ================================
7441
+ // Execution History (Admin)
7442
+ // GET /hooks/executions
7443
+ // ================================
7444
+ /**
7445
+ * List webhook executions (Admin)
7446
+ * GET /hooks/executions
7447
+ */
7448
+ async listExecutions(options) {
7449
+ const params = new URLSearchParams();
7450
+ if (options?.webhookId)
7451
+ params.append('hookId', options.webhookId);
7452
+ if (options?.status)
7453
+ params.append('status', options.status);
7454
+ if (options?.fromDate)
7455
+ params.append('dateFrom', options.fromDate);
7456
+ if (options?.toDate)
7457
+ params.append('dateTo', options.toDate);
7458
+ if (options?.page)
7459
+ params.append('page', String(options.page));
7460
+ if (options?.limit)
7461
+ params.append('limit', String(options.limit));
7462
+ const queryString = params.toString();
7463
+ const url = queryString ? `${this.basePath}/executions?${queryString}` : `${this.basePath}/executions`;
7464
+ return this.apiClient.get(url);
7465
+ }
7466
+ /**
7467
+ * Get execution by ID (Admin)
7468
+ * Note: This endpoint doesn't exist in current backend - would need to filter by ID
7469
+ */
7470
+ async getExecutionById(executionId) {
7471
+ // Filter executions by ID since direct endpoint doesn't exist
7472
+ const result = await this.listExecutions({ page: 1, limit: 1 });
7473
+ const execution = result.data.find(e => e.id === executionId);
7474
+ if (!execution) {
7475
+ throw new Error(`Execution ${executionId} not found`);
7476
+ }
7477
+ return execution;
7478
+ }
7479
+ // ================================
7480
+ // Callback (Public - called by external systems)
7481
+ // POST /hooks/:projectKey/executions/:executionId/callback
7482
+ // ================================
7483
+ /**
7484
+ * Send callback result (for testing or manual callback trigger)
7485
+ * This is normally called by external systems like n8n
7486
+ *
7487
+ * POST /hooks/:projectKey/executions/:executionId/callback
7488
+ */
7489
+ async sendCallback(executionId, callback, projectKey) {
7490
+ return this.apiClient.post(`${this.basePath}/${projectKey}/executions/${executionId}/callback`, callback);
7491
+ }
7492
+ }
7493
+
7407
7494
  /**
7408
- * PERS SDK - Main SDK class with domain managers
7495
+ * Webhook Service - Business logic layer for webhook operations
7409
7496
  *
7410
- * Main SDK interface providing clean, high-level managers for common operations
7411
- * while maintaining full access to the underlying API client and domain services.
7412
- *
7413
- * The SDK follows a layered architecture:
7414
- * - **Managers**: High-level, intuitive APIs for common operations
7415
- * - **Domain Services**: Full-featured access for advanced use cases
7416
- * - **API Client**: Direct REST API access for custom operations
7497
+ * Provides higher-level operations on top of WebhookApi including:
7498
+ * - Validation and error handling
7499
+ * - Convenience methods for common patterns
7500
+ * - Async workflow support with callbacks
7417
7501
  *
7418
- * @group Core
7419
- * @category SDK
7502
+ * Note: Trigger operations require projectKey which is obtained from SDK config.
7420
7503
  *
7421
- * @example Basic Setup
7422
- * ```typescript
7423
- * import { PersSDK } from '@explorins/pers-sdk';
7424
- * import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';
7504
+ * @group Services
7505
+ * @category Webhook
7506
+ */
7507
+ class WebhookService {
7508
+ constructor(webhookApi, projectKey) {
7509
+ this.webhookApi = webhookApi;
7510
+ this.projectKey = projectKey;
7511
+ }
7512
+ // ================================
7513
+ // Admin: Webhook Configuration
7514
+ // ================================
7515
+ /**
7516
+ * Get all webhooks with optional filters
7517
+ */
7518
+ async getWebhooks(options) {
7519
+ return this.webhookApi.listWebhooks(options);
7520
+ }
7521
+ /**
7522
+ * Get active webhooks only
7523
+ */
7524
+ async getActiveWebhooks() {
7525
+ return this.webhookApi.listWebhooks({ active: true });
7526
+ }
7527
+ /**
7528
+ * Get webhook by ID
7529
+ */
7530
+ async getWebhookById(webhookId) {
7531
+ return this.webhookApi.getWebhookById(webhookId);
7532
+ }
7533
+ /**
7534
+ * Create a new webhook
7535
+ */
7536
+ async createWebhook(webhook) {
7537
+ // Validate required fields
7538
+ if (!webhook.name?.trim()) {
7539
+ throw new Error('Webhook name is required');
7540
+ }
7541
+ if (!webhook.targetUrl?.trim()) {
7542
+ throw new Error('Target URL is required');
7543
+ }
7544
+ // Validate URL format
7545
+ try {
7546
+ new URL(webhook.targetUrl);
7547
+ }
7548
+ catch {
7549
+ throw new Error('Invalid target URL format');
7550
+ }
7551
+ return this.webhookApi.createWebhook(webhook);
7552
+ }
7553
+ /**
7554
+ * Update a webhook
7555
+ */
7556
+ async updateWebhook(webhookId, webhook) {
7557
+ // Validate URL if provided (cast to access optional property from PartialType)
7558
+ const update = webhook;
7559
+ if (update.targetUrl) {
7560
+ try {
7561
+ new URL(update.targetUrl);
7562
+ }
7563
+ catch {
7564
+ throw new Error('Invalid target URL format');
7565
+ }
7566
+ }
7567
+ return this.webhookApi.updateWebhook(webhookId, webhook);
7568
+ }
7569
+ /**
7570
+ * Enable a webhook
7571
+ */
7572
+ async enableWebhook(webhookId) {
7573
+ return this.webhookApi.updateWebhook(webhookId, { isActive: true });
7574
+ }
7575
+ /**
7576
+ * Disable a webhook
7577
+ */
7578
+ async disableWebhook(webhookId) {
7579
+ return this.webhookApi.updateWebhook(webhookId, { isActive: false });
7580
+ }
7581
+ /**
7582
+ * Delete a webhook
7583
+ */
7584
+ async deleteWebhook(webhookId) {
7585
+ return this.webhookApi.deleteWebhook(webhookId);
7586
+ }
7587
+ // ================================
7588
+ // Webhook Triggering (via Proxy)
7589
+ // Uses /hooks/:projectKey/:hookId
7590
+ // ================================
7591
+ /**
7592
+ * Trigger a webhook with full control over request parameters
7593
+ */
7594
+ async triggerWebhook(request) {
7595
+ return this.webhookApi.triggerWebhook(request, this.projectKey);
7596
+ }
7597
+ /**
7598
+ * Simple POST trigger with JSON body
7599
+ */
7600
+ async post(hookId, body) {
7601
+ return this.webhookApi.triggerWebhook({
7602
+ hookId,
7603
+ method: persShared.WebhookMethod.POST,
7604
+ body
7605
+ }, this.projectKey);
7606
+ }
7607
+ /**
7608
+ * Simple GET trigger
7609
+ */
7610
+ async get(hookId, queryParams) {
7611
+ return this.webhookApi.triggerWebhook({
7612
+ hookId,
7613
+ method: persShared.WebhookMethod.GET,
7614
+ queryParams
7615
+ }, this.projectKey);
7616
+ }
7617
+ /**
7618
+ * Trigger and wait for async callback (for workflow systems like n8n)
7619
+ *
7620
+ * @param hookId - Webhook ID to trigger
7621
+ * @param body - Request body
7622
+ * @param timeoutMs - How long to wait for callback (default: 30000ms)
7623
+ */
7624
+ async triggerAndWait(hookId, body, timeoutMs = 30000) {
7625
+ return this.webhookApi.triggerWebhook({
7626
+ hookId,
7627
+ method: persShared.WebhookMethod.POST,
7628
+ body,
7629
+ waitForCallback: true,
7630
+ callbackTimeoutMs: timeoutMs
7631
+ }, this.projectKey);
7632
+ }
7633
+ // ================================
7634
+ // Execution History (Admin)
7635
+ // ================================
7636
+ /**
7637
+ * Get execution history with filters
7638
+ */
7639
+ async getExecutions(options) {
7640
+ return this.webhookApi.listExecutions(options);
7641
+ }
7642
+ /**
7643
+ * Get executions for a specific webhook
7644
+ */
7645
+ async getWebhookExecutions(webhookId, options) {
7646
+ return this.webhookApi.listExecutions({ ...options, webhookId });
7647
+ }
7648
+ /**
7649
+ * Get failed executions
7650
+ */
7651
+ async getFailedExecutions(options) {
7652
+ return this.webhookApi.listExecutions({ ...options, status: persShared.WebhookExecutionStatus.FAILED });
7653
+ }
7654
+ /**
7655
+ * Get execution details by ID
7656
+ */
7657
+ async getExecutionById(executionId) {
7658
+ return this.webhookApi.getExecutionById(executionId);
7659
+ }
7660
+ // ================================
7661
+ // Callback (for testing/manual triggers)
7662
+ // ================================
7663
+ /**
7664
+ * Send a callback result for an execution
7665
+ * Normally this is called by external systems like n8n
7666
+ */
7667
+ async sendCallback(executionId, callback) {
7668
+ return this.webhookApi.sendCallback(executionId, callback, this.projectKey);
7669
+ }
7670
+ }
7671
+
7672
+ /**
7673
+ * Webhook Manager - Clean, high-level interface for webhook operations
7425
7674
  *
7426
- * const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
7427
- * environment: 'production',
7428
- * apiProjectKey: 'your-project-key'
7429
- * });
7430
- * ```
7675
+ * Webhooks enable integration with external systems (n8n, Zapier, custom backends):
7676
+ * - **Admin operations**: Create, configure, and manage webhook endpoints
7677
+ * - **Trigger operations**: Programmatically trigger webhooks with payloads
7678
+ * - **Async workflows**: Wait for callbacks from workflow systems
7679
+ * - **Monitoring**: Track execution history
7431
7680
  *
7432
- * @example Authentication Flow
7433
- * ```typescript
7434
- * // Login with external JWT
7435
- * await sdk.auth.loginWithToken(firebaseJWT, 'user');
7681
+ * ## Security Model
7682
+ * - Admin endpoints require tenant authentication
7683
+ * - Trigger endpoints work with user/business/tenant auth
7684
+ * - Caller identity is passed to webhook target
7436
7685
  *
7437
- * // Check authentication
7438
- * if (await sdk.auth.isAuthenticated()) {
7439
- * const user = await sdk.auth.getCurrentUser();
7440
- * console.log('Welcome,', user.name);
7441
- * }
7442
- * ```
7686
+ * @group Managers
7687
+ * @category Webhook Management
7443
7688
  *
7444
- * @example Business Operations
7689
+ * @example Basic Webhook Operations
7445
7690
  * ```typescript
7446
- * // Get active businesses
7447
- * const businesses = await sdk.businesses.getActiveBusinesses();
7691
+ * // Admin: Create a webhook for order processing
7692
+ * const webhook = await sdk.webhooks.create({
7693
+ * name: 'Process Order',
7694
+ * targetUrl: 'https://n8n.example.com/webhook/orders',
7695
+ * method: 'POST',
7696
+ * headers: { 'X-Custom-Header': 'value' }
7697
+ * });
7448
7698
  *
7449
- * // Get business details
7450
- * const business = await sdk.businesses.getBusinessById(businessId);
7699
+ * // Trigger webhook with order data
7700
+ * const result = await sdk.webhooks.trigger(webhook.id, {
7701
+ * orderId: 'order-123',
7702
+ * items: [{ productId: 'prod-1', quantity: 2 }]
7703
+ * });
7704
+ *
7705
+ * console.log('Execution ID:', result.executionId);
7451
7706
  * ```
7452
7707
  *
7453
- * @example Token Operations
7708
+ * @example Async Workflow with Callback
7454
7709
  * ```typescript
7455
- * // Get user's token balances
7456
- * const tokens = await sdk.tokens.getTokens();
7710
+ * // Trigger and wait for workflow completion (up to 30s)
7711
+ * const result = await sdk.webhooks.triggerAndWait(
7712
+ * 'ai-processing-webhook',
7713
+ * { prompt: 'Generate report for Q1' },
7714
+ * 30000
7715
+ * );
7457
7716
  *
7458
- * // Get active credit token
7459
- * const creditToken = await sdk.tokens.getActiveCreditToken();
7717
+ * if (result.status === 'COMPLETED') {
7718
+ * console.log('AI Response:', result.body);
7719
+ * }
7460
7720
  * ```
7461
- *
7462
- * @since 1.3.0 - Manager pattern architecture
7463
7721
  */
7464
- class PersSDK {
7722
+ class WebhookManager {
7723
+ constructor(apiClient, projectKey, events) {
7724
+ this.apiClient = apiClient;
7725
+ this.projectKey = projectKey;
7726
+ this.events = events;
7727
+ const webhookApi = new WebhookApi(apiClient);
7728
+ this.webhookService = new WebhookService(webhookApi, projectKey);
7729
+ }
7730
+ // ================================
7731
+ // Admin: Webhook Configuration
7732
+ // ================================
7465
7733
  /**
7466
- * Creates a new PERS SDK instance
7467
- *
7468
- * Initializes all domain managers and sets up the API client with the provided
7469
- * HTTP client adapter and configuration.
7734
+ * Get all webhooks with optional filters (Admin)
7470
7735
  *
7471
- * @param httpClient - Platform-specific HTTP client implementation
7472
- * @param config - SDK configuration options
7736
+ * @param options - Filter and pagination options
7737
+ * @returns Promise resolving to paginated webhooks
7473
7738
  *
7474
- * @example Browser Setup
7739
+ * @example
7475
7740
  * ```typescript
7476
- * import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';
7741
+ * // Get all webhooks
7742
+ * const webhooks = await sdk.webhooks.getAll();
7477
7743
  *
7478
- * const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
7479
- * environment: 'production',
7480
- * apiProjectKey: 'your-project-key'
7481
- * });
7744
+ * // Get only active webhooks
7745
+ * const active = await sdk.webhooks.getAll({ active: true });
7746
+ *
7747
+ * // Search by name
7748
+ * const found = await sdk.webhooks.getAll({ search: 'order' });
7482
7749
  * ```
7750
+ */
7751
+ async getAll(options) {
7752
+ return this.webhookService.getWebhooks(options);
7753
+ }
7754
+ /**
7755
+ * Get active webhooks only (Admin)
7756
+ */
7757
+ async getActive() {
7758
+ return this.webhookService.getActiveWebhooks();
7759
+ }
7760
+ /**
7761
+ * Get webhook by ID (Admin)
7483
7762
  *
7484
- * @example Node.js Setup
7485
- * ```typescript
7486
- * import { NodeHttpClientAdapter } from '@explorins/pers-sdk/platform-adapters';
7763
+ * @param webhookId - UUID of the webhook
7764
+ * @returns Promise resolving to webhook details
7765
+ */
7766
+ async getById(webhookId) {
7767
+ return this.webhookService.getWebhookById(webhookId);
7768
+ }
7769
+ /**
7770
+ * Create a new webhook (Admin)
7487
7771
  *
7488
- * const sdk = new PersSDK(new NodeHttpClientAdapter(), {
7489
- * environment: 'production',
7490
- * apiProjectKey: 'your-project-key',
7491
- * baseUrl: 'https://api.yourpers.com'
7772
+ * @param webhook - Webhook configuration
7773
+ * @returns Promise resolving to created webhook
7774
+ *
7775
+ * @example
7776
+ * ```typescript
7777
+ * const webhook = await sdk.webhooks.create({
7778
+ * name: 'Order Notifications',
7779
+ * targetUrl: 'https://api.example.com/webhooks/orders',
7780
+ * method: 'POST',
7781
+ * headers: {
7782
+ * 'Authorization': 'Bearer secret-token'
7783
+ * },
7784
+ * rateLimitPerMinute: 60
7492
7785
  * });
7493
7786
  * ```
7787
+ */
7788
+ async create(webhook) {
7789
+ const result = await this.webhookService.createWebhook(webhook);
7790
+ this.events?.emitSuccess({
7791
+ domain: 'webhook',
7792
+ type: 'WEBHOOK_CREATED',
7793
+ userMessage: 'Webhook created successfully',
7794
+ details: { webhookId: result.id, name: result.name }
7795
+ });
7796
+ return result;
7797
+ }
7798
+ /**
7799
+ * Update a webhook (Admin)
7494
7800
  *
7495
- * @example Angular Setup
7801
+ * @param webhookId - UUID of the webhook to update
7802
+ * @param webhook - Updated configuration (partial)
7803
+ * @returns Promise resolving to updated webhook
7804
+ */
7805
+ async update(webhookId, webhook) {
7806
+ const result = await this.webhookService.updateWebhook(webhookId, webhook);
7807
+ this.events?.emitSuccess({
7808
+ domain: 'webhook',
7809
+ type: 'WEBHOOK_UPDATED',
7810
+ userMessage: 'Webhook updated successfully',
7811
+ details: { webhookId }
7812
+ });
7813
+ return result;
7814
+ }
7815
+ /**
7816
+ * Enable a webhook (Admin)
7817
+ */
7818
+ async enable(webhookId) {
7819
+ return this.webhookService.enableWebhook(webhookId);
7820
+ }
7821
+ /**
7822
+ * Disable a webhook (Admin)
7823
+ */
7824
+ async disable(webhookId) {
7825
+ return this.webhookService.disableWebhook(webhookId);
7826
+ }
7827
+ /**
7828
+ * Delete a webhook (Admin)
7829
+ *
7830
+ * @param webhookId - UUID of the webhook to delete
7831
+ * @returns Promise resolving to deletion confirmation
7832
+ */
7833
+ async delete(webhookId) {
7834
+ const result = await this.webhookService.deleteWebhook(webhookId);
7835
+ this.events?.emitSuccess({
7836
+ domain: 'webhook',
7837
+ type: 'WEBHOOK_DELETED',
7838
+ userMessage: 'Webhook deleted successfully',
7839
+ details: { webhookId }
7840
+ });
7841
+ return result;
7842
+ }
7843
+ // ================================
7844
+ // Webhook Triggering
7845
+ // ================================
7846
+ /**
7847
+ * Trigger a webhook with GET method
7848
+ *
7849
+ * Sends a GET request through the webhook proxy. Returns the full response
7850
+ * including execution metadata and raw data from the target.
7851
+ *
7852
+ * @param hookId - Webhook ID to trigger
7853
+ * @param queryParams - Optional query parameters
7854
+ * @returns Promise resolving to trigger response with metadata and data
7855
+ *
7856
+ * @example
7857
+ * ```typescript
7858
+ * // Fetch data from external API via webhook proxy
7859
+ * const result = await sdk.webhooks.get('user-directory-webhook');
7860
+ * console.log('Success:', result.success);
7861
+ * console.log('Data:', result.data); // Raw data from target
7862
+ * ```
7863
+ */
7864
+ async get(hookId, queryParams) {
7865
+ return this.webhookService.get(hookId, queryParams);
7866
+ }
7867
+ /**
7868
+ * Trigger a webhook with POST method
7869
+ *
7870
+ * Sends data to the configured webhook target. The caller's identity
7871
+ * (user/business/tenant) is included in the request context.
7872
+ *
7873
+ * @param hookId - Webhook ID to trigger
7874
+ * @param body - Payload to send
7875
+ * @returns Promise resolving to trigger response with execution metadata
7876
+ *
7877
+ * @example
7878
+ * ```typescript
7879
+ * const result = await sdk.webhooks.trigger('order-webhook', {
7880
+ * orderId: 'order-123',
7881
+ * action: 'created',
7882
+ * items: [...]
7883
+ * });
7884
+ *
7885
+ * console.log('Success:', result.success);
7886
+ * console.log('Execution ID:', result.executionId);
7887
+ * console.log('Response:', result.data);
7888
+ * ```
7889
+ */
7890
+ async trigger(hookId, body) {
7891
+ const result = await this.webhookService.post(hookId, body);
7892
+ this.events?.emitSuccess({
7893
+ domain: 'webhook',
7894
+ type: 'WEBHOOK_TRIGGERED',
7895
+ userMessage: 'Webhook triggered',
7896
+ details: { hookId, executionId: result.executionId }
7897
+ });
7898
+ return result;
7899
+ }
7900
+ /**
7901
+ * Trigger a webhook and wait for async callback
7902
+ *
7903
+ * Use this for workflow systems (n8n, Zapier) that need time to process
7904
+ * and return results via callback. The SDK will wait for the callback
7905
+ * up to the specified timeout.
7906
+ *
7907
+ * @param hookId - Webhook ID to trigger
7908
+ * @param body - Payload to send
7909
+ * @param timeoutMs - Max time to wait for callback (default: 30s)
7910
+ * @returns Promise resolving when callback received or timeout
7911
+ *
7912
+ * @example
7913
+ * ```typescript
7914
+ * // Trigger AI workflow and wait for result
7915
+ * const result = await sdk.webhooks.triggerAndWait(
7916
+ * 'ai-analysis-webhook',
7917
+ * { documentId: 'doc-123', analysisType: 'sentiment' },
7918
+ * 60000 // Wait up to 60 seconds
7919
+ * );
7920
+ *
7921
+ * if (result.success) {
7922
+ * console.log('Analysis:', result.data);
7923
+ * }
7924
+ * ```
7925
+ */
7926
+ async triggerAndWait(hookId, body, timeoutMs = 30000) {
7927
+ return this.webhookService.triggerAndWait(hookId, body, timeoutMs);
7928
+ }
7929
+ // ================================
7930
+ // Execution History & Monitoring
7931
+ // ================================
7932
+ /**
7933
+ * Get webhook execution history (Admin)
7934
+ *
7935
+ * @param options - Filter and pagination options
7936
+ * @returns Promise resolving to paginated executions
7937
+ *
7938
+ * @example
7939
+ * ```typescript
7940
+ * // Get recent failed executions
7941
+ * const failed = await sdk.webhooks.getExecutions({ status: 'FAILED' });
7942
+ *
7943
+ * // Get executions for specific webhook
7944
+ * const executions = await sdk.webhooks.getExecutions({
7945
+ * webhookId: 'webhook-123',
7946
+ * fromDate: '2024-01-01'
7947
+ * });
7948
+ * ```
7949
+ */
7950
+ async getExecutions(options) {
7951
+ return this.webhookService.getExecutions(options);
7952
+ }
7953
+ /**
7954
+ * Get execution details by ID (Admin)
7955
+ */
7956
+ async getExecutionById(executionId) {
7957
+ return this.webhookService.getExecutionById(executionId);
7958
+ }
7959
+ /**
7960
+ * Get the full webhook service for advanced operations
7961
+ *
7962
+ * @returns WebhookService instance with full API access
7963
+ */
7964
+ getWebhookService() {
7965
+ return this.webhookService;
7966
+ }
7967
+ }
7968
+
7969
+ /**
7970
+ * PERS Events Client
7971
+ *
7972
+ * Lightweight WebSocket client for real-time blockchain event streaming.
7973
+ * Connects to the PERS WS Relay server to receive events for user's wallets.
7974
+ *
7975
+ * ## v1.2.0 Subscription Model
7976
+ *
7977
+ * JWT is used for authentication only. After connecting, you must explicitly
7978
+ * subscribe to wallets (user SDK) or chains (admin dashboard).
7979
+ *
7980
+ * @example User SDK - Subscribe to specific wallets
7981
+ * ```typescript
7982
+ * const client = new PersEventsClient({
7983
+ * wsUrl: 'wss://events.pers.ninja',
7984
+ * autoReconnect: true
7985
+ * });
7986
+ *
7987
+ * await client.connect(jwtToken);
7988
+ *
7989
+ * // Subscribe to user's wallets
7990
+ * await client.subscribeWallets([
7991
+ * { address: '0x123...', chainId: 39123 }
7992
+ * ]);
7993
+ *
7994
+ * client.on('Transfer', (event) => {
7995
+ * console.log(`Received ${event.data.value} tokens`);
7996
+ * });
7997
+ * ```
7998
+ *
7999
+ * @example Admin Dashboard - Subscribe to all events on chains
8000
+ * ```typescript
8001
+ * await client.connect(adminJwtToken);
8002
+ *
8003
+ * // Subscribe to all events on specific chains
8004
+ * await client.subscribeChains([39123, 137]);
8005
+ *
8006
+ * client.on('*', (event) => {
8007
+ * console.log(`Chain ${event.chainId}: ${event.type}`);
8008
+ * });
8009
+ * ```
8010
+ */
8011
+ const DEFAULT_CONFIG = {
8012
+ autoReconnect: true,
8013
+ maxReconnectAttempts: 10,
8014
+ reconnectDelay: 1000,
8015
+ connectionTimeout: 30000,
8016
+ debug: false,
8017
+ tokenRefresher: undefined,
8018
+ };
8019
+ class PersEventsClient {
8020
+ constructor(config) {
8021
+ this.ws = null;
8022
+ this.state = 'disconnected';
8023
+ this.reconnectAttempts = 0;
8024
+ this.reconnectTimeout = null;
8025
+ this.token = null;
8026
+ // Event handlers by type
8027
+ this.handlers = new Map();
8028
+ this.stateHandlers = new Set();
8029
+ // Connection info from server
8030
+ this.connectionInfo = null;
8031
+ // Current subscription state
8032
+ this.subscriptionState = { wallets: [], chains: [], activeChains: [] };
8033
+ // Pending subscription promise resolver
8034
+ this.pendingSubscription = null;
8035
+ // Subscriptions to restore on reconnect
8036
+ this.savedSubscriptions = { wallets: [], chains: [] };
8037
+ this.config = { ...DEFAULT_CONFIG, ...config };
8038
+ }
8039
+ /**
8040
+ * Connect to the WS relay server
8041
+ * @param token - JWT token for authentication (wallets no longer required in JWT)
8042
+ */
8043
+ async connect(token) {
8044
+ if (this.state === 'connected' || this.state === 'connecting') {
8045
+ this.log('Already connected or connecting');
8046
+ return;
8047
+ }
8048
+ this.token = token;
8049
+ this.setState('connecting');
8050
+ return new Promise((resolve, reject) => {
8051
+ // Connection timeout
8052
+ const connectionTimeout = setTimeout(() => {
8053
+ this.log('Connection timeout');
8054
+ this.cleanup();
8055
+ this.setState('error');
8056
+ reject(new Error(`Connection timeout after ${this.config.connectionTimeout}ms`));
8057
+ }, this.config.connectionTimeout);
8058
+ const clearTimeoutAndResolve = () => {
8059
+ clearTimeout(connectionTimeout);
8060
+ resolve();
8061
+ };
8062
+ const clearTimeoutAndReject = (err) => {
8063
+ clearTimeout(connectionTimeout);
8064
+ reject(err);
8065
+ };
8066
+ try {
8067
+ const url = new URL(this.config.wsUrl);
8068
+ url.searchParams.set('token', token);
8069
+ this.ws = new WebSocket(url.toString());
8070
+ this.ws.onopen = () => {
8071
+ // Wait for server 'connected' message
8072
+ };
8073
+ this.ws.onmessage = (event) => {
8074
+ this.handleMessage(event.data, clearTimeoutAndResolve);
8075
+ };
8076
+ this.ws.onerror = (error) => {
8077
+ this.log('WebSocket error:', error);
8078
+ this.setState('error');
8079
+ clearTimeoutAndReject(new Error('Connection failed'));
8080
+ };
8081
+ this.ws.onclose = (event) => {
8082
+ this.log(`WebSocket closed: ${event.code} ${event.reason}`);
8083
+ // Only reject if we were still connecting
8084
+ if (this.state === 'connecting') {
8085
+ clearTimeoutAndReject(new Error(`Connection closed during handshake: ${event.code} ${event.reason}`));
8086
+ }
8087
+ this.handleDisconnect();
8088
+ };
8089
+ }
8090
+ catch (err) {
8091
+ this.log('Error creating WebSocket:', err);
8092
+ this.setState('error');
8093
+ clearTimeoutAndReject(err instanceof Error ? err : new Error(String(err)));
8094
+ }
8095
+ });
8096
+ }
8097
+ /**
8098
+ * Disconnect from the server
8099
+ */
8100
+ disconnect() {
8101
+ this.config.autoReconnect = false; // Prevent reconnect
8102
+ this.cleanup();
8103
+ this.setState('disconnected');
8104
+ }
8105
+ // ─────────────────────────────────────────────────────────────────────────────
8106
+ // Subscription Methods (v1.2.0)
8107
+ // ─────────────────────────────────────────────────────────────────────────────
8108
+ /**
8109
+ * Subscribe to wallet events (User SDK)
8110
+ *
8111
+ * Receives events only for the specified wallet addresses.
8112
+ * Can be called multiple times to add more wallets.
8113
+ *
8114
+ * @param wallets - Array of wallet info (address + chainId)
8115
+ * @returns Promise that resolves when subscription is confirmed
8116
+ */
8117
+ async subscribeWallets(wallets) {
8118
+ return this.sendSubscription({ type: 'subscribe', wallets });
8119
+ }
8120
+ /**
8121
+ * Subscribe to chain events (Admin Dashboard)
8122
+ *
8123
+ * Receives ALL events on the specified chains.
8124
+ * Use for admin dashboards that need to monitor all activity.
8125
+ *
8126
+ * @param chains - Array of chain IDs to subscribe to
8127
+ * @returns Promise that resolves when subscription is confirmed
8128
+ */
8129
+ async subscribeChains(chains) {
8130
+ return this.sendSubscription({ type: 'subscribe', chains });
8131
+ }
8132
+ /**
8133
+ * Unsubscribe from wallet events
8134
+ *
8135
+ * @param wallets - Array of wallet info to unsubscribe from
8136
+ * @returns Promise that resolves when unsubscription is confirmed
8137
+ */
8138
+ async unsubscribeWallets(wallets) {
8139
+ return this.sendSubscription({ type: 'unsubscribe', wallets });
8140
+ }
8141
+ /**
8142
+ * Unsubscribe from chain events
8143
+ *
8144
+ * @param chains - Array of chain IDs to unsubscribe from
8145
+ * @returns Promise that resolves when unsubscription is confirmed
8146
+ */
8147
+ async unsubscribeChains(chains) {
8148
+ return this.sendSubscription({ type: 'unsubscribe', chains });
8149
+ }
8150
+ /**
8151
+ * Get current subscription state
8152
+ */
8153
+ getSubscriptionState() {
8154
+ return { ...this.subscriptionState };
8155
+ }
8156
+ // ─────────────────────────────────────────────────────────────────────────────
8157
+ // Event Handlers
8158
+ // ─────────────────────────────────────────────────────────────────────────────
8159
+ /**
8160
+ * Subscribe to blockchain events
8161
+ * @param eventType - Event type to listen for, or '*' for all events
8162
+ * @param handler - Event handler function
8163
+ * @returns Unsubscribe function
8164
+ */
8165
+ on(eventType, handler) {
8166
+ if (!this.handlers.has(eventType)) {
8167
+ this.handlers.set(eventType, new Set());
8168
+ }
8169
+ this.handlers.get(eventType).add(handler);
8170
+ return () => {
8171
+ this.handlers.get(eventType)?.delete(handler);
8172
+ };
8173
+ }
8174
+ /**
8175
+ * Subscribe to connection state changes
8176
+ */
8177
+ onStateChange(handler) {
8178
+ this.stateHandlers.add(handler);
8179
+ return () => this.stateHandlers.delete(handler);
8180
+ }
8181
+ /**
8182
+ * Get current connection state
8183
+ */
8184
+ getState() {
8185
+ return this.state;
8186
+ }
8187
+ /**
8188
+ * Get connection info (userId, initial wallets, activeChains)
8189
+ */
8190
+ getConnectionInfo() {
8191
+ return this.connectionInfo;
8192
+ }
8193
+ // ─────────────────────────────────────────────────────────────────────────────
8194
+ // Private methods
8195
+ // ─────────────────────────────────────────────────────────────────────────────
8196
+ async sendSubscription(message) {
8197
+ if (this.state !== 'connected' || !this.ws) {
8198
+ throw new Error('Not connected to server');
8199
+ }
8200
+ // Save subscriptions for reconnect
8201
+ if (message.type === 'subscribe') {
8202
+ if (message.wallets) {
8203
+ this.savedSubscriptions.wallets = [
8204
+ ...this.savedSubscriptions.wallets,
8205
+ ...message.wallets.filter(w => !this.savedSubscriptions.wallets.some(sw => sw.address === w.address && sw.chainId === w.chainId))
8206
+ ];
8207
+ }
8208
+ if (message.chains) {
8209
+ this.savedSubscriptions.chains = [
8210
+ ...new Set([...this.savedSubscriptions.chains, ...message.chains])
8211
+ ];
8212
+ }
8213
+ }
8214
+ else if (message.type === 'unsubscribe') {
8215
+ if (message.wallets) {
8216
+ this.savedSubscriptions.wallets = this.savedSubscriptions.wallets.filter(sw => !message.wallets.some(w => w.address === sw.address && w.chainId === sw.chainId));
8217
+ }
8218
+ if (message.chains) {
8219
+ this.savedSubscriptions.chains = this.savedSubscriptions.chains.filter(c => !message.chains.includes(c));
8220
+ }
8221
+ }
8222
+ return new Promise((resolve, reject) => {
8223
+ // Set up timeout
8224
+ const timeout = setTimeout(() => {
8225
+ this.pendingSubscription = null;
8226
+ reject(new Error('Subscription timeout'));
8227
+ }, 10000);
8228
+ this.pendingSubscription = {
8229
+ resolve: () => {
8230
+ clearTimeout(timeout);
8231
+ this.pendingSubscription = null;
8232
+ resolve(this.subscriptionState);
8233
+ },
8234
+ reject: (err) => {
8235
+ clearTimeout(timeout);
8236
+ this.pendingSubscription = null;
8237
+ reject(err);
8238
+ }
8239
+ };
8240
+ this.ws.send(JSON.stringify(message));
8241
+ this.log('Sent subscription message:', message);
8242
+ });
8243
+ }
8244
+ handleMessage(data, onConnected) {
8245
+ try {
8246
+ const message = JSON.parse(data);
8247
+ switch (message.type) {
8248
+ case 'connected':
8249
+ this.connectionInfo = message.payload;
8250
+ this.reconnectAttempts = 0;
8251
+ this.setState('connected');
8252
+ this.log('Connected:', message.payload);
8253
+ onConnected?.();
8254
+ // Restore subscriptions on reconnect
8255
+ this.restoreSubscriptions();
8256
+ break;
8257
+ case 'subscribed':
8258
+ this.subscriptionState = message.payload;
8259
+ this.log('Subscription confirmed:', message.payload);
8260
+ this.pendingSubscription?.resolve();
8261
+ break;
8262
+ case 'unsubscribed':
8263
+ this.subscriptionState = message.payload;
8264
+ this.log('Unsubscription confirmed:', message.payload);
8265
+ this.pendingSubscription?.resolve();
8266
+ break;
8267
+ case 'event':
8268
+ this.routeEvent(message.payload);
8269
+ break;
8270
+ case 'ping':
8271
+ // Respond to server ping with pong
8272
+ this.sendPong();
8273
+ break;
8274
+ case 'error':
8275
+ this.log('Server error:', message.payload);
8276
+ this.pendingSubscription?.reject(new Error(message.payload.message));
8277
+ break;
8278
+ }
8279
+ }
8280
+ catch (err) {
8281
+ this.log('Failed to parse message:', err);
8282
+ }
8283
+ }
8284
+ sendPong() {
8285
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
8286
+ this.ws.send(JSON.stringify({ type: 'pong' }));
8287
+ this.log('Sent pong');
8288
+ }
8289
+ }
8290
+ async restoreSubscriptions() {
8291
+ // Restore wallet subscriptions
8292
+ if (this.savedSubscriptions.wallets.length > 0) {
8293
+ this.log('Restoring wallet subscriptions:', this.savedSubscriptions.wallets);
8294
+ try {
8295
+ await this.subscribeWallets(this.savedSubscriptions.wallets);
8296
+ }
8297
+ catch (err) {
8298
+ this.log('Failed to restore wallet subscriptions:', err);
8299
+ }
8300
+ }
8301
+ // Restore chain subscriptions
8302
+ if (this.savedSubscriptions.chains.length > 0) {
8303
+ this.log('Restoring chain subscriptions:', this.savedSubscriptions.chains);
8304
+ try {
8305
+ await this.subscribeChains(this.savedSubscriptions.chains);
8306
+ }
8307
+ catch (err) {
8308
+ this.log('Failed to restore chain subscriptions:', err);
8309
+ }
8310
+ }
8311
+ }
8312
+ routeEvent(event) {
8313
+ this.log('Event received:', event.type, event);
8314
+ // Call specific type handlers
8315
+ const typeHandlers = this.handlers.get(event.type);
8316
+ typeHandlers?.forEach(handler => {
8317
+ try {
8318
+ handler(event);
8319
+ }
8320
+ catch (err) {
8321
+ console.error('[PERS-Events] Handler error:', err);
8322
+ }
8323
+ });
8324
+ // Call wildcard handlers
8325
+ const wildcardHandlers = this.handlers.get('*');
8326
+ wildcardHandlers?.forEach(handler => {
8327
+ try {
8328
+ handler(event);
8329
+ }
8330
+ catch (err) {
8331
+ console.error('[PERS-Events] Handler error:', err);
8332
+ }
8333
+ });
8334
+ }
8335
+ handleDisconnect() {
8336
+ this.cleanup();
8337
+ this.setState('disconnected');
8338
+ if (this.config.autoReconnect && this.token) {
8339
+ if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
8340
+ this.attemptReconnect();
8341
+ }
8342
+ else {
8343
+ this.log(`Max reconnect attempts (${this.config.maxReconnectAttempts}) reached. Call connect() manually to retry.`);
8344
+ // Reset counter so manual connect() will work
8345
+ this.reconnectAttempts = 0;
8346
+ }
8347
+ }
8348
+ }
8349
+ attemptReconnect() {
8350
+ const delay = Math.min(this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts), 30000 // Max 30 second delay
8351
+ );
8352
+ this.reconnectAttempts++;
8353
+ this.log(`🔄 Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`);
8354
+ this.setState('reconnecting');
8355
+ this.reconnectTimeout = setTimeout(async () => {
8356
+ try {
8357
+ // Try to get fresh token if tokenRefresher is provided
8358
+ let tokenToUse = this.token;
8359
+ if (this.config.tokenRefresher) {
8360
+ try {
8361
+ this.log('🔄 Refreshing token before reconnect...');
8362
+ tokenToUse = await this.config.tokenRefresher();
8363
+ this.token = tokenToUse; // Update stored token
8364
+ }
8365
+ catch (refreshErr) {
8366
+ this.log('⚠️ Token refresh failed, using existing token:', refreshErr);
8367
+ }
8368
+ }
8369
+ if (tokenToUse) {
8370
+ this.log(`🔄 Attempting reconnection...`);
8371
+ await this.connect(tokenToUse);
8372
+ }
8373
+ }
8374
+ catch (err) {
8375
+ this.log('❌ Reconnect failed:', err);
8376
+ }
8377
+ }, delay);
8378
+ }
8379
+ cleanup() {
8380
+ if (this.reconnectTimeout) {
8381
+ clearTimeout(this.reconnectTimeout);
8382
+ this.reconnectTimeout = null;
8383
+ }
8384
+ if (this.ws) {
8385
+ this.ws.onopen = null;
8386
+ this.ws.onmessage = null;
8387
+ this.ws.onerror = null;
8388
+ this.ws.onclose = null;
8389
+ if (this.ws.readyState === WebSocket.OPEN) {
8390
+ this.ws.close();
8391
+ }
8392
+ this.ws = null;
8393
+ }
8394
+ }
8395
+ setState(state) {
8396
+ if (this.state !== state) {
8397
+ this.state = state;
8398
+ this.stateHandlers.forEach(handler => handler(state));
8399
+ }
8400
+ }
8401
+ log(...args) {
8402
+ if (this.config.debug) {
8403
+ console.log('[PERS-Events]', ...args);
8404
+ }
8405
+ }
8406
+ }
8407
+ /**
8408
+ * Create a PERS Events client instance
8409
+ */
8410
+ function createPersEventsClient(config) {
8411
+ return new PersEventsClient(config);
8412
+ }
8413
+
8414
+ /**
8415
+ * Wallet Events Manager - Real-time blockchain events for user's wallets
8416
+ *
8417
+ * Provides automatic connection management and event routing integrated
8418
+ * with the SDK's authentication flow and event system.
8419
+ *
8420
+ * Events are routed through the SDK's PersEventEmitter, so you can either:
8421
+ * 1. Subscribe via `sdk.walletEvents.on()` for wallet-specific handling
8422
+ * 2. Subscribe via `sdk.events.subscribe()` for unified event stream
8423
+ *
8424
+ * ## v1.2.0 - Subscription Model
8425
+ *
8426
+ * After connecting, you must explicitly subscribe to wallets or chains:
8427
+ * - User SDK: `subscribeWallets([{ address, chainId }])`
8428
+ * - Admin Dashboard: `subscribeChains([chainId1, chainId2])`
8429
+ *
8430
+ * For convenience, use `sdk.connectWalletEvents()` which auto-subscribes
8431
+ * based on auth type.
8432
+ *
8433
+ * @example Manual subscription
8434
+ * ```typescript
8435
+ * await sdk.walletEvents.connect();
8436
+ * await sdk.walletEvents.subscribeWallets([
8437
+ * { address: user.wallets[0].address, chainId: 39123 }
8438
+ * ]);
8439
+ *
8440
+ * sdk.walletEvents.on('Transfer', (event) => {
8441
+ * console.log(`Received ${event.data.value} tokens`);
8442
+ * });
8443
+ * ```
8444
+ *
8445
+ * @example Auto-subscription (recommended)
8446
+ * ```typescript
8447
+ * // Auto-subscribes based on auth type (user/business/admin)
8448
+ * await sdk.connectWalletEvents();
8449
+ * ```
8450
+ */
8451
+ /**
8452
+ * Wallet Events Manager - Manages real-time blockchain event subscriptions
8453
+ *
8454
+ * Low-level WS client wrapper. For auto-subscription based on auth type,
8455
+ * use `sdk.connectWalletEvents()` which handles subscription automatically.
8456
+ */
8457
+ class WalletEventsManager {
8458
+ constructor(apiClient, eventEmitter, config) {
8459
+ this.apiClient = apiClient;
8460
+ this.eventEmitter = eventEmitter;
8461
+ this.client = null;
8462
+ this.pendingHandlers = [];
8463
+ this.unsubscribes = [];
8464
+ this.config = {
8465
+ autoReconnect: true,
8466
+ connectionTimeout: 30000,
8467
+ debug: false,
8468
+ ...config,
8469
+ };
8470
+ }
8471
+ /**
8472
+ * Connect to real-time wallet events
8473
+ *
8474
+ * Establishes WebSocket connection to the WS relay server.
8475
+ * After connecting, call subscribeWallets() or subscribeChains() to start
8476
+ * receiving events.
8477
+ *
8478
+ * For auto-subscription, use `sdk.connectWalletEvents()` instead.
8479
+ */
8480
+ async connect() {
8481
+ // Already connected?
8482
+ if (this.client?.getState() === 'connected') {
8483
+ return;
8484
+ }
8485
+ const sdkConfig = this.apiClient.getConfig();
8486
+ const authProvider = sdkConfig.authProvider;
8487
+ if (!authProvider) {
8488
+ throw new Error('Not authenticated. Call sdk.auth.login() first.');
8489
+ }
8490
+ const token = await authProvider.getToken();
8491
+ if (!token) {
8492
+ throw new Error('No authentication token available. Call sdk.auth.login() first.');
8493
+ }
8494
+ // Resolve wsUrl: config override > SDK config > environment default
8495
+ const wsUrl = this.config.wsUrl
8496
+ || sdkConfig.walletEventsWsUrl
8497
+ || buildWalletEventsWsUrl(sdkConfig.environment);
8498
+ // Create token refresher that fetches fresh token from auth provider
8499
+ const tokenRefresher = async () => {
8500
+ const freshToken = await authProvider.getToken();
8501
+ if (!freshToken) {
8502
+ throw new Error('Failed to refresh token');
8503
+ }
8504
+ return freshToken;
8505
+ };
8506
+ this.client = new PersEventsClient({
8507
+ wsUrl,
8508
+ autoReconnect: this.config.autoReconnect,
8509
+ connectionTimeout: this.config.connectionTimeout,
8510
+ debug: this.config.debug,
8511
+ tokenRefresher,
8512
+ });
8513
+ await this.client.connect(token);
8514
+ // Clear previous unsubscribes on new connection (prevent memory leak)
8515
+ this.unsubscribes.forEach(unsub => unsub());
8516
+ this.unsubscribes = [];
8517
+ // Route all events through PersEventEmitter for unified stream
8518
+ // Note: Cast needed because web3-types uses string for event.type while pers-shared uses strict union
8519
+ this.client.on('*', (event) => this.emitToPersEvents(event));
8520
+ // Re-attach any handlers registered before connect
8521
+ for (const { type, handler } of this.pendingHandlers) {
8522
+ this.unsubscribes.push(this.client.on(type, handler));
8523
+ }
8524
+ this.pendingHandlers = [];
8525
+ }
8526
+ /**
8527
+ * Disconnect from real-time events
8528
+ */
8529
+ disconnect() {
8530
+ this.unsubscribes.forEach(unsub => unsub());
8531
+ this.unsubscribes = [];
8532
+ this.client?.disconnect();
8533
+ this.client = null;
8534
+ }
8535
+ // ─────────────────────────────────────────────────────────────────────────────
8536
+ // Subscription Methods (v1.2.0)
8537
+ // ─────────────────────────────────────────────────────────────────────────────
8538
+ /**
8539
+ * Subscribe to wallet events
8540
+ *
8541
+ * Receives events only for the specified wallet addresses.
8542
+ * Use this for user-facing SDK where you want to monitor specific wallets.
8543
+ *
8544
+ * @param wallets - Array of wallet info (address + chainId)
8545
+ * @returns Promise that resolves when subscription is confirmed
8546
+ *
8547
+ * @example
8548
+ * ```typescript
8549
+ * await sdk.walletEvents.connect();
8550
+ * await sdk.walletEvents.subscribeWallets([
8551
+ * { address: '0x123...', chainId: 39123 }
8552
+ * ]);
8553
+ * ```
8554
+ */
8555
+ async subscribeWallets(wallets) {
8556
+ if (!this.client) {
8557
+ throw new Error('Not connected. Call connect() first.');
8558
+ }
8559
+ return this.client.subscribeWallets(wallets);
8560
+ }
8561
+ /**
8562
+ * Subscribe to chain events (Admin Dashboard)
8563
+ *
8564
+ * Receives ALL events on the specified chains.
8565
+ * Use for admin dashboards that need to monitor all activity.
8566
+ *
8567
+ * @param chains - Array of chain IDs to subscribe to
8568
+ * @returns Promise that resolves when subscription is confirmed
8569
+ *
8570
+ * @example
8571
+ * ```typescript
8572
+ * await sdk.walletEvents.connect();
8573
+ * await sdk.walletEvents.subscribeChains([39123, 137]);
8574
+ * ```
8575
+ */
8576
+ async subscribeChains(chains) {
8577
+ if (!this.client) {
8578
+ throw new Error('Not connected. Call connect() first.');
8579
+ }
8580
+ return this.client.subscribeChains(chains);
8581
+ }
8582
+ /**
8583
+ * Unsubscribe from wallet events
8584
+ *
8585
+ * @param wallets - Array of wallet info to unsubscribe from
8586
+ * @returns Promise that resolves when unsubscription is confirmed
8587
+ */
8588
+ async unsubscribeWallets(wallets) {
8589
+ if (!this.client) {
8590
+ throw new Error('Not connected. Call connect() first.');
8591
+ }
8592
+ return this.client.unsubscribeWallets(wallets);
8593
+ }
8594
+ /**
8595
+ * Unsubscribe from chain events
8596
+ *
8597
+ * @param chains - Array of chain IDs to unsubscribe from
8598
+ * @returns Promise that resolves when unsubscription is confirmed
8599
+ */
8600
+ async unsubscribeChains(chains) {
8601
+ if (!this.client) {
8602
+ throw new Error('Not connected. Call connect() first.');
8603
+ }
8604
+ return this.client.unsubscribeChains(chains);
8605
+ }
8606
+ /**
8607
+ * Get current subscription state
8608
+ */
8609
+ getSubscriptionState() {
8610
+ return this.client?.getSubscriptionState() ?? { wallets: [], chains: [], activeChains: [] };
8611
+ }
8612
+ // ─────────────────────────────────────────────────────────────────────────────
8613
+ // Event Handlers
8614
+ // ─────────────────────────────────────────────────────────────────────────────
8615
+ /**
8616
+ * Subscribe to blockchain events
8617
+ *
8618
+ * @param eventType - Event type ('Transfer', 'Approval', etc.) or '*' for all
8619
+ * @param handler - Callback function when event is received
8620
+ * @returns Unsubscribe function
8621
+ *
8622
+ * @example
8623
+ * ```typescript
8624
+ * const unsub = sdk.walletEvents.on('Transfer', (event) => {
8625
+ * console.log(`Transfer: ${event.data.from} -> ${event.data.to}`);
8626
+ * });
8627
+ *
8628
+ * // Later: stop listening
8629
+ * unsub();
8630
+ * ```
8631
+ */
8632
+ on(eventType, handler) {
8633
+ if (this.client) {
8634
+ const unsub = this.client.on(eventType, handler);
8635
+ this.unsubscribes.push(unsub);
8636
+ return unsub;
8637
+ }
8638
+ else {
8639
+ // Store for when connect() is called
8640
+ this.pendingHandlers.push({ type: eventType, handler });
8641
+ return () => {
8642
+ const idx = this.pendingHandlers.findIndex(h => h.handler === handler);
8643
+ if (idx !== -1)
8644
+ this.pendingHandlers.splice(idx, 1);
8645
+ };
8646
+ }
8647
+ }
8648
+ /**
8649
+ * Subscribe to connection state changes
8650
+ */
8651
+ onStateChange(handler) {
8652
+ if (this.client) {
8653
+ return this.client.onStateChange(handler);
8654
+ }
8655
+ return () => { };
8656
+ }
8657
+ /**
8658
+ * Get current connection state
8659
+ */
8660
+ getState() {
8661
+ return this.client?.getState() ?? 'disconnected';
8662
+ }
8663
+ /**
8664
+ * Check if connected
8665
+ */
8666
+ isConnected() {
8667
+ return this.getState() === 'connected';
8668
+ }
8669
+ /**
8670
+ * Get connection info (wallets, active chains)
8671
+ */
8672
+ getConnectionInfo() {
8673
+ return this.client?.getConnectionInfo();
8674
+ }
8675
+ // ─────────────────────────────────────────────────────────────────────────────
8676
+ // Private Methods
8677
+ // ─────────────────────────────────────────────────────────────────────────────
8678
+ /**
8679
+ * Build a descriptive message for blockchain events
8680
+ * Neutral tone - frontend will handle user-facing presentation
8681
+ */
8682
+ buildUserMessage(event) {
8683
+ const { type, data } = event;
8684
+ // Use string for switch since event types may extend beyond strict BlockchainEventType union
8685
+ const eventType = type;
8686
+ const from = data.from?.toLowerCase();
8687
+ const to = data.to?.toLowerCase();
8688
+ const shortFrom = from ? `${from.slice(0, 6)}...${from.slice(-4)}` : 'unknown';
8689
+ const shortTo = to ? `${to.slice(0, 6)}...${to.slice(-4)}` : 'unknown';
8690
+ // Get user's wallets to determine direction
8691
+ const subscriptionState = this.getSubscriptionState();
8692
+ const userWallets = subscriptionState.wallets.map(w => w.address.toLowerCase());
8693
+ const isIncoming = to && userWallets.includes(to);
8694
+ const isOutgoing = from && userWallets.includes(from);
8695
+ const isMint = from === '0x0000000000000000000000000000000000000000';
8696
+ const isBurn = to === '0x0000000000000000000000000000000000000000';
8697
+ switch (eventType) {
8698
+ case 'Transfer':
8699
+ if (isMint) {
8700
+ return `Token minted to ${shortTo}`;
8701
+ }
8702
+ else if (isBurn) {
8703
+ return `Token burned from ${shortFrom}`;
8704
+ }
8705
+ else if (isIncoming) {
8706
+ return `Transfer received from ${shortFrom}`;
8707
+ }
8708
+ else if (isOutgoing) {
8709
+ return `Transfer sent to ${shortTo}`;
8710
+ }
8711
+ return `Transfer from ${shortFrom} to ${shortTo}`;
8712
+ case 'Approval':
8713
+ return `Approval from ${shortFrom}`;
8714
+ case 'SmartWalletCreated':
8715
+ return `Smart wallet created: ${shortTo}`;
8716
+ case 'TransactionExecuted':
8717
+ return `Transaction executed by ${shortFrom}`;
8718
+ case 'OwnershipTransferred':
8719
+ return `Ownership transferred from ${shortFrom} to ${shortTo}`;
8720
+ default:
8721
+ return `${eventType} event`;
8722
+ }
8723
+ }
8724
+ /**
8725
+ * Route blockchain event to PersEventEmitter for unified event stream
8726
+ */
8727
+ emitToPersEvents(event) {
8728
+ const userMessage = this.buildUserMessage(event);
8729
+ this.eventEmitter.emitSuccess({
8730
+ type: `wallet_${event.type.toLowerCase()}`,
8731
+ domain: 'wallet',
8732
+ userMessage,
8733
+ details: {
8734
+ chainId: event.chainId,
8735
+ txHash: event.txHash,
8736
+ blockNumber: event.blockNumber,
8737
+ timestamp: event.timestamp,
8738
+ contractAddress: event.contractAddress,
8739
+ ...event.data,
8740
+ },
8741
+ });
8742
+ }
8743
+ }
8744
+
8745
+ /**
8746
+ * @fileoverview PERS SDK - Platform-agnostic TypeScript SDK with High-Level Managers
8747
+ *
8748
+ * @module @explorins/pers-sdk
8749
+ * @version 1.6.4
8750
+ * @author Explorins
8751
+ * @license MIT
8752
+ */
8753
+ /**
8754
+ * PERS SDK - Main SDK class with domain managers
8755
+ *
8756
+ * Main SDK interface providing clean, high-level managers for common operations
8757
+ * while maintaining full access to the underlying API client and domain services.
8758
+ *
8759
+ * The SDK follows a layered architecture:
8760
+ * - **Managers**: High-level, intuitive APIs for common operations
8761
+ * - **Domain Services**: Full-featured access for advanced use cases
8762
+ * - **API Client**: Direct REST API access for custom operations
8763
+ *
8764
+ * @group Core
8765
+ * @category SDK
8766
+ *
8767
+ * @example Basic Setup
8768
+ * ```typescript
8769
+ * import { PersSDK } from '@explorins/pers-sdk';
8770
+ * import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';
8771
+ *
8772
+ * const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
8773
+ * environment: 'production',
8774
+ * apiProjectKey: 'your-project-key'
8775
+ * });
8776
+ * ```
8777
+ *
8778
+ * @example Authentication Flow
8779
+ * ```typescript
8780
+ * // Login with external JWT
8781
+ * await sdk.auth.loginWithToken(firebaseJWT, 'user');
8782
+ *
8783
+ * // Check authentication
8784
+ * if (await sdk.auth.isAuthenticated()) {
8785
+ * const user = await sdk.auth.getCurrentUser();
8786
+ * console.log('Welcome,', user.name);
8787
+ * }
8788
+ * ```
8789
+ *
8790
+ * @example Business Operations
8791
+ * ```typescript
8792
+ * // Get active businesses
8793
+ * const businesses = await sdk.businesses.getActiveBusinesses();
8794
+ *
8795
+ * // Get business details
8796
+ * const business = await sdk.businesses.getBusinessById(businessId);
8797
+ * ```
8798
+ *
8799
+ * @example Token Operations
8800
+ * ```typescript
8801
+ * // Get user's token balances
8802
+ * const tokens = await sdk.tokens.getTokens();
8803
+ *
8804
+ * // Get active credit token
8805
+ * const creditToken = await sdk.tokens.getActiveCreditToken();
8806
+ * ```
8807
+ *
8808
+ * @since 1.3.0 - Manager pattern architecture
8809
+ */
8810
+ class PersSDK {
8811
+ /**
8812
+ * Creates a new PERS SDK instance
8813
+ *
8814
+ * Initializes all domain managers and sets up the API client with the provided
8815
+ * HTTP client adapter and configuration.
8816
+ *
8817
+ * @param httpClient - Platform-specific HTTP client implementation
8818
+ * @param config - SDK configuration options
8819
+ *
8820
+ * @example Browser Setup
8821
+ * ```typescript
8822
+ * import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';
8823
+ *
8824
+ * const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
8825
+ * environment: 'production',
8826
+ * apiProjectKey: 'your-project-key'
8827
+ * });
8828
+ * ```
8829
+ *
8830
+ * @example Node.js Setup
8831
+ * ```typescript
8832
+ * import { NodeHttpClientAdapter } from '@explorins/pers-sdk/platform-adapters';
8833
+ *
8834
+ * const sdk = new PersSDK(new NodeHttpClientAdapter(), {
8835
+ * environment: 'production',
8836
+ * apiProjectKey: 'your-project-key',
8837
+ * baseUrl: 'https://api.yourpers.com'
8838
+ * });
8839
+ * ```
8840
+ *
8841
+ * @example Angular Setup
7496
8842
  * ```typescript
7497
8843
  * import { AngularHttpClientAdapter } from '@explorins/pers-sdk/platform-adapters';
7498
8844
  *
@@ -7509,6 +8855,81 @@ class PersSDK {
7509
8855
  // Initialize event emitter and wire to API client for error events
7510
8856
  this._events = new PersEventEmitter();
7511
8857
  this.apiClient.setEvents(this._events);
8858
+ // Auto-connect to wallet events on successful login (if enabled)
8859
+ this.setupWalletEventsAutoConnect();
8860
+ }
8861
+ /**
8862
+ * Setup auto-connect for wallet events on authentication
8863
+ * @internal
8864
+ */
8865
+ setupWalletEventsAutoConnect() {
8866
+ const config = this.apiClient.getConfig();
8867
+ // Check if captureWalletEvents is enabled (default: true)
8868
+ if (config.captureWalletEvents === false) {
8869
+ return;
8870
+ }
8871
+ // Listen for login success events (both fresh login and session restoration)
8872
+ this._events.subscribe((event) => {
8873
+ if (event.level === 'success' && (event.type === 'LOGIN_SUCCESS' || event.type === 'session_restored')) {
8874
+ // Auto-connect and subscribe to wallet events (fire and forget, log errors)
8875
+ this.connectWalletEvents().catch((err) => {
8876
+ console.warn('[PersSDK] Failed to auto-connect wallet events:', err.message);
8877
+ });
8878
+ }
8879
+ // Disconnect on logout
8880
+ if (event.level === 'success' && event.type === 'logout_success') {
8881
+ this.walletEvents.disconnect();
8882
+ }
8883
+ });
8884
+ }
8885
+ /**
8886
+ * Connect to wallet events and auto-subscribe based on auth type
8887
+ *
8888
+ * Connects to the WebSocket relay and automatically subscribes to relevant
8889
+ * blockchain events based on the current authentication type:
8890
+ * - **USER**: Subscribes to all user's wallets
8891
+ * - **BUSINESS**: Subscribes to all business's wallets
8892
+ * - **TENANT**: Subscribes to all chains where tokens are deployed
8893
+ *
8894
+ * This method is called automatically on login when `captureWalletEvents` is enabled.
8895
+ * Call manually if you need to reconnect or refresh subscriptions.
8896
+ *
8897
+ * @example Manual connection
8898
+ * ```typescript
8899
+ * await sdk.connectWalletEvents();
8900
+ * ```
8901
+ */
8902
+ async connectWalletEvents() {
8903
+ await this.walletEvents.connect();
8904
+ // Get authType from auth provider (where it's stored during login)
8905
+ const authProvider = this.apiClient.getConfig().authProvider;
8906
+ const authType = authProvider?.getAuthType ? await authProvider.getAuthType() : undefined;
8907
+ switch (authType) {
8908
+ case persShared.AccountOwnerType.USER: {
8909
+ const user = await this.users.getCurrentUser();
8910
+ const wallets = user.wallets?.map(w => ({ address: w.address, chainId: w.chainId })) || [];
8911
+ if (wallets.length > 0) {
8912
+ await this.walletEvents.subscribeWallets(wallets);
8913
+ }
8914
+ break;
8915
+ }
8916
+ case persShared.AccountOwnerType.BUSINESS: {
8917
+ const business = await this.auth.getCurrentBusiness();
8918
+ const wallets = business.wallets?.map(w => ({ address: w.address, chainId: w.chainId })) || [];
8919
+ if (wallets.length > 0) {
8920
+ await this.walletEvents.subscribeWallets(wallets);
8921
+ }
8922
+ break;
8923
+ }
8924
+ case persShared.AccountOwnerType.TENANT: {
8925
+ const tokens = await this.tokens.getTokens();
8926
+ const chains = [...new Set(tokens.data.map(t => t.chainId))];
8927
+ if (chains.length > 0) {
8928
+ await this.walletEvents.subscribeChains(chains);
8929
+ }
8930
+ break;
8931
+ }
8932
+ }
7512
8933
  }
7513
8934
  /**
7514
8935
  * Restore user session from stored tokens
@@ -7823,7 +9244,7 @@ class PersSDK {
7823
9244
  */
7824
9245
  get tenants() {
7825
9246
  if (!this._tenants) {
7826
- this._tenants = new TenantManager(this.apiClient);
9247
+ this._tenants = new tenantManager.TenantManager(this.apiClient);
7827
9248
  }
7828
9249
  return this._tenants;
7829
9250
  }
@@ -7916,6 +9337,103 @@ class PersSDK {
7916
9337
  }
7917
9338
  return this._triggerSources;
7918
9339
  }
9340
+ /**
9341
+ * Webhook manager - High-level webhook operations
9342
+ *
9343
+ * Provides methods for creating webhooks, triggering them programmatically,
9344
+ * and monitoring execution history. Supports async workflows with callbacks.
9345
+ *
9346
+ * @returns WebhookManager instance
9347
+ *
9348
+ * @example Webhook Operations
9349
+ * ```typescript
9350
+ * // Admin: Create a webhook
9351
+ * const webhook = await sdk.webhooks.create({
9352
+ * name: 'Order Processing',
9353
+ * targetUrl: 'https://n8n.example.com/webhook/orders',
9354
+ * method: 'POST'
9355
+ * });
9356
+ *
9357
+ * // Trigger webhook with payload
9358
+ * const result = await sdk.webhooks.trigger(webhook.id, {
9359
+ * orderId: 'order-123',
9360
+ * action: 'created'
9361
+ * });
9362
+ *
9363
+ * // Trigger and wait for async workflow completion
9364
+ * const asyncResult = await sdk.webhooks.triggerAndWait(
9365
+ * 'ai-webhook',
9366
+ * { prompt: 'Analyze this data' },
9367
+ * 30000 // Wait up to 30s
9368
+ * );
9369
+ * ```
9370
+ *
9371
+ * @see {@link WebhookManager} for detailed documentation
9372
+ */
9373
+ get webhooks() {
9374
+ if (!this._webhooks) {
9375
+ const projectKey = this.apiClient.getConfig().apiProjectKey || '';
9376
+ this._webhooks = new WebhookManager(this.apiClient, projectKey, this._events);
9377
+ }
9378
+ return this._webhooks;
9379
+ }
9380
+ /**
9381
+ * Wallet Events Manager - Real-time blockchain events for user's wallets
9382
+ *
9383
+ * Provides real-time WebSocket connection to receive blockchain events
9384
+ * for the user's wallets (transfers, approvals, NFT mints, etc.).
9385
+ *
9386
+ * Events are also routed through `sdk.events` for unified event handling.
9387
+ *
9388
+ * **Important:** Requires `walletEventsWsUrl` configuration and authentication.
9389
+ *
9390
+ * @returns WalletEventsManager instance
9391
+ *
9392
+ * @example Basic Usage
9393
+ * ```typescript
9394
+ * // Configure SDK with events URL
9395
+ * sdk.configureWalletEvents({ wsUrl: 'wss://events.pers.ninja' });
9396
+ *
9397
+ * // Connect after authentication
9398
+ * await sdk.auth.loginWithToken(jwt, 'user');
9399
+ * await sdk.walletEvents.connect();
9400
+ *
9401
+ * // Listen for token transfers
9402
+ * sdk.walletEvents.on('Transfer', (event) => {
9403
+ * if (event.data.to === myWallet) {
9404
+ * showNotification(`Received ${event.data.value} tokens!`);
9405
+ * }
9406
+ * });
9407
+ * ```
9408
+ *
9409
+ * @example Unified Event Stream
9410
+ * ```typescript
9411
+ * // Wallet events also flow through sdk.events
9412
+ * sdk.events.subscribe((event) => {
9413
+ * if (event.domain === 'wallet') {
9414
+ * console.log('Wallet event:', event.type);
9415
+ * }
9416
+ * });
9417
+ * ```
9418
+ *
9419
+ * @see {@link WalletEventsManager} for detailed documentation
9420
+ */
9421
+ get walletEvents() {
9422
+ if (!this._walletEvents) {
9423
+ this._walletEvents = new WalletEventsManager(this.apiClient, this._events, this.walletEventsConfig);
9424
+ }
9425
+ return this._walletEvents;
9426
+ }
9427
+ /**
9428
+ * Configure wallet events (call before accessing walletEvents)
9429
+ *
9430
+ * @param config - Events configuration including wsUrl
9431
+ */
9432
+ configureWalletEvents(config) {
9433
+ this.walletEventsConfig = config;
9434
+ // Reset manager so it picks up new config
9435
+ this._walletEvents = undefined;
9436
+ }
7919
9437
  /**
7920
9438
  * Gets the API client for direct PERS API requests
7921
9439
  *
@@ -7987,6 +9505,7 @@ exports.LocalStorageTokenStorage = LocalStorageTokenStorage;
7987
9505
  exports.MemoryTokenStorage = MemoryTokenStorage;
7988
9506
  exports.PersApiClient = PersApiClient;
7989
9507
  exports.PersEventEmitter = PersEventEmitter;
9508
+ exports.PersEventsClient = PersEventsClient;
7990
9509
  exports.PersSDK = PersSDK;
7991
9510
  exports.PurchaseManager = PurchaseManager;
7992
9511
  exports.RedemptionManager = RedemptionManager;
@@ -7994,14 +9513,19 @@ exports.SDK_NAME = SDK_NAME;
7994
9513
  exports.SDK_USER_AGENT = SDK_USER_AGENT;
7995
9514
  exports.SDK_VERSION = SDK_VERSION;
7996
9515
  exports.StaticJwtAuthProvider = StaticJwtAuthProvider;
7997
- exports.TenantManager = TenantManager;
7998
9516
  exports.TokenManager = TokenManager;
7999
9517
  exports.TransactionManager = TransactionManager;
8000
9518
  exports.TriggerSourceManager = TriggerSourceManager;
8001
9519
  exports.UserManager = UserManager;
8002
9520
  exports.UserStatusManager = UserStatusManager;
9521
+ exports.WalletEventsManager = WalletEventsManager;
8003
9522
  exports.WebDPoPCryptoProvider = WebDPoPCryptoProvider;
9523
+ exports.WebhookApi = WebhookApi;
9524
+ exports.WebhookManager = WebhookManager;
9525
+ exports.WebhookService = WebhookService;
8004
9526
  exports.buildApiRoot = buildApiRoot;
9527
+ exports.buildWalletEventsWsUrl = buildWalletEventsWsUrl;
9528
+ exports.createPersEventsClient = createPersEventsClient;
8005
9529
  exports.createPersSDK = createPersSDK;
8006
9530
  exports.mergeWithDefaults = mergeWithDefaults;
8007
- //# sourceMappingURL=pers-sdk-Co-R9M1O.cjs.map
9531
+ //# sourceMappingURL=pers-sdk-Cv7hM1I7.cjs.map