@explorins/pers-sdk 1.1.2 → 1.2.0

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 (276) hide show
  1. package/dist/analytics/api/analytics-api.d.ts +17 -0
  2. package/dist/analytics/api/analytics-api.d.ts.map +1 -0
  3. package/{src/analytics/index.ts → dist/analytics/index.d.ts} +28 -52
  4. package/dist/analytics/index.d.ts.map +1 -0
  5. package/dist/analytics/models/index.d.ts +61 -0
  6. package/dist/analytics/models/index.d.ts.map +1 -0
  7. package/dist/analytics/services/analytics-service.d.ts +19 -0
  8. package/dist/analytics/services/analytics-service.d.ts.map +1 -0
  9. package/dist/analytics.cjs +76 -0
  10. package/dist/analytics.cjs.map +1 -0
  11. package/dist/analytics.js +72 -0
  12. package/dist/analytics.js.map +1 -0
  13. package/dist/auth-admin/api/auth-admin-api.d.ts +27 -0
  14. package/dist/auth-admin/api/auth-admin-api.d.ts.map +1 -0
  15. package/dist/auth-admin/index.d.ts +26 -0
  16. package/dist/auth-admin/index.d.ts.map +1 -0
  17. package/dist/auth-admin/services/auth-admin-service.d.ts +23 -0
  18. package/dist/auth-admin/services/auth-admin-service.d.ts.map +1 -0
  19. package/dist/auth-admin.cjs +96 -0
  20. package/dist/auth-admin.cjs.map +1 -0
  21. package/dist/auth-admin.js +92 -0
  22. package/dist/auth-admin.js.map +1 -0
  23. package/dist/business/api/business-api.d.ts +133 -0
  24. package/dist/business/api/business-api.d.ts.map +1 -0
  25. package/dist/business/index.d.ts +34 -0
  26. package/dist/business/index.d.ts.map +1 -0
  27. package/dist/business/models/index.d.ts +8 -0
  28. package/dist/business/models/index.d.ts.map +1 -0
  29. package/dist/business/services/business-service.d.ts +51 -0
  30. package/dist/business/services/business-service.d.ts.map +1 -0
  31. package/dist/business.cjs +303 -0
  32. package/dist/business.cjs.map +1 -0
  33. package/dist/business.js +299 -0
  34. package/dist/business.js.map +1 -0
  35. package/dist/campaign/api/campaign-api.d.ts +213 -0
  36. package/dist/campaign/api/campaign-api.d.ts.map +1 -0
  37. package/dist/campaign/index.d.ts +44 -0
  38. package/dist/campaign/index.d.ts.map +1 -0
  39. package/dist/campaign/services/campaign-service.d.ts +88 -0
  40. package/dist/campaign/services/campaign-service.d.ts.map +1 -0
  41. package/dist/campaign.cjs +506 -0
  42. package/dist/campaign.cjs.map +1 -0
  43. package/dist/campaign.js +502 -0
  44. package/dist/campaign.js.map +1 -0
  45. package/dist/core/abstractions/http-client.d.ts +22 -0
  46. package/dist/core/abstractions/http-client.d.ts.map +1 -0
  47. package/dist/core/auth/auth-provider.interface.d.ts +12 -0
  48. package/dist/core/auth/auth-provider.interface.d.ts.map +1 -0
  49. package/dist/core/auth/create-auth-provider.d.ts +27 -0
  50. package/dist/core/auth/create-auth-provider.d.ts.map +1 -0
  51. package/dist/core/auth/simple-auth-config.interface.d.ts +15 -0
  52. package/dist/core/auth/simple-auth-config.interface.d.ts.map +1 -0
  53. package/dist/core/index.d.ts +13 -0
  54. package/dist/core/index.d.ts.map +1 -0
  55. package/dist/core/pers-api-client.d.ts +54 -0
  56. package/dist/core/pers-api-client.d.ts.map +1 -0
  57. package/dist/core/pers-config.d.ts +38 -0
  58. package/dist/core/pers-config.d.ts.map +1 -0
  59. package/dist/core/utils/jwt.function.d.ts +2 -0
  60. package/dist/core/utils/jwt.function.d.ts.map +1 -0
  61. package/dist/core.cjs +506 -0
  62. package/dist/core.cjs.map +1 -0
  63. package/dist/core.js +497 -0
  64. package/dist/core.js.map +1 -0
  65. package/dist/donation/api/donation-api.d.ts +18 -0
  66. package/dist/donation/api/donation-api.d.ts.map +1 -0
  67. package/dist/donation/index.d.ts +25 -0
  68. package/dist/donation/index.d.ts.map +1 -0
  69. package/{src/donation/models/index.ts → dist/donation/models/index.d.ts} +8 -11
  70. package/dist/donation/models/index.d.ts.map +1 -0
  71. package/dist/donation/services/donation-service.d.ts +19 -0
  72. package/dist/donation/services/donation-service.d.ts.map +1 -0
  73. package/dist/donation.cjs +78 -0
  74. package/dist/donation.cjs.map +1 -0
  75. package/dist/donation.js +74 -0
  76. package/dist/donation.js.map +1 -0
  77. package/dist/index.cjs +4217 -0
  78. package/dist/index.cjs.map +1 -0
  79. package/dist/index.d.ts +21 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +4164 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/package.json +129 -0
  84. package/dist/payment/api/payment-api.d.ts +105 -0
  85. package/dist/payment/api/payment-api.d.ts.map +1 -0
  86. package/dist/payment/index.d.ts +36 -0
  87. package/dist/payment/index.d.ts.map +1 -0
  88. package/dist/payment/models/index.d.ts +12 -0
  89. package/dist/payment/models/index.d.ts.map +1 -0
  90. package/dist/payment/services/payment-service.d.ts +40 -0
  91. package/dist/payment/services/payment-service.d.ts.map +1 -0
  92. package/dist/payment.cjs +258 -0
  93. package/dist/payment.cjs.map +1 -0
  94. package/dist/payment.js +254 -0
  95. package/dist/payment.js.map +1 -0
  96. package/dist/pers-sdk.d.ts +29 -0
  97. package/dist/pers-sdk.d.ts.map +1 -0
  98. package/dist/redemption/api/redemption-api.d.ts +147 -0
  99. package/dist/redemption/api/redemption-api.d.ts.map +1 -0
  100. package/dist/redemption/index.d.ts +35 -0
  101. package/dist/redemption/index.d.ts.map +1 -0
  102. package/dist/redemption/models/index.d.ts +8 -0
  103. package/dist/redemption/models/index.d.ts.map +1 -0
  104. package/dist/redemption/services/redemption-service.d.ts +56 -0
  105. package/dist/redemption/services/redemption-service.d.ts.map +1 -0
  106. package/dist/redemption.cjs +333 -0
  107. package/dist/redemption.cjs.map +1 -0
  108. package/dist/redemption.js +329 -0
  109. package/dist/redemption.js.map +1 -0
  110. package/dist/shared/interfaces/pers-shared-lib.interfaces.d.ts +18 -0
  111. package/dist/shared/interfaces/pers-shared-lib.interfaces.d.ts.map +1 -0
  112. package/dist/tenant/api/tenant-api.d.ts +56 -0
  113. package/dist/tenant/api/tenant-api.d.ts.map +1 -0
  114. package/dist/tenant/index.d.ts +34 -0
  115. package/dist/tenant/index.d.ts.map +1 -0
  116. package/dist/tenant/models/index.d.ts +11 -0
  117. package/dist/tenant/models/index.d.ts.map +1 -0
  118. package/dist/tenant/services/tenant-service.d.ts +46 -0
  119. package/dist/tenant/services/tenant-service.d.ts.map +1 -0
  120. package/dist/tenant.cjs +177 -0
  121. package/dist/tenant.cjs.map +1 -0
  122. package/dist/tenant.js +173 -0
  123. package/dist/tenant.js.map +1 -0
  124. package/dist/token/api/token-api.d.ts +64 -0
  125. package/dist/token/api/token-api.d.ts.map +1 -0
  126. package/dist/token/base/base-token-service.d.ts +87 -0
  127. package/dist/token/base/base-token-service.d.ts.map +1 -0
  128. package/dist/token/index.d.ts +13 -0
  129. package/dist/token/index.d.ts.map +1 -0
  130. package/dist/token/models/index.d.ts +18 -0
  131. package/dist/token/models/index.d.ts.map +1 -0
  132. package/dist/token/services/token-service.d.ts +68 -0
  133. package/dist/token/services/token-service.d.ts.map +1 -0
  134. package/dist/token/token-sdk.d.ts +140 -0
  135. package/dist/token/token-sdk.d.ts.map +1 -0
  136. package/dist/token.cjs +537 -0
  137. package/dist/token.cjs.map +1 -0
  138. package/dist/token.js +532 -0
  139. package/dist/token.js.map +1 -0
  140. package/dist/transaction/api/transaction-api.d.ts +133 -0
  141. package/dist/transaction/api/transaction-api.d.ts.map +1 -0
  142. package/dist/transaction/index.d.ts +38 -0
  143. package/dist/transaction/index.d.ts.map +1 -0
  144. package/dist/transaction/models/index.d.ts +42 -0
  145. package/dist/transaction/models/index.d.ts.map +1 -0
  146. package/dist/transaction/services/transaction-service.d.ts +56 -0
  147. package/dist/transaction/services/transaction-service.d.ts.map +1 -0
  148. package/dist/transaction.cjs +394 -0
  149. package/dist/transaction.cjs.map +1 -0
  150. package/dist/transaction.js +390 -0
  151. package/dist/transaction.js.map +1 -0
  152. package/dist/user/api/user-api.d.ts +56 -0
  153. package/dist/user/api/user-api.d.ts.map +1 -0
  154. package/dist/user/index.d.ts +36 -0
  155. package/dist/user/index.d.ts.map +1 -0
  156. package/{src/user/models/index.ts → dist/user/models/index.d.ts} +12 -10
  157. package/dist/user/models/index.d.ts.map +1 -0
  158. package/dist/user/services/user-service.d.ts +46 -0
  159. package/dist/user/services/user-service.d.ts.map +1 -0
  160. package/dist/user-status/api/user-status-api.d.ts +37 -0
  161. package/dist/user-status/api/user-status-api.d.ts.map +1 -0
  162. package/dist/user-status/index.d.ts +28 -0
  163. package/dist/user-status/index.d.ts.map +1 -0
  164. package/{src/user-status/models/index.ts → dist/user-status/models/index.d.ts} +8 -11
  165. package/dist/user-status/models/index.d.ts.map +1 -0
  166. package/dist/user-status/services/user-status-service.d.ts +26 -0
  167. package/dist/user-status/services/user-status-service.d.ts.map +1 -0
  168. package/dist/user-status.cjs +147 -0
  169. package/dist/user-status.cjs.map +1 -0
  170. package/dist/user-status.js +143 -0
  171. package/dist/user-status.js.map +1 -0
  172. package/dist/user.cjs +188 -0
  173. package/dist/user.cjs.map +1 -0
  174. package/dist/user.js +184 -0
  175. package/dist/user.js.map +1 -0
  176. package/dist/web3/api/web3-api.d.ts +27 -0
  177. package/dist/web3/api/web3-api.d.ts.map +1 -0
  178. package/dist/web3/index.d.ts +16 -0
  179. package/dist/web3/index.d.ts.map +1 -0
  180. package/dist/web3/models/index.d.ts +92 -0
  181. package/dist/web3/models/index.d.ts.map +1 -0
  182. package/dist/web3/services/web3-service.d.ts +21 -0
  183. package/dist/web3/services/web3-service.d.ts.map +1 -0
  184. package/dist/web3-chain/api/web3-chain-api.d.ts +19 -0
  185. package/dist/web3-chain/api/web3-chain-api.d.ts.map +1 -0
  186. package/dist/web3-chain/index.d.ts +17 -0
  187. package/dist/web3-chain/index.d.ts.map +1 -0
  188. package/{src/web3-chain/models/index.ts → dist/web3-chain/models/index.d.ts} +38 -45
  189. package/dist/web3-chain/models/index.d.ts.map +1 -0
  190. package/dist/web3-chain/services/getWeb3FCD.service.d.ts +8 -0
  191. package/dist/web3-chain/services/getWeb3FCD.service.d.ts.map +1 -0
  192. package/dist/web3-chain/services/provider.service.d.ts +15 -0
  193. package/dist/web3-chain/services/provider.service.d.ts.map +1 -0
  194. package/dist/web3-chain/services/public-http-provider.service.d.ts +8 -0
  195. package/dist/web3-chain/services/public-http-provider.service.d.ts.map +1 -0
  196. package/dist/web3-chain/services/web3-chain-service.d.ts +17 -0
  197. package/dist/web3-chain/services/web3-chain-service.d.ts.map +1 -0
  198. package/dist/web3-chain.cjs +316 -0
  199. package/dist/web3-chain.cjs.map +1 -0
  200. package/dist/web3-chain.js +310 -0
  201. package/dist/web3-chain.js.map +1 -0
  202. package/dist/web3.cjs +516 -0
  203. package/dist/web3.cjs.map +1 -0
  204. package/dist/web3.js +513 -0
  205. package/dist/web3.js.map +1 -0
  206. package/package.json +56 -72
  207. package/config/domains.js +0 -22
  208. package/explorins-pers-sdk-1.0.0-alpha.1.tgz +0 -0
  209. package/rollup.config.js +0 -74
  210. package/scripts/copy-declarations.js +0 -147
  211. package/src/analytics/api/analytics-api.ts +0 -24
  212. package/src/analytics/models/index.ts +0 -74
  213. package/src/analytics/services/analytics-service.ts +0 -28
  214. package/src/auth-admin/api/auth-admin-api.ts +0 -42
  215. package/src/auth-admin/index.ts +0 -47
  216. package/src/auth-admin/services/auth-admin-service.ts +0 -36
  217. package/src/business/api/business-api.ts +0 -234
  218. package/src/business/index.ts +0 -53
  219. package/src/business/models/index.ts +0 -13
  220. package/src/business/services/business-service.ts +0 -88
  221. package/src/campaign/api/campaign-api.ts +0 -376
  222. package/src/campaign/index.ts +0 -67
  223. package/src/campaign/services/campaign-service.ts +0 -164
  224. package/src/core/abstractions/http-client.ts +0 -24
  225. package/src/core/auth/auth-provider.interface.ts +0 -16
  226. package/src/core/auth/create-auth-provider.ts +0 -136
  227. package/src/core/auth/simple-auth-config.interface.ts +0 -15
  228. package/src/core/index.ts +0 -33
  229. package/src/core/pers-api-client.ts +0 -343
  230. package/src/core/pers-config.ts +0 -65
  231. package/src/core/utils/jwt.function.ts +0 -24
  232. package/src/donation/api/donation-api.ts +0 -24
  233. package/src/donation/index.ts +0 -47
  234. package/src/donation/services/donation-service.ts +0 -25
  235. package/src/index.ts +0 -55
  236. package/src/payment/api/payment-api.ts +0 -185
  237. package/src/payment/index.ts +0 -64
  238. package/src/payment/models/index.ts +0 -29
  239. package/src/payment/services/payment-service.ts +0 -70
  240. package/src/pers-sdk.ts +0 -45
  241. package/src/redemption/api/redemption-api.ts +0 -241
  242. package/src/redemption/index.ts +0 -60
  243. package/src/redemption/models/index.ts +0 -17
  244. package/src/redemption/services/redemption-service.ts +0 -103
  245. package/src/shared/interfaces/pers-shared-lib.interfaces.ts +0 -99
  246. package/src/tenant/api/tenant-api.ts +0 -92
  247. package/src/tenant/index.ts +0 -61
  248. package/src/tenant/models/index.ts +0 -20
  249. package/src/tenant/services/tenant-service.ts +0 -78
  250. package/src/token/api/token-api.ts +0 -129
  251. package/src/token/base/base-token-service.ts +0 -167
  252. package/src/token/index.ts +0 -38
  253. package/src/token/models/index.ts +0 -30
  254. package/src/token/services/token-service.ts +0 -125
  255. package/src/token/token-sdk.ts +0 -231
  256. package/src/transaction/api/transaction-api.ts +0 -296
  257. package/src/transaction/index.ts +0 -65
  258. package/src/transaction/models/index.ts +0 -60
  259. package/src/transaction/services/transaction-service.ts +0 -104
  260. package/src/user/api/user-api.ts +0 -98
  261. package/src/user/index.ts +0 -62
  262. package/src/user/services/user-service.ts +0 -75
  263. package/src/user-status/api/user-status-api.ts +0 -78
  264. package/src/user-status/index.ts +0 -55
  265. package/src/user-status/services/user-status-service.ts +0 -51
  266. package/src/web3/api/web3-api.ts +0 -68
  267. package/src/web3/index.ts +0 -38
  268. package/src/web3/models/index.ts +0 -150
  269. package/src/web3/services/web3-service.ts +0 -338
  270. package/src/web3-chain/api/web3-chain-api.ts +0 -42
  271. package/src/web3-chain/index.ts +0 -27
  272. package/src/web3-chain/services/getWeb3FCD.service.ts +0 -47
  273. package/src/web3-chain/services/provider.service.ts +0 -123
  274. package/src/web3-chain/services/public-http-provider.service.ts +0 -26
  275. package/src/web3-chain/services/web3-chain-service.ts +0 -131
  276. package/tsconfig.json +0 -28
package/dist/index.cjs ADDED
@@ -0,0 +1,4217 @@
1
+ 'use strict';
2
+
3
+ var jwtDecode = require('jwt-decode');
4
+ var Web3 = require('web3');
5
+ var web3Ts = require('@explorins/web3-ts');
6
+ var ethers = require('ethers');
7
+
8
+ /**
9
+ * PERS SDK Configuration interfaces
10
+ */
11
+ /**
12
+ * Default configuration values
13
+ */
14
+ const DEFAULT_PERS_CONFIG = {
15
+ environment: 'production',
16
+ apiVersion: 'v2',
17
+ timeout: 30000,
18
+ retries: 3
19
+ };
20
+ /**
21
+ * Internal function to construct API root from environment
22
+ * Now defaults to production and v2
23
+ */
24
+ function buildApiRoot(environment = 'production', version = 'v2') {
25
+ const baseUrls = {
26
+ development: 'https://explorins-loyalty.ngrok.io',
27
+ staging: `https://dev.api.pers.ninja/${version}`,
28
+ production: `https://api.pers.ninja/${version}`
29
+ };
30
+ return `${baseUrls[environment]}`;
31
+ }
32
+ /**
33
+ * Merge user config with defaults
34
+ */
35
+ function mergeWithDefaults(config) {
36
+ return {
37
+ ...DEFAULT_PERS_CONFIG,
38
+ ...config,
39
+ environment: config.environment ?? DEFAULT_PERS_CONFIG.environment,
40
+ apiVersion: config.apiVersion ?? DEFAULT_PERS_CONFIG.apiVersion,
41
+ timeout: config.timeout ?? DEFAULT_PERS_CONFIG.timeout,
42
+ retries: config.retries ?? DEFAULT_PERS_CONFIG.retries
43
+ };
44
+ }
45
+
46
+ // packages/pers-sdk/src/core/pers-api-client.ts
47
+ /**
48
+ * PERS API Client - Core platform-agnostic client for PERS backend
49
+ */
50
+ class PersApiClient {
51
+ constructor(httpClient, config) {
52
+ this.httpClient = httpClient;
53
+ this.config = config;
54
+ // Merge user config with defaults (production + v2)
55
+ this.mergedConfig = mergeWithDefaults(config);
56
+ // Build API root from merged environment and version
57
+ this.apiRoot = buildApiRoot(this.mergedConfig.environment, this.mergedConfig.apiVersion);
58
+ }
59
+ /**
60
+ * Get request headers including auth token and project key
61
+ */
62
+ async getHeaders() {
63
+ const headers = {
64
+ 'Content-Type': 'application/json',
65
+ };
66
+ // Add authentication token
67
+ if (this.mergedConfig.authProvider) {
68
+ const token = await this.mergedConfig.authProvider.getToken();
69
+ if (token) {
70
+ headers['Authorization'] = `Bearer ${token}`;
71
+ }
72
+ }
73
+ // Add project key
74
+ if (this.mergedConfig.authProvider) {
75
+ const projectKey = await this.mergedConfig.authProvider.getProjectKey();
76
+ if (projectKey) {
77
+ headers['x-project-key'] = projectKey;
78
+ }
79
+ }
80
+ else if (this.mergedConfig.apiProjectKey) {
81
+ // Fallback to config project key if no auth provider
82
+ headers['x-project-key'] = this.mergedConfig.apiProjectKey;
83
+ }
84
+ return headers;
85
+ }
86
+ /**
87
+ * Make a request with proper headers, auth, and error handling
88
+ */
89
+ async request(method, endpoint, body, options) {
90
+ const { retryCount = 0, responseType = 'json' } = options || {};
91
+ const url = `${this.apiRoot}${endpoint}`;
92
+ // ✅ DEBUGGING: Add extensive logging for CSV endpoint
93
+ const isCSVEndpoint = endpoint.includes('/export/csv');
94
+ const requestOptions = {
95
+ headers: await this.getHeaders(),
96
+ timeout: this.mergedConfig.timeout,
97
+ responseType
98
+ };
99
+ try {
100
+ let result;
101
+ switch (method) {
102
+ case 'GET':
103
+ result = await this.httpClient.get(url, requestOptions);
104
+ break;
105
+ case 'POST':
106
+ result = await this.httpClient.post(url, body, requestOptions);
107
+ break;
108
+ case 'PUT':
109
+ result = await this.httpClient.put(url, body, requestOptions);
110
+ break;
111
+ case 'DELETE':
112
+ result = await this.httpClient.delete(url, requestOptions);
113
+ break;
114
+ default:
115
+ throw new Error(`Unsupported HTTP method: ${method}`);
116
+ }
117
+ return result;
118
+ }
119
+ catch (error) {
120
+ if (isCSVEndpoint) {
121
+ console.error('❌ [PERS API CLIENT] CSV Request failed:', error);
122
+ }
123
+ // Handle 401 errors with automatic token refresh
124
+ const apiError = error;
125
+ if (apiError.status === 401 && retryCount === 0 && this.mergedConfig.authProvider?.onTokenExpired) {
126
+ try {
127
+ await this.mergedConfig.authProvider.onTokenExpired();
128
+ // Retry once with refreshed token
129
+ return this.request(method, endpoint, body, { ...options, retryCount: 1 });
130
+ }
131
+ catch (refreshError) {
132
+ throw new PersApiError(`Authentication refresh failed: ${refreshError}`, endpoint, method, 401);
133
+ }
134
+ }
135
+ throw new PersApiError(`PERS API request failed: ${apiError.message || error}`, endpoint, method, apiError.status);
136
+ }
137
+ }
138
+ /**
139
+ * Generic GET request
140
+ */
141
+ async get(endpoint, responseType) {
142
+ return this.request('GET', endpoint, undefined, { responseType });
143
+ }
144
+ /**
145
+ * Generic POST request
146
+ */
147
+ async post(endpoint, body) {
148
+ return this.request('POST', endpoint, body);
149
+ }
150
+ /**
151
+ * Generic PUT request
152
+ */
153
+ async put(endpoint, body) {
154
+ return this.request('PUT', endpoint, body);
155
+ }
156
+ /**
157
+ * Generic DELETE request
158
+ */
159
+ async delete(endpoint) {
160
+ return this.request('DELETE', endpoint);
161
+ }
162
+ /**
163
+ * Get current configuration (returns merged config)
164
+ */
165
+ getConfig() {
166
+ return this.mergedConfig;
167
+ }
168
+ /**
169
+ * Get original user configuration
170
+ */
171
+ getOriginalConfig() {
172
+ return this.config;
173
+ }
174
+ }
175
+ class PersApiError extends Error {
176
+ constructor(message, endpoint, method, statusCode) {
177
+ super(message);
178
+ this.endpoint = endpoint;
179
+ this.method = method;
180
+ this.statusCode = statusCode;
181
+ this.name = 'PersApiError';
182
+ }
183
+ }
184
+ /**
185
+ * PERS API Client - Core platform-agnostic client for PERS backend
186
+ */
187
+ /*import { HttpClient, RequestOptions } from './abstractions/http-client';
188
+ import { PersConfig, buildApiRoot, mergeWithDefaults } from './pers-config';
189
+
190
+ export class PersApiClient {
191
+ private readonly apiRoot: string;
192
+ private readonly mergedConfig: ReturnType<typeof mergeWithDefaults>;
193
+
194
+ constructor(
195
+ private httpClient: HttpClient,
196
+ private config: PersConfig
197
+ ) {
198
+ // Merge user config with defaults (production + v2)
199
+ this.mergedConfig = mergeWithDefaults(config);
200
+
201
+ // Build API root from merged environment and version
202
+ this.apiRoot = buildApiRoot(this.mergedConfig.environment, this.mergedConfig.apiVersion);
203
+ }
204
+
205
+ /**
206
+ * Get request headers including auth token and project key
207
+ */
208
+ /*private async getHeaders(): Promise<Record<string, string>> {
209
+ const headers: Record<string, string> = {
210
+ 'Content-Type': 'application/json',
211
+ };
212
+
213
+ // Add authentication token
214
+ if (this.mergedConfig.authProvider) {
215
+ const token = await this.mergedConfig.authProvider.getToken();
216
+ if (token) {
217
+ headers['Authorization'] = `Bearer ${token}`;
218
+ }
219
+ }
220
+
221
+ // Add project key
222
+ if (this.mergedConfig.authProvider) {
223
+ const projectKey = await this.mergedConfig.authProvider.getProjectKey();
224
+ if (projectKey) {
225
+ headers['x-project-key'] = projectKey;
226
+ }
227
+ } else if(this.mergedConfig.apiProjectKey) {
228
+ // Fallback to config project key if no auth provider
229
+ headers['x-project-key'] = this.mergedConfig.apiProjectKey;
230
+ }
231
+
232
+ return headers;
233
+ }
234
+
235
+ /**
236
+ * Make a request with proper headers, auth, and error handling
237
+ */
238
+ /*private async request<T>(
239
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE',
240
+ endpoint: string,
241
+ body?: any,
242
+ options?: { retryCount?: number }
243
+ ): Promise<T> {
244
+ const { retryCount = 0 } = options || {};
245
+ const url = `${this.apiRoot}${endpoint}`;
246
+
247
+ const requestOptions: RequestOptions = {
248
+ headers: await this.getHeaders(),
249
+ timeout: this.mergedConfig.timeout,
250
+ };
251
+
252
+ try {
253
+ switch (method) {
254
+ case 'GET':
255
+ return await this.httpClient.get<T>(url, requestOptions);
256
+ case 'POST':
257
+ return await this.httpClient.post<T>(url, body, requestOptions);
258
+ case 'PUT':
259
+ return await this.httpClient.put<T>(url, body, requestOptions);
260
+ case 'DELETE':
261
+ return await this.httpClient.delete<T>(url, requestOptions);
262
+ default:
263
+ throw new Error(`Unsupported HTTP method: ${method}`);
264
+ }
265
+ } catch (error: any) {
266
+ // Handle 401 errors with automatic token refresh
267
+ if (error.status === 401 && retryCount === 0 && this.mergedConfig.authProvider?.onTokenExpired) {
268
+ try {
269
+ await this.mergedConfig.authProvider.onTokenExpired();
270
+ // Retry once with refreshed token
271
+ return this.request<T>(method, endpoint, body, { ...options, retryCount: 1 });
272
+ } catch (refreshError) {
273
+ throw new PersApiError(
274
+ `Authentication refresh failed: ${refreshError}`,
275
+ endpoint,
276
+ method,
277
+ 401
278
+ );
279
+ }
280
+ }
281
+
282
+ throw new PersApiError(
283
+ `PERS API request failed: ${error.message || error}`,
284
+ endpoint,
285
+ method,
286
+ error.status
287
+ );
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Generic GET request
293
+ */
294
+ /*async get<T>(endpoint: string): Promise<T> {
295
+ return this.request<T>('GET', endpoint);
296
+ }
297
+
298
+ /**
299
+ * Generic POST request
300
+ */
301
+ /*async post<T>(endpoint: string, body?: any): Promise<T> {
302
+ return this.request<T>('POST', endpoint, body);
303
+ }
304
+
305
+ /**
306
+ * Generic PUT request
307
+ */
308
+ /*async put<T>(endpoint: string, body?: any): Promise<T> {
309
+ return this.request<T>('PUT', endpoint, body);
310
+ }
311
+
312
+ /**
313
+ * Generic DELETE request
314
+ */
315
+ /*async delete<T>(endpoint: string): Promise<T> {
316
+ return this.request<T>('DELETE', endpoint);
317
+ }
318
+
319
+ /**
320
+ * Get current configuration (returns merged config)
321
+ */
322
+ /*getConfig(): ReturnType<typeof mergeWithDefaults> {
323
+ return this.mergedConfig;
324
+ }
325
+
326
+ /**
327
+ * Get original user configuration
328
+ */
329
+ /*getOriginalConfig(): PersConfig {
330
+ return this.config;
331
+ }
332
+ }
333
+
334
+ export class PersApiError extends Error {
335
+ constructor(
336
+ message: string,
337
+ public endpoint: string,
338
+ public method: string,
339
+ public statusCode?: number
340
+ ) {
341
+ super(message);
342
+ this.name = 'PersApiError';
343
+ }
344
+ }*/
345
+
346
+ /**
347
+ * Creates a platform-agnostic AuthProvider from simple configuration
348
+ *
349
+ * This factory function is completely platform-agnostic and can be used
350
+ * across Angular, React, Vue, Node.js, or any other JavaScript environment.
351
+ *
352
+ * Features:
353
+ * - Token caching with refresh support
354
+ * - Automatic token refresh on expiration
355
+ * - Configurable token providers
356
+ * - Platform-independent (no localStorage assumptions)
357
+ *
358
+ * @param config - Simple auth configuration
359
+ * @returns AuthProvider implementation
360
+ */
361
+ function createAuthProvider(config) {
362
+ // Store current token for refresh scenarios and caching
363
+ let currentToken = config.token || null;
364
+ let isRefreshing = false; // Prevent concurrent refresh attempts
365
+ let refreshPromise = null;
366
+ return {
367
+ authType: config.authType || 'user',
368
+ async getToken() {
369
+ // If currently refreshing, wait for it to complete
370
+ if (isRefreshing && refreshPromise) {
371
+ await refreshPromise;
372
+ return currentToken;
373
+ }
374
+ // Use cached current token (updated after refresh)
375
+ if (currentToken) {
376
+ return currentToken;
377
+ }
378
+ // Custom token provider function (always fresh)
379
+ if (config.tokenProvider) {
380
+ const token = await config.tokenProvider();
381
+ currentToken = token; // Cache for future calls
382
+ return token;
383
+ }
384
+ // No token available
385
+ return null;
386
+ },
387
+ async getProjectKey() {
388
+ return config.projectKey || null;
389
+ },
390
+ async onTokenExpired() {
391
+ // Prevent concurrent refresh attempts
392
+ if (isRefreshing) {
393
+ if (refreshPromise) {
394
+ await refreshPromise;
395
+ }
396
+ return;
397
+ }
398
+ // No refresh logic provided
399
+ if (!config.onTokenExpired) {
400
+ console.warn('Token expired but no refresh logic provided');
401
+ currentToken = null; // Clear expired token
402
+ return;
403
+ }
404
+ // Start refresh process
405
+ isRefreshing = true;
406
+ refreshPromise = (async () => {
407
+ try {
408
+ // Execute refresh logic (should update token source)
409
+ await config.onTokenExpired();
410
+ // After refresh, get the new token
411
+ if (config.tokenProvider) {
412
+ const newToken = await config.tokenProvider();
413
+ if (newToken && newToken !== currentToken) {
414
+ currentToken = newToken;
415
+ // Notify about successful token refresh
416
+ if (config.onTokenRefreshed) {
417
+ config.onTokenRefreshed(newToken);
418
+ }
419
+ }
420
+ else {
421
+ console.warn('Token refresh completed but no new token received');
422
+ currentToken = null;
423
+ }
424
+ }
425
+ else {
426
+ // For static token configs, clear the token since we can't refresh
427
+ console.warn('Token expired for static token config - clearing token');
428
+ currentToken = null;
429
+ }
430
+ }
431
+ catch (error) {
432
+ console.error('Token refresh failed:', error);
433
+ currentToken = null; // Clear token on refresh failure
434
+ throw error; // Re-throw to let SDK handle the error
435
+ }
436
+ finally {
437
+ isRefreshing = false;
438
+ refreshPromise = null;
439
+ }
440
+ })();
441
+ await refreshPromise;
442
+ }
443
+ };
444
+ }
445
+ /**
446
+ * Platform-specific localStorage token provider for browsers
447
+ * This is a convenience function for browser environments
448
+ */
449
+ /* export function createBrowserTokenProvider(tokenKey: string = 'userJwt'): () => Promise<string | null> {
450
+ return async () => {
451
+ if (typeof localStorage !== 'undefined') {
452
+ return localStorage.getItem(tokenKey);
453
+ }
454
+ return null;
455
+ };
456
+ } */
457
+ /**
458
+ * Platform-specific environment variable token provider for Node.js
459
+ * This is a convenience function for Node.js environments
460
+ */
461
+ /* export function createNodeTokenProvider(envVar: string = 'JWT_TOKEN'): () => Promise<string | null> {
462
+ return async () => {
463
+ if (typeof process !== 'undefined' && process.env) {
464
+ return process.env[envVar] || null;
465
+ }
466
+ return null;
467
+ };
468
+ } */
469
+
470
+ /**
471
+ * PERS SDK - Minimal platform-agnostic client with built-in authentication
472
+ * Authentication is now handled at the SDK core level for better scalability
473
+ */
474
+ /**
475
+ * Minimal PERS SDK - API client with authentication built-in
476
+ * Platform adapters provide auth providers and HTTP clients
477
+ */
478
+ class PersSDK {
479
+ constructor(httpClient, config) {
480
+ this.apiClient = new PersApiClient(httpClient, config);
481
+ }
482
+ /**
483
+ * Get the API client for direct PERS API calls
484
+ * This is the main interface - keep it simple!
485
+ */
486
+ api() {
487
+ return this.apiClient;
488
+ }
489
+ /**
490
+ * Quick config check
491
+ */
492
+ isProduction() {
493
+ return this.apiClient.getConfig().environment === 'production';
494
+ }
495
+ }
496
+ /**
497
+ * Simple factory function
498
+ */
499
+ function createPersSDK(httpClient, config) {
500
+ return new PersSDK(httpClient, config);
501
+ }
502
+
503
+ /**
504
+ * Platform-Agnostic Business API Client
505
+ *
506
+ * Updated to match the new RESTful /businesses endpoints.
507
+ * Uses @explorins/pers-shared DTOs for full type safety and consistency with backend.
508
+ */
509
+ class BusinessApi {
510
+ constructor(apiClient) {
511
+ this.apiClient = apiClient;
512
+ this.basePath = '/businesses';
513
+ }
514
+ // ==========================================
515
+ // 🌐 BUSINESS TYPES MANAGEMENT
516
+ // ==========================================
517
+ /**
518
+ * Get all business types (project key required)
519
+ *
520
+ * Endpoint: GET /businesses/types
521
+ * Auth: @ApiSecurity('projectKey')
522
+ */
523
+ async getAllBusinessTypes() {
524
+ return this.apiClient.get(`${this.basePath}/types`);
525
+ }
526
+ /**
527
+ * ADMIN: Create business type
528
+ *
529
+ * Endpoint: POST /businesses/types
530
+ * Auth: @TenantAdmin()
531
+ */
532
+ async createBusinessType(dto) {
533
+ return this.apiClient.post(`${this.basePath}/types`, dto);
534
+ }
535
+ /**
536
+ * ADMIN: Update business type
537
+ *
538
+ * Endpoint: PUT /businesses/types
539
+ * Auth: @TenantAdmin()
540
+ */
541
+ async updateBusinessType(dto) {
542
+ return this.apiClient.put(`${this.basePath}/types`, dto);
543
+ }
544
+ /**
545
+ * ADMIN: Delete business type
546
+ *
547
+ * Endpoint: DELETE /businesses/types/{id}
548
+ * Auth: @TenantAdmin()
549
+ */
550
+ async deleteBusinessType(id) {
551
+ return this.apiClient.delete(`${this.basePath}/types/${id}`);
552
+ }
553
+ // ==========================================
554
+ // 🏢 BUSINESS MANAGEMENT
555
+ // ==========================================
556
+ /**
557
+ * Get current business info (business authentication required)
558
+ *
559
+ * Endpoint: GET /businesses/me
560
+ * Auth: @Business()
561
+ */
562
+ async getCurrentBusiness() {
563
+ return this.apiClient.get(`${this.basePath}/me`);
564
+ }
565
+ /**
566
+ * Get all active businesses (project key required)
567
+ *
568
+ * Endpoint: GET /businesses
569
+ * Auth: @ApiSecurity('projectKey')
570
+ * Note: Regular users automatically get active businesses only
571
+ */
572
+ async getActiveBusinesses() {
573
+ return this.apiClient.get(`${this.basePath}`);
574
+ }
575
+ /**
576
+ * Get all businesses with filtering (admin users can access inactive)
577
+ *
578
+ * Endpoint: GET /businesses?active={boolean}&sanitize={mode}
579
+ * Auth: @ApiSecurity('projectKey') (enhanced with role-based filtering)
580
+ */
581
+ async getAllBusinesses(options) {
582
+ const params = new URLSearchParams();
583
+ if (options?.active !== undefined) {
584
+ params.append('active', String(options.active));
585
+ }
586
+ if (options?.sanitize) {
587
+ params.append('sanitize', options.sanitize);
588
+ }
589
+ const queryString = params.toString();
590
+ const url = queryString ? `${this.basePath}?${queryString}` : this.basePath;
591
+ return this.apiClient.get(url);
592
+ }
593
+ /**
594
+ * ADMIN: Get all businesses (admin endpoint with full access)
595
+ *
596
+ * Endpoint: GET /businesses/admin
597
+ * Auth: @TenantAdmin()
598
+ */
599
+ async getAllBusinessesAdmin(options) {
600
+ const params = new URLSearchParams();
601
+ if (options?.active !== undefined) {
602
+ params.append('active', String(options.active));
603
+ }
604
+ if (options?.sanitize) {
605
+ params.append('sanitize', options.sanitize);
606
+ }
607
+ const queryString = params.toString();
608
+ const url = queryString ? `${this.basePath}?${queryString}` : this.basePath;
609
+ return this.apiClient.get(url);
610
+ }
611
+ /**
612
+ * Get business by ID
613
+ *
614
+ * Endpoint: GET /businesses/{id}
615
+ * Auth: @ApiSecurity('projectKey')
616
+ */
617
+ async getBusinessById(businessId) {
618
+ return this.apiClient.get(`${this.basePath}/${businessId}`);
619
+ }
620
+ /**
621
+ * Get business by account address
622
+ *
623
+ * Endpoint: GET /businesses/account/{accountAddress}
624
+ * Auth: @ApiSecurity('projectKey')
625
+ */
626
+ async getBusinessByAccount(accountAddress) {
627
+ return this.apiClient.get(`${this.basePath}/account/${accountAddress}`);
628
+ }
629
+ // ==========================================
630
+ // 🔧 ADMIN OPERATIONS
631
+ // ==========================================
632
+ /**
633
+ * ADMIN: Create business
634
+ *
635
+ * Endpoint: POST /businesses
636
+ * Auth: @TenantAdmin()
637
+ * Returns: BusinessApiKeyDTO | BusinessTokenBalancesDTO
638
+ */
639
+ async createBusiness(dto) {
640
+ return this.apiClient.post(`${this.basePath}`, dto);
641
+ }
642
+ /**
643
+ * ADMIN: Create business by display name (convenience method)
644
+ *
645
+ * This is a convenience wrapper that creates the proper DTO structure
646
+ */
647
+ async createBusinessByDisplayName(displayName) {
648
+ const dto = {
649
+ displayName,
650
+ // Add other required fields based on BusinessCreateRequestDTO structure
651
+ // You may need to check the DTO definition for required fields
652
+ };
653
+ return this.createBusiness(dto);
654
+ }
655
+ /**
656
+ * ADMIN: Create businesses from URL
657
+ *
658
+ * Endpoint: POST /businesses/bulk/url
659
+ * Auth: @TenantAdmin()
660
+ */
661
+ async createBusinessesFromUrl(url) {
662
+ return this.apiClient.post(`${this.basePath}/bulk/url`, { url });
663
+ }
664
+ /**
665
+ * ADMIN: Update business
666
+ *
667
+ * Endpoint: PUT /businesses/{id}
668
+ * Auth: @TenantAdmin()
669
+ */
670
+ async updateBusiness(id, businessData) {
671
+ return this.apiClient.put(`${this.basePath}/${id}`, businessData);
672
+ }
673
+ /**
674
+ * ADMIN: Toggle business active status
675
+ *
676
+ * Endpoint: PUT /businesses/{id}/activate
677
+ * Auth: @TenantAdmin()
678
+ */
679
+ async toggleBusinessActive(id, isActive) {
680
+ const dto = { isActive };
681
+ return this.apiClient.put(`${this.basePath}/${id}/status`, dto);
682
+ }
683
+ /**
684
+ * ADMIN: Generate new business API key
685
+ *
686
+ * Endpoint: PUT /businesses/{id}/api-key
687
+ * Auth: @TenantAdmin()
688
+ */
689
+ async generateNewBusinessApiKey(id) {
690
+ return this.apiClient.put(`${this.basePath}/${id}/api-key`, {});
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Platform-Agnostic Business Service
696
+ *
697
+ * Contains business logic and operations that work across platforms.
698
+ * No framework dependencies - pure TypeScript business logic.
699
+ *
700
+ * Focuses only on actual backend capabilities.
701
+ */
702
+ class BusinessService {
703
+ constructor(businessApi) {
704
+ this.businessApi = businessApi;
705
+ }
706
+ /**
707
+ * Get all active businesses
708
+ */
709
+ async getActiveBusinesses() {
710
+ return this.businessApi.getActiveBusinesses();
711
+ }
712
+ /**
713
+ * Get all business types
714
+ */
715
+ async getAllBusinessTypes() {
716
+ return this.businessApi.getAllBusinessTypes();
717
+ }
718
+ /**
719
+ * Get business by ID
720
+ */
721
+ async getBusinessById(businessId) {
722
+ return this.businessApi.getBusinessById(businessId);
723
+ }
724
+ /**
725
+ * Get business by account address
726
+ */
727
+ async getBusinessByAccount(accountAddress) {
728
+ return this.businessApi.getBusinessByAccount(accountAddress);
729
+ }
730
+ /**
731
+ * Get businesses by type (client-side filtering)
732
+ */
733
+ async getBusinessesByType(typeId) {
734
+ const businesses = await this.getActiveBusinesses();
735
+ return businesses.filter(business => business.businessType && business.businessType.id === parseInt(typeId));
736
+ }
737
+ // ==========================================
738
+ // ADMIN OPERATIONS
739
+ // ==========================================
740
+ /**
741
+ * ADMIN: Get all businesses (active and inactive)
742
+ */
743
+ async getAllBusinesses() {
744
+ return this.businessApi.getAllBusinesses();
745
+ }
746
+ /**
747
+ * ADMIN: Create business by display name
748
+ */
749
+ async createBusinessByDisplayName(displayName) {
750
+ return this.businessApi.createBusinessByDisplayName(displayName);
751
+ }
752
+ /**
753
+ * ADMIN: Update business
754
+ */
755
+ async updateBusiness(id, businessData) {
756
+ return this.businessApi.updateBusiness(id, businessData);
757
+ }
758
+ /**
759
+ * ADMIN: Toggle business active status
760
+ */
761
+ async toggleBusinessActive(id, isActive) {
762
+ return this.businessApi.toggleBusinessActive(id, isActive);
763
+ }
764
+ }
765
+
766
+ /**
767
+ * @explorins/pers-sdk-business
768
+ *
769
+ * Platform-agnostic Business Domain SDK for PERS ecosystem
770
+ * Focuses on non-admin business operations
771
+ */
772
+ // API Layer
773
+ /**
774
+ * Create a complete Business SDK instance
775
+ *
776
+ * @param apiClient - Configured PERS API client
777
+ * @returns Business SDK with flattened structure for better DX
778
+ */
779
+ function createBusinessSDK(apiClient) {
780
+ const businessApi = new BusinessApi(apiClient);
781
+ const businessService = new BusinessService(businessApi);
782
+ return {
783
+ // Direct access to service methods (primary interface)
784
+ getActiveBusinesses: () => businessService.getActiveBusinesses(),
785
+ getAllBusinessTypes: () => businessService.getAllBusinessTypes(),
786
+ getBusinessById: (businessId) => businessService.getBusinessById(businessId),
787
+ getBusinessByAccount: (accountAddress) => businessService.getBusinessByAccount(accountAddress),
788
+ getBusinessesByType: (typeId) => businessService.getBusinessesByType(typeId),
789
+ // Admin methods
790
+ getAllBusinesses: () => businessService.getAllBusinesses(),
791
+ createBusinessByDisplayName: (displayName) => businessService.createBusinessByDisplayName(displayName),
792
+ updateBusiness: (id, businessData) => businessService.updateBusiness(id, businessData),
793
+ toggleBusinessActive: (id, isActive) => businessService.toggleBusinessActive(id, isActive),
794
+ // Advanced access for edge cases
795
+ api: businessApi,
796
+ service: businessService
797
+ };
798
+ }
799
+
800
+ /**
801
+ * Platform-Agnostic Transaction API Client (UPDATED FOR NEW RESTful ENDPOINTS)
802
+ *
803
+ * Handles transaction operations using the PERS backend.
804
+ * Uses @explorins/pers-shared DTOs for consistency with backend.
805
+ *
806
+ * MIGRATION NOTES:
807
+ * - All endpoints changed from /transaction to /transactions
808
+ * - Role-based paths removed (no more /auth, /admin, /business in URLs)
809
+ * - New RESTful resource-based structure
810
+ * - Added new client-side transaction flow methods
811
+ * - Enhanced admin query capabilities
812
+ */
813
+ class TransactionApi {
814
+ constructor(apiClient) {
815
+ this.apiClient = apiClient;
816
+ this.basePath = '/transactions';
817
+ }
818
+ /**
819
+ * Get transaction by ID (public endpoint)
820
+ *
821
+ * UPDATED: /transaction/{id} → /transactions/{id}
822
+ */
823
+ async getTransactionById(transactionId) {
824
+ return this.apiClient.get(`${this.basePath}/${transactionId}`);
825
+ }
826
+ // ==========================================
827
+ // AUTHENTICATED USER OPERATIONS
828
+ // ==========================================
829
+ /**
830
+ * AUTH: Create authenticated user transaction
831
+ *
832
+ * UPDATED: /transaction/auth/transaction → /transactions/user
833
+ */
834
+ async createAuthTransaction(request) {
835
+ return this.apiClient.post(`${this.basePath}/user`, request);
836
+ }
837
+ /**
838
+ * AUTH: Get user's sent transactions
839
+ *
840
+ * UPDATED: /transaction/auth/sender → /transactions/me/sent
841
+ */
842
+ async getUserSentTransactions() {
843
+ const params = new URLSearchParams({
844
+ timestamp: Date.now().toString()
845
+ });
846
+ return this.apiClient.get(`${this.basePath}/me/sent?${params.toString()}`);
847
+ }
848
+ /**
849
+ * AUTH: Get user's received transactions
850
+ *
851
+ * UPDATED: /transaction/auth/recipient → /transactions/me/received
852
+ */
853
+ async getUserReceivedTransactions() {
854
+ const params = new URLSearchParams({
855
+ timestamp: Date.now().toString()
856
+ });
857
+ return this.apiClient.get(`${this.basePath}/me/received?${params.toString()}`);
858
+ }
859
+ /**
860
+ * AUTH: Get user transaction history by type (backwards compatibility)
861
+ *
862
+ * UPDATED: Maps to appropriate /transactions/me/* endpoints
863
+ */
864
+ async getUserTransactionHistory(type) {
865
+ const params = new URLSearchParams({
866
+ timestamp: Date.now().toString()
867
+ });
868
+ // Map legacy type parameter to new endpoints
869
+ switch (type.toLowerCase()) {
870
+ case 'sender':
871
+ case 'sent':
872
+ return this.apiClient.get(`${this.basePath}/me/sent?${params.toString()}`);
873
+ case 'recipient':
874
+ case 'received':
875
+ return this.apiClient.get(`${this.basePath}/me/received?${params.toString()}`);
876
+ default:
877
+ // Default to sent transactions for backwards compatibility
878
+ return this.apiClient.get(`${this.basePath}/me/sent?${params.toString()}`);
879
+ }
880
+ }
881
+ /**
882
+ * AUTH: Prepare client signed transaction
883
+ *
884
+ * UPDATED: /transaction/auth/prepare-signing → /transactions/prepare
885
+ */
886
+ async prepareClientSignedTransaction(request) {
887
+ return this.apiClient.post(`${this.basePath}/prepare`, request);
888
+ }
889
+ /**
890
+ * AUTH: Prepare existing transaction for client-side signing
891
+ *
892
+ * NEW ENDPOINT: GET /transactions/{id}/prepare
893
+ */
894
+ async prepareExistingTransaction(transactionId) {
895
+ return this.apiClient.get(`${this.basePath}/${transactionId}/prepare`);
896
+ }
897
+ /**
898
+ * AUTH: Submit client-side signed transaction
899
+ *
900
+ * NEW ENDPOINT: POST /transactions/{id}/submit
901
+ */
902
+ async submitSignedTransaction(transactionId, signedData) {
903
+ return this.apiClient.post(`${this.basePath}/${transactionId}/submit`, signedData);
904
+ }
905
+ /**
906
+ * AUTH: Burn user tokens
907
+ *
908
+ * UPDATED: Uses new user transaction endpoint with burn-specific parameters
909
+ * Note: This might need backend confirmation on burn functionality implementation
910
+ */
911
+ async burnUserTokens(request) {
912
+ // Map burn request to TransactionRequestDTO format for new endpoint
913
+ const transactionRequest = {
914
+ ...request,
915
+ // Add any specific burn transaction parameters here
916
+ };
917
+ return this.apiClient.post(`${this.basePath}/user`, transactionRequest);
918
+ }
919
+ // ==========================================
920
+ // BUSINESS OPERATIONS
921
+ // ==========================================
922
+ /**
923
+ * BUSINESS: Create business transaction
924
+ *
925
+ * UPDATED: /transaction/business/transaction → /transactions/business
926
+ */
927
+ async createBusinessTransaction(request) {
928
+ return this.apiClient.post(`${this.basePath}/business`, request);
929
+ }
930
+ // ==========================================
931
+ // ADMIN OPERATIONS
932
+ // ==========================================
933
+ /**
934
+ * ADMIN: Create admin transaction
935
+ *
936
+ * UPDATED: /transaction/admin/transaction → /transactions/admin
937
+ */
938
+ async createAdminTransaction(request) {
939
+ return this.apiClient.post(`${this.basePath}/system`, request);
940
+ }
941
+ /**
942
+ * ADMIN: Get all tenant transactions
943
+ *
944
+ * UPDATED: /transaction/admin → /transactions
945
+ */
946
+ async getTenantTransactions() {
947
+ const result = await this.apiClient.get(`${this.basePath}`);
948
+ // If the new endpoint returns paginated response, extract the data array
949
+ if ('data' in result) {
950
+ return result.data;
951
+ }
952
+ // Fallback for direct array response
953
+ return result;
954
+ }
955
+ /**
956
+ * ADMIN: Get paginated transactions with filtering and sorting
957
+ *
958
+ * UPDATED: /transaction/admin → /transactions (same endpoint, better structure)
959
+ */
960
+ async getPaginatedTransactions(params) {
961
+ // Build query parameters
962
+ const queryParams = {
963
+ page: params.page.toString(),
964
+ limit: params.limit.toString()
965
+ };
966
+ // Add sorting parameters if provided
967
+ if (params.sortBy) {
968
+ queryParams['sortBy'] = params.sortBy;
969
+ }
970
+ if (params.sortOrder) {
971
+ queryParams['sortOrder'] = params.sortOrder;
972
+ }
973
+ // Add user-specific filtering if provided
974
+ if (params.participantId) {
975
+ queryParams['participantId'] = params.participantId;
976
+ }
977
+ // Add status filtering if provided
978
+ if (params.status) {
979
+ queryParams['status'] = params.status;
980
+ }
981
+ // Add additional filters if provided
982
+ if (params.filters) {
983
+ if (params.filters.startDate) {
984
+ queryParams['startDate'] = params.filters.startDate;
985
+ }
986
+ if (params.filters.endDate) {
987
+ queryParams['endDate'] = params.filters.endDate;
988
+ }
989
+ if (params.filters.type && params.filters.type.length > 0) {
990
+ queryParams['type'] = params.filters.type.join(',');
991
+ }
992
+ if (params.filters.tokenType && params.filters.tokenType.length > 0) {
993
+ queryParams['tokenType'] = params.filters.tokenType.join(',');
994
+ }
995
+ }
996
+ // Build query string
997
+ const queryString = Object.entries(queryParams)
998
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
999
+ .join('&');
1000
+ return this.apiClient.get(`${this.basePath}?${queryString}`);
1001
+ }
1002
+ /**
1003
+ * ADMIN: Export transactions to CSV
1004
+ *
1005
+ * UPDATED: /transaction/admin/export/csv → /transactions/export/csv
1006
+ */
1007
+ async exportTransactionsCSV() {
1008
+ return this.apiClient.get(`${this.basePath}/export/csv`, 'blob');
1009
+ }
1010
+ // ==========================================
1011
+ // NEW ADMIN QUERY METHODS
1012
+ // ==========================================
1013
+ /**
1014
+ * ADMIN: Query transactions by sender
1015
+ *
1016
+ * NEW ENDPOINT: POST /transactions/query-sender
1017
+ */
1018
+ async queryTransactionsBySender(accountSelector) {
1019
+ return this.apiClient.post(`${this.basePath}/query-sender`, accountSelector);
1020
+ }
1021
+ /**
1022
+ * ADMIN: Query transactions by recipient
1023
+ *
1024
+ * NEW ENDPOINT: POST /transactions/query-recipient
1025
+ */
1026
+ async queryTransactionsByRecipient(accountSelector) {
1027
+ return this.apiClient.post(`${this.basePath}/query-recipient`, accountSelector);
1028
+ }
1029
+ /**
1030
+ * ADMIN: Get transaction analytics
1031
+ *
1032
+ * NEW ENDPOINT: POST /transactions/analytics
1033
+ */
1034
+ async getTransactionAnalytics(analyticsRequest) {
1035
+ return this.apiClient.post(`${this.basePath}/analytics`, analyticsRequest);
1036
+ }
1037
+ // ==========================================
1038
+ // CONVENIENCE METHODS (BACKWARDS COMPATIBILITY)
1039
+ // ==========================================
1040
+ /**
1041
+ * Convenience method: Get user sent transactions (alias)
1042
+ */
1043
+ async getUserSenderTransactions() {
1044
+ return this.getUserSentTransactions();
1045
+ }
1046
+ /**
1047
+ * Convenience method: Get user received transactions (alias)
1048
+ */
1049
+ async getUserRecipientTransactions() {
1050
+ return this.getUserReceivedTransactions();
1051
+ }
1052
+ }
1053
+
1054
+ /**
1055
+ * Platform-Agnostic Transaction Service
1056
+ *
1057
+ * Contains transaction business logic and operations that work across platforms.
1058
+ * No framework dependencies - pure TypeScript business logic.
1059
+ *
1060
+ * Focuses only on actual backend capabilities.
1061
+ */
1062
+ class TransactionService {
1063
+ constructor(transactionApi) {
1064
+ this.transactionApi = transactionApi;
1065
+ }
1066
+ /**
1067
+ * Get transaction by ID
1068
+ */
1069
+ async getTransactionById(transactionId) {
1070
+ return this.transactionApi.getTransactionById(transactionId);
1071
+ }
1072
+ // ==========================================
1073
+ // AUTHENTICATED OPERATIONS
1074
+ // ==========================================
1075
+ /**
1076
+ * AUTH: Create authenticated transaction
1077
+ */
1078
+ async createAuthTransaction(request) {
1079
+ return this.transactionApi.createAuthTransaction(request);
1080
+ }
1081
+ /**
1082
+ * AUTH: Get user transaction history by type
1083
+ */
1084
+ async getUserTransactionHistory(type) {
1085
+ return this.transactionApi.getUserTransactionHistory(type);
1086
+ }
1087
+ /**
1088
+ * AUTH: Prepare client signed transaction
1089
+ */
1090
+ async prepareClientSignedTransaction(request) {
1091
+ return this.transactionApi.prepareClientSignedTransaction(request);
1092
+ }
1093
+ /**
1094
+ * AUTH: Burn user tokens
1095
+ */
1096
+ async burnUserTokens(request) {
1097
+ return this.transactionApi.burnUserTokens(request);
1098
+ }
1099
+ // ==========================================
1100
+ // BUSINESS OPERATIONS
1101
+ // ==========================================
1102
+ /**
1103
+ * BUSINESS: Create business transaction
1104
+ */
1105
+ async createBusinessTransaction(request) {
1106
+ return this.transactionApi.createBusinessTransaction(request);
1107
+ }
1108
+ // ==========================================
1109
+ // ADMIN OPERATIONS
1110
+ // ==========================================
1111
+ /**
1112
+ * ADMIN: Create admin transaction
1113
+ */
1114
+ async createAdminTransaction(request) {
1115
+ return this.transactionApi.createAdminTransaction(request);
1116
+ }
1117
+ /**
1118
+ * ADMIN: Get all tenant transactions
1119
+ */
1120
+ async getTenantTransactions() {
1121
+ return this.transactionApi.getTenantTransactions();
1122
+ }
1123
+ /**
1124
+ * ADMIN: Get paginated transactions with filtering and sorting
1125
+ */
1126
+ async getPaginatedTransactions(params) {
1127
+ return this.transactionApi.getPaginatedTransactions(params);
1128
+ }
1129
+ /**
1130
+ * ADMIN: Export transactions to CSV
1131
+ */
1132
+ async exportTransactionsCSV() {
1133
+ return this.transactionApi.exportTransactionsCSV();
1134
+ }
1135
+ }
1136
+
1137
+ /**
1138
+ * Transaction Domain Models
1139
+ *
1140
+ * Re-exports from @explorins/pers-shared for consistency with backend
1141
+ * and to provide a single import source for transaction-related types.
1142
+ */
1143
+ // Transaction account types (domain-specific enum)
1144
+ exports.TransactionAccountType = void 0;
1145
+ (function (TransactionAccountType) {
1146
+ // Add specific transaction account types as needed
1147
+ // This should match the enum used in the infrastructure layer
1148
+ })(exports.TransactionAccountType || (exports.TransactionAccountType = {}));
1149
+
1150
+ /**
1151
+ * @explorins/pers-sdk-transaction
1152
+ *
1153
+ * Platform-agnostic Transaction Domain SDK for PERS ecosystem
1154
+ * Handles transaction operations across different authorization levels
1155
+ */
1156
+ // API Layer
1157
+ /**
1158
+ * Create a complete Transaction SDK instance
1159
+ *
1160
+ * @param apiClient - Configured PERS API client
1161
+ * @returns Transaction SDK with flattened structure for better DX
1162
+ */
1163
+ function createTransactionSDK(apiClient) {
1164
+ const transactionApi = new TransactionApi(apiClient);
1165
+ const transactionService = new TransactionService(transactionApi);
1166
+ return {
1167
+ // Direct access to service methods (primary interface)
1168
+ // Public methods
1169
+ getTransactionById: (transactionId) => transactionService.getTransactionById(transactionId),
1170
+ // Auth methods
1171
+ createAuthTransaction: (request) => transactionService.createAuthTransaction(request),
1172
+ getUserTransactionHistory: (type) => transactionService.getUserTransactionHistory(type),
1173
+ prepareClientSignedTransaction: (request) => transactionService.prepareClientSignedTransaction(request),
1174
+ burnUserTokens: (request) => transactionService.burnUserTokens(request),
1175
+ // Business methods
1176
+ createBusinessTransaction: (request) => transactionService.createBusinessTransaction(request),
1177
+ // Admin methods
1178
+ createAdminTransaction: (request) => transactionService.createAdminTransaction(request),
1179
+ getTenantTransactions: () => transactionService.getTenantTransactions(),
1180
+ getPaginatedTransactions: (params) => transactionService.getPaginatedTransactions(params),
1181
+ exportTransactionsCSV: () => transactionService.exportTransactionsCSV(),
1182
+ // Advanced access for edge cases
1183
+ api: transactionApi,
1184
+ service: transactionService
1185
+ };
1186
+ }
1187
+
1188
+ /**
1189
+ * Platform-Agnostic Analytics API Client
1190
+ *
1191
+ * Handles analytics operations using the PERS backend.
1192
+ * Uses @explorins/pers-shared DTOs for consistency with backend.
1193
+ */
1194
+ class AnalyticsApi {
1195
+ constructor(apiClient) {
1196
+ this.apiClient = apiClient;
1197
+ }
1198
+ // ==========================================
1199
+ // ADMIN OPERATIONS
1200
+ // ==========================================
1201
+ /**
1202
+ * ADMIN: Get transaction analytics with filtering and aggregation
1203
+ */
1204
+ async getTransactionAnalytics(request) {
1205
+ return this.apiClient.post('/transactions/analytics', request);
1206
+ }
1207
+ }
1208
+
1209
+ /**
1210
+ * Platform-Agnostic Analytics Service
1211
+ *
1212
+ * Contains analytics business logic and operations that work across platforms.
1213
+ * No framework dependencies - pure TypeScript business logic.
1214
+ *
1215
+ * Focuses only on actual backend capabilities.
1216
+ */
1217
+ class AnalyticsService {
1218
+ constructor(analyticsApi) {
1219
+ this.analyticsApi = analyticsApi;
1220
+ }
1221
+ // ==========================================
1222
+ // ADMIN OPERATIONS
1223
+ // ==========================================
1224
+ /**
1225
+ * ADMIN: Get transaction analytics with filtering and aggregation
1226
+ */
1227
+ async getTransactionAnalytics(request) {
1228
+ return this.analyticsApi.getTransactionAnalytics(request);
1229
+ }
1230
+ }
1231
+
1232
+ /**
1233
+ * @explorins/pers-sdk-analytics
1234
+ *
1235
+ * Platform-agnostic Analytics Domain SDK for PERS ecosystem
1236
+ * Handles analytics operations and data aggregation
1237
+ */
1238
+ // API Layer
1239
+ /**
1240
+ * Create a complete Analytics SDK instance
1241
+ *
1242
+ * @param apiClient - Configured PERS API client
1243
+ * @returns Analytics SDK with flattened structure for better DX
1244
+ */
1245
+ function createAnalyticsSDK(apiClient) {
1246
+ const analyticsApi = new AnalyticsApi(apiClient);
1247
+ const analyticsService = new AnalyticsService(analyticsApi);
1248
+ return {
1249
+ // Direct access to service methods (primary interface)
1250
+ // Admin methods
1251
+ getTransactionAnalytics: (request) => analyticsService.getTransactionAnalytics(request),
1252
+ // Advanced access for edge cases
1253
+ api: analyticsApi,
1254
+ service: analyticsService
1255
+ };
1256
+ }
1257
+
1258
+ /**
1259
+ * Platform-Agnostic Auth Admin API Client
1260
+ *
1261
+ * Handles authentication and authorization admin operations using the PERS backend.
1262
+ * Uses @explorins/pers-shared DTOs for consistency with backend.
1263
+ *
1264
+ * Note: Special header handling (bypass-auth-interceptor) may need to be implemented
1265
+ * at the PersApiClient level or through a specialized auth client.
1266
+ */
1267
+ class AuthAdminApi {
1268
+ constructor(apiClient) {
1269
+ this.apiClient = apiClient;
1270
+ this.basePath = '/auth';
1271
+ }
1272
+ // ==========================================
1273
+ // ADMIN AUTHENTICATION OPERATIONS
1274
+ // ==========================================
1275
+ /**
1276
+ * ADMIN: Login tenant admin with JWT
1277
+ * Note: JWT handling and auth bypass headers may need special implementation
1278
+ */
1279
+ async loginTenantAdmin(jwt) {
1280
+ // TODO: Implement proper JWT and bypass header handling when PersApiClient supports it
1281
+ return this.apiClient.post(`${this.basePath}/token`, {});
1282
+ }
1283
+ /**
1284
+ * ADMIN: Refresh access token
1285
+ * Note: Bypass header handling may need special implementation
1286
+ */
1287
+ async refreshAccessToken(refreshToken) {
1288
+ return this.apiClient.post(`${this.basePath}/refresh`, { refreshToken });
1289
+ }
1290
+ }
1291
+
1292
+ /**
1293
+ * Platform-Agnostic Auth Admin Service
1294
+ *
1295
+ * Contains auth admin business logic and operations that work across platforms.
1296
+ * No framework dependencies - pure TypeScript business logic.
1297
+ *
1298
+ * Focuses only on actual backend capabilities.
1299
+ */
1300
+ class AuthAdminService {
1301
+ constructor(authAdminApi) {
1302
+ this.authAdminApi = authAdminApi;
1303
+ }
1304
+ // ==========================================
1305
+ // ADMIN AUTHENTICATION OPERATIONS
1306
+ // ==========================================
1307
+ /**
1308
+ * ADMIN: Login tenant admin with JWT
1309
+ */
1310
+ async loginTenantAdmin(jwt) {
1311
+ return this.authAdminApi.loginTenantAdmin(jwt);
1312
+ }
1313
+ /**
1314
+ * ADMIN: Refresh access token
1315
+ */
1316
+ async refreshAccessToken(refreshToken) {
1317
+ return this.authAdminApi.refreshAccessToken(refreshToken);
1318
+ }
1319
+ }
1320
+
1321
+ /**
1322
+ * @explorins/pers-sdk-auth-admin
1323
+ *
1324
+ * Platform-agnostic Auth Admin Domain SDK for PERS ecosystem
1325
+ * Handles authentication and authorization admin operations
1326
+ */
1327
+ // API Layer
1328
+ /**
1329
+ * Create a complete Auth Admin SDK instance
1330
+ *
1331
+ * @param apiClient - Configured PERS API client
1332
+ * @returns Auth Admin SDK with flattened structure for better DX
1333
+ */
1334
+ function createAuthAdminSDK(apiClient) {
1335
+ const authAdminApi = new AuthAdminApi(apiClient);
1336
+ const authAdminService = new AuthAdminService(authAdminApi);
1337
+ return {
1338
+ // Direct access to service methods (primary interface)
1339
+ // Admin authentication methods
1340
+ loginTenantAdmin: (jwt) => authAdminService.loginTenantAdmin(jwt),
1341
+ refreshAccessToken: (refreshToken) => authAdminService.refreshAccessToken(refreshToken),
1342
+ // Advanced access for edge cases
1343
+ api: authAdminApi,
1344
+ service: authAdminService
1345
+ };
1346
+ }
1347
+
1348
+ /**
1349
+ * Platform-Agnostic Campaign API Client (NEW - RESTful Design)
1350
+ *
1351
+ * Updated to use the new microservice-ready campaign controllers:
1352
+ * - CampaignsController: Core campaign operations
1353
+ * - CampaignTagsController: Tag management
1354
+ * - CampaignTokensController: Token unit operations
1355
+ * - CampaignTriggersController: Trigger system
1356
+ * - CampaignEngagementsController: Business relationships
1357
+ * - CampaignClaimsController: Claims processing
1358
+ *
1359
+ * Uses @explorins/pers-shared DTOs for consistency with backend.
1360
+ * All endpoints updated to new RESTful patterns without role revelation.
1361
+ */
1362
+ class CampaignApi {
1363
+ constructor(apiClient) {
1364
+ this.apiClient = apiClient;
1365
+ }
1366
+ // ==========================================
1367
+ // CORE CAMPAIGN OPERATIONS (/campaigns)
1368
+ // ==========================================
1369
+ /**
1370
+ * PUBLIC: Get all active campaigns
1371
+ * NEW: /campaigns (intelligent access detection)
1372
+ */
1373
+ async getActiveCampaigns() {
1374
+ return this.apiClient.get('/campaigns');
1375
+ }
1376
+ /**
1377
+ * ADMIN: Get campaigns with filtering options
1378
+ * NEW: /campaigns with query parameters (admin access detected automatically)
1379
+ */
1380
+ async getCampaigns(options) {
1381
+ let url = '/campaigns';
1382
+ const params = [];
1383
+ if (options) {
1384
+ if (options.active !== undefined)
1385
+ params.push(`active=${options.active}`);
1386
+ if (options.tag)
1387
+ params.push(`tag=${encodeURIComponent(options.tag)}`);
1388
+ if (options.limit)
1389
+ params.push(`limit=${options.limit}`);
1390
+ if (options.offset)
1391
+ params.push(`offset=${options.offset}`);
1392
+ if (options.sort)
1393
+ params.push(`sort=${options.sort}`);
1394
+ if (options.order)
1395
+ params.push(`order=${options.order}`);
1396
+ }
1397
+ if (params.length > 0) {
1398
+ url += `?${params.join('&')}`;
1399
+ }
1400
+ return this.apiClient.get(url);
1401
+ }
1402
+ /**
1403
+ * PUBLIC: Get campaign by ID
1404
+ * NEW: /campaigns/{id}
1405
+ */
1406
+ async getCampaignById(id) {
1407
+ return this.apiClient.get(`/campaigns/${id}`);
1408
+ }
1409
+ /**
1410
+ * ADMIN: Create campaign
1411
+ * NEW: POST /campaigns
1412
+ */
1413
+ async createCampaign(campaign) {
1414
+ return this.apiClient.post('/campaigns', campaign);
1415
+ }
1416
+ /**
1417
+ * ADMIN: Update campaign
1418
+ * NEW: PUT /campaigns/{id}
1419
+ */
1420
+ async updateCampaign(campaignId, campaign) {
1421
+ return this.apiClient.put(`/campaigns/${campaignId}`, campaign);
1422
+ }
1423
+ /**
1424
+ * ADMIN: Toggle campaign active status
1425
+ * NEW: PUT /campaigns/{id}/status
1426
+ */
1427
+ async toggleCampaignActive(campaignId) {
1428
+ return this.apiClient.put(`/campaigns/${campaignId}/status`, {});
1429
+ }
1430
+ /**
1431
+ * ADMIN: Toggle campaign testnet environment
1432
+ * NEW: PUT /campaigns/{id}/environment
1433
+ */
1434
+ async toggleCampaignTestnet(campaignId) {
1435
+ return this.apiClient.put(`/campaigns/${campaignId}/environment`, {});
1436
+ }
1437
+ /**
1438
+ * ADMIN: Delete campaign
1439
+ * NEW: DELETE /campaigns/{id}
1440
+ */
1441
+ async deleteCampaign(campaignId) {
1442
+ return this.apiClient.delete(`/campaigns/${campaignId}`);
1443
+ }
1444
+ // ==========================================
1445
+ // TAG MANAGEMENT (/campaign-tags)
1446
+ // ==========================================
1447
+ /**
1448
+ * ADMIN: Get all unique campaign tags
1449
+ * NEW: GET /campaign-tags
1450
+ */
1451
+ async getAllUniqueTags() {
1452
+ return this.apiClient.get('/campaign-tags');
1453
+ }
1454
+ /**
1455
+ * ADMIN: Update campaign tags (replace all)
1456
+ * NEW: PUT /campaign-tags/{id}
1457
+ */
1458
+ async updateCampaignTags(campaignId, tags) {
1459
+ return this.apiClient.put(`/campaign-tags/${campaignId}`, { tags });
1460
+ }
1461
+ /**
1462
+ * ADMIN: Add tags to campaign
1463
+ * NEW: POST /campaign-tags/{id}
1464
+ */
1465
+ async addTagsToCampaign(campaignId, tags) {
1466
+ return this.apiClient.post(`/campaign-tags/${campaignId}`, { tags });
1467
+ }
1468
+ /**
1469
+ * ADMIN: Remove tag from campaign
1470
+ * NEW: DELETE /campaign-tags/{id}/{tag}
1471
+ */
1472
+ async removeTagFromCampaign(campaignId, tag) {
1473
+ return this.apiClient.delete(`/campaign-tags/${campaignId}/${encodeURIComponent(tag)}`);
1474
+ }
1475
+ // ==========================================
1476
+ // TOKEN MANAGEMENT (/campaign-tokens)
1477
+ // ==========================================
1478
+ /**
1479
+ * ADMIN: Create campaign token unit
1480
+ * NEW: POST /campaign-tokens/{id}
1481
+ */
1482
+ async createCampaignTokenUnit(campaignId, campaignTokenUnit) {
1483
+ return this.apiClient.post(`/campaign-tokens/${campaignId}`, campaignTokenUnit);
1484
+ }
1485
+ /**
1486
+ * ADMIN: Update campaign token unit
1487
+ * NEW: PUT /campaign-tokens/{id}/{tokenUnitId}
1488
+ */
1489
+ async updateCampaignTokenUnit(campaignId, tokenUnitId, campaignTokenUnit) {
1490
+ return this.apiClient.put(`/campaign-tokens/${campaignId}/${tokenUnitId}`, campaignTokenUnit);
1491
+ }
1492
+ /**
1493
+ * ADMIN: Delete campaign token unit
1494
+ * NEW: DELETE /campaign-tokens/{id}/{tokenUnitId}
1495
+ */
1496
+ async deleteCampaignTokenUnit(campaignId, campaignTokenUnitId) {
1497
+ return this.apiClient.delete(`/campaign-tokens/${campaignId}/${campaignTokenUnitId}`);
1498
+ }
1499
+ // ==========================================
1500
+ // TRIGGER SYSTEM (/campaign-triggers)
1501
+ // ==========================================
1502
+ /**
1503
+ * PUBLIC: Get campaign triggers catalog
1504
+ * NEW: GET /campaign-triggers
1505
+ */
1506
+ async getCampaignTriggers() {
1507
+ return this.apiClient.get('/campaign-triggers');
1508
+ }
1509
+ /**
1510
+ * ADMIN: Create campaign trigger
1511
+ * NEW: POST /campaign-triggers
1512
+ */
1513
+ async createCampaignTrigger(trigger) {
1514
+ return this.apiClient.post('/campaign-triggers', trigger);
1515
+ }
1516
+ /**
1517
+ * ADMIN: Update campaign trigger
1518
+ * NEW: PUT /campaign-triggers/{id}
1519
+ */
1520
+ async updateCampaignTrigger(triggerId, trigger) {
1521
+ return this.apiClient.put(`/campaign-triggers/${triggerId}`, trigger);
1522
+ }
1523
+ /**
1524
+ * ADMIN: Delete campaign trigger
1525
+ * NEW: DELETE /campaign-triggers/{id}
1526
+ */
1527
+ async deleteCampaignTrigger(triggerId) {
1528
+ return this.apiClient.delete(`/campaign-triggers/${triggerId}`);
1529
+ }
1530
+ /**
1531
+ * ADMIN: Set campaign trigger
1532
+ * NEW: PUT /campaign-triggers/campaigns/{id}/trigger/{triggerId}
1533
+ */
1534
+ async setCampaignTrigger(campaignId, triggerId) {
1535
+ return this.apiClient.put(`/campaign-triggers/campaigns/${campaignId}/trigger/${triggerId}`, {});
1536
+ }
1537
+ /**
1538
+ * ADMIN: Create trigger condition
1539
+ * NEW: POST /campaign-triggers/conditions
1540
+ */
1541
+ async createTriggerCondition(condition) {
1542
+ return this.apiClient.post('/campaign-triggers/conditions', condition);
1543
+ }
1544
+ /**
1545
+ * ADMIN: Update trigger condition
1546
+ * NEW: PUT /campaign-triggers/conditions/{id}
1547
+ */
1548
+ async updateTriggerCondition(conditionId, condition) {
1549
+ return this.apiClient.put(`/campaign-triggers/conditions/${conditionId}`, condition);
1550
+ }
1551
+ /**
1552
+ * ADMIN: Add/Remove condition to trigger
1553
+ * NEW: PUT /campaign-triggers/{triggerId}/condition/{conditionId}
1554
+ */
1555
+ async addOrRemoveConditionToTrigger(triggerId, conditionId) {
1556
+ return this.apiClient.put(`/campaign-triggers/${triggerId}/condition/${conditionId}`, {});
1557
+ }
1558
+ // ==========================================
1559
+ // BUSINESS ENGAGEMENTS (/campaign-engagements)
1560
+ // ==========================================
1561
+ /**
1562
+ * ADMIN: Add business engagement to campaign
1563
+ * NEW: POST /campaign-engagements/{id}
1564
+ */
1565
+ async addBusinessEngagementToCampaign(campaignId, campaignBusinessEngagement) {
1566
+ return this.apiClient.post(`/campaign-engagements/${campaignId}`, campaignBusinessEngagement);
1567
+ }
1568
+ /**
1569
+ * ADMIN: Update campaign business engagement
1570
+ * NEW: PUT /campaign-engagements/{id}/{businessEngagementId}
1571
+ */
1572
+ async updateCampaignBusinessEngagement(campaignId, businessEngagementId, campaignBusinessEngagement) {
1573
+ return this.apiClient.put(`/campaign-engagements/${campaignId}/${businessEngagementId}`, campaignBusinessEngagement);
1574
+ }
1575
+ /**
1576
+ * ADMIN: Delete campaign business engagement
1577
+ * NEW: DELETE /campaign-engagements/{id}/{businessEngagementId}
1578
+ */
1579
+ async deleteCampaignBusinessEngagement(campaignId, businessEngagementId) {
1580
+ return this.apiClient.delete(`/campaign-engagements/${campaignId}/${businessEngagementId}`);
1581
+ }
1582
+ // ==========================================
1583
+ // CLAIMS PROCESSING (/campaign-claims)
1584
+ // ==========================================
1585
+ /**
1586
+ * USER: Claim campaign reward
1587
+ * NEW: POST /campaign-claims/user
1588
+ */
1589
+ async claimCampaign(request) {
1590
+ return this.apiClient.post('/campaign-claims/user', request);
1591
+ }
1592
+ /**
1593
+ * USER: Get claims for logged user
1594
+ * NEW: GET /campaign-claims/users/me
1595
+ */
1596
+ async getClaimsForLoggedUser() {
1597
+ return this.apiClient.get('/campaign-claims/users/me');
1598
+ }
1599
+ /**
1600
+ * BUSINESS: Claim campaign reward for customer
1601
+ * NEW: POST /campaign-claims/business
1602
+ */
1603
+ async businessClaimCampaign(request) {
1604
+ return this.apiClient.post('/campaign-claims/business', request);
1605
+ }
1606
+ /**
1607
+ * SYSTEM: Process automated claim
1608
+ * NEW: POST /campaign-claims/system
1609
+ */
1610
+ async systemClaimCampaign(request) {
1611
+ return this.apiClient.post('/campaign-claims/system', request);
1612
+ }
1613
+ /**
1614
+ * ADMIN: Manual claim processing
1615
+ * NEW: POST /campaign-claims/admin
1616
+ */
1617
+ async adminClaimCampaign(request) {
1618
+ return this.apiClient.post('/campaign-claims/admin', request);
1619
+ }
1620
+ /**
1621
+ * ADMIN: Get all campaign claims
1622
+ * NEW: GET /campaign-claims/admin
1623
+ */
1624
+ async getCampaignClaims() {
1625
+ return this.apiClient.get('/campaign-claims/admin');
1626
+ }
1627
+ /**
1628
+ * ADMIN: Get campaign claims by campaign ID
1629
+ * NEW: GET /campaign-claims/admin/{campaignId}
1630
+ */
1631
+ async getCampaignClaimsByCampaignId(campaignId) {
1632
+ return this.apiClient.get(`/campaign-claims/admin/${campaignId}`);
1633
+ }
1634
+ /**
1635
+ * ADMIN: Get campaign claims by user ID
1636
+ * NEW: GET /campaign-claims/admin/users/{userId}
1637
+ */
1638
+ async getCampaignClaimsByUserId(userId) {
1639
+ return this.apiClient.get(`/campaign-claims/admin/users/${userId}`);
1640
+ }
1641
+ /**
1642
+ * ADMIN: Get campaign claims by business ID
1643
+ * NEW: GET /campaign-claims/admin/businesses/{businessId}
1644
+ */
1645
+ async getCampaignClaimsByBusinessId(businessId) {
1646
+ return this.apiClient.get(`/campaign-claims/admin/businesses/${businessId}`);
1647
+ }
1648
+ /**
1649
+ * USER: Get user's claims for specific campaign
1650
+ * NEW: GET /campaign-claims/campaigns/{campaignId}/users/me
1651
+ */
1652
+ async getUserClaimsForCampaign(campaignId) {
1653
+ return this.apiClient.get(`/campaign-claims/campaigns/${campaignId}/users/me`);
1654
+ }
1655
+ // ==========================================
1656
+ // BACKWARD COMPATIBILITY (DEPRECATED)
1657
+ // ==========================================
1658
+ /**
1659
+ * @deprecated Use getCampaigns() instead
1660
+ * LEGACY: Get campaigns with active filter
1661
+ */
1662
+ async getCampaignsLegacy(active) {
1663
+ console.warn('CampaignApi.getCampaignsLegacy() is deprecated. Use getCampaigns() instead.');
1664
+ return this.getCampaigns(active !== undefined ? { active } : undefined);
1665
+ }
1666
+ }
1667
+
1668
+ /**
1669
+ * Platform-Agnostic Campaign Service
1670
+ *
1671
+ * Contains campaign business logic and operations that work across platforms.
1672
+ * No framework dependencies - pure TypeScript business logic.
1673
+ *
1674
+ * Focuses only on actual backend capabilities.
1675
+ */
1676
+ class CampaignService {
1677
+ constructor(campaignApi) {
1678
+ this.campaignApi = campaignApi;
1679
+ }
1680
+ // ==========================================
1681
+ // PUBLIC OPERATIONS
1682
+ // ==========================================
1683
+ /**
1684
+ * PUBLIC: Get all active campaigns
1685
+ */
1686
+ async getActiveCampaigns() {
1687
+ return this.campaignApi.getActiveCampaigns();
1688
+ }
1689
+ /**
1690
+ * PUBLIC: Get campaign by ID
1691
+ */
1692
+ async getCampaignById(id) {
1693
+ return this.campaignApi.getCampaignById(id);
1694
+ }
1695
+ // ==========================================
1696
+ // AUTHENTICATED OPERATIONS
1697
+ // ==========================================
1698
+ /**
1699
+ * AUTH: Claim campaign
1700
+ */
1701
+ async claimCampaign(request) {
1702
+ return this.campaignApi.claimCampaign(request);
1703
+ }
1704
+ /**
1705
+ * AUTH: Get claims for logged user
1706
+ */
1707
+ async getClaimsForLoggedUser() {
1708
+ return this.campaignApi.getClaimsForLoggedUser();
1709
+ }
1710
+ // ==========================================
1711
+ // ADMIN OPERATIONS
1712
+ // ==========================================
1713
+ /**
1714
+ * ADMIN: Get campaigns with optional active filter
1715
+ */
1716
+ async getCampaigns(active) {
1717
+ return this.campaignApi.getCampaigns(active !== undefined ? { active } : undefined);
1718
+ }
1719
+ /**
1720
+ * ADMIN: Get campaign triggers
1721
+ */
1722
+ async getCampaignTriggers() {
1723
+ return this.campaignApi.getCampaignTriggers();
1724
+ }
1725
+ /**
1726
+ * ADMIN: Toggle campaign active status
1727
+ */
1728
+ async toggleCampaignActive(campaignId) {
1729
+ return this.campaignApi.toggleCampaignActive(campaignId);
1730
+ }
1731
+ /**
1732
+ * ADMIN: Toggle campaign testnet environment
1733
+ */
1734
+ async toggleCampaignTestnet(campaignId) {
1735
+ return this.campaignApi.toggleCampaignTestnet(campaignId);
1736
+ }
1737
+ /**
1738
+ * ADMIN: Create campaign
1739
+ */
1740
+ async createCampaign(campaign) {
1741
+ return this.campaignApi.createCampaign(campaign);
1742
+ }
1743
+ /**
1744
+ * ADMIN: Set campaign trigger
1745
+ */
1746
+ async setCampaignTrigger(campaignId, triggerId) {
1747
+ return this.campaignApi.setCampaignTrigger(campaignId, triggerId);
1748
+ }
1749
+ /**
1750
+ * ADMIN: Update campaign
1751
+ */
1752
+ async updateCampaign(campaignId, campaign) {
1753
+ return this.campaignApi.updateCampaign(campaignId, campaign);
1754
+ }
1755
+ /**
1756
+ * ADMIN: Create campaign token unit
1757
+ */
1758
+ async createCampaignTokenUnit(campaignId, campaignTokenUnit) {
1759
+ return this.campaignApi.createCampaignTokenUnit(campaignId, campaignTokenUnit);
1760
+ }
1761
+ /**
1762
+ * ADMIN: Delete campaign token unit
1763
+ */
1764
+ async deleteCampaignTokenUnit(campaignId, campaignTokenUnitId) {
1765
+ return this.campaignApi.deleteCampaignTokenUnit(campaignId, campaignTokenUnitId);
1766
+ }
1767
+ /**
1768
+ * ADMIN: Add business engagement to campaign
1769
+ */
1770
+ async addBusinessEngagementToCampaign(campaignId, campaignBusinessEngagement) {
1771
+ return this.campaignApi.addBusinessEngagementToCampaign(campaignId, campaignBusinessEngagement);
1772
+ }
1773
+ /**
1774
+ * ADMIN: Update campaign business engagement
1775
+ */
1776
+ async updateCampaignBusinessEngagement(campaignId, businessEngagementId, campaignBusinessEngagement) {
1777
+ return this.campaignApi.updateCampaignBusinessEngagement(campaignId, businessEngagementId, campaignBusinessEngagement);
1778
+ }
1779
+ async deleteCampaignBusinessEngagement(campaignId, businessEngagementId) {
1780
+ return this.campaignApi.deleteCampaignBusinessEngagement(campaignId, businessEngagementId);
1781
+ }
1782
+ /**
1783
+ * ADMIN: Get all campaign claims
1784
+ */
1785
+ async getCampaignClaims() {
1786
+ return this.campaignApi.getCampaignClaims();
1787
+ }
1788
+ /**
1789
+ * ADMIN: Get campaign claims by user ID
1790
+ */
1791
+ async getCampaignClaimsByUserId(userId) {
1792
+ return this.campaignApi.getCampaignClaimsByUserId(userId);
1793
+ }
1794
+ /**
1795
+ * ADMIN: Get campaign claims by business ID
1796
+ */
1797
+ async getCampaignClaimsByBusinessId(businessId) {
1798
+ return this.campaignApi.getCampaignClaimsByBusinessId(businessId);
1799
+ }
1800
+ }
1801
+
1802
+ /**
1803
+ * @explorins/pers-sdk-campaign
1804
+ *
1805
+ * Platform-agnostic Campaign Domain SDK for PERS ecosystem
1806
+ * Handles campaign operations across different authorization levels
1807
+ */
1808
+ // API Layer
1809
+ /**
1810
+ * Create a complete Campaign SDK instance
1811
+ *
1812
+ * @param apiClient - Configured PERS API client
1813
+ * @returns Campaign SDK with flattened structure for better DX
1814
+ */
1815
+ function createCampaignSDK(apiClient) {
1816
+ const campaignApi = new CampaignApi(apiClient);
1817
+ const campaignService = new CampaignService(campaignApi);
1818
+ return {
1819
+ // Direct access to service methods (primary interface)
1820
+ // Public methods
1821
+ getActiveCampaigns: () => campaignService.getActiveCampaigns(),
1822
+ getCampaignById: (id) => campaignService.getCampaignById(id),
1823
+ // Auth methods
1824
+ claimCampaign: (request) => campaignService.claimCampaign(request),
1825
+ getClaimsForLoggedUser: () => campaignService.getClaimsForLoggedUser(),
1826
+ // Admin methods
1827
+ getCampaigns: (active) => campaignService.getCampaigns(active),
1828
+ getCampaignTriggers: () => campaignService.getCampaignTriggers(),
1829
+ toggleCampaignActive: (campaignId) => campaignService.toggleCampaignActive(campaignId),
1830
+ toggleCampaignTestnet: (campaignId) => campaignService.toggleCampaignTestnet(campaignId),
1831
+ createCampaign: (campaign) => campaignService.createCampaign(campaign),
1832
+ setCampaignTrigger: (campaignId, triggerId) => campaignService.setCampaignTrigger(campaignId, triggerId),
1833
+ updateCampaign: (campaignId, campaign) => campaignService.updateCampaign(campaignId, campaign),
1834
+ createCampaignTokenUnit: (campaignId, campaignTokenUnit) => campaignService.createCampaignTokenUnit(campaignId, campaignTokenUnit),
1835
+ deleteCampaignTokenUnit: (campaignId, campaignTokenUnitId) => campaignService.deleteCampaignTokenUnit(campaignId, campaignTokenUnitId),
1836
+ addBusinessEngagementToCampaign: (campaignId, campaignBusinessEngagement) => campaignService.addBusinessEngagementToCampaign(campaignId, campaignBusinessEngagement),
1837
+ updateCampaignBusinessEngagement: (campaignId, businessEngagementId, campaignBusinessEngagement) => campaignService.updateCampaignBusinessEngagement(campaignId, businessEngagementId, campaignBusinessEngagement),
1838
+ deleteCampaignBusinessEngagement: (campaignId, businessEngagementId) => campaignService.deleteCampaignBusinessEngagement(campaignId, businessEngagementId),
1839
+ getCampaignClaims: () => campaignService.getCampaignClaims(),
1840
+ getCampaignClaimsByUserId: (userId) => campaignService.getCampaignClaimsByUserId(userId),
1841
+ getCampaignClaimsByBusinessId: (businessId) => campaignService.getCampaignClaimsByBusinessId(businessId),
1842
+ // Advanced access for edge cases
1843
+ api: campaignApi,
1844
+ service: campaignService
1845
+ };
1846
+ }
1847
+
1848
+ /**
1849
+ * Platform-Agnostic Donation API Client
1850
+ *
1851
+ * Handles donation operations using the PERS backend.
1852
+ * Matches framework DonationApiService methods exactly.
1853
+ */
1854
+ class DonationApi {
1855
+ constructor(apiClient) {
1856
+ this.apiClient = apiClient;
1857
+ }
1858
+ // ==========================================
1859
+ // PUBLIC OPERATIONS
1860
+ // ==========================================
1861
+ /**
1862
+ * PUBLIC: Get all donation types
1863
+ * ✅ ONLY method actually used by framework
1864
+ */
1865
+ async getAllDonationTypes() {
1866
+ return this.apiClient.get('/purchase/donation/type');
1867
+ }
1868
+ }
1869
+
1870
+ /**
1871
+ * Platform-Agnostic Donation Service
1872
+ *
1873
+ * Contains donation business logic and operations that work across platforms.
1874
+ * No framework dependencies - pure TypeScript business logic.
1875
+ * Matches framework DonationApiService capabilities exactly.
1876
+ */
1877
+ class DonationService {
1878
+ constructor(donationApi) {
1879
+ this.donationApi = donationApi;
1880
+ }
1881
+ // ==========================================
1882
+ // PUBLIC OPERATIONS
1883
+ // ==========================================
1884
+ /**
1885
+ * PUBLIC: Get all donation types
1886
+ * ✅ ONLY method actually used by framework
1887
+ */
1888
+ async getAllDonationTypes() {
1889
+ return this.donationApi.getAllDonationTypes();
1890
+ }
1891
+ }
1892
+
1893
+ /**
1894
+ * @explorins/pers-sdk-donation
1895
+ *
1896
+ * Platform-agnostic Donation Domain SDK for PERS ecosystem
1897
+ * Handles donation type retrieval for purchase flow integration
1898
+ */
1899
+ // API Layer
1900
+ /**
1901
+ * Create a complete Donation SDK instance
1902
+ *
1903
+ * @param apiClient - Configured PERS API client
1904
+ * @returns Donation SDK with flattened structure for better DX
1905
+ */
1906
+ function createDonationSDK(apiClient) {
1907
+ const donationApi = new DonationApi(apiClient);
1908
+ const donationService = new DonationService(donationApi);
1909
+ return {
1910
+ // Direct access to service methods (primary interface)
1911
+ // ✅ FRAMEWORK ALIGNED: Only method actually used by framework
1912
+ // Public methods
1913
+ getAllDonationTypes: () => donationService.getAllDonationTypes(),
1914
+ // Advanced access for edge cases
1915
+ api: donationApi,
1916
+ service: donationService
1917
+ };
1918
+ }
1919
+
1920
+ /**
1921
+ * Platform-Agnostic Purchase API Client (RESTful Architecture)
1922
+ *
1923
+ * Handles purchase and payment operations using the PERS backend's new RESTful endpoints.
1924
+ * Uses @explorins/pers-shared DTOs for consistency with backend.
1925
+ *
1926
+ * Migration Status: Updated to match /purchases controller (replaces /purchase endpoints)
1927
+ *
1928
+ * Available Access Levels:
1929
+ * - PUBLIC: Project key authentication for catalog browsing and payment operations
1930
+ * - USER: Requires user authentication JWT (purchase creation, history access)
1931
+ * - ADMIN: Requires tenant admin privileges (not implemented in this client)
1932
+ *
1933
+ * Note: This SDK focuses on backend purchase operations only.
1934
+ * Payment provider integrations (Stripe, etc.) should remain in infrastructure layer.
1935
+ */
1936
+ class PurchaseApi {
1937
+ constructor(apiClient) {
1938
+ this.apiClient = apiClient;
1939
+ this.basePath = '/purchases';
1940
+ }
1941
+ // ==========================================
1942
+ // PUBLIC OPERATIONS (Project Key)
1943
+ // ==========================================
1944
+ /**
1945
+ * PUBLIC: Get purchase tokens (Intelligent Access)
1946
+ *
1947
+ * RESTful endpoint: GET /purchases/tokens
1948
+ * Replaces: GET /purchase/token
1949
+ *
1950
+ * INTELLIGENT ACCESS:
1951
+ * - PUBLIC (Project Key): Returns active tokens only (active parameter ignored)
1952
+ * - ADMIN (Tenant Admin JWT): Returns filtered results based on active parameter
1953
+ */
1954
+ async getPurchaseTokens(active) {
1955
+ let url = `${this.basePath}/tokens`;
1956
+ if (active !== undefined) {
1957
+ url += `?active=${active}`;
1958
+ }
1959
+ return this.apiClient.get(url);
1960
+ }
1961
+ /**
1962
+ * PUBLIC: Get donation types
1963
+ *
1964
+ * RESTful endpoint: GET /purchases/donation-types
1965
+ * Replaces: GET /purchase/donation/type
1966
+ */
1967
+ async getDonationTypes() {
1968
+ return this.apiClient.get(`${this.basePath}/donation-types`);
1969
+ }
1970
+ // ==========================================
1971
+ // PAYMENT OPERATIONS (FINANCIAL - CRITICAL)
1972
+ // ==========================================
1973
+ /**
1974
+ * PUBLIC: Create payment intent (FINANCIAL OPERATION)
1975
+ *
1976
+ * RESTful endpoint: POST /purchases/payment-intents
1977
+ * Replaces: POST /purchase/payment-intent
1978
+ *
1979
+ * CRITICAL: Handles real money operations - tenant context required
1980
+ */
1981
+ async createPaymentIntent(amount, currency, receiptEmail, description) {
1982
+ const body = {
1983
+ amount,
1984
+ currency,
1985
+ receiptEmail,
1986
+ description
1987
+ };
1988
+ return this.apiClient.post(`${this.basePath}/payment-intents`, body);
1989
+ }
1990
+ /**
1991
+ * PUBLIC: Update payment intent (FINANCIAL OPERATION)
1992
+ *
1993
+ * RESTful endpoint: PUT /purchases/payment-intents/{paymentIntentId}
1994
+ * Replaces: PUT /purchase/payment-intent/{paymentIntentId}
1995
+ *
1996
+ * CRITICAL: Handles real money operations - tenant context required
1997
+ */
1998
+ async updatePaymentIntent(paymentIntentId, amount, currency, receiptEmail, description) {
1999
+ const body = {
2000
+ amount,
2001
+ currency,
2002
+ receiptEmail,
2003
+ description
2004
+ };
2005
+ return this.apiClient.put(`${this.basePath}/payment-intents/${paymentIntentId}`, body);
2006
+ }
2007
+ /**
2008
+ * PUBLIC: Cancel payment intent (FINANCIAL OPERATION)
2009
+ *
2010
+ * RESTful endpoint: DELETE /purchases/payment-intents/{paymentIntentId}
2011
+ * Replaces: DELETE /purchase/payment-intent/{paymentIntentId}
2012
+ *
2013
+ * CRITICAL: Handles real money operations - tenant context required
2014
+ */
2015
+ async cancelPaymentIntent(paymentIntentId) {
2016
+ return this.apiClient.delete(`${this.basePath}/payment-intents/${paymentIntentId}`);
2017
+ }
2018
+ // ==========================================
2019
+ // USER OPERATIONS (JWT + Project Key)
2020
+ // ==========================================
2021
+ /**
2022
+ * USER: Create purchase (BUSINESS CRITICAL - FINANCIAL TRANSACTION)
2023
+ *
2024
+ * RESTful endpoint: POST /purchases
2025
+ * Replaces: POST /purchase/auth
2026
+ *
2027
+ * USER-ONLY: Requires user authentication JWT for purchase creation
2028
+ * CRITICAL: Real financial transaction with Stripe integration
2029
+ */
2030
+ async createUserPurchase(paymentIntentId, amount, purchaseTokenId, donationTypeId, donationAccountAddress) {
2031
+ const body = {
2032
+ quantity: amount,
2033
+ purchaseTokenId: purchaseTokenId || '',
2034
+ donationTypeId,
2035
+ donationAccountAddress,
2036
+ paymentIntentId
2037
+ };
2038
+ return this.apiClient.post(`${this.basePath}`, body);
2039
+ }
2040
+ /**
2041
+ * USER: Get user purchase history
2042
+ *
2043
+ * RESTful endpoint: GET /purchases/me/history
2044
+ * Replaces: GET /purchase/auth
2045
+ *
2046
+ * USER-ONLY: Get authenticated user's purchase history
2047
+ * FINANCIAL RECORDS: User attribution critical for compliance
2048
+ */
2049
+ async getUserPurchaseHistory() {
2050
+ return this.apiClient.get(`${this.basePath}/me/history`);
2051
+ }
2052
+ // ==========================================
2053
+ // CONVENIENCE METHODS (Backward Compatibility)
2054
+ // ==========================================
2055
+ /**
2056
+ * @deprecated Use getPurchaseTokens() instead
2057
+ * Backward compatibility alias for getActivePurchaseTokens
2058
+ */
2059
+ async getActivePurchaseTokens(active = true) {
2060
+ return this.getPurchaseTokens(active);
2061
+ }
2062
+ /**
2063
+ * @deprecated Use createUserPurchase() instead
2064
+ * Backward compatibility alias for createPurchase
2065
+ */
2066
+ async createPurchase(paymentIntentId, amount, purchaseTokenId, donationTypeId, donationAccountAddress) {
2067
+ return this.createUserPurchase(paymentIntentId, amount, purchaseTokenId, donationTypeId, donationAccountAddress);
2068
+ }
2069
+ /**
2070
+ * @deprecated Use getUserPurchaseHistory() instead
2071
+ * Backward compatibility alias for getAllUserPurchases
2072
+ */
2073
+ async getAllUserPurchases() {
2074
+ return this.getUserPurchaseHistory();
2075
+ }
2076
+ }
2077
+
2078
+ /**
2079
+ * Platform-Agnostic Payment Service
2080
+ *
2081
+ * Contains payment business logic and operations that work across platforms.
2082
+ * No framework dependencies - pure TypeScript business logic.
2083
+ *
2084
+ * Focuses only on actual backend capabilities.
2085
+ * Payment provider logic (Stripe, etc.) should remain in infrastructure layer.
2086
+ */
2087
+ class PaymentService {
2088
+ constructor(paymentApi) {
2089
+ this.paymentApi = paymentApi;
2090
+ }
2091
+ // ==========================================
2092
+ // PUBLIC OPERATIONS
2093
+ // ==========================================
2094
+ /**
2095
+ * PUBLIC: Get active purchase tokens
2096
+ */
2097
+ async getActivePurchaseTokens(active = true) {
2098
+ return this.paymentApi.getActivePurchaseTokens(active);
2099
+ }
2100
+ /**
2101
+ * PUBLIC: Create payment intent
2102
+ */
2103
+ async createPaymentIntent(amount, currency, receiptEmail, description) {
2104
+ return this.paymentApi.createPaymentIntent(amount, currency, receiptEmail, description);
2105
+ }
2106
+ /**
2107
+ * PUBLIC: Update payment intent
2108
+ */
2109
+ async updatePaymentIntent(paymentIntentId, amount, currency, receiptEmail, description) {
2110
+ return this.paymentApi.updatePaymentIntent(paymentIntentId, amount, currency, receiptEmail, description);
2111
+ }
2112
+ /**
2113
+ * PUBLIC: Cancel payment intent
2114
+ */
2115
+ async cancelPaymentIntent(paymentIntentId) {
2116
+ return this.paymentApi.cancelPaymentIntent(paymentIntentId);
2117
+ }
2118
+ // ==========================================
2119
+ // AUTHENTICATED OPERATIONS
2120
+ // ==========================================
2121
+ /**
2122
+ * AUTH: Create purchase
2123
+ */
2124
+ async createPurchase(paymentIntentId, amount, purchaseTokenId, donationTypeId, donationAccountAddress) {
2125
+ return this.paymentApi.createPurchase(paymentIntentId, amount, purchaseTokenId, donationTypeId, donationAccountAddress);
2126
+ }
2127
+ /**
2128
+ * AUTH: Get all user purchases
2129
+ */
2130
+ async getAllUserPurchases() {
2131
+ return this.paymentApi.getAllUserPurchases();
2132
+ }
2133
+ }
2134
+
2135
+ /**
2136
+ * @explorins/pers-sdk-payment
2137
+ *
2138
+ * Platform-agnostic Payment Domain SDK for PERS ecosystem
2139
+ * Handles payment intents, purchases, and purchase tokens
2140
+ *
2141
+ * Note: Payment provider integrations (Stripe, etc.) are kept separate
2142
+ * in the infrastructure layer to maintain platform-agnostic principles.
2143
+ */
2144
+ // API Layer
2145
+ /**
2146
+ * Create a complete Payment SDK instance
2147
+ *
2148
+ * @param apiClient - Configured PERS API client
2149
+ * @returns Payment SDK with flattened structure for better DX
2150
+ */
2151
+ function createPaymentSDK(apiClient) {
2152
+ const paymentApi = new PurchaseApi(apiClient);
2153
+ const paymentService = new PaymentService(paymentApi);
2154
+ return {
2155
+ // Direct access to service methods (primary interface)
2156
+ // Public methods
2157
+ getActivePurchaseTokens: (active) => paymentService.getActivePurchaseTokens(active),
2158
+ // ✅ FIXED: Proper type instead of any
2159
+ createPaymentIntent: (amount, currency, receiptEmail, description) => paymentService.createPaymentIntent(amount, currency, receiptEmail, description),
2160
+ // ✅ FIXED: Proper type instead of any
2161
+ updatePaymentIntent: (paymentIntentId, amount, currency, receiptEmail, description) => paymentService.updatePaymentIntent(paymentIntentId, amount, currency, receiptEmail, description),
2162
+ cancelPaymentIntent: (paymentIntentId) => paymentService.cancelPaymentIntent(paymentIntentId),
2163
+ // Auth methods
2164
+ createPurchase: (paymentIntentId, amount, purchaseTokenId, donationTypeId, donationAccountAddress) => paymentService.createPurchase(paymentIntentId, amount, purchaseTokenId, donationTypeId, donationAccountAddress),
2165
+ getAllUserPurchases: () => paymentService.getAllUserPurchases(),
2166
+ // Advanced access for edge cases
2167
+ api: paymentApi,
2168
+ service: paymentService
2169
+ };
2170
+ }
2171
+
2172
+ /**
2173
+ * Platform-Agnostic Redemption API Client (UPDATED - RESTful Design)
2174
+ *
2175
+ * Updated to work with the new RESTful /redemptions endpoints.
2176
+ * Handles redemption operations using the PERS backend with intelligent access detection.
2177
+ * Uses @explorins/pers-shared DTOs for consistency with backend.
2178
+ *
2179
+ * Migration Update: Updated all endpoints from /redemption to /redemptions
2180
+ * - Removed role revelation from URLs (no more /admin, /auth paths)
2181
+ * - Added intelligent access detection for unified endpoints
2182
+ * - Updated toggle endpoint to follow /status pattern
2183
+ * - Enhanced redemption process with path-based IDs
2184
+ */
2185
+ class RedemptionApi {
2186
+ constructor(apiClient) {
2187
+ this.apiClient = apiClient;
2188
+ this.basePath = '/redemptions';
2189
+ }
2190
+ // ==========================================
2191
+ // PUBLIC OPERATIONS (Project Key)
2192
+ // ==========================================
2193
+ /**
2194
+ * PUBLIC: Get redemptions (intelligent access)
2195
+ *
2196
+ * NEW: Intelligent endpoint that adapts based on authentication
2197
+ * - Public users: Get active redemptions only
2198
+ * - Admin users: Get all redemptions with optional filtering
2199
+ *
2200
+ * Replaces: getActiveRedemptions() + getRedemptionsAsAdmin()
2201
+ */
2202
+ async getRedemptions(active) {
2203
+ let url = `${this.basePath}`;
2204
+ if (active !== undefined) {
2205
+ url += `?active=${active}`;
2206
+ }
2207
+ return this.apiClient.get(url);
2208
+ }
2209
+ /**
2210
+ * PUBLIC: Get active redemptions
2211
+ *
2212
+ * Updated: Now uses unified endpoint (backward compatibility)
2213
+ */
2214
+ async getActiveRedemptions() {
2215
+ return this.getRedemptions(); // Will return active only for public access
2216
+ }
2217
+ /**
2218
+ * PUBLIC: Get redemption types
2219
+ *
2220
+ * Updated: /redemption/type → /redemptions/types
2221
+ */
2222
+ async getRedemptionTypes() {
2223
+ return this.apiClient.get(`${this.basePath}/types`);
2224
+ }
2225
+ /**
2226
+ * PUBLIC: Get redemption by ID
2227
+ *
2228
+ * Updated: /redemption/:id → /redemptions/:id
2229
+ */
2230
+ async getRedemptionById(id) {
2231
+ return this.apiClient.get(`${this.basePath}/${id}`);
2232
+ }
2233
+ /**
2234
+ * PUBLIC: Get available supply for redemption
2235
+ *
2236
+ * Updated: /redemption/:id/available-supply → /redemptions/:id/available-supply
2237
+ */
2238
+ async getRedemptionAvailableSupply(id) {
2239
+ return this.apiClient.get(`${this.basePath}/${id}/available-supply`);
2240
+ }
2241
+ // ==========================================
2242
+ // USER OPERATIONS (JWT + Project Key)
2243
+ // ==========================================
2244
+ /**
2245
+ * USER: Redeem a redemption
2246
+ *
2247
+ * Updated: /redemption/auth/redeem → /redemptions/:id/redeem
2248
+ * Enhanced: Path-based redemption ID for better RESTful design
2249
+ */
2250
+ async redeemRedemption(redemptionId) {
2251
+ const body = {
2252
+ redemptionId: redemptionId,
2253
+ };
2254
+ return this.apiClient.post(`${this.basePath}/${redemptionId}/redeem`, body);
2255
+ }
2256
+ /**
2257
+ * USER: Get user redemption history
2258
+ *
2259
+ * Updated: /redemption/auth/redeem → /redemptions/me/history
2260
+ */
2261
+ async getUserRedemptionHistory() {
2262
+ return this.apiClient.get(`${this.basePath}/me/history`);
2263
+ }
2264
+ /**
2265
+ * USER: Get user redemptions (backward compatibility)
2266
+ *
2267
+ * Deprecated: Use getUserRedemptionHistory() instead
2268
+ */
2269
+ async getUserRedeems() {
2270
+ return this.getUserRedemptionHistory();
2271
+ }
2272
+ // ==========================================
2273
+ // ADMIN OPERATIONS (Tenant Admin JWT)
2274
+ // ==========================================
2275
+ /**
2276
+ * ADMIN: Get redemptions with filtering (using intelligent endpoint)
2277
+ *
2278
+ * Updated: /redemption/admin → /redemptions (intelligent access detection)
2279
+ * The unified endpoint will detect admin privileges and allow filtering
2280
+ */
2281
+ async getRedemptionsAsAdmin(active) {
2282
+ return this.getRedemptions(active); // Uses intelligent endpoint
2283
+ }
2284
+ /**
2285
+ * ADMIN: Create redemption
2286
+ *
2287
+ * Updated: /redemption/admin → /redemptions
2288
+ */
2289
+ async createRedemption(redemption) {
2290
+ return this.apiClient.post(`${this.basePath}`, redemption);
2291
+ }
2292
+ /**
2293
+ * ADMIN: Update redemption
2294
+ *
2295
+ * Updated: /redemption/admin/:id → /redemptions/:id
2296
+ */
2297
+ async updateRedemption(id, redemptionCreateRequest) {
2298
+ return this.apiClient.put(`${this.basePath}/${id}`, redemptionCreateRequest);
2299
+ }
2300
+ /**
2301
+ * ADMIN: Toggle redemption status
2302
+ *
2303
+ * Updated: /redemption/admin/:id/toggle-active → /redemptions/:id/status
2304
+ * Following standard /status pattern used across domains
2305
+ */
2306
+ async toggleRedemptionStatus(redemptionId) {
2307
+ return this.apiClient.put(`${this.basePath}/${redemptionId}/status`, {});
2308
+ }
2309
+ /**
2310
+ * ADMIN: Toggle redemption active (backward compatibility)
2311
+ *
2312
+ * Deprecated: Use toggleRedemptionStatus() instead
2313
+ */
2314
+ async toggleRedemptionActive(redemptionId) {
2315
+ return this.toggleRedemptionStatus(redemptionId);
2316
+ }
2317
+ /**
2318
+ * ADMIN: Delete redemption
2319
+ *
2320
+ * Updated: /redemption/admin/:id → /redemptions/:id
2321
+ */
2322
+ async deleteRedemption(id) {
2323
+ return this.apiClient.delete(`${this.basePath}/${id}`);
2324
+ }
2325
+ /**
2326
+ * ADMIN: Create redemption type
2327
+ *
2328
+ * Updated: /redemption/admin/type → /redemptions/types
2329
+ */
2330
+ async createRedemptionType(redemptionType) {
2331
+ return this.apiClient.post(`${this.basePath}/types`, redemptionType);
2332
+ }
2333
+ // ==========================================
2334
+ // TOKEN UNIT MANAGEMENT (Admin)
2335
+ // ==========================================
2336
+ /**
2337
+ * ADMIN: Create redemption token unit
2338
+ *
2339
+ * Updated: /redemption/admin/:id/token-units → /redemptions/:id/token-units
2340
+ */
2341
+ async createRedemptionTokenUnit(redemptionId, redemptionTokenUnit) {
2342
+ return this.apiClient.post(`${this.basePath}/${redemptionId}/token-units`, redemptionTokenUnit);
2343
+ }
2344
+ /**
2345
+ * ADMIN: Update redemption token unit
2346
+ *
2347
+ * Updated: /redemption/admin/:id/token-units/:tokenUnitId → /redemptions/:id/token-units/:tokenUnitId
2348
+ */
2349
+ async updateRedemptionTokenUnit(redemptionId, tokenUnitId, redemptionTokenUnit) {
2350
+ return this.apiClient.put(`${this.basePath}/${redemptionId}/token-units/${tokenUnitId}`, redemptionTokenUnit);
2351
+ }
2352
+ /**
2353
+ * ADMIN: Delete redemption token unit
2354
+ *
2355
+ * Updated: /redemption/admin/:id/token-units/:tokenUnitId → /redemptions/:id/token-units/:tokenUnitId
2356
+ */
2357
+ async deleteRedemptionTokenUnit(redemptionId, redemptionTokenUnitId) {
2358
+ return this.apiClient.delete(`${this.basePath}/${redemptionId}/token-units/${redemptionTokenUnitId}`);
2359
+ }
2360
+ // ==========================================
2361
+ // BACKWARD COMPATIBILITY METHODS
2362
+ // ==========================================
2363
+ /**
2364
+ * @deprecated Use getRedemptions() instead
2365
+ * Backward compatibility for old admin endpoint
2366
+ */
2367
+ async getRedemptionsAdmin(active) {
2368
+ return this.getRedemptionsAsAdmin(active);
2369
+ }
2370
+ /**
2371
+ * @deprecated Use redeemRedemption() instead
2372
+ * Backward compatibility for old redeem method
2373
+ */
2374
+ async redeem(redemptionId) {
2375
+ return this.redeemRedemption(redemptionId);
2376
+ }
2377
+ }
2378
+
2379
+ /**
2380
+ * Platform-Agnostic Redemption Service
2381
+ *
2382
+ * Contains redemption business logic and operations that work across platforms.
2383
+ * No framework dependencies - pure TypeScript business logic.
2384
+ *
2385
+ * Focuses only on actual backend capabilities.
2386
+ */
2387
+ class RedemptionService {
2388
+ constructor(redemptionApi) {
2389
+ this.redemptionApi = redemptionApi;
2390
+ }
2391
+ // ==========================================
2392
+ // PUBLIC OPERATIONS
2393
+ // ==========================================
2394
+ /**
2395
+ * PUBLIC: Get active redemptions
2396
+ */
2397
+ async getActiveRedemptions() {
2398
+ return this.redemptionApi.getActiveRedemptions();
2399
+ }
2400
+ /**
2401
+ * PUBLIC: Get redemption types
2402
+ */
2403
+ async getRedemptionTypes() {
2404
+ return this.redemptionApi.getRedemptionTypes();
2405
+ }
2406
+ // ==========================================
2407
+ // AUTHENTICATED OPERATIONS
2408
+ // ==========================================
2409
+ /**
2410
+ * AUTH: Redeem a redemption
2411
+ */
2412
+ async redeemRedemption(redemptionId) {
2413
+ return this.redemptionApi.redeemRedemption(redemptionId);
2414
+ }
2415
+ /**
2416
+ * AUTH: Get user redemptions
2417
+ */
2418
+ async getUserRedeems() {
2419
+ return this.redemptionApi.getUserRedeems();
2420
+ }
2421
+ // ==========================================
2422
+ // ADMIN OPERATIONS
2423
+ // ==========================================
2424
+ /**
2425
+ * ADMIN: Get redemptions with optional active filter
2426
+ */
2427
+ async getRedemptionsAsAdmin(active) {
2428
+ return this.redemptionApi.getRedemptionsAsAdmin(active);
2429
+ }
2430
+ /**
2431
+ * ADMIN: Create redemption
2432
+ */
2433
+ async createRedemption(redemption) {
2434
+ return this.redemptionApi.createRedemption(redemption);
2435
+ }
2436
+ /**
2437
+ * ADMIN: Update redemption
2438
+ */
2439
+ async updateRedemption(id, redemptionCreateRequest) {
2440
+ return this.redemptionApi.updateRedemption(id, redemptionCreateRequest); // ✅ CORRECTED: Fixed parameter
2441
+ }
2442
+ /**
2443
+ * ADMIN: Toggle redemption active status
2444
+ */
2445
+ async toggleRedemptionActive(redemptionId) {
2446
+ return this.redemptionApi.toggleRedemptionActive(redemptionId);
2447
+ }
2448
+ /**
2449
+ * ADMIN: Create redemption token unit
2450
+ */
2451
+ async createRedemptionTokenUnit(redemptionId, redemptionTokenUnit) {
2452
+ return this.redemptionApi.createRedemptionTokenUnit(redemptionId, redemptionTokenUnit);
2453
+ }
2454
+ /**
2455
+ * ADMIN: Delete redemption token unit
2456
+ */
2457
+ async deleteRedemptionTokenUnit(redemptionId, redemptionTokenUnitId) {
2458
+ return this.redemptionApi.deleteRedemptionTokenUnit(redemptionId, redemptionTokenUnitId);
2459
+ }
2460
+ }
2461
+
2462
+ /**
2463
+ * @explorins/pers-sdk-redemption
2464
+ *
2465
+ * Platform-agnostic Redemption Domain SDK for PERS ecosystem
2466
+ * Handles redemption operations across different authorization levels
2467
+ */
2468
+ // API Layer
2469
+ /**
2470
+ * Create a complete Redemption SDK instance
2471
+ *
2472
+ * @param apiClient - Configured PERS API client
2473
+ * @returns Redemption SDK with flattened structure for better DX
2474
+ */
2475
+ function createRedemptionSDK(apiClient) {
2476
+ const redemptionApi = new RedemptionApi(apiClient);
2477
+ const redemptionService = new RedemptionService(redemptionApi);
2478
+ return {
2479
+ // Direct access to service methods (primary interface)
2480
+ // Public methods
2481
+ getActiveRedemptions: () => redemptionService.getActiveRedemptions(),
2482
+ getRedemptionTypes: () => redemptionService.getRedemptionTypes(),
2483
+ // Auth methods
2484
+ redeemRedemption: (redemptionId) => redemptionService.redeemRedemption(redemptionId),
2485
+ getUserRedeems: () => redemptionService.getUserRedeems(),
2486
+ // Admin methods
2487
+ getRedemptionsAsAdmin: (active) => redemptionService.getRedemptionsAsAdmin(active),
2488
+ createRedemption: (redemption) => redemptionService.createRedemption(redemption),
2489
+ updateRedemption: (id, redemptionCreateRequest) => redemptionService.updateRedemption(id, redemptionCreateRequest),
2490
+ toggleRedemptionActive: (redemptionId) => redemptionService.toggleRedemptionActive(redemptionId),
2491
+ createRedemptionTokenUnit: (redemptionId, redemptionTokenUnit) => redemptionService.createRedemptionTokenUnit(redemptionId, redemptionTokenUnit),
2492
+ deleteRedemptionTokenUnit: (redemptionId, redemptionTokenUnitId) => redemptionService.deleteRedemptionTokenUnit(redemptionId, redemptionTokenUnitId),
2493
+ // Advanced access for edge cases
2494
+ api: redemptionApi,
2495
+ service: redemptionService
2496
+ };
2497
+ }
2498
+
2499
+ /**
2500
+ * Platform-Agnostic Tenant API Client
2501
+ *
2502
+ * Handles tenant and admin operations using the PERS backend.
2503
+ * Matches framework TenantApiService methods exactly.
2504
+ *
2505
+ * Note: Special header handling (bypass-auth-interceptor) should be handled by PersApiClient internally
2506
+ * or through endpoint-specific configuration.
2507
+ */
2508
+ class TenantApi {
2509
+ constructor(apiClient) {
2510
+ this.apiClient = apiClient;
2511
+ this.basePath = '/tenants';
2512
+ this.adminPath = '/admins';
2513
+ }
2514
+ // ==========================================
2515
+ // PUBLIC OPERATIONS
2516
+ // ==========================================
2517
+ /**
2518
+ * PUBLIC: Get tenant public information
2519
+ * ✅ FIXED: Matches framework cache busting pattern exactly
2520
+ */
2521
+ async getRemoteTenant() {
2522
+ const timestamp = Date.now().toString();
2523
+ const url = `${this.basePath}/public?date=${timestamp}`;
2524
+ return this.apiClient.get(url);
2525
+ }
2526
+ /**
2527
+ * PUBLIC: Get remote login token
2528
+ */
2529
+ async getRemoteLoginToken() {
2530
+ return this.apiClient.get(`${this.basePath}/login-token`);
2531
+ }
2532
+ /**
2533
+ * PUBLIC: Get remote client configuration
2534
+ * ✅ FIXED: Removed second parameter - PersApiClient handles bypass auth internally
2535
+ * Note: The /tenants/client-config endpoint should be configured to bypass auth at the API client level
2536
+ */
2537
+ async getRemoteClientConfig() {
2538
+ return this.apiClient.get(`${this.basePath}/client-config`);
2539
+ }
2540
+ // ==========================================
2541
+ // ADMIN OPERATIONS
2542
+ // ==========================================
2543
+ /**
2544
+ * ADMIN: Update tenant information
2545
+ * ✅ FIXED: Uses TenantPublicDTO directly like framework
2546
+ */
2547
+ async updateRemoteTenant(tenantData) {
2548
+ return this.apiClient.put(`${this.basePath}`, tenantData);
2549
+ }
2550
+ /**
2551
+ * ADMIN: Get all tenant admins
2552
+ */
2553
+ async getAdmins() {
2554
+ return this.apiClient.get(`${this.adminPath}`);
2555
+ }
2556
+ /**
2557
+ * ADMIN: Create new admin
2558
+ * ✅ FIXED: Renamed to match framework postAdmin method
2559
+ */
2560
+ async postAdmin(adminData) {
2561
+ return this.apiClient.post(`${this.adminPath}`, adminData);
2562
+ }
2563
+ /**
2564
+ * ADMIN: Update admin (toggle tenant association)
2565
+ * ✅ FIXED: Renamed to match framework putAdmin method
2566
+ */
2567
+ async putAdmin(adminId, adminData) {
2568
+ return this.apiClient.put(`${this.adminPath}/${adminId}/tenant`, adminData);
2569
+ }
2570
+ }
2571
+
2572
+ /**
2573
+ * Platform-Agnostic Tenant Service
2574
+ *
2575
+ * Contains tenant business logic and operations that work across platforms.
2576
+ * No framework dependencies - pure TypeScript business logic.
2577
+ * Matches framework TenantApiService capabilities exactly.
2578
+ */
2579
+ class TenantService {
2580
+ constructor(tenantApi) {
2581
+ this.tenantApi = tenantApi;
2582
+ }
2583
+ // ==========================================
2584
+ // PUBLIC OPERATIONS
2585
+ // ==========================================
2586
+ /**
2587
+ * PUBLIC: Get tenant public information
2588
+ */
2589
+ async getRemoteTenant() {
2590
+ return this.tenantApi.getRemoteTenant();
2591
+ }
2592
+ /**
2593
+ * PUBLIC: Get remote login token
2594
+ */
2595
+ async getRemoteLoginToken() {
2596
+ return this.tenantApi.getRemoteLoginToken();
2597
+ }
2598
+ /**
2599
+ * PUBLIC: Get remote client configuration
2600
+ */
2601
+ async getRemoteClientConfig() {
2602
+ return this.tenantApi.getRemoteClientConfig();
2603
+ }
2604
+ // ==========================================
2605
+ // ADMIN OPERATIONS
2606
+ // ==========================================
2607
+ /**
2608
+ * ADMIN: Update tenant information
2609
+ * ✅ FIXED: Uses TenantPublicDTO directly like framework
2610
+ */
2611
+ async updateRemoteTenant(tenantData) {
2612
+ return this.tenantApi.updateRemoteTenant(tenantData);
2613
+ }
2614
+ /**
2615
+ * ADMIN: Get all tenant admins
2616
+ */
2617
+ async getAdmins() {
2618
+ return this.tenantApi.getAdmins();
2619
+ }
2620
+ /**
2621
+ * ADMIN: Create new admin
2622
+ * ✅ FIXED: Renamed to match framework postAdmin method
2623
+ */
2624
+ async postAdmin(adminData) {
2625
+ return this.tenantApi.postAdmin(adminData);
2626
+ }
2627
+ /**
2628
+ * ADMIN: Update admin (toggle tenant association)
2629
+ * ✅ FIXED: Renamed to match framework putAdmin method
2630
+ */
2631
+ async putAdmin(adminId, adminData) {
2632
+ return this.tenantApi.putAdmin(adminId, adminData);
2633
+ }
2634
+ }
2635
+
2636
+ /**
2637
+ * @explorins/pers-sdk-tenant
2638
+ *
2639
+ * Platform-agnostic Tenant Domain SDK for PERS ecosystem
2640
+ * Handles tenant management and admin operations for multi-tenant architecture
2641
+ */
2642
+ // API Layer
2643
+ /**
2644
+ * Create a complete Tenant SDK instance
2645
+ *
2646
+ * @param apiClient - Configured PERS API client
2647
+ * @returns Tenant SDK with flattened structure for better DX
2648
+ */
2649
+ function createTenantSDK(apiClient) {
2650
+ const tenantApi = new TenantApi(apiClient);
2651
+ const tenantService = new TenantService(tenantApi);
2652
+ return {
2653
+ // Direct access to service methods (primary interface)
2654
+ // ✅ FRAMEWORK ALIGNED: Only methods actually used by framework
2655
+ // Public methods
2656
+ getRemoteTenant: () => tenantService.getRemoteTenant(),
2657
+ getRemoteLoginToken: () => tenantService.getRemoteLoginToken(),
2658
+ getRemoteClientConfig: () => tenantService.getRemoteClientConfig(),
2659
+ // Admin methods - ✅ FIXED: Matches framework method names exactly
2660
+ updateRemoteTenant: (tenantData) => tenantService.updateRemoteTenant(tenantData),
2661
+ getAdmins: () => tenantService.getAdmins(),
2662
+ postAdmin: (adminData) => tenantService.postAdmin(adminData),
2663
+ putAdmin: (adminId, adminData) => tenantService.putAdmin(adminId, adminData),
2664
+ // Advanced access for edge cases
2665
+ api: tenantApi,
2666
+ service: tenantService
2667
+ };
2668
+ }
2669
+
2670
+ class TokenApi {
2671
+ constructor(apiClient) {
2672
+ this.apiClient = apiClient;
2673
+ this.basePath = '/tokens';
2674
+ }
2675
+ // ==========================================
2676
+ // PUBLIC OPERATIONS
2677
+ // ==========================================
2678
+ /**
2679
+ * PUBLIC: Get all remote tokens
2680
+ * ENHANCED: Added admin filtering capability
2681
+ */
2682
+ async getRemoteTokens(includeInactive = false) {
2683
+ const url = includeInactive ? `${this.basePath}?active=false` : `${this.basePath}`;
2684
+ return this.apiClient.get(url);
2685
+ }
2686
+ /**
2687
+ * PUBLIC: Get all remote token types
2688
+ */
2689
+ async getRemoteTokenTypes() {
2690
+ return this.apiClient.get(`${this.basePath}/types`);
2691
+ }
2692
+ /**
2693
+ * PUBLIC: Get active point token (was credit token)
2694
+ */
2695
+ async getRemoteActiveCreditToken() {
2696
+ return this.apiClient.get(`${this.basePath}/points`);
2697
+ }
2698
+ /**
2699
+ * PUBLIC: Get reward tokens
2700
+ * ENHANCED: Added admin filtering capability
2701
+ */
2702
+ async getRemoteRewardTokens(includeInactive = false) {
2703
+ const url = includeInactive ? `${this.basePath}/rewards?active=false` : `${this.basePath}/rewards`;
2704
+ return this.apiClient.get(url);
2705
+ }
2706
+ /**
2707
+ * PUBLIC: Get stamp tokens (was status tokens)
2708
+ * ENHANCED: Added admin filtering capability
2709
+ */
2710
+ async getRemoteStatusTokens(includeInactive = false) {
2711
+ const url = includeInactive ? `${this.basePath}/stamps?active=false` : `${this.basePath}/stamps`;
2712
+ return this.apiClient.get(url);
2713
+ }
2714
+ /**
2715
+ * PUBLIC: Get token by contract address
2716
+ */
2717
+ async getTokenByContractAddress(contractAddress, contractTokenId) {
2718
+ let url = `${this.basePath}/address/${contractAddress}`;
2719
+ if (contractTokenId) {
2720
+ url += `?contractTokenId=${contractTokenId}`;
2721
+ }
2722
+ return this.apiClient.get(url);
2723
+ }
2724
+ // ==========================================
2725
+ // ADMIN OPERATIONS
2726
+ // ==========================================
2727
+ /**
2728
+ * ADMIN: Create new token
2729
+ */
2730
+ async createToken(tokenData) {
2731
+ return this.apiClient.post(`${this.basePath}`, tokenData);
2732
+ }
2733
+ /**
2734
+ * ADMIN: Update token
2735
+ */
2736
+ async updateToken(tokenId, tokenData) {
2737
+ return this.apiClient.put(`${this.basePath}/${tokenId}`, tokenData);
2738
+ }
2739
+ /**
2740
+ * ADMIN: Toggle token active status
2741
+ * FIXED: Now calls correct endpoint
2742
+ */
2743
+ async toggleTokenActive(tokenId) {
2744
+ return this.apiClient.put(`${this.basePath}/${tokenId}/status`, {});
2745
+ }
2746
+ /**
2747
+ * ADMIN: Set mainnet contract address
2748
+ */
2749
+ async setMainnetContract(tokenId, contractAddress, chainId) {
2750
+ return this.apiClient.put(`${this.basePath}/${tokenId}/mainnet`, {
2751
+ contractAddress,
2752
+ chainId
2753
+ });
2754
+ }
2755
+ /**
2756
+ * ADMIN: Create token metadata
2757
+ */
2758
+ async createTokenMetadata(tokenId, tokenData) {
2759
+ return this.apiClient.post(`${this.basePath}/${tokenId}/metadata`, tokenData);
2760
+ }
2761
+ /**
2762
+ * ADMIN: Toggle token metadata status (separate from token status)
2763
+ */
2764
+ async toggleTokenMetadataStatus(metadataId) {
2765
+ return this.apiClient.put(`${this.basePath}/metadata/${metadataId}/status`, {});
2766
+ }
2767
+ /**
2768
+ * ADMIN: Create token type
2769
+ */
2770
+ async createTokenType(tokenType) {
2771
+ return this.apiClient.post(`${this.basePath}/types`, tokenType);
2772
+ }
2773
+ }
2774
+
2775
+ /**
2776
+ * Platform-Agnostic Token Service
2777
+ *
2778
+ * Contains token business logic and operations that work across platforms.
2779
+ * No framework dependencies - pure TypeScript business logic.
2780
+ * Matches framework TokenApiService capabilities exactly.
2781
+ */
2782
+ class TokenService {
2783
+ constructor(tokenApi) {
2784
+ this.tokenApi = tokenApi;
2785
+ }
2786
+ // ==========================================
2787
+ // PUBLIC OPERATIONS
2788
+ // ==========================================
2789
+ /**
2790
+ * PUBLIC: Get all remote tokens
2791
+ */
2792
+ async getRemoteTokens() {
2793
+ return this.tokenApi.getRemoteTokens();
2794
+ }
2795
+ /**
2796
+ * PUBLIC: Get all remote token types
2797
+ */
2798
+ async getRemoteTokenTypes() {
2799
+ return this.tokenApi.getRemoteTokenTypes();
2800
+ }
2801
+ /**
2802
+ * PUBLIC: Get active credit token
2803
+ */
2804
+ async getRemoteActiveCreditToken() {
2805
+ return this.tokenApi.getRemoteActiveCreditToken();
2806
+ }
2807
+ /**
2808
+ * PUBLIC: Get reward tokens
2809
+ */
2810
+ async getRemoteRewardTokens() {
2811
+ return this.tokenApi.getRemoteRewardTokens();
2812
+ }
2813
+ /**
2814
+ * PUBLIC: Get status tokens
2815
+ */
2816
+ async getRemoteStatusTokens() {
2817
+ return this.tokenApi.getRemoteStatusTokens();
2818
+ }
2819
+ /**
2820
+ * PUBLIC: Get token by contract address
2821
+ * ✅ FIXED: Matches framework parameter types exactly
2822
+ */
2823
+ async getTokenByContractAddress(contractAddress, contractTokenId) {
2824
+ return this.tokenApi.getTokenByContractAddress(contractAddress, contractTokenId);
2825
+ }
2826
+ // ==========================================
2827
+ // ADMIN OPERATIONS
2828
+ // ==========================================
2829
+ /**
2830
+ * ADMIN: Create token metadata
2831
+ */
2832
+ async createTokenMetadata(tokenId, tokenData) {
2833
+ return this.tokenApi.createTokenMetadata(tokenId, tokenData);
2834
+ }
2835
+ /**
2836
+ * ADMIN: Toggle token active status
2837
+ */
2838
+ async toggleTokenActive(tokenId) {
2839
+ return this.tokenApi.toggleTokenActive(tokenId);
2840
+ }
2841
+ /**
2842
+ * ADMIN: Create new token
2843
+ */
2844
+ async createToken(tokenData) {
2845
+ return this.tokenApi.createToken(tokenData);
2846
+ }
2847
+ /**
2848
+ * ADMIN: Update token
2849
+ */
2850
+ async updateToken(tokenId, tokenData) {
2851
+ return this.tokenApi.updateToken(tokenId, tokenData);
2852
+ }
2853
+ /**
2854
+ * ADMIN: Set mainnet contract address
2855
+ */
2856
+ async setMainnetContract(tokenId, contractAddress, chainId) {
2857
+ return this.tokenApi.setMainnetContract(tokenId, contractAddress, chainId);
2858
+ }
2859
+ /**
2860
+ * ADMIN: Toggle token metadata status
2861
+ */
2862
+ async toggleTokenMetadataStatus(metadataId) {
2863
+ return this.tokenApi.toggleTokenMetadataStatus(metadataId);
2864
+ }
2865
+ /**
2866
+ * ADMIN: Create token type
2867
+ */
2868
+ async createTokenType(tokenType) {
2869
+ return this.tokenApi.createTokenType(tokenType);
2870
+ }
2871
+ }
2872
+
2873
+ /**
2874
+ * Token SDK - Class-based Promise SDK for Token Operations
2875
+ *
2876
+ * Modern, performant SDK with direct method access and excellent TypeScript support.
2877
+ * Optimized for bundle size, performance, and developer experience.
2878
+ *
2879
+ * Usage:
2880
+ * const tokenSDK = new TokenSDK(apiClient);
2881
+ * const tokens = await tokenSDK.getTokens();
2882
+ * const creditToken = await tokenSDK.getActiveCreditToken();
2883
+ */
2884
+ class TokenSDK {
2885
+ constructor(apiClient) {
2886
+ this.tokenApi = new TokenApi(apiClient);
2887
+ this.tokenService = new TokenService(this.tokenApi);
2888
+ }
2889
+ // ==========================================
2890
+ // CONVENIENCE METHODS - Direct Promise API
2891
+ // ==========================================
2892
+ // Simplified method names for better developer experience
2893
+ /**
2894
+ * Get all tokens
2895
+ * @returns Promise with all available tokens
2896
+ */
2897
+ async getTokens() {
2898
+ return this.tokenService.getRemoteTokens();
2899
+ }
2900
+ /**
2901
+ * Get all token types
2902
+ * @returns Promise with all available token types
2903
+ */
2904
+ async getTokenTypes() {
2905
+ return this.tokenService.getRemoteTokenTypes();
2906
+ }
2907
+ /**
2908
+ * Get the active credit token
2909
+ * @returns Promise with the current active credit token
2910
+ */
2911
+ async getActiveCreditToken() {
2912
+ return this.tokenService.getRemoteActiveCreditToken();
2913
+ }
2914
+ /**
2915
+ * Get all reward tokens
2916
+ * @returns Promise with all reward tokens
2917
+ */
2918
+ async getRewardTokens() {
2919
+ return this.tokenService.getRemoteRewardTokens();
2920
+ }
2921
+ /**
2922
+ * Get all status tokens
2923
+ * @returns Promise with all status tokens
2924
+ */
2925
+ async getStatusTokens() {
2926
+ return this.tokenService.getRemoteStatusTokens();
2927
+ }
2928
+ /**
2929
+ * Get token by contract address
2930
+ * @param contractAddress - The contract address to search for
2931
+ * @param contractTokenId - Optional contract token ID
2932
+ * @returns Promise with the matching token
2933
+ */
2934
+ async getTokenByContract(contractAddress, contractTokenId = null) {
2935
+ return this.tokenService.getTokenByContractAddress(contractAddress, contractTokenId);
2936
+ }
2937
+ // ==========================================
2938
+ // ADMIN METHODS - Token Management
2939
+ // ==========================================
2940
+ /**
2941
+ * Create token metadata
2942
+ * @param tokenId - The token ID
2943
+ * @param tokenData - The token storage data
2944
+ * @returns Promise with the updated token
2945
+ */
2946
+ async createTokenMetadata(tokenId, tokenData) {
2947
+ return this.tokenService.createTokenMetadata(tokenId, tokenData);
2948
+ }
2949
+ /**
2950
+ * Toggle token active status
2951
+ * @param tokenId - The token ID to toggle
2952
+ * @returns Promise with the updated token
2953
+ */
2954
+ async toggleTokenActive(tokenId) {
2955
+ return this.tokenService.toggleTokenActive(tokenId);
2956
+ }
2957
+ /**
2958
+ * Create a new token
2959
+ * @param tokenData - The token creation data
2960
+ * @returns Promise with the created token
2961
+ */
2962
+ async createToken(tokenData) {
2963
+ return this.tokenService.createToken(tokenData);
2964
+ }
2965
+ /**
2966
+ * Update an existing token
2967
+ * @param tokenId - The token ID to update
2968
+ * @param tokenData - The token update data
2969
+ * @returns Promise with the updated token
2970
+ */
2971
+ async updateToken(tokenId, tokenData) {
2972
+ return this.tokenService.updateToken(tokenId, tokenData);
2973
+ }
2974
+ /**
2975
+ * Set mainnet contract address for a token
2976
+ * @param tokenId - The token ID
2977
+ * @param contractAddress - The contract address
2978
+ * @param chainId - The blockchain chain ID
2979
+ * @returns Promise with the updated token
2980
+ */
2981
+ async setMainnetContract(tokenId, contractAddress, chainId) {
2982
+ return this.tokenService.setMainnetContract(tokenId, contractAddress, chainId);
2983
+ }
2984
+ /**
2985
+ * Toggle token metadata status
2986
+ * @param metadataId - The metadata ID to toggle
2987
+ * @returns Promise with the updated metadata
2988
+ */
2989
+ async toggleTokenMetadataStatus(metadataId) {
2990
+ return this.tokenService.toggleTokenMetadataStatus(metadataId);
2991
+ }
2992
+ /**
2993
+ * Create a new token type
2994
+ * @param tokenType - The token type data
2995
+ * @returns Promise with the created token type
2996
+ */
2997
+ async createTokenType(tokenType) {
2998
+ return this.tokenService.createTokenType(tokenType);
2999
+ }
3000
+ // ==========================================
3001
+ // ADVANCED ACCESS - For Complex Operations
3002
+ // ==========================================
3003
+ /**
3004
+ * Get direct access to the token service for advanced operations
3005
+ * @returns The underlying TokenService instance
3006
+ */
3007
+ getTokenService() {
3008
+ return this.tokenService;
3009
+ }
3010
+ /**
3011
+ * Get direct access to the token API for low-level operations
3012
+ * @returns The underlying TokenApi instance
3013
+ */
3014
+ getTokenApi() {
3015
+ return this.tokenApi;
3016
+ }
3017
+ // ==========================================
3018
+ // FRAMEWORK COMPATIBILITY METHODS
3019
+ // ==========================================
3020
+ // These maintain compatibility with existing framework method names
3021
+ /**
3022
+ * @deprecated Use getTokens() instead
3023
+ * Framework compatibility method
3024
+ */
3025
+ async getRemoteTokens() {
3026
+ return this.getTokens();
3027
+ }
3028
+ /**
3029
+ * @deprecated Use getTokenTypes() instead
3030
+ * Framework compatibility method
3031
+ */
3032
+ async getRemoteTokenTypes() {
3033
+ return this.getTokenTypes();
3034
+ }
3035
+ /**
3036
+ * @deprecated Use getActiveCreditToken() instead
3037
+ * Framework compatibility method
3038
+ */
3039
+ async getRemoteActiveCreditToken() {
3040
+ return this.getActiveCreditToken();
3041
+ }
3042
+ /**
3043
+ * @deprecated Use getRewardTokens() instead
3044
+ * Framework compatibility method
3045
+ */
3046
+ async getRemoteRewardTokens() {
3047
+ return this.getRewardTokens();
3048
+ }
3049
+ /**
3050
+ * @deprecated Use getStatusTokens() instead
3051
+ * Framework compatibility method
3052
+ */
3053
+ async getRemoteStatusTokens() {
3054
+ return this.getStatusTokens();
3055
+ }
3056
+ /**
3057
+ * @deprecated Use getTokenByContract() instead
3058
+ * Framework compatibility method
3059
+ */
3060
+ async getTokenByContractAddress(contractAddress, contractTokenId) {
3061
+ return this.getTokenByContract(contractAddress, contractTokenId);
3062
+ }
3063
+ }
3064
+
3065
+ /**
3066
+ * Abstract Base Token Service - Explicit Initialization Pattern
3067
+ *
3068
+ * Platform-agnostic token operations with Promise-based API.
3069
+ * Framework services extend this and control initialization lifecycle.
3070
+ *
3071
+ * Benefits:
3072
+ * - Explicit initialization control
3073
+ * - Better error boundaries
3074
+ * - Clear lifecycle management
3075
+ * - Testable initialization state
3076
+ * - Zero framework dependencies
3077
+ */
3078
+ class BaseTokenService {
3079
+ constructor() {
3080
+ this._isInitialized = false;
3081
+ }
3082
+ // ==========================================
3083
+ // INITIALIZATION LIFECYCLE
3084
+ // ==========================================
3085
+ /**
3086
+ * LIFECYCLE: Initialize token service with API client
3087
+ * Must be called before using any token operations
3088
+ */
3089
+ initializeTokenService(apiClient) {
3090
+ if (!apiClient) {
3091
+ throw new Error('Cannot initialize TokenService: apiClient is null or undefined');
3092
+ }
3093
+ if (!this._isInitialized) {
3094
+ this._tokenApi = new TokenApi(apiClient);
3095
+ this._tokenBusinessService = new TokenService(this._tokenApi);
3096
+ this._isInitialized = true;
3097
+ }
3098
+ }
3099
+ /**
3100
+ * LIFECYCLE: Check if token service is initialized
3101
+ */
3102
+ get isTokenServiceInitialized() {
3103
+ return this._isInitialized;
3104
+ }
3105
+ /**
3106
+ * INTERNAL: Get token business service (throws if not initialized)
3107
+ */
3108
+ get tokenBusinessService() {
3109
+ if (!this._tokenBusinessService) {
3110
+ throw new Error('TokenService not initialized. Call initializeTokenService(apiClient) first.');
3111
+ }
3112
+ return this._tokenBusinessService;
3113
+ }
3114
+ // ==========================================
3115
+ // PUBLIC OPERATIONS
3116
+ // ==========================================
3117
+ /**
3118
+ * PUBLIC: Get all remote tokens
3119
+ */
3120
+ getRemoteTokens() {
3121
+ return this.tokenBusinessService.getRemoteTokens();
3122
+ }
3123
+ /**
3124
+ * PUBLIC: Get all remote token types
3125
+ */
3126
+ getRemoteTokenTypes() {
3127
+ return this.tokenBusinessService.getRemoteTokenTypes();
3128
+ }
3129
+ /**
3130
+ * PUBLIC: Get active credit token
3131
+ */
3132
+ getRemoteActiveCreditToken() {
3133
+ return this.tokenBusinessService.getRemoteActiveCreditToken();
3134
+ }
3135
+ /**
3136
+ * PUBLIC: Get reward tokens
3137
+ */
3138
+ getRemoteRewardTokens() {
3139
+ return this.tokenBusinessService.getRemoteRewardTokens();
3140
+ }
3141
+ /**
3142
+ * PUBLIC: Get status tokens
3143
+ */
3144
+ getRemoteStatusTokens() {
3145
+ return this.tokenBusinessService.getRemoteStatusTokens();
3146
+ }
3147
+ /**
3148
+ * PUBLIC: Get token by contract address
3149
+ */
3150
+ getTokenByContractAddress(contractAddress, contractTokenId) {
3151
+ return this.tokenBusinessService.getTokenByContractAddress(contractAddress, contractTokenId);
3152
+ }
3153
+ // ==========================================
3154
+ // ADMIN OPERATIONS
3155
+ // ==========================================
3156
+ /**
3157
+ * ADMIN: Create token metadata
3158
+ */
3159
+ createTokenMetadata(tokenId, tokenData) {
3160
+ return this.tokenBusinessService.createTokenMetadata(tokenId, tokenData);
3161
+ }
3162
+ /**
3163
+ * ADMIN: Toggle token active status
3164
+ */
3165
+ toggleTokenActive(tokenId) {
3166
+ return this.tokenBusinessService.toggleTokenActive(tokenId);
3167
+ }
3168
+ /**
3169
+ * ADMIN: Create new token
3170
+ */
3171
+ createToken(tokenData) {
3172
+ return this.tokenBusinessService.createToken(tokenData);
3173
+ }
3174
+ /**
3175
+ * ADMIN: Update token
3176
+ */
3177
+ updateToken(tokenId, tokenData) {
3178
+ return this.tokenBusinessService.updateToken(tokenId, tokenData);
3179
+ }
3180
+ /**
3181
+ * ADMIN: Set mainnet contract address
3182
+ */
3183
+ setMainnetContract(tokenId, contractAddress, chainId) {
3184
+ return this.tokenBusinessService.setMainnetContract(tokenId, contractAddress, chainId);
3185
+ }
3186
+ /**
3187
+ * ADMIN: Toggle token metadata status
3188
+ */
3189
+ toggleTokenMetadataStatus(metadataId) {
3190
+ return this.tokenBusinessService.toggleTokenMetadataStatus(metadataId);
3191
+ }
3192
+ /**
3193
+ * ADMIN: Create token type
3194
+ */
3195
+ createTokenType(tokenType) {
3196
+ return this.tokenBusinessService.createTokenType(tokenType);
3197
+ }
3198
+ }
3199
+
3200
+ /**
3201
+ * Platform-Agnostic User API Client
3202
+ *
3203
+ * Handles user operations using the PERS backend RESTful API.
3204
+ * Updated to use new /users endpoints with enhanced security and consistency.
3205
+ * Maintains framework UserApiService method compatibility.
3206
+ */
3207
+ class UserApi {
3208
+ constructor(apiClient) {
3209
+ this.apiClient = apiClient;
3210
+ this.basePath = '/users';
3211
+ }
3212
+ // ==========================================
3213
+ // PUBLIC OPERATIONS
3214
+ // ==========================================
3215
+ /**
3216
+ * PUBLIC: Get all users public profiles with optional filtering
3217
+ * ✅ UPDATED: Uses new RESTful /users/public endpoint
3218
+ */
3219
+ async getAllUsersPublicProfiles(filter = null) {
3220
+ let url = `${this.basePath}/public`;
3221
+ if (filter) {
3222
+ // ✅ MAINTAINED: Same parameter pattern for compatibility
3223
+ const params = new URLSearchParams();
3224
+ params.set('filterKey', filter.key);
3225
+ params.set('filterValue', filter.value);
3226
+ url += `?${params.toString()}`;
3227
+ }
3228
+ return this.apiClient.get(url);
3229
+ }
3230
+ // ==========================================
3231
+ // AUTHENTICATED OPERATIONS
3232
+ // ==========================================
3233
+ /**
3234
+ * AUTH: Get current authenticated user
3235
+ * ✅ UPDATED: Uses new RESTful /users/me endpoint
3236
+ */
3237
+ async getRemoteUser() {
3238
+ return this.apiClient.get(`${this.basePath}/me`);
3239
+ }
3240
+ /**
3241
+ * AUTH: Update current authenticated user
3242
+ * ✅ UPDATED: Uses new RESTful /users/me endpoint
3243
+ */
3244
+ async updateRemoteUser(updateRequest) {
3245
+ return this.apiClient.put(`${this.basePath}/me`, updateRequest);
3246
+ }
3247
+ // ==========================================
3248
+ // ADMIN OPERATIONS
3249
+ // ==========================================
3250
+ /**
3251
+ * ADMIN: Get all remote users with query parameters
3252
+ * ✅ UPDATED: Uses new RESTful /users endpoint with role-based access
3253
+ * Note: Admin users get full data, non-admin users get public profiles only
3254
+ */
3255
+ async getAllRemoteUsers() {
3256
+ // ✅ MAINTAINED: Same merge=soft parameter for compatibility
3257
+ const url = `${this.basePath}?merge=soft`;
3258
+ return this.apiClient.get(url);
3259
+ }
3260
+ /**
3261
+ * ADMIN: Update user as admin
3262
+ * ✅ UPDATED: Uses new RESTful /users/{id} endpoint
3263
+ */
3264
+ async updateUserAsAdmin(id, userData) {
3265
+ return this.apiClient.put(`${this.basePath}/${id}`, userData);
3266
+ }
3267
+ /**
3268
+ * ADMIN: Toggle user active status
3269
+ * ✅ UPDATED: Uses new consistent /users/{id}/status endpoint
3270
+ * Enhanced: Follows RESTful status management pattern across all domains
3271
+ */
3272
+ async toggleUserActiveStatusByUser(user) {
3273
+ return this.apiClient.put(`${this.basePath}/${user.id}/status`, {});
3274
+ }
3275
+ /**
3276
+ * ADMIN: Get user by unique identifier
3277
+ * ✅ UPDATED: Uses new RESTful /users/{id} endpoint
3278
+ */
3279
+ async getUserByUniqueIdentifier(id) {
3280
+ return this.apiClient.get(`${this.basePath}/${id}`);
3281
+ }
3282
+ }
3283
+
3284
+ /**
3285
+ * Platform-Agnostic User Service
3286
+ *
3287
+ * Contains user business logic and operations that work across platforms.
3288
+ * No framework dependencies - pure TypeScript business logic.
3289
+ * Matches framework UserApiService capabilities exactly.
3290
+ */
3291
+ class UserService {
3292
+ constructor(userApi) {
3293
+ this.userApi = userApi;
3294
+ }
3295
+ // ==========================================
3296
+ // PUBLIC OPERATIONS
3297
+ // ==========================================
3298
+ /**
3299
+ * PUBLIC: Get all users public profiles with optional filtering
3300
+ * ✅ FIXED: Uses framework-compatible inline filter type
3301
+ */
3302
+ async getAllUsersPublicProfiles(filter = null) {
3303
+ return this.userApi.getAllUsersPublicProfiles(filter);
3304
+ }
3305
+ // ==========================================
3306
+ // AUTHENTICATED OPERATIONS
3307
+ // ==========================================
3308
+ /**
3309
+ * AUTH: Get current authenticated user
3310
+ */
3311
+ async getRemoteUser() {
3312
+ return this.userApi.getRemoteUser();
3313
+ }
3314
+ /**
3315
+ * AUTH: Update current authenticated user
3316
+ */
3317
+ async updateRemoteUser(updateRequest) {
3318
+ return this.userApi.updateRemoteUser(updateRequest);
3319
+ }
3320
+ // ==========================================
3321
+ // ADMIN OPERATIONS
3322
+ // ==========================================
3323
+ /**
3324
+ * ADMIN: Get all remote users
3325
+ * ✅ FIXED: Matches API method signature (no parameters needed)
3326
+ */
3327
+ async getAllRemoteUsers() {
3328
+ return this.userApi.getAllRemoteUsers();
3329
+ }
3330
+ /**
3331
+ * ADMIN: Update user as admin
3332
+ */
3333
+ async updateUserAsAdmin(id, userData) {
3334
+ return this.userApi.updateUserAsAdmin(id, userData);
3335
+ }
3336
+ async getUserByUniqueIdentifier(id) {
3337
+ return this.userApi.getUserByUniqueIdentifier(id);
3338
+ }
3339
+ /**
3340
+ * ADMIN: Toggle user active status by user object
3341
+ * ✅ FIXED: Matches API method signature exactly
3342
+ */
3343
+ async toggleUserActiveStatusByUser(user) {
3344
+ return this.userApi.toggleUserActiveStatusByUser(user);
3345
+ }
3346
+ }
3347
+
3348
+ /**
3349
+ * @explorins/pers-sdk-user
3350
+ *
3351
+ * Platform-agnostic User Domain SDK for PERS ecosystem
3352
+ * Handles user management, profiles, and authentication operations
3353
+ */
3354
+ // API Layer
3355
+ /**
3356
+ * Create a complete User SDK instance
3357
+ *
3358
+ * @param apiClient - Configured PERS API client
3359
+ * @returns User SDK with flattened structure for better DX
3360
+ */
3361
+ function createUserSDK(apiClient) {
3362
+ const userApi = new UserApi(apiClient);
3363
+ const userService = new UserService(userApi);
3364
+ return {
3365
+ // Direct access to service methods (primary interface)
3366
+ // Public methods - matches framework exactly
3367
+ getAllUsersPublicProfiles: (filter = null) => userService.getAllUsersPublicProfiles(filter),
3368
+ // Auth methods - matches framework exactly
3369
+ getRemoteUser: () => userService.getRemoteUser(),
3370
+ updateRemoteUser: (updateRequest) => userService.updateRemoteUser(updateRequest),
3371
+ // Admin methods - matches framework exactly
3372
+ getAllRemoteUsers: () => userService.getAllRemoteUsers(),
3373
+ updateUserAsAdmin: (id, userData) => userService.updateUserAsAdmin(id, userData),
3374
+ toggleUserActiveStatusByUser: (user) => userService.toggleUserActiveStatusByUser(user),
3375
+ getUserByUniqueIdentifier: (id) => userService.getUserByUniqueIdentifier(id),
3376
+ // Advanced access for edge cases
3377
+ api: userApi,
3378
+ service: userService
3379
+ };
3380
+ }
3381
+
3382
+ /**
3383
+ * Platform-Agnostic User Status API Client
3384
+ *
3385
+ * Handles user status operations using the PERS backend.
3386
+ * Matches framework UserStatusApiService methods exactly.
3387
+ *
3388
+ * ✅ UPDATED: All endpoints updated to new RESTful /users patterns
3389
+ *
3390
+ * Error handling patterns follow framework implementation:
3391
+ * - getRemoteEarnedUserStatus() uses silent fallback (empty array)
3392
+ * - Other operations throw errors for proper error handling
3393
+ */
3394
+ class UserStatusApi {
3395
+ constructor(apiClient) {
3396
+ this.apiClient = apiClient;
3397
+ this.basePath = '/users';
3398
+ }
3399
+ // ==========================================
3400
+ // PUBLIC OPERATIONS
3401
+ // ==========================================
3402
+ /**
3403
+ * PUBLIC: Get remote user status types
3404
+ * ✅ UPDATED: /user/status-type → /users/status-types
3405
+ */
3406
+ async getRemoteUserStatusTypes() {
3407
+ try {
3408
+ return await this.apiClient.get(`${this.basePath}/status-types`);
3409
+ }
3410
+ catch (error) {
3411
+ console.error('Error getting user status types', error);
3412
+ throw error;
3413
+ }
3414
+ }
3415
+ // ==========================================
3416
+ // AUTHENTICATED OPERATIONS
3417
+ // ==========================================
3418
+ /**
3419
+ * AUTH: Get earned user status for authenticated user
3420
+ * ✅ UPDATED: /user/auth/status → /users/me/status
3421
+ * ✅ FIXED: Returns UserStatusTypeDTO[] to match framework exactly
3422
+ * Note: Uses silent fallback pattern from framework - returns empty array on error
3423
+ */
3424
+ async getRemoteEarnedUserStatus() {
3425
+ try {
3426
+ return await this.apiClient.get(`${this.basePath}/me/status`);
3427
+ }
3428
+ catch (error) {
3429
+ console.error('Error getting user status', error);
3430
+ // ✅ FIXED: Silent fallback pattern from framework implementation
3431
+ return [];
3432
+ }
3433
+ }
3434
+ // ==========================================
3435
+ // ADMIN OPERATIONS
3436
+ // ==========================================
3437
+ /**
3438
+ * ADMIN: Create user status type
3439
+ * ✅ UPDATED: /user/admin/status-type → /users/status-types
3440
+ */
3441
+ async createUserStatusType(userStatusType) {
3442
+ try {
3443
+ return await this.apiClient.post(`${this.basePath}/status-types`, userStatusType);
3444
+ }
3445
+ catch (error) {
3446
+ console.error('Error creating user status type', error);
3447
+ throw error;
3448
+ }
3449
+ }
3450
+ }
3451
+
3452
+ /**
3453
+ * Platform-Agnostic User Status Service
3454
+ *
3455
+ * Contains user status business logic and operations that work across platforms.
3456
+ * No framework dependencies - pure TypeScript business logic.
3457
+ * Matches framework UserStatusApiService capabilities exactly.
3458
+ */
3459
+ class UserStatusService {
3460
+ constructor(userStatusApi) {
3461
+ this.userStatusApi = userStatusApi;
3462
+ }
3463
+ // ==========================================
3464
+ // PUBLIC OPERATIONS
3465
+ // ==========================================
3466
+ /**
3467
+ * PUBLIC: Get remote user status types
3468
+ */
3469
+ async getRemoteUserStatusTypes() {
3470
+ return this.userStatusApi.getRemoteUserStatusTypes();
3471
+ }
3472
+ // ==========================================
3473
+ // AUTHENTICATED OPERATIONS
3474
+ // ==========================================
3475
+ /**
3476
+ * AUTH: Get earned user status for authenticated user
3477
+ */
3478
+ async getRemoteEarnedUserStatus() {
3479
+ return this.userStatusApi.getRemoteEarnedUserStatus();
3480
+ }
3481
+ // ==========================================
3482
+ // ADMIN OPERATIONS
3483
+ // ==========================================
3484
+ /**
3485
+ * ADMIN: Create user status type
3486
+ */
3487
+ async createUserStatusType(userStatusType) {
3488
+ return this.userStatusApi.createUserStatusType(userStatusType);
3489
+ }
3490
+ }
3491
+
3492
+ /**
3493
+ * @explorins/pers-sdk-user-status
3494
+ *
3495
+ * Platform-agnostic User Status Domain SDK for PERS ecosystem
3496
+ * Handles user status management and type operations
3497
+ */
3498
+ // API Layer
3499
+ /**
3500
+ * Create a complete User Status SDK instance
3501
+ *
3502
+ * @param apiClient - Configured PERS API client
3503
+ * @returns User Status SDK with flattened structure for better DX
3504
+ */
3505
+ function createUserStatusSDK(apiClient) {
3506
+ const userStatusApi = new UserStatusApi(apiClient);
3507
+ const userStatusService = new UserStatusService(userStatusApi);
3508
+ return {
3509
+ // Direct access to service methods (primary interface)
3510
+ // ✅ FRAMEWORK ALIGNED: Only methods actually used by framework
3511
+ // Public methods
3512
+ getRemoteUserStatusTypes: () => userStatusService.getRemoteUserStatusTypes(),
3513
+ // Auth methods
3514
+ getRemoteEarnedUserStatus: () => userStatusService.getRemoteEarnedUserStatus(),
3515
+ // Admin methods
3516
+ createUserStatusType: (userStatusType) => userStatusService.createUserStatusType(userStatusType),
3517
+ // Advanced access for edge cases
3518
+ api: userStatusApi,
3519
+ service: userStatusService
3520
+ };
3521
+ }
3522
+
3523
+ /**
3524
+ * Platform-Agnostic Web3 Chain API Client
3525
+ *
3526
+ * Handles blockchain chain operations using the PERS backend.
3527
+ * Uses @explorins/web3-ts types for perfect framework compatibility.
3528
+ */
3529
+ class Web3ChainApi {
3530
+ constructor(apiClient) {
3531
+ this.apiClient = apiClient;
3532
+ this.basePath = '/chains';
3533
+ }
3534
+ // ==========================================
3535
+ // PUBLIC OPERATIONS
3536
+ // ==========================================
3537
+ /**
3538
+ * PUBLIC: Get chain data by chain ID
3539
+ * ✅ Returns ChainData exactly as framework expects from @explorins/web3-ts
3540
+ */
3541
+ async getChainData(chainId) {
3542
+ try {
3543
+ console.log('🔍 [Web3ChainApi] Fetching chain data for chainId:', chainId);
3544
+ const response = await this.apiClient.get(`${this.basePath}/${chainId}`);
3545
+ if (!response) {
3546
+ throw new Error(`No chain data received for chainId: ${chainId}`);
3547
+ }
3548
+ return response;
3549
+ }
3550
+ catch (error) {
3551
+ console.error('❌ [Web3ChainApi] Failed to get chain data:', {
3552
+ chainId,
3553
+ error: error instanceof Error ? error.message : String(error)
3554
+ });
3555
+ throw error;
3556
+ }
3557
+ }
3558
+ }
3559
+
3560
+ const isTokenExpired = (token, margin = 60) => {
3561
+ // ✅ SANITIZE: Remove any words (Bearer etc) if exists and get the token
3562
+ const cleanToken = token.split(' ')[1] || token;
3563
+ // ✅ VALIDATION: Check if token is jwt
3564
+ if (!cleanToken || cleanToken.split('.').length !== 3) {
3565
+ return true; // Consider invalid tokens as expired
3566
+ }
3567
+ try {
3568
+ // ✅ TYPE-SAFE: Decode with proper typing
3569
+ const decoded = jwtDecode.jwtDecode(cleanToken);
3570
+ const currentTime = Math.floor(Date.now() / 1000);
3571
+ // ✅ CONFIGURABLE: Use provided margin (default 60 seconds)
3572
+ return decoded.exp < (currentTime + margin);
3573
+ }
3574
+ catch (error) {
3575
+ console.error('[SDK JWT Utils] Error decoding JWT token:', error);
3576
+ return true; // Consider unparseable tokens as expired
3577
+ }
3578
+ };
3579
+
3580
+ class Web3ChainService {
3581
+ constructor(web3ChainApi, providerService) {
3582
+ this.web3ChainApi = web3ChainApi;
3583
+ this.providerService = providerService;
3584
+ this.web3InstanceCache = new Map();
3585
+ }
3586
+ async getWeb3ByChainId(chainId) {
3587
+ const cached = this.web3InstanceCache.get(chainId);
3588
+ // ✅ SMART CACHE: Check if cache is valid based on token expiration
3589
+ if (cached && !this.checkIsTokenExpired(cached.chainData.authHeader)) {
3590
+ console.log(`♻️ [Web3ChainService] Using cached Web3 instance for chain ${chainId}`);
3591
+ return cached.web3Instance;
3592
+ }
3593
+ // ✅ CREATE WITH ERROR RECOVERY: Handle auth errors gracefully
3594
+ try {
3595
+ console.log(`🔧 [Web3ChainService] Creating new Web3 instance for chain ${chainId}`);
3596
+ const { web3, chainData } = await this.createWeb3Instance(chainId);
3597
+ // ✅ CACHE NEW INSTANCE
3598
+ this.web3InstanceCache.set(chainId, {
3599
+ web3Instance: web3,
3600
+ chainData: chainData,
3601
+ createdAt: Date.now(),
3602
+ chainId
3603
+ });
3604
+ return web3;
3605
+ }
3606
+ catch (error) {
3607
+ // ✅ AUTH ERROR RECOVERY: Clear cache and retry once
3608
+ if (this.isAuthError(error)) {
3609
+ console.warn(`🔄 [Web3ChainService] Auth error detected, clearing cache and retrying...`);
3610
+ this.web3InstanceCache.delete(chainId);
3611
+ // Retry once with fresh instance
3612
+ const { web3, chainData } = await this.createWeb3Instance(chainId);
3613
+ this.web3InstanceCache.set(chainId, {
3614
+ web3Instance: web3,
3615
+ chainData: chainData,
3616
+ createdAt: Date.now(),
3617
+ chainId
3618
+ });
3619
+ return web3;
3620
+ }
3621
+ throw error;
3622
+ }
3623
+ }
3624
+ async getChainDataWithCache(chainId) {
3625
+ const cached = this.web3InstanceCache.get(chainId);
3626
+ //const now = Date.now();
3627
+ if (cached && !this.checkIsTokenExpired(cached.chainData.authHeader)) {
3628
+ console.log(`♻️ [Web3ChainService] Using cached ChainData for chain ${chainId}`);
3629
+ return cached.chainData;
3630
+ }
3631
+ // If not cached, fetch fresh data
3632
+ const chainData = await this.getChainDataById(chainId);
3633
+ if (!chainData) {
3634
+ throw new Error(`Chain data not found for chainId: ${chainId}`);
3635
+ }
3636
+ return chainData;
3637
+ }
3638
+ checkIsTokenExpired(authHeader) {
3639
+ // ✅ NULL CHECK: Handle undefined case
3640
+ if (!authHeader) {
3641
+ return false; // No token means no expiration (public chains)
3642
+ }
3643
+ // ✅ FUNCTION CALL: Use imported function, not method
3644
+ return isTokenExpired(authHeader);
3645
+ }
3646
+ async createWeb3Instance(chainId) {
3647
+ const chainData = await this.getChainDataById(chainId);
3648
+ if (!chainData) {
3649
+ throw new Error(`Chain data not found for chainId: ${chainId}`);
3650
+ }
3651
+ // ✅ CRITICAL CHECK: Ensure authHeader is present for private chains
3652
+ if (chainData.chainType === 'PRIVATE' && !chainData.authHeader) {
3653
+ console.error('❌ [Web3ChainService] CRITICAL: Private chain missing authHeader!');
3654
+ throw new Error(`Private chain ${chainId} missing authentication header`);
3655
+ }
3656
+ const web3 = await this.providerService.getWeb3(chainId, chainData.chainType || 'PUBLIC', chainData);
3657
+ return { web3, chainData };
3658
+ }
3659
+ // ✅ HELPER: Type-safe error checking
3660
+ isAuthError(error) {
3661
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
3662
+ return (message.includes('unauthorized') ||
3663
+ message.includes('401') ||
3664
+ message.includes('token expired') ||
3665
+ message.includes('invalid token'));
3666
+ }
3667
+ async getChainDataById(chainId) {
3668
+ try {
3669
+ return await this.web3ChainApi.getChainData(chainId);
3670
+ }
3671
+ catch (error) {
3672
+ console.error('❌ [SDK Web3ChainService] Error getting chain data for chainId:', chainId, error);
3673
+ return null;
3674
+ }
3675
+ }
3676
+ }
3677
+
3678
+ //IMPORTANT//
3679
+ //This function is temporary so we install ethers just to make it work, once we delete this function we must uninstall Ethers
3680
+ const getWeb3ProviderFromChainData = (chainData, timeout = 15000, customUserAgentName = '', tokenRefresher) => {
3681
+ // Fixed ethers provider setup for authenticated requests
3682
+ let ethersProvider;
3683
+ if (chainData.authHeader) {
3684
+ // For authenticated requests, create a custom FetchRequest
3685
+ const fetchRequest = new ethers.FetchRequest(chainData.rpcUrl);
3686
+ fetchRequest.timeout = timeout;
3687
+ fetchRequest.setHeader('Authorization', chainData.authHeader);
3688
+ fetchRequest.setHeader('Content-Type', 'application/json');
3689
+ if (customUserAgentName) {
3690
+ fetchRequest.setHeader('User-Agent', customUserAgentName);
3691
+ }
3692
+ // Create provider with the configured FetchRequest
3693
+ ethersProvider = new ethers.JsonRpcProvider(fetchRequest, undefined, {
3694
+ staticNetwork: false,
3695
+ polling: false, // Disable polling for better Lambda performance
3696
+ });
3697
+ }
3698
+ else {
3699
+ // For public chains, use simple URL-based provider
3700
+ ethersProvider = new ethers.JsonRpcProvider(chainData.rpcUrl, undefined, {
3701
+ staticNetwork: false,
3702
+ polling: false,
3703
+ });
3704
+ }
3705
+ return {
3706
+ web3Provider: null,
3707
+ ethersProvider: ethersProvider,
3708
+ isAuthenticated: !!chainData.authHeader,
3709
+ };
3710
+ };
3711
+
3712
+ class Web3ProviderService {
3713
+ constructor(publicHttpProviderService) {
3714
+ this.publicHttpProviderService = publicHttpProviderService;
3715
+ this._web3 = null;
3716
+ this._currentChainId = null;
3717
+ }
3718
+ async getWeb3(chainId, chainType, privateChainData = null) {
3719
+ if (!this._web3 || this._currentChainId !== chainId) {
3720
+ if (!chainId)
3721
+ throw new Error('ChainId not found');
3722
+ try {
3723
+ this._currentChainId = chainId;
3724
+ const provider = await this.getWeb3ByChainId(chainId, chainType, privateChainData);
3725
+ this._web3 = this.convertToWeb3(provider);
3726
+ }
3727
+ catch (error) {
3728
+ console.error('Error getting web3 connection from chain id ' + chainId, error);
3729
+ throw new Error('Error getting web3 connection from chain id ' + chainId);
3730
+ }
3731
+ }
3732
+ return this._web3;
3733
+ }
3734
+ // Keep return type as 'any' to avoid TypeScript errors while still being adapted later
3735
+ getWeb3ByChainId(chainId, chainType, privateChainData = null) {
3736
+ // Rest of the method remains the same
3737
+ if (chainType === web3Ts.ChainTypes.PRIVATE && privateChainData) {
3738
+ //const privateProvider = this.privateChainProviderService.getProviderFromChainData(privateChainData)
3739
+ const privateProvider = getWeb3ProviderFromChainData(privateChainData);
3740
+ if (!privateProvider || privateProvider instanceof Error)
3741
+ throw new Error('Error getting web3 provider');
3742
+ return privateProvider;
3743
+ }
3744
+ else {
3745
+ const publicProvider = this.publicHttpProviderService.getProvider(chainId);
3746
+ if (!publicProvider || publicProvider instanceof Error)
3747
+ throw new Error('Error getting web3 provider');
3748
+ return publicProvider;
3749
+ }
3750
+ }
3751
+ convertToWeb3(provider) {
3752
+ if (provider instanceof Web3) {
3753
+ return provider;
3754
+ }
3755
+ if (provider && typeof provider === 'object' && 'web3Provider' in provider) {
3756
+ const providerObj = provider;
3757
+ // If we want to user the web3Provider directly:
3758
+ /*if (providerObj.web3Provider) {
3759
+ return new Web3(providerObj.web3Provider as never);
3760
+ }*/
3761
+ if (providerObj.ethersProvider) {
3762
+ const url = this.extractUrlFromEthersProvider(providerObj.ethersProvider);
3763
+ const headers = this.extractHeadersFromEthersProvider(providerObj.ethersProvider);
3764
+ const web3 = new Web3(url);
3765
+ const currentProvider = web3.currentProvider;
3766
+ if (currentProvider) {
3767
+ currentProvider['url'] = url;
3768
+ if (headers && Object.keys(headers).length > 0) {
3769
+ currentProvider['request'] = async (payload) => {
3770
+ const response = await fetch(url, {
3771
+ method: 'POST',
3772
+ headers: {
3773
+ 'Content-Type': 'application/json',
3774
+ ...headers
3775
+ },
3776
+ body: JSON.stringify(payload)
3777
+ });
3778
+ if (!response.ok) {
3779
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
3780
+ }
3781
+ return await response.json();
3782
+ };
3783
+ }
3784
+ }
3785
+ return web3;
3786
+ }
3787
+ }
3788
+ return new Web3(provider);
3789
+ }
3790
+ extractUrlFromEthersProvider(ethersProvider) {
3791
+ return ethersProvider.connection?.url ||
3792
+ ethersProvider._getConnection?.()?.url ||
3793
+ ethersProvider.url ||
3794
+ '';
3795
+ }
3796
+ extractHeadersFromEthersProvider(ethersProvider) {
3797
+ return ethersProvider.connection?.headers ||
3798
+ ethersProvider._getConnection?.()?.headers ||
3799
+ {};
3800
+ }
3801
+ }
3802
+
3803
+ /**
3804
+ * Web3 Chain Domain Models
3805
+ *
3806
+ * Minimal interface definitions matching framework usage exactly.
3807
+ * Local ChainData interface to avoid external dependency issues.
3808
+ */
3809
+ const ChainTypes = {
3810
+ PUBLIC: 'PUBLIC',
3811
+ PRIVATE: 'PRIVATE'
3812
+ };
3813
+
3814
+ function createWeb3ChainSDK(apiClient, providerService) {
3815
+ const web3ChainApi = new Web3ChainApi(apiClient);
3816
+ const web3ChainService = new Web3ChainService(web3ChainApi, providerService); // ✅ DIRECT INJECTION
3817
+ return {
3818
+ // ✅ REPLICA: Same methods as framework
3819
+ getChainDataById: (chainId) => web3ChainService.getChainDataById(chainId),
3820
+ getWeb3ByChainId: (chainId) => web3ChainService.getWeb3ByChainId(chainId),
3821
+ api: web3ChainApi,
3822
+ service: web3ChainService
3823
+ };
3824
+ }
3825
+
3826
+ class Web3Api {
3827
+ constructor(web3ChainService) {
3828
+ this.web3ChainService = web3ChainService;
3829
+ }
3830
+ async getTokenBalance(request) {
3831
+ const web3 = await this.web3ChainService.getWeb3ByChainId(request.chainId);
3832
+ const tokenContract = web3Ts.getSmartContractInstance(request.contractAddress, request.abi, web3);
3833
+ const balance = await web3Ts.getAddressTokenBalanceByContract(tokenContract, request.accountAddress, request.tokenId);
3834
+ return Number(balance);
3835
+ }
3836
+ async getTokenUri(request) {
3837
+ const web3 = await this.web3ChainService.getWeb3ByChainId(request.chainId);
3838
+ // ✅ DIRECT: Use web3-ts functions directly
3839
+ const tokenContract = web3Ts.getSmartContractInstance(request.contractAddress, request.abi, web3);
3840
+ const tokenId = Number(request.tokenId);
3841
+ const tokenUri = await web3Ts.getTokenUri(tokenContract, tokenId);
3842
+ return String(tokenUri);
3843
+ }
3844
+ async getTokenOfOwnerByIndex(request) {
3845
+ const web3 = await this.web3ChainService.getWeb3ByChainId(request.chainId);
3846
+ // ✅ DIRECT: Use web3-ts functions directly
3847
+ const tokenContract = web3Ts.getSmartContractInstance(request.contractAddress, request.abi, web3);
3848
+ const tokenId = await web3Ts.getTokenOfOwnerByIndex(tokenContract, request.accountAddress, request.tokenIndex);
3849
+ return String(tokenId);
3850
+ }
3851
+ }
3852
+
3853
+ class SimpleCache {
3854
+ constructor() {
3855
+ this.storage = {};
3856
+ this.defaultTTL = 10 * 1000; // 10 seconds
3857
+ }
3858
+ set(key, data, ttl) {
3859
+ this.storage[key] = {
3860
+ data,
3861
+ timestamp: Date.now(),
3862
+ ttl: ttl ?? this.defaultTTL
3863
+ };
3864
+ }
3865
+ get(key) {
3866
+ const entry = this.storage[key];
3867
+ if (!entry) {
3868
+ return null;
3869
+ }
3870
+ const now = Date.now();
3871
+ const isExpired = (now - entry.timestamp) > entry.ttl;
3872
+ if (isExpired) {
3873
+ delete this.storage[key];
3874
+ return null;
3875
+ }
3876
+ return entry.data;
3877
+ }
3878
+ clear() {
3879
+ this.storage = {};
3880
+ }
3881
+ cleanup() {
3882
+ const now = Date.now();
3883
+ Object.keys(this.storage).forEach(key => {
3884
+ const entry = this.storage[key];
3885
+ if ((now - entry.timestamp) > entry.ttl) {
3886
+ delete this.storage[key];
3887
+ }
3888
+ });
3889
+ }
3890
+ }
3891
+
3892
+ class Web3Service {
3893
+ constructor(web3Api, web3ChainService) {
3894
+ this.web3Api = web3Api;
3895
+ this.web3ChainService = web3ChainService;
3896
+ //temporary fix, remove when the backend supports custom gateways
3897
+ this.defaultIpfsGatewayDomain = 'pers.mypinata.cloud';
3898
+ // ✅ CACHE: Simple 10-second cache instance
3899
+ this.cache = new SimpleCache();
3900
+ this.cleanupInterval = null;
3901
+ this.cleanupInterval = setInterval(() => {
3902
+ this.cache.cleanup();
3903
+ }, 30 * 1000);
3904
+ }
3905
+ destroy() {
3906
+ if (this.cleanupInterval) {
3907
+ clearInterval(this.cleanupInterval);
3908
+ this.cleanupInterval = null;
3909
+ }
3910
+ this.cache.clear();
3911
+ }
3912
+ async getERC20Balance(request) {
3913
+ const cacheKey = `erc20_balance_${request.accountAddress}_${request.token.contractAddress}_${request.token.chainId}`;
3914
+ // ✅ CACHE CHECK: Try to get from cache first
3915
+ const cached = this.cache.get(cacheKey);
3916
+ if (cached) {
3917
+ console.debug(`💾 [Web3Service] Using cached ERC20 balance for ${request.token.symbol}`);
3918
+ return cached;
3919
+ }
3920
+ console.debug(`🔄 [Web3Service] Fetching fresh ERC20 balance for ${request.token.symbol}`);
3921
+ const rawBalance = await this.web3Api.getTokenBalance({
3922
+ accountAddress: request.accountAddress,
3923
+ contractAddress: request.token.contractAddress,
3924
+ abi: request.token.abi,
3925
+ tokenId: null, // Always null for ERC20
3926
+ chainId: request.token.chainId
3927
+ });
3928
+ const decimals = request.token.decimals ?? 18;
3929
+ const symbol = request.token.symbol ?? 'UNKNOWN';
3930
+ const response = {
3931
+ rawBalance,
3932
+ formattedBalance: this.formatBalance(rawBalance, decimals),
3933
+ decimals,
3934
+ symbol,
3935
+ hasBalance: rawBalance > 0
3936
+ };
3937
+ // ✅ CACHE SET: Store result in cache
3938
+ this.cache.set(cacheKey, response);
3939
+ return response;
3940
+ }
3941
+ async getERC1155Collection(request) {
3942
+ // ✅ CACHE KEY: Create unique cache key for collection request
3943
+ const contractAddresses = request.tokens.map(t => t.contractAddress).sort().join(',');
3944
+ const cacheKey = `erc1155_collection_${request.accountAddress}_${contractAddresses}`;
3945
+ // ✅ CACHE CHECK: Try to get from cache first
3946
+ const cached = this.cache.get(cacheKey);
3947
+ if (cached) {
3948
+ console.debug(`💾 [Web3Service] Using cached ERC1155 collection for ${request.accountAddress}`);
3949
+ return cached;
3950
+ }
3951
+ console.debug(`🔄 [Web3Service] Fetching fresh ERC1155 collection for ${request.accountAddress}`);
3952
+ const tokenResults = await Promise.all(request.tokens.map(async (token) => {
3953
+ // ✅ FIXED: Handle null metadata properly
3954
+ const tokenIds = token.metadata?.map(m => m.tokenMetadataIncrementalId?.toString()).filter((id) => id !== undefined) ?? [];
3955
+ // Check balance for each known tokenId
3956
+ const balanceResults = await Promise.allSettled(tokenIds.map(async (tokenId) => {
3957
+ try {
3958
+ const rawBalance = await this.web3Api.getTokenBalance({
3959
+ accountAddress: request.accountAddress,
3960
+ contractAddress: token.contractAddress,
3961
+ abi: token.abi,
3962
+ tokenId,
3963
+ chainId: token.chainId
3964
+ });
3965
+ const decimals = token.decimals ?? 0; // ERC1155 usually no decimals
3966
+ return {
3967
+ tokenId,
3968
+ balance: rawBalance,
3969
+ formattedBalance: this.formatBalance(rawBalance, decimals),
3970
+ hasBalance: rawBalance > 0,
3971
+ // ✅ FIXED: Convert null to undefined for findMetadata
3972
+ metadata: this.findMetadata(token.metadata ?? undefined, tokenId)
3973
+ };
3974
+ }
3975
+ catch (error) {
3976
+ console.warn(`Failed to get balance for token ${token.contractAddress}:${tokenId}`, error);
3977
+ return null; // Skip failed tokens
3978
+ }
3979
+ }));
3980
+ // Filter successful results with balance > 0
3981
+ const successfulResults = [];
3982
+ for (const result of balanceResults) {
3983
+ if (result.status === 'fulfilled' && result.value !== null && result.value.hasBalance) {
3984
+ successfulResults.push(result.value);
3985
+ }
3986
+ }
3987
+ return {
3988
+ token,
3989
+ results: successfulResults
3990
+ };
3991
+ }));
3992
+ const response = {
3993
+ accountAddress: request.accountAddress,
3994
+ tokens: tokenResults.filter(t => t.results.length > 0)
3995
+ };
3996
+ // ✅ CACHE SET: Store complete collection result
3997
+ this.cache.set(cacheKey, response);
3998
+ return response;
3999
+ }
4000
+ async getERC721Collection(request) {
4001
+ // ✅ CACHE KEY: Create unique cache key for NFT collection
4002
+ const contractAddresses = request.nftContracts.map(t => t.contractAddress).sort().join(',');
4003
+ const maxNFTs = request.maxNFTsPerContract || 50;
4004
+ const cacheKey = `erc721_collection_${request.accountAddress}_${contractAddresses}_${maxNFTs}`;
4005
+ // ✅ CACHE CHECK: Try to get from cache first
4006
+ const cached = this.cache.get(cacheKey);
4007
+ if (cached) {
4008
+ console.debug(`💾 [Web3Service] Using cached ERC721 collection for ${request.accountAddress}`);
4009
+ return cached;
4010
+ }
4011
+ console.debug(`🔄 [Web3Service] Fetching fresh ERC721 collection for ${request.accountAddress}`);
4012
+ const startTime = Date.now();
4013
+ const contractResults = await Promise.all(request.nftContracts.map(async (token) => {
4014
+ try {
4015
+ const totalBalance = await this.web3Api.getTokenBalance({
4016
+ accountAddress: request.accountAddress,
4017
+ contractAddress: token.contractAddress,
4018
+ abi: token.abi,
4019
+ tokenId: null,
4020
+ chainId: token.chainId
4021
+ });
4022
+ if (totalBalance === 0) {
4023
+ return {
4024
+ token,
4025
+ totalNFTs: 0,
4026
+ nfts: [],
4027
+ hasMore: false
4028
+ };
4029
+ }
4030
+ const nftsToLoad = Math.min(totalBalance, maxNFTs);
4031
+ const nftResults = await Promise.allSettled(Array.from({ length: nftsToLoad }, async (_, index) => {
4032
+ try {
4033
+ const tokenId = await this.web3Api.getTokenOfOwnerByIndex({
4034
+ contractAddress: token.contractAddress,
4035
+ abi: token.abi,
4036
+ accountAddress: request.accountAddress,
4037
+ tokenIndex: index,
4038
+ chainId: token.chainId
4039
+ });
4040
+ const tokenUri = await this.web3Api.getTokenUri({
4041
+ contractAddress: token.contractAddress,
4042
+ abi: token.abi,
4043
+ tokenId,
4044
+ chainId: token.chainId
4045
+ });
4046
+ const metadata = await this.fetchMetadata(tokenUri, token.chainId);
4047
+ const nftItem = {
4048
+ tokenId,
4049
+ name: metadata?.name || `Token #${tokenId}`,
4050
+ description: metadata?.description || '',
4051
+ imageUrl: await this.resolveIPFSUrl(metadata?.image || '', token.chainId),
4052
+ rawBalance: 1,
4053
+ formattedBalance: '1',
4054
+ hasBalance: true,
4055
+ metadata,
4056
+ tokenIndex: index
4057
+ };
4058
+ return nftItem;
4059
+ }
4060
+ catch (error) {
4061
+ console.warn(`Failed to load NFT at index ${index} for ${token.symbol}:`, error);
4062
+ return null;
4063
+ }
4064
+ }));
4065
+ // ✅ FIXED: Usar tipo específico NFTItem
4066
+ const successfulNFTs = nftResults
4067
+ .filter((result) => result.status === 'fulfilled' && result.value !== null)
4068
+ .map(result => result.value);
4069
+ return {
4070
+ token,
4071
+ totalNFTs: totalBalance,
4072
+ nfts: successfulNFTs,
4073
+ hasMore: totalBalance > maxNFTs
4074
+ };
4075
+ }
4076
+ catch (error) {
4077
+ console.error(`Failed to load NFT collection for ${token.symbol}:`, error);
4078
+ return {
4079
+ token,
4080
+ totalNFTs: 0,
4081
+ nfts: [],
4082
+ hasMore: false
4083
+ };
4084
+ }
4085
+ }));
4086
+ const totalNFTs = contractResults.reduce((sum, contract) => sum + contract.nfts.length, 0);
4087
+ const loadingTime = Date.now() - startTime;
4088
+ const response = {
4089
+ accountAddress: request.accountAddress,
4090
+ contracts: contractResults,
4091
+ summary: {
4092
+ totalContracts: request.nftContracts.length,
4093
+ totalNFTs,
4094
+ loadingTime
4095
+ }
4096
+ };
4097
+ // ✅ CACHE SET: Store complete collection response
4098
+ this.cache.set(cacheKey, response);
4099
+ return response;
4100
+ }
4101
+ // ==========================================
4102
+ // HELPER METHODS
4103
+ // ==========================================
4104
+ formatBalance(rawBalance, decimals) {
4105
+ const balance = rawBalance / Math.pow(10, decimals);
4106
+ return balance.toLocaleString('en-US', {
4107
+ minimumFractionDigits: 0,
4108
+ maximumFractionDigits: decimals > 0 ? 2 : 0
4109
+ });
4110
+ }
4111
+ // ✅ FIXED: Update method signature to handle null properly
4112
+ findMetadata(metadata, tokenId) {
4113
+ if (!metadata || tokenId === null)
4114
+ return null;
4115
+ return metadata.find(m => m.tokenMetadataIncrementalId?.toString() === tokenId) || null;
4116
+ }
4117
+ async fetchMetadata(uri, chainId) {
4118
+ try {
4119
+ const httpUrl = await this.resolveIPFSUrl(uri, chainId);
4120
+ const response = await fetch(httpUrl);
4121
+ if (!response.ok) {
4122
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4123
+ }
4124
+ return await response.json();
4125
+ }
4126
+ catch (error) {
4127
+ console.warn('Failed to fetch NFT metadata:', error);
4128
+ return null;
4129
+ }
4130
+ }
4131
+ async getIpfsGatewayDomain(chainId) {
4132
+ try {
4133
+ const chainData = await this.web3ChainService.getChainDataWithCache(chainId);
4134
+ return chainData.ipfsGatewayDomain || this.defaultIpfsGatewayDomain;
4135
+ }
4136
+ catch (error) {
4137
+ console.warn(`Failed to get chain data for chainId ${chainId}, using default IPFS gateway:`, error);
4138
+ return this.defaultIpfsGatewayDomain;
4139
+ }
4140
+ }
4141
+ async resolveIPFSUrl(url, chainId) {
4142
+ if (!url)
4143
+ return '';
4144
+ if (url.startsWith('ipfs://')) {
4145
+ const gatewayDomain = await this.getIpfsGatewayDomain(chainId);
4146
+ return `https://${gatewayDomain}/ipfs/${url.slice(7)}`;
4147
+ }
4148
+ return url;
4149
+ }
4150
+ }
4151
+
4152
+ function createWeb3SDK(apiClient, web3ProviderService) {
4153
+ const web3ChainSDK = createWeb3ChainSDK(apiClient, web3ProviderService);
4154
+ const web3Api = new Web3Api(web3ChainSDK.service);
4155
+ const web3Service = new Web3Service(web3Api, web3ChainSDK.service);
4156
+ return {
4157
+ getCreditsBalance: (request) => web3Service.getERC20Balance(request),
4158
+ getRewardsCollection: (request) => web3Service.getERC1155Collection(request),
4159
+ getStampsCollection: (request) => web3Service.getERC721Collection(request),
4160
+ api: web3Api,
4161
+ service: web3Service
4162
+ };
4163
+ }
4164
+
4165
+ exports.AnalyticsApi = AnalyticsApi;
4166
+ exports.AnalyticsService = AnalyticsService;
4167
+ exports.AuthAdminApi = AuthAdminApi;
4168
+ exports.AuthAdminService = AuthAdminService;
4169
+ exports.BaseTokenService = BaseTokenService;
4170
+ exports.BusinessApi = BusinessApi;
4171
+ exports.BusinessService = BusinessService;
4172
+ exports.CampaignApi = CampaignApi;
4173
+ exports.CampaignService = CampaignService;
4174
+ exports.ChainTypes = ChainTypes;
4175
+ exports.DEFAULT_PERS_CONFIG = DEFAULT_PERS_CONFIG;
4176
+ exports.DonationApi = DonationApi;
4177
+ exports.DonationService = DonationService;
4178
+ exports.PaymentApi = PurchaseApi;
4179
+ exports.PaymentService = PaymentService;
4180
+ exports.PersApiClient = PersApiClient;
4181
+ exports.PersApiError = PersApiError;
4182
+ exports.PersSDK = PersSDK;
4183
+ exports.RedemptionApi = RedemptionApi;
4184
+ exports.RedemptionService = RedemptionService;
4185
+ exports.SimpleCache = SimpleCache;
4186
+ exports.TenantApi = TenantApi;
4187
+ exports.TenantService = TenantService;
4188
+ exports.TokenApi = TokenApi;
4189
+ exports.TokenSDK = TokenSDK;
4190
+ exports.TokenService = TokenService;
4191
+ exports.TransactionApi = TransactionApi;
4192
+ exports.TransactionService = TransactionService;
4193
+ exports.UserApi = UserApi;
4194
+ exports.UserService = UserService;
4195
+ exports.UserStatusApi = UserStatusApi;
4196
+ exports.UserStatusService = UserStatusService;
4197
+ exports.Web3ChainApi = Web3ChainApi;
4198
+ exports.Web3ChainService = Web3ChainService;
4199
+ exports.Web3ProviderService = Web3ProviderService;
4200
+ exports.buildApiRoot = buildApiRoot;
4201
+ exports.createAnalyticsSDK = createAnalyticsSDK;
4202
+ exports.createAuthAdminSDK = createAuthAdminSDK;
4203
+ exports.createAuthProvider = createAuthProvider;
4204
+ exports.createBusinessSDK = createBusinessSDK;
4205
+ exports.createCampaignSDK = createCampaignSDK;
4206
+ exports.createDonationSDK = createDonationSDK;
4207
+ exports.createPaymentSDK = createPaymentSDK;
4208
+ exports.createPersSDK = createPersSDK;
4209
+ exports.createRedemptionSDK = createRedemptionSDK;
4210
+ exports.createTenantSDK = createTenantSDK;
4211
+ exports.createTransactionSDK = createTransactionSDK;
4212
+ exports.createUserSDK = createUserSDK;
4213
+ exports.createUserStatusSDK = createUserStatusSDK;
4214
+ exports.createWeb3ChainSDK = createWeb3ChainSDK;
4215
+ exports.createWeb3SDK = createWeb3SDK;
4216
+ exports.mergeWithDefaults = mergeWithDefaults;
4217
+ //# sourceMappingURL=index.cjs.map