@xbg.solutions/bpsk-utils-api-client 1.2.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.
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +14 -0
- package/lib/index.js.map +1 -0
- package/lib/services/api/api.service.d.ts +67 -0
- package/lib/services/api/api.service.d.ts.map +1 -0
- package/lib/services/api/api.service.js +344 -0
- package/lib/services/api/api.service.js.map +1 -0
- package/lib/services/api/index.d.ts +13 -0
- package/lib/services/api/index.d.ts.map +1 -0
- package/lib/services/api/index.js +15 -0
- package/lib/services/api/index.js.map +1 -0
- package/lib/services/api/request-handler.d.ts +17 -0
- package/lib/services/api/request-handler.d.ts.map +1 -0
- package/lib/services/api/request-handler.js +289 -0
- package/lib/services/api/request-handler.js.map +1 -0
- package/lib/services/api/response-handler.d.ts +16 -0
- package/lib/services/api/response-handler.d.ts.map +1 -0
- package/lib/services/api/response-handler.js +254 -0
- package/lib/services/api/response-handler.js.map +1 -0
- package/lib/services/caching/api-cache.service.d.ts +97 -0
- package/lib/services/caching/api-cache.service.d.ts.map +1 -0
- package/lib/services/caching/api-cache.service.js +294 -0
- package/lib/services/caching/api-cache.service.js.map +1 -0
- package/lib/services/caching/cache.service.d.ts +86 -0
- package/lib/services/caching/cache.service.d.ts.map +1 -0
- package/lib/services/caching/cache.service.js +519 -0
- package/lib/services/caching/cache.service.js.map +1 -0
- package/lib/services/caching/index.d.ts +5 -0
- package/lib/services/caching/index.d.ts.map +1 -0
- package/lib/services/caching/index.js +3 -0
- package/lib/services/caching/index.js.map +1 -0
- package/lib/stores/api.service.d.ts +6 -0
- package/lib/stores/api.service.d.ts.map +1 -0
- package/lib/stores/api.service.js +6 -0
- package/lib/stores/api.service.js.map +1 -0
- package/lib/stores/request-handler.d.ts +6 -0
- package/lib/stores/request-handler.d.ts.map +1 -0
- package/lib/stores/request-handler.js +6 -0
- package/lib/stores/request-handler.js.map +1 -0
- package/lib/stores/response-handler.d.ts +6 -0
- package/lib/stores/response-handler.d.ts.map +1 -0
- package/lib/stores/response-handler.js +6 -0
- package/lib/stores/response-handler.js.map +1 -0
- package/lib/utils/cache-helpers.d.ts +112 -0
- package/lib/utils/cache-helpers.d.ts.map +1 -0
- package/lib/utils/cache-helpers.js +302 -0
- package/lib/utils/cache-helpers.js.map +1 -0
- package/package.json +27 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lib/services/api/request-handler.ts
|
|
3
|
+
* API Request Handler
|
|
4
|
+
*
|
|
5
|
+
* Handles API request preparation with support for:
|
|
6
|
+
* - Authentication token inclusion
|
|
7
|
+
* - CSRF protection
|
|
8
|
+
* - Request timeout management
|
|
9
|
+
* - Header normalization
|
|
10
|
+
* - Data serialization
|
|
11
|
+
*/
|
|
12
|
+
import { loggerService } from '@xbg.solutions/bpsk-core';
|
|
13
|
+
import { tokenService } from '@xbg.solutions/bpsk-utils-firebase-auth';
|
|
14
|
+
import { csrfProtection } from '@xbg.solutions/bpsk-utils-csrf';
|
|
15
|
+
import { inputSanitizer } from '@xbg.solutions/bpsk-utils-sanitizer';
|
|
16
|
+
import { ApiError, NetworkError, normalizeError } from '@xbg.solutions/bpsk-core';
|
|
17
|
+
import { API_CONSTANTS } from '@xbg.solutions/bpsk-core';
|
|
18
|
+
// Create a context-aware logger
|
|
19
|
+
const requestLogger = loggerService.withContext('RequestHandler');
|
|
20
|
+
/**
|
|
21
|
+
* Creates a timeout promise that rejects after the specified time
|
|
22
|
+
*
|
|
23
|
+
* @param ms Timeout in milliseconds
|
|
24
|
+
* @param controller AbortController to abort the request
|
|
25
|
+
* @param url Request URL for error context
|
|
26
|
+
* @returns A promise that rejects after the timeout
|
|
27
|
+
*/
|
|
28
|
+
function createRequestTimeout(ms, controller, url) {
|
|
29
|
+
return new Promise((_, reject) => {
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
controller.abort();
|
|
32
|
+
const timeoutError = new NetworkError('Request timeout', {
|
|
33
|
+
isTimeout: true,
|
|
34
|
+
context: { endpoint: url },
|
|
35
|
+
userMessage: 'The request took too long to complete. Please try again.'
|
|
36
|
+
});
|
|
37
|
+
reject(timeoutError);
|
|
38
|
+
}, ms);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Request Handler implementation
|
|
43
|
+
*/
|
|
44
|
+
export const requestHandler = {
|
|
45
|
+
/**
|
|
46
|
+
* Prepares a request with proper headers, authentication, and CSRF protection
|
|
47
|
+
*/
|
|
48
|
+
async prepareRequest(method, url, data, options = {}) {
|
|
49
|
+
try {
|
|
50
|
+
const logContext = {
|
|
51
|
+
method,
|
|
52
|
+
url,
|
|
53
|
+
hasData: !!data
|
|
54
|
+
};
|
|
55
|
+
// Log the preparation start
|
|
56
|
+
requestLogger.info(`Preparing ${method} request to ${url}`, logContext);
|
|
57
|
+
// Create request headers combining defaults with provided options
|
|
58
|
+
const headers = new Headers(API_CONSTANTS.REQUEST_CONFIG.headers);
|
|
59
|
+
// Add any custom headers from options
|
|
60
|
+
if (options.headers) {
|
|
61
|
+
Object.entries(options.headers).forEach(([key, value]) => {
|
|
62
|
+
headers.set(key, value);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Create base request options
|
|
66
|
+
const requestOptions = {
|
|
67
|
+
method,
|
|
68
|
+
credentials: options.credentials || API_CONSTANTS.REQUEST_CONFIG.credentials,
|
|
69
|
+
headers
|
|
70
|
+
};
|
|
71
|
+
// Copy valid RequestInit properties from options
|
|
72
|
+
if (options.mode)
|
|
73
|
+
requestOptions.mode = options.mode;
|
|
74
|
+
if (options.enableCache)
|
|
75
|
+
requestOptions.cache = 'default';
|
|
76
|
+
if (options.redirect)
|
|
77
|
+
requestOptions.redirect = options.redirect;
|
|
78
|
+
if (options.referrer)
|
|
79
|
+
requestOptions.referrer = options.referrer;
|
|
80
|
+
if (options.referrerPolicy)
|
|
81
|
+
requestOptions.referrerPolicy = options.referrerPolicy;
|
|
82
|
+
if (options.integrity)
|
|
83
|
+
requestOptions.integrity = options.integrity;
|
|
84
|
+
if (options.keepalive)
|
|
85
|
+
requestOptions.keepalive = options.keepalive;
|
|
86
|
+
// Add auth token if user is authenticated
|
|
87
|
+
if (tokenService.isAuthenticated()) {
|
|
88
|
+
const token = tokenService.getToken();
|
|
89
|
+
if (token) {
|
|
90
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
91
|
+
requestLogger.info('Added authentication token to request', {
|
|
92
|
+
...logContext,
|
|
93
|
+
tokenPresent: true
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Prepare request body for non-GET requests
|
|
98
|
+
if (data && method !== 'GET') {
|
|
99
|
+
// Don't sanitize file uploads or FormData
|
|
100
|
+
if (data instanceof FormData) {
|
|
101
|
+
requestOptions.body = data;
|
|
102
|
+
// Remove content-type so browser can set it with correct boundary
|
|
103
|
+
headers.delete('Content-Type');
|
|
104
|
+
requestLogger.info('Prepared FormData request body', logContext);
|
|
105
|
+
}
|
|
106
|
+
else if (data instanceof Blob || data instanceof ArrayBuffer) {
|
|
107
|
+
requestOptions.body = data;
|
|
108
|
+
requestLogger.info('Prepared binary request body', logContext);
|
|
109
|
+
}
|
|
110
|
+
else if (options.body) {
|
|
111
|
+
// Use body from options if provided
|
|
112
|
+
requestOptions.body = options.body;
|
|
113
|
+
requestLogger.info('Using provided body from options', logContext);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// For JSON data, sanitize input and stringify
|
|
117
|
+
const sanitizedData = inputSanitizer(data);
|
|
118
|
+
requestOptions.body = JSON.stringify(sanitizedData);
|
|
119
|
+
requestLogger.info('Prepared JSON request body', {
|
|
120
|
+
...logContext,
|
|
121
|
+
dataType: 'json'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (options.body) {
|
|
126
|
+
// Use body from options if provided
|
|
127
|
+
requestOptions.body = options.body;
|
|
128
|
+
requestLogger.info('Using provided body from options', logContext);
|
|
129
|
+
}
|
|
130
|
+
// Add CSRF protection for state-changing methods
|
|
131
|
+
if (API_CONSTANTS.CSRF_PROTECTED_METHODS.includes(method)) {
|
|
132
|
+
requestLogger.info(`Adding CSRF protection to ${method} request`, logContext);
|
|
133
|
+
return await csrfProtection.protectRequest(url, requestOptions);
|
|
134
|
+
}
|
|
135
|
+
requestLogger.info(`Successfully prepared ${method} request to ${url}`, logContext);
|
|
136
|
+
return requestOptions;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const normalizedError = normalizeError(error, 'Failed to prepare request', {
|
|
140
|
+
category: 'api',
|
|
141
|
+
context: { method, url }
|
|
142
|
+
});
|
|
143
|
+
requestLogger.error(`Failed to prepare ${method} request to ${url}`, normalizedError, { method, url });
|
|
144
|
+
throw normalizedError;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
/**
|
|
148
|
+
* Executes a request with timeout handling
|
|
149
|
+
*/
|
|
150
|
+
async executeRequest(url, options, timeoutMs = API_CONSTANTS.REQUEST_CONFIG.timeout) {
|
|
151
|
+
const logContext = {
|
|
152
|
+
url,
|
|
153
|
+
timeout: timeoutMs,
|
|
154
|
+
method: options.method
|
|
155
|
+
};
|
|
156
|
+
requestLogger.info(`Executing request to ${url}`, logContext);
|
|
157
|
+
// Create AbortController for timeout handling
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
// If options already has a signal, we need to handle both
|
|
160
|
+
const originalSignal = options.signal;
|
|
161
|
+
// Clone options to avoid mutating the original
|
|
162
|
+
const requestOptions = { ...options };
|
|
163
|
+
// Add our signal to the options
|
|
164
|
+
requestOptions.signal = controller.signal;
|
|
165
|
+
// If there was an original signal, we need to abort when it aborts
|
|
166
|
+
if (originalSignal) {
|
|
167
|
+
if (originalSignal.aborted) {
|
|
168
|
+
// Already aborted
|
|
169
|
+
controller.abort();
|
|
170
|
+
requestLogger.info('Request already aborted by original signal', logContext);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Support both addEventListener and onabort patterns for different environments
|
|
174
|
+
if (typeof originalSignal.addEventListener === 'function') {
|
|
175
|
+
originalSignal.addEventListener('abort', () => {
|
|
176
|
+
requestLogger.info('Request aborted by original signal', logContext);
|
|
177
|
+
controller.abort();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else if ('onabort' in originalSignal) {
|
|
181
|
+
// For environments that don't support addEventListener (like some test runners)
|
|
182
|
+
const originalOnAbort = originalSignal.onabort;
|
|
183
|
+
originalSignal.onabort = (event) => {
|
|
184
|
+
if (originalOnAbort)
|
|
185
|
+
originalOnAbort.call(originalSignal, event);
|
|
186
|
+
requestLogger.info('Request aborted by original signal', logContext);
|
|
187
|
+
controller.abort();
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
// Create a promise that races between the fetch and the timeout
|
|
194
|
+
requestLogger.info(`Starting request to ${url} with ${timeoutMs}ms timeout`, logContext);
|
|
195
|
+
const response = await Promise.race([
|
|
196
|
+
fetch(url, requestOptions),
|
|
197
|
+
createRequestTimeout(timeoutMs, controller, url)
|
|
198
|
+
]);
|
|
199
|
+
// Use format expected by tests
|
|
200
|
+
requestLogger.info(`Request to ${url} completed`, {
|
|
201
|
+
status: response.status,
|
|
202
|
+
statusText: response.statusText || 'OK',
|
|
203
|
+
method: options.method,
|
|
204
|
+
url
|
|
205
|
+
});
|
|
206
|
+
return response;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
// Handle network errors and timeouts
|
|
210
|
+
if (error instanceof Error && error.name === 'AbortError' && !originalSignal?.aborted) {
|
|
211
|
+
const timeoutError = new NetworkError('Request timeout', {
|
|
212
|
+
isTimeout: true,
|
|
213
|
+
context: { endpoint: url },
|
|
214
|
+
userMessage: 'The request took too long to complete. Please try again.'
|
|
215
|
+
});
|
|
216
|
+
// Use the exact message format expected by the test
|
|
217
|
+
requestLogger.error(`Request to ${url} timed out`, timeoutError, logContext);
|
|
218
|
+
throw timeoutError;
|
|
219
|
+
}
|
|
220
|
+
if (!navigator.onLine) {
|
|
221
|
+
const offlineError = new NetworkError('Network offline', {
|
|
222
|
+
isOffline: true,
|
|
223
|
+
context: { endpoint: url },
|
|
224
|
+
userMessage: 'You appear to be offline. Please check your internet connection.'
|
|
225
|
+
});
|
|
226
|
+
requestLogger.error(`Cannot execute request to ${url} while offline`, offlineError, logContext);
|
|
227
|
+
throw offlineError;
|
|
228
|
+
}
|
|
229
|
+
// Rethrow original error or wrap in ApiError
|
|
230
|
+
if (error instanceof ApiError || error instanceof NetworkError) {
|
|
231
|
+
// For timeout errors, use a specific format expected by the test
|
|
232
|
+
if (error instanceof NetworkError && error.isTimeout) {
|
|
233
|
+
requestLogger.error(`Request to ${url} timed out`, error, logContext);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
requestLogger.error(`Request to ${url} failed with network error`, error, logContext);
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
const networkError = new ApiError('Network request failed', {
|
|
241
|
+
context: {
|
|
242
|
+
endpoint: url,
|
|
243
|
+
originalError: error instanceof Error ? error.message : String(error)
|
|
244
|
+
},
|
|
245
|
+
userMessage: 'Failed to connect to the server. Please try again.',
|
|
246
|
+
cause: error instanceof Error ? error : undefined
|
|
247
|
+
});
|
|
248
|
+
requestLogger.error(`Request to ${url} failed with network error`, networkError, logContext);
|
|
249
|
+
throw networkError;
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
/**
|
|
253
|
+
* Combines URL and query parameters
|
|
254
|
+
*/
|
|
255
|
+
buildUrl(url, params) {
|
|
256
|
+
if (!params || Object.keys(params).length === 0) {
|
|
257
|
+
return url;
|
|
258
|
+
}
|
|
259
|
+
const filteredParams = Object.entries(params)
|
|
260
|
+
.filter(([_, value]) => value !== undefined && value !== null);
|
|
261
|
+
if (filteredParams.length === 0) {
|
|
262
|
+
return url; // Return original URL if no valid params
|
|
263
|
+
}
|
|
264
|
+
const queryString = filteredParams.map(([key, value]) => {
|
|
265
|
+
// Handle arrays and objects
|
|
266
|
+
if (Array.isArray(value)) {
|
|
267
|
+
return value
|
|
268
|
+
.map(item => `${encodeURIComponent(key)}=${encodeURIComponent(String(item))}`)
|
|
269
|
+
.join('&');
|
|
270
|
+
}
|
|
271
|
+
else if (typeof value === 'object') {
|
|
272
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(value))}`;
|
|
273
|
+
}
|
|
274
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;
|
|
275
|
+
}).join('&');
|
|
276
|
+
// Add query string to URL only if we have parameters
|
|
277
|
+
if (!queryString || queryString.length === 0) {
|
|
278
|
+
return url;
|
|
279
|
+
}
|
|
280
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
281
|
+
const fullUrl = `${url}${separator}${queryString}`;
|
|
282
|
+
requestLogger.info(`Built URL with parameters: ${fullUrl}`, {
|
|
283
|
+
originalUrl: url,
|
|
284
|
+
paramCount: filteredParams.length
|
|
285
|
+
});
|
|
286
|
+
return fullUrl;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
//# sourceMappingURL=request-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-handler.js","sourceRoot":"","sources":["../../../src/services/api/request-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lib/services/api/response-handler.ts
|
|
3
|
+
* API Response Handler
|
|
4
|
+
*
|
|
5
|
+
* Handles API response processing with support for:
|
|
6
|
+
* - Status code interpretation
|
|
7
|
+
* - Content type handling
|
|
8
|
+
* - Error response processing
|
|
9
|
+
* - Token refresh on authentication failures
|
|
10
|
+
*/
|
|
11
|
+
import type { IResponseHandler } from '@xbg.solutions/bpsk-core';
|
|
12
|
+
/**
|
|
13
|
+
* Response Handler implementation
|
|
14
|
+
*/
|
|
15
|
+
export declare const responseHandler: IResponseHandler;
|
|
16
|
+
//# sourceMappingURL=response-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-handler.d.ts","sourceRoot":"","sources":["../../../src/services/api/response-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AA8IjE;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,gBAsJ7B,CAAC"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lib/services/api/response-handler.ts
|
|
3
|
+
* API Response Handler
|
|
4
|
+
*
|
|
5
|
+
* Handles API response processing with support for:
|
|
6
|
+
* - Status code interpretation
|
|
7
|
+
* - Content type handling
|
|
8
|
+
* - Error response processing
|
|
9
|
+
* - Token refresh on authentication failures
|
|
10
|
+
*/
|
|
11
|
+
import { loggerService } from '@xbg.solutions/bpsk-core';
|
|
12
|
+
import { tokenService } from '@xbg.solutions/bpsk-utils-firebase-auth';
|
|
13
|
+
import { ApiError, AuthError, ValidationError, normalizeError } from '@xbg.solutions/bpsk-core';
|
|
14
|
+
import { API_CONSTANTS } from '@xbg.solutions/bpsk-core';
|
|
15
|
+
// Create a context-aware logger
|
|
16
|
+
const responseLogger = loggerService.withContext('ResponseHandler');
|
|
17
|
+
/**
|
|
18
|
+
* Parses response body based on content type
|
|
19
|
+
*
|
|
20
|
+
* @param response Fetch Response object
|
|
21
|
+
* @param logContext Optional logging context
|
|
22
|
+
* @returns Parsed response body
|
|
23
|
+
*/
|
|
24
|
+
async function parseResponseBody(response, logContext) {
|
|
25
|
+
const contentType = response.headers.get('Content-Type') || '';
|
|
26
|
+
// Handle empty responses
|
|
27
|
+
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
|
|
28
|
+
responseLogger.info('Received empty response', logContext);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
// Parse based on content type
|
|
33
|
+
if (contentType.includes('application/json')) {
|
|
34
|
+
responseLogger.info('Parsing JSON response', logContext);
|
|
35
|
+
return await response.json();
|
|
36
|
+
}
|
|
37
|
+
else if (contentType.includes('text/')) {
|
|
38
|
+
responseLogger.info('Parsing text response', logContext);
|
|
39
|
+
return await response.text();
|
|
40
|
+
}
|
|
41
|
+
else if (contentType.includes('application/octet-stream')) {
|
|
42
|
+
responseLogger.info('Parsing binary response', logContext);
|
|
43
|
+
return await response.blob();
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Default to JSON parsing with fallback to text
|
|
47
|
+
responseLogger.info(`Attempting to parse unknown content type: ${contentType}`, logContext);
|
|
48
|
+
try {
|
|
49
|
+
return await response.json();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return await response.text();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
responseLogger.error(`Failed to parse response body (${contentType})`, error instanceof Error ? error : new Error(String(error)), logContext);
|
|
58
|
+
throw new ApiError('Failed to parse response body', {
|
|
59
|
+
context: {
|
|
60
|
+
contentType,
|
|
61
|
+
responseSize: response.headers.get('Content-Length')
|
|
62
|
+
},
|
|
63
|
+
userMessage: 'Failed to read response from server.'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Creates appropriate API error based on status code
|
|
69
|
+
*
|
|
70
|
+
* @param status HTTP status code
|
|
71
|
+
* @param errorData Error data from response
|
|
72
|
+
* @param method HTTP method
|
|
73
|
+
* @param url Request URL
|
|
74
|
+
* @returns Appropriate error object
|
|
75
|
+
*/
|
|
76
|
+
function createApiErrorFromStatus(status, errorData, method, url) {
|
|
77
|
+
// Default error message and user message
|
|
78
|
+
const errorMessage = errorData?.message || errorData?.error || 'API request failed';
|
|
79
|
+
const userMessage = errorData?.userMessage || errorData?.message || 'An error occurred during the request.';
|
|
80
|
+
// Create context object for error
|
|
81
|
+
const context = {
|
|
82
|
+
endpoint: url,
|
|
83
|
+
method,
|
|
84
|
+
status,
|
|
85
|
+
errorData: errorData ? {
|
|
86
|
+
message: errorData.message,
|
|
87
|
+
code: errorData.code
|
|
88
|
+
} : undefined
|
|
89
|
+
};
|
|
90
|
+
// Create appropriate error based on status code
|
|
91
|
+
switch (true) {
|
|
92
|
+
case status === API_CONSTANTS.STATUS.BAD_REQUEST:
|
|
93
|
+
return new ValidationError(errorMessage, {
|
|
94
|
+
statusCode: status,
|
|
95
|
+
userMessage,
|
|
96
|
+
context,
|
|
97
|
+
fieldErrors: errorData?.errors || errorData?.fieldErrors || {}
|
|
98
|
+
});
|
|
99
|
+
case status === API_CONSTANTS.STATUS.UNAUTHORIZED:
|
|
100
|
+
return new AuthError(errorMessage, {
|
|
101
|
+
action: 'login', // Assume token expiration for 401
|
|
102
|
+
statusCode: status,
|
|
103
|
+
userMessage: 'Your session has expired. Please log in again.',
|
|
104
|
+
context
|
|
105
|
+
});
|
|
106
|
+
case status === API_CONSTANTS.STATUS.FORBIDDEN:
|
|
107
|
+
return new AuthError(errorMessage, {
|
|
108
|
+
action: 'authorize',
|
|
109
|
+
statusCode: status,
|
|
110
|
+
userMessage: "You don't have permission to perform this action.",
|
|
111
|
+
context
|
|
112
|
+
});
|
|
113
|
+
case status === API_CONSTANTS.STATUS.NOT_FOUND:
|
|
114
|
+
return new ApiError(errorMessage, {
|
|
115
|
+
statusCode: status,
|
|
116
|
+
userMessage: 'The requested resource was not found.',
|
|
117
|
+
context
|
|
118
|
+
});
|
|
119
|
+
case status >= 500:
|
|
120
|
+
// Server errors are usually retryable
|
|
121
|
+
return new ApiError(errorMessage, {
|
|
122
|
+
statusCode: status,
|
|
123
|
+
userMessage: 'The server encountered an error. Please try again later.',
|
|
124
|
+
context: {
|
|
125
|
+
...context,
|
|
126
|
+
isRetryable: true
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
default:
|
|
130
|
+
return new ApiError(errorMessage, {
|
|
131
|
+
statusCode: status,
|
|
132
|
+
userMessage,
|
|
133
|
+
context
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Response Handler implementation
|
|
139
|
+
*/
|
|
140
|
+
export const responseHandler = {
|
|
141
|
+
/**
|
|
142
|
+
* Processes an API response based on status code and content type
|
|
143
|
+
*/
|
|
144
|
+
async processResponse(response, method, url) {
|
|
145
|
+
const logContext = {
|
|
146
|
+
url,
|
|
147
|
+
method,
|
|
148
|
+
status: response.status,
|
|
149
|
+
contentType: response.headers.get('Content-Type')
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
responseLogger.info(`Processing ${method} response from ${url} (${response.status})`, logContext);
|
|
153
|
+
// Handle successful responses
|
|
154
|
+
if (response.ok) {
|
|
155
|
+
const responseData = await parseResponseBody(response, logContext);
|
|
156
|
+
responseLogger.info(`Successfully processed ${method} response from ${url}`, logContext);
|
|
157
|
+
return responseData;
|
|
158
|
+
}
|
|
159
|
+
// Log error response
|
|
160
|
+
responseLogger.warn(`Received error response from ${url}: ${response.status} ${response.statusText}`, logContext);
|
|
161
|
+
// Handle error responses
|
|
162
|
+
try {
|
|
163
|
+
// Try to parse error response data
|
|
164
|
+
let errorData = null;
|
|
165
|
+
const contentType = response.headers.get('Content-Type') || '';
|
|
166
|
+
if (contentType.includes('application/json')) {
|
|
167
|
+
errorData = await response.json();
|
|
168
|
+
responseLogger.info('Parsed JSON error response', {
|
|
169
|
+
...logContext,
|
|
170
|
+
errorData: errorData?.message || errorData?.error
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else if (contentType.includes('text/')) {
|
|
174
|
+
errorData = { message: await response.text() };
|
|
175
|
+
responseLogger.info('Parsed text error response', logContext);
|
|
176
|
+
}
|
|
177
|
+
// Create appropriate error object based on status code
|
|
178
|
+
const error = createApiErrorFromStatus(response.status, errorData, method, url);
|
|
179
|
+
// Special handling for 401 Unauthorized - attempt token refresh
|
|
180
|
+
if (response.status === API_CONSTANTS.STATUS.UNAUTHORIZED && tokenService.isAuthenticated()) {
|
|
181
|
+
responseLogger.warn(`Authentication error for ${method} ${url}, attempting token refresh`, logContext);
|
|
182
|
+
// Try to refresh the token
|
|
183
|
+
const refreshResult = await tokenService.refreshToken();
|
|
184
|
+
if (refreshResult.success) {
|
|
185
|
+
// Token refreshed successfully, suggest retry
|
|
186
|
+
responseLogger.info('Token refreshed successfully after auth error', logContext);
|
|
187
|
+
// Create a special error with a token refreshed flag
|
|
188
|
+
const refreshedError = new AuthError('Token refreshed successfully', {
|
|
189
|
+
action: 'login',
|
|
190
|
+
statusCode: API_CONSTANTS.STATUS.UNAUTHORIZED,
|
|
191
|
+
context: {
|
|
192
|
+
endpoint: url,
|
|
193
|
+
method,
|
|
194
|
+
tokenRefreshed: true // Special flag for API service to recognize
|
|
195
|
+
},
|
|
196
|
+
userMessage: 'Your session has been refreshed. Please try again.'
|
|
197
|
+
});
|
|
198
|
+
throw refreshedError;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// Token refresh failed, throw original error
|
|
202
|
+
responseLogger.warn('Token refresh failed after auth error', {
|
|
203
|
+
...logContext,
|
|
204
|
+
refreshError: refreshResult.error?.message
|
|
205
|
+
});
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// For other status codes, throw the error
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
// If error is already one of our error types, rethrow it
|
|
214
|
+
if (error instanceof ApiError || error instanceof AuthError || error instanceof ValidationError) {
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
// Otherwise create a generic API error
|
|
218
|
+
const apiError = new ApiError(`Error response from ${url}`, {
|
|
219
|
+
statusCode: response.status,
|
|
220
|
+
context: {
|
|
221
|
+
url,
|
|
222
|
+
method,
|
|
223
|
+
status: response.status,
|
|
224
|
+
statusText: response.statusText
|
|
225
|
+
},
|
|
226
|
+
userMessage: 'The server returned an error response.'
|
|
227
|
+
});
|
|
228
|
+
throw apiError;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
// Handle errors that occurred during response processing
|
|
233
|
+
// Pass through our specific error types
|
|
234
|
+
if (error instanceof ApiError || error instanceof AuthError || error instanceof ValidationError) {
|
|
235
|
+
// Special case for token refreshed error
|
|
236
|
+
if (error instanceof AuthError && error.context && error.context.tokenRefreshed) {
|
|
237
|
+
responseLogger.info('Propagating token refreshed error for retry', logContext);
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
responseLogger.error(`Error processing ${method} response from ${url}`, error, logContext);
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
// Normalize other errors
|
|
244
|
+
const normalizedError = normalizeError(error, `Error processing ${method} response from ${url}`, {
|
|
245
|
+
category: 'api',
|
|
246
|
+
context: logContext,
|
|
247
|
+
statusCode: response.status
|
|
248
|
+
});
|
|
249
|
+
responseLogger.error(`Failed to process ${method} response from ${url}`, normalizedError, logContext);
|
|
250
|
+
throw normalizedError;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
//# sourceMappingURL=response-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-handler.js","sourceRoot":"","sources":["../../../src/services/api/response-handler.ts"],"names":[],"mappings":"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"}
|