AjaxObservable.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. "use strict";
  2. var __extends = (this && this.__extends) || function (d, b) {
  3. for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
  4. function __() { this.constructor = d; }
  5. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  6. };
  7. var root_1 = require('../../util/root');
  8. var tryCatch_1 = require('../../util/tryCatch');
  9. var errorObject_1 = require('../../util/errorObject');
  10. var Observable_1 = require('../../Observable');
  11. var Subscriber_1 = require('../../Subscriber');
  12. var map_1 = require('../../operators/map');
  13. function getCORSRequest() {
  14. if (root_1.root.XMLHttpRequest) {
  15. return new root_1.root.XMLHttpRequest();
  16. }
  17. else if (!!root_1.root.XDomainRequest) {
  18. return new root_1.root.XDomainRequest();
  19. }
  20. else {
  21. throw new Error('CORS is not supported by your browser');
  22. }
  23. }
  24. function getXMLHttpRequest() {
  25. if (root_1.root.XMLHttpRequest) {
  26. return new root_1.root.XMLHttpRequest();
  27. }
  28. else {
  29. var progId = void 0;
  30. try {
  31. var progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
  32. for (var i = 0; i < 3; i++) {
  33. try {
  34. progId = progIds[i];
  35. if (new root_1.root.ActiveXObject(progId)) {
  36. break;
  37. }
  38. }
  39. catch (e) {
  40. }
  41. }
  42. return new root_1.root.ActiveXObject(progId);
  43. }
  44. catch (e) {
  45. throw new Error('XMLHttpRequest is not supported by your browser');
  46. }
  47. }
  48. }
  49. function ajaxGet(url, headers) {
  50. if (headers === void 0) { headers = null; }
  51. return new AjaxObservable({ method: 'GET', url: url, headers: headers });
  52. }
  53. exports.ajaxGet = ajaxGet;
  54. ;
  55. function ajaxPost(url, body, headers) {
  56. return new AjaxObservable({ method: 'POST', url: url, body: body, headers: headers });
  57. }
  58. exports.ajaxPost = ajaxPost;
  59. ;
  60. function ajaxDelete(url, headers) {
  61. return new AjaxObservable({ method: 'DELETE', url: url, headers: headers });
  62. }
  63. exports.ajaxDelete = ajaxDelete;
  64. ;
  65. function ajaxPut(url, body, headers) {
  66. return new AjaxObservable({ method: 'PUT', url: url, body: body, headers: headers });
  67. }
  68. exports.ajaxPut = ajaxPut;
  69. ;
  70. function ajaxPatch(url, body, headers) {
  71. return new AjaxObservable({ method: 'PATCH', url: url, body: body, headers: headers });
  72. }
  73. exports.ajaxPatch = ajaxPatch;
  74. ;
  75. var mapResponse = map_1.map(function (x, index) { return x.response; });
  76. function ajaxGetJSON(url, headers) {
  77. return mapResponse(new AjaxObservable({
  78. method: 'GET',
  79. url: url,
  80. responseType: 'json',
  81. headers: headers
  82. }));
  83. }
  84. exports.ajaxGetJSON = ajaxGetJSON;
  85. ;
  86. /**
  87. * We need this JSDoc comment for affecting ESDoc.
  88. * @extends {Ignored}
  89. * @hide true
  90. */
  91. var AjaxObservable = (function (_super) {
  92. __extends(AjaxObservable, _super);
  93. function AjaxObservable(urlOrRequest) {
  94. _super.call(this);
  95. var request = {
  96. async: true,
  97. createXHR: function () {
  98. return this.crossDomain ? getCORSRequest.call(this) : getXMLHttpRequest();
  99. },
  100. crossDomain: false,
  101. withCredentials: false,
  102. headers: {},
  103. method: 'GET',
  104. responseType: 'json',
  105. timeout: 0
  106. };
  107. if (typeof urlOrRequest === 'string') {
  108. request.url = urlOrRequest;
  109. }
  110. else {
  111. for (var prop in urlOrRequest) {
  112. if (urlOrRequest.hasOwnProperty(prop)) {
  113. request[prop] = urlOrRequest[prop];
  114. }
  115. }
  116. }
  117. this.request = request;
  118. }
  119. /** @deprecated internal use only */ AjaxObservable.prototype._subscribe = function (subscriber) {
  120. return new AjaxSubscriber(subscriber, this.request);
  121. };
  122. /**
  123. * Creates an observable for an Ajax request with either a request object with
  124. * url, headers, etc or a string for a URL.
  125. *
  126. * @example
  127. * source = Rx.Observable.ajax('/products');
  128. * source = Rx.Observable.ajax({ url: 'products', method: 'GET' });
  129. *
  130. * @param {string|Object} request Can be one of the following:
  131. * A string of the URL to make the Ajax call.
  132. * An object with the following properties
  133. * - url: URL of the request
  134. * - body: The body of the request
  135. * - method: Method of the request, such as GET, POST, PUT, PATCH, DELETE
  136. * - async: Whether the request is async
  137. * - headers: Optional headers
  138. * - crossDomain: true if a cross domain request, else false
  139. * - createXHR: a function to override if you need to use an alternate
  140. * XMLHttpRequest implementation.
  141. * - resultSelector: a function to use to alter the output value type of
  142. * the Observable. Gets {@link AjaxResponse} as an argument.
  143. * @return {Observable} An observable sequence containing the XMLHttpRequest.
  144. * @static true
  145. * @name ajax
  146. * @owner Observable
  147. */
  148. AjaxObservable.create = (function () {
  149. var create = function (urlOrRequest) {
  150. return new AjaxObservable(urlOrRequest);
  151. };
  152. create.get = ajaxGet;
  153. create.post = ajaxPost;
  154. create.delete = ajaxDelete;
  155. create.put = ajaxPut;
  156. create.patch = ajaxPatch;
  157. create.getJSON = ajaxGetJSON;
  158. return create;
  159. })();
  160. return AjaxObservable;
  161. }(Observable_1.Observable));
  162. exports.AjaxObservable = AjaxObservable;
  163. /**
  164. * We need this JSDoc comment for affecting ESDoc.
  165. * @ignore
  166. * @extends {Ignored}
  167. */
  168. var AjaxSubscriber = (function (_super) {
  169. __extends(AjaxSubscriber, _super);
  170. function AjaxSubscriber(destination, request) {
  171. _super.call(this, destination);
  172. this.request = request;
  173. this.done = false;
  174. var headers = request.headers = request.headers || {};
  175. // force CORS if requested
  176. if (!request.crossDomain && !headers['X-Requested-With']) {
  177. headers['X-Requested-With'] = 'XMLHttpRequest';
  178. }
  179. // ensure content type is set
  180. if (!('Content-Type' in headers) && !(root_1.root.FormData && request.body instanceof root_1.root.FormData) && typeof request.body !== 'undefined') {
  181. headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
  182. }
  183. // properly serialize body
  184. request.body = this.serializeBody(request.body, request.headers['Content-Type']);
  185. this.send();
  186. }
  187. AjaxSubscriber.prototype.next = function (e) {
  188. this.done = true;
  189. var _a = this, xhr = _a.xhr, request = _a.request, destination = _a.destination;
  190. var response = new AjaxResponse(e, xhr, request);
  191. destination.next(response);
  192. };
  193. AjaxSubscriber.prototype.send = function () {
  194. var _a = this, request = _a.request, _b = _a.request, user = _b.user, method = _b.method, url = _b.url, async = _b.async, password = _b.password, headers = _b.headers, body = _b.body;
  195. var createXHR = request.createXHR;
  196. var xhr = tryCatch_1.tryCatch(createXHR).call(request);
  197. if (xhr === errorObject_1.errorObject) {
  198. this.error(errorObject_1.errorObject.e);
  199. }
  200. else {
  201. this.xhr = xhr;
  202. // set up the events before open XHR
  203. // https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
  204. // You need to add the event listeners before calling open() on the request.
  205. // Otherwise the progress events will not fire.
  206. this.setupEvents(xhr, request);
  207. // open XHR
  208. var result = void 0;
  209. if (user) {
  210. result = tryCatch_1.tryCatch(xhr.open).call(xhr, method, url, async, user, password);
  211. }
  212. else {
  213. result = tryCatch_1.tryCatch(xhr.open).call(xhr, method, url, async);
  214. }
  215. if (result === errorObject_1.errorObject) {
  216. this.error(errorObject_1.errorObject.e);
  217. return null;
  218. }
  219. // timeout, responseType and withCredentials can be set once the XHR is open
  220. if (async) {
  221. xhr.timeout = request.timeout;
  222. xhr.responseType = request.responseType;
  223. }
  224. if ('withCredentials' in xhr) {
  225. xhr.withCredentials = !!request.withCredentials;
  226. }
  227. // set headers
  228. this.setHeaders(xhr, headers);
  229. // finally send the request
  230. result = body ? tryCatch_1.tryCatch(xhr.send).call(xhr, body) : tryCatch_1.tryCatch(xhr.send).call(xhr);
  231. if (result === errorObject_1.errorObject) {
  232. this.error(errorObject_1.errorObject.e);
  233. return null;
  234. }
  235. }
  236. return xhr;
  237. };
  238. AjaxSubscriber.prototype.serializeBody = function (body, contentType) {
  239. if (!body || typeof body === 'string') {
  240. return body;
  241. }
  242. else if (root_1.root.FormData && body instanceof root_1.root.FormData) {
  243. return body;
  244. }
  245. if (contentType) {
  246. var splitIndex = contentType.indexOf(';');
  247. if (splitIndex !== -1) {
  248. contentType = contentType.substring(0, splitIndex);
  249. }
  250. }
  251. switch (contentType) {
  252. case 'application/x-www-form-urlencoded':
  253. return Object.keys(body).map(function (key) { return (encodeURI(key) + "=" + encodeURI(body[key])); }).join('&');
  254. case 'application/json':
  255. return JSON.stringify(body);
  256. default:
  257. return body;
  258. }
  259. };
  260. AjaxSubscriber.prototype.setHeaders = function (xhr, headers) {
  261. for (var key in headers) {
  262. if (headers.hasOwnProperty(key)) {
  263. xhr.setRequestHeader(key, headers[key]);
  264. }
  265. }
  266. };
  267. AjaxSubscriber.prototype.setupEvents = function (xhr, request) {
  268. var progressSubscriber = request.progressSubscriber;
  269. function xhrTimeout(e) {
  270. var _a = xhrTimeout, subscriber = _a.subscriber, progressSubscriber = _a.progressSubscriber, request = _a.request;
  271. if (progressSubscriber) {
  272. progressSubscriber.error(e);
  273. }
  274. subscriber.error(new AjaxTimeoutError(this, request)); //TODO: Make betterer.
  275. }
  276. ;
  277. xhr.ontimeout = xhrTimeout;
  278. xhrTimeout.request = request;
  279. xhrTimeout.subscriber = this;
  280. xhrTimeout.progressSubscriber = progressSubscriber;
  281. if (xhr.upload && 'withCredentials' in xhr) {
  282. if (progressSubscriber) {
  283. var xhrProgress_1;
  284. xhrProgress_1 = function (e) {
  285. var progressSubscriber = xhrProgress_1.progressSubscriber;
  286. progressSubscriber.next(e);
  287. };
  288. if (root_1.root.XDomainRequest) {
  289. xhr.onprogress = xhrProgress_1;
  290. }
  291. else {
  292. xhr.upload.onprogress = xhrProgress_1;
  293. }
  294. xhrProgress_1.progressSubscriber = progressSubscriber;
  295. }
  296. var xhrError_1;
  297. xhrError_1 = function (e) {
  298. var _a = xhrError_1, progressSubscriber = _a.progressSubscriber, subscriber = _a.subscriber, request = _a.request;
  299. if (progressSubscriber) {
  300. progressSubscriber.error(e);
  301. }
  302. subscriber.error(new AjaxError('ajax error', this, request));
  303. };
  304. xhr.onerror = xhrError_1;
  305. xhrError_1.request = request;
  306. xhrError_1.subscriber = this;
  307. xhrError_1.progressSubscriber = progressSubscriber;
  308. }
  309. function xhrReadyStateChange(e) {
  310. var _a = xhrReadyStateChange, subscriber = _a.subscriber, progressSubscriber = _a.progressSubscriber, request = _a.request;
  311. if (this.readyState === 4) {
  312. // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
  313. var status_1 = this.status === 1223 ? 204 : this.status;
  314. var response = (this.responseType === 'text' ? (this.response || this.responseText) : this.response);
  315. // fix status code when it is 0 (0 status is undocumented).
  316. // Occurs when accessing file resources or on Android 4.1 stock browser
  317. // while retrieving files from application cache.
  318. if (status_1 === 0) {
  319. status_1 = response ? 200 : 0;
  320. }
  321. if (200 <= status_1 && status_1 < 300) {
  322. if (progressSubscriber) {
  323. progressSubscriber.complete();
  324. }
  325. subscriber.next(e);
  326. subscriber.complete();
  327. }
  328. else {
  329. if (progressSubscriber) {
  330. progressSubscriber.error(e);
  331. }
  332. subscriber.error(new AjaxError('ajax error ' + status_1, this, request));
  333. }
  334. }
  335. }
  336. ;
  337. xhr.onreadystatechange = xhrReadyStateChange;
  338. xhrReadyStateChange.subscriber = this;
  339. xhrReadyStateChange.progressSubscriber = progressSubscriber;
  340. xhrReadyStateChange.request = request;
  341. };
  342. AjaxSubscriber.prototype.unsubscribe = function () {
  343. var _a = this, done = _a.done, xhr = _a.xhr;
  344. if (!done && xhr && xhr.readyState !== 4 && typeof xhr.abort === 'function') {
  345. xhr.abort();
  346. }
  347. _super.prototype.unsubscribe.call(this);
  348. };
  349. return AjaxSubscriber;
  350. }(Subscriber_1.Subscriber));
  351. exports.AjaxSubscriber = AjaxSubscriber;
  352. /**
  353. * A normalized AJAX response.
  354. *
  355. * @see {@link ajax}
  356. *
  357. * @class AjaxResponse
  358. */
  359. var AjaxResponse = (function () {
  360. function AjaxResponse(originalEvent, xhr, request) {
  361. this.originalEvent = originalEvent;
  362. this.xhr = xhr;
  363. this.request = request;
  364. this.status = xhr.status;
  365. this.responseType = xhr.responseType || request.responseType;
  366. this.response = parseXhrResponse(this.responseType, xhr);
  367. }
  368. return AjaxResponse;
  369. }());
  370. exports.AjaxResponse = AjaxResponse;
  371. /**
  372. * A normalized AJAX error.
  373. *
  374. * @see {@link ajax}
  375. *
  376. * @class AjaxError
  377. */
  378. var AjaxError = (function (_super) {
  379. __extends(AjaxError, _super);
  380. function AjaxError(message, xhr, request) {
  381. _super.call(this, message);
  382. this.message = message;
  383. this.xhr = xhr;
  384. this.request = request;
  385. this.status = xhr.status;
  386. this.responseType = xhr.responseType || request.responseType;
  387. this.response = parseXhrResponse(this.responseType, xhr);
  388. }
  389. return AjaxError;
  390. }(Error));
  391. exports.AjaxError = AjaxError;
  392. function parseXhrResponse(responseType, xhr) {
  393. switch (responseType) {
  394. case 'json':
  395. if ('response' in xhr) {
  396. //IE does not support json as responseType, parse it internally
  397. return xhr.responseType ? xhr.response : JSON.parse(xhr.response || xhr.responseText || 'null');
  398. }
  399. else {
  400. // HACK(benlesh): TypeScript shennanigans
  401. // tslint:disable-next-line:no-any latest TS seems to think xhr is "never" here.
  402. return JSON.parse(xhr.responseText || 'null');
  403. }
  404. case 'xml':
  405. return xhr.responseXML;
  406. case 'text':
  407. default:
  408. // HACK(benlesh): TypeScript shennanigans
  409. // tslint:disable-next-line:no-any latest TS seems to think xhr is "never" here.
  410. return ('response' in xhr) ? xhr.response : xhr.responseText;
  411. }
  412. }
  413. /**
  414. * @see {@link ajax}
  415. *
  416. * @class AjaxTimeoutError
  417. */
  418. var AjaxTimeoutError = (function (_super) {
  419. __extends(AjaxTimeoutError, _super);
  420. function AjaxTimeoutError(xhr, request) {
  421. _super.call(this, 'ajax timeout', xhr, request);
  422. }
  423. return AjaxTimeoutError;
  424. }(AjaxError));
  425. exports.AjaxTimeoutError = AjaxTimeoutError;
  426. //# sourceMappingURL=AjaxObservable.js.map