@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.
- package/README.md +5 -6
- package/lib/commonjs/core/HttpService.js +481 -0
- package/lib/commonjs/core/HttpService.js.map +1 -0
- package/lib/commonjs/core/OxyServices.base.js +29 -26
- package/lib/commonjs/core/OxyServices.base.js.map +1 -1
- package/lib/commonjs/core/OxyServices.js +3 -4
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.assets.js +3 -9
- package/lib/commonjs/core/mixins/OxyServices.assets.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.user.js +9 -5
- package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.utility.js +1 -0
- package/lib/commonjs/core/mixins/OxyServices.utility.js.map +1 -1
- package/lib/commonjs/index.js +15 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileCard.js +5 -1
- package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
- package/lib/commonjs/ui/hooks/index.js +13 -0
- package/lib/commonjs/ui/hooks/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFileDownloadUrl.js +103 -0
- package/lib/commonjs/ui/hooks/useFileDownloadUrl.js.map +1 -0
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js +0 -3
- package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
- package/lib/commonjs/utils/errorUtils.js +35 -15
- package/lib/commonjs/utils/errorUtils.js.map +1 -1
- package/lib/module/core/HttpService.js +476 -0
- package/lib/module/core/HttpService.js.map +1 -0
- package/lib/module/core/OxyServices.base.js +29 -26
- package/lib/module/core/OxyServices.base.js.map +1 -1
- package/lib/module/core/OxyServices.js +3 -4
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.assets.js +3 -9
- package/lib/module/core/mixins/OxyServices.assets.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.user.js +9 -5
- package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.utility.js +1 -0
- package/lib/module/core/mixins/OxyServices.utility.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/ProfileCard.js +5 -1
- package/lib/module/ui/components/ProfileCard.js.map +1 -1
- package/lib/module/ui/hooks/index.js +1 -0
- package/lib/module/ui/hooks/index.js.map +1 -1
- package/lib/module/ui/hooks/useFileDownloadUrl.js +97 -0
- package/lib/module/ui/hooks/useFileDownloadUrl.js.map +1 -0
- package/lib/module/ui/screens/AccountOverviewScreen.js +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaFAQScreen.js +1 -4
- package/lib/module/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
- package/lib/module/utils/errorUtils.js +36 -15
- package/lib/module/utils/errorUtils.js.map +1 -1
- package/lib/typescript/core/HttpService.d.ts +111 -0
- package/lib/typescript/core/HttpService.d.ts.map +1 -0
- package/lib/typescript/core/OxyServices.base.d.ts +6 -8
- package/lib/typescript/core/OxyServices.base.d.ts.map +1 -1
- package/lib/typescript/core/OxyServices.d.ts +3 -4
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.analytics.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.analytics.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.assets.d.ts +4 -9
- package/lib/typescript/core/mixins/OxyServices.assets.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.developer.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.developer.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.devices.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.devices.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.karma.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.karma.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.language.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.language.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.location.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.location.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.payment.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.payment.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.privacy.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.privacy.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.totp.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.totp.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.user.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.utility.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.utility.d.ts.map +1 -1
- package/lib/typescript/core/mixins/index.d.ts +52 -66
- package/lib/typescript/core/mixins/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/index.d.ts +1 -0
- package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useFileDownloadUrl.d.ts +19 -0
- package/lib/typescript/ui/hooks/useFileDownloadUrl.d.ts.map +1 -0
- package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -1
- package/lib/typescript/utils/errorUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/HttpService.ts +523 -0
- package/src/core/OxyServices.base.ts +36 -34
- package/src/core/OxyServices.ts +3 -4
- package/src/core/mixins/OxyServices.assets.ts +2 -8
- package/src/core/mixins/OxyServices.user.ts +7 -6
- package/src/core/mixins/OxyServices.utility.ts +1 -0
- package/src/index.ts +1 -0
- package/src/ui/components/ProfileCard.tsx +4 -1
- package/src/ui/hooks/index.ts +2 -1
- package/src/ui/hooks/useFileDownloadUrl.ts +118 -0
- package/src/ui/screens/AccountOverviewScreen.tsx +4 -1
- package/src/ui/screens/karma/KarmaFAQScreen.tsx +1 -5
- package/src/utils/errorUtils.ts +65 -19
- package/lib/commonjs/core/HttpClient.js +0 -317
- package/lib/commonjs/core/HttpClient.js.map +0 -1
- package/lib/commonjs/core/RequestManager.js +0 -170
- package/lib/commonjs/core/RequestManager.js.map +0 -1
- package/lib/module/core/HttpClient.js +0 -311
- package/lib/module/core/HttpClient.js.map +0 -1
- package/lib/module/core/RequestManager.js +0 -165
- package/lib/module/core/RequestManager.js.map +0 -1
- package/lib/typescript/core/HttpClient.d.ts +0 -110
- package/lib/typescript/core/HttpClient.d.ts.map +0 -1
- package/lib/typescript/core/RequestManager.d.ts +0 -63
- package/lib/typescript/core/RequestManager.d.ts.map +0 -1
- package/src/core/HttpClient.ts +0 -346
- 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 {
|
|
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
|
|
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
|
|
43
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
76
|
+
return this.httpService.getBaseURL();
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
|
-
* Get the HTTP
|
|
80
|
-
* Useful for advanced use cases where direct access to the HTTP
|
|
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():
|
|
83
|
-
return this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
256
|
+
} catch (error: unknown) {
|
|
256
257
|
const isLastAttempt = attempt === maxRetries;
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
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:
|
|
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
|
|
308
|
+
err.details = api.details;
|
|
307
309
|
return err;
|
|
308
310
|
}
|
|
309
311
|
|
package/src/core/OxyServices.ts
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* const file = fileInput.files[0];
|
|
23
23
|
* await oxy.uploadRawFile(file);
|
|
24
24
|
*
|
|
25
|
-
* // Get a file
|
|
26
|
-
* const url = oxy.
|
|
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
|
-
* - **
|
|
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
|
|
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
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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 ?
|
|
49
|
+
uri={user?.avatar ? avatarUrl : undefined}
|
|
47
50
|
name={user?.name?.full || user?.username}
|
|
48
51
|
size={60}
|
|
49
52
|
theme={theme}
|
package/src/ui/hooks/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
/**
|
package/src/utils/errorUtils.ts
CHANGED
|
@@ -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
|
|
65
|
-
if (error &&
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
-
|
|
193
|
+
logger.error(error.message, {
|
|
194
|
+
component: context || 'errorUtils',
|
|
195
|
+
method: 'logError',
|
|
196
|
+
stack: error.stack,
|
|
197
|
+
});
|
|
155
198
|
} else {
|
|
156
|
-
|
|
199
|
+
logger.error(String(error), {
|
|
200
|
+
component: context || 'errorUtils',
|
|
201
|
+
method: 'logError',
|
|
202
|
+
});
|
|
157
203
|
}
|
|
158
204
|
}
|
|
159
205
|
|