EventuallyQueue.js 11 KB

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