@oxyhq/services 5.13.25 → 5.13.27

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 (125) hide show
  1. package/README.md +5 -6
  2. package/lib/commonjs/core/HttpService.js +481 -0
  3. package/lib/commonjs/core/HttpService.js.map +1 -0
  4. package/lib/commonjs/core/OxyServices.base.js +29 -26
  5. package/lib/commonjs/core/OxyServices.base.js.map +1 -1
  6. package/lib/commonjs/core/OxyServices.js +3 -4
  7. package/lib/commonjs/core/OxyServices.js.map +1 -1
  8. package/lib/commonjs/core/mixins/OxyServices.assets.js +3 -9
  9. package/lib/commonjs/core/mixins/OxyServices.assets.js.map +1 -1
  10. package/lib/commonjs/core/mixins/OxyServices.user.js +9 -5
  11. package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
  12. package/lib/commonjs/core/mixins/OxyServices.utility.js +1 -0
  13. package/lib/commonjs/core/mixins/OxyServices.utility.js.map +1 -1
  14. package/lib/commonjs/index.js +15 -0
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/ui/components/ProfileCard.js +5 -1
  17. package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
  18. package/lib/commonjs/ui/hooks/index.js +13 -0
  19. package/lib/commonjs/ui/hooks/index.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/useFileDownloadUrl.js +103 -0
  21. package/lib/commonjs/ui/hooks/useFileDownloadUrl.js.map +1 -0
  22. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +1 -1
  23. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  24. package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js +0 -3
  25. package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
  26. package/lib/commonjs/utils/errorUtils.js +35 -15
  27. package/lib/commonjs/utils/errorUtils.js.map +1 -1
  28. package/lib/module/core/HttpService.js +476 -0
  29. package/lib/module/core/HttpService.js.map +1 -0
  30. package/lib/module/core/OxyServices.base.js +29 -26
  31. package/lib/module/core/OxyServices.base.js.map +1 -1
  32. package/lib/module/core/OxyServices.js +3 -4
  33. package/lib/module/core/OxyServices.js.map +1 -1
  34. package/lib/module/core/mixins/OxyServices.assets.js +3 -9
  35. package/lib/module/core/mixins/OxyServices.assets.js.map +1 -1
  36. package/lib/module/core/mixins/OxyServices.user.js +9 -5
  37. package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
  38. package/lib/module/core/mixins/OxyServices.utility.js +1 -0
  39. package/lib/module/core/mixins/OxyServices.utility.js.map +1 -1
  40. package/lib/module/index.js +1 -0
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/ui/components/ProfileCard.js +5 -1
  43. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  44. package/lib/module/ui/hooks/index.js +1 -0
  45. package/lib/module/ui/hooks/index.js.map +1 -1
  46. package/lib/module/ui/hooks/useFileDownloadUrl.js +97 -0
  47. package/lib/module/ui/hooks/useFileDownloadUrl.js.map +1 -0
  48. package/lib/module/ui/screens/AccountOverviewScreen.js +1 -1
  49. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  50. package/lib/module/ui/screens/karma/KarmaFAQScreen.js +1 -4
  51. package/lib/module/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
  52. package/lib/module/utils/errorUtils.js +36 -15
  53. package/lib/module/utils/errorUtils.js.map +1 -1
  54. package/lib/typescript/core/HttpService.d.ts +111 -0
  55. package/lib/typescript/core/HttpService.d.ts.map +1 -0
  56. package/lib/typescript/core/OxyServices.base.d.ts +6 -8
  57. package/lib/typescript/core/OxyServices.base.d.ts.map +1 -1
  58. package/lib/typescript/core/OxyServices.d.ts +3 -4
  59. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  60. package/lib/typescript/core/mixins/OxyServices.analytics.d.ts +4 -5
  61. package/lib/typescript/core/mixins/OxyServices.analytics.d.ts.map +1 -1
  62. package/lib/typescript/core/mixins/OxyServices.assets.d.ts +4 -9
  63. package/lib/typescript/core/mixins/OxyServices.assets.d.ts.map +1 -1
  64. package/lib/typescript/core/mixins/OxyServices.auth.d.ts +4 -5
  65. package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -1
  66. package/lib/typescript/core/mixins/OxyServices.developer.d.ts +4 -5
  67. package/lib/typescript/core/mixins/OxyServices.developer.d.ts.map +1 -1
  68. package/lib/typescript/core/mixins/OxyServices.devices.d.ts +4 -5
  69. package/lib/typescript/core/mixins/OxyServices.devices.d.ts.map +1 -1
  70. package/lib/typescript/core/mixins/OxyServices.karma.d.ts +4 -5
  71. package/lib/typescript/core/mixins/OxyServices.karma.d.ts.map +1 -1
  72. package/lib/typescript/core/mixins/OxyServices.language.d.ts +4 -5
  73. package/lib/typescript/core/mixins/OxyServices.language.d.ts.map +1 -1
  74. package/lib/typescript/core/mixins/OxyServices.location.d.ts +4 -5
  75. package/lib/typescript/core/mixins/OxyServices.location.d.ts.map +1 -1
  76. package/lib/typescript/core/mixins/OxyServices.payment.d.ts +4 -5
  77. package/lib/typescript/core/mixins/OxyServices.payment.d.ts.map +1 -1
  78. package/lib/typescript/core/mixins/OxyServices.privacy.d.ts +4 -5
  79. package/lib/typescript/core/mixins/OxyServices.privacy.d.ts.map +1 -1
  80. package/lib/typescript/core/mixins/OxyServices.totp.d.ts +4 -5
  81. package/lib/typescript/core/mixins/OxyServices.totp.d.ts.map +1 -1
  82. package/lib/typescript/core/mixins/OxyServices.user.d.ts +4 -5
  83. package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
  84. package/lib/typescript/core/mixins/OxyServices.utility.d.ts +4 -5
  85. package/lib/typescript/core/mixins/OxyServices.utility.d.ts.map +1 -1
  86. package/lib/typescript/core/mixins/index.d.ts +52 -66
  87. package/lib/typescript/core/mixins/index.d.ts.map +1 -1
  88. package/lib/typescript/index.d.ts +1 -0
  89. package/lib/typescript/index.d.ts.map +1 -1
  90. package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
  91. package/lib/typescript/ui/hooks/index.d.ts +1 -0
  92. package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
  93. package/lib/typescript/ui/hooks/useFileDownloadUrl.d.ts +19 -0
  94. package/lib/typescript/ui/hooks/useFileDownloadUrl.d.ts.map +1 -0
  95. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  96. package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -1
  97. package/lib/typescript/utils/errorUtils.d.ts.map +1 -1
  98. package/package.json +1 -1
  99. package/src/core/HttpService.ts +523 -0
  100. package/src/core/OxyServices.base.ts +36 -34
  101. package/src/core/OxyServices.ts +3 -4
  102. package/src/core/mixins/OxyServices.assets.ts +2 -8
  103. package/src/core/mixins/OxyServices.user.ts +7 -6
  104. package/src/core/mixins/OxyServices.utility.ts +1 -0
  105. package/src/index.ts +1 -0
  106. package/src/ui/components/ProfileCard.tsx +4 -1
  107. package/src/ui/hooks/index.ts +2 -1
  108. package/src/ui/hooks/useFileDownloadUrl.ts +118 -0
  109. package/src/ui/screens/AccountOverviewScreen.tsx +4 -1
  110. package/src/ui/screens/karma/KarmaFAQScreen.tsx +1 -5
  111. package/src/utils/errorUtils.ts +65 -19
  112. package/lib/commonjs/core/HttpClient.js +0 -317
  113. package/lib/commonjs/core/HttpClient.js.map +0 -1
  114. package/lib/commonjs/core/RequestManager.js +0 -170
  115. package/lib/commonjs/core/RequestManager.js.map +0 -1
  116. package/lib/module/core/HttpClient.js +0 -311
  117. package/lib/module/core/HttpClient.js.map +0 -1
  118. package/lib/module/core/RequestManager.js +0 -165
  119. package/lib/module/core/RequestManager.js.map +0 -1
  120. package/lib/typescript/core/HttpClient.d.ts +0 -110
  121. package/lib/typescript/core/HttpClient.d.ts.map +0 -1
  122. package/lib/typescript/core/RequestManager.d.ts +0 -63
  123. package/lib/typescript/core/RequestManager.d.ts.map +0 -1
  124. package/src/core/HttpClient.ts +0 -346
  125. package/src/core/RequestManager.ts +0 -205
@@ -6,8 +6,7 @@
6
6
  import { jwtDecode } from 'jwt-decode';
7
7
  import type { OxyConfig as OxyConfigBase, ApiError, User } from '../models/interfaces';
8
8
  import { handleHttpError } from '../utils/errorUtils';
9
- import { HttpClient } from './HttpClient';
10
- import { RequestManager, type RequestOptions } from './RequestManager';
9
+ import { HttpService, type RequestOptions } from './HttpService';
11
10
  import { OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.errors';
12
11
 
13
12
  export interface OxyConfig extends OxyConfigBase {
@@ -26,8 +25,7 @@ interface JwtPayload {
26
25
  * Base class for OxyServices with core infrastructure
27
26
  */
28
27
  export class OxyServicesBase {
29
- public httpClient: HttpClient;
30
- public requestManager: RequestManager;
28
+ public httpService: HttpService;
31
29
  public cloudURL: string;
32
30
  public config: OxyConfig;
33
31
 
@@ -39,16 +37,13 @@ export class OxyServicesBase {
39
37
  this.config = config;
40
38
  this.cloudURL = config.cloudURL || 'https://cloud.oxy.so';
41
39
 
42
- // Initialize HTTP client (handles authentication and interceptors)
43
- this.httpClient = new HttpClient(config);
44
-
45
- // Initialize request manager (handles caching, deduplication, queuing, retry)
46
- this.requestManager = new RequestManager(this.httpClient, config);
40
+ // Initialize unified HTTP service (handles auth, caching, deduplication, queuing, retry)
41
+ this.httpService = new HttpService(config);
47
42
  }
48
43
 
49
44
  // Test-only utility to reset global tokens between jest tests
50
45
  static __resetTokensForTests(): void {
51
- HttpClient.__resetTokensForTests();
46
+ HttpService.__resetTokensForTests();
52
47
  }
53
48
 
54
49
  /**
@@ -61,7 +56,13 @@ export class OxyServicesBase {
61
56
  data?: any,
62
57
  options: RequestOptions = {}
63
58
  ): Promise<T> {
64
- return this.requestManager.request<T>(method, url, data, options);
59
+ return this.httpService.request<T>({
60
+ method,
61
+ url,
62
+ data: method !== 'GET' ? data : undefined,
63
+ params: method === 'GET' ? data : undefined,
64
+ ...options,
65
+ });
65
66
  }
66
67
 
67
68
  // ============================================================================
@@ -72,43 +73,43 @@ export class OxyServicesBase {
72
73
  * Get the configured Oxy API base URL
73
74
  */
74
75
  public getBaseURL(): string {
75
- return this.httpClient.getBaseURL();
76
+ return this.httpService.getBaseURL();
76
77
  }
77
78
 
78
79
  /**
79
- * Get the HTTP client instance
80
- * Useful for advanced use cases where direct access to the HTTP client is needed
80
+ * Get the HTTP service instance
81
+ * Useful for advanced use cases where direct access to the HTTP service is needed
81
82
  */
82
- public getClient(): HttpClient {
83
- return this.httpClient;
83
+ public getClient(): HttpService {
84
+ return this.httpService;
84
85
  }
85
86
 
86
87
  /**
87
88
  * Get performance metrics
88
89
  */
89
90
  public getMetrics() {
90
- return this.requestManager.getMetrics();
91
+ return this.httpService.getMetrics();
91
92
  }
92
93
 
93
94
  /**
94
95
  * Clear request cache
95
96
  */
96
97
  public clearCache(): void {
97
- this.requestManager.clearCache();
98
+ this.httpService.clearCache();
98
99
  }
99
100
 
100
101
  /**
101
102
  * Clear specific cache entry
102
103
  */
103
104
  public clearCacheEntry(key: string): void {
104
- this.requestManager.clearCacheEntry(key);
105
+ this.httpService.clearCacheEntry(key);
105
106
  }
106
107
 
107
108
  /**
108
109
  * Get cache statistics
109
110
  */
110
111
  public getCacheStats() {
111
- return this.requestManager.getCacheStats();
112
+ return this.httpService.getCacheStats();
112
113
  }
113
114
 
114
115
  /**
@@ -122,21 +123,21 @@ export class OxyServicesBase {
122
123
  * Set authentication tokens
123
124
  */
124
125
  public setTokens(accessToken: string, refreshToken = ''): void {
125
- this.httpClient.setTokens(accessToken, refreshToken);
126
+ this.httpService.setTokens(accessToken, refreshToken);
126
127
  }
127
128
 
128
129
  /**
129
130
  * Clear stored authentication tokens
130
131
  */
131
132
  public clearTokens(): void {
132
- this.httpClient.clearTokens();
133
+ this.httpService.clearTokens();
133
134
  }
134
135
 
135
136
  /**
136
137
  * Get the current user ID from the access token
137
138
  */
138
139
  public getCurrentUserId(): string | null {
139
- const accessToken = this.httpClient.getAccessToken();
140
+ const accessToken = this.httpService.getAccessToken();
140
141
  if (!accessToken) {
141
142
  return null;
142
143
  }
@@ -153,14 +154,14 @@ export class OxyServicesBase {
153
154
  * Check if the client has a valid access token (public method)
154
155
  */
155
156
  public hasValidToken(): boolean {
156
- return this.httpClient.hasAccessToken();
157
+ return this.httpService.hasAccessToken();
157
158
  }
158
159
 
159
160
  /**
160
161
  * Get the raw access token (for constructing anchor URLs when needed)
161
162
  */
162
163
  public getAccessToken(): string | null {
163
- return this.httpClient.getAccessToken();
164
+ return this.httpService.getAccessToken();
164
165
  }
165
166
 
166
167
  /**
@@ -183,7 +184,7 @@ export class OxyServicesBase {
183
184
  */
184
185
  public async waitForAuth(timeoutMs = 5000): Promise<boolean> {
185
186
  // Immediate synchronous check - no delay if token is ready
186
- if (this.httpClient.hasAccessToken()) {
187
+ if (this.httpService.hasAccessToken()) {
187
188
  return true;
188
189
  }
189
190
 
@@ -196,7 +197,7 @@ export class OxyServicesBase {
196
197
  while (performance.now() < maxTime) {
197
198
  await new Promise(resolve => setTimeout(resolve, pollInterval));
198
199
 
199
- if (this.httpClient.hasAccessToken()) {
200
+ if (this.httpService.hasAccessToken()) {
200
201
  return true;
201
202
  }
202
203
 
@@ -232,7 +233,7 @@ export class OxyServicesBase {
232
233
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
233
234
  try {
234
235
  // First attempt: check if we have a token
235
- if (!this.httpClient.hasAccessToken()) {
236
+ if (!this.httpService.hasAccessToken()) {
236
237
  if (attempt === 0) {
237
238
  // On first attempt, wait briefly for authentication to complete
238
239
  const authReady = await this.waitForAuth(authTimeoutMs);
@@ -252,11 +253,12 @@ export class OxyServicesBase {
252
253
  // Execute the operation
253
254
  return await operation();
254
255
 
255
- } catch (error: any) {
256
+ } catch (error: unknown) {
256
257
  const isLastAttempt = attempt === maxRetries;
257
- const isAuthError = error?.response?.status === 401 ||
258
- error?.code === 'MISSING_TOKEN' ||
259
- error?.message?.includes('Authentication') ||
258
+ const errorObj = error && typeof error === 'object' ? error as { response?: { status?: number }; code?: string; message?: string } : null;
259
+ const isAuthError = errorObj?.response?.status === 401 ||
260
+ errorObj?.code === 'MISSING_TOKEN' ||
261
+ errorObj?.message?.includes('Authentication') ||
260
262
  error instanceof OxyAuthenticationError;
261
263
 
262
264
  if (isAuthError && !isLastAttempt && !(error instanceof OxyAuthenticationTimeoutError)) {
@@ -298,12 +300,12 @@ export class OxyServicesBase {
298
300
  /**
299
301
  * Centralized error handling
300
302
  */
301
- public handleError(error: any): Error {
303
+ public handleError(error: unknown): Error {
302
304
  const api = handleHttpError(error);
303
305
  const err = new Error(api.message) as Error & { code?: string; status?: number; details?: Record<string, unknown> };
304
306
  err.code = api.code;
305
307
  err.status = api.status;
306
- err.details = api.details as any;
308
+ err.details = api.details;
307
309
  return err;
308
310
  }
309
311
 
@@ -22,8 +22,8 @@
22
22
  * const file = fileInput.files[0];
23
23
  * await oxy.uploadRawFile(file);
24
24
  *
25
- * // Get a file stream URL for <img src>
26
- * const url = oxy.getFileStreamUrl('fileId');
25
+ * // Get a file download URL for <img src>
26
+ * const url = oxy.getFileDownloadUrl('fileId', 'thumb');
27
27
  * ```
28
28
  *
29
29
  * ## Node.js (CommonJS/TypeScript)
@@ -68,8 +68,7 @@ import { composeOxyServices } from './mixins';
68
68
  * This class provides all API functionality in one simple, easy-to-use interface.
69
69
  *
70
70
  * ## Architecture
71
- * - **HttpClient**: Handles HTTP communication and authentication
72
- * - **RequestManager**: Handles caching, deduplication, queuing, and retry
71
+ * - **HttpService**: Unified HTTP service handling authentication, caching, deduplication, queuing, and retry
73
72
  * - **OxyServices**: Provides high-level API methods
74
73
  *
75
74
  * ## Mixin Composition
@@ -66,13 +66,6 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
66
66
  }
67
67
  }
68
68
 
69
- /**
70
- * Get file stream URL (direct Oxy Cloud/CDN URL, no token)
71
- */
72
- getFileStreamUrl(fileId: string): string {
73
- return `${this.getCloudURL()}/files/${fileId}/stream`;
74
- }
75
-
76
69
  /**
77
70
  * List user files
78
71
  */
@@ -235,11 +228,12 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
235
228
  // Fallback: direct upload via API to avoid CORS issues
236
229
  const fd = new FormData();
237
230
  fd.append('file', file);
238
- // Use httpClient directly for FormData uploads (bypasses RequestManager for special handling)
231
+ // Use httpService directly for FormData uploads (bypasses caching for special handling)
239
232
  await this.getClient().request({
240
233
  method: 'POST',
241
234
  url: `/api/assets/${encodeURIComponent(initResponse.fileId)}/upload-direct`,
242
235
  data: fd,
236
+ cache: false,
243
237
  });
244
238
  }
245
239
 
@@ -189,14 +189,15 @@ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T)
189
189
  */
190
190
  async downloadAccountData(format: 'json' | 'csv' = 'json'): Promise<Blob> {
191
191
  try {
192
- // Use axios instance directly for blob responses since RequestManager doesn't handle blobs
193
- const axiosInstance = this.getClient().getAxiosInstance();
194
-
195
- const response = await axiosInstance.get(`/api/users/me/data?format=${format}`, {
196
- responseType: 'blob',
192
+ // Use httpService for blob responses (it handles blob responses automatically)
193
+ const result = await this.getClient().request<Blob>({
194
+ method: 'GET',
195
+ url: `/api/users/me/data`,
196
+ params: { format },
197
+ cache: false,
197
198
  });
198
199
 
199
- return response.data as Blob;
200
+ return result;
200
201
  } catch (error) {
201
202
  throw this.handleError(error);
202
203
  }
@@ -177,6 +177,7 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
177
177
  const apiError = this.handleError(error) as any;
178
178
 
179
179
  if (debug) {
180
+ // Debug logging - using console.log is acceptable here for development
180
181
  console.log(`❌ Auth: Unexpected error:`, apiError);
181
182
  }
182
183
 
package/src/index.ts CHANGED
@@ -97,6 +97,7 @@ export { useAuthStore } from './ui/stores/authStore';
97
97
  export { useAssetStore, useAssets as useAssetsStore, useAsset, useUploadProgress, useAssetLoading, useAssetErrors, useAssetsByApp, useAssetsByEntity, useAssetUsageCount, useIsAssetLinked } from './ui/stores/assetStore';
98
98
  export { useSessionSocket } from './ui/hooks/useSessionSocket';
99
99
  export { useAssets, setOxyAssetInstance } from './ui/hooks/useAssets';
100
+ export { useFileDownloadUrl, setOxyFileUrlInstance } from './ui/hooks/useFileDownloadUrl';
100
101
 
101
102
  // UI components
102
103
  export { OxySignInButton } from './ui/components/OxySignInButton';
@@ -4,6 +4,7 @@ import { Ionicons } from '@expo/vector-icons';
4
4
  import { useI18n } from '../hooks/useI18n';
5
5
  import Avatar from './Avatar';
6
6
  import { useOxy } from '../context/OxyContext';
7
+ import { useFileDownloadUrl } from '../hooks';
7
8
  import { fontFamilies } from '../styles/fonts';
8
9
 
9
10
  interface ProfileCardProps {
@@ -33,6 +34,8 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
33
34
  const secondaryBackgroundColor = isDarkTheme ? '#222222' : '#FFFFFF';
34
35
  const primaryColor = '#0066CC';
35
36
 
37
+ const avatarUrl = useFileDownloadUrl(user?.avatar, { variant: 'thumb' }).url || undefined;
38
+
36
39
  return (
37
40
  <View style={styles.headerSection}>
38
41
  <View style={[
@@ -43,7 +46,7 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
43
46
  ]}>
44
47
  <View style={styles.userProfile}>
45
48
  <Avatar
46
- uri={user?.avatar ? oxyServices.getFileDownloadUrl(user.avatar as string, 'thumb') : undefined}
49
+ uri={user?.avatar ? avatarUrl : undefined}
47
50
  name={user?.name?.full || user?.username}
48
51
  size={60}
49
52
  theme={theme}
@@ -1 +1,2 @@
1
- export { useFollow, useFollowerCounts } from './useFollow';
1
+ export { useFollow, useFollowerCounts } from './useFollow';
2
+ export { useFileDownloadUrl, setOxyFileUrlInstance } from './useFileDownloadUrl';
@@ -0,0 +1,118 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { OxyServices } from '../../core/OxyServices';
3
+
4
+ let oxyInstance: OxyServices | null = null;
5
+
6
+ export const setOxyFileUrlInstance = (instance: OxyServices) => {
7
+ oxyInstance = instance;
8
+ };
9
+
10
+ export interface UseFileDownloadUrlOptions {
11
+ variant?: string;
12
+ expiresIn?: number;
13
+ }
14
+
15
+ export interface UseFileDownloadUrlResult {
16
+ url: string | null;
17
+ loading: boolean;
18
+ error: Error | null;
19
+ }
20
+
21
+ /**
22
+ * Hook to resolve a file's download URL asynchronously.
23
+ *
24
+ * Prefers `getFileDownloadUrlAsync` and falls back to the synchronous
25
+ * `getFileDownloadUrl` helper if the async call fails.
26
+ */
27
+ export const useFileDownloadUrl = (
28
+ fileId?: string | null,
29
+ options?: UseFileDownloadUrlOptions
30
+ ): UseFileDownloadUrlResult => {
31
+ const [url, setUrl] = useState<string | null>(null);
32
+ const [loading, setLoading] = useState(false);
33
+ const [error, setError] = useState<Error | null>(null);
34
+
35
+ useEffect(() => {
36
+ if (!fileId) {
37
+ setUrl(null);
38
+ setLoading(false);
39
+ setError(null);
40
+ return;
41
+ }
42
+
43
+ if (!oxyInstance) {
44
+ // Fail silently but don't crash the UI – caller can decide what to do with null URL.
45
+ setUrl(null);
46
+ setLoading(false);
47
+ setError(new Error('OxyServices instance not configured for useFileDownloadUrl'));
48
+ return;
49
+ }
50
+
51
+ let cancelled = false;
52
+
53
+ const load = async () => {
54
+ setLoading(true);
55
+ setError(null);
56
+
57
+ // Store instance in local variable for TypeScript null checking
58
+ const instance = oxyInstance;
59
+ if (!instance) {
60
+ setLoading(false);
61
+ setError(new Error('OxyServices instance not configured for useFileDownloadUrl'));
62
+ return;
63
+ }
64
+
65
+ try {
66
+ const { variant, expiresIn } = options || {};
67
+ let resolvedUrl: string | null = null;
68
+
69
+ if (typeof instance.getFileDownloadUrlAsync === 'function') {
70
+ resolvedUrl = await instance.getFileDownloadUrlAsync(fileId, variant, expiresIn);
71
+ }
72
+
73
+ if (!resolvedUrl && typeof instance.getFileDownloadUrl === 'function') {
74
+ resolvedUrl = instance.getFileDownloadUrl(fileId, variant, expiresIn);
75
+ }
76
+
77
+ if (!cancelled) {
78
+ setUrl(resolvedUrl || null);
79
+ }
80
+ } catch (err: any) {
81
+ // Fallback to sync URL on error where possible
82
+ try {
83
+ if (typeof instance.getFileDownloadUrl === 'function') {
84
+ const { variant, expiresIn } = options || {};
85
+ const fallbackUrl = instance.getFileDownloadUrl(fileId, variant, expiresIn);
86
+ if (!cancelled) {
87
+ setUrl(fallbackUrl || null);
88
+ setError(err instanceof Error ? err : new Error(String(err)));
89
+ }
90
+ return;
91
+ }
92
+ } catch {
93
+ // ignore secondary failure, we'll surface the original error below
94
+ }
95
+
96
+ if (!cancelled) {
97
+ setError(err instanceof Error ? err : new Error(String(err)));
98
+ }
99
+ } finally {
100
+ if (!cancelled) {
101
+ setLoading(false);
102
+ }
103
+ }
104
+ };
105
+
106
+ load();
107
+
108
+ return () => {
109
+ cancelled = true;
110
+ };
111
+ }, [fileId, options?.variant, options?.expiresIn]);
112
+
113
+ return { url, loading, error };
114
+ };
115
+
116
+
117
+
118
+
@@ -420,7 +420,10 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
420
420
  <>
421
421
  <View style={styles.userIcon}>
422
422
  {account.avatar ? (
423
- <Image source={{ uri: oxyServices.getFileStreamUrl(account.avatar as string) }} style={styles.accountAvatarImage} />
423
+ <Image
424
+ source={{ uri: oxyServices.getFileDownloadUrl(account.avatar as string, 'thumb') }}
425
+ style={styles.accountAvatarImage}
426
+ />
424
427
  ) : (
425
428
  <View style={styles.accountAvatarFallback}>
426
429
  <Text style={styles.accountAvatarText}>
@@ -1,14 +1,10 @@
1
1
  import React, { useState, useMemo, useCallback } from 'react';
2
- import { View, Text, StyleSheet, ScrollView, Platform, TouchableOpacity, TextInput, LayoutAnimation, UIManager } from 'react-native';
2
+ import { View, Text, StyleSheet, ScrollView, Platform, TouchableOpacity, TextInput, LayoutAnimation } from 'react-native';
3
3
  import type { BaseScreenProps } from '../../navigation/types';
4
4
  import { Ionicons } from '@expo/vector-icons';
5
5
  import { Header } from '../../components';
6
6
  import { useI18n } from '../../hooks/useI18n';
7
7
 
8
- if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
9
- UIManager.setLayoutAnimationEnabledExperimental(true);
10
- }
11
-
12
8
  const FAQ_KEYS = ['what', 'earn', 'lose', 'use', 'transfer', 'support'] as const;
13
9
 
14
10
  /**
@@ -1,4 +1,5 @@
1
1
  import type { ApiError } from '../models/interfaces';
2
+ import { logger } from './loggerUtils';
2
3
 
3
4
  /**
4
5
  * Error handling utilities for consistent error processing
@@ -61,32 +62,72 @@ export function handleHttpError(error: unknown): ApiError {
61
62
  return error as ApiError;
62
63
  }
63
64
 
64
- // Handle axios errors - check if it looks like an axios error
65
- if (error && typeof error === 'object' && 'response' in error) {
66
- const axiosError = error as { response?: { status: number; data?: { message?: string; code?: string } } };
67
- if (axiosError.response) {
68
- const { status, data } = axiosError.response;
69
-
65
+ // Handle AbortError (timeout or cancelled requests)
66
+ if (error instanceof Error && error.name === 'AbortError') {
67
+ return createApiError(
68
+ 'Request timeout or cancelled',
69
+ ErrorCodes.TIMEOUT,
70
+ 0
71
+ );
72
+ }
73
+
74
+ // Handle TypeError (network failures, CORS, etc.)
75
+ if (error instanceof TypeError) {
76
+ // Check if it's a network-related TypeError
77
+ if (error.message.includes('fetch') || error.message.includes('network') || error.message.includes('Failed to fetch')) {
70
78
  return createApiError(
71
- data?.message || `HTTP ${status} error`,
72
- data?.code || getErrorCodeFromStatus(status),
73
- status,
74
- data
79
+ 'Network error - failed to connect to server',
80
+ ErrorCodes.NETWORK_ERROR,
81
+ 0
75
82
  );
76
83
  }
77
- }
78
-
79
- // Handle network errors - check if it looks like a network error
80
- if (error && typeof error === 'object' && 'request' in error) {
81
84
  return createApiError(
82
- 'Network error - no response received',
85
+ error.message || 'Network error occurred',
83
86
  ErrorCodes.NETWORK_ERROR,
84
87
  0
85
88
  );
86
89
  }
87
90
 
91
+ // Handle fetch Response errors - check if it has response property with status
92
+ if (error && typeof error === 'object' && 'response' in error) {
93
+ const fetchError = error as {
94
+ response?: {
95
+ status: number;
96
+ statusText?: string;
97
+ };
98
+ status?: number;
99
+ message?: string;
100
+ };
101
+
102
+ const status = fetchError.response?.status || fetchError.status;
103
+ if (status) {
104
+ return createApiError(
105
+ fetchError.message || `HTTP ${status} error`,
106
+ getErrorCodeFromStatus(status),
107
+ status
108
+ );
109
+ }
110
+ }
111
+
88
112
  // Handle standard errors
89
113
  if (error instanceof Error) {
114
+ // Check for common error patterns
115
+ if (error.message.includes('timeout') || error.message.includes('aborted')) {
116
+ return createApiError(
117
+ 'Request timeout',
118
+ ErrorCodes.TIMEOUT,
119
+ 0
120
+ );
121
+ }
122
+
123
+ if (error.message.includes('network') || error.message.includes('fetch')) {
124
+ return createApiError(
125
+ error.message || 'Network error occurred',
126
+ ErrorCodes.NETWORK_ERROR,
127
+ 0
128
+ );
129
+ }
130
+
90
131
  return createApiError(
91
132
  error.message || 'Unknown error occurred',
92
133
  ErrorCodes.INTERNAL_ERROR,
@@ -148,12 +189,17 @@ export function validateRequiredFields(data: Record<string, unknown>, fields: st
148
189
  * Safe error logging with context
149
190
  */
150
191
  export function logError(error: unknown, context?: string): void {
151
- const prefix = context ? `[${context}]` : '[Error]';
152
-
153
192
  if (error instanceof Error) {
154
- console.error(`${prefix} ${error.message}`, error.stack);
193
+ logger.error(error.message, {
194
+ component: context || 'errorUtils',
195
+ method: 'logError',
196
+ stack: error.stack,
197
+ });
155
198
  } else {
156
- console.error(`${prefix}`, error);
199
+ logger.error(String(error), {
200
+ component: context || 'errorUtils',
201
+ method: 'logError',
202
+ });
157
203
  }
158
204
  }
159
205