mirror of
https://github.com/tenrok/axios.git
synced 2026-06-11 18:02:32 +03:00
feat(adapter): add fetch adapter; (#6371)
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import utils from '../utils.js';
|
||||
import httpAdapter from './http.js';
|
||||
import xhrAdapter from './xhr.js';
|
||||
import fetchAdapter from './fetch.js';
|
||||
import AxiosError from "../core/AxiosError.js";
|
||||
|
||||
const knownAdapters = {
|
||||
http: httpAdapter,
|
||||
xhr: xhrAdapter
|
||||
xhr: xhrAdapter,
|
||||
fetch: fetchAdapter
|
||||
}
|
||||
|
||||
utils.forEach(knownAdapters, (fn, value) => {
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
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 from "../helpers/progressEventReducer.js";
|
||||
import resolveConfig from "../helpers/resolveConfig.js";
|
||||
import settle from "../core/settle.js";
|
||||
|
||||
const fetchProgressDecorator = (total, fn) => {
|
||||
const lengthComputable = total != null;
|
||||
return (loaded) => setTimeout(() => fn({
|
||||
lengthComputable,
|
||||
total,
|
||||
loaded
|
||||
}));
|
||||
}
|
||||
|
||||
const isFetchSupported = typeof fetch !== 'undefined';
|
||||
|
||||
const supportsRequestStreams = isFetchSupported && (() => {
|
||||
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 DEFAULT_CHUNK_SIZE = 64 * 1024;
|
||||
|
||||
const resolvers = {
|
||||
stream: (res) => res.body
|
||||
};
|
||||
|
||||
isFetchSupported && ['text', 'arrayBuffer', 'blob', 'formData'].forEach(type => [
|
||||
resolvers[type] = utils.isFunction(Response.prototype[type]) ? (res) => res[type]() : (_, config) => {
|
||||
throw new AxiosError(`Response type ${type} is not supported`, AxiosError.ERR_NOT_SUPPORT, config);
|
||||
}
|
||||
])
|
||||
|
||||
const getBodyLength = async (body) => {
|
||||
if(utils.isBlob(body)) {
|
||||
return body.size;
|
||||
}
|
||||
|
||||
if(utils.isSpecCompliantForm(body)) {
|
||||
return (await new Request(body).arrayBuffer()).byteLength;
|
||||
}
|
||||
|
||||
if(utils.isArrayBufferView(body)) {
|
||||
return body.byteLength;
|
||||
}
|
||||
|
||||
if(utils.isURLSearchParams(body)) {
|
||||
body = body + '';
|
||||
}
|
||||
|
||||
if(utils.isString(body)) {
|
||||
return (await new TextEncoder().encode(body)).byteLength;
|
||||
}
|
||||
}
|
||||
|
||||
const resolveBodyLength = async (headers, body) => {
|
||||
const length = utils.toFiniteNumber(headers.getContentLength());
|
||||
|
||||
return length == null ? getBodyLength(body) : length;
|
||||
}
|
||||
|
||||
export default async (config) => {
|
||||
let {
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
signal,
|
||||
cancelToken,
|
||||
timeout,
|
||||
onDownloadProgress,
|
||||
onUploadProgress,
|
||||
responseType,
|
||||
headers,
|
||||
withCredentials = 'same-origin',
|
||||
fetchOptions
|
||||
} = resolveConfig(config);
|
||||
|
||||
responseType = responseType ? (responseType + '').toLowerCase() : 'text';
|
||||
|
||||
let [composedSignal, stopTimeout] = (signal || cancelToken || timeout) ?
|
||||
composeSignals([signal, cancelToken], timeout) : [];
|
||||
|
||||
let finished, request;
|
||||
|
||||
const onFinish = () => {
|
||||
!finished && setTimeout(() => {
|
||||
composedSignal && composedSignal.unsubscribe();
|
||||
});
|
||||
|
||||
finished = true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (onUploadProgress && supportsRequestStreams && method !== 'get' && method !== 'head') {
|
||||
let requestContentLength = await resolveBodyLength(headers, data);
|
||||
|
||||
let _request = new Request(url, {
|
||||
method,
|
||||
body: data,
|
||||
duplex: "half"
|
||||
});
|
||||
|
||||
let contentTypeHeader;
|
||||
|
||||
if (utils.isFormData(data) && (contentTypeHeader = _request.headers.get('content-type'))) {
|
||||
headers.setContentType(contentTypeHeader)
|
||||
}
|
||||
|
||||
data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, fetchProgressDecorator(
|
||||
requestContentLength,
|
||||
progressEventReducer(onUploadProgress)
|
||||
));
|
||||
}
|
||||
|
||||
if (!utils.isString(withCredentials)) {
|
||||
withCredentials = withCredentials ? 'cors' : 'omit';
|
||||
}
|
||||
|
||||
request = new Request(url, {
|
||||
...fetchOptions,
|
||||
signal: composedSignal,
|
||||
method,
|
||||
headers: headers.normalize().toJSON(),
|
||||
body: data,
|
||||
duplex: "half",
|
||||
withCredentials
|
||||
});
|
||||
|
||||
let response = await fetch(request);
|
||||
|
||||
const isStreamResponse = responseType === 'stream' || responseType === 'response';
|
||||
|
||||
if (onDownloadProgress || isStreamResponse) {
|
||||
const options = {};
|
||||
|
||||
Object.getOwnPropertyNames(response).forEach(prop => {
|
||||
options[prop] = response[prop];
|
||||
});
|
||||
|
||||
const responseContentLength = utils.toFiniteNumber(response.headers.get('content-length'));
|
||||
|
||||
response = new Response(
|
||||
trackStream(response.body, DEFAULT_CHUNK_SIZE, onDownloadProgress && fetchProgressDecorator(
|
||||
responseContentLength,
|
||||
progressEventReducer(onDownloadProgress, true)
|
||||
), isStreamResponse && onFinish),
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
responseType = responseType || 'text';
|
||||
|
||||
let responseData = await resolvers[utils.findKey(resolvers, responseType) || 'text'](response, config);
|
||||
|
||||
!isStreamResponse && onFinish();
|
||||
|
||||
stopTimeout && stopTimeout();
|
||||
|
||||
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) {
|
||||
onFinish();
|
||||
|
||||
let {code} = err;
|
||||
|
||||
if (err.name === 'NetworkError') {
|
||||
code = AxiosError.ERR_NETWORK;
|
||||
}
|
||||
|
||||
throw AxiosError.from(err, code, config, request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+31
-101
@@ -1,93 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
import utils from './../utils.js';
|
||||
import settle from './../core/settle.js';
|
||||
import cookies from './../helpers/cookies.js';
|
||||
import buildURL from './../helpers/buildURL.js';
|
||||
import buildFullPath from '../core/buildFullPath.js';
|
||||
import isURLSameOrigin from './../helpers/isURLSameOrigin.js';
|
||||
import transitionalDefaults from '../defaults/transitional.js';
|
||||
import AxiosError from '../core/AxiosError.js';
|
||||
import CanceledError from '../cancel/CanceledError.js';
|
||||
import parseProtocol from '../helpers/parseProtocol.js';
|
||||
import platform from '../platform/index.js';
|
||||
import AxiosHeaders from '../core/AxiosHeaders.js';
|
||||
import speedometer from '../helpers/speedometer.js';
|
||||
|
||||
function progressEventReducer(listener, isDownloadStream) {
|
||||
let bytesNotified = 0;
|
||||
const _speedometer = speedometer(50, 250);
|
||||
|
||||
return e => {
|
||||
const loaded = e.loaded;
|
||||
const total = e.lengthComputable ? e.total : undefined;
|
||||
const progressBytes = loaded - bytesNotified;
|
||||
const rate = _speedometer(progressBytes);
|
||||
const inRange = loaded <= total;
|
||||
|
||||
bytesNotified = loaded;
|
||||
|
||||
const data = {
|
||||
loaded,
|
||||
total,
|
||||
progress: total ? (loaded / total) : undefined,
|
||||
bytes: progressBytes,
|
||||
rate: rate ? rate : undefined,
|
||||
estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
|
||||
event: e
|
||||
};
|
||||
|
||||
data[isDownloadStream ? 'download' : 'upload'] = true;
|
||||
|
||||
listener(data);
|
||||
};
|
||||
}
|
||||
import progressEventReducer from '../helpers/progressEventReducer.js';
|
||||
import resolveConfig from "../helpers/resolveConfig.js";
|
||||
|
||||
const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
|
||||
|
||||
export default isXHRAdapterSupported && function (config) {
|
||||
return new Promise(function dispatchXhrRequest(resolve, reject) {
|
||||
let requestData = config.data;
|
||||
const requestHeaders = AxiosHeaders.from(config.headers).normalize();
|
||||
let {responseType, withXSRFToken} = config;
|
||||
const _config = resolveConfig(config);
|
||||
let requestData = _config.data;
|
||||
const requestHeaders = AxiosHeaders.from(_config.headers).normalize();
|
||||
let {responseType} = _config;
|
||||
let onCanceled;
|
||||
function done() {
|
||||
if (config.cancelToken) {
|
||||
config.cancelToken.unsubscribe(onCanceled);
|
||||
if (_config.cancelToken) {
|
||||
_config.cancelToken.unsubscribe(onCanceled);
|
||||
}
|
||||
|
||||
if (config.signal) {
|
||||
config.signal.removeEventListener('abort', onCanceled);
|
||||
}
|
||||
}
|
||||
|
||||
let contentType;
|
||||
|
||||
if (utils.isFormData(requestData)) {
|
||||
if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) {
|
||||
requestHeaders.setContentType(false); // Let the browser set it
|
||||
} else if ((contentType = requestHeaders.getContentType()) !== false) {
|
||||
// fix semicolon duplication issue for ReactNative FormData implementation
|
||||
const [type, ...tokens] = contentType ? contentType.split(';').map(token => token.trim()).filter(Boolean) : [];
|
||||
requestHeaders.setContentType([type || 'multipart/form-data', ...tokens].join('; '));
|
||||
if (_config.signal) {
|
||||
_config.signal.removeEventListener('abort', onCanceled);
|
||||
}
|
||||
}
|
||||
|
||||
let request = new XMLHttpRequest();
|
||||
|
||||
// HTTP basic authentication
|
||||
if (config.auth) {
|
||||
const username = config.auth.username || '';
|
||||
const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
|
||||
requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
|
||||
}
|
||||
|
||||
const fullPath = buildFullPath(config.baseURL, config.url);
|
||||
|
||||
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
|
||||
request.open(_config.method.toUpperCase(), _config.url, true);
|
||||
|
||||
// Set the request timeout in MS
|
||||
request.timeout = config.timeout;
|
||||
request.timeout = _config.timeout;
|
||||
|
||||
function onloadend() {
|
||||
if (!request) {
|
||||
@@ -149,7 +95,7 @@ export default isXHRAdapterSupported && function (config) {
|
||||
return;
|
||||
}
|
||||
|
||||
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
|
||||
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, _config, request));
|
||||
|
||||
// Clean up request
|
||||
request = null;
|
||||
@@ -159,7 +105,7 @@ export default isXHRAdapterSupported && function (config) {
|
||||
request.onerror = function handleError() {
|
||||
// Real errors are hidden from us by the browser
|
||||
// onerror should only fire if it's a network error
|
||||
reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
|
||||
reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, _config, request));
|
||||
|
||||
// Clean up request
|
||||
request = null;
|
||||
@@ -167,37 +113,21 @@ export default isXHRAdapterSupported && function (config) {
|
||||
|
||||
// Handle timeout
|
||||
request.ontimeout = function handleTimeout() {
|
||||
let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
|
||||
const transitional = config.transitional || transitionalDefaults;
|
||||
if (config.timeoutErrorMessage) {
|
||||
timeoutErrorMessage = config.timeoutErrorMessage;
|
||||
let timeoutErrorMessage = _config.timeout ? 'timeout of ' + _config.timeout + 'ms exceeded' : 'timeout exceeded';
|
||||
const transitional = _config.transitional || transitionalDefaults;
|
||||
if (_config.timeoutErrorMessage) {
|
||||
timeoutErrorMessage = _config.timeoutErrorMessage;
|
||||
}
|
||||
reject(new AxiosError(
|
||||
timeoutErrorMessage,
|
||||
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
|
||||
config,
|
||||
_config,
|
||||
request));
|
||||
|
||||
// Clean up request
|
||||
request = null;
|
||||
};
|
||||
|
||||
// Add xsrf header
|
||||
// This is only done if running in a standard browser environment.
|
||||
// Specifically not if we're in a web worker, or react-native.
|
||||
if(platform.hasStandardBrowserEnv) {
|
||||
withXSRFToken && utils.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(config));
|
||||
|
||||
if (withXSRFToken || (withXSRFToken !== false && isURLSameOrigin(fullPath))) {
|
||||
// Add xsrf header
|
||||
const xsrfValue = config.xsrfHeaderName && config.xsrfCookieName && cookies.read(config.xsrfCookieName);
|
||||
|
||||
if (xsrfValue) {
|
||||
requestHeaders.set(config.xsrfHeaderName, xsrfValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove Content-Type if data is undefined
|
||||
requestData === undefined && requestHeaders.setContentType(null);
|
||||
|
||||
@@ -209,26 +139,26 @@ export default isXHRAdapterSupported && function (config) {
|
||||
}
|
||||
|
||||
// Add withCredentials to request if needed
|
||||
if (!utils.isUndefined(config.withCredentials)) {
|
||||
request.withCredentials = !!config.withCredentials;
|
||||
if (!utils.isUndefined(_config.withCredentials)) {
|
||||
request.withCredentials = !!_config.withCredentials;
|
||||
}
|
||||
|
||||
// Add responseType to request if needed
|
||||
if (responseType && responseType !== 'json') {
|
||||
request.responseType = config.responseType;
|
||||
request.responseType = _config.responseType;
|
||||
}
|
||||
|
||||
// Handle progress if needed
|
||||
if (typeof config.onDownloadProgress === 'function') {
|
||||
request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
|
||||
if (typeof _config.onDownloadProgress === 'function') {
|
||||
request.addEventListener('progress', progressEventReducer(_config.onDownloadProgress, true));
|
||||
}
|
||||
|
||||
// Not all browsers support upload events
|
||||
if (typeof config.onUploadProgress === 'function' && request.upload) {
|
||||
request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
|
||||
if (typeof _config.onUploadProgress === 'function' && request.upload) {
|
||||
request.upload.addEventListener('progress', progressEventReducer(_config.onUploadProgress));
|
||||
}
|
||||
|
||||
if (config.cancelToken || config.signal) {
|
||||
if (_config.cancelToken || _config.signal) {
|
||||
// Handle cancellation
|
||||
// eslint-disable-next-line func-names
|
||||
onCanceled = cancel => {
|
||||
@@ -240,13 +170,13 @@ export default isXHRAdapterSupported && function (config) {
|
||||
request = null;
|
||||
};
|
||||
|
||||
config.cancelToken && config.cancelToken.subscribe(onCanceled);
|
||||
if (config.signal) {
|
||||
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
|
||||
_config.cancelToken && _config.cancelToken.subscribe(onCanceled);
|
||||
if (_config.signal) {
|
||||
_config.signal.aborted ? onCanceled() : _config.signal.addEventListener('abort', onCanceled);
|
||||
}
|
||||
}
|
||||
|
||||
const protocol = parseProtocol(fullPath);
|
||||
const protocol = parseProtocol(_config.url);
|
||||
|
||||
if (protocol && platform.protocols.indexOf(protocol) === -1) {
|
||||
reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
|
||||
|
||||
@@ -100,6 +100,10 @@ class AxiosHeaders {
|
||||
setHeaders(header, valueOrRewrite)
|
||||
} else if(utils.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) {
|
||||
setHeaders(parseHeaders(header), valueOrRewrite);
|
||||
} else if (utils.isHeaders(header)) {
|
||||
for (const [key, value] of header.entries()) {
|
||||
setHeader(value, key, rewrite);
|
||||
}
|
||||
} else {
|
||||
header != null && setHeader(valueOrRewrite, header, rewrite);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ const defaults = {
|
||||
|
||||
transitional: transitionalDefaults,
|
||||
|
||||
adapter: ['xhr', 'http'],
|
||||
adapter: ['xhr', 'http', 'fetch'],
|
||||
|
||||
transformRequest: [function transformRequest(data, headers) {
|
||||
const contentType = headers.getContentType() || '';
|
||||
@@ -58,7 +58,8 @@ const defaults = {
|
||||
utils.isBuffer(data) ||
|
||||
utils.isStream(data) ||
|
||||
utils.isFile(data) ||
|
||||
utils.isBlob(data)
|
||||
utils.isBlob(data) ||
|
||||
utils.isReadableStream(data)
|
||||
) {
|
||||
return data;
|
||||
}
|
||||
@@ -101,6 +102,10 @@ const defaults = {
|
||||
const forcedJSONParsing = transitional && transitional.forcedJSONParsing;
|
||||
const JSONRequested = this.responseType === 'json';
|
||||
|
||||
if (utils.isResponse(data) || utils.isReadableStream(data)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (data && utils.isString(data) && ((forcedJSONParsing && !this.responseType) || JSONRequested)) {
|
||||
const silentJSONParsing = transitional && transitional.silentJSONParsing;
|
||||
const strictJSONParsing = !silentJSONParsing && JSONRequested;
|
||||
|
||||
@@ -65,19 +65,20 @@ class AxiosTransformStream extends stream.Transform{
|
||||
|
||||
process.nextTick(() => {
|
||||
self.emit('progress', {
|
||||
'loaded': bytesTransferred,
|
||||
'total': totalBytes,
|
||||
'progress': totalBytes ? (bytesTransferred / totalBytes) : undefined,
|
||||
'bytes': progressBytes,
|
||||
'rate': rate ? rate : undefined,
|
||||
'estimated': rate && totalBytes && bytesTransferred <= totalBytes ?
|
||||
(totalBytes - bytesTransferred) / rate : undefined
|
||||
loaded: bytesTransferred,
|
||||
total: totalBytes,
|
||||
progress: totalBytes ? (bytesTransferred / totalBytes) : undefined,
|
||||
bytes: progressBytes,
|
||||
rate: rate ? rate : undefined,
|
||||
estimated: rate && totalBytes && bytesTransferred <= totalBytes ?
|
||||
(totalBytes - bytesTransferred) / rate : undefined,
|
||||
lengthComputable: totalBytes != null
|
||||
});
|
||||
});
|
||||
}, internals.ticksRate);
|
||||
|
||||
const onFinish = () => {
|
||||
internals.updateProgress(true);
|
||||
internals.updateProgress.call(true);
|
||||
};
|
||||
|
||||
this.once('end', onFinish);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import CanceledError from "../cancel/CanceledError.js";
|
||||
import AxiosError from "../core/AxiosError.js";
|
||||
|
||||
const composeSignals = (signals, timeout) => {
|
||||
let controller = new AbortController();
|
||||
|
||||
let aborted;
|
||||
|
||||
const onabort = function (cancel) {
|
||||
if (!aborted) {
|
||||
aborted = true;
|
||||
unsubscribe();
|
||||
const err = cancel instanceof Error ? cancel : this.reason;
|
||||
controller.abort(err instanceof AxiosError ? err : new CanceledError(err instanceof Error ? err.message : err));
|
||||
}
|
||||
}
|
||||
|
||||
let timer = timeout && setTimeout(() => {
|
||||
onabort(new AxiosError(`timeout ${timeout} of ms exceeded`, AxiosError.ETIMEDOUT))
|
||||
}, timeout)
|
||||
|
||||
const unsubscribe = () => {
|
||||
if (signals) {
|
||||
timer && clearTimeout(timer);
|
||||
timer = null;
|
||||
signals.forEach(signal => {
|
||||
signal &&
|
||||
(signal.removeEventListener ? signal.removeEventListener('abort', onabort) : signal.unsubscribe(onabort));
|
||||
});
|
||||
signals = null;
|
||||
}
|
||||
}
|
||||
|
||||
signals.forEach((signal) => signal && signal.addEventListener && signal.addEventListener('abort', onabort));
|
||||
|
||||
const {signal} = controller;
|
||||
|
||||
signal.unsubscribe = unsubscribe;
|
||||
|
||||
return [signal, () => {
|
||||
timer && clearTimeout(timer);
|
||||
timer = null;
|
||||
}];
|
||||
}
|
||||
|
||||
export default composeSignals;
|
||||
@@ -0,0 +1,32 @@
|
||||
import speedometer from "./speedometer.js";
|
||||
import throttle from "./throttle.js";
|
||||
|
||||
export default (listener, isDownloadStream, freq = 3) => {
|
||||
let bytesNotified = 0;
|
||||
const _speedometer = speedometer(50, 250);
|
||||
|
||||
return throttle(e => {
|
||||
const loaded = e.loaded;
|
||||
const total = e.lengthComputable ? e.total : undefined;
|
||||
const progressBytes = loaded - bytesNotified;
|
||||
const rate = _speedometer(progressBytes);
|
||||
const inRange = loaded <= total;
|
||||
|
||||
bytesNotified = loaded;
|
||||
|
||||
const data = {
|
||||
loaded,
|
||||
total,
|
||||
progress: total ? (loaded / total) : undefined,
|
||||
bytes: progressBytes,
|
||||
rate: rate ? rate : undefined,
|
||||
estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
|
||||
event: e,
|
||||
lengthComputable: total != null
|
||||
};
|
||||
|
||||
data[isDownloadStream ? 'download' : 'upload'] = true;
|
||||
|
||||
listener(data);
|
||||
}, freq);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import platform from "../platform/index.js";
|
||||
import utils from "../utils.js";
|
||||
import isURLSameOrigin from "./isURLSameOrigin.js";
|
||||
import cookies from "./cookies.js";
|
||||
import buildFullPath from "../core/buildFullPath.js";
|
||||
import mergeConfig from "../core/mergeConfig.js";
|
||||
import AxiosHeaders from "../core/AxiosHeaders.js";
|
||||
import buildURL from "./buildURL.js";
|
||||
|
||||
export default (config) => {
|
||||
const newConfig = mergeConfig({}, config);
|
||||
|
||||
let {data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth} = newConfig;
|
||||
|
||||
newConfig.headers = headers = AxiosHeaders.from(headers);
|
||||
|
||||
newConfig.url = buildURL(buildFullPath(newConfig.baseURL, newConfig.url), config.params, config.paramsSerializer);
|
||||
|
||||
// HTTP basic authentication
|
||||
if (auth) {
|
||||
headers.set('Authorization', 'Basic ' +
|
||||
btoa((auth.username || '') + ':' + (auth.password ? unescape(encodeURIComponent(auth.password)) : ''))
|
||||
);
|
||||
}
|
||||
|
||||
let contentType;
|
||||
|
||||
if (utils.isFormData(data)) {
|
||||
if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) {
|
||||
headers.setContentType(undefined); // Let the browser set it
|
||||
} else if ((contentType = headers.getContentType()) !== false) {
|
||||
// fix semicolon duplication issue for ReactNative FormData implementation
|
||||
const [type, ...tokens] = contentType ? contentType.split(';').map(token => token.trim()).filter(Boolean) : [];
|
||||
headers.setContentType([type || 'multipart/form-data', ...tokens].join('; '));
|
||||
}
|
||||
}
|
||||
|
||||
// Add xsrf header
|
||||
// This is only done if running in a standard browser environment.
|
||||
// Specifically not if we're in a web worker, or react-native.
|
||||
|
||||
if (platform.hasStandardBrowserEnv) {
|
||||
withXSRFToken && utils.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(newConfig));
|
||||
|
||||
if (withXSRFToken || (withXSRFToken !== false && isURLSameOrigin(newConfig.url))) {
|
||||
// Add xsrf header
|
||||
const xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName);
|
||||
|
||||
if (xsrfValue) {
|
||||
headers.set(xsrfHeaderName, xsrfValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ function throttle(fn, freq) {
|
||||
let timestamp = 0;
|
||||
const threshold = 1000 / freq;
|
||||
let timer = null;
|
||||
return function throttled(force, args) {
|
||||
return function throttled() {
|
||||
const force = this === true;
|
||||
|
||||
const now = Date.now();
|
||||
if (force || now - timestamp > threshold) {
|
||||
if (timer) {
|
||||
@@ -18,13 +20,13 @@ function throttle(fn, freq) {
|
||||
timer = null;
|
||||
}
|
||||
timestamp = now;
|
||||
return fn.apply(null, args);
|
||||
return fn.apply(null, arguments);
|
||||
}
|
||||
if (!timer) {
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
timestamp = Date.now();
|
||||
return fn.apply(null, args);
|
||||
return fn.apply(null, arguments);
|
||||
}, threshold - (now - timestamp));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
export const streamChunk = function* (chunk, chunkSize) {
|
||||
let len = chunk.byteLength;
|
||||
|
||||
if (!chunkSize || len < chunkSize) {
|
||||
yield chunk;
|
||||
return;
|
||||
}
|
||||
|
||||
let pos = 0;
|
||||
let end;
|
||||
|
||||
while (pos < len) {
|
||||
end = pos + chunkSize;
|
||||
yield chunk.slice(pos, end);
|
||||
pos = end;
|
||||
}
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
export const readBytes = async function* (iterable, chunkSize) {
|
||||
for await (const chunk of iterable) {
|
||||
yield* streamChunk(ArrayBuffer.isView(chunk) ? chunk : (await encoder.encode(String(chunk))), chunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
export const trackStream = (stream, chunkSize, onProgress, onFinish) => {
|
||||
const iterator = readBytes(stream, chunkSize);
|
||||
|
||||
let bytes = 0;
|
||||
|
||||
return new ReadableStream({
|
||||
type: 'bytes',
|
||||
|
||||
async pull(controller) {
|
||||
const {done, value} = await iterator.next();
|
||||
|
||||
if (done) {
|
||||
controller.close();
|
||||
onFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
let len = value.byteLength;
|
||||
onProgress && onProgress(bytes += len);
|
||||
controller.enqueue(new Uint8Array(value));
|
||||
},
|
||||
cancel(reason) {
|
||||
onFinish(reason);
|
||||
return iterator.return();
|
||||
}
|
||||
}, {
|
||||
highWaterMark: 2
|
||||
})
|
||||
}
|
||||
@@ -40,8 +40,11 @@ const hasStandardBrowserWebWorkerEnv = (() => {
|
||||
);
|
||||
})();
|
||||
|
||||
const origin = hasBrowserEnv && window.location.href || 'http://localhost';
|
||||
|
||||
export {
|
||||
hasBrowserEnv,
|
||||
hasStandardBrowserWebWorkerEnv,
|
||||
hasStandardBrowserEnv
|
||||
hasStandardBrowserEnv,
|
||||
origin
|
||||
}
|
||||
|
||||
+7
-2
@@ -209,6 +209,8 @@ const isFormData = (thing) => {
|
||||
*/
|
||||
const isURLSearchParams = kindOfTest('URLSearchParams');
|
||||
|
||||
const [isReadableStream, isRequest, isResponse, isHeaders] = ['ReadableStream', 'Request', 'Response', 'Headers'].map(kindOfTest);
|
||||
|
||||
/**
|
||||
* Trim excess whitespace off the beginning and end of a string
|
||||
*
|
||||
@@ -597,8 +599,7 @@ const toObjectSet = (arrayOrString, delimiter) => {
|
||||
const noop = () => {}
|
||||
|
||||
const toFiniteNumber = (value, defaultValue) => {
|
||||
value = +value;
|
||||
return Number.isFinite(value) ? value : defaultValue;
|
||||
return value != null && Number.isFinite(value = +value) ? value : defaultValue;
|
||||
}
|
||||
|
||||
const ALPHA = 'abcdefghijklmnopqrstuvwxyz'
|
||||
@@ -679,6 +680,10 @@ export default {
|
||||
isBoolean,
|
||||
isObject,
|
||||
isPlainObject,
|
||||
isReadableStream,
|
||||
isRequest,
|
||||
isResponse,
|
||||
isHeaders,
|
||||
isUndefined,
|
||||
isDate,
|
||||
isFile,
|
||||
|
||||
Reference in New Issue
Block a user