source: frontend/node_modules/axios/lib/adapters/fetch.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 13 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 13.9 KB
Line 
1import platform from '../platform/index.js';
2import utils from '../utils.js';
3import AxiosError from '../core/AxiosError.js';
4import composeSignals from '../helpers/composeSignals.js';
5import { trackStream } from '../helpers/trackStream.js';
6import AxiosHeaders from '../core/AxiosHeaders.js';
7import {
8 progressEventReducer,
9 progressEventDecorator,
10 asyncDecorator,
11} from '../helpers/progressEventReducer.js';
12import resolveConfig from '../helpers/resolveConfig.js';
13import settle from '../core/settle.js';
14import estimateDataURLDecodedBytes from '../helpers/estimateDataURLDecodedBytes.js';
15import { VERSION } from '../env/data.js';
16import { toByteStringHeaderObject } from '../helpers/sanitizeHeaderValue.js';
17
18const DEFAULT_CHUNK_SIZE = 64 * 1024;
19
20const { isFunction } = utils;
21
22const test = (fn, ...args) => {
23 try {
24 return !!fn(...args);
25 } catch (e) {
26 return false;
27 }
28};
29
30const factory = (env) => {
31 const globalObject =
32 utils.global !== undefined && utils.global !== null
33 ? utils.global
34 : globalThis;
35 const { ReadableStream, TextEncoder } = globalObject;
36
37 env = utils.merge.call(
38 {
39 skipUndefined: true,
40 },
41 {
42 Request: globalObject.Request,
43 Response: globalObject.Response,
44 },
45 env
46 );
47
48 const { fetch: envFetch, Request, Response } = env;
49 const isFetchSupported = envFetch ? isFunction(envFetch) : typeof fetch === 'function';
50 const isRequestSupported = isFunction(Request);
51 const isResponseSupported = isFunction(Response);
52
53 if (!isFetchSupported) {
54 return false;
55 }
56
57 const isReadableStreamSupported = isFetchSupported && isFunction(ReadableStream);
58
59 const encodeText =
60 isFetchSupported &&
61 (typeof TextEncoder === 'function'
62 ? (
63 (encoder) => (str) =>
64 encoder.encode(str)
65 )(new TextEncoder())
66 : async (str) => new Uint8Array(await new Request(str).arrayBuffer()));
67
68 const supportsRequestStream =
69 isRequestSupported &&
70 isReadableStreamSupported &&
71 test(() => {
72 let duplexAccessed = false;
73
74 const request = new Request(platform.origin, {
75 body: new ReadableStream(),
76 method: 'POST',
77 get duplex() {
78 duplexAccessed = true;
79 return 'half';
80 },
81 });
82
83 const hasContentType = request.headers.has('Content-Type');
84
85 if (request.body != null) {
86 request.body.cancel();
87 }
88
89 return duplexAccessed && !hasContentType;
90 });
91
92 const supportsResponseStream =
93 isResponseSupported &&
94 isReadableStreamSupported &&
95 test(() => utils.isReadableStream(new Response('').body));
96
97 const resolvers = {
98 stream: supportsResponseStream && ((res) => res.body),
99 };
100
101 isFetchSupported &&
102 (() => {
103 ['text', 'arrayBuffer', 'blob', 'formData', 'stream'].forEach((type) => {
104 !resolvers[type] &&
105 (resolvers[type] = (res, config) => {
106 let method = res && res[type];
107
108 if (method) {
109 return method.call(res);
110 }
111
112 throw new AxiosError(
113 `Response type '${type}' is not supported`,
114 AxiosError.ERR_NOT_SUPPORT,
115 config
116 );
117 });
118 });
119 })();
120
121 const getBodyLength = async (body) => {
122 if (body == null) {
123 return 0;
124 }
125
126 if (utils.isBlob(body)) {
127 return body.size;
128 }
129
130 if (utils.isSpecCompliantForm(body)) {
131 const _request = new Request(platform.origin, {
132 method: 'POST',
133 body,
134 });
135 return (await _request.arrayBuffer()).byteLength;
136 }
137
138 if (utils.isArrayBufferView(body) || utils.isArrayBuffer(body)) {
139 return body.byteLength;
140 }
141
142 if (utils.isURLSearchParams(body)) {
143 body = body + '';
144 }
145
146 if (utils.isString(body)) {
147 return (await encodeText(body)).byteLength;
148 }
149 };
150
151 const resolveBodyLength = async (headers, body) => {
152 const length = utils.toFiniteNumber(headers.getContentLength());
153
154 return length == null ? getBodyLength(body) : length;
155 };
156
157 return async (config) => {
158 let {
159 url,
160 method,
161 data,
162 signal,
163 cancelToken,
164 timeout,
165 onDownloadProgress,
166 onUploadProgress,
167 responseType,
168 headers,
169 withCredentials = 'same-origin',
170 fetchOptions,
171 maxContentLength,
172 maxBodyLength,
173 } = resolveConfig(config);
174
175 const hasMaxContentLength = utils.isNumber(maxContentLength) && maxContentLength > -1;
176 const hasMaxBodyLength = utils.isNumber(maxBodyLength) && maxBodyLength > -1;
177
178 let _fetch = envFetch || fetch;
179
180 responseType = responseType ? (responseType + '').toLowerCase() : 'text';
181
182 let composedSignal = composeSignals(
183 [signal, cancelToken && cancelToken.toAbortSignal()],
184 timeout
185 );
186
187 let request = null;
188
189 const unsubscribe =
190 composedSignal &&
191 composedSignal.unsubscribe &&
192 (() => {
193 composedSignal.unsubscribe();
194 });
195
196 let requestContentLength;
197
198 try {
199 // Enforce maxContentLength for data: URLs up-front so we never materialize
200 // an oversized payload. The HTTP adapter applies the same check (see http.js
201 // "if (protocol === 'data:')" branch).
202 if (hasMaxContentLength && typeof url === 'string' && url.startsWith('data:')) {
203 const estimated = estimateDataURLDecodedBytes(url);
204 if (estimated > maxContentLength) {
205 throw new AxiosError(
206 'maxContentLength size of ' + maxContentLength + ' exceeded',
207 AxiosError.ERR_BAD_RESPONSE,
208 config,
209 request
210 );
211 }
212 }
213
214 // Enforce maxBodyLength against the outbound request body before dispatch.
215 // Mirrors http.js behavior (ERR_BAD_REQUEST / 'Request body larger than
216 // maxBodyLength limit'). Skip when the body length cannot be determined
217 // (e.g. a live ReadableStream supplied by the caller).
218 if (hasMaxBodyLength && method !== 'get' && method !== 'head') {
219 const outboundLength = await resolveBodyLength(headers, data);
220 if (
221 typeof outboundLength === 'number' &&
222 isFinite(outboundLength) &&
223 outboundLength > maxBodyLength
224 ) {
225 throw new AxiosError(
226 'Request body larger than maxBodyLength limit',
227 AxiosError.ERR_BAD_REQUEST,
228 config,
229 request
230 );
231 }
232 }
233
234 if (
235 onUploadProgress &&
236 supportsRequestStream &&
237 method !== 'get' &&
238 method !== 'head' &&
239 (requestContentLength = await resolveBodyLength(headers, data)) !== 0
240 ) {
241 let _request = new Request(url, {
242 method: 'POST',
243 body: data,
244 duplex: 'half',
245 });
246
247 let contentTypeHeader;
248
249 if (utils.isFormData(data) && (contentTypeHeader = _request.headers.get('content-type'))) {
250 headers.setContentType(contentTypeHeader);
251 }
252
253 if (_request.body) {
254 const [onProgress, flush] = progressEventDecorator(
255 requestContentLength,
256 progressEventReducer(asyncDecorator(onUploadProgress))
257 );
258
259 data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, onProgress, flush);
260 }
261 }
262
263 if (!utils.isString(withCredentials)) {
264 withCredentials = withCredentials ? 'include' : 'omit';
265 }
266
267 // Cloudflare Workers throws when credentials are defined
268 // see https://github.com/cloudflare/workerd/issues/902
269 const isCredentialsSupported = isRequestSupported && 'credentials' in Request.prototype;
270
271 // If data is FormData and Content-Type is multipart/form-data without boundary,
272 // delete it so fetch can set it correctly with the boundary
273 if (utils.isFormData(data)) {
274 const contentType = headers.getContentType();
275 if (
276 contentType &&
277 /^multipart\/form-data/i.test(contentType) &&
278 !/boundary=/i.test(contentType)
279 ) {
280 headers.delete('content-type');
281 }
282 }
283
284 // Set User-Agent header if not already set (fetch defaults to 'node' in Node.js)
285 headers.set('User-Agent', 'axios/' + VERSION, false);
286
287 const resolvedOptions = {
288 ...fetchOptions,
289 signal: composedSignal,
290 method: method.toUpperCase(),
291 headers: toByteStringHeaderObject(headers.normalize()),
292 body: data,
293 duplex: 'half',
294 credentials: isCredentialsSupported ? withCredentials : undefined,
295 };
296
297 request = isRequestSupported && new Request(url, resolvedOptions);
298
299 let response = await (isRequestSupported
300 ? _fetch(request, fetchOptions)
301 : _fetch(url, resolvedOptions));
302
303 // Cheap pre-check: if the server honestly declares a content-length that
304 // already exceeds the cap, reject before we start streaming.
305 if (hasMaxContentLength) {
306 const declaredLength = utils.toFiniteNumber(response.headers.get('content-length'));
307 if (declaredLength != null && declaredLength > maxContentLength) {
308 throw new AxiosError(
309 'maxContentLength size of ' + maxContentLength + ' exceeded',
310 AxiosError.ERR_BAD_RESPONSE,
311 config,
312 request
313 );
314 }
315 }
316
317 const isStreamResponse =
318 supportsResponseStream && (responseType === 'stream' || responseType === 'response');
319
320 if (
321 supportsResponseStream &&
322 response.body &&
323 (onDownloadProgress || hasMaxContentLength || (isStreamResponse && unsubscribe))
324 ) {
325 const options = {};
326
327 ['status', 'statusText', 'headers'].forEach((prop) => {
328 options[prop] = response[prop];
329 });
330
331 const responseContentLength = utils.toFiniteNumber(response.headers.get('content-length'));
332
333 const [onProgress, flush] =
334 (onDownloadProgress &&
335 progressEventDecorator(
336 responseContentLength,
337 progressEventReducer(asyncDecorator(onDownloadProgress), true)
338 )) ||
339 [];
340
341 let bytesRead = 0;
342 const onChunkProgress = (loadedBytes) => {
343 if (hasMaxContentLength) {
344 bytesRead = loadedBytes;
345 if (bytesRead > maxContentLength) {
346 throw new AxiosError(
347 'maxContentLength size of ' + maxContentLength + ' exceeded',
348 AxiosError.ERR_BAD_RESPONSE,
349 config,
350 request
351 );
352 }
353 }
354 onProgress && onProgress(loadedBytes);
355 };
356
357 response = new Response(
358 trackStream(response.body, DEFAULT_CHUNK_SIZE, onChunkProgress, () => {
359 flush && flush();
360 unsubscribe && unsubscribe();
361 }),
362 options
363 );
364 }
365
366 responseType = responseType || 'text';
367
368 let responseData = await resolvers[utils.findKey(resolvers, responseType) || 'text'](
369 response,
370 config
371 );
372
373 // Fallback enforcement for environments without ReadableStream support
374 // (legacy runtimes). Detect materialized size from typed output; skip
375 // streams/Response passthrough since the user will read those themselves.
376 if (hasMaxContentLength && !supportsResponseStream && !isStreamResponse) {
377 let materializedSize;
378 if (responseData != null) {
379 if (typeof responseData.byteLength === 'number') {
380 materializedSize = responseData.byteLength;
381 } else if (typeof responseData.size === 'number') {
382 materializedSize = responseData.size;
383 } else if (typeof responseData === 'string') {
384 materializedSize =
385 typeof TextEncoder === 'function'
386 ? new TextEncoder().encode(responseData).byteLength
387 : responseData.length;
388 }
389 }
390 if (typeof materializedSize === 'number' && materializedSize > maxContentLength) {
391 throw new AxiosError(
392 'maxContentLength size of ' + maxContentLength + ' exceeded',
393 AxiosError.ERR_BAD_RESPONSE,
394 config,
395 request
396 );
397 }
398 }
399
400 !isStreamResponse && unsubscribe && unsubscribe();
401
402 return await new Promise((resolve, reject) => {
403 settle(resolve, reject, {
404 data: responseData,
405 headers: AxiosHeaders.from(response.headers),
406 status: response.status,
407 statusText: response.statusText,
408 config,
409 request,
410 });
411 });
412 } catch (err) {
413 unsubscribe && unsubscribe();
414
415 // Safari can surface fetch aborts as a DOMException-like object whose
416 // branded getters throw. Prefer our composed signal reason before reading
417 // the caught error, preserving timeout vs cancellation semantics.
418 if (composedSignal && composedSignal.aborted && composedSignal.reason instanceof AxiosError) {
419 const canceledError = composedSignal.reason;
420 canceledError.config = config;
421 request && (canceledError.request = request);
422 err !== canceledError && (canceledError.cause = err);
423 throw canceledError;
424 }
425
426 if (err && err.name === 'TypeError' && /Load failed|fetch/i.test(err.message)) {
427 throw Object.assign(
428 new AxiosError(
429 'Network Error',
430 AxiosError.ERR_NETWORK,
431 config,
432 request,
433 err && err.response
434 ),
435 {
436 cause: err.cause || err,
437 }
438 );
439 }
440
441 throw AxiosError.from(err, err && err.code, config, request, err && err.response);
442 }
443 };
444};
445
446const seedCache = new Map();
447
448export const getFetch = (config) => {
449 let env = (config && config.env) || {};
450 const { fetch, Request, Response } = env;
451 const seeds = [Request, Response, fetch];
452
453 let len = seeds.length,
454 i = len,
455 seed,
456 target,
457 map = seedCache;
458
459 while (i--) {
460 seed = seeds[i];
461 target = map.get(seed);
462
463 target === undefined && map.set(seed, (target = i ? new Map() : factory(env)));
464
465 map = target;
466 }
467
468 return target;
469};
470
471const adapter = getFetch();
472
473export default adapter;
Note: See TracBrowser for help on using the repository browser.