index.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. "use strict";
  2. var window = require("global/window");
  3. var _extends = require("@babel/runtime/helpers/extends");
  4. var isFunction = require('is-function');
  5. createXHR.httpHandler = require('./http-handler.js');
  6. /**
  7. * @license
  8. * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
  9. * Copyright (c) 2014 David Björklund
  10. * Available under the MIT license
  11. * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
  12. */
  13. var parseHeaders = function parseHeaders(headers) {
  14. var result = {};
  15. if (!headers) {
  16. return result;
  17. }
  18. headers.trim().split('\n').forEach(function (row) {
  19. var index = row.indexOf(':');
  20. var key = row.slice(0, index).trim().toLowerCase();
  21. var value = row.slice(index + 1).trim();
  22. if (typeof result[key] === 'undefined') {
  23. result[key] = value;
  24. } else if (Array.isArray(result[key])) {
  25. result[key].push(value);
  26. } else {
  27. result[key] = [result[key], value];
  28. }
  29. });
  30. return result;
  31. };
  32. module.exports = createXHR; // Allow use of default import syntax in TypeScript
  33. module.exports.default = createXHR;
  34. createXHR.XMLHttpRequest = window.XMLHttpRequest || noop;
  35. createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window.XDomainRequest;
  36. forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
  37. createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
  38. options = initParams(uri, options, callback);
  39. options.method = method.toUpperCase();
  40. return _createXHR(options);
  41. };
  42. });
  43. function forEachArray(array, iterator) {
  44. for (var i = 0; i < array.length; i++) {
  45. iterator(array[i]);
  46. }
  47. }
  48. function isEmpty(obj) {
  49. for (var i in obj) {
  50. if (obj.hasOwnProperty(i)) return false;
  51. }
  52. return true;
  53. }
  54. function initParams(uri, options, callback) {
  55. var params = uri;
  56. if (isFunction(options)) {
  57. callback = options;
  58. if (typeof uri === "string") {
  59. params = {
  60. uri: uri
  61. };
  62. }
  63. } else {
  64. params = _extends({}, options, {
  65. uri: uri
  66. });
  67. }
  68. params.callback = callback;
  69. return params;
  70. }
  71. function createXHR(uri, options, callback) {
  72. options = initParams(uri, options, callback);
  73. return _createXHR(options);
  74. }
  75. function _createXHR(options) {
  76. if (typeof options.callback === "undefined") {
  77. throw new Error("callback argument missing");
  78. }
  79. var called = false;
  80. var callback = function cbOnce(err, response, body) {
  81. if (!called) {
  82. called = true;
  83. options.callback(err, response, body);
  84. }
  85. };
  86. function readystatechange() {
  87. if (xhr.readyState === 4) {
  88. setTimeout(loadFunc, 0);
  89. }
  90. }
  91. function getBody() {
  92. // Chrome with requestType=blob throws errors arround when even testing access to responseText
  93. var body = undefined;
  94. if (xhr.response) {
  95. body = xhr.response;
  96. } else {
  97. body = xhr.responseText || getXml(xhr);
  98. }
  99. if (isJson) {
  100. try {
  101. body = JSON.parse(body);
  102. } catch (e) {}
  103. }
  104. return body;
  105. }
  106. function errorFunc(evt) {
  107. clearTimeout(timeoutTimer);
  108. if (!(evt instanceof Error)) {
  109. evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
  110. }
  111. evt.statusCode = 0;
  112. return callback(evt, failureResponse);
  113. } // will load the data & process the response in a special response object
  114. function loadFunc() {
  115. if (aborted) return;
  116. var status;
  117. clearTimeout(timeoutTimer);
  118. if (options.useXDR && xhr.status === undefined) {
  119. //IE8 CORS GET successful response doesn't have a status field, but body is fine
  120. status = 200;
  121. } else {
  122. status = xhr.status === 1223 ? 204 : xhr.status;
  123. }
  124. var response = failureResponse;
  125. var err = null;
  126. if (status !== 0) {
  127. response = {
  128. body: getBody(),
  129. statusCode: status,
  130. method: method,
  131. headers: {},
  132. url: uri,
  133. rawRequest: xhr
  134. };
  135. if (xhr.getAllResponseHeaders) {
  136. //remember xhr can in fact be XDR for CORS in IE
  137. response.headers = parseHeaders(xhr.getAllResponseHeaders());
  138. }
  139. } else {
  140. err = new Error("Internal XMLHttpRequest Error");
  141. }
  142. return callback(err, response, response.body);
  143. }
  144. var xhr = options.xhr || null;
  145. if (!xhr) {
  146. if (options.cors || options.useXDR) {
  147. xhr = new createXHR.XDomainRequest();
  148. } else {
  149. xhr = new createXHR.XMLHttpRequest();
  150. }
  151. }
  152. var key;
  153. var aborted;
  154. var uri = xhr.url = options.uri || options.url;
  155. var method = xhr.method = options.method || "GET";
  156. var body = options.body || options.data;
  157. var headers = xhr.headers = options.headers || {};
  158. var sync = !!options.sync;
  159. var isJson = false;
  160. var timeoutTimer;
  161. var failureResponse = {
  162. body: undefined,
  163. headers: {},
  164. statusCode: 0,
  165. method: method,
  166. url: uri,
  167. rawRequest: xhr
  168. };
  169. if ("json" in options && options.json !== false) {
  170. isJson = true;
  171. headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
  172. if (method !== "GET" && method !== "HEAD") {
  173. headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
  174. body = JSON.stringify(options.json === true ? body : options.json);
  175. }
  176. }
  177. xhr.onreadystatechange = readystatechange;
  178. xhr.onload = loadFunc;
  179. xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
  180. xhr.onprogress = function () {// IE must die
  181. };
  182. xhr.onabort = function () {
  183. aborted = true;
  184. };
  185. xhr.ontimeout = errorFunc;
  186. xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
  187. if (!sync) {
  188. xhr.withCredentials = !!options.withCredentials;
  189. } // Cannot set timeout with sync request
  190. // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
  191. // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
  192. if (!sync && options.timeout > 0) {
  193. timeoutTimer = setTimeout(function () {
  194. if (aborted) return;
  195. aborted = true; //IE9 may still call readystatechange
  196. xhr.abort("timeout");
  197. var e = new Error("XMLHttpRequest timeout");
  198. e.code = "ETIMEDOUT";
  199. errorFunc(e);
  200. }, options.timeout);
  201. }
  202. if (xhr.setRequestHeader) {
  203. for (key in headers) {
  204. if (headers.hasOwnProperty(key)) {
  205. xhr.setRequestHeader(key, headers[key]);
  206. }
  207. }
  208. } else if (options.headers && !isEmpty(options.headers)) {
  209. throw new Error("Headers cannot be set on an XDomainRequest object");
  210. }
  211. if ("responseType" in options) {
  212. xhr.responseType = options.responseType;
  213. }
  214. if ("beforeSend" in options && typeof options.beforeSend === "function") {
  215. options.beforeSend(xhr);
  216. } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
  217. // XMLHttpRequest spec says to pass null as body to indicate no body
  218. // See https://github.com/naugtur/xhr/issues/100.
  219. xhr.send(body || null);
  220. return xhr;
  221. }
  222. function getXml(xhr) {
  223. // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
  224. // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
  225. try {
  226. if (xhr.responseType === "document") {
  227. return xhr.responseXML;
  228. }
  229. var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
  230. if (xhr.responseType === "" && !firefoxBugTakenEffect) {
  231. return xhr.responseXML;
  232. }
  233. } catch (e) {}
  234. return null;
  235. }
  236. function noop() {}