259 lines
8.5 KiB
JavaScript
259 lines
8.5 KiB
JavaScript
|
'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);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
const responseType = config.responseType;
|
||
|
let onCanceled;
|
||
|
function done() {
|
||
|
if (config.cancelToken) {
|
||
|
config.cancelToken.unsubscribe(onCanceled);
|
||
|
}
|
||
|
|
||
|
if (config.signal) {
|
||
|
config.signal.removeEventListener('abort', onCanceled);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let contentType;
|
||
|
|
||
|
if (utils.isFormData(requestData)) {
|
||
|
if (platform.isStandardBrowserEnv || platform.isStandardBrowserWebWorkerEnv) {
|
||
|
requestHeaders.setContentType(false); // Let the browser set it
|
||
|
} else if(!requestHeaders.getContentType(/^\s*multipart\/form-data/)){
|
||
|
requestHeaders.setContentType('multipart/form-data'); // mobile/desktop app frameworks
|
||
|
} else if(utils.isString(contentType = requestHeaders.getContentType())){
|
||
|
// fix semicolon duplication issue for ReactNative FormData implementation
|
||
|
requestHeaders.setContentType(contentType.replace(/^\s*(multipart\/form-data);+/, '$1'))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
// Set the request timeout in MS
|
||
|
request.timeout = config.timeout;
|
||
|
|
||
|
function onloadend() {
|
||
|
if (!request) {
|
||
|
return;
|
||
|
}
|
||
|
// Prepare the response
|
||
|
const responseHeaders = AxiosHeaders.from(
|
||
|
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
|
||
|
);
|
||
|
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
|
||
|
request.responseText : request.response;
|
||
|
const response = {
|
||
|
data: responseData,
|
||
|
status: request.status,
|
||
|
statusText: request.statusText,
|
||
|
headers: responseHeaders,
|
||
|
config,
|
||
|
request
|
||
|
};
|
||
|
|
||
|
settle(function _resolve(value) {
|
||
|
resolve(value);
|
||
|
done();
|
||
|
}, function _reject(err) {
|
||
|
reject(err);
|
||
|
done();
|
||
|
}, response);
|
||
|
|
||
|
// Clean up request
|
||
|
request = null;
|
||
|
}
|
||
|
|
||
|
if ('onloadend' in request) {
|
||
|
// Use onloadend if available
|
||
|
request.onloadend = onloadend;
|
||
|
} else {
|
||
|
// Listen for ready state to emulate onloadend
|
||
|
request.onreadystatechange = function handleLoad() {
|
||
|
if (!request || request.readyState !== 4) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// The request errored out and we didn't get a response, this will be
|
||
|
// handled by onerror instead
|
||
|
// With one exception: request that using file: protocol, most browsers
|
||
|
// will return status as 0 even though it's a successful request
|
||
|
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
|
||
|
return;
|
||
|
}
|
||
|
// readystate handler is calling before onerror or ontimeout handlers,
|
||
|
// so we should call onloadend on the next 'tick'
|
||
|
setTimeout(onloadend);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Handle browser request cancellation (as opposed to a manual cancellation)
|
||
|
request.onabort = function handleAbort() {
|
||
|
if (!request) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
|
||
|
|
||
|
// Clean up request
|
||
|
request = null;
|
||
|
};
|
||
|
|
||
|
// Handle low level network errors
|
||
|
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));
|
||
|
|
||
|
// Clean up request
|
||
|
request = null;
|
||
|
};
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
reject(new AxiosError(
|
||
|
timeoutErrorMessage,
|
||
|
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
|
||
|
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.isStandardBrowserEnv) {
|
||
|
// Add xsrf header
|
||
|
const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
|
||
|
&& 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);
|
||
|
|
||
|
// Add headers to the request
|
||
|
if ('setRequestHeader' in request) {
|
||
|
utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
|
||
|
request.setRequestHeader(key, val);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Add withCredentials to request if needed
|
||
|
if (!utils.isUndefined(config.withCredentials)) {
|
||
|
request.withCredentials = !!config.withCredentials;
|
||
|
}
|
||
|
|
||
|
// Add responseType to request if needed
|
||
|
if (responseType && responseType !== 'json') {
|
||
|
request.responseType = config.responseType;
|
||
|
}
|
||
|
|
||
|
// Handle progress if needed
|
||
|
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 (config.cancelToken || config.signal) {
|
||
|
// Handle cancellation
|
||
|
// eslint-disable-next-line func-names
|
||
|
onCanceled = cancel => {
|
||
|
if (!request) {
|
||
|
return;
|
||
|
}
|
||
|
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
|
||
|
request.abort();
|
||
|
request = null;
|
||
|
};
|
||
|
|
||
|
config.cancelToken && config.cancelToken.subscribe(onCanceled);
|
||
|
if (config.signal) {
|
||
|
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const protocol = parseProtocol(fullPath);
|
||
|
|
||
|
if (protocol && platform.protocols.indexOf(protocol) === -1) {
|
||
|
reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Send the request
|
||
|
request.send(requestData || null);
|
||
|
});
|
||
|
}
|