From 753ec7bb1106227edddb7e3f22fed93b7fb1e2ef Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 7 Jun 2022 21:25:03 +1000 Subject: [PATCH 1/8] fix: only fire LiveQuery if fields are updated using .fields --- spec/ParseLiveQuery.spec.js | 43 +++++++++++++++++++++++++++ src/LiveQuery/ParseLiveQueryServer.js | 24 ++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index d9b79bc588..96024860a8 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -393,6 +393,49 @@ describe('ParseLiveQuery', function () { await object.save(); }); + it('can handle live query with fields', async () => { + await reconfigureServer({ + liveQuery: { + classNames: ['Test'], + }, + startLiveQueryServer: true, + }); + const query = new Parse.Query('Test'); + query.select('yolo'); + const subscription = await query.subscribe(); + const spy = { + create(obj) { + if (!obj.get('yolo')) { + fail('create should not have been called'); + } + }, + update(object, original) { + if (object.get('yolo') === original.get('yolo')) { + fail('create should not have been called'); + } + }, + }; + const createSpy = spyOn(spy, 'create').and.callThrough(); + const updateSpy = spyOn(spy, 'update').and.callThrough(); + subscription.on('create', spy.create); + subscription.on('update', spy.update); + const obj = new Parse.Object('Test'); + obj.set('foo', 'bar'); + await obj.save(); + obj.set('foo', 'xyz'); + obj.set('yolo', 'xyz'); + await obj.save(); + const obj2 = new Parse.Object('Test'); + obj2.set('foo', 'bar'); + obj2.set('yolo', 'bar'); + await obj2.save(); + obj2.set('foo', 'bart'); + await obj2.save(); + await new Promise(resolve => setTimeout(resolve, 2000)); + expect(createSpy).toHaveBeenCalledTimes(1); + expect(updateSpy).toHaveBeenCalledTimes(1); + }); + it('can handle afterEvent set pointers', async done => { await reconfigureServer({ liveQuery: { diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index fa05f23711..6c9e7f4a52 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -10,7 +10,13 @@ import { ParsePubSub } from './ParsePubSub'; import SchemaController from '../Controllers/SchemaController'; import _ from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { runLiveQueryEventHandlers, getTrigger, runTrigger, resolveError, toJSONwithObjects } from '../triggers'; +import { + runLiveQueryEventHandlers, + getTrigger, + runTrigger, + resolveError, + toJSONwithObjects, +} from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; import LRU from 'lru-cache'; @@ -242,6 +248,11 @@ class ParseLiveQueryServer { continue; } requestIds.forEach(async requestId => { + const updatedFields = this._checkFields(client, requestId, message); + if (!updatedFields) { + return; + } + // Set orignal ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let originalACLCheckingPromise; @@ -608,6 +619,17 @@ class ParseLiveQueryServer { return auth; } + _checkFields(client: any, requestId: any, message: any) { + const subscriptionInfo = client.getSubscriptionInfo(requestId); + const fields = subscriptionInfo?.fields; + if (!fields) { + return true; + } + const object = message.currentParseObject; + const original = message.originalParseObject; + return fields.some(field => object.get(field) != original?.get(field)); + } + async _matchesACL(acl: any, client: any, requestId: number): Promise { // Return true directly if ACL isn't present, ACL is public read, or client has master key if (!acl || acl.getPublicReadAccess() || client.hasMasterKey) { From 6c1d16f39eea1088e8216b190e73117b41bb24d3 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 8 Jun 2022 12:02:58 +1000 Subject: [PATCH 2/8] fix tests --- spec/ParseLiveQueryServer.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 0d1a1e6387..2da5fda0b9 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -977,6 +977,7 @@ describe('ParseLiveQueryServer', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(true); + message.currentParseObject.set('test', 'bar'); const clientId = 1; const parseWebSocket = { @@ -1072,7 +1073,7 @@ describe('ParseLiveQueryServer', function () { where: { key: 'value', }, - fields: ['test'], + fields: ['key'], }; await addMockSubscription(parseLiveQueryServer, clientId, requestId, parseWebSocket, query); // Mock _matchesSubscription to return matching From ed5728b057b58d12ee0668be88e15d2f87b44372 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 8 Jun 2022 17:28:28 +1000 Subject: [PATCH 3/8] rename to triggerFields --- spec/ParseLiveQuery.spec.js | 2 +- spec/ParseLiveQueryServer.spec.js | 3 +-- src/LiveQuery/ParseLiveQueryServer.js | 15 +++++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 96024860a8..b99c86915f 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -401,7 +401,7 @@ describe('ParseLiveQuery', function () { startLiveQueryServer: true, }); const query = new Parse.Query('Test'); - query.select('yolo'); + query.triggerFields = ['yolo'] const subscription = await query.subscribe(); const spy = { create(obj) { diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 2da5fda0b9..0d1a1e6387 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -977,7 +977,6 @@ describe('ParseLiveQueryServer', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(true); - message.currentParseObject.set('test', 'bar'); const clientId = 1; const parseWebSocket = { @@ -1073,7 +1072,7 @@ describe('ParseLiveQueryServer', function () { where: { key: 'value', }, - fields: ['key'], + fields: ['test'], }; await addMockSubscription(parseLiveQueryServer, clientId, requestId, parseWebSocket, query); // Mock _matchesSubscription to return matching diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 6c9e7f4a52..d2bd2afa33 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -248,8 +248,8 @@ class ParseLiveQueryServer { continue; } requestIds.forEach(async requestId => { - const updatedFields = this._checkFields(client, requestId, message); - if (!updatedFields) { + const triggerFieldsChanged = this._checkTriggerFields(client, requestId, message); + if (!triggerFieldsChanged) { return; } @@ -619,15 +619,15 @@ class ParseLiveQueryServer { return auth; } - _checkFields(client: any, requestId: any, message: any) { + _checkTriggerFields(client: any, requestId: any, message: any) { const subscriptionInfo = client.getSubscriptionInfo(requestId); - const fields = subscriptionInfo?.fields; - if (!fields) { + const triggerFields = subscriptionInfo?.triggerFields; + if (!triggerFields) { return true; } const object = message.currentParseObject; const original = message.originalParseObject; - return fields.some(field => object.get(field) != original?.get(field)); + return triggerFields.some(field => !_.isEqual(object.get(field), original?.get(field))); } async _matchesACL(acl: any, client: any, requestId: number): Promise { @@ -811,6 +811,9 @@ class ParseLiveQueryServer { if (request.query.fields) { subscriptionInfo.fields = request.query.fields; } + if (request.query.triggerFields) { + subscriptionInfo.triggerFields = request.query.triggerFields; + } if (request.sessionToken) { subscriptionInfo.sessionToken = request.sessionToken; } From f7b410f5d3f0395be22c4c9b66abbec3bc61e844 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 8 Jun 2022 17:41:37 +1000 Subject: [PATCH 4/8] log --- src/LiveQuery/ParseLiveQueryServer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index d2bd2afa33..b70736944d 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -622,6 +622,7 @@ class ParseLiveQueryServer { _checkTriggerFields(client: any, requestId: any, message: any) { const subscriptionInfo = client.getSubscriptionInfo(requestId); const triggerFields = subscriptionInfo?.triggerFields; + console.log({triggerFields}) if (!triggerFields) { return true; } @@ -811,6 +812,7 @@ class ParseLiveQueryServer { if (request.query.fields) { subscriptionInfo.fields = request.query.fields; } + console.log(request.query) if (request.query.triggerFields) { subscriptionInfo.triggerFields = request.query.triggerFields; } From fa18eb7cd091cd120bbf2f1f21bf376710c18df9 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 9 Jun 2022 00:48:14 +1000 Subject: [PATCH 5/8] rename --- spec/ParseLiveQuery.spec.js | 4 +- spec/ParseLiveQueryServer.spec.js | 55 +++++++++++++++++++++++++++ src/LiveQuery/ParseLiveQueryServer.js | 2 - 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index b99c86915f..d2ba81bd67 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -393,7 +393,7 @@ describe('ParseLiveQuery', function () { await object.save(); }); - it('can handle live query with fields', async () => { + xit('can handle live query with fields - enable upon JS SDK support', async () => { await reconfigureServer({ liveQuery: { classNames: ['Test'], @@ -401,7 +401,7 @@ describe('ParseLiveQuery', function () { startLiveQueryServer: true, }); const query = new Parse.Query('Test'); - query.triggerFields = ['yolo'] + query.triggerFields('yolo'); const subscription = await query.subscribe(); const spy = { create(obj) { diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 0d1a1e6387..f3da0b3d5e 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -1099,6 +1099,61 @@ describe('ParseLiveQueryServer', function () { done(); }); + it('can handle create command with triggerFields', async () => { + jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client'); + const Client = require('../lib/LiveQuery/Client').Client; + const parseLiveQueryServer = new ParseLiveQueryServer({}); + // Make mock request message + const message = generateMockMessage(); + + const clientId = 1; + const parseWebSocket = { + clientId, + send: jasmine.createSpy('send'), + }; + const client = new Client(clientId, parseWebSocket); + spyOn(client, 'pushCreate').and.callThrough(); + parseLiveQueryServer.clients.set(clientId, client); + + // Add mock subscription + const requestId = 2; + const query = { + className: testClassName, + where: { + key: 'value', + }, + triggerFields: ['yolo'], + }; + await addMockSubscription(parseLiveQueryServer, clientId, requestId, parseWebSocket, query); + // Mock _matchesSubscription to return matching + parseLiveQueryServer._matchesSubscription = function (parseObject) { + if (!parseObject) { + return false; + } + return true; + }; + parseLiveQueryServer._matchesACL = function () { + return Promise.resolve(true); + }; + + parseLiveQueryServer._onAfterSave(message); + + // Make sure we send create command to client + await timeout(); + + expect(client.pushCreate).not.toHaveBeenCalled(); + + message.currentParseObject.set('yolo', 'test'); + parseLiveQueryServer._onAfterSave(message); + + await timeout(); + + const args = parseWebSocket.send.calls.mostRecent().args; + const toSend = JSON.parse(args[0]); + expect(toSend.object).toBeDefined(); + expect(toSend.original).toBeUndefined(); + }); + it('can match subscription for null or undefined parse object', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock subscription diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index b70736944d..d2bd2afa33 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -622,7 +622,6 @@ class ParseLiveQueryServer { _checkTriggerFields(client: any, requestId: any, message: any) { const subscriptionInfo = client.getSubscriptionInfo(requestId); const triggerFields = subscriptionInfo?.triggerFields; - console.log({triggerFields}) if (!triggerFields) { return true; } @@ -812,7 +811,6 @@ class ParseLiveQueryServer { if (request.query.fields) { subscriptionInfo.fields = request.query.fields; } - console.log(request.query) if (request.query.triggerFields) { subscriptionInfo.triggerFields = request.query.triggerFields; } From e309ec76da6b3521bec16e2094ecffd68c1b9ee9 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 9 Jun 2022 00:57:16 +1000 Subject: [PATCH 6/8] Update ParseLiveQueryServer.js --- src/LiveQuery/ParseLiveQueryServer.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index d2bd2afa33..17470c2de7 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -248,11 +248,6 @@ class ParseLiveQueryServer { continue; } requestIds.forEach(async requestId => { - const triggerFieldsChanged = this._checkTriggerFields(client, requestId, message); - if (!triggerFieldsChanged) { - return; - } - // Set orignal ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let originalACLCheckingPromise; @@ -313,6 +308,10 @@ class ParseLiveQueryServer { } else { return null; } + const triggerFieldsChanged = this._checkTriggerFields(client, requestId, message); + if (!triggerFieldsChanged && (type === 'update' || type === 'create')) { + return; + } res = { event: type, sessionToken: client.sessionToken, From 2ec03d4f00e270e4053c4de29e9ce954a7a4fa60 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 30 Dec 2022 18:26:47 +1100 Subject: [PATCH 7/8] wip --- spec/ParseLiveQuery.spec.js | 2 +- spec/ParseLiveQueryServer.spec.js | 4 ++-- src/LiveQuery/ParseLiveQueryServer.js | 17 +++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index ee21fa05d6..a3296246c1 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -401,7 +401,7 @@ describe('ParseLiveQuery', function () { startLiveQueryServer: true, }); const query = new Parse.Query('Test'); - query.triggerFields('yolo'); + query.listen('yolo'); const subscription = await query.subscribe(); const spy = { create(obj) { diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 00046181af..5849fdf311 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -1087,7 +1087,7 @@ describe('ParseLiveQueryServer', function () { done(); }); - it('can handle create command with triggerFields', async () => { + it('can handle create command with listen', async () => { jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client'); const Client = require('../lib/LiveQuery/Client').Client; const parseLiveQueryServer = new ParseLiveQueryServer({}); @@ -1110,7 +1110,7 @@ describe('ParseLiveQueryServer', function () { where: { key: 'value', }, - triggerFields: ['yolo'], + listen: ['yolo'], }; await addMockSubscription(parseLiveQueryServer, clientId, requestId, parseWebSocket, query); // Mock _matchesSubscription to return matching diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index b3cf5150a5..717087d08d 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -22,6 +22,7 @@ import { getCacheController, getDatabaseController } from '../Controllers'; import LRU from 'lru-cache'; import UserRouter from '../Routers/UsersRouter'; import DatabaseController from '../Controllers/DatabaseController'; +import { isDeepStrictEqual } from 'util'; class ParseLiveQueryServer { clients: Map; @@ -329,8 +330,8 @@ class ParseLiveQueryServer { } else { return null; } - const triggerFieldsChanged = this._checkTriggerFields(client, requestId, message); - if (!triggerFieldsChanged && (type === 'update' || type === 'create')) { + const listenFieldsChanged = this._checkListenFields(client, requestId, message); + if (!listenFieldsChanged && (type === 'update' || type === 'create')) { return; } res = { @@ -710,15 +711,15 @@ class ParseLiveQueryServer { return auth; } - _checkTriggerFields(client: any, requestId: any, message: any) { + _checkListenFields(client: any, requestId: any, message: any) { const subscriptionInfo = client.getSubscriptionInfo(requestId); - const triggerFields = subscriptionInfo?.triggerFields; - if (!triggerFields) { + const listen = subscriptionInfo?.listen; + if (!listen) { return true; } const object = message.currentParseObject; const original = message.originalParseObject; - return triggerFields.some(field => !_.isEqual(object.get(field), original?.get(field))); + return listen.some(field => !isDeepStrictEqual(object.get(field), original?.get(field))); } async _matchesACL(acl: any, client: any, requestId: number): Promise { @@ -902,8 +903,8 @@ class ParseLiveQueryServer { if (request.query.fields) { subscriptionInfo.fields = request.query.fields; } - if (request.query.triggerFields) { - subscriptionInfo.triggerFields = request.query.triggerFields; + if (request.query.listen) { + subscriptionInfo.listen = request.query.listen; } if (request.sessionToken) { subscriptionInfo.sessionToken = request.sessionToken; From 0ae3b3db60a60f246c95be306417a4257187aabd Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 16 Jan 2023 11:39:56 +1100 Subject: [PATCH 8/8] change to watch --- spec/ParseLiveQuery.spec.js | 2 +- spec/ParseLiveQueryServer.spec.js | 4 ++-- src/LiveQuery/ParseLiveQueryServer.js | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index df70659dfe..fbac779ce8 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -402,7 +402,7 @@ describe('ParseLiveQuery', function () { startLiveQueryServer: true, }); const query = new Parse.Query('Test'); - query.listen('yolo'); + query.watch('yolo'); const subscription = await query.subscribe(); const spy = { create(obj) { diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 5849fdf311..47e90733a1 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -1087,7 +1087,7 @@ describe('ParseLiveQueryServer', function () { done(); }); - it('can handle create command with listen', async () => { + it('can handle create command with watch', async () => { jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client'); const Client = require('../lib/LiveQuery/Client').Client; const parseLiveQueryServer = new ParseLiveQueryServer({}); @@ -1110,7 +1110,7 @@ describe('ParseLiveQueryServer', function () { where: { key: 'value', }, - listen: ['yolo'], + watch: ['yolo'], }; await addMockSubscription(parseLiveQueryServer, clientId, requestId, parseWebSocket, query); // Mock _matchesSubscription to return matching diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 74a02cdddd..1ecbda9372 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -330,8 +330,8 @@ class ParseLiveQueryServer { } else { return null; } - const listenFieldsChanged = this._checkListenFields(client, requestId, message); - if (!listenFieldsChanged && (type === 'update' || type === 'create')) { + const watchFieldsChanged = this._checkWatchFields(client, requestId, message); + if (!watchFieldsChanged && (type === 'update' || type === 'create')) { return; } res = { @@ -712,15 +712,15 @@ class ParseLiveQueryServer { return auth; } - _checkListenFields(client: any, requestId: any, message: any) { + _checkWatchFields(client: any, requestId: any, message: any) { const subscriptionInfo = client.getSubscriptionInfo(requestId); - const listen = subscriptionInfo?.listen; - if (!listen) { + const watch = subscriptionInfo?.watch; + if (!watch) { return true; } const object = message.currentParseObject; const original = message.originalParseObject; - return listen.some(field => !isDeepStrictEqual(object.get(field), original?.get(field))); + return watch.some(field => !isDeepStrictEqual(object.get(field), original?.get(field))); } async _matchesACL(acl: any, client: any, requestId: number): Promise { @@ -904,8 +904,8 @@ class ParseLiveQueryServer { if (request.query.fields) { subscriptionInfo.fields = request.query.fields; } - if (request.query.listen) { - subscriptionInfo.listen = request.query.listen; + if (request.query.watch) { + subscriptionInfo.watch = request.query.watch; } if (request.sessionToken) { subscriptionInfo.sessionToken = request.sessionToken;