@explorins/pers-sdk 1.6.45 → 1.6.47

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 (137) hide show
  1. package/README.md +185 -0
  2. package/dist/analytics.cjs +12 -0
  3. package/dist/analytics.cjs.map +1 -1
  4. package/dist/analytics.js +1 -1
  5. package/dist/business/api/business-membership-api.d.ts +186 -0
  6. package/dist/business/api/business-membership-api.d.ts.map +1 -0
  7. package/dist/business/index.d.ts +2 -0
  8. package/dist/business/index.d.ts.map +1 -1
  9. package/dist/business/services/business-membership-service.d.ts +186 -0
  10. package/dist/business/services/business-membership-service.d.ts.map +1 -0
  11. package/dist/business.cjs +727 -9
  12. package/dist/business.cjs.map +1 -1
  13. package/dist/business.js +711 -2
  14. package/dist/business.js.map +1 -1
  15. package/dist/campaign/api/campaign-api.d.ts +7 -10
  16. package/dist/campaign/api/campaign-api.d.ts.map +1 -1
  17. package/dist/campaign/services/campaign-service.d.ts +18 -7
  18. package/dist/campaign/services/campaign-service.d.ts.map +1 -1
  19. package/dist/campaign.cjs +13 -1
  20. package/dist/campaign.cjs.map +1 -1
  21. package/dist/campaign.js +2 -2
  22. package/dist/chunks/{campaign-service-CJPXgFBe.js → campaign-service-CKwkiOLx.js} +29 -39
  23. package/dist/chunks/campaign-service-CKwkiOLx.js.map +1 -0
  24. package/dist/chunks/{campaign-service-B1tQMNqA.cjs → campaign-service-D-v9ZlUB.cjs} +29 -39
  25. package/dist/chunks/campaign-service-D-v9ZlUB.cjs.map +1 -0
  26. package/dist/chunks/{pers-sdk-DzS7mkm7.cjs → pers-sdk-DULFjOW2.cjs} +862 -97
  27. package/dist/chunks/pers-sdk-DULFjOW2.cjs.map +1 -0
  28. package/dist/chunks/{pers-sdk-VGEG59g3.js → pers-sdk-VmeBqUEP.js} +858 -97
  29. package/dist/chunks/pers-sdk-VmeBqUEP.js.map +1 -0
  30. package/dist/chunks/transaction-request.builder-DltmruUC.js +296 -0
  31. package/dist/chunks/transaction-request.builder-DltmruUC.js.map +1 -0
  32. package/dist/chunks/transaction-request.builder-DrqTWcyC.cjs +303 -0
  33. package/dist/chunks/transaction-request.builder-DrqTWcyC.cjs.map +1 -0
  34. package/dist/chunks/{user-service-D1Rn4U8u.cjs → user-service-B89wBOV1.cjs} +60 -10
  35. package/dist/chunks/user-service-B89wBOV1.cjs.map +1 -0
  36. package/dist/chunks/{user-service-D6mTa_WZ.js → user-service-doT5vsBD.js} +60 -10
  37. package/dist/chunks/user-service-doT5vsBD.js.map +1 -0
  38. package/dist/chunks/{web3-chain-service-BLFxB5TA.cjs → web3-chain-service-6vsVHPjl.cjs} +116 -16
  39. package/dist/chunks/web3-chain-service-6vsVHPjl.cjs.map +1 -0
  40. package/dist/chunks/{web3-chain-service-JRSwxr-s.js → web3-chain-service-BcUeeujC.js} +111 -17
  41. package/dist/chunks/web3-chain-service-BcUeeujC.js.map +1 -0
  42. package/dist/core/auth/api/auth-api.d.ts +35 -0
  43. package/dist/core/auth/api/auth-api.d.ts.map +1 -1
  44. package/dist/core/auth/auth-provider.interface.d.ts +7 -1
  45. package/dist/core/auth/auth-provider.interface.d.ts.map +1 -1
  46. package/dist/core/auth/services/auth-service.d.ts +26 -1
  47. package/dist/core/auth/services/auth-service.d.ts.map +1 -1
  48. package/dist/core/auth/token-storage.d.ts +3 -2
  49. package/dist/core/auth/token-storage.d.ts.map +1 -1
  50. package/dist/core/errors/index.d.ts +75 -6
  51. package/dist/core/errors/index.d.ts.map +1 -1
  52. package/dist/core/events/event-emitter.d.ts +106 -0
  53. package/dist/core/events/event-emitter.d.ts.map +1 -0
  54. package/dist/core/events/event-types.d.ts +127 -0
  55. package/dist/core/events/event-types.d.ts.map +1 -0
  56. package/dist/core/events/index.d.ts +22 -0
  57. package/dist/core/events/index.d.ts.map +1 -0
  58. package/dist/core/index.d.ts +3 -0
  59. package/dist/core/index.d.ts.map +1 -1
  60. package/dist/core/pers-api-client.d.ts +12 -0
  61. package/dist/core/pers-api-client.d.ts.map +1 -1
  62. package/dist/core/version.d.ts +15 -0
  63. package/dist/core/version.d.ts.map +1 -0
  64. package/dist/core.cjs +19 -6
  65. package/dist/core.cjs.map +1 -1
  66. package/dist/core.js +6 -6
  67. package/dist/donation.cjs +12 -0
  68. package/dist/donation.cjs.map +1 -1
  69. package/dist/donation.js +1 -1
  70. package/dist/index.cjs +43 -9
  71. package/dist/index.cjs.map +1 -1
  72. package/dist/index.js +7 -6
  73. package/dist/index.js.map +1 -1
  74. package/dist/managers/auth-manager.d.ts +77 -4
  75. package/dist/managers/auth-manager.d.ts.map +1 -1
  76. package/dist/managers/business-manager.d.ts +192 -4
  77. package/dist/managers/business-manager.d.ts.map +1 -1
  78. package/dist/managers/campaign-manager.d.ts +48 -65
  79. package/dist/managers/campaign-manager.d.ts.map +1 -1
  80. package/dist/managers/redemption-manager.d.ts +3 -1
  81. package/dist/managers/redemption-manager.d.ts.map +1 -1
  82. package/dist/managers/transaction-manager.d.ts +4 -2
  83. package/dist/managers/transaction-manager.d.ts.map +1 -1
  84. package/dist/managers/user-manager.d.ts +57 -1
  85. package/dist/managers/user-manager.d.ts.map +1 -1
  86. package/dist/managers/web3-manager.d.ts.map +1 -1
  87. package/dist/package.json +2 -2
  88. package/dist/payment.cjs +12 -0
  89. package/dist/payment.cjs.map +1 -1
  90. package/dist/payment.js +1 -1
  91. package/dist/pers-sdk.d.ts +49 -0
  92. package/dist/pers-sdk.d.ts.map +1 -1
  93. package/dist/redemption.cjs +12 -0
  94. package/dist/redemption.cjs.map +1 -1
  95. package/dist/redemption.js +1 -1
  96. package/dist/shared/interfaces/pers-shared-lib.interfaces.d.ts +2 -1
  97. package/dist/shared/interfaces/pers-shared-lib.interfaces.d.ts.map +1 -1
  98. package/dist/tenant.cjs +12 -0
  99. package/dist/tenant.cjs.map +1 -1
  100. package/dist/tenant.js +1 -1
  101. package/dist/token.cjs +12 -0
  102. package/dist/token.cjs.map +1 -1
  103. package/dist/token.js +1 -1
  104. package/dist/transaction/models/index.d.ts +2 -0
  105. package/dist/transaction/models/index.d.ts.map +1 -1
  106. package/dist/transaction/models/transaction-request.builder.d.ts +256 -0
  107. package/dist/transaction/models/transaction-request.builder.d.ts.map +1 -0
  108. package/dist/transaction.cjs +7 -0
  109. package/dist/transaction.cjs.map +1 -1
  110. package/dist/transaction.js +1 -0
  111. package/dist/transaction.js.map +1 -1
  112. package/dist/user/api/user-api.d.ts +28 -7
  113. package/dist/user/api/user-api.d.ts.map +1 -1
  114. package/dist/user/services/user-service.d.ts +21 -0
  115. package/dist/user/services/user-service.d.ts.map +1 -1
  116. package/dist/user-status.cjs +12 -0
  117. package/dist/user-status.cjs.map +1 -1
  118. package/dist/user-status.js +1 -1
  119. package/dist/user.cjs +13 -1
  120. package/dist/user.cjs.map +1 -1
  121. package/dist/user.js +2 -2
  122. package/dist/web3-chain.cjs +13 -1
  123. package/dist/web3-chain.cjs.map +1 -1
  124. package/dist/web3-chain.js +2 -2
  125. package/package.json +2 -2
  126. package/dist/chunks/business-service-8Xd3d5oY.js +0 -238
  127. package/dist/chunks/business-service-8Xd3d5oY.js.map +0 -1
  128. package/dist/chunks/business-service-P9o4cwQL.cjs +0 -241
  129. package/dist/chunks/business-service-P9o4cwQL.cjs.map +0 -1
  130. package/dist/chunks/campaign-service-B1tQMNqA.cjs.map +0 -1
  131. package/dist/chunks/campaign-service-CJPXgFBe.js.map +0 -1
  132. package/dist/chunks/pers-sdk-DzS7mkm7.cjs.map +0 -1
  133. package/dist/chunks/pers-sdk-VGEG59g3.js.map +0 -1
  134. package/dist/chunks/user-service-D1Rn4U8u.cjs.map +0 -1
  135. package/dist/chunks/user-service-D6mTa_WZ.js.map +0 -1
  136. package/dist/chunks/web3-chain-service-BLFxB5TA.cjs.map +0 -1
  137. package/dist/chunks/web3-chain-service-JRSwxr-s.js.map +0 -1
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var persShared = require('@explorins/pers-shared');
4
- var web3ChainService = require('./web3-chain-service-BLFxB5TA.cjs');
5
- var userService = require('./user-service-D1Rn4U8u.cjs');
4
+ var web3ChainService = require('./web3-chain-service-6vsVHPjl.cjs');
5
+ var userService = require('./user-service-B89wBOV1.cjs');
6
6
  var userStatus = require('../user-status.cjs');
7
7
  var tokenService = require('./token-service-BWScn8Qa.cjs');
8
- var businessService = require('./business-service-P9o4cwQL.cjs');
9
- var campaignService = require('./campaign-service-B1tQMNqA.cjs');
8
+ var business = require('../business.cjs');
9
+ var campaignService = require('./campaign-service-D-v9ZlUB.cjs');
10
10
  var redemptionService = require('./redemption-service-7qbeQxEM.cjs');
11
11
  var transactionService = require('./transaction-service-CXjTHCFu.cjs');
12
12
  var paymentService = require('./payment-service-B4qx0qiE.cjs');
@@ -61,6 +61,11 @@ function mergeWithDefaults(config) {
61
61
  /**
62
62
  * Platform-Agnostic Auth API Client
63
63
  * Handles authentication operations using the PERS backend
64
+ *
65
+ * Supports the universal token endpoint:
66
+ * - User authentication (default)
67
+ * - Business authentication with context selection
68
+ * - Admin/Tenant authentication
64
69
  */
65
70
  class AuthApi {
66
71
  constructor(apiClient) {
@@ -87,6 +92,36 @@ class AuthApi {
87
92
  };
88
93
  return this.apiClient.post(`${this.basePath}/token`, body, { bypassAuth: true });
89
94
  }
95
+ /**
96
+ * Login as business with JWT
97
+ *
98
+ * Authenticates a user in a business context with role included in JWT.
99
+ *
100
+ * @param jwt - Authentication token (passkey or Firebase JWT)
101
+ * @param options - Business authentication options
102
+ * @param options.businessId - The business ID to authenticate as
103
+ * - If user has single business membership, auto-selected
104
+ * - If user has multiple memberships without businessId, throws MULTIPLE_CONTEXT_SELECTION_REQUIRED
105
+ * @returns Session response with business context and role in JWT
106
+ * @throws MultipleContextSelectionError when businessId is required but not provided
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * // Single business membership - auto-selects
111
+ * const response = await authApi.loginBusiness(jwt);
112
+ *
113
+ * // Multiple memberships - explicit selection
114
+ * const response = await authApi.loginBusiness(jwt, { businessId: 'biz-123' });
115
+ * ```
116
+ */
117
+ async loginBusiness(jwt, options) {
118
+ const body = {
119
+ authToken: jwt,
120
+ authType: persShared.AccountOwnerType.BUSINESS,
121
+ context: options?.businessId ? { businessId: options.businessId } : undefined
122
+ };
123
+ return this.apiClient.post(`${this.basePath}/token`, body, { bypassAuth: true });
124
+ }
90
125
  /**
91
126
  * Login user with raw data (no external authentication)
92
127
  */
@@ -174,6 +209,37 @@ class AuthService {
174
209
  }
175
210
  return response;
176
211
  }
212
+ /**
213
+ * Login as business with JWT
214
+ *
215
+ * Authenticates a user in a business context. The returned JWT contains
216
+ * the user's role within that business.
217
+ *
218
+ * @param jwt - Authentication token (passkey or Firebase JWT)
219
+ * @param options - Business authentication options
220
+ * @param options.businessId - The business ID to authenticate as
221
+ * - If user has single business membership, auto-selected
222
+ * - If user has multiple memberships without businessId, throws MULTIPLE_CONTEXT_SELECTION_REQUIRED
223
+ * @returns Session response with business context and role baked into JWT
224
+ * @throws MultipleContextSelectionRequiredError when businessId is required but not provided
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * // Auto-select if single membership
229
+ * const response = await authService.loginBusiness(jwt);
230
+ *
231
+ * // Explicit business selection
232
+ * const response = await authService.loginBusiness(jwt, { businessId: 'biz-123' });
233
+ * console.log('Business:', response.business?.displayName);
234
+ * ```
235
+ */
236
+ async loginBusiness(jwt, options) {
237
+ const response = await this.authApi.loginBusiness(jwt, options);
238
+ if (this.authProvider && response.accessToken) {
239
+ await this.storeTokens(response.accessToken, response.refreshToken, 'business', jwt);
240
+ }
241
+ return response;
242
+ }
177
243
  /**
178
244
  * Login user with raw data (no external auth)
179
245
  */
@@ -743,6 +809,21 @@ class DefaultAuthProvider {
743
809
  }
744
810
  }
745
811
 
812
+ /**
813
+ * SDK Version Information
814
+ *
815
+ * Exported for consumers who need version info.
816
+ * Used internally for X-SDK-Version header.
817
+ *
818
+ * @module @explorins/pers-sdk/core
819
+ */
820
+ /** SDK package name */
821
+ const SDK_NAME = '@explorins/pers-sdk';
822
+ /** SDK version - update on each release (should match package.json) */
823
+ const SDK_VERSION = '1.6.48';
824
+ /** Full SDK identifier for headers */
825
+ const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
826
+
746
827
  // packages/pers-sdk/src/core/pers-api-client.ts
747
828
  /**
748
829
  * PERS API Client - Platform-agnostic HTTP client with authentication
@@ -883,6 +964,7 @@ class PersApiClient {
883
964
  const backendError = web3ChainService.ErrorUtils.extractBackendErrorDetails(error);
884
965
  // If this IS a refresh request that failed with 401, don't retry to avoid infinite loop
885
966
  if (options?.isRefreshRequest) {
967
+ this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
886
968
  throw new web3ChainService.AuthenticationError(backendError.userMessage || backendError.message || 'Refresh token expired', endpoint, method, backendError.code, backendError.userMessage, backendError.title);
887
969
  }
888
970
  // For regular requests: try refresh once, then fail
@@ -900,11 +982,47 @@ class PersApiClient {
900
982
  }
901
983
  // Auth failure - let AuthService handle cleanup and notify app
902
984
  await this.authService.handleAuthFailure();
985
+ this.emitErrorEvent(backendError, errorMessage, endpoint, method, status, error);
903
986
  throw new web3ChainService.AuthenticationError(backendError.userMessage || backendError.message || 'Authentication required', endpoint, method, backendError.code, backendError.userMessage, backendError.title);
904
987
  }
905
- throw new web3ChainService.PersApiError(errorMessage, endpoint, method, status || undefined, web3ChainService.ErrorUtils.isRetryable(error));
988
+ // Extract backend error details for non-401 errors
989
+ const errorDetails = web3ChainService.ErrorUtils.extractBackendErrorDetails(error);
990
+ this.emitErrorEvent(errorDetails, errorMessage, endpoint, method, status, error);
991
+ throw new web3ChainService.PersApiError(errorMessage, endpoint, method, status || undefined, web3ChainService.ErrorUtils.isRetryable(error), errorDetails);
906
992
  }
907
993
  }
994
+ /**
995
+ * Emit error event if events emitter is available
996
+ * @internal
997
+ */
998
+ emitErrorEvent(errorDetails, errorMessage, endpoint, method, status, error) {
999
+ console.log('[PersApiClient] emitErrorEvent called', {
1000
+ hasEvents: !!this._events,
1001
+ instanceId: this._events?.instanceId ?? 'none',
1002
+ subscriberCount: this._events?.subscriberCount ?? 0,
1003
+ domain: errorDetails.domain,
1004
+ userMessage: errorDetails.userMessage || errorMessage
1005
+ });
1006
+ this._events?.emitError({
1007
+ domain: errorDetails.domain || 'external',
1008
+ type: errorDetails.code || 'API_ERROR',
1009
+ userMessage: errorDetails.userMessage || errorMessage,
1010
+ code: errorDetails.code,
1011
+ details: {
1012
+ endpoint,
1013
+ method,
1014
+ status: status || undefined,
1015
+ retryable: web3ChainService.ErrorUtils.isRetryable(error)
1016
+ }
1017
+ });
1018
+ }
1019
+ /**
1020
+ * Set event emitter for error event emission
1021
+ * @internal
1022
+ */
1023
+ setEvents(events) {
1024
+ this._events = events;
1025
+ }
908
1026
  /**
909
1027
  * Performs an authenticated GET request
910
1028
  *
@@ -950,6 +1068,7 @@ class PersApiClient {
950
1068
  async getHeaders(includeAuth = true, method, url) {
951
1069
  const headers = {
952
1070
  'Content-Type': 'application/json',
1071
+ 'X-SDK-Version': SDK_USER_AGENT,
953
1072
  };
954
1073
  if (this.mergedConfig.authProvider) {
955
1074
  let token = null;
@@ -1156,12 +1275,231 @@ function warnIfProblematicEnvironment(feature) {
1156
1275
  }
1157
1276
  }
1158
1277
 
1278
+ /**
1279
+ * PERS SDK Event Emitter
1280
+ *
1281
+ * Simplified global event stream.
1282
+ * Platform-agnostic, zero dependencies.
1283
+ *
1284
+ * @module @explorins/pers-sdk/events
1285
+ */
1286
+ /**
1287
+ * Generates a unique event ID
1288
+ */
1289
+ function generateEventId() {
1290
+ return `evt_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
1291
+ }
1292
+ /**
1293
+ * Simplified event emitter - single global stream
1294
+ *
1295
+ * All events flow through one subscription point.
1296
+ * Optional filtering at subscription level.
1297
+ *
1298
+ * @example Basic Usage
1299
+ * ```typescript
1300
+ * // Subscribe to ALL events - one handler
1301
+ * const unsubscribe = sdk.events.subscribe((event) => {
1302
+ * showNotification(event.userMessage, event.level);
1303
+ * });
1304
+ *
1305
+ * // With filter - only transaction errors
1306
+ * sdk.events.subscribe((event) => {
1307
+ * showErrorToast(event.userMessage);
1308
+ * }, { domains: ['transaction'], levels: ['error'] });
1309
+ *
1310
+ * // Multiple domains (whitelist)
1311
+ * sdk.events.subscribe((event) => {
1312
+ * showNotification(event.userMessage);
1313
+ * }, { domains: ['transaction', 'campaign', 'redemption'] });
1314
+ *
1315
+ * // Exclude domains (blacklist)
1316
+ * sdk.events.subscribe((event) => {
1317
+ * showNotification(event.userMessage);
1318
+ * }, { excludeDomains: ['validation'] });
1319
+ *
1320
+ * // Cleanup
1321
+ * unsubscribe();
1322
+ * ```
1323
+ */
1324
+ let emitterInstanceCounter = 0;
1325
+ class PersEventEmitter {
1326
+ constructor() {
1327
+ this.handlers = new Set();
1328
+ this._instanceId = ++emitterInstanceCounter;
1329
+ console.log(`[PersEventEmitter] Instance #${this._instanceId} created`);
1330
+ }
1331
+ get instanceId() {
1332
+ return this._instanceId;
1333
+ }
1334
+ /**
1335
+ * Subscribe to events
1336
+ *
1337
+ * @param handler - Callback for matching events
1338
+ * @param filter - Optional filter by domain and/or level
1339
+ * @returns Unsubscribe function
1340
+ *
1341
+ * @example All events
1342
+ * ```typescript
1343
+ * const unsub = sdk.events.subscribe((event) => {
1344
+ * console.log(`[${event.domain}] ${event.type}: ${event.userMessage}`);
1345
+ * });
1346
+ * ```
1347
+ *
1348
+ * @example Only errors
1349
+ * ```typescript
1350
+ * sdk.events.subscribe((event) => {
1351
+ * logToSentry(event);
1352
+ * }, { levels: ['error'] });
1353
+ * ```
1354
+ *
1355
+ * @example Only transaction successes
1356
+ * ```typescript
1357
+ * sdk.events.subscribe((event) => {
1358
+ * playSuccessSound();
1359
+ * confetti();
1360
+ * }, { domains: ['transaction'], levels: ['success'] });
1361
+ * ```
1362
+ */
1363
+ subscribe(handler, filter) {
1364
+ const filteredHandler = { handler, filter };
1365
+ this.handlers.add(filteredHandler);
1366
+ return () => this.handlers.delete(filteredHandler);
1367
+ }
1368
+ /**
1369
+ * Subscribe and auto-unsubscribe after first matching event
1370
+ *
1371
+ * @param handler - Callback for the first matching event
1372
+ * @param filter - Optional filter by domain and/or level
1373
+ * @returns Unsubscribe function
1374
+ */
1375
+ once(handler, filter) {
1376
+ const filteredHandler = {
1377
+ handler: (event) => {
1378
+ this.handlers.delete(filteredHandler);
1379
+ handler(event);
1380
+ },
1381
+ filter
1382
+ };
1383
+ this.handlers.add(filteredHandler);
1384
+ return () => this.handlers.delete(filteredHandler);
1385
+ }
1386
+ /**
1387
+ * Emit a success event
1388
+ *
1389
+ * Domain is restricted to business domains only (Domain type).
1390
+ *
1391
+ * @param event - Success event data (id and timestamp auto-generated)
1392
+ * @returns The complete event object
1393
+ *
1394
+ * @example
1395
+ * ```typescript
1396
+ * sdk.events.emitSuccess({
1397
+ * domain: 'transaction', // Only business domains allowed
1398
+ * type: 'CONFIRMED',
1399
+ * userMessage: 'Transaction confirmed!'
1400
+ * });
1401
+ * ```
1402
+ */
1403
+ emitSuccess(event) {
1404
+ const fullEvent = {
1405
+ ...event,
1406
+ level: 'success',
1407
+ id: generateEventId(),
1408
+ timestamp: Date.now()
1409
+ };
1410
+ this.notifyHandlers(fullEvent);
1411
+ return fullEvent;
1412
+ }
1413
+ /**
1414
+ * Emit an error event
1415
+ *
1416
+ * Domain can be any domain including technical (ErrorDomain type).
1417
+ *
1418
+ * @param event - Error event data (id and timestamp auto-generated)
1419
+ * @returns The complete event object
1420
+ *
1421
+ * @example
1422
+ * ```typescript
1423
+ * sdk.events.emitError({
1424
+ * domain: 'validation', // Technical domains allowed
1425
+ * type: 'INVALID_INPUT',
1426
+ * userMessage: 'Please check your input'
1427
+ * });
1428
+ * ```
1429
+ */
1430
+ emitError(event) {
1431
+ const fullEvent = {
1432
+ ...event,
1433
+ level: 'error',
1434
+ id: generateEventId(),
1435
+ timestamp: Date.now()
1436
+ };
1437
+ this.notifyHandlers(fullEvent);
1438
+ return fullEvent;
1439
+ }
1440
+ /**
1441
+ * Check if event matches filter
1442
+ */
1443
+ matchesFilter(event, filter) {
1444
+ // Level filtering
1445
+ if (filter.levels && !filter.levels.includes(event.level))
1446
+ return false;
1447
+ // Domain exclusion (blacklist) - checked first
1448
+ if (filter.excludeDomains && filter.excludeDomains.includes(event.domain))
1449
+ return false;
1450
+ // Domain inclusion (whitelist)
1451
+ if (filter.domains && !filter.domains.includes(event.domain))
1452
+ return false;
1453
+ return true;
1454
+ }
1455
+ /**
1456
+ * Notify all handlers of an event (applying filters)
1457
+ */
1458
+ notifyHandlers(event) {
1459
+ for (const { handler, filter } of this.handlers) {
1460
+ // Apply filter if present
1461
+ if (filter && !this.matchesFilter(event, filter)) {
1462
+ continue;
1463
+ }
1464
+ try {
1465
+ const result = handler(event);
1466
+ // Catch async handler errors too
1467
+ if (result && typeof result.catch === 'function') {
1468
+ result.catch(error => {
1469
+ console.error('[PersEventEmitter] Async handler error:', error);
1470
+ });
1471
+ }
1472
+ }
1473
+ catch (error) {
1474
+ console.error('[PersEventEmitter] Handler error:', error);
1475
+ }
1476
+ }
1477
+ }
1478
+ /**
1479
+ * Remove all handlers
1480
+ */
1481
+ clear() {
1482
+ this.handlers.clear();
1483
+ }
1484
+ /**
1485
+ * Get count of active subscriptions
1486
+ */
1487
+ get subscriberCount() {
1488
+ return this.handlers.size;
1489
+ }
1490
+ }
1491
+
1159
1492
  /**
1160
1493
  * Authentication Manager - Clean, high-level interface for authentication operations
1161
1494
  *
1162
1495
  * Provides a simplified API for common authentication tasks while maintaining
1163
1496
  * access to the underlying API client for advanced use cases.
1164
1497
  *
1498
+ * Supports the universal token endpoint (POST /auth/token):
1499
+ * - User authentication (default)
1500
+ * - Business authentication with role in JWT
1501
+ * - Admin/Tenant authentication
1502
+ *
1165
1503
  * @group Managers
1166
1504
  * @category Authentication
1167
1505
  *
@@ -1170,6 +1508,10 @@ function warnIfProblematicEnvironment(feature) {
1170
1508
  * // Login with external JWT
1171
1509
  * const authResult = await sdk.auth.loginWithToken(firebaseJWT, 'user');
1172
1510
  *
1511
+ * // Login as business (with role in JWT)
1512
+ * const bizResult = await sdk.auth.loginAsBusiness(jwt, { businessId: 'biz-123' });
1513
+ * console.log('Business:', bizResult.business?.displayName);
1514
+ *
1173
1515
  * // Check authentication
1174
1516
  * if (await sdk.auth.isAuthenticated()) {
1175
1517
  * const user = await sdk.auth.getCurrentUser();
@@ -1181,8 +1523,9 @@ function warnIfProblematicEnvironment(feature) {
1181
1523
  * ```
1182
1524
  */
1183
1525
  class AuthManager {
1184
- constructor(apiClient) {
1526
+ constructor(apiClient, events) {
1185
1527
  this.apiClient = apiClient;
1528
+ this.events = events;
1186
1529
  }
1187
1530
  /**
1188
1531
  * Login with JWT token
@@ -1211,8 +1554,78 @@ class AuthManager {
1211
1554
  const result = userType === 'admin'
1212
1555
  ? await authService.loginTenantAdmin(jwtToken)
1213
1556
  : await authService.loginUser(jwtToken);
1557
+ this.events?.emitSuccess({
1558
+ domain: 'authentication',
1559
+ type: 'LOGIN_SUCCESS',
1560
+ userMessage: 'Successfully logged in'
1561
+ });
1214
1562
  return result;
1215
1563
  }
1564
+ /**
1565
+ * Login as business with JWT token
1566
+ *
1567
+ * Authenticates a user in a business context. The returned JWT contains
1568
+ * the user's role (OWNER, ADMIN, EDITOR, VIEWER) within that business.
1569
+ *
1570
+ * **Auto-Selection Behavior:**
1571
+ * - If user has a single business membership, it's auto-selected
1572
+ * - If user has multiple memberships and no businessId is provided,
1573
+ * throws `MULTIPLE_CONTEXT_SELECTION_REQUIRED` error with available options
1574
+ *
1575
+ * @param jwtToken - JWT token from auth provider (passkey, Firebase, etc.)
1576
+ * @param options - Business authentication options
1577
+ * @param options.businessId - The business ID to authenticate as (required for multi-business users)
1578
+ * @returns Promise resolving to authentication response with business context and role in JWT
1579
+ * @throws Error with code `MULTIPLE_CONTEXT_SELECTION_REQUIRED` when businessId is needed
1580
+ *
1581
+ * @example Single Business Membership
1582
+ * ```typescript
1583
+ * // Auto-selects the user's only business
1584
+ * const result = await sdk.auth.loginAsBusiness(jwt);
1585
+ * console.log('Business:', result.business?.displayName);
1586
+ * ```
1587
+ *
1588
+ * @example Multiple Business Memberships
1589
+ * ```typescript
1590
+ * try {
1591
+ * const result = await sdk.auth.loginAsBusiness(jwt);
1592
+ * } catch (error) {
1593
+ * if (error.code === 'MULTIPLE_CONTEXT_SELECTION_REQUIRED') {
1594
+ * // Show business selector UI
1595
+ * const selectedId = await showBusinessSelector(error.availableOptions);
1596
+ * const result = await sdk.auth.loginAsBusiness(jwt, { businessId: selectedId });
1597
+ * }
1598
+ * }
1599
+ * ```
1600
+ *
1601
+ * @example Direct Business Selection
1602
+ * ```typescript
1603
+ * const result = await sdk.auth.loginAsBusiness(jwt, { businessId: 'biz-123' });
1604
+ * console.log('Authenticated as:', result.business?.displayName);
1605
+ * ```
1606
+ */
1607
+ async loginAsBusiness(jwtToken, options) {
1608
+ const authService = this.apiClient.getAuthService();
1609
+ return authService.loginBusiness(jwtToken, options);
1610
+ }
1611
+ /**
1612
+ * Get current business context
1613
+ *
1614
+ * Retrieves the current business context if authenticated as business.
1615
+ * Requires prior business authentication via {@link loginAsBusiness}.
1616
+ *
1617
+ * @returns Promise resolving to current business data
1618
+ * @throws {PersApiError} When not authenticated as business
1619
+ *
1620
+ * @example
1621
+ * ```typescript
1622
+ * const business = await sdk.auth.getCurrentBusiness();
1623
+ * console.log('Current business:', business.displayName);
1624
+ * ```
1625
+ */
1626
+ async getCurrentBusiness() {
1627
+ return this.apiClient.get('/businesses/me');
1628
+ }
1216
1629
  /**
1217
1630
  * Login with raw user data
1218
1631
  *
@@ -1414,8 +1827,9 @@ class AuthManager {
1414
1827
  * ```
1415
1828
  */
1416
1829
  class UserManager {
1417
- constructor(apiClient) {
1830
+ constructor(apiClient, events) {
1418
1831
  this.apiClient = apiClient;
1832
+ this.events = events;
1419
1833
  const userApi = new userService.UserApi(apiClient);
1420
1834
  this.userService = new userService.UserService(userApi);
1421
1835
  }
@@ -1471,7 +1885,14 @@ class UserManager {
1471
1885
  * ```
1472
1886
  */
1473
1887
  async updateCurrentUser(userData) {
1474
- return this.userService.updateRemoteUser(userData);
1888
+ const result = await this.userService.updateRemoteUser(userData);
1889
+ this.events?.emitSuccess({
1890
+ domain: 'user',
1891
+ type: 'PROFILE_UPDATED',
1892
+ userMessage: 'Profile updated successfully',
1893
+ details: { userId: result.id }
1894
+ });
1895
+ return result;
1475
1896
  }
1476
1897
  /**
1477
1898
  * Get user by unique identifier
@@ -1568,6 +1989,64 @@ class UserManager {
1568
1989
  async getAllUsers() {
1569
1990
  return this.userService.getAllRemoteUsers();
1570
1991
  }
1992
+ /**
1993
+ * Business/Admin: Create or update a user
1994
+ *
1995
+ * Creates a new user or updates an existing one. This method requires
1996
+ * business authentication (with canManageUsers permission) or admin authentication.
1997
+ *
1998
+ * @param userData - User data for creation/update
1999
+ * @returns Promise resolving to created or updated user
2000
+ * @throws {PersApiError} When not authenticated or insufficient permissions
2001
+ *
2002
+ * @example Create New User
2003
+ * ```typescript
2004
+ * // Business or Admin operation - create a new user
2005
+ * const newUser = await sdk.users.createOrUpdateUser({
2006
+ * identifierEmail: 'newuser@example.com',
2007
+ * firstName: 'John',
2008
+ * lastName: 'Doe',
2009
+ * externalId: 'external-123'
2010
+ * });
2011
+ * console.log('User created:', newUser.id);
2012
+ * ```
2013
+ *
2014
+ * @example Update Existing User
2015
+ * ```typescript
2016
+ * // If user with same identifier exists, it will be updated
2017
+ * const updated = await sdk.users.createOrUpdateUser({
2018
+ * identifierEmail: 'existing@example.com',
2019
+ * firstName: 'Updated Name'
2020
+ * });
2021
+ * ```
2022
+ */
2023
+ async createOrUpdateUser(userData) {
2024
+ return this.userService.createOrUpdateUser(userData);
2025
+ }
2026
+ /**
2027
+ * Admin: Bulk create or update users
2028
+ *
2029
+ * Creates or updates multiple users in a single operation. This method
2030
+ * requires admin authentication - business users cannot perform bulk operations.
2031
+ *
2032
+ * @param users - Array of user data for creation/update
2033
+ * @returns Promise resolving to array of created/updated users
2034
+ * @throws {PersApiError} When not authenticated as admin
2035
+ *
2036
+ * @example Bulk User Creation
2037
+ * ```typescript
2038
+ * // Admin-only operation - bulk create users
2039
+ * const users = await sdk.users.createOrUpdateUsers([
2040
+ * { identifierEmail: 'user1@example.com', firstName: 'User', lastName: 'One' },
2041
+ * { identifierEmail: 'user2@example.com', firstName: 'User', lastName: 'Two' },
2042
+ * { identifierEmail: 'user3@example.com', firstName: 'User', lastName: 'Three' }
2043
+ * ]);
2044
+ * console.log(`Created ${users.length} users`);
2045
+ * ```
2046
+ */
2047
+ async createOrUpdateUsers(users) {
2048
+ return this.userService.createOrUpdateUsers(users);
2049
+ }
1571
2050
  /**
1572
2051
  * Admin: Update user data
1573
2052
  *
@@ -2072,10 +2551,14 @@ class TokenManager {
2072
2551
  * ```
2073
2552
  */
2074
2553
  class BusinessManager {
2075
- constructor(apiClient) {
2554
+ constructor(apiClient, events) {
2076
2555
  this.apiClient = apiClient;
2077
- this.businessApi = new businessService.BusinessApi(apiClient);
2078
- this.businessService = new businessService.BusinessService(this.businessApi);
2556
+ this.events = events;
2557
+ this.businessApi = new business.BusinessApi(apiClient);
2558
+ this.businessService = new business.BusinessService(this.businessApi);
2559
+ this.membershipApi = new business.BusinessMembershipApi(apiClient);
2560
+ this.membershipService = new business.BusinessMembershipService(this.membershipApi);
2561
+ this.userApi = new userService.UserApi(apiClient);
2079
2562
  }
2080
2563
  /**
2081
2564
  * Get all active businesses
@@ -2155,7 +2638,7 @@ class BusinessManager {
2155
2638
  *
2156
2639
  * // Use for transaction verification
2157
2640
  * if (business.isActive) {
2158
- * console.log('Verified business partner');
2641
+ * console.log('Verified business partner');
2159
2642
  * }
2160
2643
  * ```
2161
2644
  */
@@ -2284,7 +2767,14 @@ class BusinessManager {
2284
2767
  * ```
2285
2768
  */
2286
2769
  async createBusiness(displayName) {
2287
- return this.businessService.createBusinessByDisplayName(displayName);
2770
+ const result = await this.businessService.createBusinessByDisplayName(displayName);
2771
+ this.events?.emitSuccess({
2772
+ domain: 'business',
2773
+ type: 'BUSINESS_CREATED',
2774
+ userMessage: 'Business created successfully',
2775
+ details: { businessId: result.id, displayName: result.displayName }
2776
+ });
2777
+ return result;
2288
2778
  }
2289
2779
  /**
2290
2780
  * Admin: Update business
@@ -2312,7 +2802,14 @@ class BusinessManager {
2312
2802
  * ```
2313
2803
  */
2314
2804
  async updateBusiness(businessId, businessData) {
2315
- return this.businessService.updateBusiness(businessId, businessData);
2805
+ const result = await this.businessService.updateBusiness(businessId, businessData);
2806
+ this.events?.emitSuccess({
2807
+ domain: 'business',
2808
+ type: 'BUSINESS_UPDATED',
2809
+ userMessage: 'Business updated successfully',
2810
+ details: { businessId: result.id, displayName: result.displayName }
2811
+ });
2812
+ return result;
2316
2813
  }
2317
2814
  /**
2318
2815
  * Admin: Toggle business active status
@@ -2370,6 +2867,209 @@ class BusinessManager {
2370
2867
  getBusinessService() {
2371
2868
  return this.businessService;
2372
2869
  }
2870
+ // ==========================================
2871
+ // BUSINESS MEMBERSHIP MANAGEMENT
2872
+ // ==========================================
2873
+ /**
2874
+ * Get all members of a business
2875
+ *
2876
+ * Retrieves all users who have access to the specified business with their roles.
2877
+ * Any member of the business can view the member list.
2878
+ *
2879
+ * @param businessId - The business UUID
2880
+ * @returns Promise resolving to array of business memberships
2881
+ * @throws {PersApiError} 401 - Not authenticated
2882
+ * @throws {PersApiError} 403 - Not a member of this business
2883
+ *
2884
+ * @example
2885
+ * ```typescript
2886
+ * const members = await sdk.business.getMembers('business-123');
2887
+ *
2888
+ * console.log('Business Members:');
2889
+ * members.forEach(member => {
2890
+ * console.log(`- User ${member.userId}: ${member.role}`);
2891
+ * });
2892
+ *
2893
+ * // Count members by role
2894
+ * const admins = members.filter(m => m.role === 'ADMIN' || m.role === 'OWNER');
2895
+ * console.log(`Administrators: ${admins.length}`);
2896
+ * ```
2897
+ */
2898
+ async getMembers(businessId) {
2899
+ return this.membershipService.getMembers(businessId);
2900
+ }
2901
+ /**
2902
+ * Get members filtered by role
2903
+ *
2904
+ * @param businessId - The business UUID
2905
+ * @param role - The role to filter by
2906
+ * @returns Promise resolving to array of memberships with the specified role
2907
+ *
2908
+ * @example
2909
+ * ```typescript
2910
+ * const owners = await sdk.business.getMembersByRole('business-123', 'OWNER');
2911
+ * console.log(`Business has ${owners.length} owner(s)`);
2912
+ * ```
2913
+ */
2914
+ async getMembersByRole(businessId, role) {
2915
+ return this.membershipService.getMembersByRole(businessId, role);
2916
+ }
2917
+ /**
2918
+ * Add a new member to a business
2919
+ *
2920
+ * Adds a user as a member of the business with the specified role.
2921
+ * Requires ADMIN role or higher.
2922
+ *
2923
+ * @param businessId - The business UUID
2924
+ * @param userId - The user UUID to add
2925
+ * @param role - The role to assign (defaults to VIEWER)
2926
+ * @returns Promise resolving to the created membership
2927
+ * @throws {PersApiError} 401 - Not authenticated
2928
+ * @throws {PersApiError} 403 - Insufficient role (requires ADMIN)
2929
+ * @throws {PersApiError} 404 - User not found
2930
+ * @throws {PersApiError} 409 - User is already a member
2931
+ *
2932
+ * @example
2933
+ * ```typescript
2934
+ * // Add a new editor to the business
2935
+ * const newMember = await sdk.business.addMember(
2936
+ * 'business-123',
2937
+ * 'user-456',
2938
+ * 'EDITOR'
2939
+ * );
2940
+ *
2941
+ * console.log(`Added ${newMember.userId} as ${newMember.role}`);
2942
+ * ```
2943
+ */
2944
+ async addMember(businessId, userId, role = persShared.MembershipRole.VIEWER) {
2945
+ return this.membershipService.addMember(businessId, userId, role);
2946
+ }
2947
+ /**
2948
+ * Update a member's role
2949
+ *
2950
+ * Changes the role of an existing business member.
2951
+ * Requires ADMIN role or higher. Cannot demote the last OWNER.
2952
+ * Can only assign roles up to your own level (ADMIN cannot create OWNER).
2953
+ *
2954
+ * @param businessId - The business UUID
2955
+ * @param userId - The user UUID to update
2956
+ * @param newRole - The new role to assign
2957
+ * @returns Promise resolving to the updated membership
2958
+ * @throws {PersApiError} 401 - Not authenticated
2959
+ * @throws {PersApiError} 403 - Insufficient role (requires ADMIN)
2960
+ * @throws {PersApiError} 404 - Membership not found
2961
+ * @throws {PersApiError} 400 - Cannot demote last OWNER
2962
+ *
2963
+ * @example
2964
+ * ```typescript
2965
+ * // Promote an editor to admin
2966
+ * const updated = await sdk.business.updateMemberRole(
2967
+ * 'business-123',
2968
+ * 'user-456',
2969
+ * 'ADMIN'
2970
+ * );
2971
+ *
2972
+ * console.log(`${updated.userId} is now ${updated.role}`);
2973
+ * ```
2974
+ */
2975
+ async updateMemberRole(businessId, userId, newRole) {
2976
+ const result = await this.membershipService.updateMemberRole(businessId, userId, newRole);
2977
+ this.events?.emitSuccess({
2978
+ domain: 'business',
2979
+ type: 'MEMBERSHIP_UPDATED',
2980
+ userMessage: 'Membership role updated',
2981
+ details: { businessId, userId, role: newRole }
2982
+ });
2983
+ return result;
2984
+ }
2985
+ /**
2986
+ * Remove a member from a business
2987
+ *
2988
+ * Removes a user's access to the business.
2989
+ * Requires ADMIN role or higher. Cannot remove the last OWNER.
2990
+ *
2991
+ * @param businessId - The business UUID
2992
+ * @param userId - The user UUID to remove
2993
+ * @returns Promise resolving to success confirmation
2994
+ * @throws {PersApiError} 401 - Not authenticated
2995
+ * @throws {PersApiError} 403 - Insufficient role (requires ADMIN)
2996
+ * @throws {PersApiError} 404 - Membership not found
2997
+ * @throws {PersApiError} 400 - Cannot remove last OWNER
2998
+ *
2999
+ * @example
3000
+ * ```typescript
3001
+ * const result = await sdk.business.removeMember('business-123', 'user-456');
3002
+ *
3003
+ * if (result.success) {
3004
+ * console.log('Member removed successfully');
3005
+ * }
3006
+ * ```
3007
+ */
3008
+ async removeMember(businessId, userId) {
3009
+ return this.membershipService.removeMember(businessId, userId);
3010
+ }
3011
+ /**
3012
+ * Get permission flags for a role
3013
+ *
3014
+ * Returns an object with boolean flags indicating what actions
3015
+ * a user with the given role can perform. Useful for UI rendering.
3016
+ *
3017
+ * @param role - The membership role (or null if not a member)
3018
+ * @returns Object with permission flags
3019
+ *
3020
+ * @example
3021
+ * ```typescript
3022
+ * const permissions = sdk.business.getPermissions('EDITOR');
3023
+ * // { canViewMembers: true, canManageMembers: false, canEditContent: true, canDeleteBusiness: false }
3024
+ *
3025
+ * if (permissions.canManageMembers) {
3026
+ * showAddMemberButton();
3027
+ * }
3028
+ * ```
3029
+ */
3030
+ getPermissions(role) {
3031
+ return this.membershipService.getPermissions(role);
3032
+ }
3033
+ /**
3034
+ * Add a member to a business by email address
3035
+ *
3036
+ * Convenience method that creates or retrieves a user by email and adds them
3037
+ * as a member of the business. If the user doesn't exist, they will be created.
3038
+ * Requires ADMIN role or higher.
3039
+ *
3040
+ * @param businessId - The business UUID
3041
+ * @param email - The email address of the user to add
3042
+ * @param role - The role to assign (defaults to VIEWER)
3043
+ * @returns Promise resolving to the created membership
3044
+ * @throws {PersApiError} 401 - Not authenticated
3045
+ * @throws {PersApiError} 403 - Insufficient role (requires ADMIN)
3046
+ * @throws {PersApiError} 409 - User is already a member
3047
+ *
3048
+ * @example
3049
+ * ```typescript
3050
+ * // Add a new editor by email - user is created if they don't exist
3051
+ * const membership = await sdk.business.addMemberByEmail(
3052
+ * 'business-123',
3053
+ * 'newuser@example.com',
3054
+ * 'EDITOR'
3055
+ * );
3056
+ *
3057
+ * console.log(`Added user ${membership.userId} as ${membership.role}`);
3058
+ * ```
3059
+ */
3060
+ async addMemberByEmail(businessId, email, role = persShared.MembershipRole.VIEWER) {
3061
+ // POST /users is an upsert - creates user if not exists, returns existing if found
3062
+ const user = await this.userApi.createOrUpdateUser({ email });
3063
+ return this.membershipService.addMember(businessId, user.id, role);
3064
+ }
3065
+ /**
3066
+ * Get the membership service for advanced operations
3067
+ *
3068
+ * @returns BusinessMembershipService instance
3069
+ */
3070
+ getMembershipService() {
3071
+ return this.membershipService;
3072
+ }
2373
3073
  }
2374
3074
 
2375
3075
  /**
@@ -2385,9 +3085,9 @@ class BusinessManager {
2385
3085
  *
2386
3086
  * @example Basic Campaign Operations
2387
3087
  * ```typescript
2388
- * // Get all available campaigns for users
2389
- * const campaigns = await sdk.campaigns.getActiveCampaigns();
2390
- * console.log(`${campaigns.length} active campaigns available`);
3088
+ * // Get all available campaigns (paginated)
3089
+ * const result = await sdk.campaigns.getCampaigns({ page: 1, limit: 20 });
3090
+ * console.log(`${result.data.length} of ${result.total} campaigns`);
2391
3091
  *
2392
3092
  * // Get specific campaign details
2393
3093
  * const campaign = await sdk.campaigns.getCampaignById('summer-promo-2024');
@@ -2413,7 +3113,8 @@ class BusinessManager {
2413
3113
  * });
2414
3114
  *
2415
3115
  * // Check if user can claim specific campaign
2416
- * const eligibleCampaigns = campaigns.filter(c =>
3116
+ * const campaigns = await sdk.campaigns.getCampaigns({ active: true });
3117
+ * const eligibleCampaigns = campaigns.data.filter(c =>
2417
3118
  * !userClaims.some(claim => claim.campaignId === c.id)
2418
3119
  * );
2419
3120
  * ```
@@ -2438,47 +3139,12 @@ class BusinessManager {
2438
3139
  * ```
2439
3140
  */
2440
3141
  class CampaignManager {
2441
- constructor(apiClient) {
3142
+ constructor(apiClient, events) {
2442
3143
  this.apiClient = apiClient;
3144
+ this.events = events;
2443
3145
  const campaignApi = new campaignService.CampaignApi(apiClient);
2444
3146
  this.campaignService = new campaignService.CampaignService(campaignApi);
2445
3147
  }
2446
- /**
2447
- * Get all active campaigns
2448
- *
2449
- * Retrieves all currently active campaigns that users can discover and claim.
2450
- * Active campaigns are live promotional offers with valid date ranges and
2451
- * available rewards. Includes campaign details, eligibility requirements, and reward information.
2452
- *
2453
- * @returns Promise resolving to array of active campaign data
2454
- *
2455
- * @example
2456
- * ```typescript
2457
- * const activeCampaigns = await sdk.campaigns.getActiveCampaigns();
2458
- *
2459
- * console.log('Available Campaigns:');
2460
- * activeCampaigns.forEach(campaign => {
2461
- * console.log(`\n📢 ${campaign.title}`);
2462
- * console.log(` ${campaign.description}`);
2463
- * console.log(` Valid: ${campaign.startDate} to ${campaign.endDate}`);
2464
- * console.log(` Rewards: ${campaign.tokenUnits?.length || 0} types`);
2465
- *
2466
- * if (campaign.businessEngagements?.length) {
2467
- * console.log(` Partner businesses: ${campaign.businessEngagements.length}`);
2468
- * }
2469
- * });
2470
- *
2471
- * // Filter campaigns by type or business
2472
- * const hotelCampaigns = activeCampaigns.filter(c =>
2473
- * c.businessEngagements?.some(be =>
2474
- * be.business?.businessType?.name?.includes('Hotel')
2475
- * )
2476
- * );
2477
- * ```
2478
- */
2479
- async getActiveCampaigns() {
2480
- return this.campaignService.getActiveCampaigns();
2481
- }
2482
3148
  /**
2483
3149
  * Get campaign by ID
2484
3150
  *
@@ -2576,7 +3242,14 @@ class CampaignManager {
2576
3242
  * ```
2577
3243
  */
2578
3244
  async claimCampaign(claimRequest) {
2579
- return this.campaignService.claimCampaign(claimRequest);
3245
+ const result = await this.campaignService.claimCampaign(claimRequest);
3246
+ this.events?.emitSuccess({
3247
+ domain: 'campaign',
3248
+ type: 'CLAIM_SUCCESS',
3249
+ userMessage: 'Campaign reward claimed successfully',
3250
+ details: { campaignId: claimRequest.campaignId }
3251
+ });
3252
+ return result;
2580
3253
  }
2581
3254
  /**
2582
3255
  * Get user's campaign claims
@@ -2618,40 +3291,46 @@ class CampaignManager {
2618
3291
  return this.campaignService.getClaimsForLoggedUser();
2619
3292
  }
2620
3293
  /**
2621
- * Admin: Get all campaigns
3294
+ * Get campaigns with pagination support
2622
3295
  *
2623
- * Retrieves all campaigns in the system regardless of status. This operation
2624
- * requires administrator privileges and is used for comprehensive campaign
2625
- * management, reporting, and lifecycle operations.
3296
+ * Returns campaigns with pagination metadata for efficient data loading.
3297
+ * Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all.
2626
3298
  *
2627
- * @param active - Optional filter to show only active or inactive campaigns
2628
- * @returns Promise resolving to array of campaigns
2629
- * @throws {PersApiError} When not authenticated as administrator
3299
+ * @param options - Pagination and filter options
3300
+ * @param options.active - Filter by active status (true/false/undefined for all)
3301
+ * @param options.businessId - Filter by business engagement
3302
+ * @param options.page - Page number (1-based, default: 1)
3303
+ * @param options.limit - Items per page (default: 50)
3304
+ * @param options.sortBy - Sort field ('name', 'createdAt', 'startDate')
3305
+ * @param options.sortOrder - Sort direction ('ASC' or 'DESC')
3306
+ * @returns Promise resolving to paginated campaigns with metadata
2630
3307
  *
2631
3308
  * @example
2632
3309
  * ```typescript
2633
- * // Admin operation - get campaign overview
2634
- * const allCampaigns = await sdk.campaigns.getAllCampaigns();
2635
- * const activeCampaigns = await sdk.campaigns.getAllCampaigns(true);
2636
- * const inactiveCampaigns = await sdk.campaigns.getAllCampaigns(false);
2637
- *
2638
- * console.log('📊 Campaign Statistics:');
2639
- * console.log(`Total campaigns: ${allCampaigns.length}`);
2640
- * console.log(`Active campaigns: ${activeCampaigns.length}`);
2641
- * console.log(`Inactive campaigns: ${inactiveCampaigns.length}`);
2642
- *
2643
- * // Generate campaign performance report
2644
- * const campaignReport = allCampaigns.map(campaign => ({
2645
- * title: campaign.title,
2646
- * status: campaign.isActive ? 'Active' : 'Inactive',
2647
- * period: `${campaign.startDate} to ${campaign.endDate}`,
2648
- * businesses: campaign.businessEngagements?.length || 0,
2649
- * rewardTypes: campaign.tokenUnits?.length || 0
2650
- * }));
3310
+ * // Get first page of campaigns
3311
+ * const result = await sdk.campaigns.getCampaigns({ page: 1, limit: 10 });
3312
+ * console.log(`Showing ${result.data.length} of ${result.total} campaigns`);
3313
+ * console.log(`Has more pages: ${result.hasMore}`);
3314
+ *
3315
+ * // Get campaigns for a specific business
3316
+ * const businessCampaigns = await sdk.campaigns.getCampaigns({
3317
+ * businessId: 'business-123',
3318
+ * page: 1,
3319
+ * limit: 20
3320
+ * });
3321
+ *
3322
+ * // Get active campaigns sorted by creation date
3323
+ * const activeCampaigns = await sdk.campaigns.getCampaigns({
3324
+ * active: true,
3325
+ * sortBy: 'createdAt',
3326
+ * sortOrder: 'DESC',
3327
+ * page: 1,
3328
+ * limit: 25
3329
+ * });
2651
3330
  * ```
2652
3331
  */
2653
- async getAllCampaigns(active) {
2654
- return this.campaignService.getCampaigns(active);
3332
+ async getCampaigns(options) {
3333
+ return this.campaignService.getCampaigns(options);
2655
3334
  }
2656
3335
  /**
2657
3336
  * Admin: Create new campaign
@@ -3170,8 +3849,9 @@ class CampaignManager {
3170
3849
  * ```
3171
3850
  */
3172
3851
  class RedemptionManager {
3173
- constructor(apiClient) {
3852
+ constructor(apiClient, events) {
3174
3853
  this.apiClient = apiClient;
3854
+ this.events = events;
3175
3855
  const redemptionApi = new redemptionService.RedemptionApi(apiClient);
3176
3856
  this.redemptionService = new redemptionService.RedemptionService(redemptionApi);
3177
3857
  }
@@ -3335,7 +4015,14 @@ class RedemptionManager {
3335
4015
  * ```
3336
4016
  */
3337
4017
  async redeem(redemptionId) {
3338
- return this.redemptionService.redeemRedemption(redemptionId);
4018
+ const result = await this.redemptionService.redeemRedemption(redemptionId);
4019
+ this.events?.emitSuccess({
4020
+ domain: 'redemption',
4021
+ type: 'REDEEM_SUCCESS',
4022
+ userMessage: 'Reward redeemed successfully',
4023
+ details: { redemptionId }
4024
+ });
4025
+ return result;
3339
4026
  }
3340
4027
  /**
3341
4028
  * Get user's redemption history
@@ -3699,8 +4386,9 @@ class RedemptionManager {
3699
4386
  * ```
3700
4387
  */
3701
4388
  class TransactionManager {
3702
- constructor(apiClient) {
4389
+ constructor(apiClient, events) {
3703
4390
  this.apiClient = apiClient;
4391
+ this.events = events;
3704
4392
  const transactionApi = new transactionService.TransactionApi(apiClient);
3705
4393
  this.transactionService = new transactionService.TransactionService(transactionApi);
3706
4394
  }
@@ -3836,7 +4524,14 @@ class TransactionManager {
3836
4524
  * ```
3837
4525
  */
3838
4526
  async createTransaction(transactionData) {
3839
- return this.transactionService.createTransaction(transactionData);
4527
+ const result = await this.transactionService.createTransaction(transactionData);
4528
+ this.events?.emitSuccess({
4529
+ domain: 'transaction',
4530
+ type: 'TRANSACTION_CREATED',
4531
+ userMessage: 'Transaction created successfully',
4532
+ details: { transactionId: result.transaction?.id }
4533
+ });
4534
+ return result;
3840
4535
  }
3841
4536
  /**
3842
4537
  * Get user's transaction history
@@ -3853,7 +4548,7 @@ class TransactionManager {
3853
4548
  * ```typescript
3854
4549
  * const allTransactions = await sdk.transactions.getUserTransactionHistory('ALL');
3855
4550
  *
3856
- * console.log(`📜 Transaction History (${allTransactions.length} transactions):`);
4551
+ * console.log(` Transaction History (${allTransactions.length} transactions):`);
3857
4552
  *
3858
4553
  * allTransactions.forEach((transaction, index) => {
3859
4554
  * const date = new Date(transaction.createdAt).toLocaleDateString();
@@ -4141,7 +4836,14 @@ class TransactionManager {
4141
4836
  * @returns Promise resolving to the submission result
4142
4837
  */
4143
4838
  async submitSignedTransaction(signedTxData) {
4144
- return this.transactionService.submitSignedTransaction(signedTxData);
4839
+ const result = await this.transactionService.submitSignedTransaction(signedTxData);
4840
+ this.events?.emitSuccess({
4841
+ domain: 'transaction',
4842
+ type: 'TRANSACTION_SUBMITTED',
4843
+ userMessage: 'Transaction submitted successfully',
4844
+ details: { transactionId: result.transaction?.id }
4845
+ });
4846
+ return result;
4145
4847
  }
4146
4848
  /**
4147
4849
  * Query transactions by sender
@@ -5837,6 +6539,13 @@ class DonationManager {
5837
6539
  * ```
5838
6540
  */
5839
6541
  class Web3Manager {
6542
+ // TODO: Add PersEventEmitter support for blockchain events
6543
+ // When ready, add:
6544
+ // 1. constructor param: private events?: PersEventEmitter
6545
+ // 2. Subscribe to contract events (Transfer, Approval, etc.)
6546
+ // 3. Emit via: this.events?.emitSuccess({ domain: 'web3', type: 'NFT_TRANSFERRED', ... })
6547
+ // 4. Filter to only user's address (not all transfers)
6548
+ // 5. Add cleanup for event listeners on SDK destroy
5840
6549
  constructor(apiClient) {
5841
6550
  this.apiClient = apiClient;
5842
6551
  // Initialize Web3 Chain service
@@ -6160,15 +6869,18 @@ class PersSDK {
6160
6869
  */
6161
6870
  constructor(httpClient, config) {
6162
6871
  this.apiClient = new PersApiClient(httpClient, config);
6163
- // Initialize domain managers
6164
- this._auth = new AuthManager(this.apiClient);
6165
- this._users = new UserManager(this.apiClient);
6872
+ // Initialize event emitter and wire to API client for error events
6873
+ this._events = new PersEventEmitter();
6874
+ this.apiClient.setEvents(this._events);
6875
+ // Initialize domain managers (pass events to managers that emit success events)
6876
+ this._auth = new AuthManager(this.apiClient, this._events);
6877
+ this._users = new UserManager(this.apiClient, this._events);
6166
6878
  this._userStatus = new UserStatusManager(this.apiClient);
6167
6879
  this._tokens = new TokenManager(this.apiClient);
6168
- this._businesses = new BusinessManager(this.apiClient);
6169
- this._campaigns = new CampaignManager(this.apiClient);
6170
- this._redemptions = new RedemptionManager(this.apiClient);
6171
- this._transactions = new TransactionManager(this.apiClient);
6880
+ this._businesses = new BusinessManager(this.apiClient, this._events);
6881
+ this._campaigns = new CampaignManager(this.apiClient, this._events);
6882
+ this._redemptions = new RedemptionManager(this.apiClient, this._events);
6883
+ this._transactions = new TransactionManager(this.apiClient, this._events);
6172
6884
  this._purchases = new PurchaseManager(this.apiClient);
6173
6885
  this._files = new FileManager(this.apiClient);
6174
6886
  this._tenants = new TenantManager(this.apiClient);
@@ -6177,6 +6889,55 @@ class PersSDK {
6177
6889
  this._donations = new DonationManager(this.apiClient);
6178
6890
  this._web3 = new Web3Manager(this.apiClient);
6179
6891
  }
6892
+ /**
6893
+ * Event emitter - Subscribe to SDK-wide events
6894
+ *
6895
+ * Provides a platform-agnostic event system for subscribing to transaction,
6896
+ * authentication, campaign, and system events. Use this to display
6897
+ * notifications, update UI, or trigger side effects in your application.
6898
+ *
6899
+ * All events have a `userMessage` field ready for display.
6900
+ *
6901
+ * @returns PersEventEmitter instance
6902
+ *
6903
+ * @example Basic Usage - Show All Notifications
6904
+ * ```typescript
6905
+ * const unsubscribe = sdk.events.subscribe((event) => {
6906
+ * // userMessage is always present and UI-ready
6907
+ * showNotification(event.userMessage, event.level);
6908
+ * });
6909
+ *
6910
+ * // Later: cleanup
6911
+ * unsubscribe();
6912
+ * ```
6913
+ *
6914
+ * @example Filter by Domain and Level
6915
+ * ```typescript
6916
+ * sdk.events.subscribe((event) => {
6917
+ * if (event.level === 'success' && event.domain === 'transaction') {
6918
+ * playSuccessSound();
6919
+ * confetti();
6920
+ * }
6921
+ *
6922
+ * if (event.level === 'error') {
6923
+ * logToSentry(event);
6924
+ * }
6925
+ * });
6926
+ * ```
6927
+ *
6928
+ * @example One-time Event
6929
+ * ```typescript
6930
+ * // Auto-unsubscribe after first event
6931
+ * sdk.events.once((event) => {
6932
+ * console.log('First event received:', event.type);
6933
+ * });
6934
+ * ```
6935
+ *
6936
+ * @see {@link PersEventEmitter} for detailed documentation
6937
+ */
6938
+ get events() {
6939
+ return this._events;
6940
+ }
6180
6941
  /**
6181
6942
  * Authentication manager - High-level authentication operations
6182
6943
  *
@@ -6476,9 +7237,13 @@ exports.IndexedDBTokenStorage = IndexedDBTokenStorage;
6476
7237
  exports.LocalStorageTokenStorage = LocalStorageTokenStorage;
6477
7238
  exports.MemoryTokenStorage = MemoryTokenStorage;
6478
7239
  exports.PersApiClient = PersApiClient;
7240
+ exports.PersEventEmitter = PersEventEmitter;
6479
7241
  exports.PersSDK = PersSDK;
6480
7242
  exports.PurchaseManager = PurchaseManager;
6481
7243
  exports.RedemptionManager = RedemptionManager;
7244
+ exports.SDK_NAME = SDK_NAME;
7245
+ exports.SDK_USER_AGENT = SDK_USER_AGENT;
7246
+ exports.SDK_VERSION = SDK_VERSION;
6482
7247
  exports.TenantManager = TenantManager;
6483
7248
  exports.TokenManager = TokenManager;
6484
7249
  exports.TransactionManager = TransactionManager;
@@ -6492,4 +7257,4 @@ exports.detectEnvironment = detectEnvironment;
6492
7257
  exports.environment = environment;
6493
7258
  exports.mergeWithDefaults = mergeWithDefaults;
6494
7259
  exports.warnIfProblematicEnvironment = warnIfProblematicEnvironment;
6495
- //# sourceMappingURL=pers-sdk-DzS7mkm7.cjs.map
7260
+ //# sourceMappingURL=pers-sdk-DULFjOW2.cjs.map