[d24f17c] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | import utils from './../utils.js';
|
---|
| 4 | import settle from './../core/settle.js';
|
---|
| 5 | import cookies from './../helpers/cookies.js';
|
---|
| 6 | import buildURL from './../helpers/buildURL.js';
|
---|
| 7 | import buildFullPath from '../core/buildFullPath.js';
|
---|
| 8 | import isURLSameOrigin from './../helpers/isURLSameOrigin.js';
|
---|
| 9 | import transitionalDefaults from '../defaults/transitional.js';
|
---|
| 10 | import AxiosError from '../core/AxiosError.js';
|
---|
| 11 | import CanceledError from '../cancel/CanceledError.js';
|
---|
| 12 | import parseProtocol from '../helpers/parseProtocol.js';
|
---|
| 13 | import platform from '../platform/index.js';
|
---|
| 14 | import AxiosHeaders from '../core/AxiosHeaders.js';
|
---|
| 15 | import speedometer from '../helpers/speedometer.js';
|
---|
| 16 |
|
---|
| 17 | function progressEventReducer(listener, isDownloadStream) {
|
---|
| 18 | let bytesNotified = 0;
|
---|
| 19 | const _speedometer = speedometer(50, 250);
|
---|
| 20 |
|
---|
| 21 | return e => {
|
---|
| 22 | const loaded = e.loaded;
|
---|
| 23 | const total = e.lengthComputable ? e.total : undefined;
|
---|
| 24 | const progressBytes = loaded - bytesNotified;
|
---|
| 25 | const rate = _speedometer(progressBytes);
|
---|
| 26 | const inRange = loaded <= total;
|
---|
| 27 |
|
---|
| 28 | bytesNotified = loaded;
|
---|
| 29 |
|
---|
| 30 | const data = {
|
---|
| 31 | loaded,
|
---|
| 32 | total,
|
---|
| 33 | progress: total ? (loaded / total) : undefined,
|
---|
| 34 | bytes: progressBytes,
|
---|
| 35 | rate: rate ? rate : undefined,
|
---|
| 36 | estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
|
---|
| 37 | event: e
|
---|
| 38 | };
|
---|
| 39 |
|
---|
| 40 | data[isDownloadStream ? 'download' : 'upload'] = true;
|
---|
| 41 |
|
---|
| 42 | listener(data);
|
---|
| 43 | };
|
---|
| 44 | }
|
---|
| 45 |
|
---|
| 46 | const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
|
---|
| 47 |
|
---|
| 48 | export default isXHRAdapterSupported && function (config) {
|
---|
| 49 | return new Promise(function dispatchXhrRequest(resolve, reject) {
|
---|
| 50 | let requestData = config.data;
|
---|
| 51 | const requestHeaders = AxiosHeaders.from(config.headers).normalize();
|
---|
| 52 | let {responseType, withXSRFToken} = config;
|
---|
| 53 | let onCanceled;
|
---|
| 54 | function done() {
|
---|
| 55 | if (config.cancelToken) {
|
---|
| 56 | config.cancelToken.unsubscribe(onCanceled);
|
---|
| 57 | }
|
---|
| 58 |
|
---|
| 59 | if (config.signal) {
|
---|
| 60 | config.signal.removeEventListener('abort', onCanceled);
|
---|
| 61 | }
|
---|
| 62 | }
|
---|
| 63 |
|
---|
| 64 | let contentType;
|
---|
| 65 |
|
---|
| 66 | if (utils.isFormData(requestData)) {
|
---|
| 67 | if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) {
|
---|
| 68 | requestHeaders.setContentType(false); // Let the browser set it
|
---|
| 69 | } else if ((contentType = requestHeaders.getContentType()) !== false) {
|
---|
| 70 | // fix semicolon duplication issue for ReactNative FormData implementation
|
---|
| 71 | const [type, ...tokens] = contentType ? contentType.split(';').map(token => token.trim()).filter(Boolean) : [];
|
---|
| 72 | requestHeaders.setContentType([type || 'multipart/form-data', ...tokens].join('; '));
|
---|
| 73 | }
|
---|
| 74 | }
|
---|
| 75 |
|
---|
| 76 | let request = new XMLHttpRequest();
|
---|
| 77 |
|
---|
| 78 | // HTTP basic authentication
|
---|
| 79 | if (config.auth) {
|
---|
| 80 | const username = config.auth.username || '';
|
---|
| 81 | const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
|
---|
| 82 | requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
|
---|
| 83 | }
|
---|
| 84 |
|
---|
| 85 | const fullPath = buildFullPath(config.baseURL, config.url);
|
---|
| 86 |
|
---|
| 87 | request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
|
---|
| 88 |
|
---|
| 89 | // Set the request timeout in MS
|
---|
| 90 | request.timeout = config.timeout;
|
---|
| 91 |
|
---|
| 92 | function onloadend() {
|
---|
| 93 | if (!request) {
|
---|
| 94 | return;
|
---|
| 95 | }
|
---|
| 96 | // Prepare the response
|
---|
| 97 | const responseHeaders = AxiosHeaders.from(
|
---|
| 98 | 'getAllResponseHeaders' in request && request.getAllResponseHeaders()
|
---|
| 99 | );
|
---|
| 100 | const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
|
---|
| 101 | request.responseText : request.response;
|
---|
| 102 | const response = {
|
---|
| 103 | data: responseData,
|
---|
| 104 | status: request.status,
|
---|
| 105 | statusText: request.statusText,
|
---|
| 106 | headers: responseHeaders,
|
---|
| 107 | config,
|
---|
| 108 | request
|
---|
| 109 | };
|
---|
| 110 |
|
---|
| 111 | settle(function _resolve(value) {
|
---|
| 112 | resolve(value);
|
---|
| 113 | done();
|
---|
| 114 | }, function _reject(err) {
|
---|
| 115 | reject(err);
|
---|
| 116 | done();
|
---|
| 117 | }, response);
|
---|
| 118 |
|
---|
| 119 | // Clean up request
|
---|
| 120 | request = null;
|
---|
| 121 | }
|
---|
| 122 |
|
---|
| 123 | if ('onloadend' in request) {
|
---|
| 124 | // Use onloadend if available
|
---|
| 125 | request.onloadend = onloadend;
|
---|
| 126 | } else {
|
---|
| 127 | // Listen for ready state to emulate onloadend
|
---|
| 128 | request.onreadystatechange = function handleLoad() {
|
---|
| 129 | if (!request || request.readyState !== 4) {
|
---|
| 130 | return;
|
---|
| 131 | }
|
---|
| 132 |
|
---|
| 133 | // The request errored out and we didn't get a response, this will be
|
---|
| 134 | // handled by onerror instead
|
---|
| 135 | // With one exception: request that using file: protocol, most browsers
|
---|
| 136 | // will return status as 0 even though it's a successful request
|
---|
| 137 | if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
|
---|
| 138 | return;
|
---|
| 139 | }
|
---|
| 140 | // readystate handler is calling before onerror or ontimeout handlers,
|
---|
| 141 | // so we should call onloadend on the next 'tick'
|
---|
| 142 | setTimeout(onloadend);
|
---|
| 143 | };
|
---|
| 144 | }
|
---|
| 145 |
|
---|
| 146 | // Handle browser request cancellation (as opposed to a manual cancellation)
|
---|
| 147 | request.onabort = function handleAbort() {
|
---|
| 148 | if (!request) {
|
---|
| 149 | return;
|
---|
| 150 | }
|
---|
| 151 |
|
---|
| 152 | reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
|
---|
| 153 |
|
---|
| 154 | // Clean up request
|
---|
| 155 | request = null;
|
---|
| 156 | };
|
---|
| 157 |
|
---|
| 158 | // Handle low level network errors
|
---|
| 159 | request.onerror = function handleError() {
|
---|
| 160 | // Real errors are hidden from us by the browser
|
---|
| 161 | // onerror should only fire if it's a network error
|
---|
| 162 | reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
|
---|
| 163 |
|
---|
| 164 | // Clean up request
|
---|
| 165 | request = null;
|
---|
| 166 | };
|
---|
| 167 |
|
---|
| 168 | // Handle timeout
|
---|
| 169 | request.ontimeout = function handleTimeout() {
|
---|
| 170 | let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
|
---|
| 171 | const transitional = config.transitional || transitionalDefaults;
|
---|
| 172 | if (config.timeoutErrorMessage) {
|
---|
| 173 | timeoutErrorMessage = config.timeoutErrorMessage;
|
---|
| 174 | }
|
---|
| 175 | reject(new AxiosError(
|
---|
| 176 | timeoutErrorMessage,
|
---|
| 177 | transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
|
---|
| 178 | config,
|
---|
| 179 | request));
|
---|
| 180 |
|
---|
| 181 | // Clean up request
|
---|
| 182 | request = null;
|
---|
| 183 | };
|
---|
| 184 |
|
---|
| 185 | // Add xsrf header
|
---|
| 186 | // This is only done if running in a standard browser environment.
|
---|
| 187 | // Specifically not if we're in a web worker, or react-native.
|
---|
| 188 | if(platform.hasStandardBrowserEnv) {
|
---|
| 189 | withXSRFToken && utils.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(config));
|
---|
| 190 |
|
---|
| 191 | if (withXSRFToken || (withXSRFToken !== false && isURLSameOrigin(fullPath))) {
|
---|
| 192 | // Add xsrf header
|
---|
| 193 | const xsrfValue = config.xsrfHeaderName && config.xsrfCookieName && cookies.read(config.xsrfCookieName);
|
---|
| 194 |
|
---|
| 195 | if (xsrfValue) {
|
---|
| 196 | requestHeaders.set(config.xsrfHeaderName, xsrfValue);
|
---|
| 197 | }
|
---|
| 198 | }
|
---|
| 199 | }
|
---|
| 200 |
|
---|
| 201 | // Remove Content-Type if data is undefined
|
---|
| 202 | requestData === undefined && requestHeaders.setContentType(null);
|
---|
| 203 |
|
---|
| 204 | // Add headers to the request
|
---|
| 205 | if ('setRequestHeader' in request) {
|
---|
| 206 | utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
|
---|
| 207 | request.setRequestHeader(key, val);
|
---|
| 208 | });
|
---|
| 209 | }
|
---|
| 210 |
|
---|
| 211 | // Add withCredentials to request if needed
|
---|
| 212 | if (!utils.isUndefined(config.withCredentials)) {
|
---|
| 213 | request.withCredentials = !!config.withCredentials;
|
---|
| 214 | }
|
---|
| 215 |
|
---|
| 216 | // Add responseType to request if needed
|
---|
| 217 | if (responseType && responseType !== 'json') {
|
---|
| 218 | request.responseType = config.responseType;
|
---|
| 219 | }
|
---|
| 220 |
|
---|
| 221 | // Handle progress if needed
|
---|
| 222 | if (typeof config.onDownloadProgress === 'function') {
|
---|
| 223 | request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
|
---|
| 224 | }
|
---|
| 225 |
|
---|
| 226 | // Not all browsers support upload events
|
---|
| 227 | if (typeof config.onUploadProgress === 'function' && request.upload) {
|
---|
| 228 | request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
|
---|
| 229 | }
|
---|
| 230 |
|
---|
| 231 | if (config.cancelToken || config.signal) {
|
---|
| 232 | // Handle cancellation
|
---|
| 233 | // eslint-disable-next-line func-names
|
---|
| 234 | onCanceled = cancel => {
|
---|
| 235 | if (!request) {
|
---|
| 236 | return;
|
---|
| 237 | }
|
---|
| 238 | reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
|
---|
| 239 | request.abort();
|
---|
| 240 | request = null;
|
---|
| 241 | };
|
---|
| 242 |
|
---|
| 243 | config.cancelToken && config.cancelToken.subscribe(onCanceled);
|
---|
| 244 | if (config.signal) {
|
---|
| 245 | config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
|
---|
| 246 | }
|
---|
| 247 | }
|
---|
| 248 |
|
---|
| 249 | const protocol = parseProtocol(fullPath);
|
---|
| 250 |
|
---|
| 251 | if (protocol && platform.protocols.indexOf(protocol) === -1) {
|
---|
| 252 | reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
|
---|
| 253 | return;
|
---|
| 254 | }
|
---|
| 255 |
|
---|
| 256 |
|
---|
| 257 | // Send the request
|
---|
| 258 | request.send(requestData || null);
|
---|
| 259 | });
|
---|
| 260 | }
|
---|