Repository: parse-community/Parse-SDK-JS
Pull request #977 (2019-11-04 20:08:56)

Add installationId to LiveQuery
Adds reconnect for error event from server (already reconnects with web socket error).
Adds backwards compatibility for additionalProperties that do not exist in the server.

This PR will require a server update.

Use Case: Determine which client the live query request was sent from.

Will open a PR separately for the following.
```
subscription.on('create', (object, options) => {
  options.installationId, options.clientId, etc
});
```



codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977?src=pr&el=h1) Report
> Merging [#977](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/bcd86460205ff4b039295f9d7527b78af5915764?src=pr&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977/graphs/tree.svg?width=650&token=8nCENM0QHj&height=150&src=pr)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #977      +/-   ##
==========================================
+ Coverage   91.84%   91.85%   +0.01%     
==========================================
  Files          54       54              
  Lines        5075     5084       +9     
  Branches     1142     1145       +3     
==========================================
+ Hits         4661     4670       +9     
  Misses        414      414
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/LiveQueryClient.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977/diff?src=pr&el=tree#diff-c3JjL0xpdmVRdWVyeUNsaWVudC5qcw==) | `87.56% <100%> (+0.56%)` | :arrow_up: |
| [src/ParseLiveQuery.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977/diff?src=pr&el=tree#diff-c3JjL1BhcnNlTGl2ZVF1ZXJ5Lmpz) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977?src=pr&el=footer). Last update [bcd8646...23dedd1](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/977?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/LiveQueryClient.js b/src/LiveQueryClient.js
index d96a9c0..252661d 100644
--- a/src/LiveQueryClient.js
+++ b/src/LiveQueryClient.js
@@ -122,6 +122,8 @@ class LiveQueryClient extends EventEmitter {
   javascriptKey: ?string;
   masterKey: ?string;
   sessionToken: ?string;
+  installationId: ?string;
+  additionalProperties: boolean;
   connectPromise: Promise;
   subscriptions: Map;
   socket: any;
@@ -134,13 +136,15 @@ class LiveQueryClient extends EventEmitter {
    * @param {string} options.javascriptKey (optional)
    * @param {string} options.masterKey (optional) Your Parse Master Key. (Node.js only!)
    * @param {string} options.sessionToken (optional)
+   * @param {string} options.installationId (optional)
    */
   constructor({
     applicationId,
     serverURL,
     javascriptKey,
     masterKey,
-    sessionToken
+    sessionToken,
+    installationId,
   }) {
     super();
 
@@ -157,6 +161,8 @@ class LiveQueryClient extends EventEmitter {
     this.javascriptKey = javascriptKey;
     this.masterKey = masterKey;
     this.sessionToken = sessionToken;
+    this.installationId = installationId;
+    this.additionalProperties = true;
     this.connectPromise = resolvingPromise();
     this.subscriptions = new Map();
     this.state = CLIENT_STATE.INITIALIZED;
@@ -334,6 +340,9 @@ class LiveQueryClient extends EventEmitter {
       masterKey: this.masterKey,
       sessionToken: this.sessionToken
     };
+    if (this.additionalProperties) {
+      connectRequest.installationId = this.installationId;
+    }
     this.socket.send(JSON.stringify(connectRequest));
   }
 
@@ -373,6 +382,12 @@ class LiveQueryClient extends EventEmitter {
       } else {
         this.emit(CLIENT_EMMITER_TYPES.ERROR, data.error);
       }
+      if (data.error === 'Additional properties not allowed') {
+        this.additionalProperties = false;
+      }
+      if (data.reconnect) {
+        this._handleReconnect();
+      }
       break;
     case OP_EVENTS.UNSUBSCRIBED:
       // We have already deleted subscription in unsubscribe(), do nothing here
diff --git a/src/ParseLiveQuery.js b/src/ParseLiveQuery.js
index 5566acf..0f1fcb8 100644
--- a/src/ParseLiveQuery.js
+++ b/src/ParseLiveQuery.js
@@ -86,7 +86,10 @@ const DefaultLiveQueryController = {
     if (defaultLiveQueryClient) {
       return defaultLiveQueryClient;
     }
-    const currentUser = await CoreManager.getUserController().currentUserAsync();
+    const [currentUser, installationId] = await Promise.all([
+      CoreManager.getUserController().currentUserAsync(),
+      CoreManager.getInstallationController().currentInstallationId()
+    ]);
     const sessionToken = currentUser ? currentUser.getSessionToken() : undefined;
 
     let liveQueryServerURL = CoreManager.get('LIVEQUERY_SERVER_URL');
@@ -115,6 +118,7 @@ const DefaultLiveQueryController = {
       javascriptKey,
       masterKey,
       sessionToken,
+      installationId,
     });
     defaultLiveQueryClient.on('error', (error) => {
       LiveQuery.emit('error', error);


Test patch:
diff --git a/src/__tests__/LiveQueryClient-test.js b/src/__tests__/LiveQueryClient-test.js
index d321319..46c9f65 100644
--- a/src/__tests__/LiveQueryClient-test.js
+++ b/src/__tests__/LiveQueryClient-test.js
@@ -490,6 +490,38 @@ describe('LiveQueryClient', () => {
     expect(isChecked).toBe(true);
   });
 
+  it('can handle WebSocket reconnect on error event', () => {
+    const liveQueryClient = new LiveQueryClient({
+      applicationId: 'applicationId',
+      serverURL: 'ws://test',
+      javascriptKey: 'javascriptKey',
+      masterKey: 'masterKey',
+      sessionToken: 'sessionToken'
+    });
+    expect(liveQueryClient.additionalProperties).toBe(true);
+    const data = {
+      op: 'error',
+      code: 1,
+      reconnect: true,
+      error: 'Additional properties not allowed',
+    };
+    const event = {
+      data: JSON.stringify(data)
+    }
+    let isChecked = false;
+    liveQueryClient.on('error', function(error) {
+      isChecked = true;
+      expect(error).toEqual(data.error);
+    });
+    const spy = jest.spyOn(liveQueryClient, '_handleReconnect');
+    liveQueryClient._handleWebSocketMessage(event);
+
+    expect(isChecked).toBe(true);
+    expect(liveQueryClient._handleReconnect).toHaveBeenCalledTimes(1);
+    expect(liveQueryClient.additionalProperties).toBe(false);
+    spy.mockRestore();
+  });
+
   it('can subscribe', async () => {
     const liveQueryClient = new LiveQueryClient({
       applicationId: 'applicationId',
diff --git a/src/__tests__/ParseLiveQuery-test.js b/src/__tests__/ParseLiveQuery-test.js
index be9c894..366ced8 100644
--- a/src/__tests__/ParseLiveQuery-test.js
+++ b/src/__tests__/ParseLiveQuery-test.js
@@ -9,6 +9,7 @@
 
 jest.dontMock('../ParseLiveQuery');
 jest.dontMock('../CoreManager');
+jest.dontMock('../InstallationController');
 jest.dontMock('../LiveQueryClient');
 jest.dontMock('../LiveQuerySubscription');
 jest.dontMock('../ParseObject');
@@ -30,6 +31,11 @@ describe('ParseLiveQuery', () => {
   beforeEach(() => {
     const controller = CoreManager.getLiveQueryController();
     controller._clearCachedDefaultClient();
+    CoreManager.set('InstallationController', {
+      currentInstallationId() {
+        return Promise.resolve('1234');
+      }
+    });
   });
 
   it('fails with an invalid livequery server url', (done) => {
@@ -63,6 +69,8 @@ describe('ParseLiveQuery', () => {
       expect(client.applicationId).toBe('appid');
       expect(client.javascriptKey).toBe('jskey');
       expect(client.sessionToken).toBe(undefined);
+      expect(client.installationId).toBe('1234');
+      expect(client.additionalProperties).toBe(true);
       done();
     });
   });