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;
 |