@oxyhq/services 5.13.1 → 5.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +71 -0
  2. package/lib/commonjs/core/HttpClient.js +238 -0
  3. package/lib/commonjs/core/HttpClient.js.map +1 -0
  4. package/lib/commonjs/core/OxyServices.js +538 -332
  5. package/lib/commonjs/core/OxyServices.js.map +1 -1
  6. package/lib/commonjs/core/RequestManager.js +199 -0
  7. package/lib/commonjs/core/RequestManager.js.map +1 -0
  8. package/lib/commonjs/core/index.js +38 -1
  9. package/lib/commonjs/core/index.js.map +1 -1
  10. package/lib/commonjs/index.js +36 -0
  11. package/lib/commonjs/index.js.map +1 -1
  12. package/lib/commonjs/ui/components/Avatar.js +94 -27
  13. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  14. package/lib/commonjs/ui/components/FollowButton.js +1 -0
  15. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  16. package/lib/commonjs/ui/components/internal/TextField.js +13 -8
  17. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  18. package/lib/commonjs/ui/context/OxyContext.js +183 -224
  19. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/useSessionSocket.js +80 -22
  21. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  22. package/lib/commonjs/ui/index.js +4 -1
  23. package/lib/commonjs/ui/index.js.map +1 -1
  24. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +32 -2
  25. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  26. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +101 -59
  27. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  28. package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -2
  29. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  30. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +75 -117
  31. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
  32. package/lib/commonjs/ui/screens/SignInScreen.js +0 -11
  33. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  34. package/lib/commonjs/ui/screens/SignUpScreen.js +14 -16
  35. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  36. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +50 -18
  37. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  38. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +10 -10
  39. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  40. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +16 -26
  41. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  42. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +104 -212
  43. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  44. package/lib/commonjs/ui/stores/accountStore.js +237 -0
  45. package/lib/commonjs/ui/stores/accountStore.js.map +1 -0
  46. package/lib/commonjs/ui/stores/authStore.js +2 -1
  47. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  48. package/lib/commonjs/ui/styles/authStyles.js +14 -7
  49. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  50. package/lib/commonjs/utils/asyncUtils.js +9 -22
  51. package/lib/commonjs/utils/asyncUtils.js.map +1 -1
  52. package/lib/commonjs/utils/cache.js +259 -0
  53. package/lib/commonjs/utils/cache.js.map +1 -0
  54. package/lib/commonjs/utils/index.js +99 -0
  55. package/lib/commonjs/utils/index.js.map +1 -1
  56. package/lib/commonjs/utils/languageUtils.js +159 -0
  57. package/lib/commonjs/utils/languageUtils.js.map +1 -0
  58. package/lib/commonjs/utils/requestUtils.js +217 -0
  59. package/lib/commonjs/utils/requestUtils.js.map +1 -0
  60. package/lib/commonjs/utils/sessionUtils.js +191 -0
  61. package/lib/commonjs/utils/sessionUtils.js.map +1 -0
  62. package/lib/module/core/HttpClient.js +232 -0
  63. package/lib/module/core/HttpClient.js.map +1 -0
  64. package/lib/module/core/OxyServices.js +536 -326
  65. package/lib/module/core/OxyServices.js.map +1 -1
  66. package/lib/module/core/RequestManager.js +194 -0
  67. package/lib/module/core/RequestManager.js.map +1 -0
  68. package/lib/module/core/index.js +2 -0
  69. package/lib/module/core/index.js.map +1 -1
  70. package/lib/module/index.js +2 -0
  71. package/lib/module/index.js.map +1 -1
  72. package/lib/module/ui/components/Avatar.js +94 -27
  73. package/lib/module/ui/components/Avatar.js.map +1 -1
  74. package/lib/module/ui/components/FollowButton.js +1 -0
  75. package/lib/module/ui/components/FollowButton.js.map +1 -1
  76. package/lib/module/ui/components/internal/TextField.js +13 -8
  77. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  78. package/lib/module/ui/context/OxyContext.js +182 -223
  79. package/lib/module/ui/context/OxyContext.js.map +1 -1
  80. package/lib/module/ui/hooks/useSessionSocket.js +80 -22
  81. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  82. package/lib/module/ui/index.js +4 -2
  83. package/lib/module/ui/index.js.map +1 -1
  84. package/lib/module/ui/screens/AccountSettingsScreen.js +33 -2
  85. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  86. package/lib/module/ui/screens/AccountSwitcherScreen.js +102 -60
  87. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  88. package/lib/module/ui/screens/FileManagementScreen.js +3 -2
  89. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  90. package/lib/module/ui/screens/LanguageSelectorScreen.js +73 -117
  91. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
  92. package/lib/module/ui/screens/SignInScreen.js +0 -11
  93. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  94. package/lib/module/ui/screens/SignUpScreen.js +14 -16
  95. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  96. package/lib/module/ui/screens/WelcomeNewUserScreen.js +50 -18
  97. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  98. package/lib/module/ui/screens/internal/SignInPasswordStep.js +10 -10
  99. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  100. package/lib/module/ui/screens/steps/SignInPasswordStep.js +16 -26
  101. package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  102. package/lib/module/ui/screens/steps/SignInUsernameStep.js +105 -214
  103. package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  104. package/lib/module/ui/stores/accountStore.js +229 -0
  105. package/lib/module/ui/stores/accountStore.js.map +1 -0
  106. package/lib/module/ui/stores/authStore.js +2 -1
  107. package/lib/module/ui/stores/authStore.js.map +1 -1
  108. package/lib/module/ui/styles/authStyles.js +14 -7
  109. package/lib/module/ui/styles/authStyles.js.map +1 -1
  110. package/lib/module/utils/asyncUtils.js +10 -22
  111. package/lib/module/utils/asyncUtils.js.map +1 -1
  112. package/lib/module/utils/cache.js +250 -0
  113. package/lib/module/utils/cache.js.map +1 -0
  114. package/lib/module/utils/index.js +7 -0
  115. package/lib/module/utils/index.js.map +1 -1
  116. package/lib/module/utils/languageUtils.js +151 -0
  117. package/lib/module/utils/languageUtils.js.map +1 -0
  118. package/lib/module/utils/requestUtils.js +210 -0
  119. package/lib/module/utils/requestUtils.js.map +1 -0
  120. package/lib/module/utils/sessionUtils.js +180 -0
  121. package/lib/module/utils/sessionUtils.js.map +1 -0
  122. package/lib/typescript/core/HttpClient.d.ts +64 -0
  123. package/lib/typescript/core/HttpClient.d.ts.map +1 -0
  124. package/lib/typescript/core/OxyServices.d.ts +88 -71
  125. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  126. package/lib/typescript/core/RequestManager.d.ts +67 -0
  127. package/lib/typescript/core/RequestManager.d.ts.map +1 -0
  128. package/lib/typescript/core/index.d.ts +2 -0
  129. package/lib/typescript/core/index.d.ts.map +1 -1
  130. package/lib/typescript/index.d.ts +2 -0
  131. package/lib/typescript/index.d.ts.map +1 -1
  132. package/lib/typescript/models/interfaces.d.ts +15 -0
  133. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  134. package/lib/typescript/models/session.d.ts +1 -0
  135. package/lib/typescript/models/session.d.ts.map +1 -1
  136. package/lib/typescript/ui/components/Avatar.d.ts +6 -7
  137. package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
  138. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  139. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  140. package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
  141. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  142. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  143. package/lib/typescript/ui/index.d.ts +2 -2
  144. package/lib/typescript/ui/index.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  146. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +3 -3
  148. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
  149. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
  154. package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
  155. package/lib/typescript/ui/stores/accountStore.d.ts +34 -0
  156. package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -0
  157. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  158. package/lib/typescript/ui/styles/authStyles.d.ts +18 -2
  159. package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
  160. package/lib/typescript/utils/asyncUtils.d.ts +2 -0
  161. package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
  162. package/lib/typescript/utils/cache.d.ts +128 -0
  163. package/lib/typescript/utils/cache.d.ts.map +1 -0
  164. package/lib/typescript/utils/index.d.ts +4 -0
  165. package/lib/typescript/utils/index.d.ts.map +1 -1
  166. package/lib/typescript/utils/languageUtils.d.ts +38 -0
  167. package/lib/typescript/utils/languageUtils.d.ts.map +1 -0
  168. package/lib/typescript/utils/requestUtils.d.ts +122 -0
  169. package/lib/typescript/utils/requestUtils.d.ts.map +1 -0
  170. package/lib/typescript/utils/sessionUtils.d.ts +55 -0
  171. package/lib/typescript/utils/sessionUtils.d.ts.map +1 -0
  172. package/package.json +1 -1
  173. package/src/core/HttpClient.ts +277 -0
  174. package/src/core/OxyServices.ts +466 -351
  175. package/src/core/RequestManager.ts +240 -0
  176. package/src/core/index.ts +10 -0
  177. package/src/index.ts +10 -0
  178. package/src/models/interfaces.ts +19 -0
  179. package/src/models/session.ts +1 -1
  180. package/src/ui/components/Avatar.tsx +151 -35
  181. package/src/ui/components/FollowButton.tsx +1 -0
  182. package/src/ui/components/internal/TextField.tsx +7 -6
  183. package/src/ui/context/OxyContext.tsx +213 -217
  184. package/src/ui/hooks/useSessionSocket.ts +72 -18
  185. package/src/ui/index.ts +4 -1
  186. package/src/ui/screens/AccountSettingsScreen.tsx +34 -2
  187. package/src/ui/screens/AccountSwitcherScreen.tsx +102 -68
  188. package/src/ui/screens/FileManagementScreen.tsx +1 -1
  189. package/src/ui/screens/LanguageSelectorScreen.tsx +86 -143
  190. package/src/ui/screens/SignInScreen.tsx +0 -7
  191. package/src/ui/screens/SignUpScreen.tsx +14 -15
  192. package/src/ui/screens/WelcomeNewUserScreen.tsx +52 -15
  193. package/src/ui/screens/internal/SignInPasswordStep.tsx +4 -6
  194. package/src/ui/screens/steps/SignInPasswordStep.tsx +4 -8
  195. package/src/ui/screens/steps/SignInUsernameStep.tsx +110 -256
  196. package/src/ui/stores/accountStore.ts +285 -0
  197. package/src/ui/stores/authStore.ts +2 -1
  198. package/src/ui/styles/authStyles.ts +14 -7
  199. package/src/utils/asyncUtils.ts +10 -24
  200. package/src/utils/cache.ts +264 -0
  201. package/src/utils/index.ts +19 -0
  202. package/src/utils/languageUtils.ts +174 -0
  203. package/src/utils/requestUtils.ts +234 -0
  204. package/src/utils/sessionUtils.ts +206 -0
@@ -56,7 +56,6 @@
56
56
  *
57
57
  * See method JSDoc for more details and options.
58
58
  */
59
- import axios, { type AxiosInstance, type InternalAxiosRequestConfig } from 'axios';
60
59
  import { jwtDecode } from 'jwt-decode';
61
60
  import type {
62
61
  OxyConfig as OxyConfigBase,
@@ -67,6 +66,8 @@ import type {
67
66
  AssetUrlResponse,
68
67
  AssetVariant
69
68
  } from '../models/interfaces';
69
+ import { normalizeLanguageCode, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils';
70
+ import type { LanguageMetadata } from '../utils/languageUtils';
70
71
  /**
71
72
  * OxyConfig - Configuration for OxyServices
72
73
  * @property baseURL - The Oxy API base URL (e.g., https://api.oxy.so)
@@ -78,6 +79,8 @@ export interface OxyConfig extends OxyConfigBase {
78
79
  import type { SessionLoginResponse } from '../models/session';
79
80
  import { handleHttpError } from '../utils/errorUtils';
80
81
  import { buildSearchParams, buildPaginationParams, type PaginationParams } from '../utils/apiUtils';
82
+ import { HttpClient } from './HttpClient';
83
+ import { RequestManager, type RequestOptions } from './RequestManager';
81
84
 
82
85
  interface JwtPayload {
83
86
  exp?: number;
@@ -117,55 +120,18 @@ export class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
117
120
  * OxyServices - Unified client library for interacting with the Oxy API
118
121
  *
119
122
  * This class provides all API functionality in one simple, easy-to-use interface.
120
- * No need to manage multiple service instances - everything is available directly.
123
+ * Architecture:
124
+ * - HttpClient: Handles HTTP communication and authentication
125
+ * - RequestManager: Handles caching, deduplication, queuing, and retry
126
+ * - OxyServices: Provides high-level API methods
121
127
  */
122
- // Centralized token store
123
- class TokenStore {
124
- private static instance: TokenStore;
125
- private accessToken: string | null = null;
126
- private refreshToken: string | null = null;
127
-
128
- private constructor() {}
129
-
130
- static getInstance(): TokenStore {
131
- if (!TokenStore.instance) {
132
- TokenStore.instance = new TokenStore();
133
- }
134
- return TokenStore.instance;
135
- }
136
-
137
- setTokens(accessToken: string, refreshToken = ''): void {
138
- this.accessToken = accessToken;
139
- this.refreshToken = refreshToken;
140
- }
141
-
142
- getAccessToken(): string | null {
143
- return this.accessToken;
144
- }
145
-
146
- getRefreshToken(): string | null {
147
- return this.refreshToken;
148
- }
149
-
150
- clearTokens(): void {
151
- this.accessToken = null;
152
- this.refreshToken = null;
153
- }
154
-
155
- hasAccessToken(): boolean {
156
- return !!this.accessToken;
157
- }
158
- }
159
-
160
128
  export class OxyServices {
161
- protected client: AxiosInstance;
162
- private tokenStore: TokenStore;
129
+ private httpClient: HttpClient;
130
+ private requestManager: RequestManager;
163
131
  private cloudURL: string;
132
+ private config: OxyConfig;
133
+
164
134
 
165
- /**
166
- * Creates a new instance of the OxyServices client
167
- * @param config - Configuration for the client
168
- */
169
135
  /**
170
136
  * Creates a new instance of the OxyServices client
171
137
  * @param config - Configuration for the client
@@ -173,99 +139,33 @@ export class OxyServices {
173
139
  * config.cloudURL: Oxy Cloud URL (e.g., https://cloud.oxy.so)
174
140
  */
175
141
  constructor(config: OxyConfig) {
176
- this.client = axios.create({
177
- baseURL: config.baseURL,
178
- timeout: 5000 // 5 second timeout
179
- });
142
+ this.config = config;
180
143
  this.cloudURL = config.cloudURL || OXY_CLOUD_URL;
181
- this.tokenStore = TokenStore.getInstance();
182
- this.setupInterceptors();
144
+
145
+ // Initialize HTTP client (handles authentication and interceptors)
146
+ this.httpClient = new HttpClient(config);
147
+
148
+ // Initialize request manager (handles caching, deduplication, queuing, retry)
149
+ this.requestManager = new RequestManager(this.httpClient, config);
183
150
  }
184
151
 
185
152
  // Test-only utility to reset global tokens between jest tests
186
153
  static __resetTokensForTests(): void {
187
- try {
188
- TokenStore.getInstance().clearTokens();
189
- } catch {}
154
+ HttpClient.__resetTokensForTests();
190
155
  }
191
156
 
157
+
192
158
  /**
193
- * Setup axios interceptors for authentication and error handling
159
+ * Make a request with all performance optimizations
160
+ * This is the main method for all API calls - ensures authentication and performance features
194
161
  */
195
- private setupInterceptors(): void {
196
- // Request interceptor for adding auth header and handling token refresh
197
- this.client.interceptors.request.use(
198
- async (req: InternalAxiosRequestConfig) => {
199
- console.log('🔍 Interceptor - URL:', req.url);
200
- console.log('🔍 Interceptor - Has token:', this.tokenStore.hasAccessToken());
201
-
202
- const accessToken = this.tokenStore.getAccessToken();
203
- if (!accessToken) {
204
- console.log('❌ Interceptor - No token available');
205
- return req;
206
- }
207
-
208
- try {
209
- console.log('✅ Interceptor - Adding Authorization header');
210
-
211
- const decoded = jwtDecode<JwtPayload>(accessToken);
212
- const currentTime = Math.floor(Date.now() / 1000);
213
-
214
- // If token expires in less than 60 seconds, refresh it
215
- if (decoded.exp && decoded.exp - currentTime < 60) {
216
- // For session-based tokens, get a new token from the session
217
- if (decoded.sessionId) {
218
- try {
219
- // Create a new axios instance to avoid interceptor recursion
220
- const refreshClient = axios.create({
221
- baseURL: this.client.defaults.baseURL,
222
- timeout: this.client.defaults.timeout
223
- });
224
- const res = await refreshClient.get(`/api/session/token/${decoded.sessionId}`);
225
- this.tokenStore.setTokens(res.data.accessToken);
226
- req.headers.Authorization = `Bearer ${res.data.accessToken}`;
227
- console.log('✅ Interceptor - Token refreshed and Authorization header set');
228
- } catch (refreshError) {
229
- // If refresh fails, use current token anyway
230
- req.headers.Authorization = `Bearer ${accessToken}`;
231
- console.log('❌ Interceptor - Token refresh failed, using current token');
232
- }
233
- } else {
234
- // No session ID, use current token
235
- req.headers.Authorization = `Bearer ${accessToken}`;
236
- console.log('✅ Interceptor - No session ID, using current token');
237
- }
238
- } else {
239
- // Add authorization header with current token
240
- req.headers.Authorization = `Bearer ${accessToken}`;
241
- console.log('✅ Interceptor - Authorization header set with current token');
242
- }
243
- } catch (error) {
244
- console.log('❌ Interceptor - Error processing token:', error);
245
- // Even if there's an error, still try to use the token
246
- req.headers.Authorization = `Bearer ${accessToken}`;
247
- console.log('⚠️ Interceptor - Using token despite error');
248
- }
249
-
250
- return req;
251
- },
252
- (error) => {
253
- console.log('❌ Interceptor - Request error:', error);
254
- return Promise.reject(error);
255
- }
256
- );
257
-
258
- // Response interceptor for handling auth errors
259
- this.client.interceptors.response.use(
260
- (response) => response,
261
- (error) => {
262
- if (error.response?.status === 401) {
263
- console.log('❌ Response interceptor - 401 Unauthorized, clearing tokens');
264
- this.clearTokens();
265
- }
266
- return Promise.reject(error);
267
- }
268
- );
162
+ private async makeRequest<T>(
163
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
164
+ url: string,
165
+ data?: any,
166
+ options: RequestOptions = {}
167
+ ): Promise<T> {
168
+ return this.requestManager.request<T>(method, url, data, options);
269
169
  }
270
170
 
271
171
  // ============================================================================
@@ -276,7 +176,35 @@ export class OxyServices {
276
176
  * Get the configured Oxy API base URL
277
177
  */
278
178
  public getBaseURL(): string {
279
- return this.client.defaults.baseURL || '';
179
+ return this.httpClient.getBaseURL();
180
+ }
181
+
182
+ /**
183
+ * Get performance metrics
184
+ */
185
+ public getMetrics() {
186
+ return this.requestManager.getMetrics();
187
+ }
188
+
189
+ /**
190
+ * Clear request cache
191
+ */
192
+ public clearCache(): void {
193
+ this.requestManager.clearCache();
194
+ }
195
+
196
+ /**
197
+ * Clear specific cache entry
198
+ */
199
+ public clearCacheEntry(key: string): void {
200
+ this.requestManager.clearCacheEntry(key);
201
+ }
202
+
203
+ /**
204
+ * Get cache statistics
205
+ */
206
+ public getCacheStats() {
207
+ return this.requestManager.getCacheStats();
280
208
  }
281
209
 
282
210
  /**
@@ -286,25 +214,33 @@ export class OxyServices {
286
214
  return this.cloudURL;
287
215
  }
288
216
 
217
+ /**
218
+ * Get the underlying HTTP client instance
219
+ * Useful for advanced use cases that need direct access to the HttpClient
220
+ */
221
+ public getClient(): HttpClient {
222
+ return this.httpClient;
223
+ }
224
+
289
225
  /**
290
226
  * Set authentication tokens
291
227
  */
292
228
  public setTokens(accessToken: string, refreshToken = ''): void {
293
- this.tokenStore.setTokens(accessToken, refreshToken);
229
+ this.httpClient.setTokens(accessToken, refreshToken);
294
230
  }
295
231
 
296
232
  /**
297
233
  * Clear stored authentication tokens
298
234
  */
299
235
  public clearTokens(): void {
300
- this.tokenStore.clearTokens();
236
+ this.httpClient.clearTokens();
301
237
  }
302
238
 
303
239
  /**
304
240
  * Get the current user ID from the access token
305
241
  */
306
242
  public getCurrentUserId(): string | null {
307
- const accessToken = this.tokenStore.getAccessToken();
243
+ const accessToken = this.httpClient.getAccessToken();
308
244
  if (!accessToken) {
309
245
  return null;
310
246
  }
@@ -321,21 +257,21 @@ export class OxyServices {
321
257
  * Check if the client has a valid access token
322
258
  */
323
259
  private hasAccessToken(): boolean {
324
- return this.tokenStore.hasAccessToken();
260
+ return this.httpClient.hasAccessToken();
325
261
  }
326
262
 
327
263
  /**
328
264
  * Check if the client has a valid access token (public method)
329
265
  */
330
266
  public hasValidToken(): boolean {
331
- return this.tokenStore.hasAccessToken();
267
+ return this.httpClient.hasAccessToken();
332
268
  }
333
269
 
334
270
  /**
335
271
  * Get the raw access token (for constructing anchor URLs when needed)
336
272
  */
337
273
  public getAccessToken(): string | null {
338
- return this.tokenStore.getAccessToken();
274
+ return this.httpClient.getAccessToken();
339
275
  }
340
276
 
341
277
  /**
@@ -354,7 +290,7 @@ export class OxyServices {
354
290
  const checkInterval = 100; // Check every 100ms
355
291
 
356
292
  while (Date.now() - startTime < timeoutMs) {
357
- if (this.tokenStore.hasAccessToken()) {
293
+ if (this.httpClient.hasAccessToken()) {
358
294
  return true;
359
295
  }
360
296
  await new Promise(resolve => setTimeout(resolve, checkInterval));
@@ -385,17 +321,14 @@ export class OxyServices {
385
321
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
386
322
  try {
387
323
  // First attempt: check if we have a token
388
- if (!this.tokenStore.hasAccessToken()) {
324
+ if (!this.httpClient.hasAccessToken()) {
389
325
  if (attempt === 0) {
390
326
  // On first attempt, wait briefly for authentication to complete
391
- console.log(`🔄 ${operationName} - Waiting for authentication...`);
392
327
  const authReady = await this.waitForAuthentication(authTimeoutMs);
393
328
 
394
329
  if (!authReady) {
395
330
  throw new OxyAuthenticationTimeoutError(operationName, authTimeoutMs);
396
331
  }
397
-
398
- console.log(`✅ ${operationName} - Authentication ready, proceeding...`);
399
332
  } else {
400
333
  // On retry attempts, fail immediately if no token
401
334
  throw new OxyAuthenticationError(
@@ -416,7 +349,6 @@ export class OxyServices {
416
349
  error instanceof OxyAuthenticationError;
417
350
 
418
351
  if (isAuthError && !isLastAttempt && !(error instanceof OxyAuthenticationTimeoutError)) {
419
- console.log(`🔄 ${operationName} - Auth error on attempt ${attempt + 1}, retrying in ${retryDelay}ms...`);
420
352
  await new Promise(resolve => setTimeout(resolve, retryDelay));
421
353
  continue;
422
354
  }
@@ -442,19 +374,16 @@ export class OxyServices {
442
374
  }
443
375
 
444
376
  try {
445
- const res = await this.client.get('/api/auth/validate');
446
- return res.data.valid === true;
377
+ const res = await this.makeRequest<{ valid: boolean }>('GET', '/api/auth/validate', undefined, {
378
+ cache: false,
379
+ retry: false,
380
+ });
381
+ return res.valid === true;
447
382
  } catch (error) {
448
383
  return false;
449
384
  }
450
385
  }
451
386
 
452
- /**
453
- * Get the HTTP client instance (public for external use)
454
- */
455
- public getClient(): AxiosInstance {
456
- return this.client;
457
- }
458
387
 
459
388
  /**
460
389
  * Centralized error handling
@@ -478,8 +407,7 @@ export class OxyServices {
478
407
  [key: string]: any
479
408
  }> {
480
409
  try {
481
- const res = await this.client.get('/health');
482
- return res.data;
410
+ return await this.makeRequest('GET', '/health', undefined, { cache: false });
483
411
  } catch (error) {
484
412
  throw this.handleError(error);
485
413
  }
@@ -494,15 +422,15 @@ export class OxyServices {
494
422
  */
495
423
  async signUp(username: string, email: string, password: string): Promise<{ message: string; token: string; user: User }> {
496
424
  try {
497
- const res = await this.client.post('/api/auth/signup', {
425
+ const res = await this.makeRequest<{ message: string; token: string; user: User }>('POST', '/api/auth/signup', {
498
426
  username,
499
427
  email,
500
428
  password
501
- });
502
- if (!res || !res.data || (typeof res.data === 'object' && Object.keys(res.data).length === 0)) {
429
+ }, { cache: false });
430
+ if (!res || (typeof res === 'object' && Object.keys(res).length === 0)) {
503
431
  throw new OxyAuthenticationError('Sign up failed', 'SIGNUP_FAILED', 400);
504
432
  }
505
- return res.data;
433
+ return res;
506
434
  } catch (error) {
507
435
  throw this.handleError(error);
508
436
  }
@@ -513,8 +441,7 @@ export class OxyServices {
513
441
  */
514
442
  async requestRecovery(identifier: string): Promise<{ delivery?: string; destination?: string }> {
515
443
  try {
516
- const res = await this.client.post('/api/auth/recover/request', { identifier });
517
- return res.data;
444
+ return await this.makeRequest('POST', '/api/auth/recover/request', { identifier }, { cache: false });
518
445
  } catch (error: any) {
519
446
  throw this.handleError(error);
520
447
  }
@@ -525,8 +452,7 @@ export class OxyServices {
525
452
  */
526
453
  async verifyRecoveryCode(identifier: string, code: string): Promise<{ verified: boolean }> {
527
454
  try {
528
- const res = await this.client.post('/api/auth/recover/verify', { identifier, code });
529
- return res.data;
455
+ return await this.makeRequest('POST', '/api/auth/recover/verify', { identifier, code }, { cache: false });
530
456
  } catch (error: any) {
531
457
  throw this.handleError(error);
532
458
  }
@@ -537,8 +463,7 @@ export class OxyServices {
537
463
  */
538
464
  async resetPassword(identifier: string, code: string, newPassword: string): Promise<{ success: boolean }> {
539
465
  try {
540
- const res = await this.client.post('/api/auth/recover/reset', { identifier, code, newPassword });
541
- return res.data;
466
+ return await this.makeRequest('POST', '/api/auth/recover/reset', { identifier, code, newPassword }, { cache: false });
542
467
  } catch (error: any) {
543
468
  throw this.handleError(error);
544
469
  }
@@ -549,8 +474,7 @@ export class OxyServices {
549
474
  */
550
475
  async resetPasswordWithTotp(identifier: string, code: string, newPassword: string): Promise<{ success: boolean }> {
551
476
  try {
552
- const res = await this.client.post('/api/auth/recover/totp/reset', { identifier, code, newPassword });
553
- return res.data;
477
+ return await this.makeRequest('POST', '/api/auth/recover/totp/reset', { identifier, code, newPassword }, { cache: false });
554
478
  } catch (error: any) {
555
479
  throw this.handleError(error);
556
480
  }
@@ -558,8 +482,7 @@ export class OxyServices {
558
482
 
559
483
  async resetPasswordWithBackupCode(identifier: string, backupCode: string, newPassword: string): Promise<{ success: boolean }> {
560
484
  try {
561
- const res = await this.client.post('/api/auth/recover/backup/reset', { identifier, backupCode, newPassword });
562
- return res.data;
485
+ return await this.makeRequest('POST', '/api/auth/recover/backup/reset', { identifier, backupCode, newPassword }, { cache: false });
563
486
  } catch (error: any) {
564
487
  throw this.handleError(error);
565
488
  }
@@ -567,8 +490,7 @@ export class OxyServices {
567
490
 
568
491
  async resetPasswordWithRecoveryKey(identifier: string, recoveryKey: string, newPassword: string): Promise<{ success: boolean; nextRecoveryKey?: string }> {
569
492
  try {
570
- const res = await this.client.post('/api/auth/recover/recovery-key/reset', { identifier, recoveryKey, newPassword });
571
- return res.data;
493
+ return await this.makeRequest('POST', '/api/auth/recover/recovery-key/reset', { identifier, recoveryKey, newPassword }, { cache: false });
572
494
  } catch (error: any) {
573
495
  throw this.handleError(error);
574
496
  }
@@ -584,13 +506,12 @@ export class OxyServices {
584
506
  deviceFingerprint?: any
585
507
  ): Promise<SessionLoginResponse | { mfaRequired: true; mfaToken: string; expiresAt: string }> {
586
508
  try {
587
- const res = await this.client.post('/api/auth/login', {
509
+ return await this.makeRequest<SessionLoginResponse | { mfaRequired: true; mfaToken: string; expiresAt: string }>('POST', '/api/auth/login', {
588
510
  username,
589
511
  password,
590
512
  deviceName,
591
513
  deviceFingerprint
592
- });
593
- return res.data;
514
+ }, { cache: false });
594
515
  } catch (error) {
595
516
  throw this.handleError(error);
596
517
  }
@@ -601,8 +522,7 @@ export class OxyServices {
601
522
  */
602
523
  async verifyTotpLogin(mfaToken: string, code: string): Promise<SessionLoginResponse> {
603
524
  try {
604
- const res = await this.client.post('/api/auth/totp/verify-login', { mfaToken, code });
605
- return res.data;
525
+ return await this.makeRequest<SessionLoginResponse>('POST', '/api/auth/totp/verify-login', { mfaToken, code }, { cache: false });
606
526
  } catch (error) {
607
527
  throw this.handleError(error);
608
528
  }
@@ -613,8 +533,38 @@ export class OxyServices {
613
533
  */
614
534
  async getUserBySession(sessionId: string): Promise<User> {
615
535
  try {
616
- const res = await this.client.get(`/api/session/user/${sessionId}`);
617
- return res.data;
536
+ return await this.makeRequest<User>('GET', `/api/session/user/${sessionId}`, undefined, {
537
+ cache: true,
538
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache for user data
539
+ });
540
+ } catch (error) {
541
+ throw this.handleError(error);
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Batch get multiple user profiles by session IDs (optimized for account switching)
547
+ * Returns array of { sessionId, user } objects
548
+ */
549
+ async getUsersBySessions(sessionIds: string[]): Promise<Array<{ sessionId: string; user: User | null }>> {
550
+ try {
551
+ if (!Array.isArray(sessionIds) || sessionIds.length === 0) {
552
+ return [];
553
+ }
554
+
555
+ // Deduplicate and sort sessionIds for consistent cache keys
556
+ const uniqueSessionIds = Array.from(new Set(sessionIds)).sort();
557
+
558
+ return await this.makeRequest<Array<{ sessionId: string; user: User | null }>>(
559
+ 'POST',
560
+ '/api/session/users/batch',
561
+ { sessionIds: uniqueSessionIds },
562
+ {
563
+ cache: true,
564
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
565
+ deduplicate: true, // Important for batch requests
566
+ }
567
+ );
618
568
  } catch (error) {
619
569
  throw this.handleError(error);
620
570
  }
@@ -625,19 +575,16 @@ export class OxyServices {
625
575
  */
626
576
  async getTokenBySession(sessionId: string): Promise<{ accessToken: string; expiresAt: string }> {
627
577
  try {
628
- console.log('🔑 getTokenBySession - Fetching token for session:', sessionId);
629
- const res = await this.client.get(`/api/session/token/${sessionId}`);
630
- const { accessToken } = res.data;
631
-
632
- console.log('🔑 getTokenBySession - Token received:', !!accessToken);
578
+ const res = await this.makeRequest<{ accessToken: string; expiresAt: string }>('GET', `/api/session/token/${sessionId}`, undefined, {
579
+ cache: false,
580
+ retry: false,
581
+ });
633
582
 
634
583
  // Set the token in the centralized token store
635
- this.setTokens(accessToken);
636
- console.log('🔑 getTokenBySession - Token set in store');
584
+ this.setTokens(res.accessToken);
637
585
 
638
- return res.data;
586
+ return res;
639
587
  } catch (error) {
640
- console.log('❌ getTokenBySession - Error:', error);
641
588
  throw this.handleError(error);
642
589
  }
643
590
  }
@@ -647,8 +594,9 @@ export class OxyServices {
647
594
  */
648
595
  async getSessionsBySessionId(sessionId: string): Promise<any[]> {
649
596
  try {
650
- const res = await this.client.get(`/api/session/sessions/${sessionId}`);
651
- return res.data;
597
+ return await this.makeRequest('GET', `/api/session/sessions/${sessionId}`, undefined, {
598
+ cache: false,
599
+ });
652
600
  } catch (error) {
653
601
  throw this.handleError(error);
654
602
  }
@@ -663,7 +611,7 @@ export class OxyServices {
663
611
  ? `/api/session/logout/${sessionId}/${targetSessionId}`
664
612
  : `/api/session/logout/${sessionId}`;
665
613
 
666
- await this.client.post(url);
614
+ await this.makeRequest('POST', url, undefined, { cache: false });
667
615
  } catch (error) {
668
616
  throw this.handleError(error);
669
617
  }
@@ -674,7 +622,7 @@ export class OxyServices {
674
622
  */
675
623
  async logoutAllSessions(sessionId: string): Promise<void> {
676
624
  try {
677
- await this.client.post(`/api/session/logout-all/${sessionId}`);
625
+ await this.makeRequest('POST', `/api/session/logout-all/${sessionId}`, undefined, { cache: false });
678
626
  } catch (error) {
679
627
  throw this.handleError(error);
680
628
  }
@@ -706,9 +654,11 @@ export class OxyServices {
706
654
  params.append('useHeaderValidation', 'true');
707
655
  }
708
656
 
709
- const url = `/api/session/validate/${sessionId}?${params.toString()}`;
710
- const res = await this.client.get(url);
711
- return res.data;
657
+ const url = `/api/session/validate/${sessionId}`;
658
+ const urlParams: any = {};
659
+ if (options.deviceFingerprint) urlParams.deviceFingerprint = options.deviceFingerprint;
660
+ if (options.useHeaderValidation) urlParams.useHeaderValidation = 'true';
661
+ return await this.makeRequest('GET', url, urlParams, { cache: false });
712
662
  } catch (error) {
713
663
  throw this.handleError(error);
714
664
  }
@@ -719,8 +669,7 @@ export class OxyServices {
719
669
  */
720
670
  async checkUsernameAvailability(username: string): Promise<{ available: boolean; message: string }> {
721
671
  try {
722
- const res = await this.client.get(`/api/auth/check-username/${username}`);
723
- return res.data;
672
+ return await this.makeRequest('GET', `/api/auth/check-username/${username}`, undefined, { cache: false });
724
673
  } catch (error) {
725
674
  throw this.handleError(error);
726
675
  }
@@ -731,8 +680,7 @@ export class OxyServices {
731
680
  */
732
681
  async checkEmailAvailability(email: string): Promise<{ available: boolean; message: string }> {
733
682
  try {
734
- const res = await this.client.get(`/api/auth/check-email/${email}`);
735
- return res.data;
683
+ return await this.makeRequest('GET', `/api/auth/check-email/${email}`, undefined, { cache: false });
736
684
  } catch (error) {
737
685
  throw this.handleError(error);
738
686
  }
@@ -747,8 +695,10 @@ export class OxyServices {
747
695
  */
748
696
  async getProfileByUsername(username: string): Promise<User> {
749
697
  try {
750
- const res = await this.client.get(`/api/profiles/username/${username}`);
751
- return res.data;
698
+ return await this.makeRequest<User>('GET', `/api/profiles/username/${username}`, undefined, {
699
+ cache: true,
700
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
701
+ });
752
702
  } catch (error) {
753
703
  throw this.handleError(error);
754
704
  }
@@ -760,10 +710,8 @@ export class OxyServices {
760
710
 
761
711
  async startTotpEnrollment(sessionId: string): Promise<{ secret: string; otpauthUrl: string; issuer: string; label: string }> {
762
712
  try {
763
- const res = await this.client.post('/api/auth/totp/enroll/start', { sessionId }, {
764
- headers: { 'x-session-id': sessionId }
765
- });
766
- return res.data;
713
+ // Note: x-session-id header is handled by HttpClient interceptors if needed
714
+ return await this.makeRequest('POST', '/api/auth/totp/enroll/start', { sessionId }, { cache: false });
767
715
  } catch (error) {
768
716
  throw this.handleError(error);
769
717
  }
@@ -771,10 +719,7 @@ export class OxyServices {
771
719
 
772
720
  async verifyTotpEnrollment(sessionId: string, code: string): Promise<{ enabled: boolean; backupCodes?: string[]; recoveryKey?: string }> {
773
721
  try {
774
- const res = await this.client.post('/api/auth/totp/enroll/verify', { sessionId, code }, {
775
- headers: { 'x-session-id': sessionId }
776
- });
777
- return res.data;
722
+ return await this.makeRequest('POST', '/api/auth/totp/enroll/verify', { sessionId, code }, { cache: false });
778
723
  } catch (error) {
779
724
  throw this.handleError(error);
780
725
  }
@@ -782,10 +727,7 @@ export class OxyServices {
782
727
 
783
728
  async disableTotp(sessionId: string, code: string): Promise<{ disabled: boolean }> {
784
729
  try {
785
- const res = await this.client.post('/api/auth/totp/disable', { sessionId, code }, {
786
- headers: { 'x-session-id': sessionId }
787
- });
788
- return res.data;
730
+ return await this.makeRequest('POST', '/api/auth/totp/disable', { sessionId, code }, { cache: false });
789
731
  } catch (error) {
790
732
  throw this.handleError(error);
791
733
  }
@@ -798,9 +740,14 @@ export class OxyServices {
798
740
  try {
799
741
  const params = { query, ...pagination };
800
742
  const searchParams = buildSearchParams(params);
801
-
802
- const res = await this.client.get(`/api/profiles/search?${searchParams.toString()}`);
803
- return res.data;
743
+ const paramsObj: any = {};
744
+ searchParams.forEach((value, key) => {
745
+ paramsObj[key] = value;
746
+ });
747
+ return await this.makeRequest<User[]>('GET', '/api/profiles/search', paramsObj, {
748
+ cache: true,
749
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
750
+ });
804
751
  } catch (error) {
805
752
  throw this.handleError(error);
806
753
  }
@@ -818,8 +765,7 @@ export class OxyServices {
818
765
  [key: string]: any;
819
766
  }>> {
820
767
  return this.withAuthRetry(async () => {
821
- const res = await this.client.get('/api/profiles/recommendations');
822
- return res.data;
768
+ return await this.makeRequest('GET', '/api/profiles/recommendations', undefined, { cache: true });
823
769
  }, 'getProfileRecommendations');
824
770
  }
825
771
 
@@ -828,8 +774,10 @@ export class OxyServices {
828
774
  */
829
775
  async getUserById(userId: string): Promise<User> {
830
776
  try {
831
- const res = await this.client.get(`/api/users/${userId}`);
832
- return res.data;
777
+ return await this.makeRequest<User>('GET', `/api/users/${userId}`, undefined, {
778
+ cache: true,
779
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache
780
+ });
833
781
  } catch (error) {
834
782
  throw this.handleError(error);
835
783
  }
@@ -840,8 +788,10 @@ export class OxyServices {
840
788
  */
841
789
  async getCurrentUser(): Promise<User> {
842
790
  return this.withAuthRetry(async () => {
843
- const res = await this.client.get('/api/users/me');
844
- return res.data;
791
+ return await this.makeRequest<User>('GET', '/api/users/me', undefined, {
792
+ cache: true,
793
+ cacheTTL: 1 * 60 * 1000, // 1 minute cache for current user
794
+ });
845
795
  }, 'getCurrentUser');
846
796
  }
847
797
 
@@ -850,20 +800,136 @@ export class OxyServices {
850
800
  */
851
801
  async updateProfile(updates: Record<string, any>): Promise<User> {
852
802
  try {
853
- const res = await this.client.put('/api/users/me', updates);
854
- return res.data;
803
+ return await this.makeRequest<User>('PUT', '/api/users/me', updates, { cache: false });
855
804
  } catch (error) {
856
805
  throw this.handleError(error);
857
806
  }
858
807
  }
859
808
 
809
+ // ============================================================================
810
+ // LANGUAGE METHODS
811
+ // ============================================================================
812
+
813
+ /**
814
+ * Get the current language from storage or user profile
815
+ * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
816
+ * @returns The current language code (e.g., 'en-US') or null if not set
817
+ */
818
+ async getCurrentLanguage(storageKeyPrefix: string = 'oxy_session'): Promise<string | null> {
819
+ try {
820
+ // First try to get from user profile if authenticated
821
+ try {
822
+ const user = await this.getCurrentUser();
823
+ const userLanguage = (user as Record<string, unknown>)?.language as string | undefined;
824
+ if (userLanguage) {
825
+ return normalizeLanguageCode(userLanguage) || userLanguage;
826
+ }
827
+ } catch (e) {
828
+ // User not authenticated or error, continue to storage
829
+ }
830
+
831
+ // Fall back to storage
832
+ const storage = await this.getStorage();
833
+ const storageKey = `${storageKeyPrefix}_language`;
834
+ const storedLanguage = await storage.getItem(storageKey);
835
+ if (storedLanguage) {
836
+ return normalizeLanguageCode(storedLanguage) || storedLanguage;
837
+ }
838
+
839
+ return null;
840
+ } catch (error) {
841
+ if (__DEV__) {
842
+ console.warn('Failed to get current language:', error);
843
+ }
844
+ return null;
845
+ }
846
+ }
847
+
848
+ /**
849
+ * Get the current language with metadata (name, nativeName, etc.)
850
+ * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
851
+ * @returns Language metadata object or null if not set
852
+ */
853
+ async getCurrentLanguageMetadata(storageKeyPrefix: string = 'oxy_session'): Promise<LanguageMetadata | null> {
854
+ const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
855
+ return getLanguageMetadata(languageCode);
856
+ }
857
+
858
+ /**
859
+ * Get the current language name (e.g., 'English')
860
+ * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
861
+ * @returns Language name or null if not set
862
+ */
863
+ async getCurrentLanguageName(storageKeyPrefix: string = 'oxy_session'): Promise<string | null> {
864
+ const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
865
+ if (!languageCode) return null;
866
+ return getLanguageName(languageCode);
867
+ }
868
+
869
+ /**
870
+ * Get the current native language name (e.g., 'Español')
871
+ * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
872
+ * @returns Native language name or null if not set
873
+ */
874
+ async getCurrentNativeLanguageName(storageKeyPrefix: string = 'oxy_session'): Promise<string | null> {
875
+ const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
876
+ if (!languageCode) return null;
877
+ return getNativeLanguageName(languageCode);
878
+ }
879
+
880
+ /**
881
+ * Get appropriate storage for the platform (similar to DeviceManager)
882
+ * @private
883
+ */
884
+ private async getStorage(): Promise<{
885
+ getItem: (key: string) => Promise<string | null>;
886
+ setItem: (key: string, value: string) => Promise<void>;
887
+ removeItem: (key: string) => Promise<void>;
888
+ }> {
889
+ const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
890
+
891
+ if (isReactNative) {
892
+ try {
893
+ const asyncStorageModule = await import('@react-native-async-storage/async-storage');
894
+ const storage = (asyncStorageModule.default as unknown) as import('@react-native-async-storage/async-storage').AsyncStorageStatic;
895
+ return {
896
+ getItem: storage.getItem.bind(storage),
897
+ setItem: storage.setItem.bind(storage),
898
+ removeItem: storage.removeItem.bind(storage),
899
+ };
900
+ } catch (error) {
901
+ console.error('AsyncStorage not available in React Native:', error);
902
+ throw new Error('AsyncStorage is required in React Native environment');
903
+ }
904
+ } else {
905
+ // Use localStorage for web
906
+ return {
907
+ getItem: async (key: string) => {
908
+ if (typeof window !== 'undefined' && window.localStorage) {
909
+ return localStorage.getItem(key);
910
+ }
911
+ return null;
912
+ },
913
+ setItem: async (key: string, value: string) => {
914
+ if (typeof window !== 'undefined' && window.localStorage) {
915
+ localStorage.setItem(key, value);
916
+ }
917
+ },
918
+ removeItem: async (key: string) => {
919
+ if (typeof window !== 'undefined' && window.localStorage) {
920
+ localStorage.removeItem(key);
921
+ }
922
+ }
923
+ };
924
+ }
925
+ }
926
+
860
927
  /**
861
928
  * Update user by ID (admin function)
862
929
  */
863
930
  async updateUser(userId: string, updates: Record<string, any>): Promise<User> {
864
931
  try {
865
- const res = await this.client.put(`/api/users/${userId}`, updates);
866
- return res.data;
932
+ return await this.makeRequest<User>('PUT', `/api/users/${userId}`, updates, { cache: false });
867
933
  } catch (error) {
868
934
  throw this.handleError(error);
869
935
  }
@@ -874,8 +940,7 @@ export class OxyServices {
874
940
  */
875
941
  async followUser(userId: string): Promise<{ success: boolean; message: string }> {
876
942
  try {
877
- const res = await this.client.post(`/api/users/${userId}/follow`);
878
- return res.data;
943
+ return await this.makeRequest('POST', `/api/users/${userId}/follow`, undefined, { cache: false });
879
944
  } catch (error) {
880
945
  throw this.handleError(error);
881
946
  }
@@ -886,8 +951,7 @@ export class OxyServices {
886
951
  */
887
952
  async unfollowUser(userId: string): Promise<{ success: boolean; message: string }> {
888
953
  try {
889
- const res = await this.client.delete(`/api/users/${userId}/follow`);
890
- return res.data;
954
+ return await this.makeRequest('DELETE', `/api/users/${userId}/follow`, undefined, { cache: false });
891
955
  } catch (error) {
892
956
  throw this.handleError(error);
893
957
  }
@@ -898,8 +962,10 @@ export class OxyServices {
898
962
  */
899
963
  async getFollowStatus(userId: string): Promise<{ isFollowing: boolean }> {
900
964
  try {
901
- const res = await this.client.get(`/api/users/${userId}/follow-status`);
902
- return res.data;
965
+ return await this.makeRequest('GET', `/api/users/${userId}/follow-status`, undefined, {
966
+ cache: true,
967
+ cacheTTL: 1 * 60 * 1000, // 1 minute cache
968
+ });
903
969
  } catch (error) {
904
970
  throw this.handleError(error);
905
971
  }
@@ -914,8 +980,15 @@ export class OxyServices {
914
980
  ): Promise<{ followers: User[]; total: number; hasMore: boolean }> {
915
981
  try {
916
982
  const params = buildPaginationParams(pagination || {});
917
- const res = await this.client.get(`/api/users/${userId}/followers?${params.toString()}`);
918
- return res.data;
983
+ const response = await this.makeRequest<{ data: User[]; pagination: { total: number; hasMore: boolean } }>('GET', `/api/users/${userId}/followers`, params, {
984
+ cache: true,
985
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
986
+ });
987
+ return {
988
+ followers: response.data || [],
989
+ total: response.pagination.total,
990
+ hasMore: response.pagination.hasMore,
991
+ };
919
992
  } catch (error) {
920
993
  throw this.handleError(error);
921
994
  }
@@ -930,8 +1003,15 @@ export class OxyServices {
930
1003
  ): Promise<{ following: User[]; total: number; hasMore: boolean }> {
931
1004
  try {
932
1005
  const params = buildPaginationParams(pagination || {});
933
- const res = await this.client.get(`/api/users/${userId}/following?${params.toString()}`);
934
- return res.data;
1006
+ const response = await this.makeRequest<{ data: User[]; pagination: { total: number; hasMore: boolean } }>('GET', `/api/users/${userId}/following`, params, {
1007
+ cache: true,
1008
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
1009
+ });
1010
+ return {
1011
+ following: response.data || [],
1012
+ total: response.pagination.total,
1013
+ hasMore: response.pagination.hasMore,
1014
+ };
935
1015
  } catch (error) {
936
1016
  throw this.handleError(error);
937
1017
  }
@@ -942,8 +1022,9 @@ export class OxyServices {
942
1022
  */
943
1023
  async getNotifications(): Promise<Notification[]> {
944
1024
  return this.withAuthRetry(async () => {
945
- const res = await this.client.get('/api/notifications');
946
- return res.data;
1025
+ return await this.makeRequest<Notification[]>('GET', '/api/notifications', undefined, {
1026
+ cache: false, // Don't cache notifications - always get fresh data
1027
+ });
947
1028
  }, 'getNotifications');
948
1029
  }
949
1030
 
@@ -952,8 +1033,10 @@ export class OxyServices {
952
1033
  */
953
1034
  async getUnreadCount(): Promise<number> {
954
1035
  try {
955
- const res = await this.client.get('/api/notifications/unread-count');
956
- return res.data.count;
1036
+ const res = await this.makeRequest<{ count: number }>('GET', '/api/notifications/unread-count', undefined, {
1037
+ cache: false, // Don't cache unread count - always get fresh data
1038
+ });
1039
+ return res.count;
957
1040
  } catch (error) {
958
1041
  throw this.handleError(error);
959
1042
  }
@@ -964,8 +1047,7 @@ export class OxyServices {
964
1047
  */
965
1048
  async createNotification(data: Partial<Notification>): Promise<Notification> {
966
1049
  try {
967
- const res = await this.client.post('/api/notifications', data);
968
- return res.data;
1050
+ return await this.makeRequest<Notification>('POST', '/api/notifications', data, { cache: false });
969
1051
  } catch (error) {
970
1052
  throw this.handleError(error);
971
1053
  }
@@ -976,7 +1058,7 @@ export class OxyServices {
976
1058
  */
977
1059
  async markNotificationAsRead(notificationId: string): Promise<void> {
978
1060
  try {
979
- await this.client.put(`/api/notifications/${notificationId}/read`);
1061
+ await this.makeRequest('PUT', `/api/notifications/${notificationId}/read`, undefined, { cache: false });
980
1062
  } catch (error) {
981
1063
  throw this.handleError(error);
982
1064
  }
@@ -987,7 +1069,7 @@ export class OxyServices {
987
1069
  */
988
1070
  async markAllNotificationsAsRead(): Promise<void> {
989
1071
  try {
990
- await this.client.put('/api/notifications/read-all');
1072
+ await this.makeRequest('PUT', '/api/notifications/read-all', undefined, { cache: false });
991
1073
  } catch (error) {
992
1074
  throw this.handleError(error);
993
1075
  }
@@ -998,7 +1080,7 @@ export class OxyServices {
998
1080
  */
999
1081
  async deleteNotification(notificationId: string): Promise<void> {
1000
1082
  try {
1001
- await this.client.delete(`/api/notifications/${notificationId}`);
1083
+ await this.makeRequest('DELETE', `/api/notifications/${notificationId}`, undefined, { cache: false });
1002
1084
  } catch (error) {
1003
1085
  throw this.handleError(error);
1004
1086
  }
@@ -1013,8 +1095,7 @@ export class OxyServices {
1013
1095
  */
1014
1096
  async createPayment(data: any): Promise<any> {
1015
1097
  try {
1016
- const res = await this.client.post('/api/payments', data);
1017
- return res.data;
1098
+ return await this.makeRequest('POST', '/api/payments', data, { cache: false });
1018
1099
  } catch (error) {
1019
1100
  throw this.handleError(error);
1020
1101
  }
@@ -1025,8 +1106,10 @@ export class OxyServices {
1025
1106
  */
1026
1107
  async getPayment(paymentId: string): Promise<any> {
1027
1108
  try {
1028
- const res = await this.client.get(`/api/payments/${paymentId}`);
1029
- return res.data;
1109
+ return await this.makeRequest('GET', `/api/payments/${paymentId}`, undefined, {
1110
+ cache: true,
1111
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache
1112
+ });
1030
1113
  } catch (error) {
1031
1114
  throw this.handleError(error);
1032
1115
  }
@@ -1037,8 +1120,9 @@ export class OxyServices {
1037
1120
  */
1038
1121
  async getUserPayments(): Promise<any[]> {
1039
1122
  try {
1040
- const res = await this.client.get('/api/payments/user');
1041
- return res.data;
1123
+ return await this.makeRequest('GET', '/api/payments/user', undefined, {
1124
+ cache: false, // Don't cache user payments - always get fresh data
1125
+ });
1042
1126
  } catch (error) {
1043
1127
  throw this.handleError(error);
1044
1128
  }
@@ -1053,8 +1137,10 @@ export class OxyServices {
1053
1137
  */
1054
1138
  async getUserKarma(userId: string): Promise<any> {
1055
1139
  try {
1056
- const res = await this.client.get(`/api/karma/${userId}`);
1057
- return res.data;
1140
+ return await this.makeRequest('GET', `/api/karma/${userId}`, undefined, {
1141
+ cache: true,
1142
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
1143
+ });
1058
1144
  } catch (error) {
1059
1145
  throw this.handleError(error);
1060
1146
  }
@@ -1065,11 +1151,10 @@ export class OxyServices {
1065
1151
  */
1066
1152
  async giveKarma(userId: string, amount: number, reason?: string): Promise<any> {
1067
1153
  try {
1068
- const res = await this.client.post(`/api/karma/${userId}/give`, {
1154
+ return await this.makeRequest('POST', `/api/karma/${userId}/give`, {
1069
1155
  amount,
1070
1156
  reason
1071
- });
1072
- return res.data;
1157
+ }, { cache: false });
1073
1158
  } catch (error) {
1074
1159
  throw this.handleError(error);
1075
1160
  }
@@ -1080,8 +1165,10 @@ export class OxyServices {
1080
1165
  */
1081
1166
  async getUserKarmaTotal(userId: string): Promise<any> {
1082
1167
  try {
1083
- const res = await this.client.get(`/api/karma/${userId}/total`);
1084
- return res.data;
1168
+ return await this.makeRequest('GET', `/api/karma/${userId}/total`, undefined, {
1169
+ cache: true,
1170
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
1171
+ });
1085
1172
  } catch (error) {
1086
1173
  throw this.handleError(error);
1087
1174
  }
@@ -1092,12 +1179,14 @@ export class OxyServices {
1092
1179
  */
1093
1180
  async getUserKarmaHistory(userId: string, limit?: number, offset?: number): Promise<any> {
1094
1181
  try {
1095
- const params = new URLSearchParams();
1096
- if (limit) params.append('limit', limit.toString());
1097
- if (offset) params.append('offset', offset.toString());
1182
+ const params: any = {};
1183
+ if (limit) params.limit = limit;
1184
+ if (offset) params.offset = offset;
1098
1185
 
1099
- const res = await this.client.get(`/api/karma/${userId}/history?${params.toString()}`);
1100
- return res.data;
1186
+ return await this.makeRequest('GET', `/api/karma/${userId}/history`, params, {
1187
+ cache: true,
1188
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
1189
+ });
1101
1190
  } catch (error) {
1102
1191
  throw this.handleError(error);
1103
1192
  }
@@ -1108,8 +1197,10 @@ export class OxyServices {
1108
1197
  */
1109
1198
  async getKarmaLeaderboard(): Promise<any> {
1110
1199
  try {
1111
- const res = await this.client.get('/api/karma/leaderboard');
1112
- return res.data;
1200
+ return await this.makeRequest('GET', '/api/karma/leaderboard', undefined, {
1201
+ cache: true,
1202
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache
1203
+ });
1113
1204
  } catch (error) {
1114
1205
  throw this.handleError(error);
1115
1206
  }
@@ -1120,8 +1211,10 @@ export class OxyServices {
1120
1211
  */
1121
1212
  async getKarmaRules(): Promise<any> {
1122
1213
  try {
1123
- const res = await this.client.get('/api/karma/rules');
1124
- return res.data;
1214
+ return await this.makeRequest('GET', '/api/karma/rules', undefined, {
1215
+ cache: true,
1216
+ cacheTTL: 30 * 60 * 1000, // 30 minutes cache (rules don't change often)
1217
+ });
1125
1218
  } catch (error) {
1126
1219
  throw this.handleError(error);
1127
1220
  }
@@ -1137,8 +1230,7 @@ export class OxyServices {
1137
1230
  async deleteFile(fileId: string): Promise<any> {
1138
1231
  try {
1139
1232
  // Central Asset Service delete with force=true behavior controlled by caller via assetDelete
1140
- const res = await this.client.delete(`/api/assets/${encodeURIComponent(fileId)}`);
1141
- return res.data;
1233
+ return await this.makeRequest('DELETE', `/api/assets/${encodeURIComponent(fileId)}`, undefined, { cache: false });
1142
1234
  } catch (error) {
1143
1235
  throw this.handleError(error);
1144
1236
  }
@@ -1153,7 +1245,7 @@ export class OxyServices {
1153
1245
  if (variant) params.set('variant', variant);
1154
1246
  if (expiresIn) params.set('expiresIn', String(expiresIn));
1155
1247
  params.set('fallback', 'placeholderVisible');
1156
- const token = this.tokenStore.getAccessToken();
1248
+ const token = this.httpClient.getAccessToken();
1157
1249
  if (token) params.set('token', token);
1158
1250
 
1159
1251
  // Use params.toString() to detect whether there are query params.
@@ -1179,12 +1271,12 @@ export class OxyServices {
1179
1271
  */
1180
1272
  async listUserFiles(limit?: number, offset?: number): Promise<{ files: any[]; total: number; hasMore: boolean }> {
1181
1273
  try {
1182
- const params = new URLSearchParams();
1183
- if (limit) params.append('limit', String(limit));
1184
- if (offset) params.append('offset', String(offset));
1185
- const qs = params.toString();
1186
- const res = await this.client.get(`/api/assets${qs ? `?${qs}` : ''}`);
1187
- return res.data;
1274
+ const paramsObj: any = {};
1275
+ if (limit) paramsObj.limit = limit;
1276
+ if (offset) paramsObj.offset = offset;
1277
+ return await this.makeRequest('GET', '/api/assets', paramsObj, {
1278
+ cache: false, // Don't cache file lists - always get fresh data
1279
+ });
1188
1280
  } catch (error) {
1189
1281
  throw this.handleError(error);
1190
1282
  }
@@ -1197,8 +1289,12 @@ export class OxyServices {
1197
1289
  */
1198
1290
  async getFileContentAsText(fileId: string, variant?: string): Promise<string> {
1199
1291
  try {
1200
- const urlRes = await this.client.get(`/api/assets/${encodeURIComponent(fileId)}/url${variant ? `?variant=${encodeURIComponent(variant)}` : ''}`);
1201
- const downloadUrl = urlRes.data?.url;
1292
+ const params: any = variant ? { variant } : undefined;
1293
+ const urlRes = await this.makeRequest<{ url: string }>('GET', `/api/assets/${encodeURIComponent(fileId)}/url`, params, {
1294
+ cache: true,
1295
+ cacheTTL: 10 * 60 * 1000, // 10 minutes cache for URLs
1296
+ });
1297
+ const downloadUrl = urlRes?.url;
1202
1298
  const response = await fetch(downloadUrl);
1203
1299
  return await response.text();
1204
1300
  } catch (error) {
@@ -1211,8 +1307,12 @@ export class OxyServices {
1211
1307
  */
1212
1308
  async getFileContentAsBlob(fileId: string, variant?: string): Promise<Blob> {
1213
1309
  try {
1214
- const urlRes = await this.client.get(`/api/assets/${encodeURIComponent(fileId)}/url${variant ? `?variant=${encodeURIComponent(variant)}` : ''}`);
1215
- const downloadUrl = urlRes.data?.url;
1310
+ const params: any = variant ? { variant } : undefined;
1311
+ const urlRes = await this.makeRequest<{ url: string }>('GET', `/api/assets/${encodeURIComponent(fileId)}/url`, params, {
1312
+ cache: true,
1313
+ cacheTTL: 10 * 60 * 1000, // 10 minutes cache for URLs
1314
+ });
1315
+ const downloadUrl = urlRes?.url;
1216
1316
  const response = await fetch(downloadUrl);
1217
1317
  return await response.blob();
1218
1318
  } catch (error) {
@@ -1247,12 +1347,11 @@ export class OxyServices {
1247
1347
  */
1248
1348
  async assetInit(sha256: string, size: number, mime: string): Promise<AssetInitResponse> {
1249
1349
  try {
1250
- const res = await this.client.post('/api/assets/init', {
1350
+ return await this.makeRequest<AssetInitResponse>('POST', '/api/assets/init', {
1251
1351
  sha256,
1252
1352
  size,
1253
1353
  mime
1254
- });
1255
- return res.data;
1354
+ }, { cache: false });
1256
1355
  } catch (error) {
1257
1356
  throw this.handleError(error);
1258
1357
  }
@@ -1263,15 +1362,14 @@ export class OxyServices {
1263
1362
  */
1264
1363
  async assetComplete(fileId: string, originalName: string, size: number, mime: string, visibility?: 'private' | 'public' | 'unlisted', metadata?: Record<string, any>): Promise<any> {
1265
1364
  try {
1266
- const res = await this.client.post('/api/assets/complete', {
1365
+ return await this.makeRequest('POST', '/api/assets/complete', {
1267
1366
  fileId,
1268
1367
  originalName,
1269
1368
  size,
1270
1369
  mime,
1271
1370
  visibility,
1272
1371
  metadata
1273
- });
1274
- return res.data;
1372
+ }, { cache: false });
1275
1373
  } catch (error) {
1276
1374
  throw this.handleError(error);
1277
1375
  }
@@ -1295,8 +1393,11 @@ export class OxyServices {
1295
1393
  // Fallback: direct upload via API to avoid CORS issues
1296
1394
  const fd = new FormData();
1297
1395
  fd.append('file', file);
1298
- await this.client.post(`/api/assets/${encodeURIComponent(initResponse.fileId)}/upload-direct`, fd, {
1299
- headers: { 'Content-Type': 'multipart/form-data' }
1396
+ // Use httpClient directly for FormData uploads (bypasses RequestManager for special handling)
1397
+ await this.httpClient.request({
1398
+ method: 'POST',
1399
+ url: `/api/assets/${encodeURIComponent(initResponse.fileId)}/upload-direct`,
1400
+ data: fd,
1300
1401
  });
1301
1402
  }
1302
1403
 
@@ -1354,8 +1455,7 @@ export class OxyServices {
1354
1455
  const body: any = { app, entityType, entityId };
1355
1456
  if (visibility) body.visibility = visibility;
1356
1457
  if (webhookUrl) body.webhookUrl = webhookUrl;
1357
- const res = await this.client.post(`/api/assets/${fileId}/links`, body);
1358
- return res.data;
1458
+ return await this.makeRequest('POST', `/api/assets/${fileId}/links`, body, { cache: false });
1359
1459
  } catch (error) {
1360
1460
  throw this.handleError(error);
1361
1461
  }
@@ -1366,14 +1466,11 @@ export class OxyServices {
1366
1466
  */
1367
1467
  async assetUnlink(fileId: string, app: string, entityType: string, entityId: string): Promise<any> {
1368
1468
  try {
1369
- const res = await this.client.delete(`/api/assets/${fileId}/links`, {
1370
- data: {
1371
- app,
1372
- entityType,
1373
- entityId
1374
- }
1375
- });
1376
- return res.data;
1469
+ return await this.makeRequest('DELETE', `/api/assets/${fileId}/links`, {
1470
+ app,
1471
+ entityType,
1472
+ entityId
1473
+ }, { cache: false });
1377
1474
  } catch (error) {
1378
1475
  throw this.handleError(error);
1379
1476
  }
@@ -1384,8 +1481,10 @@ export class OxyServices {
1384
1481
  */
1385
1482
  async assetGet(fileId: string): Promise<any> {
1386
1483
  try {
1387
- const res = await this.client.get(`/api/assets/${fileId}`);
1388
- return res.data;
1484
+ return await this.makeRequest('GET', `/api/assets/${fileId}`, undefined, {
1485
+ cache: true,
1486
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache
1487
+ });
1389
1488
  } catch (error) {
1390
1489
  throw this.handleError(error);
1391
1490
  }
@@ -1396,15 +1495,14 @@ export class OxyServices {
1396
1495
  */
1397
1496
  async assetGetUrl(fileId: string, variant?: string, expiresIn?: number): Promise<AssetUrlResponse> {
1398
1497
  try {
1399
- const params = new URLSearchParams();
1400
- if (variant) params.set('variant', variant);
1401
- if (expiresIn) params.set('expiresIn', expiresIn.toString());
1402
-
1403
- const queryString = params.toString();
1404
- const url = `/api/assets/${fileId}/url${queryString ? `?${queryString}` : ''}`;
1498
+ const params: any = {};
1499
+ if (variant) params.variant = variant;
1500
+ if (expiresIn) params.expiresIn = expiresIn;
1405
1501
 
1406
- const res = await this.client.get(url);
1407
- return res.data;
1502
+ return await this.makeRequest<AssetUrlResponse>('GET', `/api/assets/${fileId}/url`, params, {
1503
+ cache: true,
1504
+ cacheTTL: 10 * 60 * 1000, // 10 minutes cache for URLs
1505
+ });
1408
1506
  } catch (error) {
1409
1507
  throw this.handleError(error);
1410
1508
  }
@@ -1415,8 +1513,7 @@ export class OxyServices {
1415
1513
  */
1416
1514
  async assetRestore(fileId: string): Promise<any> {
1417
1515
  try {
1418
- const res = await this.client.post(`/api/assets/${fileId}/restore`);
1419
- return res.data;
1516
+ return await this.makeRequest('POST', `/api/assets/${fileId}/restore`, undefined, { cache: false });
1420
1517
  } catch (error) {
1421
1518
  throw this.handleError(error);
1422
1519
  }
@@ -1427,9 +1524,8 @@ export class OxyServices {
1427
1524
  */
1428
1525
  async assetDelete(fileId: string, force: boolean = false): Promise<any> {
1429
1526
  try {
1430
- const params = force ? '?force=true' : '';
1431
- const res = await this.client.delete(`/api/assets/${fileId}${params}`);
1432
- return res.data;
1527
+ const params: any = force ? { force: 'true' } : undefined;
1528
+ return await this.makeRequest('DELETE', `/api/assets/${fileId}`, params, { cache: false });
1433
1529
  } catch (error) {
1434
1530
  throw this.handleError(error);
1435
1531
  }
@@ -1455,10 +1551,9 @@ export class OxyServices {
1455
1551
  */
1456
1552
  async assetUpdateVisibility(fileId: string, visibility: 'private' | 'public' | 'unlisted'): Promise<any> {
1457
1553
  try {
1458
- const res = await this.client.patch(`/api/assets/${fileId}/visibility`, {
1554
+ return await this.makeRequest('PATCH', `/api/assets/${fileId}/visibility`, {
1459
1555
  visibility
1460
- });
1461
- return res.data;
1556
+ }, { cache: false });
1462
1557
  } catch (error) {
1463
1558
  throw this.handleError(error);
1464
1559
  }
@@ -1515,8 +1610,11 @@ export class OxyServices {
1515
1610
  */
1516
1611
  async getDeveloperApps(): Promise<any[]> {
1517
1612
  try {
1518
- const res = await this.client.get('/api/developer/apps');
1519
- return res.data.apps || [];
1613
+ const res = await this.makeRequest<{ apps?: any[] }>('GET', '/api/developer/apps', undefined, {
1614
+ cache: true,
1615
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
1616
+ });
1617
+ return res.apps || [];
1520
1618
  } catch (error) {
1521
1619
  throw this.handleError(error);
1522
1620
  }
@@ -1533,8 +1631,8 @@ export class OxyServices {
1533
1631
  scopes?: string[];
1534
1632
  }): Promise<any> {
1535
1633
  try {
1536
- const res = await this.client.post('/api/developer/apps', data);
1537
- return res.data.app;
1634
+ const res = await this.makeRequest<{ app: any }>('POST', '/api/developer/apps', data, { cache: false });
1635
+ return res.app;
1538
1636
  } catch (error) {
1539
1637
  throw this.handleError(error);
1540
1638
  }
@@ -1545,8 +1643,11 @@ export class OxyServices {
1545
1643
  */
1546
1644
  async getDeveloperApp(appId: string): Promise<any> {
1547
1645
  try {
1548
- const res = await this.client.get(`/api/developer/apps/${appId}`);
1549
- return res.data.app;
1646
+ const res = await this.makeRequest<{ app: any }>('GET', `/api/developer/apps/${appId}`, undefined, {
1647
+ cache: true,
1648
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache
1649
+ });
1650
+ return res.app;
1550
1651
  } catch (error) {
1551
1652
  throw this.handleError(error);
1552
1653
  }
@@ -1563,8 +1664,8 @@ export class OxyServices {
1563
1664
  scopes?: string[];
1564
1665
  }): Promise<any> {
1565
1666
  try {
1566
- const res = await this.client.patch(`/api/developer/apps/${appId}`, data);
1567
- return res.data.app;
1667
+ const res = await this.makeRequest<{ app: any }>('PATCH', `/api/developer/apps/${appId}`, data, { cache: false });
1668
+ return res.app;
1568
1669
  } catch (error) {
1569
1670
  throw this.handleError(error);
1570
1671
  }
@@ -1575,8 +1676,7 @@ export class OxyServices {
1575
1676
  */
1576
1677
  async regenerateDeveloperAppSecret(appId: string): Promise<any> {
1577
1678
  try {
1578
- const res = await this.client.post(`/api/developer/apps/${appId}/regenerate-secret`);
1579
- return res.data;
1679
+ return await this.makeRequest('POST', `/api/developer/apps/${appId}/regenerate-secret`, undefined, { cache: false });
1580
1680
  } catch (error) {
1581
1681
  throw this.handleError(error);
1582
1682
  }
@@ -1587,8 +1687,7 @@ export class OxyServices {
1587
1687
  */
1588
1688
  async deleteDeveloperApp(appId: string): Promise<any> {
1589
1689
  try {
1590
- const res = await this.client.delete(`/api/developer/apps/${appId}`);
1591
- return res.data;
1690
+ return await this.makeRequest('DELETE', `/api/developer/apps/${appId}`, undefined, { cache: false });
1592
1691
  } catch (error) {
1593
1692
  throw this.handleError(error);
1594
1693
  }
@@ -1603,11 +1702,10 @@ export class OxyServices {
1603
1702
  */
1604
1703
  async updateLocation(latitude: number, longitude: number): Promise<any> {
1605
1704
  try {
1606
- const res = await this.client.post('/api/location', {
1705
+ return await this.makeRequest('POST', '/api/location', {
1607
1706
  latitude,
1608
1707
  longitude
1609
- });
1610
- return res.data;
1708
+ }, { cache: false });
1611
1709
  } catch (error) {
1612
1710
  throw this.handleError(error);
1613
1711
  }
@@ -1618,9 +1716,10 @@ export class OxyServices {
1618
1716
  */
1619
1717
  async getNearbyUsers(radius?: number): Promise<any[]> {
1620
1718
  try {
1621
- const params = radius ? `?radius=${radius}` : '';
1622
- const res = await this.client.get(`/api/location/nearby${params}`);
1623
- return res.data;
1719
+ const params: any = radius ? { radius } : undefined;
1720
+ return await this.makeRequest('GET', '/api/location/nearby', params, {
1721
+ cache: false, // Don't cache location data - always get fresh data
1722
+ });
1624
1723
  } catch (error) {
1625
1724
  throw this.handleError(error);
1626
1725
  }
@@ -1635,10 +1734,10 @@ export class OxyServices {
1635
1734
  */
1636
1735
  async trackEvent(eventName: string, properties?: Record<string, any>): Promise<void> {
1637
1736
  try {
1638
- await this.client.post('/api/analytics/events', {
1737
+ await this.makeRequest('POST', '/api/analytics/events', {
1639
1738
  event: eventName,
1640
1739
  properties
1641
- });
1740
+ }, { cache: false, retry: false }); // Don't retry analytics events
1642
1741
  } catch (error) {
1643
1742
  throw this.handleError(error);
1644
1743
  }
@@ -1649,12 +1748,14 @@ export class OxyServices {
1649
1748
  */
1650
1749
  async getAnalytics(startDate?: string, endDate?: string): Promise<any> {
1651
1750
  try {
1652
- const params = new URLSearchParams();
1653
- if (startDate) params.append('startDate', startDate);
1654
- if (endDate) params.append('endDate', endDate);
1751
+ const params: any = {};
1752
+ if (startDate) params.startDate = startDate;
1753
+ if (endDate) params.endDate = endDate;
1655
1754
 
1656
- const res = await this.client.get(`/api/analytics?${params.toString()}`);
1657
- return res.data;
1755
+ return await this.makeRequest('GET', '/api/analytics', params, {
1756
+ cache: true,
1757
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache
1758
+ });
1658
1759
  } catch (error) {
1659
1760
  throw this.handleError(error);
1660
1761
  }
@@ -1669,8 +1770,7 @@ export class OxyServices {
1669
1770
  */
1670
1771
  async registerDevice(deviceData: any): Promise<any> {
1671
1772
  try {
1672
- const res = await this.client.post('/api/devices', deviceData);
1673
- return res.data;
1773
+ return await this.makeRequest('POST', '/api/devices', deviceData, { cache: false });
1674
1774
  } catch (error) {
1675
1775
  throw this.handleError(error);
1676
1776
  }
@@ -1681,8 +1781,9 @@ export class OxyServices {
1681
1781
  */
1682
1782
  async getUserDevices(): Promise<any[]> {
1683
1783
  try {
1684
- const res = await this.client.get('/api/devices');
1685
- return res.data;
1784
+ return await this.makeRequest('GET', '/api/devices', undefined, {
1785
+ cache: false, // Don't cache device list - always get fresh data
1786
+ });
1686
1787
  } catch (error) {
1687
1788
  throw this.handleError(error);
1688
1789
  }
@@ -1693,7 +1794,7 @@ export class OxyServices {
1693
1794
  */
1694
1795
  async removeDevice(deviceId: string): Promise<void> {
1695
1796
  try {
1696
- await this.client.delete(`/api/devices/${deviceId}`);
1797
+ await this.makeRequest('DELETE', `/api/devices/${deviceId}`, undefined, { cache: false });
1697
1798
  } catch (error) {
1698
1799
  throw this.handleError(error);
1699
1800
  }
@@ -1701,11 +1802,16 @@ export class OxyServices {
1701
1802
 
1702
1803
  /**
1703
1804
  * Get device sessions
1805
+ * Note: Not cached by default to ensure fresh data, but can be cached via makeRequest if needed
1704
1806
  */
1705
1807
  async getDeviceSessions(sessionId: string): Promise<any[]> {
1706
1808
  try {
1707
- const res = await this.client.get(`/api/session/device/sessions/${sessionId}`);
1708
- return res.data;
1809
+ // Use makeRequest for consistent error handling and optional caching
1810
+ // Cache disabled by default to ensure fresh session data
1811
+ return await this.makeRequest<any[]>('GET', `/api/session/device/sessions/${sessionId}`, undefined, {
1812
+ cache: false, // Don't cache sessions - always get fresh data
1813
+ deduplicate: true, // Deduplicate concurrent requests for same sessionId
1814
+ });
1709
1815
  } catch (error) {
1710
1816
  throw this.handleError(error);
1711
1817
  }
@@ -1720,8 +1826,11 @@ export class OxyServices {
1720
1826
  if (deviceId) params.append('deviceId', deviceId);
1721
1827
  if (excludeCurrent) params.append('excludeCurrent', 'true');
1722
1828
 
1723
- const res = await this.client.post(`/api/session/device/logout-all/${sessionId}?${params.toString()}`);
1724
- return res.data;
1829
+ const urlParams: any = {};
1830
+ params.forEach((value, key) => {
1831
+ urlParams[key] = value;
1832
+ });
1833
+ return await this.makeRequest('POST', `/api/session/device/logout-all/${sessionId}`, urlParams, { cache: false });
1725
1834
  } catch (error) {
1726
1835
  throw this.handleError(error);
1727
1836
  }
@@ -1732,8 +1841,7 @@ export class OxyServices {
1732
1841
  */
1733
1842
  async updateDeviceName(sessionId: string, deviceName: string): Promise<any> {
1734
1843
  try {
1735
- const res = await this.client.put(`/api/session/device/name/${sessionId}`, { deviceName });
1736
- return res.data;
1844
+ return await this.makeRequest('PUT', `/api/session/device/name/${sessionId}`, { deviceName }, { cache: false });
1737
1845
  } catch (error) {
1738
1846
  throw this.handleError(error);
1739
1847
  }
@@ -1753,8 +1861,15 @@ export class OxyServices {
1753
1861
  image?: string;
1754
1862
  }> {
1755
1863
  try {
1756
- const res = await this.client.get(`/api/link-metadata?url=${encodeURIComponent(url)}`);
1757
- return res.data;
1864
+ return await this.makeRequest<{
1865
+ url: string;
1866
+ title: string;
1867
+ description: string;
1868
+ image?: string;
1869
+ }>('GET', '/api/link-metadata', { url }, {
1870
+ cache: true,
1871
+ cacheTTL: 30 * 60 * 1000, // 30 minutes cache for link metadata
1872
+ });
1758
1873
  } catch (error) {
1759
1874
  throw this.handleError(error);
1760
1875
  }