@explorins/pers-sdk 1.6.43 → 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.
- package/README.md +185 -0
- package/dist/analytics.cjs +12 -0
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.js +1 -1
- package/dist/business/api/business-membership-api.d.ts +186 -0
- package/dist/business/api/business-membership-api.d.ts.map +1 -0
- package/dist/business/index.d.ts +2 -0
- package/dist/business/index.d.ts.map +1 -1
- package/dist/business/services/business-membership-service.d.ts +186 -0
- package/dist/business/services/business-membership-service.d.ts.map +1 -0
- package/dist/business.cjs +727 -9
- package/dist/business.cjs.map +1 -1
- package/dist/business.js +711 -2
- package/dist/business.js.map +1 -1
- package/dist/campaign/api/campaign-api.d.ts +7 -10
- package/dist/campaign/api/campaign-api.d.ts.map +1 -1
- package/dist/campaign/services/campaign-service.d.ts +18 -7
- package/dist/campaign/services/campaign-service.d.ts.map +1 -1
- package/dist/campaign.cjs +13 -1
- package/dist/campaign.cjs.map +1 -1
- package/dist/campaign.js +2 -2
- package/dist/chunks/{campaign-service-CJPXgFBe.js → campaign-service-CKwkiOLx.js} +29 -39
- package/dist/chunks/campaign-service-CKwkiOLx.js.map +1 -0
- package/dist/chunks/{campaign-service-B1tQMNqA.cjs → campaign-service-D-v9ZlUB.cjs} +29 -39
- package/dist/chunks/campaign-service-D-v9ZlUB.cjs.map +1 -0
- package/dist/chunks/{pers-sdk-XbARV58g.cjs → pers-sdk-DULFjOW2.cjs} +923 -146
- package/dist/chunks/pers-sdk-DULFjOW2.cjs.map +1 -0
- package/dist/chunks/{pers-sdk-Nu2KgiJf.js → pers-sdk-VmeBqUEP.js} +919 -146
- package/dist/chunks/pers-sdk-VmeBqUEP.js.map +1 -0
- package/dist/chunks/transaction-request.builder-DltmruUC.js +296 -0
- package/dist/chunks/transaction-request.builder-DltmruUC.js.map +1 -0
- package/dist/chunks/transaction-request.builder-DrqTWcyC.cjs +303 -0
- package/dist/chunks/transaction-request.builder-DrqTWcyC.cjs.map +1 -0
- package/dist/chunks/{user-service-D1Rn4U8u.cjs → user-service-B89wBOV1.cjs} +60 -10
- package/dist/chunks/user-service-B89wBOV1.cjs.map +1 -0
- package/dist/chunks/{user-service-D6mTa_WZ.js → user-service-doT5vsBD.js} +60 -10
- package/dist/chunks/user-service-doT5vsBD.js.map +1 -0
- package/dist/chunks/{web3-chain-service-BLFxB5TA.cjs → web3-chain-service-6vsVHPjl.cjs} +116 -16
- package/dist/chunks/web3-chain-service-6vsVHPjl.cjs.map +1 -0
- package/dist/chunks/{web3-chain-service-JRSwxr-s.js → web3-chain-service-BcUeeujC.js} +111 -17
- package/dist/chunks/web3-chain-service-BcUeeujC.js.map +1 -0
- package/dist/core/auth/api/auth-api.d.ts +35 -0
- package/dist/core/auth/api/auth-api.d.ts.map +1 -1
- package/dist/core/auth/auth-provider.interface.d.ts +7 -1
- package/dist/core/auth/auth-provider.interface.d.ts.map +1 -1
- package/dist/core/auth/services/auth-service.d.ts +26 -1
- package/dist/core/auth/services/auth-service.d.ts.map +1 -1
- package/dist/core/auth/token-storage.d.ts +3 -2
- package/dist/core/auth/token-storage.d.ts.map +1 -1
- package/dist/core/errors/index.d.ts +75 -6
- package/dist/core/errors/index.d.ts.map +1 -1
- package/dist/core/events/event-emitter.d.ts +106 -0
- package/dist/core/events/event-emitter.d.ts.map +1 -0
- package/dist/core/events/event-types.d.ts +127 -0
- package/dist/core/events/event-types.d.ts.map +1 -0
- package/dist/core/events/index.d.ts +22 -0
- package/dist/core/events/index.d.ts.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/pers-api-client.d.ts +12 -0
- package/dist/core/pers-api-client.d.ts.map +1 -1
- package/dist/core/version.d.ts +15 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core.cjs +19 -6
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +6 -6
- package/dist/donation.cjs +12 -0
- package/dist/donation.cjs.map +1 -1
- package/dist/donation.js +1 -1
- package/dist/index.cjs +43 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/managers/auth-manager.d.ts +77 -4
- package/dist/managers/auth-manager.d.ts.map +1 -1
- package/dist/managers/business-manager.d.ts +192 -4
- package/dist/managers/business-manager.d.ts.map +1 -1
- package/dist/managers/campaign-manager.d.ts +48 -65
- package/dist/managers/campaign-manager.d.ts.map +1 -1
- package/dist/managers/index.d.ts +1 -1
- package/dist/managers/index.d.ts.map +1 -1
- package/dist/managers/redemption-manager.d.ts +3 -1
- package/dist/managers/redemption-manager.d.ts.map +1 -1
- package/dist/managers/transaction-manager.d.ts +4 -2
- package/dist/managers/transaction-manager.d.ts.map +1 -1
- package/dist/managers/user-manager.d.ts +57 -1
- package/dist/managers/user-manager.d.ts.map +1 -1
- package/dist/managers/web3-manager.d.ts +63 -64
- package/dist/managers/web3-manager.d.ts.map +1 -1
- package/dist/package.json +2 -2
- package/dist/payment.cjs +12 -0
- package/dist/payment.cjs.map +1 -1
- package/dist/payment.js +1 -1
- package/dist/pers-sdk.d.ts +49 -0
- package/dist/pers-sdk.d.ts.map +1 -1
- package/dist/redemption.cjs +12 -0
- package/dist/redemption.cjs.map +1 -1
- package/dist/redemption.js +1 -1
- package/dist/shared/interfaces/pers-shared-lib.interfaces.d.ts +2 -1
- package/dist/shared/interfaces/pers-shared-lib.interfaces.d.ts.map +1 -1
- package/dist/tenant.cjs +12 -0
- package/dist/tenant.cjs.map +1 -1
- package/dist/tenant.js +1 -1
- package/dist/token.cjs +12 -0
- package/dist/token.cjs.map +1 -1
- package/dist/token.js +1 -1
- package/dist/transaction/models/index.d.ts +2 -0
- package/dist/transaction/models/index.d.ts.map +1 -1
- package/dist/transaction/models/transaction-request.builder.d.ts +256 -0
- package/dist/transaction/models/transaction-request.builder.d.ts.map +1 -0
- package/dist/transaction.cjs +7 -0
- package/dist/transaction.cjs.map +1 -1
- package/dist/transaction.js +1 -0
- package/dist/transaction.js.map +1 -1
- package/dist/user/api/user-api.d.ts +28 -7
- package/dist/user/api/user-api.d.ts.map +1 -1
- package/dist/user/services/user-service.d.ts +21 -0
- package/dist/user/services/user-service.d.ts.map +1 -1
- package/dist/user-status.cjs +12 -0
- package/dist/user-status.cjs.map +1 -1
- package/dist/user-status.js +1 -1
- package/dist/user.cjs +13 -1
- package/dist/user.cjs.map +1 -1
- package/dist/user.js +2 -2
- package/dist/web3-chain.cjs +13 -1
- package/dist/web3-chain.cjs.map +1 -1
- package/dist/web3-chain.js +2 -2
- package/package.json +2 -2
- package/dist/chunks/business-service-8Xd3d5oY.js +0 -238
- package/dist/chunks/business-service-8Xd3d5oY.js.map +0 -1
- package/dist/chunks/business-service-P9o4cwQL.cjs +0 -241
- package/dist/chunks/business-service-P9o4cwQL.cjs.map +0 -1
- package/dist/chunks/campaign-service-B1tQMNqA.cjs.map +0 -1
- package/dist/chunks/campaign-service-CJPXgFBe.js.map +0 -1
- package/dist/chunks/pers-sdk-Nu2KgiJf.js.map +0 -1
- package/dist/chunks/pers-sdk-XbARV58g.cjs.map +0 -1
- package/dist/chunks/user-service-D1Rn4U8u.cjs.map +0 -1
- package/dist/chunks/user-service-D6mTa_WZ.js.map +0 -1
- package/dist/chunks/web3-chain-service-BLFxB5TA.cjs.map +0 -1
- 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,
|
|
3
|
-
import { a as UserService, U as UserApi } from './user-service-
|
|
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 {
|
|
7
|
-
import { a as CampaignService, C as CampaignApi } from './campaign-service-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2387
|
-
* const
|
|
2388
|
-
* console.log(`${
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
3292
|
+
* Get campaigns with pagination support
|
|
2620
3293
|
*
|
|
2621
|
-
*
|
|
2622
|
-
*
|
|
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
|
|
2626
|
-
* @
|
|
2627
|
-
* @
|
|
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
|
-
* //
|
|
2632
|
-
* const
|
|
2633
|
-
*
|
|
2634
|
-
*
|
|
2635
|
-
*
|
|
2636
|
-
*
|
|
2637
|
-
*
|
|
2638
|
-
*
|
|
2639
|
-
*
|
|
2640
|
-
*
|
|
2641
|
-
*
|
|
2642
|
-
*
|
|
2643
|
-
*
|
|
2644
|
-
*
|
|
2645
|
-
*
|
|
2646
|
-
*
|
|
2647
|
-
*
|
|
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
|
|
2652
|
-
return this.campaignService.getCampaigns(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
@@ -5803,38 +6505,45 @@ class DonationManager {
|
|
|
5803
6505
|
* Provides a simplified API for common Web3 blockchain tasks while maintaining
|
|
5804
6506
|
* access to the full Web3 SDK for advanced use cases.
|
|
5805
6507
|
*
|
|
5806
|
-
* ##
|
|
6508
|
+
* ## Supported Token Standards
|
|
6509
|
+
*
|
|
6510
|
+
* - **ERC-20**: Fungible tokens (credits, points). Query balance with `getTokenBalance()`.
|
|
6511
|
+
* - **ERC-721**: Non-fungible tokens (unique NFTs). Supports enumeration - no tokenIds needed.
|
|
6512
|
+
* - **ERC-1155**: Multi-token standard (rewards, collectibles). Requires specific tokenIds.
|
|
5807
6513
|
*
|
|
5808
|
-
*
|
|
5809
|
-
* supports enumeration). When working with ERC-1155 reward tokens:
|
|
6514
|
+
* ## Recommended: Use Helper Methods
|
|
5810
6515
|
*
|
|
5811
|
-
*
|
|
5812
|
-
*
|
|
6516
|
+
* For most use cases, use `getAccountOwnedTokensFromContract()` which automatically
|
|
6517
|
+
* handles token type detection and tokenId extraction:
|
|
5813
6518
|
*
|
|
5814
|
-
* @example ERC-1155 Reward Token Balance
|
|
5815
6519
|
* ```typescript
|
|
5816
|
-
*
|
|
5817
|
-
*
|
|
5818
|
-
*
|
|
6520
|
+
* const result = await sdk.web3.getAccountOwnedTokensFromContract(walletAddress, token);
|
|
6521
|
+
* console.log(`Wallet owns ${result.totalOwned} tokens`);
|
|
6522
|
+
* ```
|
|
5819
6523
|
*
|
|
5820
|
-
*
|
|
5821
|
-
*
|
|
5822
|
-
*
|
|
5823
|
-
* );
|
|
6524
|
+
* ## Manual Approach (Advanced)
|
|
6525
|
+
*
|
|
6526
|
+
* For ERC-1155 tokens, tokenIds are extracted from `token.metadata[].tokenMetadataIncrementalId`:
|
|
5824
6527
|
*
|
|
5825
|
-
*
|
|
6528
|
+
* ```typescript
|
|
6529
|
+
* const tokenIds = token.metadata?.map(meta => meta.tokenMetadataIncrementalId.toString());
|
|
5826
6530
|
* const collection = await sdk.web3.getTokenCollection({
|
|
5827
6531
|
* accountAddress: walletAddress,
|
|
5828
|
-
* contractAddress:
|
|
5829
|
-
* abi:
|
|
5830
|
-
* chainId:
|
|
5831
|
-
* tokenIds
|
|
6532
|
+
* contractAddress: token.contractAddress,
|
|
6533
|
+
* abi: token.abi,
|
|
6534
|
+
* chainId: token.chainId,
|
|
6535
|
+
* tokenIds // Required for ERC-1155, optional for ERC-721
|
|
5832
6536
|
* });
|
|
5833
|
-
*
|
|
5834
|
-
* const ownedTokens = collection.tokens.filter(t => t.hasBalance);
|
|
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
|
|
@@ -5924,17 +6633,21 @@ class Web3Manager {
|
|
|
5924
6633
|
// HELPER METHODS
|
|
5925
6634
|
// ==========================================
|
|
5926
6635
|
/**
|
|
5927
|
-
* Extract tokenIds from a TokenDTO
|
|
6636
|
+
* Extract tokenIds from a TokenDTO's metadata.
|
|
6637
|
+
*
|
|
6638
|
+
* Extracts `tokenMetadataIncrementalId` from each metadata entry. This is
|
|
6639
|
+
* particularly useful for ERC-1155 tokens which require specific tokenIds
|
|
6640
|
+
* to query balances, but works with any token that has metadata.
|
|
5928
6641
|
*
|
|
5929
|
-
*
|
|
5930
|
-
*
|
|
6642
|
+
* **Note:** For most use cases, prefer `getAccountOwnedTokensFromContract()`
|
|
6643
|
+
* which handles tokenId extraction automatically.
|
|
5931
6644
|
*
|
|
5932
6645
|
* @param token - Token definition containing metadata array
|
|
5933
6646
|
* @returns Array of tokenId strings, or undefined if no metadata
|
|
5934
6647
|
*
|
|
5935
|
-
* @example
|
|
6648
|
+
* @example Manual token collection request
|
|
5936
6649
|
* ```typescript
|
|
5937
|
-
* const rewardToken = await sdk.tokens.getRewardTokens()[0];
|
|
6650
|
+
* const rewardToken = (await sdk.tokens.getRewardTokens())[0];
|
|
5938
6651
|
* const tokenIds = sdk.web3.extractTokenIds(rewardToken);
|
|
5939
6652
|
*
|
|
5940
6653
|
* if (tokenIds) {
|
|
@@ -5943,10 +6656,12 @@ class Web3Manager {
|
|
|
5943
6656
|
* contractAddress: rewardToken.contractAddress,
|
|
5944
6657
|
* abi: rewardToken.abi,
|
|
5945
6658
|
* chainId: rewardToken.chainId,
|
|
5946
|
-
* tokenIds
|
|
6659
|
+
* tokenIds
|
|
5947
6660
|
* });
|
|
5948
6661
|
* }
|
|
5949
6662
|
* ```
|
|
6663
|
+
*
|
|
6664
|
+
* @see {@link getAccountOwnedTokensFromContract} - Recommended helper that handles this automatically
|
|
5950
6665
|
*/
|
|
5951
6666
|
extractTokenIds(token) {
|
|
5952
6667
|
if (!token.metadata || token.metadata.length === 0) {
|
|
@@ -5955,40 +6670,44 @@ class Web3Manager {
|
|
|
5955
6670
|
return token.metadata.map(meta => meta.tokenMetadataIncrementalId.toString());
|
|
5956
6671
|
}
|
|
5957
6672
|
/**
|
|
5958
|
-
* Get
|
|
6673
|
+
* Get owned tokens from a specific token contract for any blockchain address.
|
|
5959
6674
|
*
|
|
5960
|
-
*
|
|
5961
|
-
* - ERC-
|
|
6675
|
+
* **Recommended method** for querying token balances. Automatically handles:
|
|
6676
|
+
* - Token type detection (ERC-20, ERC-721, ERC-1155)
|
|
6677
|
+
* - TokenId extraction from metadata (uses `extractTokenIds()` internally for ERC-1155)
|
|
5962
6678
|
* - Building the collection request
|
|
5963
6679
|
* - Filtering to only tokens with balance > 0
|
|
5964
6680
|
*
|
|
5965
|
-
*
|
|
6681
|
+
* Works with any valid blockchain address - can query user wallets, external
|
|
6682
|
+
* wallets, contract addresses, or any other address holding tokens.
|
|
6683
|
+
*
|
|
6684
|
+
* @param accountAddress - Any valid blockchain address (wallet, contract, etc.)
|
|
5966
6685
|
* @param token - Token definition (from getRewardTokens, getStatusTokens, etc.)
|
|
5967
6686
|
* @param maxTokens - Maximum tokens to retrieve (default: 50)
|
|
5968
6687
|
* @returns Promise resolving to collection result with owned tokens
|
|
5969
6688
|
*
|
|
5970
|
-
* @example
|
|
6689
|
+
* @example Query user's wallet
|
|
5971
6690
|
* ```typescript
|
|
5972
|
-
* const
|
|
5973
|
-
* const
|
|
5974
|
-
*
|
|
5975
|
-
*
|
|
5976
|
-
* walletAddress,
|
|
5977
|
-
* rewardToken
|
|
5978
|
-
* );
|
|
6691
|
+
* const userWallet = user.wallets[0].address;
|
|
6692
|
+
* const result = await sdk.web3.getAccountOwnedTokensFromContract(userWallet, rewardToken);
|
|
6693
|
+
* console.log(`User owns ${result.totalOwned} tokens`);
|
|
6694
|
+
* ```
|
|
5979
6695
|
*
|
|
5980
|
-
*
|
|
5981
|
-
*
|
|
5982
|
-
*
|
|
5983
|
-
*
|
|
5984
|
-
* });
|
|
6696
|
+
* @example Query any external wallet
|
|
6697
|
+
* ```typescript
|
|
6698
|
+
* const externalWallet = '0x1234...abcd';
|
|
6699
|
+
* const result = await sdk.web3.getAccountOwnedTokensFromContract(externalWallet, rewardToken);
|
|
6700
|
+
* console.log(`Wallet owns ${result.totalOwned} tokens`);
|
|
5985
6701
|
* ```
|
|
6702
|
+
*
|
|
6703
|
+
* @see {@link extractTokenIds} - Low-level helper used internally for ERC-1155
|
|
6704
|
+
* @see {@link buildCollectionRequest} - For manual request building
|
|
5986
6705
|
*/
|
|
5987
|
-
async
|
|
6706
|
+
async getAccountOwnedTokensFromContract(accountAddress, token, maxTokens = 50) {
|
|
5988
6707
|
// For ERC-1155, extract tokenIds from metadata
|
|
5989
6708
|
const tokenIds = token.type === NativeTokenTypes.ERC1155 ? this.extractTokenIds(token) : undefined;
|
|
5990
6709
|
const collection = await this.getTokenCollection({
|
|
5991
|
-
accountAddress:
|
|
6710
|
+
accountAddress: accountAddress,
|
|
5992
6711
|
contractAddress: token.contractAddress,
|
|
5993
6712
|
abi: token.abi,
|
|
5994
6713
|
chainId: token.chainId,
|
|
@@ -6011,9 +6730,11 @@ class Web3Manager {
|
|
|
6011
6730
|
*
|
|
6012
6731
|
* Automatically handles ERC-1155 tokenId extraction from metadata.
|
|
6013
6732
|
* Use this when you need more control over the request than
|
|
6014
|
-
* `
|
|
6733
|
+
* `getAccountOwnedTokensFromContract` provides.
|
|
6015
6734
|
*
|
|
6016
|
-
*
|
|
6735
|
+
* Works with any valid blockchain address - user wallets, external wallets, etc.
|
|
6736
|
+
*
|
|
6737
|
+
* @param accountAddress - Any valid blockchain address (wallet, contract, etc.)
|
|
6017
6738
|
* @param token - Token definition
|
|
6018
6739
|
* @param maxTokens - Maximum tokens to retrieve
|
|
6019
6740
|
* @returns TokenCollectionRequest ready for getTokenCollection()
|
|
@@ -6024,9 +6745,9 @@ class Web3Manager {
|
|
|
6024
6745
|
* const collection = await sdk.web3.getTokenCollection(request);
|
|
6025
6746
|
* ```
|
|
6026
6747
|
*/
|
|
6027
|
-
buildCollectionRequest(
|
|
6748
|
+
buildCollectionRequest(accountAddress, token, maxTokens = 50) {
|
|
6028
6749
|
return {
|
|
6029
|
-
accountAddress:
|
|
6750
|
+
accountAddress: accountAddress,
|
|
6030
6751
|
contractAddress: token.contractAddress,
|
|
6031
6752
|
abi: token.abi,
|
|
6032
6753
|
chainId: token.chainId,
|
|
@@ -6146,15 +6867,18 @@ class PersSDK {
|
|
|
6146
6867
|
*/
|
|
6147
6868
|
constructor(httpClient, config) {
|
|
6148
6869
|
this.apiClient = new PersApiClient(httpClient, config);
|
|
6149
|
-
// Initialize
|
|
6150
|
-
this.
|
|
6151
|
-
this.
|
|
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);
|
|
6152
6876
|
this._userStatus = new UserStatusManager(this.apiClient);
|
|
6153
6877
|
this._tokens = new TokenManager(this.apiClient);
|
|
6154
|
-
this._businesses = new BusinessManager(this.apiClient);
|
|
6155
|
-
this._campaigns = new CampaignManager(this.apiClient);
|
|
6156
|
-
this._redemptions = new RedemptionManager(this.apiClient);
|
|
6157
|
-
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);
|
|
6158
6882
|
this._purchases = new PurchaseManager(this.apiClient);
|
|
6159
6883
|
this._files = new FileManager(this.apiClient);
|
|
6160
6884
|
this._tenants = new TenantManager(this.apiClient);
|
|
@@ -6163,6 +6887,55 @@ class PersSDK {
|
|
|
6163
6887
|
this._donations = new DonationManager(this.apiClient);
|
|
6164
6888
|
this._web3 = new Web3Manager(this.apiClient);
|
|
6165
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
|
+
}
|
|
6166
6939
|
/**
|
|
6167
6940
|
* Authentication manager - High-level authentication operations
|
|
6168
6941
|
*
|
|
@@ -6440,5 +7213,5 @@ function createPersSDK(httpClient, config) {
|
|
|
6440
7213
|
return new PersSDK(httpClient, config);
|
|
6441
7214
|
}
|
|
6442
7215
|
|
|
6443
|
-
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,
|
|
6444
|
-
//# sourceMappingURL=pers-sdk-
|
|
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
|