@explorins/pers-sdk 1.1.2 → 1.1.3

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