@explorins/pers-sdk 2.1.11 → 2.1.15

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