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 | }
|
---|