RESTController.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. "use strict";
  2. var _CoreManager = _interopRequireDefault(require("./CoreManager"));
  3. var _ParseError = _interopRequireDefault(require("./ParseError"));
  4. var _promiseUtils = require("./promiseUtils");
  5. function _interopRequireDefault(obj) {
  6. return obj && obj.__esModule ? obj : {
  7. default: obj
  8. };
  9. }
  10. /**
  11. * @flow
  12. */
  13. /* global XMLHttpRequest, XDomainRequest */
  14. const uuidv4 = require('./uuid');
  15. /*:: export type RequestOptions = {
  16. useMasterKey?: boolean,
  17. sessionToken?: string,
  18. installationId?: string,
  19. returnStatus?: boolean,
  20. batchSize?: number,
  21. include?: any,
  22. progress?: any,
  23. context?: any,
  24. usePost?: boolean,
  25. };*/
  26. /*:: export type FullOptions = {
  27. success?: any,
  28. error?: any,
  29. useMasterKey?: boolean,
  30. sessionToken?: string,
  31. installationId?: string,
  32. progress?: any,
  33. usePost?: boolean,
  34. };*/
  35. let XHR = null;
  36. if (typeof XMLHttpRequest !== 'undefined') {
  37. XHR = XMLHttpRequest;
  38. }
  39. XHR = require('xmlhttprequest').XMLHttpRequest;
  40. let useXDomainRequest = false;
  41. if (typeof XDomainRequest !== 'undefined' && !('withCredentials' in new XMLHttpRequest())) {
  42. useXDomainRequest = true;
  43. }
  44. function ajaxIE9(method /*: string*/, url /*: string*/, data /*: any*/, headers /*:: ?: any*/, options /*:: ?: FullOptions*/) {
  45. return new Promise((resolve, reject) => {
  46. const xdr = new XDomainRequest();
  47. xdr.onload = function () {
  48. let response;
  49. try {
  50. response = JSON.parse(xdr.responseText);
  51. } catch (e) {
  52. reject(e);
  53. }
  54. if (response) {
  55. resolve({
  56. response
  57. });
  58. }
  59. };
  60. xdr.onerror = xdr.ontimeout = function () {
  61. // Let's fake a real error message.
  62. const fakeResponse = {
  63. responseText: JSON.stringify({
  64. code: _ParseError.default.X_DOMAIN_REQUEST,
  65. error: "IE's XDomainRequest does not supply error info."
  66. })
  67. };
  68. reject(fakeResponse);
  69. };
  70. xdr.onprogress = function () {
  71. if (options && typeof options.progress === 'function') {
  72. options.progress(xdr.responseText);
  73. }
  74. };
  75. xdr.open(method, url);
  76. xdr.send(data);
  77. if (options && typeof options.requestTask === 'function') {
  78. options.requestTask(xdr);
  79. }
  80. });
  81. }
  82. const RESTController = {
  83. ajax(method /*: string*/, url /*: string*/, data /*: any*/, headers /*:: ?: any*/, options /*:: ?: FullOptions*/) {
  84. if (useXDomainRequest) {
  85. return ajaxIE9(method, url, data, headers, options);
  86. }
  87. const promise = (0, _promiseUtils.resolvingPromise)();
  88. const isIdempotent = _CoreManager.default.get('IDEMPOTENCY') && ['POST', 'PUT'].includes(method);
  89. const requestId = isIdempotent ? uuidv4() : '';
  90. let attempts = 0;
  91. const dispatch = function () {
  92. if (XHR == null) {
  93. throw new Error('Cannot make a request: No definition of XMLHttpRequest was found.');
  94. }
  95. let handled = false;
  96. const xhr = new XHR();
  97. xhr.onreadystatechange = function () {
  98. if (xhr.readyState !== 4 || handled || xhr._aborted) {
  99. return;
  100. }
  101. handled = true;
  102. if (xhr.status >= 200 && xhr.status < 300) {
  103. let response;
  104. try {
  105. response = JSON.parse(xhr.responseText);
  106. if (typeof xhr.getResponseHeader === 'function') {
  107. if ((xhr.getAllResponseHeaders() || '').includes('x-parse-job-status-id: ')) {
  108. response = xhr.getResponseHeader('x-parse-job-status-id');
  109. }
  110. if ((xhr.getAllResponseHeaders() || '').includes('x-parse-push-status-id: ')) {
  111. response = xhr.getResponseHeader('x-parse-push-status-id');
  112. }
  113. }
  114. } catch (e) {
  115. promise.reject(e.toString());
  116. }
  117. if (response) {
  118. promise.resolve({
  119. response,
  120. status: xhr.status,
  121. xhr
  122. });
  123. }
  124. } else if (xhr.status >= 500 || xhr.status === 0) {
  125. // retry on 5XX or node-xmlhttprequest error
  126. if (++attempts < _CoreManager.default.get('REQUEST_ATTEMPT_LIMIT')) {
  127. // Exponentially-growing random delay
  128. const delay = Math.round(Math.random() * 125 * Math.pow(2, attempts));
  129. setTimeout(dispatch, delay);
  130. } else if (xhr.status === 0) {
  131. promise.reject('Unable to connect to the Parse API');
  132. } else {
  133. // After the retry limit is reached, fail
  134. promise.reject(xhr);
  135. }
  136. } else {
  137. promise.reject(xhr);
  138. }
  139. };
  140. headers = headers || {};
  141. if (typeof headers['Content-Type'] !== 'string') {
  142. headers['Content-Type'] = 'text/plain'; // Avoid pre-flight
  143. }
  144. if (_CoreManager.default.get('IS_NODE')) {
  145. headers['User-Agent'] = 'Parse/' + _CoreManager.default.get('VERSION') + ' (NodeJS ' + process.versions.node + ')';
  146. }
  147. if (isIdempotent) {
  148. headers['X-Parse-Request-Id'] = requestId;
  149. }
  150. if (_CoreManager.default.get('SERVER_AUTH_TYPE') && _CoreManager.default.get('SERVER_AUTH_TOKEN')) {
  151. headers['Authorization'] = _CoreManager.default.get('SERVER_AUTH_TYPE') + ' ' + _CoreManager.default.get('SERVER_AUTH_TOKEN');
  152. }
  153. const customHeaders = _CoreManager.default.get('REQUEST_HEADERS');
  154. for (const key in customHeaders) {
  155. headers[key] = customHeaders[key];
  156. }
  157. if (options && typeof options.progress === 'function') {
  158. const handleProgress = function (type, event) {
  159. if (event.lengthComputable) {
  160. options.progress(event.loaded / event.total, event.loaded, event.total, {
  161. type
  162. });
  163. } else {
  164. options.progress(null, null, null, {
  165. type
  166. });
  167. }
  168. };
  169. xhr.onprogress = event => {
  170. handleProgress('download', event);
  171. };
  172. if (xhr.upload) {
  173. xhr.upload.onprogress = event => {
  174. handleProgress('upload', event);
  175. };
  176. }
  177. }
  178. xhr.open(method, url, true);
  179. for (const h in headers) {
  180. xhr.setRequestHeader(h, headers[h]);
  181. }
  182. xhr.onabort = function () {
  183. promise.resolve({
  184. response: {
  185. results: []
  186. },
  187. status: 0,
  188. xhr
  189. });
  190. };
  191. xhr.send(data);
  192. if (options && typeof options.requestTask === 'function') {
  193. options.requestTask(xhr);
  194. }
  195. };
  196. dispatch();
  197. return promise;
  198. },
  199. request(method /*: string*/, path /*: string*/, data /*: mixed*/, options /*:: ?: RequestOptions*/) {
  200. options = options || {};
  201. let url = _CoreManager.default.get('SERVER_URL');
  202. if (url[url.length - 1] !== '/') {
  203. url += '/';
  204. }
  205. url += path;
  206. const payload = {};
  207. if (data && typeof data === 'object') {
  208. for (const k in data) {
  209. payload[k] = data[k];
  210. }
  211. }
  212. // Add context
  213. const context = options.context;
  214. if (context !== undefined) {
  215. payload._context = context;
  216. }
  217. if (method !== 'POST') {
  218. payload._method = method;
  219. method = 'POST';
  220. }
  221. payload._ApplicationId = _CoreManager.default.get('APPLICATION_ID');
  222. const jsKey = _CoreManager.default.get('JAVASCRIPT_KEY');
  223. if (jsKey) {
  224. payload._JavaScriptKey = jsKey;
  225. }
  226. payload._ClientVersion = _CoreManager.default.get('VERSION');
  227. let useMasterKey = options.useMasterKey;
  228. if (typeof useMasterKey === 'undefined') {
  229. useMasterKey = _CoreManager.default.get('USE_MASTER_KEY');
  230. }
  231. if (useMasterKey) {
  232. if (_CoreManager.default.get('MASTER_KEY')) {
  233. delete payload._JavaScriptKey;
  234. payload._MasterKey = _CoreManager.default.get('MASTER_KEY');
  235. } else {
  236. throw new Error('Cannot use the Master Key, it has not been provided.');
  237. }
  238. }
  239. if (_CoreManager.default.get('FORCE_REVOCABLE_SESSION')) {
  240. payload._RevocableSession = '1';
  241. }
  242. const installationId = options.installationId;
  243. let installationIdPromise;
  244. if (installationId && typeof installationId === 'string') {
  245. installationIdPromise = Promise.resolve(installationId);
  246. } else {
  247. const installationController = _CoreManager.default.getInstallationController();
  248. installationIdPromise = installationController.currentInstallationId();
  249. }
  250. return installationIdPromise.then(iid => {
  251. payload._InstallationId = iid;
  252. const userController = _CoreManager.default.getUserController();
  253. if (options && typeof options.sessionToken === 'string') {
  254. return Promise.resolve(options.sessionToken);
  255. } else if (userController) {
  256. return userController.currentUserAsync().then(user => {
  257. if (user) {
  258. return Promise.resolve(user.getSessionToken());
  259. }
  260. return Promise.resolve(null);
  261. });
  262. }
  263. return Promise.resolve(null);
  264. }).then(token => {
  265. if (token) {
  266. payload._SessionToken = token;
  267. }
  268. const payloadString = JSON.stringify(payload);
  269. return RESTController.ajax(method, url, payloadString, {}, options).then(({
  270. response,
  271. status
  272. }) => {
  273. if (options.returnStatus) {
  274. return {
  275. ...response,
  276. _status: status
  277. };
  278. } else {
  279. return response;
  280. }
  281. });
  282. }).catch(RESTController.handleError);
  283. },
  284. handleError(response) {
  285. // Transform the error into an instance of ParseError by trying to parse
  286. // the error string as JSON
  287. let error;
  288. if (response && response.responseText) {
  289. try {
  290. const errorJSON = JSON.parse(response.responseText);
  291. error = new _ParseError.default(errorJSON.code, errorJSON.error);
  292. } catch (e) {
  293. // If we fail to parse the error text, that's okay.
  294. error = new _ParseError.default(_ParseError.default.INVALID_JSON, 'Received an error with invalid JSON from Parse: ' + response.responseText);
  295. }
  296. } else {
  297. const message = response.message ? response.message : response;
  298. error = new _ParseError.default(_ParseError.default.CONNECTION_FAILED, 'XMLHttpRequest failed: ' + JSON.stringify(message));
  299. }
  300. return Promise.reject(error);
  301. },
  302. _setXHR(xhr /*: any*/) {
  303. XHR = xhr;
  304. },
  305. _getXHR() {
  306. return XHR;
  307. }
  308. };
  309. module.exports = RESTController;