289 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import platform from "../platform/index.js";
 | |
| import utils from "../utils.js";
 | |
| import AxiosError from "../core/AxiosError.js";
 | |
| import composeSignals from "../helpers/composeSignals.js";
 | |
| import {trackStream} from "../helpers/trackStream.js";
 | |
| import AxiosHeaders from "../core/AxiosHeaders.js";
 | |
| import {progressEventReducer, progressEventDecorator, asyncDecorator} from "../helpers/progressEventReducer.js";
 | |
| import resolveConfig from "../helpers/resolveConfig.js";
 | |
| import settle from "../core/settle.js";
 | |
| 
 | |
| const DEFAULT_CHUNK_SIZE = 64 * 1024;
 | |
| 
 | |
| const {isFunction} = utils;
 | |
| 
 | |
| const globalFetchAPI = (({Request, Response}) => ({
 | |
|   Request, Response
 | |
| }))(utils.global);
 | |
| 
 | |
| const {
 | |
|   ReadableStream, TextEncoder
 | |
| } = utils.global;
 | |
| 
 | |
| 
 | |
| const test = (fn, ...args) => {
 | |
|   try {
 | |
|     return !!fn(...args);
 | |
|   } catch (e) {
 | |
|     return false
 | |
|   }
 | |
| }
 | |
| 
 | |
| const factory = (env) => {
 | |
|   env = utils.merge.call({
 | |
|     skipUndefined: true
 | |
|   }, globalFetchAPI, env);
 | |
| 
 | |
|   const {fetch: envFetch, Request, Response} = env;
 | |
|   const isFetchSupported = envFetch ? isFunction(envFetch) : typeof fetch === 'function';
 | |
|   const isRequestSupported = isFunction(Request);
 | |
|   const isResponseSupported = isFunction(Response);
 | |
| 
 | |
|   if (!isFetchSupported) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const isReadableStreamSupported = isFetchSupported && isFunction(ReadableStream);
 | |
| 
 | |
|   const encodeText = isFetchSupported && (typeof TextEncoder === 'function' ?
 | |
|       ((encoder) => (str) => encoder.encode(str))(new TextEncoder()) :
 | |
|       async (str) => new Uint8Array(await new Request(str).arrayBuffer())
 | |
|   );
 | |
| 
 | |
|   const supportsRequestStream = isRequestSupported && isReadableStreamSupported && test(() => {
 | |
|     let duplexAccessed = false;
 | |
| 
 | |
|     const hasContentType = new Request(platform.origin, {
 | |
|       body: new ReadableStream(),
 | |
|       method: 'POST',
 | |
|       get duplex() {
 | |
|         duplexAccessed = true;
 | |
|         return 'half';
 | |
|       },
 | |
|     }).headers.has('Content-Type');
 | |
| 
 | |
|     return duplexAccessed && !hasContentType;
 | |
|   });
 | |
| 
 | |
|   const supportsResponseStream = isResponseSupported && isReadableStreamSupported &&
 | |
|     test(() => utils.isReadableStream(new Response('').body));
 | |
| 
 | |
|   const resolvers = {
 | |
|     stream: supportsResponseStream && ((res) => res.body)
 | |
|   };
 | |
| 
 | |
|   isFetchSupported && ((() => {
 | |
|     ['text', 'arrayBuffer', 'blob', 'formData', 'stream'].forEach(type => {
 | |
|       !resolvers[type] && (resolvers[type] = (res, config) => {
 | |
|         let method = res && res[type];
 | |
| 
 | |
|         if (method) {
 | |
|           return method.call(res);
 | |
|         }
 | |
| 
 | |
|         throw new AxiosError(`Response type '${type}' is not supported`, AxiosError.ERR_NOT_SUPPORT, config);
 | |
|       })
 | |
|     });
 | |
|   })());
 | |
| 
 | |
|   const getBodyLength = async (body) => {
 | |
|     if (body == null) {
 | |
|       return 0;
 | |
|     }
 | |
| 
 | |
|     if (utils.isBlob(body)) {
 | |
|       return body.size;
 | |
|     }
 | |
| 
 | |
|     if (utils.isSpecCompliantForm(body)) {
 | |
|       const _request = new Request(platform.origin, {
 | |
|         method: 'POST',
 | |
|         body,
 | |
|       });
 | |
|       return (await _request.arrayBuffer()).byteLength;
 | |
|     }
 | |
| 
 | |
|     if (utils.isArrayBufferView(body) || utils.isArrayBuffer(body)) {
 | |
|       return body.byteLength;
 | |
|     }
 | |
| 
 | |
|     if (utils.isURLSearchParams(body)) {
 | |
|       body = body + '';
 | |
|     }
 | |
| 
 | |
|     if (utils.isString(body)) {
 | |
|       return (await encodeText(body)).byteLength;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const resolveBodyLength = async (headers, body) => {
 | |
|     const length = utils.toFiniteNumber(headers.getContentLength());
 | |
| 
 | |
|     return length == null ? getBodyLength(body) : length;
 | |
|   }
 | |
| 
 | |
|   return async (config) => {
 | |
|     let {
 | |
|       url,
 | |
|       method,
 | |
|       data,
 | |
|       signal,
 | |
|       cancelToken,
 | |
|       timeout,
 | |
|       onDownloadProgress,
 | |
|       onUploadProgress,
 | |
|       responseType,
 | |
|       headers,
 | |
|       withCredentials = 'same-origin',
 | |
|       fetchOptions
 | |
|     } = resolveConfig(config);
 | |
| 
 | |
|     let _fetch = envFetch || fetch;
 | |
| 
 | |
|     responseType = responseType ? (responseType + '').toLowerCase() : 'text';
 | |
| 
 | |
|     let composedSignal = composeSignals([signal, cancelToken && cancelToken.toAbortSignal()], timeout);
 | |
| 
 | |
|     let request = null;
 | |
| 
 | |
|     const unsubscribe = composedSignal && composedSignal.unsubscribe && (() => {
 | |
|       composedSignal.unsubscribe();
 | |
|     });
 | |
| 
 | |
|     let requestContentLength;
 | |
| 
 | |
|     try {
 | |
|       if (
 | |
|         onUploadProgress && supportsRequestStream && method !== 'get' && method !== 'head' &&
 | |
|         (requestContentLength = await resolveBodyLength(headers, data)) !== 0
 | |
|       ) {
 | |
|         let _request = new Request(url, {
 | |
|           method: 'POST',
 | |
|           body: data,
 | |
|           duplex: "half"
 | |
|         });
 | |
| 
 | |
|         let contentTypeHeader;
 | |
| 
 | |
|         if (utils.isFormData(data) && (contentTypeHeader = _request.headers.get('content-type'))) {
 | |
|           headers.setContentType(contentTypeHeader)
 | |
|         }
 | |
| 
 | |
|         if (_request.body) {
 | |
|           const [onProgress, flush] = progressEventDecorator(
 | |
|             requestContentLength,
 | |
|             progressEventReducer(asyncDecorator(onUploadProgress))
 | |
|           );
 | |
| 
 | |
|           data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, onProgress, flush);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (!utils.isString(withCredentials)) {
 | |
|         withCredentials = withCredentials ? 'include' : 'omit';
 | |
|       }
 | |
| 
 | |
|       // Cloudflare Workers throws when credentials are defined
 | |
|       // see https://github.com/cloudflare/workerd/issues/902
 | |
|       const isCredentialsSupported = isRequestSupported && "credentials" in Request.prototype;
 | |
| 
 | |
|       const resolvedOptions = {
 | |
|         ...fetchOptions,
 | |
|         signal: composedSignal,
 | |
|         method: method.toUpperCase(),
 | |
|         headers: headers.normalize().toJSON(),
 | |
|         body: data,
 | |
|         duplex: "half",
 | |
|         credentials: isCredentialsSupported ? withCredentials : undefined
 | |
|       };
 | |
| 
 | |
|       request = isRequestSupported && new Request(url, resolvedOptions);
 | |
| 
 | |
|       let response = await (isRequestSupported ? _fetch(request, fetchOptions) : _fetch(url, resolvedOptions));
 | |
| 
 | |
|       const isStreamResponse = supportsResponseStream && (responseType === 'stream' || responseType === 'response');
 | |
| 
 | |
|       if (supportsResponseStream && (onDownloadProgress || (isStreamResponse && unsubscribe))) {
 | |
|         const options = {};
 | |
| 
 | |
|         ['status', 'statusText', 'headers'].forEach(prop => {
 | |
|           options[prop] = response[prop];
 | |
|         });
 | |
| 
 | |
|         const responseContentLength = utils.toFiniteNumber(response.headers.get('content-length'));
 | |
| 
 | |
|         const [onProgress, flush] = onDownloadProgress && progressEventDecorator(
 | |
|           responseContentLength,
 | |
|           progressEventReducer(asyncDecorator(onDownloadProgress), true)
 | |
|         ) || [];
 | |
| 
 | |
|         response = new Response(
 | |
|           trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => {
 | |
|             flush && flush();
 | |
|             unsubscribe && unsubscribe();
 | |
|           }),
 | |
|           options
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       responseType = responseType || 'text';
 | |
| 
 | |
|       let responseData = await resolvers[utils.findKey(resolvers, responseType) || 'text'](response, config);
 | |
| 
 | |
|       !isStreamResponse && unsubscribe && unsubscribe();
 | |
| 
 | |
|       return await new Promise((resolve, reject) => {
 | |
|         settle(resolve, reject, {
 | |
|           data: responseData,
 | |
|           headers: AxiosHeaders.from(response.headers),
 | |
|           status: response.status,
 | |
|           statusText: response.statusText,
 | |
|           config,
 | |
|           request
 | |
|         })
 | |
|       })
 | |
|     } catch (err) {
 | |
|       unsubscribe && unsubscribe();
 | |
| 
 | |
|       if (err && err.name === 'TypeError' && /Load failed|fetch/i.test(err.message)) {
 | |
|         throw Object.assign(
 | |
|           new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request),
 | |
|           {
 | |
|             cause: err.cause || err
 | |
|           }
 | |
|         )
 | |
|       }
 | |
| 
 | |
|       throw AxiosError.from(err, err && err.code, config, request);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| const seedCache = new Map();
 | |
| 
 | |
| export const getFetch = (config) => {
 | |
|   let env = config ? config.env : {};
 | |
|   const {fetch, Request, Response} = env;
 | |
|   const seeds = [
 | |
|     Request, Response, fetch
 | |
|   ];
 | |
| 
 | |
|   let len = seeds.length, i = len,
 | |
|     seed, target, map = seedCache;
 | |
| 
 | |
|   while (i--) {
 | |
|     seed = seeds[i];
 | |
|     target = map.get(seed);
 | |
| 
 | |
|     target === undefined && map.set(seed, target = (i ? new Map() : factory(env)))
 | |
| 
 | |
|     map = target;
 | |
|   }
 | |
| 
 | |
|   return target;
 | |
| };
 | |
| 
 | |
| const adapter = getFetch();
 | |
| 
 | |
| export default adapter;
 |