@oxyhq/services 5.13.15 → 5.13.17

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 (138) hide show
  1. package/README.md +10 -0
  2. package/lib/commonjs/core/OxyServices.base.js +271 -0
  3. package/lib/commonjs/core/OxyServices.base.js.map +1 -0
  4. package/lib/commonjs/core/OxyServices.errors.js +26 -0
  5. package/lib/commonjs/core/OxyServices.errors.js.map +1 -0
  6. package/lib/commonjs/core/OxyServices.js +58 -2168
  7. package/lib/commonjs/core/OxyServices.js.map +1 -1
  8. package/lib/commonjs/core/mixins/OxyServices.analytics.js +60 -0
  9. package/lib/commonjs/core/mixins/OxyServices.analytics.js.map +1 -0
  10. package/lib/commonjs/core/mixins/OxyServices.assets.js +424 -0
  11. package/lib/commonjs/core/mixins/OxyServices.assets.js.map +1 -0
  12. package/lib/commonjs/core/mixins/OxyServices.auth.js +303 -0
  13. package/lib/commonjs/core/mixins/OxyServices.auth.js.map +1 -0
  14. package/lib/commonjs/core/mixins/OxyServices.developer.js +115 -0
  15. package/lib/commonjs/core/mixins/OxyServices.developer.js.map +1 -0
  16. package/lib/commonjs/core/mixins/OxyServices.devices.js +119 -0
  17. package/lib/commonjs/core/mixins/OxyServices.devices.js.map +1 -0
  18. package/lib/commonjs/core/mixins/OxyServices.karma.js +117 -0
  19. package/lib/commonjs/core/mixins/OxyServices.karma.js.map +1 -0
  20. package/lib/commonjs/core/mixins/OxyServices.language.js +124 -0
  21. package/lib/commonjs/core/mixins/OxyServices.language.js.map +1 -0
  22. package/lib/commonjs/core/mixins/OxyServices.location.js +55 -0
  23. package/lib/commonjs/core/mixins/OxyServices.location.js.map +1 -0
  24. package/lib/commonjs/core/mixins/OxyServices.payment.js +66 -0
  25. package/lib/commonjs/core/mixins/OxyServices.payment.js.map +1 -0
  26. package/lib/commonjs/core/mixins/OxyServices.privacy.js +174 -0
  27. package/lib/commonjs/core/mixins/OxyServices.privacy.js.map +1 -0
  28. package/lib/commonjs/core/mixins/OxyServices.totp.js +53 -0
  29. package/lib/commonjs/core/mixins/OxyServices.totp.js.map +1 -0
  30. package/lib/commonjs/core/mixins/OxyServices.user.js +388 -0
  31. package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -0
  32. package/lib/commonjs/core/mixins/OxyServices.utility.js +161 -0
  33. package/lib/commonjs/core/mixins/OxyServices.utility.js.map +1 -0
  34. package/lib/commonjs/core/mixins/index.js +39 -0
  35. package/lib/commonjs/core/mixins/index.js.map +1 -0
  36. package/lib/commonjs/core/mixins/mixinHelpers.js +62 -0
  37. package/lib/commonjs/core/mixins/mixinHelpers.js.map +1 -0
  38. package/lib/commonjs/ui/context/OxyContext.js +27 -2
  39. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  40. package/lib/module/core/OxyServices.base.js +265 -0
  41. package/lib/module/core/OxyServices.base.js.map +1 -0
  42. package/lib/module/core/OxyServices.errors.js +20 -0
  43. package/lib/module/core/OxyServices.errors.js.map +1 -0
  44. package/lib/module/core/OxyServices.js +43 -2164
  45. package/lib/module/core/OxyServices.js.map +1 -1
  46. package/lib/module/core/mixins/OxyServices.analytics.js +56 -0
  47. package/lib/module/core/mixins/OxyServices.analytics.js.map +1 -0
  48. package/lib/module/core/mixins/OxyServices.assets.js +420 -0
  49. package/lib/module/core/mixins/OxyServices.assets.js.map +1 -0
  50. package/lib/module/core/mixins/OxyServices.auth.js +299 -0
  51. package/lib/module/core/mixins/OxyServices.auth.js.map +1 -0
  52. package/lib/module/core/mixins/OxyServices.developer.js +111 -0
  53. package/lib/module/core/mixins/OxyServices.developer.js.map +1 -0
  54. package/lib/module/core/mixins/OxyServices.devices.js +115 -0
  55. package/lib/module/core/mixins/OxyServices.devices.js.map +1 -0
  56. package/lib/module/core/mixins/OxyServices.karma.js +113 -0
  57. package/lib/module/core/mixins/OxyServices.karma.js.map +1 -0
  58. package/lib/module/core/mixins/OxyServices.language.js +120 -0
  59. package/lib/module/core/mixins/OxyServices.language.js.map +1 -0
  60. package/lib/module/core/mixins/OxyServices.location.js +51 -0
  61. package/lib/module/core/mixins/OxyServices.location.js.map +1 -0
  62. package/lib/module/core/mixins/OxyServices.payment.js +62 -0
  63. package/lib/module/core/mixins/OxyServices.payment.js.map +1 -0
  64. package/lib/module/core/mixins/OxyServices.privacy.js +170 -0
  65. package/lib/module/core/mixins/OxyServices.privacy.js.map +1 -0
  66. package/lib/module/core/mixins/OxyServices.totp.js +49 -0
  67. package/lib/module/core/mixins/OxyServices.totp.js.map +1 -0
  68. package/lib/module/core/mixins/OxyServices.user.js +384 -0
  69. package/lib/module/core/mixins/OxyServices.user.js.map +1 -0
  70. package/lib/module/core/mixins/OxyServices.utility.js +156 -0
  71. package/lib/module/core/mixins/OxyServices.utility.js.map +1 -0
  72. package/lib/module/core/mixins/index.js +36 -0
  73. package/lib/module/core/mixins/index.js.map +1 -0
  74. package/lib/module/core/mixins/mixinHelpers.js +56 -0
  75. package/lib/module/core/mixins/mixinHelpers.js.map +1 -0
  76. package/lib/module/ui/context/OxyContext.js +27 -2
  77. package/lib/module/ui/context/OxyContext.js.map +1 -1
  78. package/lib/typescript/core/OxyServices.base.d.ts +123 -0
  79. package/lib/typescript/core/OxyServices.base.d.ts.map +1 -0
  80. package/lib/typescript/core/OxyServices.d.ts +970 -746
  81. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  82. package/lib/typescript/core/OxyServices.errors.d.ts +12 -0
  83. package/lib/typescript/core/OxyServices.errors.d.ts.map +1 -0
  84. package/lib/typescript/core/mixins/OxyServices.analytics.d.ts +70 -0
  85. package/lib/typescript/core/mixins/OxyServices.analytics.d.ts.map +1 -0
  86. package/lib/typescript/core/mixins/OxyServices.assets.d.ts +166 -0
  87. package/lib/typescript/core/mixins/OxyServices.assets.d.ts.map +1 -0
  88. package/lib/typescript/core/mixins/OxyServices.auth.d.ts +168 -0
  89. package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -0
  90. package/lib/typescript/core/mixins/OxyServices.developer.d.ts +103 -0
  91. package/lib/typescript/core/mixins/OxyServices.developer.d.ts.map +1 -0
  92. package/lib/typescript/core/mixins/OxyServices.devices.d.ts +93 -0
  93. package/lib/typescript/core/mixins/OxyServices.devices.d.ts.map +1 -0
  94. package/lib/typescript/core/mixins/OxyServices.karma.d.ts +89 -0
  95. package/lib/typescript/core/mixins/OxyServices.karma.d.ts.map +1 -0
  96. package/lib/typescript/core/mixins/OxyServices.language.d.ts +85 -0
  97. package/lib/typescript/core/mixins/OxyServices.language.d.ts.map +1 -0
  98. package/lib/typescript/core/mixins/OxyServices.location.d.ts +68 -0
  99. package/lib/typescript/core/mixins/OxyServices.location.d.ts.map +1 -0
  100. package/lib/typescript/core/mixins/OxyServices.payment.d.ts +74 -0
  101. package/lib/typescript/core/mixins/OxyServices.payment.d.ts.map +1 -0
  102. package/lib/typescript/core/mixins/OxyServices.privacy.d.ts +126 -0
  103. package/lib/typescript/core/mixins/OxyServices.privacy.d.ts.map +1 -0
  104. package/lib/typescript/core/mixins/OxyServices.totp.d.ts +69 -0
  105. package/lib/typescript/core/mixins/OxyServices.totp.d.ts.map +1 -0
  106. package/lib/typescript/core/mixins/OxyServices.user.d.ts +189 -0
  107. package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -0
  108. package/lib/typescript/core/mixins/OxyServices.utility.d.ts +97 -0
  109. package/lib/typescript/core/mixins/OxyServices.utility.d.ts.map +1 -0
  110. package/lib/typescript/core/mixins/index.d.ts +899 -0
  111. package/lib/typescript/core/mixins/index.d.ts.map +1 -0
  112. package/lib/typescript/core/mixins/mixinHelpers.d.ts +32 -0
  113. package/lib/typescript/core/mixins/mixinHelpers.d.ts.map +1 -0
  114. package/lib/typescript/models/interfaces.d.ts +10 -0
  115. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  116. package/lib/typescript/ui/context/OxyContext.d.ts +2 -0
  117. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  118. package/package.json +1 -1
  119. package/src/core/OxyServices.base.ts +311 -0
  120. package/src/core/OxyServices.errors.ts +26 -0
  121. package/src/core/OxyServices.ts +43 -2199
  122. package/src/core/mixins/OxyServices.analytics.ts +53 -0
  123. package/src/core/mixins/OxyServices.assets.ts +410 -0
  124. package/src/core/mixins/OxyServices.auth.ts +275 -0
  125. package/src/core/mixins/OxyServices.developer.ts +114 -0
  126. package/src/core/mixins/OxyServices.devices.ts +103 -0
  127. package/src/core/mixins/OxyServices.karma.ts +111 -0
  128. package/src/core/mixins/OxyServices.language.ts +127 -0
  129. package/src/core/mixins/OxyServices.location.ts +46 -0
  130. package/src/core/mixins/OxyServices.payment.ts +59 -0
  131. package/src/core/mixins/OxyServices.privacy.ts +182 -0
  132. package/src/core/mixins/OxyServices.totp.ts +36 -0
  133. package/src/core/mixins/OxyServices.user.ts +384 -0
  134. package/src/core/mixins/OxyServices.utility.ts +187 -0
  135. package/src/core/mixins/index.ts +58 -0
  136. package/src/core/mixins/mixinHelpers.ts +69 -0
  137. package/src/models/interfaces.ts +12 -0
  138. package/src/ui/context/OxyContext.tsx +36 -0
@@ -3,14 +3,23 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.oxyClient = exports.OxyServices = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OXY_CLOUD_URL = exports.OXY_API_URL = void 0;
7
- var _jwtDecode = require("jwt-decode");
8
- var _languageUtils = require("../utils/languageUtils");
9
- var _errorUtils = require("../utils/errorUtils");
10
- var _apiUtils = require("../utils/apiUtils");
11
- var _HttpClient = require("./HttpClient");
12
- var _RequestManager = require("./RequestManager");
13
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
6
+ exports.OXY_CLOUD_URL = exports.OXY_API_URL = void 0;
7
+ Object.defineProperty(exports, "OxyAuthenticationError", {
8
+ enumerable: true,
9
+ get: function () {
10
+ return _OxyServices.OxyAuthenticationError;
11
+ }
12
+ });
13
+ Object.defineProperty(exports, "OxyAuthenticationTimeoutError", {
14
+ enumerable: true,
15
+ get: function () {
16
+ return _OxyServices.OxyAuthenticationTimeoutError;
17
+ }
18
+ });
19
+ exports.oxyClient = exports.OxyServices = void 0;
20
+ var _OxyServices = require("./OxyServices.errors");
21
+ var _mixins = require("./mixins");
22
+ /**
14
23
  * OxyServices - Unified client for Oxy API and Oxy Cloud
15
24
  *
16
25
  * # Usage Examples
@@ -32,7 +41,7 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
32
41
  * // Upload a file (browser File API)
33
42
  * const fileInput = document.querySelector('input[type=file]');
34
43
  * const file = fileInput.files[0];
35
- * await oxy.uploadFile(file);
44
+ * await oxy.uploadRawFile(file);
36
45
  *
37
46
  * // Get a file stream URL for <img src>
38
47
  * const url = oxy.getFileStreamUrl('fileId');
@@ -67,2179 +76,60 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
67
76
  * - `cloudURL`: Oxy Cloud/CDN endpoint (e.g., https://cloud.oxy.so)
68
77
  *
69
78
  * See method JSDoc for more details and options.
70
- */ /**
71
- * OxyConfig - Configuration for OxyServices
72
- * @property baseURL - The Oxy API base URL (e.g., https://api.oxy.so)
73
- * @property cloudURL - The Oxy Cloud (file storage/CDN) URL (e.g., https://cloud.oxy.so)
74
79
  */
75
- /**
76
- * Custom error types for better error handling
77
- */
78
- class OxyAuthenticationError extends Error {
79
- constructor(message, code = 'AUTH_ERROR', status = 401) {
80
- super(message);
81
- this.name = 'OxyAuthenticationError';
82
- this.code = code;
83
- this.status = status;
84
- }
85
- }
86
- exports.OxyAuthenticationError = OxyAuthenticationError;
87
- class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
88
- constructor(operationName, timeoutMs) {
89
- super(`Authentication timeout (${timeoutMs}ms): ${operationName} requires user authentication. Please ensure the user is logged in before calling this method.`, 'AUTH_TIMEOUT', 408);
90
- this.name = 'OxyAuthenticationTimeoutError';
91
- }
92
- }
80
+
81
+ // Import mixin composition helper
93
82
 
94
83
  /**
95
84
  * OxyServices - Unified client library for interacting with the Oxy API
96
85
  *
97
86
  * This class provides all API functionality in one simple, easy-to-use interface.
98
- * Architecture:
99
- * - HttpClient: Handles HTTP communication and authentication
100
- * - RequestManager: Handles caching, deduplication, queuing, and retry
101
- * - OxyServices: Provides high-level API methods
87
+ *
88
+ * ## Architecture
89
+ * - **HttpClient**: Handles HTTP communication and authentication
90
+ * - **RequestManager**: Handles caching, deduplication, queuing, and retry
91
+ * - **OxyServices**: Provides high-level API methods
92
+ *
93
+ * ## Mixin Composition
94
+ * The class is composed using TypeScript mixins for better code organization:
95
+ * - **Base**: Core infrastructure (HTTP client, request management, error handling)
96
+ * - **Auth**: Authentication and session management
97
+ * - **User**: User profiles, follow, notifications
98
+ * - **TOTP**: Two-factor authentication enrollment
99
+ * - **Privacy**: Blocked and restricted users
100
+ * - **Language**: Language detection and metadata
101
+ * - **Payment**: Payment processing
102
+ * - **Karma**: Karma system
103
+ * - **Assets**: File upload and asset management
104
+ * - **Developer**: Developer API management
105
+ * - **Location**: Location-based features
106
+ * - **Analytics**: Analytics tracking
107
+ * - **Devices**: Device management
108
+ * - **Utility**: Utility methods and Express middleware
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * const oxy = new OxyServices({
113
+ * baseURL: 'https://api.oxy.so',
114
+ * cloudURL: 'https://cloud.oxy.so'
115
+ * });
116
+ * ```
102
117
  */
103
- exports.OxyAuthenticationTimeoutError = OxyAuthenticationTimeoutError;
104
- class OxyServices {
105
- /**
106
- * Creates a new instance of the OxyServices client
107
- * @param config - Configuration for the client
108
- * config.baseURL: Oxy API URL (e.g., https://api.oxy.so)
109
- * config.cloudURL: Oxy Cloud URL (e.g., https://cloud.oxy.so)
110
- */
111
- constructor(config) {
112
- this.config = config;
113
- this.cloudURL = config.cloudURL || OXY_CLOUD_URL;
114
-
115
- // Initialize HTTP client (handles authentication and interceptors)
116
- this.httpClient = new _HttpClient.HttpClient(config);
117
-
118
- // Initialize request manager (handles caching, deduplication, queuing, retry)
119
- this.requestManager = new _RequestManager.RequestManager(this.httpClient, config);
120
- }
121
-
122
- // Test-only utility to reset global tokens between jest tests
123
- static __resetTokensForTests() {
124
- _HttpClient.HttpClient.__resetTokensForTests();
125
- }
126
-
127
- /**
128
- * Make a request with all performance optimizations
129
- * This is the main method for all API calls - ensures authentication and performance features
130
- */
131
- async makeRequest(method, url, data, options = {}) {
132
- return this.requestManager.request(method, url, data, options);
133
- }
134
-
135
- // ============================================================================
136
- // CORE METHODS (HTTP Client, Token Management, Error Handling)
137
- // ============================================================================
138
-
139
- /**
140
- * Get the configured Oxy API base URL
141
- */
142
- getBaseURL() {
143
- return this.httpClient.getBaseURL();
144
- }
145
-
146
- /**
147
- * Get the HTTP client instance
148
- * Useful for advanced use cases where direct access to the HTTP client is needed
149
- */
150
- getClient() {
151
- return this.httpClient;
152
- }
153
-
154
- /**
155
- * Get performance metrics
156
- */
157
- getMetrics() {
158
- return this.requestManager.getMetrics();
159
- }
160
-
161
- /**
162
- * Clear request cache
163
- */
164
- clearCache() {
165
- this.requestManager.clearCache();
166
- }
167
-
168
- /**
169
- * Clear specific cache entry
170
- */
171
- clearCacheEntry(key) {
172
- this.requestManager.clearCacheEntry(key);
173
- }
174
-
175
- /**
176
- * Get cache statistics
177
- */
178
- getCacheStats() {
179
- return this.requestManager.getCacheStats();
180
- }
181
-
182
- /**
183
- * Get the configured Oxy Cloud (file storage/CDN) URL
184
- */
185
- getCloudURL() {
186
- return this.cloudURL;
187
- }
188
-
189
- /**
190
- * Set authentication tokens
191
- */
192
- setTokens(accessToken, refreshToken = '') {
193
- this.httpClient.setTokens(accessToken, refreshToken);
194
- }
195
-
196
- /**
197
- * Clear stored authentication tokens
198
- */
199
- clearTokens() {
200
- this.httpClient.clearTokens();
201
- }
202
-
203
- /**
204
- * Get the current user ID from the access token
205
- */
206
- getCurrentUserId() {
207
- const accessToken = this.httpClient.getAccessToken();
208
- if (!accessToken) {
209
- return null;
210
- }
211
- try {
212
- const decoded = (0, _jwtDecode.jwtDecode)(accessToken);
213
- return decoded.userId || decoded.id || null;
214
- } catch (error) {
215
- return null;
216
- }
217
- }
218
-
219
- /**
220
- * Check if the client has a valid access token
221
- */
222
- hasAccessToken() {
223
- return this.httpClient.hasAccessToken();
224
- }
225
-
226
- /**
227
- * Check if the client has a valid access token (public method)
228
- */
229
- hasValidToken() {
230
- return this.httpClient.hasAccessToken();
231
- }
232
-
233
- /**
234
- * Get the raw access token (for constructing anchor URLs when needed)
235
- */
236
- getAccessToken() {
237
- return this.httpClient.getAccessToken();
238
- }
239
-
240
- /**
241
- * Wait for authentication to be ready (public method)
242
- * Useful for apps that want to ensure authentication is complete before proceeding
243
- */
244
- async waitForAuth(timeoutMs = 5000) {
245
- return this.waitForAuthentication(timeoutMs);
246
- }
247
-
248
- /**
249
- * Wait for authentication to be ready with timeout
250
- */
251
- async waitForAuthentication(timeoutMs = 5000) {
252
- const startTime = Date.now();
253
- const checkInterval = 100; // Check every 100ms
254
-
255
- while (Date.now() - startTime < timeoutMs) {
256
- if (this.httpClient.hasAccessToken()) {
257
- return true;
258
- }
259
- await new Promise(resolve => setTimeout(resolve, checkInterval));
260
- }
261
- return false;
262
- }
263
-
264
- /**
265
- * Execute a function with automatic authentication retry logic
266
- * This handles the common case where API calls are made before authentication completes
267
- */
268
- async withAuthRetry(operation, operationName, options = {}) {
269
- const {
270
- maxRetries = 2,
271
- retryDelay = 1000,
272
- authTimeoutMs = 5000
273
- } = options;
274
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
275
- try {
276
- // First attempt: check if we have a token
277
- if (!this.httpClient.hasAccessToken()) {
278
- if (attempt === 0) {
279
- // On first attempt, wait briefly for authentication to complete
280
- const authReady = await this.waitForAuthentication(authTimeoutMs);
281
- if (!authReady) {
282
- throw new OxyAuthenticationTimeoutError(operationName, authTimeoutMs);
283
- }
284
- } else {
285
- // On retry attempts, fail immediately if no token
286
- throw new OxyAuthenticationError(`Authentication required: ${operationName} requires a valid access token.`, 'AUTH_REQUIRED');
287
- }
288
- }
289
-
290
- // Execute the operation
291
- return await operation();
292
- } catch (error) {
293
- const isLastAttempt = attempt === maxRetries;
294
- const isAuthError = error?.response?.status === 401 || error?.code === 'MISSING_TOKEN' || error?.message?.includes('Authentication') || error instanceof OxyAuthenticationError;
295
- if (isAuthError && !isLastAttempt && !(error instanceof OxyAuthenticationTimeoutError)) {
296
- await new Promise(resolve => setTimeout(resolve, retryDelay));
297
- continue;
298
- }
299
-
300
- // If it's not an auth error, or it's the last attempt, throw the error
301
- if (error instanceof OxyAuthenticationError) {
302
- throw error;
303
- }
304
- throw this.handleError(error);
305
- }
306
- }
307
-
308
- // This should never be reached, but TypeScript requires it
309
- throw new OxyAuthenticationError(`${operationName} failed after ${maxRetries + 1} attempts`);
310
- }
311
-
312
- /**
313
- * Validate the current access token with the server
314
- */
315
- async validate() {
316
- if (!this.hasAccessToken()) {
317
- return false;
318
- }
319
- try {
320
- const res = await this.makeRequest('GET', '/api/auth/validate', undefined, {
321
- cache: false,
322
- retry: false
323
- });
324
- return res.valid === true;
325
- } catch (error) {
326
- return false;
327
- }
328
- }
329
-
330
- /**
331
- * Centralized error handling
332
- */
333
- handleError(error) {
334
- const api = (0, _errorUtils.handleHttpError)(error);
335
- const err = new Error(api.message);
336
- err.code = api.code;
337
- err.status = api.status;
338
- err.details = api.details;
339
- return err;
340
- }
341
-
342
- /**
343
- * Health check endpoint
344
- */
345
- async healthCheck() {
346
- try {
347
- return await this.makeRequest('GET', '/health', undefined, {
348
- cache: false
349
- });
350
- } catch (error) {
351
- throw this.handleError(error);
352
- }
353
- }
354
-
355
- // ============================================================================
356
- // AUTHENTICATION METHODS
357
- // ============================================================================
358
-
359
- /**
360
- * Sign up a new user
361
- */
362
- async signUp(username, email, password) {
363
- try {
364
- const res = await this.makeRequest('POST', '/api/auth/signup', {
365
- username,
366
- email,
367
- password
368
- }, {
369
- cache: false
370
- });
371
- if (!res || typeof res === 'object' && Object.keys(res).length === 0) {
372
- throw new OxyAuthenticationError('Sign up failed', 'SIGNUP_FAILED', 400);
373
- }
374
- return res;
375
- } catch (error) {
376
- throw this.handleError(error);
377
- }
378
- }
379
-
380
- /**
381
- * Request account recovery (send verification code)
382
- */
383
- async requestRecovery(identifier) {
384
- try {
385
- return await this.makeRequest('POST', '/api/auth/recover/request', {
386
- identifier
387
- }, {
388
- cache: false
389
- });
390
- } catch (error) {
391
- throw this.handleError(error);
392
- }
393
- }
394
-
395
- /**
396
- * Verify recovery code
397
- */
398
- async verifyRecoveryCode(identifier, code) {
399
- try {
400
- return await this.makeRequest('POST', '/api/auth/recover/verify', {
401
- identifier,
402
- code
403
- }, {
404
- cache: false
405
- });
406
- } catch (error) {
407
- throw this.handleError(error);
408
- }
409
- }
410
-
411
- /**
412
- * Reset password using verified code
413
- */
414
- async resetPassword(identifier, code, newPassword) {
415
- try {
416
- return await this.makeRequest('POST', '/api/auth/recover/reset', {
417
- identifier,
418
- code,
419
- newPassword
420
- }, {
421
- cache: false
422
- });
423
- } catch (error) {
424
- throw this.handleError(error);
425
- }
426
- }
427
-
428
- /**
429
- * Reset password using TOTP code (recommended recovery)
430
- */
431
- async resetPasswordWithTotp(identifier, code, newPassword) {
432
- try {
433
- return await this.makeRequest('POST', '/api/auth/recover/totp/reset', {
434
- identifier,
435
- code,
436
- newPassword
437
- }, {
438
- cache: false
439
- });
440
- } catch (error) {
441
- throw this.handleError(error);
442
- }
443
- }
444
- async resetPasswordWithBackupCode(identifier, backupCode, newPassword) {
445
- try {
446
- return await this.makeRequest('POST', '/api/auth/recover/backup/reset', {
447
- identifier,
448
- backupCode,
449
- newPassword
450
- }, {
451
- cache: false
452
- });
453
- } catch (error) {
454
- throw this.handleError(error);
455
- }
456
- }
457
- async resetPasswordWithRecoveryKey(identifier, recoveryKey, newPassword) {
458
- try {
459
- return await this.makeRequest('POST', '/api/auth/recover/recovery-key/reset', {
460
- identifier,
461
- recoveryKey,
462
- newPassword
463
- }, {
464
- cache: false
465
- });
466
- } catch (error) {
467
- throw this.handleError(error);
468
- }
469
- }
470
-
471
- /**
472
- * Sign in with device management
473
- */
474
- async signIn(username, password, deviceName, deviceFingerprint) {
475
- try {
476
- return await this.makeRequest('POST', '/api/auth/login', {
477
- username,
478
- password,
479
- deviceName,
480
- deviceFingerprint
481
- }, {
482
- cache: false
483
- });
484
- } catch (error) {
485
- throw this.handleError(error);
486
- }
487
- }
488
-
489
- /**
490
- * Complete login by verifying TOTP with MFA token
491
- */
492
- async verifyTotpLogin(mfaToken, code) {
493
- try {
494
- return await this.makeRequest('POST', '/api/auth/totp/verify-login', {
495
- mfaToken,
496
- code
497
- }, {
498
- cache: false
499
- });
500
- } catch (error) {
501
- throw this.handleError(error);
502
- }
503
- }
504
-
505
- /**
506
- * Get user by session ID
507
- */
508
- async getUserBySession(sessionId) {
509
- try {
510
- return await this.makeRequest('GET', `/api/session/user/${sessionId}`, undefined, {
511
- cache: true,
512
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache for user data
513
- });
514
- } catch (error) {
515
- throw this.handleError(error);
516
- }
517
- }
518
-
519
- /**
520
- * Batch get multiple user profiles by session IDs (optimized for account switching)
521
- * Returns array of { sessionId, user } objects
522
- */
523
- async getUsersBySessions(sessionIds) {
524
- try {
525
- if (!Array.isArray(sessionIds) || sessionIds.length === 0) {
526
- return [];
527
- }
528
-
529
- // Deduplicate and sort sessionIds for consistent cache keys
530
- const uniqueSessionIds = Array.from(new Set(sessionIds)).sort();
531
- return await this.makeRequest('POST', '/api/session/users/batch', {
532
- sessionIds: uniqueSessionIds
533
- }, {
534
- cache: true,
535
- cacheTTL: 2 * 60 * 1000,
536
- // 2 minutes cache
537
- deduplicate: true // Important for batch requests
538
- });
539
- } catch (error) {
540
- throw this.handleError(error);
541
- }
542
- }
543
-
544
- /**
545
- * Get access token by session ID and set it in the token store
546
- */
547
- async getTokenBySession(sessionId) {
548
- try {
549
- const res = await this.makeRequest('GET', `/api/session/token/${sessionId}`, undefined, {
550
- cache: false,
551
- retry: false
552
- });
553
-
554
- // Set the token in the centralized token store
555
- this.setTokens(res.accessToken);
556
- return res;
557
- } catch (error) {
558
- throw this.handleError(error);
559
- }
560
- }
561
-
562
- /**
563
- * Get sessions by session ID
564
- */
565
- async getSessionsBySessionId(sessionId) {
566
- try {
567
- return await this.makeRequest('GET', `/api/session/sessions/${sessionId}`, undefined, {
568
- cache: false
569
- });
570
- } catch (error) {
571
- throw this.handleError(error);
572
- }
573
- }
574
-
575
- /**
576
- * Logout from a specific session
577
- */
578
- async logoutSession(sessionId, targetSessionId) {
579
- try {
580
- const url = targetSessionId ? `/api/session/logout/${sessionId}/${targetSessionId}` : `/api/session/logout/${sessionId}`;
581
- await this.makeRequest('POST', url, undefined, {
582
- cache: false
583
- });
584
- } catch (error) {
585
- throw this.handleError(error);
586
- }
587
- }
588
-
589
- /**
590
- * Logout from all sessions
591
- */
592
- async logoutAllSessions(sessionId) {
593
- try {
594
- await this.makeRequest('POST', `/api/session/logout-all/${sessionId}`, undefined, {
595
- cache: false
596
- });
597
- } catch (error) {
598
- throw this.handleError(error);
599
- }
600
- }
601
-
602
- /**
603
- * Validate session
604
- */
605
- async validateSession(sessionId, options = {}) {
606
- try {
607
- const params = new URLSearchParams();
608
- if (options.deviceFingerprint) {
609
- params.append('deviceFingerprint', options.deviceFingerprint);
610
- }
611
- if (options.useHeaderValidation) {
612
- params.append('useHeaderValidation', 'true');
613
- }
614
- const url = `/api/session/validate/${sessionId}`;
615
- const urlParams = {};
616
- if (options.deviceFingerprint) urlParams.deviceFingerprint = options.deviceFingerprint;
617
- if (options.useHeaderValidation) urlParams.useHeaderValidation = 'true';
618
- return await this.makeRequest('GET', url, urlParams, {
619
- cache: false
620
- });
621
- } catch (error) {
622
- throw this.handleError(error);
623
- }
624
- }
625
-
626
- /**
627
- * Check username availability
628
- */
629
- async checkUsernameAvailability(username) {
630
- try {
631
- return await this.makeRequest('GET', `/api/auth/check-username/${username}`, undefined, {
632
- cache: false
633
- });
634
- } catch (error) {
635
- throw this.handleError(error);
636
- }
637
- }
638
-
639
- /**
640
- * Check email availability
641
- */
642
- async checkEmailAvailability(email) {
643
- try {
644
- return await this.makeRequest('GET', `/api/auth/check-email/${email}`, undefined, {
645
- cache: false
646
- });
647
- } catch (error) {
648
- throw this.handleError(error);
649
- }
650
- }
651
-
652
- // ============================================================================
653
- // USER METHODS
654
- // ============================================================================
655
-
656
- /**
657
- * Get profile by username
658
- */
659
- async getProfileByUsername(username) {
660
- try {
661
- return await this.makeRequest('GET', `/api/profiles/username/${username}`, undefined, {
662
- cache: true,
663
- cacheTTL: 5 * 60 * 1000 // 5 minutes cache for profiles
664
- });
665
- } catch (error) {
666
- throw this.handleError(error);
667
- }
668
- }
669
-
670
- // ============================================================================
671
- // TOTP ENROLLMENT
672
- // ============================================================================
673
-
674
- async startTotpEnrollment(sessionId) {
675
- try {
676
- // Note: x-session-id header is handled by HttpClient interceptors if needed
677
- return await this.makeRequest('POST', '/api/auth/totp/enroll/start', {
678
- sessionId
679
- }, {
680
- cache: false
681
- });
682
- } catch (error) {
683
- throw this.handleError(error);
684
- }
685
- }
686
- async verifyTotpEnrollment(sessionId, code) {
687
- try {
688
- return await this.makeRequest('POST', '/api/auth/totp/enroll/verify', {
689
- sessionId,
690
- code
691
- }, {
692
- cache: false
693
- });
694
- } catch (error) {
695
- throw this.handleError(error);
696
- }
697
- }
698
- async disableTotp(sessionId, code) {
699
- try {
700
- return await this.makeRequest('POST', '/api/auth/totp/disable', {
701
- sessionId,
702
- code
703
- }, {
704
- cache: false
705
- });
706
- } catch (error) {
707
- throw this.handleError(error);
708
- }
709
- }
710
-
711
- /**
712
- * Search user profiles
713
- */
714
- async searchProfiles(query, pagination) {
715
- try {
716
- const params = {
717
- query,
718
- ...pagination
719
- };
720
- const searchParams = (0, _apiUtils.buildSearchParams)(params);
721
- const paramsObj = {};
722
- searchParams.forEach((value, key) => {
723
- paramsObj[key] = value;
724
- });
725
- return await this.makeRequest('GET', '/api/profiles/search', paramsObj, {
726
- cache: true,
727
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache
728
- });
729
- } catch (error) {
730
- throw this.handleError(error);
731
- }
732
- }
733
-
734
- /**
735
- * Get profile recommendations
736
- */
737
- async getProfileRecommendations() {
738
- return this.withAuthRetry(async () => {
739
- return await this.makeRequest('GET', '/api/profiles/recommendations', undefined, {
740
- cache: true
741
- });
742
- }, 'getProfileRecommendations');
743
- }
744
-
745
- /**
746
- * Get user by ID
747
- */
748
- async getUserById(userId) {
749
- try {
750
- return await this.makeRequest('GET', `/api/users/${userId}`, undefined, {
751
- cache: true,
752
- cacheTTL: 5 * 60 * 1000 // 5 minutes cache
753
- });
754
- } catch (error) {
755
- throw this.handleError(error);
756
- }
757
- }
758
-
759
- /**
760
- * Get current user
761
- */
762
- async getCurrentUser() {
763
- return this.withAuthRetry(async () => {
764
- return await this.makeRequest('GET', '/api/users/me', undefined, {
765
- cache: true,
766
- cacheTTL: 1 * 60 * 1000 // 1 minute cache for current user
767
- });
768
- }, 'getCurrentUser');
769
- }
770
-
771
- /**
772
- * Update user profile
773
- */
774
- async updateProfile(updates) {
775
- try {
776
- return await this.makeRequest('PUT', '/api/users/me', updates, {
777
- cache: false
778
- });
779
- } catch (error) {
780
- throw this.handleError(error);
781
- }
782
- }
783
-
784
- /**
785
- * Get privacy settings for a user
786
- * @param userId - The user ID (defaults to current user)
787
- */
788
- async getPrivacySettings(userId) {
789
- try {
790
- const id = userId || (await this.getCurrentUser()).id;
791
- return await this.makeRequest('GET', `/api/privacy/${id}/privacy`, undefined, {
792
- cache: true,
793
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache
794
- });
795
- } catch (error) {
796
- throw this.handleError(error);
797
- }
798
- }
799
-
800
- /**
801
- * Update privacy settings
802
- * @param settings - Partial privacy settings object
803
- * @param userId - The user ID (defaults to current user)
804
- */
805
- async updatePrivacySettings(settings, userId) {
806
- try {
807
- const id = userId || (await this.getCurrentUser()).id;
808
- return await this.makeRequest('PATCH', `/api/privacy/${id}/privacy`, settings, {
809
- cache: false
810
- });
811
- } catch (error) {
812
- throw this.handleError(error);
813
- }
814
- }
815
-
816
- // ============================================================================
817
- // BLOCKED USERS METHODS
818
- // ============================================================================
819
-
820
- /**
821
- * Get list of blocked users
822
- * @returns Array of blocked users
823
- */
824
- async getBlockedUsers() {
825
- try {
826
- return await this.makeRequest('GET', '/api/privacy/blocked', undefined, {
827
- cache: true,
828
- cacheTTL: 1 * 60 * 1000 // 1 minute cache
829
- });
830
- } catch (error) {
831
- throw this.handleError(error);
832
- }
833
- }
834
-
835
- /**
836
- * Block a user
837
- * @param userId - The user ID to block
838
- * @returns Success message
839
- */
840
- async blockUser(userId) {
841
- try {
842
- if (!userId) {
843
- throw new Error('User ID is required');
844
- }
845
- return await this.makeRequest('POST', `/api/privacy/blocked/${userId}`, undefined, {
846
- cache: false
847
- });
848
- } catch (error) {
849
- throw this.handleError(error);
850
- }
851
- }
852
-
853
- /**
854
- * Unblock a user
855
- * @param userId - The user ID to unblock
856
- * @returns Success message
857
- */
858
- async unblockUser(userId) {
859
- try {
860
- if (!userId) {
861
- throw new Error('User ID is required');
862
- }
863
- return await this.makeRequest('DELETE', `/api/privacy/blocked/${userId}`, undefined, {
864
- cache: false
865
- });
866
- } catch (error) {
867
- throw this.handleError(error);
868
- }
869
- }
870
-
871
- /**
872
- * Extract user ID from blocked/restricted user object
873
- * @private
874
- */
875
- extractUserId(userIdField) {
876
- return typeof userIdField === 'string' ? userIdField : userIdField._id;
877
- }
878
-
879
- /**
880
- * Check if a user is in a list (blocked or restricted)
881
- * @private
882
- */
883
- async isUserInList(userId, getUserList, getIdField) {
884
- try {
885
- if (!userId) {
886
- return false;
887
- }
888
- const users = await getUserList();
889
- return users.some(item => {
890
- const itemId = this.extractUserId(getIdField(item));
891
- return itemId === userId;
892
- });
893
- } catch (error) {
894
- // If there's an error, assume not in list to avoid breaking functionality
895
- if (__DEV__) {
896
- console.warn('Error checking user list:', error);
897
- }
898
- return false;
899
- }
900
- }
901
-
902
- /**
903
- * Check if a user is blocked
904
- * @param userId - The user ID to check
905
- * @returns True if the user is blocked, false otherwise
906
- */
907
- async isUserBlocked(userId) {
908
- return this.isUserInList(userId, () => this.getBlockedUsers(), block => block.blockedId);
909
- }
910
-
911
- // ============================================================================
912
- // RESTRICTED USERS METHODS
913
- // ============================================================================
914
-
915
- /**
916
- * Get list of restricted users
917
- * @returns Array of restricted users
918
- */
919
- async getRestrictedUsers() {
920
- try {
921
- return await this.makeRequest('GET', '/api/privacy/restricted', undefined, {
922
- cache: true,
923
- cacheTTL: 1 * 60 * 1000 // 1 minute cache
924
- });
925
- } catch (error) {
926
- throw this.handleError(error);
927
- }
928
- }
929
-
930
- /**
931
- * Restrict a user (limit their interactions without fully blocking)
932
- * @param userId - The user ID to restrict
933
- * @returns Success message
934
- */
935
- async restrictUser(userId) {
936
- try {
937
- if (!userId) {
938
- throw new Error('User ID is required');
939
- }
940
- return await this.makeRequest('POST', `/api/privacy/restricted/${userId}`, undefined, {
941
- cache: false
942
- });
943
- } catch (error) {
944
- throw this.handleError(error);
945
- }
946
- }
947
-
948
- /**
949
- * Unrestrict a user
950
- * @param userId - The user ID to unrestrict
951
- * @returns Success message
952
- */
953
- async unrestrictUser(userId) {
954
- try {
955
- if (!userId) {
956
- throw new Error('User ID is required');
957
- }
958
- return await this.makeRequest('DELETE', `/api/privacy/restricted/${userId}`, undefined, {
959
- cache: false
960
- });
961
- } catch (error) {
962
- throw this.handleError(error);
963
- }
964
- }
965
-
966
- /**
967
- * Check if a user is restricted
968
- * @param userId - The user ID to check
969
- * @returns True if the user is restricted, false otherwise
970
- */
971
- async isUserRestricted(userId) {
972
- return this.isUserInList(userId, () => this.getRestrictedUsers(), restrict => restrict.restrictedId);
973
- }
974
-
975
- /**
976
- * Request account verification
977
- */
978
- async requestAccountVerification(reason, evidence) {
979
- try {
980
- return await this.makeRequest('POST', '/api/users/verify/request', {
981
- reason,
982
- evidence
983
- }, {
984
- cache: false
985
- });
986
- } catch (error) {
987
- throw this.handleError(error);
988
- }
989
- }
990
-
991
- /**
992
- * Download account data export
993
- */
994
- async downloadAccountData(format = 'json') {
995
- try {
996
- // Use axios instance directly for blob responses since RequestManager doesn't handle blobs
997
- const axiosInstance = this.httpClient.getAxiosInstance();
998
- const response = await axiosInstance.get(`/api/users/me/data?format=${format}`, {
999
- responseType: 'blob'
1000
- });
1001
- return response.data;
1002
- } catch (error) {
1003
- throw this.handleError(error);
1004
- }
1005
- }
1006
-
1007
- /**
1008
- * Delete account permanently
1009
- * @param password - User password for confirmation
1010
- * @param confirmText - Confirmation text (usually username)
1011
- */
1012
- async deleteAccount(password, confirmText) {
1013
- try {
1014
- return await this.makeRequest('DELETE', '/api/users/me', {
1015
- password,
1016
- confirmText
1017
- }, {
1018
- cache: false
1019
- });
1020
- } catch (error) {
1021
- throw this.handleError(error);
1022
- }
1023
- }
118
+ // Compose all mixins into the final OxyServices class
119
+ const OxyServicesComposed = (0, _mixins.composeOxyServices)();
1024
120
 
1025
- // ============================================================================
1026
- // LANGUAGE METHODS
1027
- // ============================================================================
1028
-
1029
- /**
1030
- * Get the current language from storage or user profile
1031
- * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
1032
- * @returns The current language code (e.g., 'en-US') or null if not set
1033
- */
1034
- async getCurrentLanguage(storageKeyPrefix = 'oxy_session') {
1035
- try {
1036
- // First try to get from user profile if authenticated
1037
- try {
1038
- const user = await this.getCurrentUser();
1039
- const userLanguage = user?.language;
1040
- if (userLanguage) {
1041
- return (0, _languageUtils.normalizeLanguageCode)(userLanguage) || userLanguage;
1042
- }
1043
- } catch (e) {
1044
- // User not authenticated or error, continue to storage
1045
- }
1046
-
1047
- // Fall back to storage
1048
- const storage = await this.getStorage();
1049
- const storageKey = `${storageKeyPrefix}_language`;
1050
- const storedLanguage = await storage.getItem(storageKey);
1051
- if (storedLanguage) {
1052
- return (0, _languageUtils.normalizeLanguageCode)(storedLanguage) || storedLanguage;
1053
- }
1054
- return null;
1055
- } catch (error) {
1056
- if (__DEV__) {
1057
- console.warn('Failed to get current language:', error);
1058
- }
1059
- return null;
1060
- }
1061
- }
1062
-
1063
- /**
1064
- * Get the current language with metadata (name, nativeName, etc.)
1065
- * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
1066
- * @returns Language metadata object or null if not set
1067
- */
1068
- async getCurrentLanguageMetadata(storageKeyPrefix = 'oxy_session') {
1069
- const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
1070
- return (0, _languageUtils.getLanguageMetadata)(languageCode);
1071
- }
1072
-
1073
- /**
1074
- * Get the current language name (e.g., 'English')
1075
- * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
1076
- * @returns Language name or null if not set
1077
- */
1078
- async getCurrentLanguageName(storageKeyPrefix = 'oxy_session') {
1079
- const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
1080
- if (!languageCode) return null;
1081
- return (0, _languageUtils.getLanguageName)(languageCode);
1082
- }
1083
-
1084
- /**
1085
- * Get the current native language name (e.g., 'Español')
1086
- * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
1087
- * @returns Native language name or null if not set
1088
- */
1089
- async getCurrentNativeLanguageName(storageKeyPrefix = 'oxy_session') {
1090
- const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
1091
- if (!languageCode) return null;
1092
- return (0, _languageUtils.getNativeLanguageName)(languageCode);
1093
- }
1094
-
1095
- /**
1096
- * Get appropriate storage for the platform (similar to DeviceManager)
1097
- * @private
1098
- */
1099
- async getStorage() {
1100
- const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
1101
- if (isReactNative) {
1102
- try {
1103
- const asyncStorageModule = await Promise.resolve().then(() => _interopRequireWildcard(require('@react-native-async-storage/async-storage')));
1104
- const storage = asyncStorageModule.default;
1105
- return {
1106
- getItem: storage.getItem.bind(storage),
1107
- setItem: storage.setItem.bind(storage),
1108
- removeItem: storage.removeItem.bind(storage)
1109
- };
1110
- } catch (error) {
1111
- console.error('AsyncStorage not available in React Native:', error);
1112
- throw new Error('AsyncStorage is required in React Native environment');
1113
- }
1114
- } else {
1115
- // Use localStorage for web
1116
- return {
1117
- getItem: async key => {
1118
- if (typeof window !== 'undefined' && window.localStorage) {
1119
- return localStorage.getItem(key);
1120
- }
1121
- return null;
1122
- },
1123
- setItem: async (key, value) => {
1124
- if (typeof window !== 'undefined' && window.localStorage) {
1125
- localStorage.setItem(key, value);
1126
- }
1127
- },
1128
- removeItem: async key => {
1129
- if (typeof window !== 'undefined' && window.localStorage) {
1130
- localStorage.removeItem(key);
1131
- }
1132
- }
1133
- };
1134
- }
1135
- }
1136
-
1137
- /**
1138
- * Update user by ID (admin function)
1139
- */
1140
- async updateUser(userId, updates) {
1141
- try {
1142
- return await this.makeRequest('PUT', `/api/users/${userId}`, updates, {
1143
- cache: false
1144
- });
1145
- } catch (error) {
1146
- throw this.handleError(error);
1147
- }
1148
- }
1149
-
1150
- /**
1151
- * Follow a user
1152
- */
1153
- async followUser(userId) {
1154
- try {
1155
- return await this.makeRequest('POST', `/api/users/${userId}/follow`, undefined, {
1156
- cache: false
1157
- });
1158
- } catch (error) {
1159
- throw this.handleError(error);
1160
- }
1161
- }
1162
-
1163
- /**
1164
- * Unfollow a user
1165
- */
1166
- async unfollowUser(userId) {
1167
- try {
1168
- return await this.makeRequest('DELETE', `/api/users/${userId}/follow`, undefined, {
1169
- cache: false
1170
- });
1171
- } catch (error) {
1172
- throw this.handleError(error);
1173
- }
1174
- }
1175
-
1176
- /**
1177
- * Get follow status
1178
- */
1179
- async getFollowStatus(userId) {
1180
- try {
1181
- return await this.makeRequest('GET', `/api/users/${userId}/follow-status`, undefined, {
1182
- cache: true,
1183
- cacheTTL: 1 * 60 * 1000 // 1 minute cache
1184
- });
1185
- } catch (error) {
1186
- throw this.handleError(error);
1187
- }
1188
- }
1189
-
1190
- /**
1191
- * Get user followers
1192
- */
1193
- async getUserFollowers(userId, pagination) {
1194
- try {
1195
- const params = (0, _apiUtils.buildPaginationParams)(pagination || {});
1196
- const response = await this.makeRequest('GET', `/api/users/${userId}/followers`, params, {
1197
- cache: true,
1198
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache
1199
- });
1200
- return {
1201
- followers: response.data || [],
1202
- total: response.pagination.total,
1203
- hasMore: response.pagination.hasMore
1204
- };
1205
- } catch (error) {
1206
- throw this.handleError(error);
1207
- }
1208
- }
1209
-
1210
- /**
1211
- * Get user following
1212
- */
1213
- async getUserFollowing(userId, pagination) {
1214
- try {
1215
- const params = (0, _apiUtils.buildPaginationParams)(pagination || {});
1216
- const response = await this.makeRequest('GET', `/api/users/${userId}/following`, params, {
1217
- cache: true,
1218
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache
1219
- });
1220
- return {
1221
- following: response.data || [],
1222
- total: response.pagination.total,
1223
- hasMore: response.pagination.hasMore
1224
- };
1225
- } catch (error) {
1226
- throw this.handleError(error);
1227
- }
1228
- }
1229
-
1230
- /**
1231
- * Get notifications
1232
- */
1233
- async getNotifications() {
1234
- return this.withAuthRetry(async () => {
1235
- return await this.makeRequest('GET', '/api/notifications', undefined, {
1236
- cache: false // Don't cache notifications - always get fresh data
1237
- });
1238
- }, 'getNotifications');
1239
- }
1240
-
1241
- /**
1242
- * Get unread notification count
1243
- */
1244
- async getUnreadCount() {
1245
- try {
1246
- const res = await this.makeRequest('GET', '/api/notifications/unread-count', undefined, {
1247
- cache: false // Don't cache unread count - always get fresh data
1248
- });
1249
- return res.count;
1250
- } catch (error) {
1251
- throw this.handleError(error);
1252
- }
1253
- }
1254
-
1255
- /**
1256
- * Create notification
1257
- */
1258
- async createNotification(data) {
1259
- try {
1260
- return await this.makeRequest('POST', '/api/notifications', data, {
1261
- cache: false
1262
- });
1263
- } catch (error) {
1264
- throw this.handleError(error);
1265
- }
1266
- }
1267
-
1268
- /**
1269
- * Mark notification as read
1270
- */
1271
- async markNotificationAsRead(notificationId) {
1272
- try {
1273
- await this.makeRequest('PUT', `/api/notifications/${notificationId}/read`, undefined, {
1274
- cache: false
1275
- });
1276
- } catch (error) {
1277
- throw this.handleError(error);
1278
- }
1279
- }
1280
-
1281
- /**
1282
- * Mark all notifications as read
1283
- */
1284
- async markAllNotificationsAsRead() {
1285
- try {
1286
- await this.makeRequest('PUT', '/api/notifications/read-all', undefined, {
1287
- cache: false
1288
- });
1289
- } catch (error) {
1290
- throw this.handleError(error);
1291
- }
1292
- }
1293
-
1294
- /**
1295
- * Delete notification
1296
- */
1297
- async deleteNotification(notificationId) {
1298
- try {
1299
- await this.makeRequest('DELETE', `/api/notifications/${notificationId}`, undefined, {
1300
- cache: false
1301
- });
1302
- } catch (error) {
1303
- throw this.handleError(error);
1304
- }
1305
- }
1306
-
1307
- // ============================================================================
1308
- // PAYMENT METHODS
1309
- // ============================================================================
1310
-
1311
- /**
1312
- * Create a payment
1313
- */
1314
- async createPayment(data) {
1315
- try {
1316
- return await this.makeRequest('POST', '/api/payments', data, {
1317
- cache: false
1318
- });
1319
- } catch (error) {
1320
- throw this.handleError(error);
1321
- }
1322
- }
1323
-
1324
- /**
1325
- * Get payment by ID
1326
- */
1327
- async getPayment(paymentId) {
1328
- try {
1329
- return await this.makeRequest('GET', `/api/payments/${paymentId}`, undefined, {
1330
- cache: true,
1331
- cacheTTL: 5 * 60 * 1000 // 5 minutes cache
1332
- });
1333
- } catch (error) {
1334
- throw this.handleError(error);
1335
- }
1336
- }
1337
-
1338
- /**
1339
- * Get user payments
1340
- */
1341
- async getUserPayments() {
1342
- try {
1343
- return await this.makeRequest('GET', '/api/payments/user', undefined, {
1344
- cache: false // Don't cache user payments - always get fresh data
1345
- });
1346
- } catch (error) {
1347
- throw this.handleError(error);
1348
- }
1349
- }
1350
-
1351
- // ============================================================================
1352
- // KARMA METHODS
1353
- // ============================================================================
1354
-
1355
- /**
1356
- * Get user karma
1357
- */
1358
- async getUserKarma(userId) {
1359
- try {
1360
- return await this.makeRequest('GET', `/api/karma/${userId}`, undefined, {
1361
- cache: true,
1362
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache
1363
- });
1364
- } catch (error) {
1365
- throw this.handleError(error);
1366
- }
1367
- }
1368
-
1369
- /**
1370
- * Give karma to user
1371
- */
1372
- async giveKarma(userId, amount, reason) {
1373
- try {
1374
- return await this.makeRequest('POST', `/api/karma/${userId}/give`, {
1375
- amount,
1376
- reason
1377
- }, {
1378
- cache: false
1379
- });
1380
- } catch (error) {
1381
- throw this.handleError(error);
1382
- }
1383
- }
1384
-
1385
- /**
1386
- * Get user karma total
1387
- */
1388
- async getUserKarmaTotal(userId) {
1389
- try {
1390
- return await this.makeRequest('GET', `/api/karma/${userId}/total`, undefined, {
1391
- cache: true,
1392
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache
1393
- });
1394
- } catch (error) {
1395
- throw this.handleError(error);
1396
- }
1397
- }
1398
-
1399
- /**
1400
- * Get user karma history
1401
- */
1402
- async getUserKarmaHistory(userId, limit, offset) {
1403
- try {
1404
- const params = {};
1405
- if (limit) params.limit = limit;
1406
- if (offset) params.offset = offset;
1407
- return await this.makeRequest('GET', `/api/karma/${userId}/history`, params, {
1408
- cache: true,
1409
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache
1410
- });
1411
- } catch (error) {
1412
- throw this.handleError(error);
1413
- }
1414
- }
1415
-
1416
- /**
1417
- * Get karma leaderboard
1418
- */
1419
- async getKarmaLeaderboard() {
1420
- try {
1421
- return await this.makeRequest('GET', '/api/karma/leaderboard', undefined, {
1422
- cache: true,
1423
- cacheTTL: 5 * 60 * 1000 // 5 minutes cache
1424
- });
1425
- } catch (error) {
1426
- throw this.handleError(error);
1427
- }
1428
- }
1429
-
1430
- /**
1431
- * Get karma rules
1432
- */
1433
- async getKarmaRules() {
1434
- try {
1435
- return await this.makeRequest('GET', '/api/karma/rules', undefined, {
1436
- cache: true,
1437
- cacheTTL: 30 * 60 * 1000 // 30 minutes cache (rules don't change often)
1438
- });
1439
- } catch (error) {
1440
- throw this.handleError(error);
1441
- }
1442
- }
1443
-
1444
- // ============================================================================
1445
- // FILE METHODS (LEGACY - Using Asset Service)
1446
- // ============================================================================
1447
-
1448
- /**
1449
- * Delete file
1450
- */
1451
- async deleteFile(fileId) {
1452
- try {
1453
- // Central Asset Service delete with force=true behavior controlled by caller via assetDelete
1454
- return await this.makeRequest('DELETE', `/api/assets/${encodeURIComponent(fileId)}`, undefined, {
1455
- cache: false
1456
- });
1457
- } catch (error) {
1458
- throw this.handleError(error);
1459
- }
1460
- }
1461
-
1462
- /**
1463
- * Get file download URL (API streaming proxy, attaches token for <img src>)
1464
- */
1465
- getFileDownloadUrl(fileId, variant, expiresIn) {
1466
- const base = this.getBaseURL();
1467
- const params = new URLSearchParams();
1468
- if (variant) params.set('variant', variant);
1469
- if (expiresIn) params.set('expiresIn', String(expiresIn));
1470
- params.set('fallback', 'placeholderVisible');
1471
- const token = this.httpClient.getAccessToken();
1472
- if (token) params.set('token', token);
1473
-
1474
- // Use params.toString() to detect whether there are query params.
1475
- // URLSearchParams.size is not a standard property across all JS runtimes
1476
- // (some environments like React Native may not implement it), which
1477
- // caused the query string to be omitted on native. Checking the
1478
- // serialized string is reliable everywhere.
1479
- const qs = params.toString();
1480
- return `${base}/api/assets/${encodeURIComponent(fileId)}/stream${qs ? `?${qs}` : ''}`;
1481
- }
1482
-
1483
- /**
1484
- * Get file stream URL (direct Oxy Cloud/CDN URL, no token)
1485
- */
1486
- getFileStreamUrl(fileId) {
1487
- return `${this.getCloudURL()}/files/${fileId}/stream`;
1488
- }
1489
-
1490
- // ...existing code...
1491
-
1492
- /**
1493
- * List user files
1494
- */
1495
- async listUserFiles(limit, offset) {
1496
- try {
1497
- const paramsObj = {};
1498
- if (limit) paramsObj.limit = limit;
1499
- if (offset) paramsObj.offset = offset;
1500
- return await this.makeRequest('GET', '/api/assets', paramsObj, {
1501
- cache: false // Don't cache file lists - always get fresh data
1502
- });
1503
- } catch (error) {
1504
- throw this.handleError(error);
1505
- }
1506
- }
1507
-
1508
- // (removed legacy downloadFileContent; use getFileContentAsBlob/Text which resolve CAS URL first)
1509
-
1510
- /**
1511
- * Get file content as text
1512
- */
1513
- async getFileContentAsText(fileId, variant) {
1514
- try {
1515
- const params = variant ? {
1516
- variant
1517
- } : undefined;
1518
- const urlRes = await this.makeRequest('GET', `/api/assets/${encodeURIComponent(fileId)}/url`, params, {
1519
- cache: true,
1520
- cacheTTL: 10 * 60 * 1000 // 10 minutes cache for URLs
1521
- });
1522
- const downloadUrl = urlRes?.url;
1523
- const response = await fetch(downloadUrl);
1524
- return await response.text();
1525
- } catch (error) {
1526
- throw this.handleError(error);
1527
- }
1528
- }
1529
-
1530
- /**
1531
- * Get file content as blob
1532
- */
1533
- async getFileContentAsBlob(fileId, variant) {
1534
- try {
1535
- const params = variant ? {
1536
- variant
1537
- } : undefined;
1538
- const urlRes = await this.makeRequest('GET', `/api/assets/${encodeURIComponent(fileId)}/url`, params, {
1539
- cache: true,
1540
- cacheTTL: 10 * 60 * 1000 // 10 minutes cache for URLs
1541
- });
1542
- const downloadUrl = urlRes?.url;
1543
- const response = await fetch(downloadUrl);
1544
- return await response.blob();
1545
- } catch (error) {
1546
- throw this.handleError(error);
1547
- }
1548
- }
1549
-
1550
- /**
1551
- * Upload raw file data
1552
- */
1553
- async uploadRawFile(file, visibility, metadata) {
1554
- // Switch to Central Asset Service upload flow
1555
- return this.assetUpload(file, visibility, metadata);
1556
- }
1557
-
1558
- // ============================================================================
1559
- // CENTRAL ASSET SERVICE METHODS
1560
- // ============================================================================
1561
-
1562
- /**
1563
- * Calculate SHA256 hash of file content
1564
- */
1565
- async calculateSHA256(file) {
1566
- const buffer = await file.arrayBuffer();
1567
- const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
1568
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1569
- return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
1570
- }
1571
-
1572
- /**
1573
- * Initialize asset upload - returns pre-signed URL and file ID
1574
- */
1575
- async assetInit(sha256, size, mime) {
1576
- try {
1577
- return await this.makeRequest('POST', '/api/assets/init', {
1578
- sha256,
1579
- size,
1580
- mime
1581
- }, {
1582
- cache: false
1583
- });
1584
- } catch (error) {
1585
- throw this.handleError(error);
1586
- }
1587
- }
1588
-
1589
- /**
1590
- * Complete asset upload - commit metadata and trigger variant generation
1591
- */
1592
- async assetComplete(fileId, originalName, size, mime, visibility, metadata) {
1593
- try {
1594
- return await this.makeRequest('POST', '/api/assets/complete', {
1595
- fileId,
1596
- originalName,
1597
- size,
1598
- mime,
1599
- visibility,
1600
- metadata
1601
- }, {
1602
- cache: false
1603
- });
1604
- } catch (error) {
1605
- throw this.handleError(error);
1606
- }
1607
- }
1608
-
1609
- /**
1610
- * Upload file using Central Asset Service
1611
- */
1612
- async assetUpload(file, visibility, metadata, onProgress) {
1613
- try {
1614
- // Calculate SHA256
1615
- const sha256 = await this.calculateSHA256(file);
1616
-
1617
- // Initialize upload
1618
- const initResponse = await this.assetInit(sha256, file.size, file.type);
1619
-
1620
- // Try presigned URL first
1621
- try {
1622
- await this.uploadToPresignedUrl(initResponse.uploadUrl, file, onProgress);
1623
- } catch (e) {
1624
- // Fallback: direct upload via API to avoid CORS issues
1625
- const fd = new FormData();
1626
- fd.append('file', file);
1627
- // Use httpClient directly for FormData uploads (bypasses RequestManager for special handling)
1628
- await this.httpClient.request({
1629
- method: 'POST',
1630
- url: `/api/assets/${encodeURIComponent(initResponse.fileId)}/upload-direct`,
1631
- data: fd
1632
- });
1633
- }
1634
-
1635
- // Complete upload
1636
- return await this.assetComplete(initResponse.fileId, file.name, file.size, file.type, visibility, metadata);
1637
- } catch (error) {
1638
- throw this.handleError(error);
1639
- }
1640
- }
1641
-
1642
- /**
1643
- * Upload file to pre-signed URL
1644
- */
1645
- async uploadToPresignedUrl(url, file, onProgress) {
1646
- return new Promise((resolve, reject) => {
1647
- const xhr = new XMLHttpRequest();
1648
- xhr.upload.addEventListener('progress', event => {
1649
- if (event.lengthComputable && onProgress) {
1650
- const progress = event.loaded / event.total * 100;
1651
- onProgress(progress);
1652
- }
1653
- });
1654
- xhr.addEventListener('load', () => {
1655
- if (xhr.status >= 200 && xhr.status < 300) {
1656
- resolve();
1657
- } else {
1658
- reject(new Error(`Upload failed with status ${xhr.status}`));
1659
- }
1660
- });
1661
- xhr.addEventListener('error', () => {
1662
- reject(new Error('Upload failed'));
1663
- });
1664
- xhr.open('PUT', url);
1665
- xhr.setRequestHeader('Content-Type', file.type);
1666
- xhr.send(file);
1667
- });
1668
- }
1669
-
1670
- /**
1671
- * Link asset to an entity
1672
- */
1673
- async assetLink(fileId, app, entityType, entityId, visibility, webhookUrl) {
1674
- try {
1675
- const body = {
1676
- app,
1677
- entityType,
1678
- entityId
1679
- };
1680
- if (visibility) body.visibility = visibility;
1681
- if (webhookUrl) body.webhookUrl = webhookUrl;
1682
- return await this.makeRequest('POST', `/api/assets/${fileId}/links`, body, {
1683
- cache: false
1684
- });
1685
- } catch (error) {
1686
- throw this.handleError(error);
1687
- }
1688
- }
1689
-
1690
- /**
1691
- * Unlink asset from an entity
1692
- */
1693
- async assetUnlink(fileId, app, entityType, entityId) {
1694
- try {
1695
- return await this.makeRequest('DELETE', `/api/assets/${fileId}/links`, {
1696
- app,
1697
- entityType,
1698
- entityId
1699
- }, {
1700
- cache: false
1701
- });
1702
- } catch (error) {
1703
- throw this.handleError(error);
1704
- }
1705
- }
1706
-
1707
- /**
1708
- * Get asset metadata
1709
- */
1710
- async assetGet(fileId) {
1711
- try {
1712
- return await this.makeRequest('GET', `/api/assets/${fileId}`, undefined, {
1713
- cache: true,
1714
- cacheTTL: 5 * 60 * 1000 // 5 minutes cache
1715
- });
1716
- } catch (error) {
1717
- throw this.handleError(error);
1718
- }
1719
- }
1720
-
1721
- /**
1722
- * Get asset URL (CDN or signed URL)
1723
- */
1724
- async assetGetUrl(fileId, variant, expiresIn) {
1725
- try {
1726
- const params = {};
1727
- if (variant) params.variant = variant;
1728
- if (expiresIn) params.expiresIn = expiresIn;
1729
- return await this.makeRequest('GET', `/api/assets/${fileId}/url`, params, {
1730
- cache: true,
1731
- cacheTTL: 10 * 60 * 1000 // 10 minutes cache for URLs
1732
- });
1733
- } catch (error) {
1734
- throw this.handleError(error);
1735
- }
1736
- }
1737
-
1738
- /**
1739
- * Restore asset from trash
1740
- */
1741
- async assetRestore(fileId) {
1742
- try {
1743
- return await this.makeRequest('POST', `/api/assets/${fileId}/restore`, undefined, {
1744
- cache: false
1745
- });
1746
- } catch (error) {
1747
- throw this.handleError(error);
1748
- }
1749
- }
1750
-
1751
- /**
1752
- * Delete asset with optional force
1753
- */
1754
- async assetDelete(fileId, force = false) {
1755
- try {
1756
- const params = force ? {
1757
- force: 'true'
1758
- } : undefined;
1759
- return await this.makeRequest('DELETE', `/api/assets/${fileId}`, params, {
1760
- cache: false
1761
- });
1762
- } catch (error) {
1763
- throw this.handleError(error);
1764
- }
1765
- }
1766
-
1767
- /**
1768
- * Get list of available variants for an asset
1769
- */
1770
- async assetGetVariants(fileId) {
1771
- try {
1772
- const assetData = await this.assetGet(fileId);
1773
- return assetData.file?.variants || [];
1774
- } catch (error) {
1775
- throw this.handleError(error);
1776
- }
1777
- }
1778
-
1779
- /**
1780
- * Update asset visibility
1781
- * @param fileId - The file ID
1782
- * @param visibility - New visibility level ('private', 'public', or 'unlisted')
1783
- * @returns Updated asset information
1784
- */
1785
- async assetUpdateVisibility(fileId, visibility) {
1786
- try {
1787
- return await this.makeRequest('PATCH', `/api/assets/${fileId}/visibility`, {
1788
- visibility
1789
- }, {
1790
- cache: false
1791
- });
1792
- } catch (error) {
1793
- throw this.handleError(error);
1794
- }
1795
- }
1796
-
1797
- /**
1798
- * Helper: Upload and link avatar with automatic public visibility
1799
- * @param file - The avatar file
1800
- * @param userId - User ID to link to
1801
- * @param app - App name (defaults to 'profiles')
1802
- * @returns The uploaded and linked asset
1803
- */
1804
- async uploadAvatar(file, userId, app = 'profiles') {
1805
- try {
1806
- // Upload as public
1807
- const asset = await this.assetUpload(file, 'public');
1808
-
1809
- // Link to user profile as avatar
1810
- await this.assetLink(asset.file.id, app, 'avatar', userId, 'public');
1811
- return asset;
1812
- } catch (error) {
1813
- throw this.handleError(error);
1814
- }
1815
- }
1816
-
1817
- /**
1818
- * Helper: Upload and link profile banner with automatic public visibility
1819
- * @param file - The banner file
1820
- * @param userId - User ID to link to
1821
- * @param app - App name (defaults to 'profiles')
1822
- * @returns The uploaded and linked asset
1823
- */
1824
- async uploadProfileBanner(file, userId, app = 'profiles') {
1825
- try {
1826
- // Upload as public
1827
- const asset = await this.assetUpload(file, 'public');
1828
-
1829
- // Link to user profile as banner
1830
- await this.assetLink(asset.file.id, app, 'profile-banner', userId, 'public');
1831
- return asset;
1832
- } catch (error) {
1833
- throw this.handleError(error);
1834
- }
1835
- }
1836
-
1837
- // ============================================================================
1838
- // DEVELOPER API METHODS
1839
- // ============================================================================
1840
-
1841
- /**
1842
- * Get developer apps for the current user
1843
- */
1844
- async getDeveloperApps() {
1845
- try {
1846
- const res = await this.makeRequest('GET', '/api/developer/apps', undefined, {
1847
- cache: true,
1848
- cacheTTL: 2 * 60 * 1000 // 2 minutes cache
1849
- });
1850
- return res.apps || [];
1851
- } catch (error) {
1852
- throw this.handleError(error);
1853
- }
1854
- }
1855
-
1856
- /**
1857
- * Create a new developer app
1858
- */
1859
- async createDeveloperApp(data) {
1860
- try {
1861
- const res = await this.makeRequest('POST', '/api/developer/apps', data, {
1862
- cache: false
1863
- });
1864
- return res.app;
1865
- } catch (error) {
1866
- throw this.handleError(error);
1867
- }
1868
- }
1869
-
1870
- /**
1871
- * Get a specific developer app
1872
- */
1873
- async getDeveloperApp(appId) {
1874
- try {
1875
- const res = await this.makeRequest('GET', `/api/developer/apps/${appId}`, undefined, {
1876
- cache: true,
1877
- cacheTTL: 5 * 60 * 1000 // 5 minutes cache
1878
- });
1879
- return res.app;
1880
- } catch (error) {
1881
- throw this.handleError(error);
1882
- }
1883
- }
1884
-
1885
- /**
1886
- * Update a developer app
1887
- */
1888
- async updateDeveloperApp(appId, data) {
1889
- try {
1890
- const res = await this.makeRequest('PATCH', `/api/developer/apps/${appId}`, data, {
1891
- cache: false
1892
- });
1893
- return res.app;
1894
- } catch (error) {
1895
- throw this.handleError(error);
1896
- }
1897
- }
1898
-
1899
- /**
1900
- * Regenerate API secret for a developer app
1901
- */
1902
- async regenerateDeveloperAppSecret(appId) {
1903
- try {
1904
- return await this.makeRequest('POST', `/api/developer/apps/${appId}/regenerate-secret`, undefined, {
1905
- cache: false
1906
- });
1907
- } catch (error) {
1908
- throw this.handleError(error);
1909
- }
1910
- }
1911
-
1912
- /**
1913
- * Delete a developer app
1914
- */
1915
- async deleteDeveloperApp(appId) {
1916
- try {
1917
- return await this.makeRequest('DELETE', `/api/developer/apps/${appId}`, undefined, {
1918
- cache: false
1919
- });
1920
- } catch (error) {
1921
- throw this.handleError(error);
1922
- }
1923
- }
1924
-
1925
- // ============================================================================
1926
- // LOCATION METHODS
1927
- // ============================================================================
1928
-
1929
- /**
1930
- * Update user location
1931
- */
1932
- async updateLocation(latitude, longitude) {
1933
- try {
1934
- return await this.makeRequest('POST', '/api/location', {
1935
- latitude,
1936
- longitude
1937
- }, {
1938
- cache: false
1939
- });
1940
- } catch (error) {
1941
- throw this.handleError(error);
1942
- }
1943
- }
1944
-
1945
- /**
1946
- * Get nearby users
1947
- */
1948
- async getNearbyUsers(radius) {
1949
- try {
1950
- const params = radius ? {
1951
- radius
1952
- } : undefined;
1953
- return await this.makeRequest('GET', '/api/location/nearby', params, {
1954
- cache: false // Don't cache location data - always get fresh data
1955
- });
1956
- } catch (error) {
1957
- throw this.handleError(error);
1958
- }
1959
- }
1960
-
1961
- // ============================================================================
1962
- // ANALYTICS METHODS
1963
- // ============================================================================
1964
-
1965
- /**
1966
- * Track event
1967
- */
1968
- async trackEvent(eventName, properties) {
1969
- try {
1970
- await this.makeRequest('POST', '/api/analytics/events', {
1971
- event: eventName,
1972
- properties
1973
- }, {
1974
- cache: false,
1975
- retry: false
1976
- }); // Don't retry analytics events
1977
- } catch (error) {
1978
- throw this.handleError(error);
1979
- }
1980
- }
1981
-
1982
- /**
1983
- * Get analytics data
1984
- */
1985
- async getAnalytics(startDate, endDate) {
1986
- try {
1987
- const params = {};
1988
- if (startDate) params.startDate = startDate;
1989
- if (endDate) params.endDate = endDate;
1990
- return await this.makeRequest('GET', '/api/analytics', params, {
1991
- cache: true,
1992
- cacheTTL: 5 * 60 * 1000 // 5 minutes cache
1993
- });
1994
- } catch (error) {
1995
- throw this.handleError(error);
1996
- }
1997
- }
1998
-
1999
- // ============================================================================
2000
- // DEVICE METHODS
2001
- // ============================================================================
2002
-
2003
- /**
2004
- * Register device
2005
- */
2006
- async registerDevice(deviceData) {
2007
- try {
2008
- return await this.makeRequest('POST', '/api/devices', deviceData, {
2009
- cache: false
2010
- });
2011
- } catch (error) {
2012
- throw this.handleError(error);
2013
- }
2014
- }
2015
-
2016
- /**
2017
- * Get user devices
2018
- */
2019
- async getUserDevices() {
2020
- try {
2021
- return await this.makeRequest('GET', '/api/devices', undefined, {
2022
- cache: false // Don't cache device list - always get fresh data
2023
- });
2024
- } catch (error) {
2025
- throw this.handleError(error);
2026
- }
2027
- }
2028
-
2029
- /**
2030
- * Remove device
2031
- */
2032
- async removeDevice(deviceId) {
2033
- try {
2034
- await this.makeRequest('DELETE', `/api/devices/${deviceId}`, undefined, {
2035
- cache: false
2036
- });
2037
- } catch (error) {
2038
- throw this.handleError(error);
2039
- }
2040
- }
2041
-
2042
- /**
2043
- * Get device sessions
2044
- * Note: Not cached by default to ensure fresh data, but can be cached via makeRequest if needed
2045
- */
2046
- async getDeviceSessions(sessionId) {
2047
- try {
2048
- // Use makeRequest for consistent error handling and optional caching
2049
- // Cache disabled by default to ensure fresh session data
2050
- return await this.makeRequest('GET', `/api/session/device/sessions/${sessionId}`, undefined, {
2051
- cache: false,
2052
- // Don't cache sessions - always get fresh data
2053
- deduplicate: true // Deduplicate concurrent requests for same sessionId
2054
- });
2055
- } catch (error) {
2056
- throw this.handleError(error);
2057
- }
2058
- }
2059
-
2060
- /**
2061
- * Logout all device sessions
2062
- */
2063
- async logoutAllDeviceSessions(sessionId, deviceId, excludeCurrent) {
2064
- try {
2065
- const params = new URLSearchParams();
2066
- if (deviceId) params.append('deviceId', deviceId);
2067
- if (excludeCurrent) params.append('excludeCurrent', 'true');
2068
- const urlParams = {};
2069
- params.forEach((value, key) => {
2070
- urlParams[key] = value;
2071
- });
2072
- return await this.makeRequest('POST', `/api/session/device/logout-all/${sessionId}`, urlParams, {
2073
- cache: false
2074
- });
2075
- } catch (error) {
2076
- throw this.handleError(error);
2077
- }
2078
- }
2079
-
2080
- /**
2081
- * Update device name
2082
- */
2083
- async updateDeviceName(sessionId, deviceName) {
2084
- try {
2085
- return await this.makeRequest('PUT', `/api/session/device/name/${sessionId}`, {
2086
- deviceName
2087
- }, {
2088
- cache: false
2089
- });
2090
- } catch (error) {
2091
- throw this.handleError(error);
2092
- }
2093
- }
2094
-
2095
- // ============================================================================
2096
- // UTILITY METHODS
2097
- // ============================================================================
2098
-
2099
- /**
2100
- * Fetch link metadata
2101
- */
2102
- async fetchLinkMetadata(url) {
2103
- try {
2104
- return await this.makeRequest('GET', '/api/link-metadata', {
2105
- url
2106
- }, {
2107
- cache: true,
2108
- cacheTTL: 30 * 60 * 1000 // 30 minutes cache for link metadata
2109
- });
2110
- } catch (error) {
2111
- throw this.handleError(error);
2112
- }
2113
- }
2114
-
2115
- /**
2116
- * Simple Express.js authentication middleware
2117
- *
2118
- * Built-in authentication middleware that validates JWT tokens and adds user data to requests.
2119
- *
2120
- * @example
2121
- * ```typescript
2122
- * // Basic usage - just add it to your routes
2123
- * app.use('/api/protected', oxyServices.auth());
2124
- *
2125
- * // With debug logging
2126
- * app.use('/api/protected', oxyServices.auth({ debug: true }));
2127
- *
2128
- * // With custom error handling
2129
- * app.use('/api/protected', oxyServices.auth({
2130
- * onError: (error) => console.error('Auth failed:', error)
2131
- * }));
2132
- *
2133
- * // Load full user data
2134
- * app.use('/api/protected', oxyServices.auth({ loadUser: true }));
2135
- * ```
2136
- *
2137
- * @param options Optional configuration
2138
- * @param options.debug Enable debug logging (default: false)
2139
- * @param options.onError Custom error handler
2140
- * @param options.loadUser Load full user data (default: false for performance)
2141
- * @param options.session Use session-based validation (default: false)
2142
- * @returns Express middleware function
2143
- */
2144
- auth(options = {}) {
2145
- const {
2146
- debug = false,
2147
- onError,
2148
- loadUser = false,
2149
- session = false
2150
- } = options;
2151
-
2152
- // Return a synchronous middleware function
2153
- return (req, res, next) => {
2154
- try {
2155
- // Extract token from Authorization header
2156
- const authHeader = req.headers['authorization'];
2157
- const token = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null;
2158
- if (debug) {
2159
- console.log(`🔐 Auth: Processing ${req.method} ${req.path}`);
2160
- console.log(`🔐 Auth: Token present: ${!!token}`);
2161
- }
2162
- if (!token) {
2163
- const error = {
2164
- message: 'Access token required',
2165
- code: 'MISSING_TOKEN',
2166
- status: 401
2167
- };
2168
- if (debug) console.log(`❌ Auth: Missing token`);
2169
- if (onError) return onError(error);
2170
- return res.status(401).json(error);
2171
- }
2172
-
2173
- // Decode and validate token
2174
- let decoded;
2175
- try {
2176
- decoded = (0, _jwtDecode.jwtDecode)(token);
2177
- if (debug) {
2178
- console.log(`🔐 Auth: Token decoded, User ID: ${decoded.userId || decoded.id}`);
2179
- }
2180
- } catch (decodeError) {
2181
- const error = {
2182
- message: 'Invalid token format',
2183
- code: 'INVALID_TOKEN_FORMAT',
2184
- status: 403
2185
- };
2186
- if (debug) console.log(`❌ Auth: Token decode failed`);
2187
- if (onError) return onError(error);
2188
- return res.status(403).json(error);
2189
- }
2190
- const userId = decoded.userId || decoded.id;
2191
- if (!userId) {
2192
- const error = {
2193
- message: 'Token missing user ID',
2194
- code: 'INVALID_TOKEN_PAYLOAD',
2195
- status: 403
2196
- };
2197
- if (debug) console.log(`❌ Auth: Token missing user ID`);
2198
- if (onError) return onError(error);
2199
- return res.status(403).json(error);
2200
- }
2201
-
2202
- // Check token expiration
2203
- if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1000)) {
2204
- const error = {
2205
- message: 'Token expired',
2206
- code: 'TOKEN_EXPIRED',
2207
- status: 403
2208
- };
2209
- if (debug) console.log(`❌ Auth: Token expired`);
2210
- if (onError) return onError(error);
2211
- return res.status(403).json(error);
2212
- }
2213
-
2214
- // For now, skip session validation to keep it simple
2215
- // Session validation can be added later if needed
2216
-
2217
- // Set request properties immediately
2218
- req.userId = userId;
2219
- req.accessToken = token;
2220
- req.user = {
2221
- id: userId
2222
- };
2223
- if (debug) {
2224
- console.log(`✅ Auth: Authentication successful for user ${userId}`);
2225
- }
2226
- next();
2227
- } catch (error) {
2228
- const apiError = this.handleError(error);
2229
- if (debug) {
2230
- console.log(`❌ Auth: Unexpected error:`, apiError);
2231
- }
2232
- if (onError) return onError(apiError);
2233
- return res.status(apiError && apiError.status || 500).json(apiError);
2234
- }
2235
- };
121
+ // Export as a named class to avoid TypeScript issues with anonymous class types
122
+ class OxyServices extends OxyServicesComposed {
123
+ constructor(config) {
124
+ super(config);
2236
125
  }
2237
126
  }
2238
127
 
128
+ // Re-export error classes for convenience
129
+ exports.OxyServices = OxyServices;
2239
130
  /**
2240
131
  * Export the default Oxy Cloud URL (for backward compatibility)
2241
132
  */
2242
- exports.OxyServices = OxyServices;
2243
133
  const OXY_CLOUD_URL = exports.OXY_CLOUD_URL = 'https://cloud.oxy.so';
2244
134
 
2245
135
  /**