| 1 | import utils from '../utils.js';
|
|---|
| 2 | import settle from '../core/settle.js';
|
|---|
| 3 | import transitionalDefaults from '../defaults/transitional.js';
|
|---|
| 4 | import AxiosError from '../core/AxiosError.js';
|
|---|
| 5 | import CanceledError from '../cancel/CanceledError.js';
|
|---|
| 6 | import parseProtocol from '../helpers/parseProtocol.js';
|
|---|
| 7 | import platform from '../platform/index.js';
|
|---|
| 8 | import AxiosHeaders from '../core/AxiosHeaders.js';
|
|---|
| 9 | import { progressEventReducer } from '../helpers/progressEventReducer.js';
|
|---|
| 10 | import resolveConfig from '../helpers/resolveConfig.js';
|
|---|
| 11 | import { toByteStringHeaderObject } from '../helpers/sanitizeHeaderValue.js';
|
|---|
| 12 |
|
|---|
| 13 | const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
|
|---|
| 14 |
|
|---|
| 15 | export default isXHRAdapterSupported &&
|
|---|
| 16 | function (config) {
|
|---|
| 17 | return new Promise(function dispatchXhrRequest(resolve, reject) {
|
|---|
| 18 | const _config = resolveConfig(config);
|
|---|
| 19 | let requestData = _config.data;
|
|---|
| 20 | const requestHeaders = AxiosHeaders.from(_config.headers).normalize();
|
|---|
| 21 | let { responseType, onUploadProgress, onDownloadProgress } = _config;
|
|---|
| 22 | let onCanceled;
|
|---|
| 23 | let uploadThrottled, downloadThrottled;
|
|---|
| 24 | let flushUpload, flushDownload;
|
|---|
| 25 |
|
|---|
| 26 | function done() {
|
|---|
| 27 | flushUpload && flushUpload(); // flush events
|
|---|
| 28 | flushDownload && flushDownload(); // flush events
|
|---|
| 29 |
|
|---|
| 30 | _config.cancelToken && _config.cancelToken.unsubscribe(onCanceled);
|
|---|
| 31 |
|
|---|
| 32 | _config.signal && _config.signal.removeEventListener('abort', onCanceled);
|
|---|
| 33 | }
|
|---|
| 34 |
|
|---|
| 35 | let request = new XMLHttpRequest();
|
|---|
| 36 |
|
|---|
| 37 | request.open(_config.method.toUpperCase(), _config.url, true);
|
|---|
| 38 |
|
|---|
| 39 | // Set the request timeout in MS
|
|---|
| 40 | request.timeout = _config.timeout;
|
|---|
| 41 |
|
|---|
| 42 | function onloadend() {
|
|---|
| 43 | if (!request) {
|
|---|
| 44 | return;
|
|---|
| 45 | }
|
|---|
| 46 | // Prepare the response
|
|---|
| 47 | const responseHeaders = AxiosHeaders.from(
|
|---|
| 48 | 'getAllResponseHeaders' in request && request.getAllResponseHeaders()
|
|---|
| 49 | );
|
|---|
| 50 | const responseData =
|
|---|
| 51 | !responseType || responseType === 'text' || responseType === 'json'
|
|---|
| 52 | ? request.responseText
|
|---|
| 53 | : request.response;
|
|---|
| 54 | const response = {
|
|---|
| 55 | data: responseData,
|
|---|
| 56 | status: request.status,
|
|---|
| 57 | statusText: request.statusText,
|
|---|
| 58 | headers: responseHeaders,
|
|---|
| 59 | config,
|
|---|
| 60 | request,
|
|---|
| 61 | };
|
|---|
| 62 |
|
|---|
| 63 | settle(
|
|---|
| 64 | function _resolve(value) {
|
|---|
| 65 | resolve(value);
|
|---|
| 66 | done();
|
|---|
| 67 | },
|
|---|
| 68 | function _reject(err) {
|
|---|
| 69 | reject(err);
|
|---|
| 70 | done();
|
|---|
| 71 | },
|
|---|
| 72 | response
|
|---|
| 73 | );
|
|---|
| 74 |
|
|---|
| 75 | // Clean up request
|
|---|
| 76 | request = null;
|
|---|
| 77 | }
|
|---|
| 78 |
|
|---|
| 79 | if ('onloadend' in request) {
|
|---|
| 80 | // Use onloadend if available
|
|---|
| 81 | request.onloadend = onloadend;
|
|---|
| 82 | } else {
|
|---|
| 83 | // Listen for ready state to emulate onloadend
|
|---|
| 84 | request.onreadystatechange = function handleLoad() {
|
|---|
| 85 | if (!request || request.readyState !== 4) {
|
|---|
| 86 | return;
|
|---|
| 87 | }
|
|---|
| 88 |
|
|---|
| 89 | // The request errored out and we didn't get a response, this will be
|
|---|
| 90 | // handled by onerror instead
|
|---|
| 91 | // With one exception: request that using file: protocol, most browsers
|
|---|
| 92 | // will return status as 0 even though it's a successful request
|
|---|
| 93 | if (
|
|---|
| 94 | request.status === 0 &&
|
|---|
| 95 | !(request.responseURL && request.responseURL.startsWith('file:'))
|
|---|
| 96 | ) {
|
|---|
| 97 | return;
|
|---|
| 98 | }
|
|---|
| 99 | // readystate handler is calling before onerror or ontimeout handlers,
|
|---|
| 100 | // so we should call onloadend on the next 'tick'
|
|---|
| 101 | setTimeout(onloadend);
|
|---|
| 102 | };
|
|---|
| 103 | }
|
|---|
| 104 |
|
|---|
| 105 | // Handle browser request cancellation (as opposed to a manual cancellation)
|
|---|
| 106 | request.onabort = function handleAbort() {
|
|---|
| 107 | if (!request) {
|
|---|
| 108 | return;
|
|---|
| 109 | }
|
|---|
| 110 |
|
|---|
| 111 | reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
|
|---|
| 112 | done();
|
|---|
| 113 |
|
|---|
| 114 | // Clean up request
|
|---|
| 115 | request = null;
|
|---|
| 116 | };
|
|---|
| 117 |
|
|---|
| 118 | // Handle low level network errors
|
|---|
| 119 | request.onerror = function handleError(event) {
|
|---|
| 120 | // Browsers deliver a ProgressEvent in XHR onerror
|
|---|
| 121 | // (message may be empty; when present, surface it)
|
|---|
| 122 | // See https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/error_event
|
|---|
| 123 | const msg = event && event.message ? event.message : 'Network Error';
|
|---|
| 124 | const err = new AxiosError(msg, AxiosError.ERR_NETWORK, config, request);
|
|---|
| 125 | // attach the underlying event for consumers who want details
|
|---|
| 126 | err.event = event || null;
|
|---|
| 127 | reject(err);
|
|---|
| 128 | done();
|
|---|
| 129 | request = null;
|
|---|
| 130 | };
|
|---|
| 131 |
|
|---|
| 132 | // Handle timeout
|
|---|
| 133 | request.ontimeout = function handleTimeout() {
|
|---|
| 134 | let timeoutErrorMessage = _config.timeout
|
|---|
| 135 | ? 'timeout of ' + _config.timeout + 'ms exceeded'
|
|---|
| 136 | : 'timeout exceeded';
|
|---|
| 137 | const transitional = _config.transitional || transitionalDefaults;
|
|---|
| 138 | if (_config.timeoutErrorMessage) {
|
|---|
| 139 | timeoutErrorMessage = _config.timeoutErrorMessage;
|
|---|
| 140 | }
|
|---|
| 141 | reject(
|
|---|
| 142 | new AxiosError(
|
|---|
| 143 | timeoutErrorMessage,
|
|---|
| 144 | transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
|
|---|
| 145 | config,
|
|---|
| 146 | request
|
|---|
| 147 | )
|
|---|
| 148 | );
|
|---|
| 149 | done();
|
|---|
| 150 |
|
|---|
| 151 | // Clean up request
|
|---|
| 152 | request = null;
|
|---|
| 153 | };
|
|---|
| 154 |
|
|---|
| 155 | // Remove Content-Type if data is undefined
|
|---|
| 156 | requestData === undefined && requestHeaders.setContentType(null);
|
|---|
| 157 |
|
|---|
| 158 | // Add headers to the request
|
|---|
| 159 | if ('setRequestHeader' in request) {
|
|---|
| 160 | utils.forEach(toByteStringHeaderObject(requestHeaders), function setRequestHeader(val, key) {
|
|---|
| 161 | request.setRequestHeader(key, val);
|
|---|
| 162 | });
|
|---|
| 163 | }
|
|---|
| 164 |
|
|---|
| 165 | // Add withCredentials to request if needed
|
|---|
| 166 | if (!utils.isUndefined(_config.withCredentials)) {
|
|---|
| 167 | request.withCredentials = !!_config.withCredentials;
|
|---|
| 168 | }
|
|---|
| 169 |
|
|---|
| 170 | // Add responseType to request if needed
|
|---|
| 171 | if (responseType && responseType !== 'json') {
|
|---|
| 172 | request.responseType = _config.responseType;
|
|---|
| 173 | }
|
|---|
| 174 |
|
|---|
| 175 | // Handle progress if needed
|
|---|
| 176 | if (onDownloadProgress) {
|
|---|
| 177 | [downloadThrottled, flushDownload] = progressEventReducer(onDownloadProgress, true);
|
|---|
| 178 | request.addEventListener('progress', downloadThrottled);
|
|---|
| 179 | }
|
|---|
| 180 |
|
|---|
| 181 | // Not all browsers support upload events
|
|---|
| 182 | if (onUploadProgress && request.upload) {
|
|---|
| 183 | [uploadThrottled, flushUpload] = progressEventReducer(onUploadProgress);
|
|---|
| 184 |
|
|---|
| 185 | request.upload.addEventListener('progress', uploadThrottled);
|
|---|
| 186 |
|
|---|
| 187 | request.upload.addEventListener('loadend', flushUpload);
|
|---|
| 188 | }
|
|---|
| 189 |
|
|---|
| 190 | if (_config.cancelToken || _config.signal) {
|
|---|
| 191 | // Handle cancellation
|
|---|
| 192 | // eslint-disable-next-line func-names
|
|---|
| 193 | onCanceled = (cancel) => {
|
|---|
| 194 | if (!request) {
|
|---|
| 195 | return;
|
|---|
| 196 | }
|
|---|
| 197 | reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
|
|---|
| 198 | request.abort();
|
|---|
| 199 | done();
|
|---|
| 200 | request = null;
|
|---|
| 201 | };
|
|---|
| 202 |
|
|---|
| 203 | _config.cancelToken && _config.cancelToken.subscribe(onCanceled);
|
|---|
| 204 | if (_config.signal) {
|
|---|
| 205 | _config.signal.aborted
|
|---|
| 206 | ? onCanceled()
|
|---|
| 207 | : _config.signal.addEventListener('abort', onCanceled);
|
|---|
| 208 | }
|
|---|
| 209 | }
|
|---|
| 210 |
|
|---|
| 211 | const protocol = parseProtocol(_config.url);
|
|---|
| 212 |
|
|---|
| 213 | if (protocol && !platform.protocols.includes(protocol)) {
|
|---|
| 214 | reject(
|
|---|
| 215 | new AxiosError(
|
|---|
| 216 | 'Unsupported protocol ' + protocol + ':',
|
|---|
| 217 | AxiosError.ERR_BAD_REQUEST,
|
|---|
| 218 | config
|
|---|
| 219 | )
|
|---|
| 220 | );
|
|---|
| 221 | return;
|
|---|
| 222 | }
|
|---|
| 223 |
|
|---|
| 224 | // Send the request
|
|---|
| 225 | request.send(requestData || null);
|
|---|
| 226 | });
|
|---|
| 227 | };
|
|---|