@oxyhq/services 5.13.25 → 5.13.26
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/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 +1 -2
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.assets.js +3 -2
- 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/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 +1 -2
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.assets.js +3 -2
- 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/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 +1 -2
- 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 -5
- 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 -65
- package/lib/typescript/core/mixins/index.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 +1 -2
- package/src/core/mixins/OxyServices.assets.ts +2 -1
- package/src/core/mixins/OxyServices.user.ts +7 -6
- package/src/core/mixins/OxyServices.utility.ts +1 -0
- 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
|
@@ -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
|
|
@@ -235,11 +235,12 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
235
235
|
// Fallback: direct upload via API to avoid CORS issues
|
|
236
236
|
const fd = new FormData();
|
|
237
237
|
fd.append('file', file);
|
|
238
|
-
// Use
|
|
238
|
+
// Use httpService directly for FormData uploads (bypasses caching for special handling)
|
|
239
239
|
await this.getClient().request({
|
|
240
240
|
method: 'POST',
|
|
241
241
|
url: `/api/assets/${encodeURIComponent(initResponse.fileId)}/upload-direct`,
|
|
242
242
|
data: fd,
|
|
243
|
+
cache: false,
|
|
243
244
|
});
|
|
244
245
|
}
|
|
245
246
|
|
|
@@ -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/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
|
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.HttpClient = void 0;
|
|
7
|
-
var _axios = _interopRequireDefault(require("axios"));
|
|
8
|
-
var _jwtDecode = require("jwt-decode");
|
|
9
|
-
var _errorUtils = require("../utils/errorUtils");
|
|
10
|
-
var _requestUtils = require("../utils/requestUtils");
|
|
11
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
-
/**
|
|
13
|
-
* HTTP Client Service
|
|
14
|
-
*
|
|
15
|
-
* Handles all HTTP communication with authentication, interceptors, and error handling.
|
|
16
|
-
* This is the single source of truth for making authenticated HTTP requests.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Token store for authentication
|
|
21
|
-
*/
|
|
22
|
-
class TokenStore {
|
|
23
|
-
accessToken = null;
|
|
24
|
-
refreshToken = null;
|
|
25
|
-
constructor() {}
|
|
26
|
-
static getInstance() {
|
|
27
|
-
if (!TokenStore.instance) {
|
|
28
|
-
TokenStore.instance = new TokenStore();
|
|
29
|
-
}
|
|
30
|
-
return TokenStore.instance;
|
|
31
|
-
}
|
|
32
|
-
setTokens(accessToken, refreshToken = '') {
|
|
33
|
-
this.accessToken = accessToken;
|
|
34
|
-
this.refreshToken = refreshToken;
|
|
35
|
-
}
|
|
36
|
-
getAccessToken() {
|
|
37
|
-
return this.accessToken;
|
|
38
|
-
}
|
|
39
|
-
getRefreshToken() {
|
|
40
|
-
return this.refreshToken;
|
|
41
|
-
}
|
|
42
|
-
clearTokens() {
|
|
43
|
-
this.accessToken = null;
|
|
44
|
-
this.refreshToken = null;
|
|
45
|
-
}
|
|
46
|
-
hasAccessToken() {
|
|
47
|
-
return !!this.accessToken;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* HTTP Client Service
|
|
53
|
-
*
|
|
54
|
-
* Manages Axios instance with authentication interceptors.
|
|
55
|
-
* All HTTP requests should go through this service to ensure authentication.
|
|
56
|
-
*/
|
|
57
|
-
class HttpClient {
|
|
58
|
-
constructor(config) {
|
|
59
|
-
this.baseURL = config.baseURL;
|
|
60
|
-
this.tokenStore = TokenStore.getInstance();
|
|
61
|
-
this.logger = new _requestUtils.SimpleLogger(config.enableLogging || false, config.logLevel || 'error', 'HttpClient');
|
|
62
|
-
const timeout = config.requestTimeout || 5000;
|
|
63
|
-
|
|
64
|
-
// Create Axios instance with optimized configuration
|
|
65
|
-
this.client = _axios.default.create({
|
|
66
|
-
baseURL: config.baseURL,
|
|
67
|
-
timeout,
|
|
68
|
-
headers: {
|
|
69
|
-
'Accept': 'application/json'
|
|
70
|
-
},
|
|
71
|
-
// Enable HTTP keep-alive for connection reuse (Node.js only)
|
|
72
|
-
...(typeof process !== 'undefined' && process.env && typeof window === 'undefined' && typeof require !== 'undefined' ? {
|
|
73
|
-
httpAgent: new (require('http').Agent)({
|
|
74
|
-
keepAlive: true,
|
|
75
|
-
keepAliveMsecs: 1000,
|
|
76
|
-
maxSockets: 50
|
|
77
|
-
}),
|
|
78
|
-
httpsAgent: new (require('https').Agent)({
|
|
79
|
-
keepAlive: true,
|
|
80
|
-
keepAliveMsecs: 1000,
|
|
81
|
-
maxSockets: 50
|
|
82
|
-
})
|
|
83
|
-
} : {})
|
|
84
|
-
});
|
|
85
|
-
this.setupInterceptors();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Setup axios interceptors for authentication and error handling
|
|
90
|
-
*/
|
|
91
|
-
setupInterceptors() {
|
|
92
|
-
// Request interceptor: Add authentication header
|
|
93
|
-
this.client.interceptors.request.use(async req => {
|
|
94
|
-
const accessToken = this.tokenStore.getAccessToken();
|
|
95
|
-
if (!accessToken) {
|
|
96
|
-
return req;
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
const decoded = (0, _jwtDecode.jwtDecode)(accessToken);
|
|
100
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
101
|
-
|
|
102
|
-
// If token expires in less than 60 seconds, refresh it
|
|
103
|
-
if (decoded.exp && decoded.exp - currentTime < 60) {
|
|
104
|
-
if (decoded.sessionId) {
|
|
105
|
-
try {
|
|
106
|
-
// Create a new axios instance to avoid interceptor recursion
|
|
107
|
-
const refreshClient = _axios.default.create({
|
|
108
|
-
baseURL: this.client.defaults.baseURL,
|
|
109
|
-
timeout: this.client.defaults.timeout
|
|
110
|
-
});
|
|
111
|
-
const res = await refreshClient.get(`/api/session/token/${decoded.sessionId}`);
|
|
112
|
-
this.tokenStore.setTokens(res.data.accessToken);
|
|
113
|
-
req.headers.Authorization = `Bearer ${res.data.accessToken}`;
|
|
114
|
-
this.logger.debug('Token refreshed');
|
|
115
|
-
} catch (refreshError) {
|
|
116
|
-
// If refresh fails, use current token anyway
|
|
117
|
-
req.headers.Authorization = `Bearer ${accessToken}`;
|
|
118
|
-
this.logger.warn('Token refresh failed, using current token');
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
req.headers.Authorization = `Bearer ${accessToken}`;
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
req.headers.Authorization = `Bearer ${accessToken}`;
|
|
125
|
-
}
|
|
126
|
-
} catch (error) {
|
|
127
|
-
this.logger.error('Error processing token:', error);
|
|
128
|
-
// Even if there's an error, still try to use the token
|
|
129
|
-
req.headers.Authorization = `Bearer ${accessToken}`;
|
|
130
|
-
}
|
|
131
|
-
return req;
|
|
132
|
-
}, error => {
|
|
133
|
-
this.logger.error('Request interceptor error:', error);
|
|
134
|
-
return Promise.reject(error);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Response interceptor: Handle auth errors
|
|
138
|
-
this.client.interceptors.response.use(response => response, error => {
|
|
139
|
-
if (error.response?.status === 401) {
|
|
140
|
-
this.logger.warn('401 Unauthorized, clearing tokens');
|
|
141
|
-
this.tokenStore.clearTokens();
|
|
142
|
-
}
|
|
143
|
-
return Promise.reject(error);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Get the underlying Axios instance
|
|
149
|
-
* Use this only when you need direct access to Axios features
|
|
150
|
-
*/
|
|
151
|
-
getAxiosInstance() {
|
|
152
|
-
return this.client;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Make a raw HTTP request (no caching, deduplication, etc.)
|
|
157
|
-
* Use this for requests that need to bypass performance features
|
|
158
|
-
*/
|
|
159
|
-
async request(config) {
|
|
160
|
-
try {
|
|
161
|
-
const response = await this.client.request({
|
|
162
|
-
method: config.method,
|
|
163
|
-
url: config.url,
|
|
164
|
-
data: config.data,
|
|
165
|
-
params: config.params,
|
|
166
|
-
timeout: config.timeout,
|
|
167
|
-
signal: config.signal
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// Unwrap standardized API response format: { data: ... }
|
|
171
|
-
// This handles responses from sendSuccess() and sendPaginated() helpers
|
|
172
|
-
const responseData = response.data;
|
|
173
|
-
|
|
174
|
-
// Handle paginated responses: { data: [...], pagination: {...} }
|
|
175
|
-
// Return the data array directly - the calling method will wrap it appropriately
|
|
176
|
-
if (responseData && typeof responseData === 'object' && 'data' in responseData && 'pagination' in responseData) {
|
|
177
|
-
// For paginated responses, return the data array directly
|
|
178
|
-
// The calling methods like getUserFollowers/getUserFollowing will handle wrapping
|
|
179
|
-
// We return the whole response so methods can access both data and pagination
|
|
180
|
-
return responseData;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Handle regular success responses: { data: ... }
|
|
184
|
-
if (responseData && typeof responseData === 'object' && 'data' in responseData && !Array.isArray(responseData)) {
|
|
185
|
-
return responseData.data;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Return as-is for responses that don't use sendSuccess wrapper
|
|
189
|
-
return responseData;
|
|
190
|
-
} catch (error) {
|
|
191
|
-
throw (0, _errorUtils.handleHttpError)(error);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* GET request convenience method
|
|
197
|
-
*/
|
|
198
|
-
async get(url, config) {
|
|
199
|
-
const response = await this.request({
|
|
200
|
-
method: 'GET',
|
|
201
|
-
url,
|
|
202
|
-
params: config?.params,
|
|
203
|
-
timeout: config?.timeout,
|
|
204
|
-
signal: config?.signal
|
|
205
|
-
});
|
|
206
|
-
return {
|
|
207
|
-
data: response
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* POST request convenience method
|
|
213
|
-
*/
|
|
214
|
-
async post(url, data, config) {
|
|
215
|
-
const response = await this.request({
|
|
216
|
-
method: 'POST',
|
|
217
|
-
url,
|
|
218
|
-
data,
|
|
219
|
-
timeout: config?.timeout,
|
|
220
|
-
signal: config?.signal
|
|
221
|
-
});
|
|
222
|
-
return {
|
|
223
|
-
data: response
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* PUT request convenience method
|
|
229
|
-
*/
|
|
230
|
-
async put(url, data, config) {
|
|
231
|
-
const response = await this.request({
|
|
232
|
-
method: 'PUT',
|
|
233
|
-
url,
|
|
234
|
-
data,
|
|
235
|
-
timeout: config?.timeout,
|
|
236
|
-
signal: config?.signal
|
|
237
|
-
});
|
|
238
|
-
return {
|
|
239
|
-
data: response
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* PATCH request convenience method
|
|
245
|
-
*/
|
|
246
|
-
async patch(url, data, config) {
|
|
247
|
-
const response = await this.request({
|
|
248
|
-
method: 'PATCH',
|
|
249
|
-
url,
|
|
250
|
-
data,
|
|
251
|
-
timeout: config?.timeout,
|
|
252
|
-
signal: config?.signal
|
|
253
|
-
});
|
|
254
|
-
return {
|
|
255
|
-
data: response
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* DELETE request convenience method
|
|
261
|
-
*/
|
|
262
|
-
async delete(url, config) {
|
|
263
|
-
const response = await this.request({
|
|
264
|
-
method: 'DELETE',
|
|
265
|
-
url,
|
|
266
|
-
timeout: config?.timeout,
|
|
267
|
-
signal: config?.signal
|
|
268
|
-
});
|
|
269
|
-
return {
|
|
270
|
-
data: response
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Get base URL
|
|
276
|
-
*/
|
|
277
|
-
getBaseURL() {
|
|
278
|
-
return this.baseURL;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Set authentication tokens
|
|
283
|
-
*/
|
|
284
|
-
setTokens(accessToken, refreshToken = '') {
|
|
285
|
-
this.tokenStore.setTokens(accessToken, refreshToken);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Clear authentication tokens
|
|
290
|
-
*/
|
|
291
|
-
clearTokens() {
|
|
292
|
-
this.tokenStore.clearTokens();
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Get access token
|
|
297
|
-
*/
|
|
298
|
-
getAccessToken() {
|
|
299
|
-
return this.tokenStore.getAccessToken();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Check if has access token
|
|
304
|
-
*/
|
|
305
|
-
hasAccessToken() {
|
|
306
|
-
return this.tokenStore.hasAccessToken();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Test-only utility to reset global tokens between jest tests
|
|
310
|
-
static __resetTokensForTests() {
|
|
311
|
-
try {
|
|
312
|
-
TokenStore.getInstance().clearTokens();
|
|
313
|
-
} catch {}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
exports.HttpClient = HttpClient;
|
|
317
|
-
//# sourceMappingURL=HttpClient.js.map
|