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