EventuallyQueue.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. "use strict";
  2. var _CoreManager = _interopRequireDefault(require("./CoreManager"));
  3. var _ParseObject = _interopRequireDefault(require("./ParseObject"));
  4. var _ParseQuery = _interopRequireDefault(require("./ParseQuery"));
  5. var _Storage = _interopRequireDefault(require("./Storage"));
  6. function _interopRequireDefault(obj) {
  7. return obj && obj.__esModule ? obj : {
  8. default: obj
  9. };
  10. }
  11. /**
  12. * https://github.com/francimedia/parse-js-local-storage
  13. *
  14. * @flow
  15. */
  16. const QUEUE_KEY = 'Parse/Eventually/Queue';
  17. let queueCache = [];
  18. let dirtyCache = true;
  19. let polling = undefined;
  20. /**
  21. * Provides utility functions to queue objects that will be
  22. * saved to the server at a later date.
  23. *
  24. * @class Parse.EventuallyQueue
  25. * @static
  26. */
  27. const EventuallyQueue = {
  28. /**
  29. * Add object to queue with save operation.
  30. *
  31. * @function save
  32. * @name Parse.EventuallyQueue.save
  33. * @param {ParseObject} object Parse.Object to be saved eventually
  34. * @param {object} [serverOptions] See {@link https://parseplatform.org/Parse-SDK-JS/api/master/Parse.Object.html#save Parse.Object.save} options.
  35. * @returns {Promise} A promise that is fulfilled if object is added to queue.
  36. * @static
  37. * @see Parse.Object#saveEventually
  38. */
  39. save(object
  40. /*: ParseObject*/
  41. , serverOptions
  42. /*: SaveOptions*/
  43. = {})
  44. /*: Promise*/
  45. {
  46. return this.enqueue('save', object, serverOptions);
  47. },
  48. /**
  49. * Add object to queue with save operation.
  50. *
  51. * @function destroy
  52. * @name Parse.EventuallyQueue.destroy
  53. * @param {ParseObject} object Parse.Object to be destroyed eventually
  54. * @param {object} [serverOptions] See {@link https://parseplatform.org/Parse-SDK-JS/api/master/Parse.Object.html#destroy Parse.Object.destroy} options
  55. * @returns {Promise} A promise that is fulfilled if object is added to queue.
  56. * @static
  57. * @see Parse.Object#destroyEventually
  58. */
  59. destroy(object
  60. /*: ParseObject*/
  61. , serverOptions
  62. /*: RequestOptions*/
  63. = {})
  64. /*: Promise*/
  65. {
  66. return this.enqueue('destroy', object, serverOptions);
  67. },
  68. /**
  69. * Generate unique identifier to avoid duplicates and maintain previous state.
  70. *
  71. * @param {string} action save / destroy
  72. * @param {object} object Parse.Object to be queued
  73. * @returns {string}
  74. * @static
  75. * @ignore
  76. */
  77. generateQueueId(action
  78. /*: string*/
  79. , object
  80. /*: ParseObject*/
  81. )
  82. /*: string*/
  83. {
  84. object._getId();
  85. const {
  86. className,
  87. id,
  88. _localId
  89. } = object;
  90. const uniqueId = object.get('hash') || _localId;
  91. return [action, className, id, uniqueId].join('_');
  92. },
  93. /**
  94. * Build queue object and add to queue.
  95. *
  96. * @param {string} action save / destroy
  97. * @param {object} object Parse.Object to be queued
  98. * @param {object} [serverOptions]
  99. * @returns {Promise} A promise that is fulfilled if object is added to queue.
  100. * @static
  101. * @ignore
  102. */
  103. async enqueue(action
  104. /*: string*/
  105. , object
  106. /*: ParseObject*/
  107. , serverOptions
  108. /*: SaveOptions | RequestOptions*/
  109. )
  110. /*: Promise*/
  111. {
  112. const queueData = await this.getQueue();
  113. const queueId = this.generateQueueId(action, object);
  114. let index = this.queueItemExists(queueData, queueId);
  115. if (index > -1) {
  116. // Add cached values to new object if they don't exist
  117. for (const prop in queueData[index].object) {
  118. if (typeof object.get(prop) === 'undefined') {
  119. object.set(prop, queueData[index].object[prop]);
  120. }
  121. }
  122. } else {
  123. index = queueData.length;
  124. }
  125. queueData[index] = {
  126. queueId,
  127. action,
  128. object: object.toJSON(),
  129. serverOptions,
  130. id: object.id,
  131. className: object.className,
  132. hash: object.get('hash'),
  133. createdAt: new Date()
  134. };
  135. return this.setQueue(queueData);
  136. },
  137. store(data) {
  138. return _Storage.default.setItemAsync(QUEUE_KEY, JSON.stringify(data));
  139. },
  140. load() {
  141. return _Storage.default.getItemAsync(QUEUE_KEY);
  142. },
  143. /**
  144. * Sets the in-memory queue from local storage and returns.
  145. *
  146. * @function getQueue
  147. * @name Parse.EventuallyQueue.getQueue
  148. * @returns {Promise<Array>}
  149. * @static
  150. */
  151. async getQueue()
  152. /*: Promise<Array>*/
  153. {
  154. if (dirtyCache) {
  155. queueCache = JSON.parse((await this.load()) || '[]');
  156. dirtyCache = false;
  157. }
  158. return queueCache;
  159. },
  160. /**
  161. * Saves the queue to local storage
  162. *
  163. * @param {Queue} queue Queue containing Parse.Object data.
  164. * @returns {Promise} A promise that is fulfilled when queue is stored.
  165. * @static
  166. * @ignore
  167. */
  168. setQueue(queue
  169. /*: Queue*/
  170. )
  171. /*: Promise<void>*/
  172. {
  173. queueCache = queue;
  174. return this.store(queueCache);
  175. },
  176. /**
  177. * Removes Parse.Object data from queue.
  178. *
  179. * @param {string} queueId Unique identifier for Parse.Object data.
  180. * @returns {Promise} A promise that is fulfilled when queue is stored.
  181. * @static
  182. * @ignore
  183. */
  184. async remove(queueId
  185. /*: string*/
  186. )
  187. /*: Promise<void>*/
  188. {
  189. const queueData = await this.getQueue();
  190. const index = this.queueItemExists(queueData, queueId);
  191. if (index > -1) {
  192. queueData.splice(index, 1);
  193. await this.setQueue(queueData);
  194. }
  195. },
  196. /**
  197. * Removes all objects from queue.
  198. *
  199. * @function clear
  200. * @name Parse.EventuallyQueue.clear
  201. * @returns {Promise} A promise that is fulfilled when queue is cleared.
  202. * @static
  203. */
  204. clear()
  205. /*: Promise*/
  206. {
  207. queueCache = [];
  208. return this.store([]);
  209. },
  210. /**
  211. * Return the index of a queueId in the queue. Returns -1 if not found.
  212. *
  213. * @param {Queue} queue Queue containing Parse.Object data.
  214. * @param {string} queueId Unique identifier for Parse.Object data.
  215. * @returns {number}
  216. * @static
  217. * @ignore
  218. */
  219. queueItemExists(queue
  220. /*: Queue*/
  221. , queueId
  222. /*: string*/
  223. )
  224. /*: number*/
  225. {
  226. return queue.findIndex(data => data.queueId === queueId);
  227. },
  228. /**
  229. * Return the number of objects in the queue.
  230. *
  231. * @function length
  232. * @name Parse.EventuallyQueue.length
  233. * @returns {number}
  234. * @static
  235. */
  236. async length()
  237. /*: number*/
  238. {
  239. const queueData = await this.getQueue();
  240. return queueData.length;
  241. },
  242. /**
  243. * Sends the queue to the server.
  244. *
  245. * @function sendQueue
  246. * @name Parse.EventuallyQueue.sendQueue
  247. * @returns {Promise<boolean>} Returns true if queue was sent successfully.
  248. * @static
  249. */
  250. async sendQueue()
  251. /*: Promise<boolean>*/
  252. {
  253. const queue = await this.getQueue();
  254. const queueData = [...queue];
  255. if (queueData.length === 0) {
  256. return false;
  257. }
  258. for (let i = 0; i < queueData.length; i += 1) {
  259. const queueObject = queueData[i];
  260. const {
  261. id,
  262. hash,
  263. className
  264. } = queueObject;
  265. const ObjectType = _ParseObject.default.extend(className);
  266. if (id) {
  267. await this.process.byId(ObjectType, queueObject);
  268. } else if (hash) {
  269. await this.process.byHash(ObjectType, queueObject);
  270. } else {
  271. await this.process.create(ObjectType, queueObject);
  272. }
  273. }
  274. return true;
  275. },
  276. /**
  277. * Build queue object and add to queue.
  278. *
  279. * @param {ParseObject} object Parse.Object to be processed
  280. * @param {QueueObject} queueObject Parse.Object data from the queue
  281. * @returns {Promise} A promise that is fulfilled when operation is performed.
  282. * @static
  283. * @ignore
  284. */
  285. async sendQueueCallback(object
  286. /*: ParseObject*/
  287. , queueObject
  288. /*: QueueObject*/
  289. )
  290. /*: Promise<void>*/
  291. {
  292. if (!object) {
  293. return this.remove(queueObject.queueId);
  294. }
  295. switch (queueObject.action) {
  296. case 'save':
  297. // Queued update was overwritten by other request. Do not save
  298. if (typeof object.updatedAt !== 'undefined' && object.updatedAt > new Date(queueObject.object.createdAt)) {
  299. return this.remove(queueObject.queueId);
  300. }
  301. try {
  302. await object.save(queueObject.object, queueObject.serverOptions);
  303. await this.remove(queueObject.queueId);
  304. } catch (e) {
  305. if (e.message !== 'XMLHttpRequest failed: "Unable to connect to the Parse API"') {
  306. await this.remove(queueObject.queueId);
  307. }
  308. }
  309. break;
  310. case 'destroy':
  311. try {
  312. await object.destroy(queueObject.serverOptions);
  313. await this.remove(queueObject.queueId);
  314. } catch (e) {
  315. if (e.message !== 'XMLHttpRequest failed: "Unable to connect to the Parse API"') {
  316. await this.remove(queueObject.queueId);
  317. }
  318. }
  319. break;
  320. }
  321. },
  322. /**
  323. * Start polling server for network connection.
  324. * Will send queue if connection is established.
  325. *
  326. * @function poll
  327. * @name Parse.EventuallyQueue.poll
  328. * @param [ms] Milliseconds to ping the server. Default 2000ms
  329. * @static
  330. */
  331. poll(ms
  332. /*: number*/
  333. = 2000) {
  334. if (polling) {
  335. return;
  336. }
  337. polling = setInterval(() => {
  338. const RESTController = _CoreManager.default.getRESTController();
  339. RESTController.request('GET', 'health').then(({
  340. status
  341. }) => {
  342. if (status === 'ok') {
  343. this.stopPoll();
  344. return this.sendQueue();
  345. }
  346. }).catch(e => e);
  347. }, ms);
  348. },
  349. /**
  350. * Turns off polling.
  351. *
  352. * @function stopPoll
  353. * @name Parse.EventuallyQueue.stopPoll
  354. * @static
  355. */
  356. stopPoll() {
  357. clearInterval(polling);
  358. polling = undefined;
  359. },
  360. /**
  361. * Return true if pinging the server.
  362. *
  363. * @function isPolling
  364. * @name Parse.EventuallyQueue.isPolling
  365. * @returns {boolean}
  366. * @static
  367. */
  368. isPolling()
  369. /*: boolean*/
  370. {
  371. return !!polling;
  372. },
  373. _setPolling(flag
  374. /*: boolean*/
  375. ) {
  376. polling = flag;
  377. },
  378. process: {
  379. create(ObjectType, queueObject) {
  380. const object = new ObjectType();
  381. return EventuallyQueue.sendQueueCallback(object, queueObject);
  382. },
  383. async byId(ObjectType, queueObject) {
  384. const {
  385. sessionToken
  386. } = queueObject.serverOptions;
  387. const query = new _ParseQuery.default(ObjectType);
  388. query.equalTo('objectId', queueObject.id);
  389. const results = await query.find({
  390. sessionToken
  391. });
  392. return EventuallyQueue.sendQueueCallback(results[0], queueObject);
  393. },
  394. async byHash(ObjectType, queueObject) {
  395. const {
  396. sessionToken
  397. } = queueObject.serverOptions;
  398. const query = new _ParseQuery.default(ObjectType);
  399. query.equalTo('hash', queueObject.hash);
  400. const results = await query.find({
  401. sessionToken
  402. });
  403. if (results.length > 0) {
  404. return EventuallyQueue.sendQueueCallback(results[0], queueObject);
  405. }
  406. return EventuallyQueue.process.create(ObjectType, queueObject);
  407. }
  408. }
  409. };
  410. module.exports = EventuallyQueue;