@oxyhq/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +50 -0
  2. package/dist/cjs/AuthManager.js +361 -0
  3. package/dist/cjs/CrossDomainAuth.js +258 -0
  4. package/dist/cjs/HttpService.js +618 -0
  5. package/dist/cjs/OxyServices.base.js +263 -0
  6. package/dist/cjs/OxyServices.errors.js +22 -0
  7. package/dist/cjs/OxyServices.js +63 -0
  8. package/dist/cjs/constants/version.js +16 -0
  9. package/dist/cjs/crypto/index.js +20 -0
  10. package/dist/cjs/crypto/keyManager.js +887 -0
  11. package/dist/cjs/crypto/polyfill.js +64 -0
  12. package/dist/cjs/crypto/recoveryPhrase.js +169 -0
  13. package/dist/cjs/crypto/signatureService.js +296 -0
  14. package/dist/cjs/i18n/index.js +73 -0
  15. package/dist/cjs/i18n/locales/ar-SA.json +120 -0
  16. package/dist/cjs/i18n/locales/ca-ES.json +120 -0
  17. package/dist/cjs/i18n/locales/de-DE.json +120 -0
  18. package/dist/cjs/i18n/locales/en-US.json +956 -0
  19. package/dist/cjs/i18n/locales/es-ES.json +944 -0
  20. package/dist/cjs/i18n/locales/fr-FR.json +120 -0
  21. package/dist/cjs/i18n/locales/it-IT.json +120 -0
  22. package/dist/cjs/i18n/locales/ja-JP.json +119 -0
  23. package/dist/cjs/i18n/locales/ko-KR.json +120 -0
  24. package/dist/cjs/i18n/locales/locales/ar-SA.json +120 -0
  25. package/dist/cjs/i18n/locales/locales/ca-ES.json +120 -0
  26. package/dist/cjs/i18n/locales/locales/de-DE.json +120 -0
  27. package/dist/cjs/i18n/locales/locales/en-US.json +956 -0
  28. package/dist/cjs/i18n/locales/locales/es-ES.json +944 -0
  29. package/dist/cjs/i18n/locales/locales/fr-FR.json +120 -0
  30. package/dist/cjs/i18n/locales/locales/it-IT.json +120 -0
  31. package/dist/cjs/i18n/locales/locales/ja-JP.json +119 -0
  32. package/dist/cjs/i18n/locales/locales/ko-KR.json +120 -0
  33. package/dist/cjs/i18n/locales/locales/pt-PT.json +120 -0
  34. package/dist/cjs/i18n/locales/locales/zh-CN.json +120 -0
  35. package/dist/cjs/i18n/locales/pt-PT.json +120 -0
  36. package/dist/cjs/i18n/locales/zh-CN.json +120 -0
  37. package/dist/cjs/index.js +153 -0
  38. package/dist/cjs/mixins/OxyServices.analytics.js +49 -0
  39. package/dist/cjs/mixins/OxyServices.assets.js +380 -0
  40. package/dist/cjs/mixins/OxyServices.auth.js +259 -0
  41. package/dist/cjs/mixins/OxyServices.developer.js +97 -0
  42. package/dist/cjs/mixins/OxyServices.devices.js +116 -0
  43. package/dist/cjs/mixins/OxyServices.features.js +309 -0
  44. package/dist/cjs/mixins/OxyServices.fedcm.js +435 -0
  45. package/dist/cjs/mixins/OxyServices.karma.js +108 -0
  46. package/dist/cjs/mixins/OxyServices.language.js +154 -0
  47. package/dist/cjs/mixins/OxyServices.location.js +43 -0
  48. package/dist/cjs/mixins/OxyServices.payment.js +158 -0
  49. package/dist/cjs/mixins/OxyServices.popup.js +371 -0
  50. package/dist/cjs/mixins/OxyServices.privacy.js +162 -0
  51. package/dist/cjs/mixins/OxyServices.redirect.js +345 -0
  52. package/dist/cjs/mixins/OxyServices.security.js +81 -0
  53. package/dist/cjs/mixins/OxyServices.user.js +355 -0
  54. package/dist/cjs/mixins/OxyServices.utility.js +156 -0
  55. package/dist/cjs/mixins/index.js +79 -0
  56. package/dist/cjs/mixins/mixinHelpers.js +53 -0
  57. package/dist/cjs/models/interfaces.js +20 -0
  58. package/dist/cjs/models/session.js +2 -0
  59. package/dist/cjs/shared/index.js +70 -0
  60. package/dist/cjs/shared/utils/colorUtils.js +153 -0
  61. package/dist/cjs/shared/utils/debugUtils.js +73 -0
  62. package/dist/cjs/shared/utils/errorUtils.js +183 -0
  63. package/dist/cjs/shared/utils/index.js +49 -0
  64. package/dist/cjs/shared/utils/networkUtils.js +183 -0
  65. package/dist/cjs/shared/utils/themeUtils.js +106 -0
  66. package/dist/cjs/utils/apiUtils.js +61 -0
  67. package/dist/cjs/utils/asyncUtils.js +194 -0
  68. package/dist/cjs/utils/cache.js +226 -0
  69. package/dist/cjs/utils/deviceManager.js +205 -0
  70. package/dist/cjs/utils/errorUtils.js +154 -0
  71. package/dist/cjs/utils/index.js +26 -0
  72. package/dist/cjs/utils/languageUtils.js +165 -0
  73. package/dist/cjs/utils/loggerUtils.js +126 -0
  74. package/dist/cjs/utils/platform.js +144 -0
  75. package/dist/cjs/utils/requestUtils.js +209 -0
  76. package/dist/cjs/utils/sessionUtils.js +181 -0
  77. package/dist/cjs/utils/validationUtils.js +173 -0
  78. package/dist/esm/AuthManager.js +356 -0
  79. package/dist/esm/CrossDomainAuth.js +253 -0
  80. package/dist/esm/HttpService.js +614 -0
  81. package/dist/esm/OxyServices.base.js +259 -0
  82. package/dist/esm/OxyServices.errors.js +17 -0
  83. package/dist/esm/OxyServices.js +59 -0
  84. package/dist/esm/constants/version.js +13 -0
  85. package/dist/esm/crypto/index.js +13 -0
  86. package/dist/esm/crypto/keyManager.js +850 -0
  87. package/dist/esm/crypto/polyfill.js +61 -0
  88. package/dist/esm/crypto/recoveryPhrase.js +132 -0
  89. package/dist/esm/crypto/signatureService.js +259 -0
  90. package/dist/esm/i18n/index.js +69 -0
  91. package/dist/esm/i18n/locales/ar-SA.json +120 -0
  92. package/dist/esm/i18n/locales/ca-ES.json +120 -0
  93. package/dist/esm/i18n/locales/de-DE.json +120 -0
  94. package/dist/esm/i18n/locales/en-US.json +956 -0
  95. package/dist/esm/i18n/locales/es-ES.json +944 -0
  96. package/dist/esm/i18n/locales/fr-FR.json +120 -0
  97. package/dist/esm/i18n/locales/it-IT.json +120 -0
  98. package/dist/esm/i18n/locales/ja-JP.json +119 -0
  99. package/dist/esm/i18n/locales/ko-KR.json +120 -0
  100. package/dist/esm/i18n/locales/locales/ar-SA.json +120 -0
  101. package/dist/esm/i18n/locales/locales/ca-ES.json +120 -0
  102. package/dist/esm/i18n/locales/locales/de-DE.json +120 -0
  103. package/dist/esm/i18n/locales/locales/en-US.json +956 -0
  104. package/dist/esm/i18n/locales/locales/es-ES.json +944 -0
  105. package/dist/esm/i18n/locales/locales/fr-FR.json +120 -0
  106. package/dist/esm/i18n/locales/locales/it-IT.json +120 -0
  107. package/dist/esm/i18n/locales/locales/ja-JP.json +119 -0
  108. package/dist/esm/i18n/locales/locales/ko-KR.json +120 -0
  109. package/dist/esm/i18n/locales/locales/pt-PT.json +120 -0
  110. package/dist/esm/i18n/locales/locales/zh-CN.json +120 -0
  111. package/dist/esm/i18n/locales/pt-PT.json +120 -0
  112. package/dist/esm/i18n/locales/zh-CN.json +120 -0
  113. package/dist/esm/index.js +55 -0
  114. package/dist/esm/mixins/OxyServices.analytics.js +46 -0
  115. package/dist/esm/mixins/OxyServices.assets.js +377 -0
  116. package/dist/esm/mixins/OxyServices.auth.js +256 -0
  117. package/dist/esm/mixins/OxyServices.developer.js +94 -0
  118. package/dist/esm/mixins/OxyServices.devices.js +113 -0
  119. package/dist/esm/mixins/OxyServices.features.js +306 -0
  120. package/dist/esm/mixins/OxyServices.fedcm.js +433 -0
  121. package/dist/esm/mixins/OxyServices.karma.js +105 -0
  122. package/dist/esm/mixins/OxyServices.language.js +118 -0
  123. package/dist/esm/mixins/OxyServices.location.js +40 -0
  124. package/dist/esm/mixins/OxyServices.payment.js +155 -0
  125. package/dist/esm/mixins/OxyServices.popup.js +369 -0
  126. package/dist/esm/mixins/OxyServices.privacy.js +159 -0
  127. package/dist/esm/mixins/OxyServices.redirect.js +343 -0
  128. package/dist/esm/mixins/OxyServices.security.js +78 -0
  129. package/dist/esm/mixins/OxyServices.user.js +352 -0
  130. package/dist/esm/mixins/OxyServices.utility.js +153 -0
  131. package/dist/esm/mixins/index.js +76 -0
  132. package/dist/esm/mixins/mixinHelpers.js +48 -0
  133. package/dist/esm/models/interfaces.js +17 -0
  134. package/dist/esm/models/session.js +1 -0
  135. package/dist/esm/shared/index.js +31 -0
  136. package/dist/esm/shared/utils/colorUtils.js +143 -0
  137. package/dist/esm/shared/utils/debugUtils.js +65 -0
  138. package/dist/esm/shared/utils/errorUtils.js +170 -0
  139. package/dist/esm/shared/utils/index.js +15 -0
  140. package/dist/esm/shared/utils/networkUtils.js +173 -0
  141. package/dist/esm/shared/utils/themeUtils.js +98 -0
  142. package/dist/esm/utils/apiUtils.js +55 -0
  143. package/dist/esm/utils/asyncUtils.js +179 -0
  144. package/dist/esm/utils/cache.js +218 -0
  145. package/dist/esm/utils/deviceManager.js +168 -0
  146. package/dist/esm/utils/errorUtils.js +146 -0
  147. package/dist/esm/utils/index.js +7 -0
  148. package/dist/esm/utils/languageUtils.js +158 -0
  149. package/dist/esm/utils/loggerUtils.js +115 -0
  150. package/dist/esm/utils/platform.js +102 -0
  151. package/dist/esm/utils/requestUtils.js +203 -0
  152. package/dist/esm/utils/sessionUtils.js +171 -0
  153. package/dist/esm/utils/validationUtils.js +153 -0
  154. package/dist/types/AuthManager.d.ts +143 -0
  155. package/dist/types/CrossDomainAuth.d.ts +160 -0
  156. package/dist/types/HttpService.d.ts +163 -0
  157. package/dist/types/OxyServices.base.d.ts +126 -0
  158. package/dist/types/OxyServices.d.ts +81 -0
  159. package/dist/types/OxyServices.errors.d.ts +11 -0
  160. package/dist/types/constants/version.d.ts +13 -0
  161. package/dist/types/crypto/index.d.ts +11 -0
  162. package/dist/types/crypto/keyManager.d.ts +189 -0
  163. package/dist/types/crypto/polyfill.d.ts +11 -0
  164. package/dist/types/crypto/recoveryPhrase.d.ts +58 -0
  165. package/dist/types/crypto/signatureService.d.ts +86 -0
  166. package/dist/types/i18n/index.d.ts +3 -0
  167. package/dist/types/index.d.ts +50 -0
  168. package/dist/types/mixins/OxyServices.analytics.d.ts +66 -0
  169. package/dist/types/mixins/OxyServices.assets.d.ts +135 -0
  170. package/dist/types/mixins/OxyServices.auth.d.ts +186 -0
  171. package/dist/types/mixins/OxyServices.developer.d.ts +99 -0
  172. package/dist/types/mixins/OxyServices.devices.d.ts +96 -0
  173. package/dist/types/mixins/OxyServices.features.d.ts +228 -0
  174. package/dist/types/mixins/OxyServices.fedcm.d.ts +200 -0
  175. package/dist/types/mixins/OxyServices.karma.d.ts +85 -0
  176. package/dist/types/mixins/OxyServices.language.d.ts +81 -0
  177. package/dist/types/mixins/OxyServices.location.d.ts +64 -0
  178. package/dist/types/mixins/OxyServices.payment.d.ts +111 -0
  179. package/dist/types/mixins/OxyServices.popup.d.ts +205 -0
  180. package/dist/types/mixins/OxyServices.privacy.d.ts +122 -0
  181. package/dist/types/mixins/OxyServices.redirect.d.ts +245 -0
  182. package/dist/types/mixins/OxyServices.security.d.ts +78 -0
  183. package/dist/types/mixins/OxyServices.user.d.ts +182 -0
  184. package/dist/types/mixins/OxyServices.utility.d.ts +93 -0
  185. package/dist/types/mixins/index.d.ts +30 -0
  186. package/dist/types/mixins/mixinHelpers.d.ts +31 -0
  187. package/dist/types/models/interfaces.d.ts +415 -0
  188. package/dist/types/models/session.d.ts +27 -0
  189. package/dist/types/shared/index.d.ts +28 -0
  190. package/dist/types/shared/utils/colorUtils.d.ts +104 -0
  191. package/dist/types/shared/utils/debugUtils.d.ts +48 -0
  192. package/dist/types/shared/utils/errorUtils.d.ts +97 -0
  193. package/dist/types/shared/utils/index.d.ts +13 -0
  194. package/dist/types/shared/utils/networkUtils.d.ts +139 -0
  195. package/dist/types/shared/utils/themeUtils.d.ts +90 -0
  196. package/dist/types/utils/apiUtils.d.ts +53 -0
  197. package/dist/types/utils/asyncUtils.d.ts +58 -0
  198. package/dist/types/utils/cache.d.ts +127 -0
  199. package/dist/types/utils/deviceManager.d.ts +65 -0
  200. package/dist/types/utils/errorUtils.d.ts +46 -0
  201. package/dist/types/utils/index.d.ts +6 -0
  202. package/dist/types/utils/languageUtils.d.ts +37 -0
  203. package/dist/types/utils/loggerUtils.d.ts +48 -0
  204. package/dist/types/utils/platform.d.ts +40 -0
  205. package/dist/types/utils/requestUtils.d.ts +123 -0
  206. package/dist/types/utils/sessionUtils.d.ts +54 -0
  207. package/dist/types/utils/validationUtils.d.ts +85 -0
  208. package/package.json +84 -0
  209. package/src/AuthManager.ts +436 -0
  210. package/src/CrossDomainAuth.ts +307 -0
  211. package/src/HttpService.ts +752 -0
  212. package/src/OxyServices.base.ts +334 -0
  213. package/src/OxyServices.errors.ts +26 -0
  214. package/src/OxyServices.ts +129 -0
  215. package/src/constants/version.ts +15 -0
  216. package/src/crypto/index.ts +25 -0
  217. package/src/crypto/keyManager.ts +962 -0
  218. package/src/crypto/polyfill.ts +70 -0
  219. package/src/crypto/recoveryPhrase.ts +166 -0
  220. package/src/crypto/signatureService.ts +323 -0
  221. package/src/i18n/index.ts +75 -0
  222. package/src/i18n/locales/ar-SA.json +120 -0
  223. package/src/i18n/locales/ca-ES.json +120 -0
  224. package/src/i18n/locales/de-DE.json +120 -0
  225. package/src/i18n/locales/en-US.json +956 -0
  226. package/src/i18n/locales/es-ES.json +944 -0
  227. package/src/i18n/locales/fr-FR.json +120 -0
  228. package/src/i18n/locales/it-IT.json +120 -0
  229. package/src/i18n/locales/ja-JP.json +119 -0
  230. package/src/i18n/locales/ko-KR.json +120 -0
  231. package/src/i18n/locales/pt-PT.json +120 -0
  232. package/src/i18n/locales/zh-CN.json +120 -0
  233. package/src/index.ts +153 -0
  234. package/src/mixins/OxyServices.analytics.ts +53 -0
  235. package/src/mixins/OxyServices.assets.ts +412 -0
  236. package/src/mixins/OxyServices.auth.ts +358 -0
  237. package/src/mixins/OxyServices.developer.ts +114 -0
  238. package/src/mixins/OxyServices.devices.ts +119 -0
  239. package/src/mixins/OxyServices.features.ts +428 -0
  240. package/src/mixins/OxyServices.fedcm.ts +494 -0
  241. package/src/mixins/OxyServices.karma.ts +111 -0
  242. package/src/mixins/OxyServices.language.ts +127 -0
  243. package/src/mixins/OxyServices.location.ts +46 -0
  244. package/src/mixins/OxyServices.payment.ts +163 -0
  245. package/src/mixins/OxyServices.popup.ts +443 -0
  246. package/src/mixins/OxyServices.privacy.ts +182 -0
  247. package/src/mixins/OxyServices.redirect.ts +397 -0
  248. package/src/mixins/OxyServices.security.ts +103 -0
  249. package/src/mixins/OxyServices.user.ts +392 -0
  250. package/src/mixins/OxyServices.utility.ts +191 -0
  251. package/src/mixins/index.ts +91 -0
  252. package/src/mixins/mixinHelpers.ts +69 -0
  253. package/src/models/interfaces.ts +511 -0
  254. package/src/models/session.ts +30 -0
  255. package/src/shared/index.ts +82 -0
  256. package/src/shared/utils/colorUtils.ts +155 -0
  257. package/src/shared/utils/debugUtils.ts +73 -0
  258. package/src/shared/utils/errorUtils.ts +181 -0
  259. package/src/shared/utils/index.ts +59 -0
  260. package/src/shared/utils/networkUtils.ts +248 -0
  261. package/src/shared/utils/themeUtils.ts +115 -0
  262. package/src/types/bip39.d.ts +32 -0
  263. package/src/types/buffer.d.ts +97 -0
  264. package/src/types/color.d.ts +20 -0
  265. package/src/types/elliptic.d.ts +62 -0
  266. package/src/utils/apiUtils.ts +88 -0
  267. package/src/utils/asyncUtils.ts +252 -0
  268. package/src/utils/cache.ts +264 -0
  269. package/src/utils/deviceManager.ts +198 -0
  270. package/src/utils/errorUtils.ts +216 -0
  271. package/src/utils/index.ts +21 -0
  272. package/src/utils/languageUtils.ts +174 -0
  273. package/src/utils/loggerUtils.ts +153 -0
  274. package/src/utils/platform.ts +117 -0
  275. package/src/utils/requestUtils.ts +237 -0
  276. package/src/utils/sessionUtils.ts +206 -0
  277. package/src/utils/validationUtils.ts +174 -0
@@ -0,0 +1,618 @@
1
+ "use strict";
2
+ /**
3
+ * Unified HTTP Service
4
+ *
5
+ * Consolidates HttpClient + RequestManager into a single efficient class.
6
+ * Uses native fetch instead of axios for smaller bundle size.
7
+ *
8
+ * Handles:
9
+ * - Authentication (token management, auto-refresh)
10
+ * - Caching (TTL-based)
11
+ * - Deduplication (concurrent requests)
12
+ * - Retry logic
13
+ * - Error handling
14
+ * - Request queuing
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.HttpService = void 0;
18
+ const cache_1 = require("./utils/cache");
19
+ const requestUtils_1 = require("./utils/requestUtils");
20
+ const asyncUtils_1 = require("./utils/asyncUtils");
21
+ const errorUtils_1 = require("./utils/errorUtils");
22
+ const jwt_decode_1 = require("jwt-decode");
23
+ const platform_1 = require("./utils/platform");
24
+ /**
25
+ * Check if we're running in a native app environment (React Native, not web)
26
+ * This is used to determine CSRF handling mode
27
+ */
28
+ const isNativeApp = (0, platform_1.isNative)();
29
+ /**
30
+ * Token store for authentication (singleton)
31
+ */
32
+ class TokenStore {
33
+ constructor() {
34
+ this.accessToken = null;
35
+ this.refreshToken = null;
36
+ this.csrfToken = null;
37
+ this.csrfTokenFetchPromise = null;
38
+ }
39
+ static getInstance() {
40
+ if (!TokenStore.instance) {
41
+ TokenStore.instance = new TokenStore();
42
+ }
43
+ return TokenStore.instance;
44
+ }
45
+ setTokens(accessToken, refreshToken = '') {
46
+ this.accessToken = accessToken;
47
+ this.refreshToken = refreshToken;
48
+ }
49
+ getAccessToken() {
50
+ return this.accessToken;
51
+ }
52
+ getRefreshToken() {
53
+ return this.refreshToken;
54
+ }
55
+ clearTokens() {
56
+ this.accessToken = null;
57
+ this.refreshToken = null;
58
+ }
59
+ hasAccessToken() {
60
+ return !!this.accessToken;
61
+ }
62
+ setCsrfToken(token) {
63
+ this.csrfToken = token;
64
+ }
65
+ getCsrfToken() {
66
+ return this.csrfToken;
67
+ }
68
+ setCsrfTokenFetchPromise(promise) {
69
+ this.csrfTokenFetchPromise = promise;
70
+ }
71
+ getCsrfTokenFetchPromise() {
72
+ return this.csrfTokenFetchPromise;
73
+ }
74
+ clearCsrfToken() {
75
+ this.csrfToken = null;
76
+ this.csrfTokenFetchPromise = null;
77
+ }
78
+ }
79
+ /**
80
+ * Unified HTTP Service
81
+ *
82
+ * Consolidates HttpClient + RequestManager into a single efficient class.
83
+ * Uses native fetch instead of axios for smaller bundle size.
84
+ */
85
+ class HttpService {
86
+ constructor(config) {
87
+ // Performance monitoring
88
+ this.requestMetrics = {
89
+ totalRequests: 0,
90
+ successfulRequests: 0,
91
+ failedRequests: 0,
92
+ cacheHits: 0,
93
+ cacheMisses: 0,
94
+ averageResponseTime: 0,
95
+ };
96
+ this.config = config;
97
+ this.baseURL = config.baseURL;
98
+ this.tokenStore = TokenStore.getInstance();
99
+ this.logger = new requestUtils_1.SimpleLogger(config.enableLogging || false, config.logLevel || 'error', 'HttpService');
100
+ // Initialize performance infrastructure
101
+ this.cache = new cache_1.TTLCache(config.cacheTTL || 5 * 60 * 1000);
102
+ (0, cache_1.registerCacheForCleanup)(this.cache);
103
+ this.deduplicator = new requestUtils_1.RequestDeduplicator();
104
+ this.requestQueue = new requestUtils_1.RequestQueue(config.maxConcurrentRequests || 10, config.requestQueueSize || 100);
105
+ }
106
+ /**
107
+ * Robust FormData detection that works in browser and Node.js environments
108
+ * Checks multiple conditions to handle different FormData implementations
109
+ */
110
+ isFormData(data) {
111
+ if (!data) {
112
+ return false;
113
+ }
114
+ // Primary check: instanceof FormData (works in browser and Node.js with proper polyfills)
115
+ if (data instanceof FormData) {
116
+ return true;
117
+ }
118
+ // Fallback: Check constructor name (handles Node.js polyfills like form-data)
119
+ if (typeof data === 'object' && data !== null) {
120
+ const constructorName = data.constructor?.name;
121
+ if (constructorName === 'FormData' || constructorName === 'FormDataImpl') {
122
+ return true;
123
+ }
124
+ // Additional check: Look for FormData-like methods
125
+ if (typeof data.append === 'function' &&
126
+ typeof data.get === 'function' &&
127
+ typeof data.has === 'function') {
128
+ return true;
129
+ }
130
+ }
131
+ return false;
132
+ }
133
+ /**
134
+ * Main request method - handles everything in one place
135
+ */
136
+ async request(config) {
137
+ const { method, url, data, params, timeout = this.config.requestTimeout || 5000, signal, cache = method === 'GET', cacheTTL, deduplicate = true, retry = this.config.enableRetry !== false, maxRetries = this.config.maxRetries || 3, } = config;
138
+ // Generate cache key (optimized for large objects)
139
+ const cacheKey = cache ? this.generateCacheKey(method, url, data || params) : null;
140
+ // Check cache first
141
+ if (cache && cacheKey) {
142
+ const cached = this.cache.get(cacheKey);
143
+ if (cached !== null) {
144
+ this.requestMetrics.cacheHits++;
145
+ this.logger.debug('Cache hit:', url);
146
+ return cached;
147
+ }
148
+ this.requestMetrics.cacheMisses++;
149
+ }
150
+ // Request function
151
+ const requestFn = async () => {
152
+ const startTime = Date.now();
153
+ try {
154
+ // Build URL with params
155
+ const fullUrl = this.buildURL(url, params);
156
+ // Get auth token (with auto-refresh)
157
+ const authHeader = await this.getAuthHeader();
158
+ // Get CSRF token for state-changing requests
159
+ const isStateChangingMethod = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method);
160
+ const csrfToken = isStateChangingMethod ? await this.fetchCsrfToken() : null;
161
+ // Determine if data is FormData using robust detection
162
+ const isFormData = this.isFormData(data);
163
+ // Make fetch request
164
+ const controller = new AbortController();
165
+ const timeoutId = timeout ? setTimeout(() => controller.abort(), timeout) : null;
166
+ if (signal) {
167
+ signal.addEventListener('abort', () => controller.abort());
168
+ }
169
+ // Build headers - start with defaults
170
+ const headers = {
171
+ 'Accept': 'application/json',
172
+ };
173
+ // Only set Content-Type for non-FormData requests (FormData sets it automatically with boundary)
174
+ if (!isFormData) {
175
+ headers['Content-Type'] = 'application/json';
176
+ }
177
+ // Add authorization header if available
178
+ if (authHeader) {
179
+ headers['Authorization'] = authHeader;
180
+ }
181
+ // Add CSRF token header for state-changing requests
182
+ if (csrfToken) {
183
+ headers['X-CSRF-Token'] = csrfToken;
184
+ }
185
+ // Add native app header for React Native (required for CSRF validation)
186
+ // Native apps can't persist cookies like browsers, so the server uses
187
+ // header-only CSRF validation when this header is present
188
+ if (isNativeApp && isStateChangingMethod) {
189
+ headers['X-Native-App'] = 'true';
190
+ }
191
+ // Debug logging for CSRF issues
192
+ if (isStateChangingMethod && __DEV__) {
193
+ console.log('[HttpService] CSRF Debug:', {
194
+ url,
195
+ method,
196
+ isNativeApp,
197
+ platformOS: (0, platform_1.getPlatformOS)(),
198
+ hasCsrfToken: !!csrfToken,
199
+ csrfTokenLength: csrfToken?.length,
200
+ hasNativeAppHeader: headers['X-Native-App'] === 'true',
201
+ });
202
+ }
203
+ // Merge custom headers if provided
204
+ if (config.headers) {
205
+ Object.entries(config.headers).forEach(([key, value]) => {
206
+ // For FormData, explicitly remove Content-Type if user tries to set it
207
+ // The browser/fetch API will set it automatically with the boundary
208
+ if (isFormData && key.toLowerCase() === 'content-type') {
209
+ this.logger.debug('Ignoring Content-Type header for FormData - will be set automatically');
210
+ return;
211
+ }
212
+ headers[key] = value;
213
+ });
214
+ }
215
+ const bodyValue = method !== 'GET' && data
216
+ ? (isFormData ? data : JSON.stringify(data))
217
+ : undefined;
218
+ const response = await fetch(fullUrl, {
219
+ method,
220
+ headers,
221
+ body: bodyValue,
222
+ signal: controller.signal,
223
+ credentials: 'include', // Include cookies for cross-origin requests (CSRF, session)
224
+ });
225
+ if (timeoutId)
226
+ clearTimeout(timeoutId);
227
+ // Handle response
228
+ if (!response.ok) {
229
+ if (response.status === 401) {
230
+ this.tokenStore.clearTokens();
231
+ }
232
+ // Try to parse error response (handle empty/malformed JSON)
233
+ let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
234
+ const contentType = response.headers.get('content-type');
235
+ if (contentType && contentType.includes('application/json')) {
236
+ try {
237
+ const errorData = await response.json();
238
+ // Check both 'message' and 'error' fields for backwards compatibility
239
+ if (errorData?.message) {
240
+ errorMessage = errorData.message;
241
+ }
242
+ else if (errorData?.error) {
243
+ errorMessage = errorData.error;
244
+ }
245
+ }
246
+ catch (parseError) {
247
+ // Malformed JSON or empty response - use status text
248
+ this.logger.warn('Failed to parse error response JSON:', parseError);
249
+ }
250
+ }
251
+ const error = new Error(errorMessage);
252
+ error.status = response.status;
253
+ error.response = { status: response.status, statusText: response.statusText };
254
+ throw error;
255
+ }
256
+ // Handle different response types (optimized - read response once)
257
+ const contentType = response.headers.get('content-type');
258
+ let responseData;
259
+ if (contentType && contentType.includes('application/json')) {
260
+ // Use response.json() directly for better performance
261
+ try {
262
+ responseData = await response.json();
263
+ // Handle null/undefined responses
264
+ if (responseData === null || responseData === undefined) {
265
+ responseData = null;
266
+ }
267
+ else {
268
+ // Unwrap standardized API response format for JSON
269
+ responseData = this.unwrapResponse(responseData);
270
+ }
271
+ }
272
+ catch (parseError) {
273
+ // Handle malformed JSON or empty responses gracefully
274
+ // Note: Once response.json() is called, the body is consumed and cannot be read again
275
+ // So we check the error type to determine if it's empty or malformed
276
+ if (parseError instanceof SyntaxError) {
277
+ this.logger.warn('Failed to parse JSON response (malformed or empty):', parseError);
278
+ // SyntaxError typically means empty or malformed JSON
279
+ // For empty responses, return null; for malformed JSON, throw descriptive error
280
+ responseData = null; // Treat as empty response for safety
281
+ }
282
+ else {
283
+ this.logger.warn('Failed to read response:', parseError);
284
+ throw new Error('Failed to read response from server');
285
+ }
286
+ }
287
+ }
288
+ else if (contentType && (contentType.includes('application/octet-stream') || contentType.includes('image/') || contentType.includes('video/') || contentType.includes('audio/'))) {
289
+ // For binary responses (blobs), return the blob directly without unwrapping
290
+ responseData = await response.blob();
291
+ }
292
+ else {
293
+ // For other responses, return as text
294
+ const text = await response.text();
295
+ responseData = text || null;
296
+ }
297
+ const duration = Date.now() - startTime;
298
+ this.updateMetrics(true, duration);
299
+ this.config.onRequestEnd?.(url, method, duration, true);
300
+ return responseData;
301
+ }
302
+ catch (error) {
303
+ const duration = Date.now() - startTime;
304
+ this.updateMetrics(false, duration);
305
+ this.config.onRequestEnd?.(url, method, duration, false);
306
+ this.config.onRequestError?.(url, method, error instanceof Error ? error : new Error(String(error)));
307
+ // Handle AbortError specifically for better error messages
308
+ if (error instanceof Error && error.name === 'AbortError') {
309
+ throw (0, errorUtils_1.handleHttpError)(error);
310
+ }
311
+ throw (0, errorUtils_1.handleHttpError)(error);
312
+ }
313
+ };
314
+ // Wrap with retry if enabled
315
+ const requestWithRetry = retry
316
+ ? () => (0, asyncUtils_1.retryAsync)(requestFn, maxRetries, this.config.retryDelay || 1000)
317
+ : requestFn;
318
+ // Wrap with deduplication if enabled (use optimized key generation)
319
+ const dedupeKey = deduplicate ? this.generateCacheKey(method, url, data || params) : null;
320
+ const finalRequest = dedupeKey
321
+ ? () => this.deduplicator.deduplicate(dedupeKey, requestWithRetry)
322
+ : requestWithRetry;
323
+ // Execute request (with queue if needed)
324
+ const result = await this.requestQueue.enqueue(finalRequest);
325
+ // Cache the result if caching is enabled
326
+ if (cache && cacheKey && result) {
327
+ this.cache.set(cacheKey, result, cacheTTL);
328
+ }
329
+ return result;
330
+ }
331
+ /**
332
+ * Generate cache key efficiently
333
+ * Uses simple hash for large objects to avoid expensive JSON.stringify
334
+ */
335
+ generateCacheKey(method, url, data) {
336
+ if (!data || (typeof data === 'object' && Object.keys(data).length === 0)) {
337
+ return `${method}:${url}`;
338
+ }
339
+ // For small objects, use JSON.stringify
340
+ const dataStr = JSON.stringify(data);
341
+ if (dataStr.length < 1000) {
342
+ return `${method}:${url}:${dataStr}`;
343
+ }
344
+ // For large objects, use a simple hash based on keys and values length
345
+ // This avoids expensive serialization while still being unique enough
346
+ const hash = typeof data === 'object' && data !== null
347
+ ? Object.keys(data).sort().join(',') + ':' + dataStr.length
348
+ : String(data).substring(0, 100);
349
+ return `${method}:${url}:${hash}`;
350
+ }
351
+ /**
352
+ * Build full URL with query params
353
+ */
354
+ buildURL(url, params) {
355
+ const base = url.startsWith('http') ? url : `${this.baseURL}${url}`;
356
+ if (!params || Object.keys(params).length === 0) {
357
+ return base;
358
+ }
359
+ const searchParams = new URLSearchParams();
360
+ Object.entries(params).forEach(([key, value]) => {
361
+ if (value !== undefined && value !== null) {
362
+ searchParams.append(key, String(value));
363
+ }
364
+ });
365
+ const queryString = searchParams.toString();
366
+ return queryString ? `${base}${base.includes('?') ? '&' : '?'}${queryString}` : base;
367
+ }
368
+ /**
369
+ * Fetch CSRF token from server (with deduplication)
370
+ * Required for state-changing requests (POST, PUT, PATCH, DELETE)
371
+ */
372
+ async fetchCsrfToken() {
373
+ // Return cached token if available
374
+ const cachedToken = this.tokenStore.getCsrfToken();
375
+ if (cachedToken) {
376
+ if (__DEV__)
377
+ console.log('[HttpService] Using cached CSRF token');
378
+ return cachedToken;
379
+ }
380
+ // Deduplicate concurrent CSRF token fetches
381
+ const existingPromise = this.tokenStore.getCsrfTokenFetchPromise();
382
+ if (existingPromise) {
383
+ if (__DEV__)
384
+ console.log('[HttpService] Waiting for existing CSRF fetch');
385
+ return existingPromise;
386
+ }
387
+ const fetchPromise = (async () => {
388
+ try {
389
+ if (__DEV__)
390
+ console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/api/csrf-token`);
391
+ // Use AbortController for timeout (more compatible than AbortSignal.timeout)
392
+ const controller = new AbortController();
393
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
394
+ const response = await fetch(`${this.baseURL}/api/csrf-token`, {
395
+ method: 'GET',
396
+ headers: { 'Accept': 'application/json' },
397
+ credentials: 'include', // Required to receive and send cookies
398
+ signal: controller.signal,
399
+ });
400
+ clearTimeout(timeoutId);
401
+ if (__DEV__)
402
+ console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
403
+ if (response.ok) {
404
+ const data = await response.json();
405
+ if (__DEV__)
406
+ console.log('[HttpService] CSRF response data:', data);
407
+ const token = data.csrfToken || null;
408
+ this.tokenStore.setCsrfToken(token);
409
+ this.logger.debug('CSRF token fetched');
410
+ return token;
411
+ }
412
+ // Also check response header for CSRF token
413
+ const headerToken = response.headers.get('X-CSRF-Token');
414
+ if (headerToken) {
415
+ this.tokenStore.setCsrfToken(headerToken);
416
+ this.logger.debug('CSRF token from header');
417
+ return headerToken;
418
+ }
419
+ if (__DEV__)
420
+ console.log('[HttpService] CSRF fetch failed with status:', response.status);
421
+ this.logger.warn('Failed to fetch CSRF token:', response.status);
422
+ return null;
423
+ }
424
+ catch (error) {
425
+ if (__DEV__)
426
+ console.log('[HttpService] CSRF fetch error:', error);
427
+ this.logger.warn('CSRF token fetch error:', error);
428
+ return null;
429
+ }
430
+ finally {
431
+ this.tokenStore.setCsrfTokenFetchPromise(null);
432
+ }
433
+ })();
434
+ this.tokenStore.setCsrfTokenFetchPromise(fetchPromise);
435
+ return fetchPromise;
436
+ }
437
+ /**
438
+ * Get auth header with automatic token refresh
439
+ */
440
+ async getAuthHeader() {
441
+ const accessToken = this.tokenStore.getAccessToken();
442
+ if (!accessToken) {
443
+ return null;
444
+ }
445
+ try {
446
+ const decoded = (0, jwt_decode_1.jwtDecode)(accessToken);
447
+ const currentTime = Math.floor(Date.now() / 1000);
448
+ // If token expires in less than 60 seconds, refresh it
449
+ if (decoded.exp && decoded.exp - currentTime < 60 && decoded.sessionId) {
450
+ try {
451
+ const refreshUrl = `${this.baseURL}/api/session/token/${decoded.sessionId}`;
452
+ // Use AbortSignal.timeout for consistent timeout handling
453
+ const response = await fetch(refreshUrl, {
454
+ method: 'GET',
455
+ headers: { 'Accept': 'application/json' },
456
+ signal: AbortSignal.timeout(5000),
457
+ credentials: 'include', // Include cookies for cross-origin requests
458
+ });
459
+ if (response.ok) {
460
+ const { accessToken: newToken } = await response.json();
461
+ this.tokenStore.setTokens(newToken);
462
+ this.logger.debug('Token refreshed');
463
+ return `Bearer ${newToken}`;
464
+ }
465
+ }
466
+ catch (refreshError) {
467
+ this.logger.warn('Token refresh failed, using current token');
468
+ }
469
+ }
470
+ return `Bearer ${accessToken}`;
471
+ }
472
+ catch (error) {
473
+ this.logger.error('Error processing token:', error);
474
+ return `Bearer ${accessToken}`;
475
+ }
476
+ }
477
+ /**
478
+ * Unwrap standardized API response format
479
+ */
480
+ unwrapResponse(responseData) {
481
+ // Handle paginated responses: { data: [...], pagination: {...} }
482
+ if (responseData && typeof responseData === 'object' && 'data' in responseData && 'pagination' in responseData) {
483
+ return responseData;
484
+ }
485
+ // Handle regular success responses: { data: ... }
486
+ if (responseData && typeof responseData === 'object' && 'data' in responseData && !Array.isArray(responseData)) {
487
+ return responseData.data;
488
+ }
489
+ // Return as-is for responses that don't use sendSuccess wrapper
490
+ return responseData;
491
+ }
492
+ /**
493
+ * Update request metrics
494
+ */
495
+ updateMetrics(success, duration) {
496
+ this.requestMetrics.totalRequests++;
497
+ if (success) {
498
+ this.requestMetrics.successfulRequests++;
499
+ }
500
+ else {
501
+ this.requestMetrics.failedRequests++;
502
+ }
503
+ const alpha = 0.1;
504
+ this.requestMetrics.averageResponseTime =
505
+ this.requestMetrics.averageResponseTime * (1 - alpha) + duration * alpha;
506
+ }
507
+ // Convenience methods (for backward compatibility)
508
+ /**
509
+ * GET request convenience method
510
+ */
511
+ async get(url, config) {
512
+ const result = await this.request({ method: 'GET', url, ...config });
513
+ return { data: result };
514
+ }
515
+ /**
516
+ * POST request convenience method
517
+ * Supports FormData uploads - Content-Type will be set automatically for FormData
518
+ * @param url - Request URL
519
+ * @param data - Request body (can be FormData for file uploads)
520
+ * @param config - Request configuration including optional headers
521
+ * @example
522
+ * ```typescript
523
+ * const formData = new FormData();
524
+ * formData.append('file', file);
525
+ * await api.post('/upload', formData, { headers: { 'X-Custom-Header': 'value' } });
526
+ * ```
527
+ */
528
+ async post(url, data, config) {
529
+ const result = await this.request({ method: 'POST', url, data, ...config });
530
+ return { data: result };
531
+ }
532
+ /**
533
+ * PUT request convenience method
534
+ * Supports FormData uploads - Content-Type will be set automatically for FormData
535
+ * @param url - Request URL
536
+ * @param data - Request body (can be FormData for file uploads)
537
+ * @param config - Request configuration including optional headers
538
+ * @example
539
+ * ```typescript
540
+ * const formData = new FormData();
541
+ * formData.append('file', file);
542
+ * await api.put('/upload', formData, { headers: { 'X-Custom-Header': 'value' } });
543
+ * ```
544
+ */
545
+ async put(url, data, config) {
546
+ const result = await this.request({ method: 'PUT', url, data, ...config });
547
+ return { data: result };
548
+ }
549
+ /**
550
+ * PATCH request convenience method
551
+ * Supports FormData uploads - Content-Type will be set automatically for FormData
552
+ * @param url - Request URL
553
+ * @param data - Request body (can be FormData for file uploads)
554
+ * @param config - Request configuration including optional headers
555
+ * @example
556
+ * ```typescript
557
+ * const formData = new FormData();
558
+ * formData.append('file', file);
559
+ * await api.patch('/upload', formData, { headers: { 'X-Custom-Header': 'value' } });
560
+ * ```
561
+ */
562
+ async patch(url, data, config) {
563
+ const result = await this.request({ method: 'PATCH', url, data, ...config });
564
+ return { data: result };
565
+ }
566
+ async delete(url, config) {
567
+ const result = await this.request({ method: 'DELETE', url, ...config });
568
+ return { data: result };
569
+ }
570
+ // Token management
571
+ setTokens(accessToken, refreshToken = '') {
572
+ this.tokenStore.setTokens(accessToken, refreshToken);
573
+ }
574
+ clearTokens() {
575
+ this.tokenStore.clearTokens();
576
+ this.tokenStore.clearCsrfToken();
577
+ }
578
+ getAccessToken() {
579
+ return this.tokenStore.getAccessToken();
580
+ }
581
+ hasAccessToken() {
582
+ return this.tokenStore.hasAccessToken();
583
+ }
584
+ getBaseURL() {
585
+ return this.baseURL;
586
+ }
587
+ // Cache management
588
+ clearCache() {
589
+ this.cache.clear();
590
+ }
591
+ clearCacheEntry(key) {
592
+ this.cache.delete(key);
593
+ }
594
+ getCacheStats() {
595
+ const cacheStats = this.cache.getStats();
596
+ const total = this.requestMetrics.cacheHits + this.requestMetrics.cacheMisses;
597
+ return {
598
+ size: cacheStats.size,
599
+ hits: this.requestMetrics.cacheHits,
600
+ misses: this.requestMetrics.cacheMisses,
601
+ hitRate: total > 0 ? this.requestMetrics.cacheHits / total : 0,
602
+ };
603
+ }
604
+ getMetrics() {
605
+ return { ...this.requestMetrics };
606
+ }
607
+ // Test-only utility
608
+ static __resetTokensForTests() {
609
+ try {
610
+ TokenStore.getInstance().clearTokens();
611
+ }
612
+ catch (error) {
613
+ // Silently fail in test cleanup - this is expected behavior
614
+ // TokenStore might not be initialized in some test scenarios
615
+ }
616
+ }
617
+ }
618
+ exports.HttpService = HttpService;