LiveQueryClient.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. "use strict";
  2. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
  3. Object.defineProperty(exports, "__esModule", {
  4. value: true
  5. });
  6. exports.default = void 0;
  7. var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
  8. var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
  9. var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
  10. var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
  11. var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
  12. var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
  13. var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
  14. var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
  15. var _CoreManager = _interopRequireDefault(require("./CoreManager"));
  16. var _EventEmitter2 = _interopRequireDefault(require("./EventEmitter"));
  17. var _ParseObject = _interopRequireDefault(require("./ParseObject"));
  18. var _LiveQuerySubscription = _interopRequireDefault(require("./LiveQuerySubscription"));
  19. var _promiseUtils = require("./promiseUtils");
  20. /**
  21. * Copyright (c) 2015-present, Parse, LLC.
  22. * All rights reserved.
  23. *
  24. * This source code is licensed under the BSD-style license found in the
  25. * LICENSE file in the root directory of this source tree. An additional grant
  26. * of patent rights can be found in the PATENTS file in the same directory.
  27. *
  28. */
  29. /* global WebSocket */
  30. // The LiveQuery client inner state
  31. var CLIENT_STATE = {
  32. INITIALIZED: 'initialized',
  33. CONNECTING: 'connecting',
  34. CONNECTED: 'connected',
  35. CLOSED: 'closed',
  36. RECONNECTING: 'reconnecting',
  37. DISCONNECTED: 'disconnected'
  38. }; // The event type the LiveQuery client should sent to server
  39. var OP_TYPES = {
  40. CONNECT: 'connect',
  41. SUBSCRIBE: 'subscribe',
  42. UNSUBSCRIBE: 'unsubscribe',
  43. ERROR: 'error'
  44. }; // The event we get back from LiveQuery server
  45. var OP_EVENTS = {
  46. CONNECTED: 'connected',
  47. SUBSCRIBED: 'subscribed',
  48. UNSUBSCRIBED: 'unsubscribed',
  49. ERROR: 'error',
  50. CREATE: 'create',
  51. UPDATE: 'update',
  52. ENTER: 'enter',
  53. LEAVE: 'leave',
  54. DELETE: 'delete'
  55. }; // The event the LiveQuery client should emit
  56. var CLIENT_EMMITER_TYPES = {
  57. CLOSE: 'close',
  58. ERROR: 'error',
  59. OPEN: 'open'
  60. }; // The event the LiveQuery subscription should emit
  61. var SUBSCRIPTION_EMMITER_TYPES = {
  62. OPEN: 'open',
  63. CLOSE: 'close',
  64. ERROR: 'error',
  65. CREATE: 'create',
  66. UPDATE: 'update',
  67. ENTER: 'enter',
  68. LEAVE: 'leave',
  69. DELETE: 'delete'
  70. };
  71. var generateInterval = function (k) {
  72. return Math.random() * Math.min(30, Math.pow(2, k) - 1) * 1000;
  73. };
  74. /**
  75. * Creates a new LiveQueryClient.
  76. * Extends events.EventEmitter
  77. * <a href="https://nodejs.org/api/events.html#events_class_eventemitter">cloud functions</a>.
  78. *
  79. * A wrapper of a standard WebSocket client. We add several useful methods to
  80. * help you connect/disconnect to LiveQueryServer, subscribe/unsubscribe a ParseQuery easily.
  81. *
  82. * javascriptKey and masterKey are used for verifying the LiveQueryClient when it tries
  83. * to connect to the LiveQuery server
  84. *
  85. * We expose three events to help you monitor the status of the LiveQueryClient.
  86. *
  87. * <pre>
  88. * let Parse = require('parse/node');
  89. * let LiveQueryClient = Parse.LiveQueryClient;
  90. * let client = new LiveQueryClient({
  91. * applicationId: '',
  92. * serverURL: '',
  93. * javascriptKey: '',
  94. * masterKey: ''
  95. * });
  96. * </pre>
  97. *
  98. * Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event.
  99. * <pre>
  100. * client.on('open', () => {
  101. *
  102. * });</pre>
  103. *
  104. * Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event.
  105. * <pre>
  106. * client.on('close', () => {
  107. *
  108. * });</pre>
  109. *
  110. * Error - When some network error or LiveQuery server error happens, you'll get this event.
  111. * <pre>
  112. * client.on('error', (error) => {
  113. *
  114. * });</pre>
  115. * @alias Parse.LiveQueryClient
  116. */
  117. var LiveQueryClient =
  118. /*#__PURE__*/
  119. function (_EventEmitter) {
  120. (0, _inherits2.default)(LiveQueryClient, _EventEmitter);
  121. /**
  122. * @param {Object} options
  123. * @param {string} options.applicationId - applicationId of your Parse app
  124. * @param {string} options.serverURL - <b>the URL of your LiveQuery server</b>
  125. * @param {string} options.javascriptKey (optional)
  126. * @param {string} options.masterKey (optional) Your Parse Master Key. (Node.js only!)
  127. * @param {string} options.sessionToken (optional)
  128. */
  129. function LiveQueryClient(_ref) {
  130. var _this;
  131. var applicationId = _ref.applicationId,
  132. serverURL = _ref.serverURL,
  133. javascriptKey = _ref.javascriptKey,
  134. masterKey = _ref.masterKey,
  135. sessionToken = _ref.sessionToken;
  136. (0, _classCallCheck2.default)(this, LiveQueryClient);
  137. _this = (0, _possibleConstructorReturn2.default)(this, (0, _getPrototypeOf2.default)(LiveQueryClient).call(this));
  138. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "attempts", void 0);
  139. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "id", void 0);
  140. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "requestId", void 0);
  141. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "applicationId", void 0);
  142. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "serverURL", void 0);
  143. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "javascriptKey", void 0);
  144. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "masterKey", void 0);
  145. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "sessionToken", void 0);
  146. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "connectPromise", void 0);
  147. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "subscriptions", void 0);
  148. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "socket", void 0);
  149. (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "state", void 0);
  150. if (!serverURL || serverURL.indexOf('ws') !== 0) {
  151. throw new Error('You need to set a proper Parse LiveQuery server url before using LiveQueryClient');
  152. }
  153. _this.reconnectHandle = null;
  154. _this.attempts = 1;
  155. _this.id = 0;
  156. _this.requestId = 1;
  157. _this.serverURL = serverURL;
  158. _this.applicationId = applicationId;
  159. _this.javascriptKey = javascriptKey;
  160. _this.masterKey = masterKey;
  161. _this.sessionToken = sessionToken;
  162. _this.connectPromise = (0, _promiseUtils.resolvingPromise)();
  163. _this.subscriptions = new Map();
  164. _this.state = CLIENT_STATE.INITIALIZED;
  165. return _this;
  166. }
  167. (0, _createClass2.default)(LiveQueryClient, [{
  168. key: "shouldOpen",
  169. value: function ()
  170. /*: any*/
  171. {
  172. return this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED;
  173. }
  174. /**
  175. * Subscribes to a ParseQuery
  176. *
  177. * If you provide the sessionToken, when the LiveQuery server gets ParseObject's
  178. * updates from parse server, it'll try to check whether the sessionToken fulfills
  179. * the ParseObject's ACL. The LiveQuery server will only send updates to clients whose
  180. * sessionToken is fit for the ParseObject's ACL. You can check the LiveQuery protocol
  181. * <a href="https://github.com/parse-community/parse-server/wiki/Parse-LiveQuery-Protocol-Specification">here</a> for more details. The subscription you get is the same subscription you get
  182. * from our Standard API.
  183. *
  184. * @param {Object} query - the ParseQuery you want to subscribe to
  185. * @param {string} sessionToken (optional)
  186. * @return {Object} subscription
  187. */
  188. }, {
  189. key: "subscribe",
  190. value: function (query
  191. /*: Object*/
  192. , sessionToken
  193. /*: ?string*/
  194. )
  195. /*: Object*/
  196. {
  197. var _this2 = this;
  198. if (!query) {
  199. return;
  200. }
  201. var className = query.className;
  202. var queryJSON = query.toJSON();
  203. var where = queryJSON.where;
  204. var fields = queryJSON.keys ? queryJSON.keys.split(',') : undefined;
  205. var subscribeRequest = {
  206. op: OP_TYPES.SUBSCRIBE,
  207. requestId: this.requestId,
  208. query: {
  209. className: className,
  210. where: where,
  211. fields: fields
  212. }
  213. };
  214. if (sessionToken) {
  215. subscribeRequest.sessionToken = sessionToken;
  216. }
  217. var subscription = new _LiveQuerySubscription.default(this.requestId, query, sessionToken);
  218. this.subscriptions.set(this.requestId, subscription);
  219. this.requestId += 1;
  220. this.connectPromise.then(function () {
  221. _this2.socket.send(JSON.stringify(subscribeRequest));
  222. });
  223. return subscription;
  224. }
  225. /**
  226. * After calling unsubscribe you'll stop receiving events from the subscription object.
  227. *
  228. * @param {Object} subscription - subscription you would like to unsubscribe from.
  229. */
  230. }, {
  231. key: "unsubscribe",
  232. value: function (subscription
  233. /*: Object*/
  234. ) {
  235. var _this3 = this;
  236. if (!subscription) {
  237. return;
  238. }
  239. this.subscriptions.delete(subscription.id);
  240. var unsubscribeRequest = {
  241. op: OP_TYPES.UNSUBSCRIBE,
  242. requestId: subscription.id
  243. };
  244. this.connectPromise.then(function () {
  245. _this3.socket.send(JSON.stringify(unsubscribeRequest));
  246. });
  247. }
  248. /**
  249. * After open is called, the LiveQueryClient will try to send a connect request
  250. * to the LiveQuery server.
  251. *
  252. */
  253. }, {
  254. key: "open",
  255. value: function () {
  256. var _this4 = this;
  257. var WebSocketImplementation = _CoreManager.default.getWebSocketController();
  258. if (!WebSocketImplementation) {
  259. this.emit(CLIENT_EMMITER_TYPES.ERROR, 'Can not find WebSocket implementation');
  260. return;
  261. }
  262. if (this.state !== CLIENT_STATE.RECONNECTING) {
  263. this.state = CLIENT_STATE.CONNECTING;
  264. }
  265. this.socket = new WebSocketImplementation(this.serverURL); // Bind WebSocket callbacks
  266. this.socket.onopen = function () {
  267. _this4._handleWebSocketOpen();
  268. };
  269. this.socket.onmessage = function (event) {
  270. _this4._handleWebSocketMessage(event);
  271. };
  272. this.socket.onclose = function () {
  273. _this4._handleWebSocketClose();
  274. };
  275. this.socket.onerror = function (error) {
  276. _this4._handleWebSocketError(error);
  277. };
  278. }
  279. }, {
  280. key: "resubscribe",
  281. value: function () {
  282. var _this5 = this;
  283. this.subscriptions.forEach(function (subscription, requestId) {
  284. var query = subscription.query;
  285. var queryJSON = query.toJSON();
  286. var where = queryJSON.where;
  287. var fields = queryJSON.keys ? queryJSON.keys.split(',') : undefined;
  288. var className = query.className;
  289. var sessionToken = subscription.sessionToken;
  290. var subscribeRequest = {
  291. op: OP_TYPES.SUBSCRIBE,
  292. requestId: requestId,
  293. query: {
  294. className: className,
  295. where: where,
  296. fields: fields
  297. }
  298. };
  299. if (sessionToken) {
  300. subscribeRequest.sessionToken = sessionToken;
  301. }
  302. _this5.connectPromise.then(function () {
  303. _this5.socket.send(JSON.stringify(subscribeRequest));
  304. });
  305. });
  306. }
  307. /**
  308. * This method will close the WebSocket connection to this LiveQueryClient,
  309. * cancel the auto reconnect and unsubscribe all subscriptions based on it.
  310. *
  311. */
  312. }, {
  313. key: "close",
  314. value: function () {
  315. if (this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED) {
  316. return;
  317. }
  318. this.state = CLIENT_STATE.DISCONNECTED;
  319. this.socket.close(); // Notify each subscription about the close
  320. var _iteratorNormalCompletion = true;
  321. var _didIteratorError = false;
  322. var _iteratorError = undefined;
  323. try {
  324. for (var _iterator = this.subscriptions.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
  325. var subscription = _step.value;
  326. subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
  327. }
  328. } catch (err) {
  329. _didIteratorError = true;
  330. _iteratorError = err;
  331. } finally {
  332. try {
  333. if (!_iteratorNormalCompletion && _iterator.return != null) {
  334. _iterator.return();
  335. }
  336. } finally {
  337. if (_didIteratorError) {
  338. throw _iteratorError;
  339. }
  340. }
  341. }
  342. this._handleReset();
  343. this.emit(CLIENT_EMMITER_TYPES.CLOSE);
  344. } // ensure we start with valid state if connect is called again after close
  345. }, {
  346. key: "_handleReset",
  347. value: function () {
  348. this.attempts = 1;
  349. this.id = 0;
  350. this.requestId = 1;
  351. this.connectPromise = (0, _promiseUtils.resolvingPromise)();
  352. this.subscriptions = new Map();
  353. }
  354. }, {
  355. key: "_handleWebSocketOpen",
  356. value: function () {
  357. this.attempts = 1;
  358. var connectRequest = {
  359. op: OP_TYPES.CONNECT,
  360. applicationId: this.applicationId,
  361. javascriptKey: this.javascriptKey,
  362. masterKey: this.masterKey,
  363. sessionToken: this.sessionToken
  364. };
  365. this.socket.send(JSON.stringify(connectRequest));
  366. }
  367. }, {
  368. key: "_handleWebSocketMessage",
  369. value: function (event
  370. /*: any*/
  371. ) {
  372. var data = event.data;
  373. if (typeof data === 'string') {
  374. data = JSON.parse(data);
  375. }
  376. var subscription = null;
  377. if (data.requestId) {
  378. subscription = this.subscriptions.get(data.requestId);
  379. }
  380. switch (data.op) {
  381. case OP_EVENTS.CONNECTED:
  382. if (this.state === CLIENT_STATE.RECONNECTING) {
  383. this.resubscribe();
  384. }
  385. this.emit(CLIENT_EMMITER_TYPES.OPEN);
  386. this.id = data.clientId;
  387. this.connectPromise.resolve();
  388. this.state = CLIENT_STATE.CONNECTED;
  389. break;
  390. case OP_EVENTS.SUBSCRIBED:
  391. if (subscription) {
  392. subscription.emit(SUBSCRIPTION_EMMITER_TYPES.OPEN);
  393. }
  394. break;
  395. case OP_EVENTS.ERROR:
  396. if (data.requestId) {
  397. if (subscription) {
  398. subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR, data.error);
  399. }
  400. } else {
  401. this.emit(CLIENT_EMMITER_TYPES.ERROR, data.error);
  402. }
  403. break;
  404. case OP_EVENTS.UNSUBSCRIBED:
  405. // We have already deleted subscription in unsubscribe(), do nothing here
  406. break;
  407. default:
  408. {
  409. // create, update, enter, leave, delete cases
  410. if (!subscription) {
  411. break;
  412. }
  413. var override = false;
  414. if (data.original) {
  415. override = true;
  416. delete data.original.__type; // Check for removed fields
  417. for (var field in data.original) {
  418. if (!(field in data.object)) {
  419. data.object[field] = undefined;
  420. }
  421. }
  422. data.original = _ParseObject.default.fromJSON(data.original, false);
  423. }
  424. delete data.object.__type;
  425. var parseObject = _ParseObject.default.fromJSON(data.object, override);
  426. subscription.emit(data.op, parseObject, data.original);
  427. var localDatastore = _CoreManager.default.getLocalDatastore();
  428. if (override && localDatastore.isEnabled) {
  429. localDatastore._updateObjectIfPinned(parseObject).then(function () {});
  430. }
  431. }
  432. }
  433. }
  434. }, {
  435. key: "_handleWebSocketClose",
  436. value: function () {
  437. if (this.state === CLIENT_STATE.DISCONNECTED) {
  438. return;
  439. }
  440. this.state = CLIENT_STATE.CLOSED;
  441. this.emit(CLIENT_EMMITER_TYPES.CLOSE); // Notify each subscription about the close
  442. var _iteratorNormalCompletion2 = true;
  443. var _didIteratorError2 = false;
  444. var _iteratorError2 = undefined;
  445. try {
  446. for (var _iterator2 = this.subscriptions.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
  447. var subscription = _step2.value;
  448. subscription.emit(SUBSCRIPTION_EMMITER_TYPES.CLOSE);
  449. }
  450. } catch (err) {
  451. _didIteratorError2 = true;
  452. _iteratorError2 = err;
  453. } finally {
  454. try {
  455. if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
  456. _iterator2.return();
  457. }
  458. } finally {
  459. if (_didIteratorError2) {
  460. throw _iteratorError2;
  461. }
  462. }
  463. }
  464. this._handleReconnect();
  465. }
  466. }, {
  467. key: "_handleWebSocketError",
  468. value: function (error
  469. /*: any*/
  470. ) {
  471. this.emit(CLIENT_EMMITER_TYPES.ERROR, error);
  472. var _iteratorNormalCompletion3 = true;
  473. var _didIteratorError3 = false;
  474. var _iteratorError3 = undefined;
  475. try {
  476. for (var _iterator3 = this.subscriptions.values()[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
  477. var subscription = _step3.value;
  478. subscription.emit(SUBSCRIPTION_EMMITER_TYPES.ERROR);
  479. }
  480. } catch (err) {
  481. _didIteratorError3 = true;
  482. _iteratorError3 = err;
  483. } finally {
  484. try {
  485. if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
  486. _iterator3.return();
  487. }
  488. } finally {
  489. if (_didIteratorError3) {
  490. throw _iteratorError3;
  491. }
  492. }
  493. }
  494. this._handleReconnect();
  495. }
  496. }, {
  497. key: "_handleReconnect",
  498. value: function () {
  499. var _this6 = this; // if closed or currently reconnecting we stop attempting to reconnect
  500. if (this.state === CLIENT_STATE.DISCONNECTED) {
  501. return;
  502. }
  503. this.state = CLIENT_STATE.RECONNECTING;
  504. var time = generateInterval(this.attempts); // handle case when both close/error occur at frequent rates we ensure we do not reconnect unnecessarily.
  505. // we're unable to distinguish different between close/error when we're unable to reconnect therefore
  506. // we try to reonnect in both cases
  507. // server side ws and browser WebSocket behave differently in when close/error get triggered
  508. if (this.reconnectHandle) {
  509. clearTimeout(this.reconnectHandle);
  510. }
  511. this.reconnectHandle = setTimeout(function () {
  512. _this6.attempts++;
  513. _this6.connectPromise = (0, _promiseUtils.resolvingPromise)();
  514. _this6.open();
  515. }.bind(this), time);
  516. }
  517. }]);
  518. return LiveQueryClient;
  519. }(_EventEmitter2.default);
  520. _CoreManager.default.setWebSocketController(require('./Socket.weapp'));
  521. var _default = LiveQueryClient;
  522. exports.default = _default;