axios
Version:
Promise based HTTP client for the browser and node.js
301 lines (267 loc) • 8.66 kB
JavaScript
;
var utils = require('./../utils');
var settle = require('./../core/settle');
var cookies = require('./../helpers/cookies');
var buildURL = require('./../helpers/buildURL');
var buildFullPath = require('../core/buildFullPath');
var parseHeaders = require('./../helpers/parseHeaders');
var isURLSameOrigin = require('./../helpers/isURLSameOrigin');
var transitionalDefaults = require('../defaults/transitional');
var AxiosError = require('../core/AxiosError');
var CanceledError = require('../cancel/CanceledError');
var parseProtocol = require('../helpers/parseProtocol');
var platform = require('../platform');
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
var responseType = config.responseType;
// Guard against prototype pollution: only honor own properties.
var withXSRFToken = utils.hasOwnProperty(config, 'withXSRFToken')
? config.withXSRFToken
: undefined;
var onCanceled;
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}
if (utils.isFormData(requestData) && utils.isStandardBrowserEnv()) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
var request = new XMLHttpRequest();
// HTTP basic authentication
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password
? unescape(encodeURIComponent(config.auth.password))
: '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
var fullPath = buildFullPath(
config.baseURL,
config.url,
config.allowAbsoluteUrls
);
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
var responseHeaders =
'getAllResponseHeaders' in request
? parseHeaders(request.getAllResponseHeaders())
: null;
var responseData =
!responseType || responseType === 'text' || responseType === 'json'
? request.responseText
: request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: 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() {
var timeoutErrorMessage = config.timeout
? 'timeout of ' + config.timeout + 'ms exceeded'
: 'timeout exceeded';
var 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 (utils.isStandardBrowserEnv()) {
// Add xsrf header
if (utils.isFunction(withXSRFToken)) {
withXSRFToken = withXSRFToken(config);
}
// Strict boolean check: only `true` short-circuits the same-origin guard.
if (
withXSRFToken === true ||
(withXSRFToken !== false && isURLSameOrigin(fullPath))
) {
// Add xsrf header
var xsrfValue =
config.xsrfHeaderName &&
config.xsrfCookieName &&
cookies.read(config.xsrfCookieName);
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
}
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (
typeof requestData === 'undefined' &&
key.toLowerCase() === 'content-type'
) {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
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', config.onDownloadProgress);
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function(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);
}
}
// false, 0 (zero number), and '' (empty string) are valid JSON values
if (
!requestData &&
requestData !== false &&
requestData !== 0 &&
requestData !== ''
) {
requestData = null;
}
var 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);
});
};