From 4af34831628cfbc082dbbbc6e17848e34a326046 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 3 Feb 2023 16:59:11 -0600 Subject: [PATCH 1/3] feat: Ensure live query unsubscribes --- src/LiveQueryClient.js | 22 +++++++++----- src/LiveQuerySubscription.js | 3 +- src/__tests__/LiveQueryClient-test.js | 44 ++++++++------------------- src/__tests__/ParseLiveQuery-test.js | 4 +-- 4 files changed, 31 insertions(+), 42 deletions(-) diff --git a/src/LiveQueryClient.js b/src/LiveQueryClient.js index 7e8f4d8c4..87a052ca5 100644 --- a/src/LiveQueryClient.js +++ b/src/LiveQueryClient.js @@ -223,20 +223,21 @@ class LiveQueryClient extends EventEmitter { /** * After calling unsubscribe you'll stop receiving events from the subscription object. * - * @param {object} subscription - subscription you would like to unsubscribe from. + * @param {Object} subscription - subscription you would like to unsubscribe from. + * @returns {Promise | undefined} */ - unsubscribe(subscription: Object) { + unsubscribe(subscription: Object): ?Promise { if (!subscription) { return; } - - this.subscriptions.delete(subscription.id); const unsubscribeRequest = { op: OP_TYPES.UNSUBSCRIBE, requestId: subscription.id, }; - this.connectPromise.then(() => { - this.socket.send(JSON.stringify(unsubscribeRequest)); + return this.connectPromise.then(() => { + return this.socket.send(JSON.stringify(unsubscribeRequest)); + }).then(() => { + return subscription.unsubscribePromise; }); } @@ -400,9 +401,14 @@ class LiveQueryClient extends EventEmitter { } break; } - case OP_EVENTS.UNSUBSCRIBED: - // We have already deleted subscription in unsubscribe(), do nothing here + case OP_EVENTS.UNSUBSCRIBED: { + if (subscription) { + this.subscriptions.delete(data.requestId); + subscription.subscribed = false; + subscription.unsubscribePromise.resolve(); + } break; + } default: { // create, update, enter, leave, delete cases if (!subscription) { diff --git a/src/LiveQuerySubscription.js b/src/LiveQuerySubscription.js index 1c6c638ee..503a07175 100644 --- a/src/LiveQuerySubscription.js +++ b/src/LiveQuerySubscription.js @@ -99,6 +99,7 @@ class Subscription extends EventEmitter { this.query = query; this.sessionToken = sessionToken; this.subscribePromise = resolvingPromise(); + this.unsubscribePromise = resolvingPromise(); this.subscribed = false; // adding listener so process does not crash @@ -115,8 +116,8 @@ class Subscription extends EventEmitter { return CoreManager.getLiveQueryController() .getDefaultLiveQueryClient() .then(liveQueryClient => { - liveQueryClient.unsubscribe(this); this.emit('close'); + return liveQueryClient.unsubscribe(this); }); } } diff --git a/src/__tests__/LiveQueryClient-test.js b/src/__tests__/LiveQueryClient-test.js index ba98ea561..820049b32 100644 --- a/src/__tests__/LiveQueryClient-test.js +++ b/src/__tests__/LiveQueryClient-test.js @@ -72,34 +72,6 @@ describe('LiveQueryClient', () => { liveQueryClient.open(); }); - it('can unsubscribe', async () => { - const liveQueryClient = new LiveQueryClient({ - applicationId: 'applicationId', - serverURL: 'ws://test', - javascriptKey: 'javascriptKey', - masterKey: 'masterKey', - sessionToken: 'sessionToken', - }); - liveQueryClient.socket = { - send: jest.fn(), - }; - const subscription = { - id: 1, - }; - liveQueryClient.subscriptions.set(1, subscription); - - liveQueryClient.unsubscribe(subscription); - liveQueryClient.connectPromise.resolve(); - expect(liveQueryClient.subscriptions.size).toBe(0); - await liveQueryClient.connectPromise; - const messageStr = liveQueryClient.socket.send.mock.calls[0][0]; - const message = JSON.parse(messageStr); - expect(message).toEqual({ - op: 'unsubscribe', - requestId: 1, - }); - }); - it('can handle open / close states', () => { const liveQueryClient = new LiveQueryClient({ applicationId: 'applicationId', @@ -274,7 +246,7 @@ describe('LiveQueryClient', () => { expect(isChecked).toBe(true); }); - it('can handle WebSocket unsubscribed response message', () => { + fit('can handle WebSocket unsubscribed response message', () => { const liveQueryClient = new LiveQueryClient({ applicationId: 'applicationId', serverURL: 'ws://test', @@ -284,6 +256,7 @@ describe('LiveQueryClient', () => { }); const subscription = new events.EventEmitter(); subscription.subscribePromise = resolvingPromise(); + subscription.unsubscribePromise = resolvingPromise(); liveQueryClient.subscriptions.set(1, subscription); const data = { @@ -295,7 +268,7 @@ describe('LiveQueryClient', () => { data: JSON.stringify(data), }; liveQueryClient._handleWebSocketMessage(event); - expect(liveQueryClient.subscriptions.size).toBe(1); + expect(liveQueryClient.subscriptions.size).toBe(0); }); it('can handle WebSocket error response message', async () => { @@ -871,12 +844,13 @@ describe('LiveQueryClient', () => { }; const subscription = { id: 1, + unsubscribePromise: resolvingPromise(), }; liveQueryClient.subscriptions.set(1, subscription); liveQueryClient.unsubscribe(subscription); liveQueryClient.connectPromise.resolve(); - expect(liveQueryClient.subscriptions.size).toBe(0); + expect(liveQueryClient.subscriptions.size).toBe(1); await liveQueryClient.connectPromise; const messageStr = liveQueryClient.socket.send.mock.calls[0][0]; const message = JSON.parse(messageStr); @@ -884,6 +858,14 @@ describe('LiveQueryClient', () => { op: 'unsubscribe', requestId: 1, }); + const event = { + data: JSON.stringify({ + op: 'unsubscribed', + requestId: 1, + }), + }; + liveQueryClient._handleWebSocketMessage(event); + expect(liveQueryClient.subscriptions.size).toBe(0); }); it('can unsubscribe without subscription', async () => { diff --git a/src/__tests__/ParseLiveQuery-test.js b/src/__tests__/ParseLiveQuery-test.js index b6b55f070..63178f028 100644 --- a/src/__tests__/ParseLiveQuery-test.js +++ b/src/__tests__/ParseLiveQuery-test.js @@ -227,7 +227,7 @@ describe('ParseLiveQuery', () => { }); }); - it('should not throw on usubscribe', done => { + it('should not throw on usubscribe', () => { CoreManager.set('UserController', { currentUserAsync() { return Promise.resolve({ @@ -240,7 +240,7 @@ describe('ParseLiveQuery', () => { const query = new ParseQuery('ObjectType'); query.equalTo('test', 'value'); const subscription = new LiveQuerySubscription('0', query, 'token'); - subscription.unsubscribe().then(done).catch(done.fail); + subscription.unsubscribe(); }); it('can handle LiveQuery open event', async () => { From e519851d9535843f50afc90a21c3a8e674fe2df0 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 3 Feb 2023 17:01:28 -0600 Subject: [PATCH 2/3] Update LiveQueryClient-test.js --- src/__tests__/LiveQueryClient-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/LiveQueryClient-test.js b/src/__tests__/LiveQueryClient-test.js index 820049b32..a0dbd765c 100644 --- a/src/__tests__/LiveQueryClient-test.js +++ b/src/__tests__/LiveQueryClient-test.js @@ -246,7 +246,7 @@ describe('LiveQueryClient', () => { expect(isChecked).toBe(true); }); - fit('can handle WebSocket unsubscribed response message', () => { + it('can handle WebSocket unsubscribed response message', () => { const liveQueryClient = new LiveQueryClient({ applicationId: 'applicationId', serverURL: 'ws://test', From 32b83486c1384b4ac7592c43040ab1b689cab7f7 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 3 Feb 2023 18:25:55 -0600 Subject: [PATCH 3/3] fix jsdocs warning --- src/LiveQueryClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LiveQueryClient.js b/src/LiveQueryClient.js index 87a052ca5..17b4ab40c 100644 --- a/src/LiveQueryClient.js +++ b/src/LiveQueryClient.js @@ -223,7 +223,7 @@ class LiveQueryClient extends EventEmitter { /** * After calling unsubscribe you'll stop receiving events from the subscription object. * - * @param {Object} subscription - subscription you would like to unsubscribe from. + * @param {object} subscription - subscription you would like to unsubscribe from. * @returns {Promise | undefined} */ unsubscribe(subscription: Object): ?Promise {