From edaf032bfe1b7540a023d01e190f6fd2e9d005cf Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 13 Jul 2020 21:25:04 +1000 Subject: [PATCH 01/31] Before Connect + Before Subscribe #1 --- spec/ParseLiveQueryServer.spec.js | 326 +++++++++++++++----------- src/LiveQuery/ParseLiveQueryServer.js | 221 ++++++++++------- src/cloud-code/Parse.Cloud.js | 54 +++-- src/triggers.js | 142 +++++++++-- 4 files changed, 472 insertions(+), 271 deletions(-) diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 63ac0a0505..4b810a81e1 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -11,8 +11,8 @@ const queryHashValue = 'hash'; const testUserId = 'userId'; const testClassName = 'TestObject'; -describe('ParseLiveQueryServer', function() { - beforeEach(function(done) { +describe('ParseLiveQueryServer', function () { + beforeEach(function (done) { // Mock ParseWebSocketServer const mockParseWebSocketServer = jasmine.createSpy('ParseWebSocketServer'); jasmine.mockLibrary( @@ -21,7 +21,7 @@ describe('ParseLiveQueryServer', function() { mockParseWebSocketServer ); // Mock Client - const mockClient = function(id, socket, hasMasterKey) { + const mockClient = function (id, socket, hasMasterKey) { this.pushConnect = jasmine.createSpy('pushConnect'); this.pushSubscribe = jasmine.createSpy('pushSubscribe'); this.pushUnsubscribe = jasmine.createSpy('pushUnsubscribe'); @@ -38,7 +38,7 @@ describe('ParseLiveQueryServer', function() { mockClient.pushError = jasmine.createSpy('pushError'); jasmine.mockLibrary('../lib/LiveQuery/Client', 'Client', mockClient); // Mock Subscription - const mockSubscriotion = function() { + const mockSubscriotion = function () { this.addClientSubscription = jasmine.createSpy('addClientSubscription'); this.deleteClientSubscription = jasmine.createSpy( 'deleteClientSubscription' @@ -69,13 +69,13 @@ describe('ParseLiveQueryServer', function() { ); // Mock ParsePubSub const mockParsePubSub = { - createPublisher: function() { + createPublisher: function () { return { publish: jasmine.createSpy('publish'), on: jasmine.createSpy('on'), }; }, - createSubscriber: function() { + createSubscriber: function () { return { subscribe: jasmine.createSpy('subscribe'), on: jasmine.createSpy('on'), @@ -114,7 +114,7 @@ describe('ParseLiveQueryServer', function() { done(); }); - it('can be initialized', function() { + it('can be initialized', function () { const httpServer = {}; const parseLiveQueryServer = new ParseLiveQueryServer(httpServer); @@ -123,7 +123,7 @@ describe('ParseLiveQueryServer', function() { expect(parseLiveQueryServer.subscriptions.size).toBe(0); }); - it('can be initialized from ParseServer', function() { + it('can be initialized from ParseServer', function () { const httpServer = {}; const parseLiveQueryServer = ParseServer.createLiveQueryServer( httpServer, @@ -135,7 +135,7 @@ describe('ParseLiveQueryServer', function() { expect(parseLiveQueryServer.subscriptions.size).toBe(0); }); - it('can be initialized from ParseServer without httpServer', function(done) { + it('can be initialized from ParseServer without httpServer', function (done) { const parseLiveQueryServer = ParseServer.createLiveQueryServer(undefined, { port: 22345, }); @@ -147,7 +147,7 @@ describe('ParseLiveQueryServer', function() { }); describe_only_db('mongo')('initialization', () => { - it('can be initialized through ParseServer without liveQueryServerOptions', function(done) { + it('can be initialized through ParseServer without liveQueryServerOptions', function (done) { const parseServer = ParseServer.start({ appId: 'hello', masterKey: 'world', @@ -166,7 +166,7 @@ describe('ParseLiveQueryServer', function() { }); }); - it('can be initialized through ParseServer with liveQueryServerOptions', function(done) { + it('can be initialized through ParseServer with liveQueryServerOptions', function (done) { const parseServer = ParseServer.start({ appId: 'hello', masterKey: 'world', @@ -192,7 +192,7 @@ describe('ParseLiveQueryServer', function() { }); }); - it('properly passes the CLP to afterSave/afterDelete hook', function(done) { + it('properly passes the CLP to afterSave/afterDelete hook', function (done) { function setPermissionsOnClass(className, permissions, doPut) { const request = require('request'); let op = request.post; @@ -232,7 +232,7 @@ describe('ParseLiveQueryServer', function() { classNames: ['Yolo'], }, }) - .then(parseServer => { + .then((parseServer) => { saveSpy = spyOn(parseServer.config.liveQueryController, 'onAfterSave'); deleteSpy = spyOn( parseServer.config.liveQueryController, @@ -247,7 +247,7 @@ describe('ParseLiveQueryServer', function() { const obj = new Parse.Object('Yolo'); return obj.save(); }) - .then(obj => { + .then((obj) => { return obj.destroy(); }) .then(() => { @@ -285,7 +285,7 @@ describe('ParseLiveQueryServer', function() { .catch(done.fail); }); - it('can handle connect command', function() { + it('can handle connect command', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const parseWebSocket = { clientId: -1, @@ -293,7 +293,7 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer._validateKeys = jasmine .createSpy('validateKeys') .and.returnValue(true); - parseLiveQueryServer._handleConnect(parseWebSocket, { + await parseLiveQueryServer._handleConnect(parseWebSocket, { sessionToken: 'token', }); @@ -307,16 +307,16 @@ describe('ParseLiveQueryServer', function() { expect(client.pushConnect).toHaveBeenCalled(); }); - it('can handle subscribe command without clientId', function() { + it('can handle subscribe command without clientId', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const incompleteParseConn = {}; - parseLiveQueryServer._handleSubscribe(incompleteParseConn, {}); + await parseLiveQueryServer._handleSubscribe(incompleteParseConn, {}); const Client = require('../lib/LiveQuery/Client').Client; expect(Client.pushError).toHaveBeenCalled(); }); - it('can handle subscribe command with new query', function() { + it('can handle subscribe command with new query', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Add mock client const clientId = 1; @@ -338,7 +338,7 @@ describe('ParseLiveQueryServer', function() { requestId: requestId, sessionToken: 'sessionToken', }; - parseLiveQueryServer._handleSubscribe(parseWebSocket, request); + await parseLiveQueryServer._handleSubscribe(parseWebSocket, request); // Make sure we add the subscription to the server const subscriptions = parseLiveQueryServer.subscriptions; @@ -363,7 +363,7 @@ describe('ParseLiveQueryServer', function() { expect(client.pushSubscribe).toHaveBeenCalledWith(requestId); }); - it('can handle subscribe command with existing query', function() { + it('can handle subscribe command with existing query', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Add two mock clients const clientId = 1; @@ -382,7 +382,7 @@ describe('ParseLiveQueryServer', function() { }, fields: ['test'], }; - addMockSubscription( + await addMockSubscription( parseLiveQueryServer, clientId, requestId, @@ -401,7 +401,7 @@ describe('ParseLiveQueryServer', function() { fields: ['testAgain'], }; const requestIdAgain = 1; - addMockSubscription( + await addMockSubscription( parseLiveQueryServer, clientIdAgain, requestIdAgain, @@ -427,7 +427,7 @@ describe('ParseLiveQueryServer', function() { expect(args[1].fields).toBe(queryAgain.fields); }); - it('can handle unsubscribe command without clientId', function() { + it('can handle unsubscribe command without clientId', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); const incompleteParseConn = {}; parseLiveQueryServer._handleUnsubscribe(incompleteParseConn, {}); @@ -436,7 +436,7 @@ describe('ParseLiveQueryServer', function() { expect(Client.pushError).toHaveBeenCalled(); }); - it('can handle unsubscribe command without not existed client', function() { + it('can handle unsubscribe command without not existed client', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); const parseWebSocket = { clientId: 1, @@ -447,7 +447,7 @@ describe('ParseLiveQueryServer', function() { expect(Client.pushError).toHaveBeenCalled(); }); - it('can handle unsubscribe command without not existed query', function() { + it('can handle unsubscribe command without not existed query', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Add mock client const clientId = 1; @@ -462,7 +462,7 @@ describe('ParseLiveQueryServer', function() { expect(Client.pushError).toHaveBeenCalled(); }); - it('can handle unsubscribe command', function() { + it('can handle unsubscribe command', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Add mock client const clientId = 1; @@ -472,7 +472,7 @@ describe('ParseLiveQueryServer', function() { clientId: 1, }; const requestId = 2; - const subscription = addMockSubscription( + const subscription = await addMockSubscription( parseLiveQueryServer, clientId, requestId, @@ -481,7 +481,7 @@ describe('ParseLiveQueryServer', function() { // Mock client.getSubscriptionInfo const subscriptionInfo = client.addSubscriptionInfo.calls.mostRecent() .args[1]; - client.getSubscriptionInfo = function() { + client.getSubscriptionInfo = function () { return subscriptionInfo; }; // Handle unsubscribe command @@ -502,7 +502,7 @@ describe('ParseLiveQueryServer', function() { expect(subscriptions.size).toBe(0); }); - it('can set connect command message handler for a parseWebSocket', function() { + it('can set connect command message handler for a parseWebSocket', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Register mock connect/subscribe/unsubscribe handler for the server parseLiveQueryServer._handleConnect = jasmine.createSpy('_handleSubscribe'); @@ -525,7 +525,7 @@ describe('ParseLiveQueryServer', function() { expect(args[0]).toBe(parseWebSocket); }); - it('can set subscribe command message handler for a parseWebSocket', function() { + it('can set subscribe command message handler for a parseWebSocket', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Register mock connect/subscribe/unsubscribe handler for the server parseLiveQueryServer._handleSubscribe = jasmine.createSpy( @@ -551,7 +551,7 @@ describe('ParseLiveQueryServer', function() { expect(JSON.stringify(args[1])).toBe(subscribeRequest); }); - it('can set unsubscribe command message handler for a parseWebSocket', function() { + it('can set unsubscribe command message handler for a parseWebSocket', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Register mock connect/subscribe/unsubscribe handler for the server parseLiveQueryServer._handleUnsubscribe = jasmine.createSpy( @@ -577,7 +577,7 @@ describe('ParseLiveQueryServer', function() { expect(JSON.stringify(args[1])).toBe(unsubscribeRequest); }); - it('can set update command message handler for a parseWebSocket', function() { + it('can set update command message handler for a parseWebSocket', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Register mock connect/subscribe/unsubscribe handler for the server spyOn(parseLiveQueryServer, '_handleUpdateSubscription').and.callThrough(); @@ -612,7 +612,7 @@ describe('ParseLiveQueryServer', function() { expect(parseLiveQueryServer._handleSubscribe).toHaveBeenCalled(); }); - it('can set missing command message handler for a parseWebSocket', function() { + it('can set missing command message handler for a parseWebSocket', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock parseWebsocket const EventEmitter = require('events'); @@ -628,7 +628,7 @@ describe('ParseLiveQueryServer', function() { expect(Client.pushError).toHaveBeenCalled(); }); - it('can set unknown command message handler for a parseWebSocket', function() { + it('can set unknown command message handler for a parseWebSocket', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock parseWebsocket const EventEmitter = require('events'); @@ -644,7 +644,7 @@ describe('ParseLiveQueryServer', function() { expect(Client.pushError).toHaveBeenCalled(); }); - it('can set disconnect command message handler for a parseWebSocket which has not registered to the server', function() { + it('can set disconnect command message handler for a parseWebSocket which has not registered to the server', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); const EventEmitter = require('events'); const parseWebSocket = new EventEmitter(); @@ -657,7 +657,7 @@ describe('ParseLiveQueryServer', function() { parseWebSocket.emit('disconnect'); }); - it('can forward event to cloud code', function() { + it('can forward event to cloud code', function () { const cloudCodeHandler = { handler: () => {}, }; @@ -680,7 +680,7 @@ describe('ParseLiveQueryServer', function() { // TODO: Test server can set disconnect command message handler for a parseWebSocket - it('has no subscription and can handle object delete command', function() { + it('has no subscription and can handle object delete command', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make deletedParseObject const parseObject = new Parse.Object(testClassName); @@ -696,7 +696,7 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer._onAfterDelete(message, {}); }); - it('can handle object delete command which does not match any subscription', function() { + it('can handle object delete command which does not match any subscription', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make deletedParseObject const parseObject = new Parse.Object(testClassName); @@ -714,13 +714,13 @@ describe('ParseLiveQueryServer', function() { addMockClient(parseLiveQueryServer, clientId); // Add mock subscription const requestId = 2; - addMockSubscription(parseLiveQueryServer, clientId, requestId); + await addMockSubscription(parseLiveQueryServer, clientId, requestId); const client = parseLiveQueryServer.clients.get(clientId); // Mock _matchesSubscription to return not matching - parseLiveQueryServer._matchesSubscription = function() { + parseLiveQueryServer._matchesSubscription = function () { return false; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return true; }; parseLiveQueryServer._onAfterDelete(message); @@ -729,7 +729,7 @@ describe('ParseLiveQueryServer', function() { expect(client.pushDelete).not.toHaveBeenCalled(); }); - it('can handle object delete command which matches some subscriptions', function(done) { + it('can handle object delete command which matches some subscriptions', async (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make deletedParseObject const parseObject = new Parse.Object(testClassName); @@ -746,26 +746,26 @@ describe('ParseLiveQueryServer', function() { addMockClient(parseLiveQueryServer, clientId); // Add mock subscription const requestId = 2; - addMockSubscription(parseLiveQueryServer, clientId, requestId); + await addMockSubscription(parseLiveQueryServer, clientId, requestId); const client = parseLiveQueryServer.clients.get(clientId); // Mock _matchesSubscription to return matching - parseLiveQueryServer._matchesSubscription = function() { + parseLiveQueryServer._matchesSubscription = function () { return true; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return Promise.resolve(true); }; parseLiveQueryServer._onAfterDelete(message); // Make sure we send command to client, since _matchesACL is async, we have to // wait and check - setTimeout(function() { + setTimeout(function () { expect(client.pushDelete).toHaveBeenCalled(); done(); }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('has no subscription and can handle object save command', function() { + it('has no subscription and can handle object save command', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(); @@ -773,7 +773,7 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer._onAfterSave(message); }); - it('can handle object save command which does not match any subscription', function(done) { + it('can handle object save command which does not match any subscription', async (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(); @@ -782,19 +782,19 @@ describe('ParseLiveQueryServer', function() { const client = addMockClient(parseLiveQueryServer, clientId); // Add mock subscription const requestId = 2; - addMockSubscription(parseLiveQueryServer, clientId, requestId); + await addMockSubscription(parseLiveQueryServer, clientId, requestId); // Mock _matchesSubscription to return not matching - parseLiveQueryServer._matchesSubscription = function() { + parseLiveQueryServer._matchesSubscription = function () { return false; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return Promise.resolve(true); }; // Trigger onAfterSave parseLiveQueryServer._onAfterSave(message); // Make sure we do not send command to client - setTimeout(function() { + setTimeout(function () { expect(client.pushCreate).not.toHaveBeenCalled(); expect(client.pushEnter).not.toHaveBeenCalled(); expect(client.pushUpdate).not.toHaveBeenCalled(); @@ -804,7 +804,7 @@ describe('ParseLiveQueryServer', function() { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle object enter command which matches some subscriptions', function(done) { + it('can handle object enter command which matches some subscriptions', async (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(true); @@ -813,25 +813,25 @@ describe('ParseLiveQueryServer', function() { const client = addMockClient(parseLiveQueryServer, clientId); // Add mock subscription const requestId = 2; - addMockSubscription(parseLiveQueryServer, clientId, requestId); + await addMockSubscription(parseLiveQueryServer, clientId, requestId); // Mock _matchesSubscription to return matching // In order to mimic a enter, we need original match return false // and the current match return true let counter = 0; - parseLiveQueryServer._matchesSubscription = function(parseObject) { + parseLiveQueryServer._matchesSubscription = function (parseObject) { if (!parseObject) { return false; } counter += 1; return counter % 2 === 0; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return Promise.resolve(true); }; parseLiveQueryServer._onAfterSave(message); // Make sure we send enter command to client - setTimeout(function() { + setTimeout(function () { expect(client.pushCreate).not.toHaveBeenCalled(); expect(client.pushEnter).toHaveBeenCalled(); expect(client.pushUpdate).not.toHaveBeenCalled(); @@ -841,7 +841,7 @@ describe('ParseLiveQueryServer', function() { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle object update command which matches some subscriptions', function(done) { + it('can handle object update command which matches some subscriptions', async (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(true); @@ -850,21 +850,21 @@ describe('ParseLiveQueryServer', function() { const client = addMockClient(parseLiveQueryServer, clientId); // Add mock subscription const requestId = 2; - addMockSubscription(parseLiveQueryServer, clientId, requestId); + await addMockSubscription(parseLiveQueryServer, clientId, requestId); // Mock _matchesSubscription to return matching - parseLiveQueryServer._matchesSubscription = function(parseObject) { + parseLiveQueryServer._matchesSubscription = function (parseObject) { if (!parseObject) { return false; } return true; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return Promise.resolve(true); }; parseLiveQueryServer._onAfterSave(message); // Make sure we send update command to client - setTimeout(function() { + setTimeout(function () { expect(client.pushCreate).not.toHaveBeenCalled(); expect(client.pushEnter).not.toHaveBeenCalled(); expect(client.pushUpdate).toHaveBeenCalled(); @@ -874,7 +874,7 @@ describe('ParseLiveQueryServer', function() { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle object leave command which matches some subscriptions', function(done) { + it('can handle object leave command which matches some subscriptions', async (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(true); @@ -883,25 +883,25 @@ describe('ParseLiveQueryServer', function() { const client = addMockClient(parseLiveQueryServer, clientId); // Add mock subscription const requestId = 2; - addMockSubscription(parseLiveQueryServer, clientId, requestId); + await addMockSubscription(parseLiveQueryServer, clientId, requestId); // Mock _matchesSubscription to return matching // In order to mimic a leave, we need original match return true // and the current match return false let counter = 0; - parseLiveQueryServer._matchesSubscription = function(parseObject) { + parseLiveQueryServer._matchesSubscription = function (parseObject) { if (!parseObject) { return false; } counter += 1; return counter % 2 !== 0; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return Promise.resolve(true); }; parseLiveQueryServer._onAfterSave(message); // Make sure we send leave command to client - setTimeout(function() { + setTimeout(function () { expect(client.pushCreate).not.toHaveBeenCalled(); expect(client.pushEnter).not.toHaveBeenCalled(); expect(client.pushUpdate).not.toHaveBeenCalled(); @@ -911,7 +911,7 @@ describe('ParseLiveQueryServer', function() { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle update command with original object', function(done) { + it('can handle update command with original object', async (done) => { jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client'); const Client = require('../lib/LiveQuery/Client').Client; const parseLiveQueryServer = new ParseLiveQueryServer({}); @@ -930,27 +930,27 @@ describe('ParseLiveQueryServer', function() { // Add mock subscription const requestId = 2; - addMockSubscription( + await addMockSubscription( parseLiveQueryServer, clientId, requestId, parseWebSocket ); // Mock _matchesSubscription to return matching - parseLiveQueryServer._matchesSubscription = function(parseObject) { + parseLiveQueryServer._matchesSubscription = function (parseObject) { if (!parseObject) { return false; } return true; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return Promise.resolve(true); }; parseLiveQueryServer._onAfterSave(message); // Make sure we send update command to client - setTimeout(function() { + setTimeout(function () { expect(client.pushUpdate).toHaveBeenCalled(); const args = parseWebSocket.send.calls.mostRecent().args; const toSend = JSON.parse(args[0]); @@ -961,7 +961,7 @@ describe('ParseLiveQueryServer', function() { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle object create command which matches some subscriptions', function(done) { + it('can handle object create command which matches some subscriptions', async (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(); @@ -970,21 +970,21 @@ describe('ParseLiveQueryServer', function() { const client = addMockClient(parseLiveQueryServer, clientId); // Add mock subscription const requestId = 2; - addMockSubscription(parseLiveQueryServer, clientId, requestId); + await addMockSubscription(parseLiveQueryServer, clientId, requestId); // Mock _matchesSubscription to return matching - parseLiveQueryServer._matchesSubscription = function(parseObject) { + parseLiveQueryServer._matchesSubscription = function (parseObject) { if (!parseObject) { return false; } return true; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return Promise.resolve(true); }; parseLiveQueryServer._onAfterSave(message); // Make sure we send create command to client - setTimeout(function() { + setTimeout(function () { expect(client.pushCreate).toHaveBeenCalled(); expect(client.pushEnter).not.toHaveBeenCalled(); expect(client.pushUpdate).not.toHaveBeenCalled(); @@ -994,7 +994,7 @@ describe('ParseLiveQueryServer', function() { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle create command with fields', function(done) { + it('can handle create command with fields', async (done) => { jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client'); const Client = require('../lib/LiveQuery/Client').Client; const parseLiveQueryServer = new ParseLiveQueryServer({}); @@ -1019,7 +1019,7 @@ describe('ParseLiveQueryServer', function() { }, fields: ['test'], }; - addMockSubscription( + await addMockSubscription( parseLiveQueryServer, clientId, requestId, @@ -1027,20 +1027,20 @@ describe('ParseLiveQueryServer', function() { query ); // Mock _matchesSubscription to return matching - parseLiveQueryServer._matchesSubscription = function(parseObject) { + parseLiveQueryServer._matchesSubscription = function (parseObject) { if (!parseObject) { return false; } return true; }; - parseLiveQueryServer._matchesACL = function() { + parseLiveQueryServer._matchesACL = function () { return Promise.resolve(true); }; parseLiveQueryServer._onAfterSave(message); // Make sure we send create command to client - setTimeout(function() { + setTimeout(function () { expect(client.pushCreate).toHaveBeenCalled(); const args = parseWebSocket.send.calls.mostRecent().args; const toSend = JSON.parse(args[0]); @@ -1050,7 +1050,7 @@ describe('ParseLiveQueryServer', function() { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can match subscription for null or undefined parse object', function() { + it('can match subscription for null or undefined parse object', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock subscription const subscription = { @@ -1067,7 +1067,7 @@ describe('ParseLiveQueryServer', function() { expect(subscription.match).not.toHaveBeenCalled(); }); - it('can match subscription', function() { + it('can match subscription', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock subscription const subscription = { @@ -1082,7 +1082,7 @@ describe('ParseLiveQueryServer', function() { expect(matchesQuery).toHaveBeenCalledWith(parseObject, subscription.query); }); - it('can inflate parse object', function() { + it('can inflate parse object', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request const objectJSON = { @@ -1177,20 +1177,20 @@ describe('ParseLiveQueryServer', function() { expect(originalObject.updatedAt).not.toBeUndefined(); }); - it('can match undefined ACL', function(done) { + it('can match undefined ACL', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const client = {}; const requestId = 0; parseLiveQueryServer ._matchesACL(undefined, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(true); done(); }); }); - it('can match ACL with none exist requestId', function(done) { + it('can match ACL with none exist requestId', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); const client = { @@ -1202,13 +1202,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }); }); - it('can match ACL with public read access', function(done) { + it('can match ACL with public read access', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setPublicReadAccess(true); @@ -1223,13 +1223,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(true); done(); }); }); - it('can match ACL with valid subscription sessionToken', function(done) { + it('can match ACL with valid subscription sessionToken', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1244,13 +1244,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(true); done(); }); }); - it('can match ACL with valid client sessionToken', function(done) { + it('can match ACL with valid client sessionToken', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1267,13 +1267,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(true); done(); }); }); - it('can match ACL with invalid subscription and client sessionToken', function(done) { + it('can match ACL with invalid subscription and client sessionToken', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1290,13 +1290,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }); }); - it('can match ACL with subscription sessionToken checking error', function(done) { + it('can match ACL with subscription sessionToken checking error', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1313,13 +1313,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }); }); - it('can match ACL with client sessionToken checking error', function(done) { + it('can match ACL with client sessionToken checking error', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1336,13 +1336,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }); }); - it("won't match ACL that doesn't have public read or any roles", function(done) { + it("won't match ACL that doesn't have public read or any roles", function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setPublicReadAccess(false); @@ -1357,13 +1357,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }); }); - it("won't match non-public ACL with role when there is no user", function(done) { + it("won't match non-public ACL with role when there is no user", function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setPublicReadAccess(false); @@ -1377,14 +1377,14 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }) .catch(done.fail); }); - it("won't match ACL with role based read access set to false", function(done) { + it("won't match ACL with role based read access set to false", function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setPublicReadAccess(false); @@ -1398,7 +1398,7 @@ describe('ParseLiveQueryServer', function() { }; const requestId = 0; - spyOn(Parse, 'Query').and.callFake(function() { + spyOn(Parse, 'Query').and.callFake(function () { let shouldReturn = false; return { equalTo() { @@ -1427,20 +1427,20 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }); parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }); }); - it('will match ACL with role based read access set to true', function(done) { + it('will match ACL with role based read access set to true', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setPublicReadAccess(false); @@ -1454,7 +1454,7 @@ describe('ParseLiveQueryServer', function() { }; const requestId = 0; - spyOn(Parse, 'Query').and.callFake(function() { + spyOn(Parse, 'Query').and.callFake(function () { let shouldReturn = false; return { equalTo() { @@ -1493,14 +1493,14 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(true); done(); }); }); describe('class level permissions', () => { - it('matches CLP when find is closed', done => { + it('matches CLP when find is closed', (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1525,13 +1525,13 @@ describe('ParseLiveQueryServer', function() { requestId, 'find' ) - .then(isMatched => { + .then((isMatched) => { expect(isMatched).toBe(false); done(); }); }); - it('matches CLP when find is open', done => { + it('matches CLP when find is open', (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1556,13 +1556,13 @@ describe('ParseLiveQueryServer', function() { requestId, 'find' ) - .then(isMatched => { + .then((isMatched) => { expect(isMatched).toBe(true); done(); }); }); - it('matches CLP when find is restricted to userIds', done => { + it('matches CLP when find is restricted to userIds', (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1587,13 +1587,13 @@ describe('ParseLiveQueryServer', function() { requestId, 'find' ) - .then(isMatched => { + .then((isMatched) => { expect(isMatched).toBe(true); done(); }); }); - it('matches CLP when find is restricted to userIds', done => { + it('matches CLP when find is restricted to userIds', (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1618,14 +1618,14 @@ describe('ParseLiveQueryServer', function() { requestId, 'find' ) - .then(isMatched => { + .then((isMatched) => { expect(isMatched).toBe(false); done(); }); }); }); - it('can validate key when valid key is provided', function() { + it('can validate key when valid key is provided', function () { const parseLiveQueryServer = new ParseLiveQueryServer( {}, { @@ -1643,7 +1643,7 @@ describe('ParseLiveQueryServer', function() { ).toBeTruthy(); }); - it('can validate key when invalid key is provided', function() { + it('can validate key when invalid key is provided', function () { const parseLiveQueryServer = new ParseLiveQueryServer( {}, { @@ -1661,7 +1661,7 @@ describe('ParseLiveQueryServer', function() { ).not.toBeTruthy(); }); - it('can validate key when key is not provided', function() { + it('can validate key when key is not provided', function () { const parseLiveQueryServer = new ParseLiveQueryServer( {}, { @@ -1677,7 +1677,7 @@ describe('ParseLiveQueryServer', function() { ).not.toBeTruthy(); }); - it('can validate key when validKerPairs is empty', function() { + it('can validate key when validKerPairs is empty', function () { const parseLiveQueryServer = new ParseLiveQueryServer({}, {}); const request = {}; @@ -1686,7 +1686,7 @@ describe('ParseLiveQueryServer', function() { ).toBeTruthy(); }); - it('can validate client has master key when valid', function() { + it('can validate client has master key when valid', function () { const parseLiveQueryServer = new ParseLiveQueryServer( {}, { @@ -1704,7 +1704,7 @@ describe('ParseLiveQueryServer', function() { ).toBeTruthy(); }); - it("can validate client doesn't have master key when invalid", function() { + it("can validate client doesn't have master key when invalid", function () { const parseLiveQueryServer = new ParseLiveQueryServer( {}, { @@ -1722,7 +1722,7 @@ describe('ParseLiveQueryServer', function() { ).not.toBeTruthy(); }); - it("can validate client doesn't have master key when not provided", function() { + it("can validate client doesn't have master key when not provided", function () { const parseLiveQueryServer = new ParseLiveQueryServer( {}, { @@ -1737,7 +1737,7 @@ describe('ParseLiveQueryServer', function() { ).not.toBeTruthy(); }); - it("can validate client doesn't have master key when validKeyPairs is empty", function() { + it("can validate client doesn't have master key when validKeyPairs is empty", function () { const parseLiveQueryServer = new ParseLiveQueryServer({}, {}); const request = { masterKey: 'test', @@ -1748,7 +1748,7 @@ describe('ParseLiveQueryServer', function() { ).not.toBeTruthy(); }); - it('will match non-public ACL when client has master key', function(done) { + it('will match non-public ACL when client has master key', function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setPublicReadAccess(false); @@ -1762,13 +1762,13 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(true); done(); }); }); - it("won't match non-public ACL when client has no master key", function(done) { + it("won't match non-public ACL when client has no master key", function (done) { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setPublicReadAccess(false); @@ -1782,7 +1782,7 @@ describe('ParseLiveQueryServer', function() { parseLiveQueryServer ._matchesACL(acl, client, requestId) - .then(function(isMatched) { + .then(function (isMatched) { expect(isMatched).toBe(false); done(); }); @@ -1822,7 +1822,7 @@ describe('ParseLiveQueryServer', function() { expect(parseLiveQueryServer.authCache.get('invalid')).not.toBe(undefined); }); - afterEach(function() { + afterEach(function () { jasmine.restoreLibrary( '../lib/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer' @@ -1842,7 +1842,7 @@ describe('ParseLiveQueryServer', function() { return client; } - function addMockSubscription( + async function addMockSubscription( parseLiveQueryServer, clientId, requestId, @@ -1870,13 +1870,13 @@ describe('ParseLiveQueryServer', function() { requestId: requestId, sessionToken: 'sessionToken', }; - parseLiveQueryServer._handleSubscribe(parseWebSocket, request); + await parseLiveQueryServer._handleSubscribe(parseWebSocket, request); // Make mock subscription const subscription = parseLiveQueryServer.subscriptions .get(query.className) .get(queryHashValue); - subscription.hasSubscribingClient = function() { + subscription.hasSubscribingClient = function () { return false; }; subscription.className = query.className; @@ -1915,7 +1915,7 @@ describe('ParseLiveQueryServer', function() { }); describe('LiveQueryController', () => { - it('properly passes the CLP to afterSave/afterDelete hook', function(done) { + it('properly passes the CLP to afterSave/afterDelete hook', function (done) { function setPermissionsOnClass(className, permissions, doPut) { const request = require('request'); let op = request.post; @@ -1955,7 +1955,7 @@ describe('LiveQueryController', () => { classNames: ['Yolo'], }, }) - .then(parseServer => { + .then((parseServer) => { saveSpy = spyOn( parseServer.config.liveQueryController, 'onAfterSave' @@ -1973,7 +1973,7 @@ describe('LiveQueryController', () => { const obj = new Parse.Object('Yolo'); return obj.save(); }) - .then(obj => { + .then((obj) => { return obj.destroy(); }) .then(() => { @@ -2053,3 +2053,49 @@ describe('LiveQueryController', () => { }); }); }); + +it('basic beforeConnect rejection', async () => { + Parse.Cloud.beforeConnect(function () { + throw new Error('You shall not pass!'); + }); + const parseLiveQueryServer = new ParseLiveQueryServer({}); + const parseWebSocket = { + clientId: -1, + }; + await parseLiveQueryServer._handleConnect(parseWebSocket, { + sessionToken: 'token', + }); + expect(parseLiveQueryServer.clients.size).toBe(0); + const Client = require('../lib/LiveQuery/Client').Client; + expect(Client.pushError).toHaveBeenCalled(); +}); + +it('basic beforeSubscribe rejection', async () => { + Parse.Cloud.beforeSubscribe('test', function () { + throw new Error('You shall not pass!'); + }); + const parseLiveQueryServer = new ParseLiveQueryServer({}); + const parseWebSocket = { + clientId: -1, + }; + await parseLiveQueryServer._handleConnect(parseWebSocket, { + sessionToken: 'token', + }); + const query = { + className: 'test', + where: { + key: 'value', + }, + fields: ['test'], + }; + const requestId = 2; + const request = { + query: query, + requestId: requestId, + sessionToken: 'sessionToken', + }; + await parseLiveQueryServer._handleSubscribe(parseWebSocket, request); + expect(parseLiveQueryServer.clients.size).toBe(0); + const Client = require('../lib/LiveQuery/Client').Client; + expect(Client.pushError).toHaveBeenCalled(); +}); diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 0411745e9e..587d6aa165 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -11,6 +11,8 @@ import SchemaController from '../Controllers/SchemaController'; import _ from 'lodash'; import { v4 as uuidv4 } from 'uuid'; import { runLiveQueryEventHandlers } from '../triggers'; +import { maybeRunConnectTrigger } from '../triggers'; +import { maybeRunSubscribeTrigger } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; import LRU from 'lru-cache'; @@ -575,32 +577,54 @@ class ParseLiveQueryServer { } _handleConnect(parseWebsocket: any, request: any): any { - if (!this._validateKeys(request, this.keyPairs)) { - Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); - logger.error('Key in request is not valid'); - return; - } - const hasMasterKey = this._hasMasterKey(request, this.keyPairs); - const clientId = uuidv4(); - const client = new Client( - clientId, - parseWebsocket, - hasMasterKey, - request.sessionToken, - request.installationId - ); - parseWebsocket.clientId = clientId; - this.clients.set(parseWebsocket.clientId, client); - logger.info(`Create new client: ${parseWebsocket.clientId}`); - client.pushConnect(); - runLiveQueryEventHandlers({ - client, - event: 'connect', - clients: this.clients.size, - subscriptions: this.subscriptions.size, - sessionToken: request.sessionToken, - useMasterKey: client.hasMasterKey, - installationId: request.installationId, + return new Promise((resolve) => { + if (!this._validateKeys(request, this.keyPairs)) { + Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); + logger.error('Key in request is not valid'); + resolve(); + return; + } + const hasMasterKey = this._hasMasterKey(request, this.keyPairs); + const clientId = uuidv4(); + const client = new Client( + clientId, + parseWebsocket, + hasMasterKey, + request.sessionToken, + request.installationId + ); + const req = { + client, + event: 'connect', + clients: this.clients.size, + subscriptions: this.subscriptions.size, + sessionToken: request.sessionToken, + useMasterKey: client.hasMasterKey, + installationId: request.installationId, + }; + maybeRunConnectTrigger('beforeConnect', req).then( + () => { + parseWebsocket.clientId = clientId; + this.clients.set(parseWebsocket.clientId, client); + logger.info(`Create new client: ${parseWebsocket.clientId}`); + client.pushConnect(); + runLiveQueryEventHandlers(req); + resolve(); + }, + (error) => { + Client.pushError( + parseWebsocket, + error.code || 101, + error.message || error, + false + ); + logger.error( + `Failed running beforeConnect for session ${req.sessionToken} with:\n Error: ` + + JSON.stringify(error) + ); + resolve(); + } + ); }); } @@ -638,72 +662,93 @@ class ParseLiveQueryServer { _handleSubscribe(parseWebsocket: any, request: any): any { // If we can not find this client, return error to client - if (!Object.prototype.hasOwnProperty.call(parseWebsocket, 'clientId')) { - Client.pushError( - parseWebsocket, - 2, - 'Can not find this client, make sure you connect to server before subscribing' - ); - logger.error( - 'Can not find this client, make sure you connect to server before subscribing' - ); - return; - } - const client = this.clients.get(parseWebsocket.clientId); - - // Get subscription from subscriptions, create one if necessary - const subscriptionHash = queryHash(request.query); - // Add className to subscriptions if necessary - const className = request.query.className; - if (!this.subscriptions.has(className)) { - this.subscriptions.set(className, new Map()); - } - const classSubscriptions = this.subscriptions.get(className); - let subscription; - if (classSubscriptions.has(subscriptionHash)) { - subscription = classSubscriptions.get(subscriptionHash); - } else { - subscription = new Subscription( - className, - request.query.where, - subscriptionHash - ); - classSubscriptions.set(subscriptionHash, subscription); - } + return new Promise((resolve) => { + if (!Object.prototype.hasOwnProperty.call(parseWebsocket, 'clientId')) { + Client.pushError( + parseWebsocket, + 2, + 'Can not find this client, make sure you connect to server before subscribing' + ); + logger.error( + 'Can not find this client, make sure you connect to server before subscribing' + ); + resolve(); + return; + } + const client = this.clients.get(parseWebsocket.clientId); + const className = request.query.className; + maybeRunSubscribeTrigger('beforeSubscribe', className, request).then( + () => { + // Get subscription from subscriptions, create one if necessary + const subscriptionHash = queryHash(request.query); + // Add className to subscriptions if necessary + + if (!this.subscriptions.has(className)) { + this.subscriptions.set(className, new Map()); + } + const classSubscriptions = this.subscriptions.get(className); + let subscription; + if (classSubscriptions.has(subscriptionHash)) { + subscription = classSubscriptions.get(subscriptionHash); + } else { + subscription = new Subscription( + className, + request.query.where, + subscriptionHash + ); + classSubscriptions.set(subscriptionHash, subscription); + } - // Add subscriptionInfo to client - const subscriptionInfo = { - subscription: subscription, - }; - // Add selected fields, sessionToken and installationId for this subscription if necessary - if (request.query.fields) { - subscriptionInfo.fields = request.query.fields; - } - if (request.sessionToken) { - subscriptionInfo.sessionToken = request.sessionToken; - } - client.addSubscriptionInfo(request.requestId, subscriptionInfo); + // Add subscriptionInfo to client + const subscriptionInfo = { + subscription: subscription, + }; + // Add selected fields, sessionToken and installationId for this subscription if necessary + if (request.query.fields) { + subscriptionInfo.fields = request.query.fields; + } + if (request.sessionToken) { + subscriptionInfo.sessionToken = request.sessionToken; + } + client.addSubscriptionInfo(request.requestId, subscriptionInfo); - // Add clientId to subscription - subscription.addClientSubscription( - parseWebsocket.clientId, - request.requestId - ); + // Add clientId to subscription + subscription.addClientSubscription( + parseWebsocket.clientId, + request.requestId + ); - client.pushSubscribe(request.requestId); + client.pushSubscribe(request.requestId); - logger.verbose( - `Create client ${parseWebsocket.clientId} new subscription: ${request.requestId}` - ); - logger.verbose('Current client number: %d', this.clients.size); - runLiveQueryEventHandlers({ - client, - event: 'subscribe', - clients: this.clients.size, - subscriptions: this.subscriptions.size, - sessionToken: request.sessionToken, - useMasterKey: client.hasMasterKey, - installationId: client.installationId, + logger.verbose( + `Create client ${parseWebsocket.clientId} new subscription: ${request.requestId}` + ); + logger.verbose('Current client number: %d', this.clients.size); + runLiveQueryEventHandlers({ + client, + event: 'subscribe', + clients: this.clients.size, + subscriptions: this.subscriptions.size, + sessionToken: request.sessionToken, + useMasterKey: client.hasMasterKey, + installationId: client.installationId, + }); + resolve(); + }, + (e) => { + Client.pushError( + parseWebsocket, + e.code || 101, + e.message || e, + false + ); + logger.error( + `Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` + + JSON.stringify(e) + ); + resolve(); + } + ); }); } diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index c4bd380b1e..cef71204ad 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -38,7 +38,7 @@ var ParseCloud = {}; * @param {String} name The name of the Cloud Function * @param {Function} data The Cloud Function to register. This function can be an async function and should take one parameter a {@link Parse.Cloud.FunctionRequest}. */ -ParseCloud.define = function(functionName, handler, validationHandler) { +ParseCloud.define = function (functionName, handler, validationHandler) { triggers.addFunction( functionName, handler, @@ -58,7 +58,7 @@ ParseCloud.define = function(functionName, handler, validationHandler) { * @param {Function} func The Background Job to register. This function can be async should take a single parameters a {@link Parse.Cloud.JobRequest} * */ -ParseCloud.job = function(functionName, handler) { +ParseCloud.job = function (functionName, handler) { triggers.addJob(functionName, handler, Parse.applicationId); }; @@ -85,7 +85,7 @@ ParseCloud.job = function(functionName, handler) { * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after save function for. This can instead be a String that is the className of the subclass. * @param {Function} func The function to run before a save. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest}; */ -ParseCloud.beforeSave = function(parseClass, handler) { +ParseCloud.beforeSave = function (parseClass, handler) { var className = getClassName(parseClass); triggers.addTrigger( triggers.Types.beforeSave, @@ -116,7 +116,7 @@ ParseCloud.beforeSave = function(parseClass, handler) { * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before delete function for. This can instead be a String that is the className of the subclass. * @param {Function} func The function to run before a delete. This function can be async and should take one parameter, a {@link Parse.Cloud.TriggerRequest}. */ -ParseCloud.beforeDelete = function(parseClass, handler) { +ParseCloud.beforeDelete = function (parseClass, handler) { var className = getClassName(parseClass); triggers.addTrigger( triggers.Types.beforeDelete, @@ -149,7 +149,7 @@ ParseCloud.beforeDelete = function(parseClass, handler) { * @name Parse.Cloud.beforeLogin * @param {Function} func The function to run before a login. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest}; */ -ParseCloud.beforeLogin = function(handler) { +ParseCloud.beforeLogin = function (handler) { let className = '_User'; if (typeof handler === 'string' || isParseObjectConstructor(handler)) { // validation will occur downstream, this is to maintain internal @@ -185,7 +185,7 @@ ParseCloud.beforeLogin = function(handler) { * @name Parse.Cloud.afterLogin * @param {Function} func The function to run after a login. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest}; */ -ParseCloud.afterLogin = function(handler) { +ParseCloud.afterLogin = function (handler) { let className = '_User'; if (typeof handler === 'string' || isParseObjectConstructor(handler)) { // validation will occur downstream, this is to maintain internal @@ -220,7 +220,7 @@ ParseCloud.afterLogin = function(handler) { * @name Parse.Cloud.afterLogout * @param {Function} func The function to run after a logout. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest}; */ -ParseCloud.afterLogout = function(handler) { +ParseCloud.afterLogout = function (handler) { let className = '_Session'; if (typeof handler === 'string' || isParseObjectConstructor(handler)) { // validation will occur downstream, this is to maintain internal @@ -258,7 +258,7 @@ ParseCloud.afterLogout = function(handler) { * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after save function for. This can instead be a String that is the className of the subclass. * @param {Function} func The function to run after a save. This function can be an async function and should take just one parameter, {@link Parse.Cloud.TriggerRequest}. */ -ParseCloud.afterSave = function(parseClass, handler) { +ParseCloud.afterSave = function (parseClass, handler) { var className = getClassName(parseClass); triggers.addTrigger( triggers.Types.afterSave, @@ -289,7 +289,7 @@ ParseCloud.afterSave = function(parseClass, handler) { * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after delete function for. This can instead be a String that is the className of the subclass. * @param {Function} func The function to run after a delete. This function can be async and should take just one parameter, {@link Parse.Cloud.TriggerRequest}. */ -ParseCloud.afterDelete = function(parseClass, handler) { +ParseCloud.afterDelete = function (parseClass, handler) { var className = getClassName(parseClass); triggers.addTrigger( triggers.Types.afterDelete, @@ -320,7 +320,7 @@ ParseCloud.afterDelete = function(parseClass, handler) { * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before find function for. This can instead be a String that is the className of the subclass. * @param {Function} func The function to run before a find. This function can be async and should take just one parameter, {@link Parse.Cloud.BeforeFindRequest}. */ -ParseCloud.beforeFind = function(parseClass, handler) { +ParseCloud.beforeFind = function (parseClass, handler) { var className = getClassName(parseClass); triggers.addTrigger( triggers.Types.beforeFind, @@ -351,7 +351,7 @@ ParseCloud.beforeFind = function(parseClass, handler) { * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after find function for. This can instead be a String that is the className of the subclass. * @param {Function} func The function to run before a find. This function can be async and should take just one parameter, {@link Parse.Cloud.AfterFindRequest}. */ -ParseCloud.afterFind = function(parseClass, handler) { +ParseCloud.afterFind = function (parseClass, handler) { const className = getClassName(parseClass); triggers.addTrigger( triggers.Types.afterFind, @@ -376,7 +376,7 @@ ParseCloud.afterFind = function(parseClass, handler) { * @name Parse.Cloud.beforeSaveFile * @param {Function} func The function to run before saving a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}. */ -ParseCloud.beforeSaveFile = function(handler) { +ParseCloud.beforeSaveFile = function (handler) { triggers.addFileTrigger( triggers.Types.beforeSaveFile, handler, @@ -399,7 +399,7 @@ ParseCloud.beforeSaveFile = function(handler) { * @name Parse.Cloud.afterSaveFile * @param {Function} func The function to run after saving a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}. */ -ParseCloud.afterSaveFile = function(handler) { +ParseCloud.afterSaveFile = function (handler) { triggers.addFileTrigger( triggers.Types.afterSaveFile, handler, @@ -422,11 +422,11 @@ ParseCloud.afterSaveFile = function(handler) { * @name Parse.Cloud.beforeDeleteFile * @param {Function} func The function to run before deleting a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}. */ -ParseCloud.beforeDeleteFile = function(handler) { +ParseCloud.beforeDeleteFile = function (handler) { triggers.addFileTrigger( triggers.Types.beforeDeleteFile, handler, - Parse.applicationId, + Parse.applicationId ); }; @@ -445,15 +445,33 @@ ParseCloud.beforeDeleteFile = function(handler) { * @name Parse.Cloud.afterDeleteFile * @param {Function} func The function to after before deleting a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}. */ -ParseCloud.afterDeleteFile = function(handler) { +ParseCloud.afterDeleteFile = function (handler) { triggers.addFileTrigger( triggers.Types.afterDeleteFile, handler, - Parse.applicationId, + Parse.applicationId + ); +}; + +ParseCloud.beforeConnect = function (handler) { + triggers.addConnectTrigger( + triggers.Types.beforeConnect, + handler, + Parse.applicationId + ); +}; + +ParseCloud.beforeSubscribe = function (parseClass, handler) { + var className = getClassName(parseClass); + triggers.addTrigger( + triggers.Types.beforeSubscribe, + className, + handler, + Parse.applicationId ); }; -ParseCloud.onLiveQueryEvent = function(handler) { +ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; diff --git a/src/triggers.js b/src/triggers.js index 998e6a3cf6..4764594053 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -16,16 +16,19 @@ export const Types = { afterSaveFile: 'afterSaveFile', beforeDeleteFile: 'beforeDeleteFile', afterDeleteFile: 'afterDeleteFile', + beforeConnect: 'beforeConnect', + beforeSubscribe: 'beforeSubscribe', }; const FileClassName = '@File'; +const ConnectClassName = '@Connect'; -const baseStore = function() { +const baseStore = function () { const Validators = {}; const Functions = {}; const Jobs = {}; const LiveQuery = []; - const Triggers = Object.keys(Types).reduce(function(base, key) { + const Triggers = Object.keys(Types).reduce(function (base, key) { base[key] = {}; return base; }, {}); @@ -132,6 +135,10 @@ export function addFileTrigger(type, handler, applicationId) { add(Category.Triggers, `${type}.${FileClassName}`, handler, applicationId); } +export function addConnectTrigger(type, handler, applicationId) { + add(Category.Triggers, `${type}.${ConnectClassName}`, handler, applicationId); +} + export function addLiveQueryEventHandler(handler, applicationId) { applicationId = applicationId || Parse.applicationId; _triggerStore[applicationId] = _triggerStore[applicationId] || baseStore(); @@ -147,7 +154,7 @@ export function removeTrigger(type, className, applicationId) { } export function _unregisterAll() { - Object.keys(_triggerStore).forEach(appId => delete _triggerStore[appId]); + Object.keys(_triggerStore).forEach((appId) => delete _triggerStore[appId]); } export function getTrigger(className, triggerType, applicationId) { @@ -180,7 +187,7 @@ export function getFunctionNames(applicationId) { {}; const functionNames = []; const extractFunctionNames = (namespace, store) => { - Object.keys(store).forEach(name => { + Object.keys(store).forEach((name) => { const value = store[name]; if (namespace) { name = `${namespace}.${name}`; @@ -233,10 +240,12 @@ export function getRequestObject( request.original = originalParseObject; } - if (triggerType === Types.beforeSave || + if ( + triggerType === Types.beforeSave || triggerType === Types.afterSave || triggerType === Types.beforeDelete || - triggerType === Types.afterDelete) { + triggerType === Types.afterDelete + ) { // Set a copy of the context on the request object. request.context = Object.assign({}, context); } @@ -300,12 +309,12 @@ export function getRequestQueryObject( // Any changes made to the object in a beforeSave will be included. export function getResponseObject(request, resolve, reject) { return { - success: function(response) { + success: function (response) { if (request.triggerName === Types.afterFind) { if (!response) { response = request.objects; } - response = response.map(object => { + response = response.map((object) => { return object.toJSON(); }); return resolve(response); @@ -335,7 +344,7 @@ export function getResponseObject(request, resolve, reject) { } return resolve(response); }, - error: function(error) { + error: function (error) { if (error instanceof Parse.Error) { reject(error); } else if (error instanceof Error) { @@ -416,10 +425,10 @@ export function maybeRunAfterFindTrigger( const request = getRequestObject(triggerType, auth, null, null, config); const { success, error } = getResponseObject( request, - object => { + (object) => { resolve(object); }, - error => { + (error) => { reject(error); } ); @@ -430,7 +439,7 @@ export function maybeRunAfterFindTrigger( JSON.stringify(objects), auth ); - request.objects = objects.map(object => { + request.objects = objects.map((object) => { //setting the class name to transform into parse object object.className = className; return Parse.Object.fromJSON(object); @@ -439,7 +448,7 @@ export function maybeRunAfterFindTrigger( .then(() => { const response = trigger(request); if (response && typeof response.then === 'function') { - return response.then(results => { + return response.then((results) => { if (!results) { throw new Parse.Error( Parse.Error.SCRIPT_FAILED, @@ -452,7 +461,7 @@ export function maybeRunAfterFindTrigger( return response; }) .then(success, error); - }).then(results => { + }).then((results) => { logTriggerAfterHook(triggerType, className, JSON.stringify(results), auth); return results; }); @@ -499,7 +508,7 @@ export function maybeRunQueryTrigger( return trigger(requestObject); }) .then( - result => { + (result) => { let queryResult = parseQuery; if (result && result instanceof Parse.Query) { queryResult = result; @@ -559,7 +568,7 @@ export function maybeRunQueryTrigger( restOptions, }; }, - err => { + (err) => { if (typeof err === 'string') { throw new Parse.Error(1, err); } else { @@ -585,7 +594,7 @@ export function maybeRunTrigger( if (!parseObject) { return Promise.resolve({}); } - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { var trigger = getTrigger( parseObject.className, triggerType, @@ -602,7 +611,7 @@ export function maybeRunTrigger( ); var { success, error } = getResponseObject( request, - object => { + (object) => { logTriggerSuccessBeforeHook( triggerType, parseObject.className, @@ -620,7 +629,7 @@ export function maybeRunTrigger( } resolve(object); }, - error => { + (error) => { logTriggerErrorBeforeHook( triggerType, parseObject.className, @@ -655,7 +664,7 @@ export function maybeRunTrigger( // beforeSave is expected to return null (nothing) if (triggerType === Types.beforeSave) { if (promise && typeof promise.then === 'function') { - return promise.then(response => { + return promise.then((response) => { // response.object may come from express routing before hook if (response && response.object) { return response; @@ -693,7 +702,7 @@ export function runLiveQueryEventHandlers( ) { return; } - _triggerStore[applicationId].LiveQuery.forEach(handler => handler(data)); + _triggerStore[applicationId].LiveQuery.forEach((handler) => handler(data)); } export function getRequestFileObject(triggerType, auth, fileObject, config) { @@ -721,7 +730,12 @@ export function getRequestFileObject(triggerType, auth, fileObject, config) { return request; } -export async function maybeRunFileTrigger(triggerType, fileObject, config, auth) { +export async function maybeRunFileTrigger( + triggerType, + fileObject, + config, + auth +) { const fileTrigger = getFileTrigger(triggerType, config.applicationId); if (typeof fileTrigger === 'function') { try { @@ -737,8 +751,8 @@ export async function maybeRunFileTrigger(triggerType, fileObject, config, auth) 'Parse.File', { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize }, result, - auth, - ) + auth + ); return result || fileObject; } catch (error) { logTriggerErrorBeforeHook( @@ -746,10 +760,88 @@ export async function maybeRunFileTrigger(triggerType, fileObject, config, auth) 'Parse.File', { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize }, auth, - error, + error ); throw error; } } return fileObject; } + +export function maybeRunConnectTrigger(triggerType, request) { + return new Promise((resolve, reject) => { + const trigger = getTrigger( + ConnectClassName, + triggerType, + Parse.applicationId + ); + if (!trigger) { + resolve(); + } + let userPromise = Promise.resolve(); + if (request.sessionToken) { + userPromise = userForSessionToken(request.sessionToken); + } + userPromise + .then((user) => { + if (user) { + request.user = user; + } + return trigger(request); + }) + .then( + () => { + resolve(); + }, + (err) => { + reject(err); + } + ); + }); +} + +export function maybeRunSubscribeTrigger(triggerType, className, request) { + return new Promise((resolve, reject) => { + const trigger = getTrigger(className, triggerType, Parse.applicationId); + if (!trigger) { + resolve(); + } + const parseQuery = new Parse.Query(className); + parseQuery.withJSON(request.query); + request.query = parseQuery; + let userPromise = Promise.resolve(); + if (request.sessionToken) { + userPromise = userForSessionToken(request.sessionToken); + } + userPromise + .then((user) => { + if (user) { + request.user = user; + } + return trigger(request); + }) + .then( + () => { + resolve(); + }, + (err) => { + reject(err); + } + ); + }); +} + +async function userForSessionToken(sessionToken) { + const q = new Parse.Query('_Session'); + q.equalTo('sessionToken', sessionToken); + const session = await q.first({ useMasterKey: true }); + if (!session) { + return; + } + const user = session.get('user'); + if (!user) { + return; + } + await user.fetch({ useMasterKey: true }); + return user; +} From 24cc49301a22d21e6a19cc5aff1bb7fc94d3a4bc Mon Sep 17 00:00:00 2001 From: dplewis Date: Tue, 14 Jul 2020 13:49:19 -0500 Subject: [PATCH 02/31] Cleanup and Documentation --- spec/ParseLiveQueryServer.spec.js | 132 +++++++-------- src/LiveQuery/ParseLiveQueryServer.js | 234 ++++++++++++-------------- src/cloud-code/Parse.Cloud.js | 46 +++++ src/triggers.js | 115 +++++-------- 4 files changed, 263 insertions(+), 264 deletions(-) diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 4b810a81e1..6c1b831a5d 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -232,7 +232,7 @@ describe('ParseLiveQueryServer', function () { classNames: ['Yolo'], }, }) - .then((parseServer) => { + .then(parseServer => { saveSpy = spyOn(parseServer.config.liveQueryController, 'onAfterSave'); deleteSpy = spyOn( parseServer.config.liveQueryController, @@ -247,7 +247,7 @@ describe('ParseLiveQueryServer', function () { const obj = new Parse.Object('Yolo'); return obj.save(); }) - .then((obj) => { + .then(obj => { return obj.destroy(); }) .then(() => { @@ -307,6 +307,52 @@ describe('ParseLiveQueryServer', function () { expect(client.pushConnect).toHaveBeenCalled(); }); + it('basic beforeConnect rejection', async () => { + Parse.Cloud.beforeConnect(function () { + throw new Error('You shall not pass!'); + }); + const parseLiveQueryServer = new ParseLiveQueryServer({}); + const parseWebSocket = { + clientId: -1, + }; + await parseLiveQueryServer._handleConnect(parseWebSocket, { + sessionToken: 'token', + }); + expect(parseLiveQueryServer.clients.size).toBe(0); + const Client = require('../lib/LiveQuery/Client').Client; + expect(Client.pushError).toHaveBeenCalled(); + }); + + it('basic beforeSubscribe rejection', async () => { + Parse.Cloud.beforeSubscribe('test', function () { + throw new Error('You shall not pass!'); + }); + const parseLiveQueryServer = new ParseLiveQueryServer({}); + const parseWebSocket = { + clientId: -1, + }; + await parseLiveQueryServer._handleConnect(parseWebSocket, { + sessionToken: 'token', + }); + const query = { + className: 'test', + where: { + key: 'value', + }, + fields: ['test'], + }; + const requestId = 2; + const request = { + query: query, + requestId: requestId, + sessionToken: 'sessionToken', + }; + await parseLiveQueryServer._handleSubscribe(parseWebSocket, request); + expect(parseLiveQueryServer.clients.size).toBe(1); + const Client = require('../lib/LiveQuery/Client').Client; + expect(Client.pushError).toHaveBeenCalled(); + }); + it('can handle subscribe command without clientId', async () => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const incompleteParseConn = {}; @@ -729,7 +775,7 @@ describe('ParseLiveQueryServer', function () { expect(client.pushDelete).not.toHaveBeenCalled(); }); - it('can handle object delete command which matches some subscriptions', async (done) => { + it('can handle object delete command which matches some subscriptions', async done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make deletedParseObject const parseObject = new Parse.Object(testClassName); @@ -773,7 +819,7 @@ describe('ParseLiveQueryServer', function () { parseLiveQueryServer._onAfterSave(message); }); - it('can handle object save command which does not match any subscription', async (done) => { + it('can handle object save command which does not match any subscription', async done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(); @@ -804,7 +850,7 @@ describe('ParseLiveQueryServer', function () { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle object enter command which matches some subscriptions', async (done) => { + it('can handle object enter command which matches some subscriptions', async done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(true); @@ -841,7 +887,7 @@ describe('ParseLiveQueryServer', function () { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle object update command which matches some subscriptions', async (done) => { + it('can handle object update command which matches some subscriptions', async done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(true); @@ -874,7 +920,7 @@ describe('ParseLiveQueryServer', function () { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle object leave command which matches some subscriptions', async (done) => { + it('can handle object leave command which matches some subscriptions', async done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(true); @@ -911,7 +957,7 @@ describe('ParseLiveQueryServer', function () { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle update command with original object', async (done) => { + it('can handle update command with original object', async done => { jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client'); const Client = require('../lib/LiveQuery/Client').Client; const parseLiveQueryServer = new ParseLiveQueryServer({}); @@ -961,7 +1007,7 @@ describe('ParseLiveQueryServer', function () { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle object create command which matches some subscriptions', async (done) => { + it('can handle object create command which matches some subscriptions', async done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message const message = generateMockMessage(); @@ -994,7 +1040,7 @@ describe('ParseLiveQueryServer', function () { }, jasmine.ASYNC_TEST_WAIT_TIME); }); - it('can handle create command with fields', async (done) => { + it('can handle create command with fields', async done => { jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client'); const Client = require('../lib/LiveQuery/Client').Client; const parseLiveQueryServer = new ParseLiveQueryServer({}); @@ -1500,7 +1546,7 @@ describe('ParseLiveQueryServer', function () { }); describe('class level permissions', () => { - it('matches CLP when find is closed', (done) => { + it('matches CLP when find is closed', done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1525,13 +1571,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then((isMatched) => { + .then(isMatched => { expect(isMatched).toBe(false); done(); }); }); - it('matches CLP when find is open', (done) => { + it('matches CLP when find is open', done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1556,13 +1602,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then((isMatched) => { + .then(isMatched => { expect(isMatched).toBe(true); done(); }); }); - it('matches CLP when find is restricted to userIds', (done) => { + it('matches CLP when find is restricted to userIds', done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1587,13 +1633,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then((isMatched) => { + .then(isMatched => { expect(isMatched).toBe(true); done(); }); }); - it('matches CLP when find is restricted to userIds', (done) => { + it('matches CLP when find is restricted to userIds', done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1618,7 +1664,7 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then((isMatched) => { + .then(isMatched => { expect(isMatched).toBe(false); done(); }); @@ -1955,7 +2001,7 @@ describe('LiveQueryController', () => { classNames: ['Yolo'], }, }) - .then((parseServer) => { + .then(parseServer => { saveSpy = spyOn( parseServer.config.liveQueryController, 'onAfterSave' @@ -1973,7 +2019,7 @@ describe('LiveQueryController', () => { const obj = new Parse.Object('Yolo'); return obj.save(); }) - .then((obj) => { + .then(obj => { return obj.destroy(); }) .then(() => { @@ -2053,49 +2099,3 @@ describe('LiveQueryController', () => { }); }); }); - -it('basic beforeConnect rejection', async () => { - Parse.Cloud.beforeConnect(function () { - throw new Error('You shall not pass!'); - }); - const parseLiveQueryServer = new ParseLiveQueryServer({}); - const parseWebSocket = { - clientId: -1, - }; - await parseLiveQueryServer._handleConnect(parseWebSocket, { - sessionToken: 'token', - }); - expect(parseLiveQueryServer.clients.size).toBe(0); - const Client = require('../lib/LiveQuery/Client').Client; - expect(Client.pushError).toHaveBeenCalled(); -}); - -it('basic beforeSubscribe rejection', async () => { - Parse.Cloud.beforeSubscribe('test', function () { - throw new Error('You shall not pass!'); - }); - const parseLiveQueryServer = new ParseLiveQueryServer({}); - const parseWebSocket = { - clientId: -1, - }; - await parseLiveQueryServer._handleConnect(parseWebSocket, { - sessionToken: 'token', - }); - const query = { - className: 'test', - where: { - key: 'value', - }, - fields: ['test'], - }; - const requestId = 2; - const request = { - query: query, - requestId: requestId, - sessionToken: 'sessionToken', - }; - await parseLiveQueryServer._handleSubscribe(parseWebSocket, request); - expect(parseLiveQueryServer.clients.size).toBe(0); - const Client = require('../lib/LiveQuery/Client').Client; - expect(Client.pushError).toHaveBeenCalled(); -}); diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 4f282d7861..405d473b1a 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -10,9 +10,11 @@ import { ParsePubSub } from './ParsePubSub'; import SchemaController from '../Controllers/SchemaController'; import _ from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { runLiveQueryEventHandlers } from '../triggers'; -import { maybeRunConnectTrigger } from '../triggers'; -import { maybeRunSubscribeTrigger } from '../triggers'; +import { + runLiveQueryEventHandlers, + maybeRunConnectTrigger, + maybeRunSubscribeTrigger, +} from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; import LRU from 'lru-cache'; @@ -576,23 +578,22 @@ class ParseLiveQueryServer { return false; } - _handleConnect(parseWebsocket: any, request: any): any { - return new Promise((resolve) => { - if (!this._validateKeys(request, this.keyPairs)) { - Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); - logger.error('Key in request is not valid'); - resolve(); - return; - } - const hasMasterKey = this._hasMasterKey(request, this.keyPairs); - const clientId = uuidv4(); - const client = new Client( - clientId, - parseWebsocket, - hasMasterKey, - request.sessionToken, - request.installationId - ); + async _handleConnect(parseWebsocket: any, request: any): any { + if (!this._validateKeys(request, this.keyPairs)) { + Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); + logger.error('Key in request is not valid'); + return; + } + const hasMasterKey = this._hasMasterKey(request, this.keyPairs); + const clientId = uuidv4(); + const client = new Client( + clientId, + parseWebsocket, + hasMasterKey, + request.sessionToken, + request.installationId + ); + try { const req = { client, event: 'connect', @@ -602,30 +603,24 @@ class ParseLiveQueryServer { useMasterKey: client.hasMasterKey, installationId: request.installationId, }; - maybeRunConnectTrigger('beforeConnect', req).then( - () => { - parseWebsocket.clientId = clientId; - this.clients.set(parseWebsocket.clientId, client); - logger.info(`Create new client: ${parseWebsocket.clientId}`); - client.pushConnect(); - runLiveQueryEventHandlers(req); - resolve(); - }, - (error) => { - Client.pushError( - parseWebsocket, - error.code || 101, - error.message || error, - false - ); - logger.error( - `Failed running beforeConnect for session ${req.sessionToken} with:\n Error: ` + - JSON.stringify(error) - ); - resolve(); - } + await maybeRunConnectTrigger('beforeConnect', req); + parseWebsocket.clientId = clientId; + this.clients.set(parseWebsocket.clientId, client); + logger.info(`Create new client: ${parseWebsocket.clientId}`); + client.pushConnect(); + runLiveQueryEventHandlers(req); + } catch (error) { + Client.pushError( + parseWebsocket, + error.code || 101, + error.message || error, + false ); - }); + logger.error( + `Failed running beforeConnect for session ${request.sessionToken} with:\n Error: ` + + JSON.stringify(error) + ); + } } _hasMasterKey(request: any, validKeyPairs: any): boolean { @@ -660,96 +655,85 @@ class ParseLiveQueryServer { return isValid; } - _handleSubscribe(parseWebsocket: any, request: any): any { + async _handleSubscribe(parseWebsocket: any, request: any): any { // If we can not find this client, return error to client - return new Promise((resolve) => { - if (!Object.prototype.hasOwnProperty.call(parseWebsocket, 'clientId')) { - Client.pushError( - parseWebsocket, - 2, - 'Can not find this client, make sure you connect to server before subscribing' - ); - logger.error( - 'Can not find this client, make sure you connect to server before subscribing' + if (!Object.prototype.hasOwnProperty.call(parseWebsocket, 'clientId')) { + Client.pushError( + parseWebsocket, + 2, + 'Can not find this client, make sure you connect to server before subscribing' + ); + logger.error( + 'Can not find this client, make sure you connect to server before subscribing' + ); + return; + } + const client = this.clients.get(parseWebsocket.clientId); + const className = request.query.className; + try { + await maybeRunSubscribeTrigger('beforeSubscribe', className, request); + + // Get subscription from subscriptions, create one if necessary + const subscriptionHash = queryHash(request.query); + // Add className to subscriptions if necessary + + if (!this.subscriptions.has(className)) { + this.subscriptions.set(className, new Map()); + } + const classSubscriptions = this.subscriptions.get(className); + let subscription; + if (classSubscriptions.has(subscriptionHash)) { + subscription = classSubscriptions.get(subscriptionHash); + } else { + subscription = new Subscription( + className, + request.query.where, + subscriptionHash ); - resolve(); - return; + classSubscriptions.set(subscriptionHash, subscription); } - const client = this.clients.get(parseWebsocket.clientId); - const className = request.query.className; - maybeRunSubscribeTrigger('beforeSubscribe', className, request).then( - () => { - // Get subscription from subscriptions, create one if necessary - const subscriptionHash = queryHash(request.query); - // Add className to subscriptions if necessary - - if (!this.subscriptions.has(className)) { - this.subscriptions.set(className, new Map()); - } - const classSubscriptions = this.subscriptions.get(className); - let subscription; - if (classSubscriptions.has(subscriptionHash)) { - subscription = classSubscriptions.get(subscriptionHash); - } else { - subscription = new Subscription( - className, - request.query.where, - subscriptionHash - ); - classSubscriptions.set(subscriptionHash, subscription); - } - // Add subscriptionInfo to client - const subscriptionInfo = { - subscription: subscription, - }; - // Add selected fields, sessionToken and installationId for this subscription if necessary - if (request.query.fields) { - subscriptionInfo.fields = request.query.fields; - } - if (request.sessionToken) { - subscriptionInfo.sessionToken = request.sessionToken; - } - client.addSubscriptionInfo(request.requestId, subscriptionInfo); + // Add subscriptionInfo to client + const subscriptionInfo = { + subscription: subscription, + }; + // Add selected fields, sessionToken and installationId for this subscription if necessary + if (request.query.fields) { + subscriptionInfo.fields = request.query.fields; + } + if (request.sessionToken) { + subscriptionInfo.sessionToken = request.sessionToken; + } + client.addSubscriptionInfo(request.requestId, subscriptionInfo); - // Add clientId to subscription - subscription.addClientSubscription( - parseWebsocket.clientId, - request.requestId - ); + // Add clientId to subscription + subscription.addClientSubscription( + parseWebsocket.clientId, + request.requestId + ); - client.pushSubscribe(request.requestId); + client.pushSubscribe(request.requestId); - logger.verbose( - `Create client ${parseWebsocket.clientId} new subscription: ${request.requestId}` - ); - logger.verbose('Current client number: %d', this.clients.size); - runLiveQueryEventHandlers({ - client, - event: 'subscribe', - clients: this.clients.size, - subscriptions: this.subscriptions.size, - sessionToken: request.sessionToken, - useMasterKey: client.hasMasterKey, - installationId: client.installationId, - }); - resolve(); - }, - (e) => { - Client.pushError( - parseWebsocket, - e.code || 101, - e.message || e, - false - ); - logger.error( - `Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` + - JSON.stringify(e) - ); - resolve(); - } + logger.verbose( + `Create client ${parseWebsocket.clientId} new subscription: ${request.requestId}` ); - }); + logger.verbose('Current client number: %d', this.clients.size); + runLiveQueryEventHandlers({ + client, + event: 'subscribe', + clients: this.clients.size, + subscriptions: this.subscriptions.size, + sessionToken: request.sessionToken, + useMasterKey: client.hasMasterKey, + installationId: client.installationId, + }); + } catch (e) { + Client.pushError(parseWebsocket, e.code || 101, e.message || e, false); + logger.error( + `Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` + + JSON.stringify(e) + ); + } } _handleUpdateSubscription(parseWebsocket: any, request: any): any { diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index cef71204ad..088c4dc3c1 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -453,6 +453,21 @@ ParseCloud.afterDeleteFile = function (handler) { ); }; +/** + * Registers a before live query server connect function. + * + * **Available in Cloud Code only.** + * + * ``` + * Parse.Cloud.beforeConnect(async (request) => { + * // code here + * }) + *``` + * + * @method beforeConnect + * @name Parse.Cloud.beforeConnect + * @param {Function} func The function to before connection is made. This function can be async and should take just one parameter, {@link Parse.Cloud.ConnectTriggerRequest}. + */ ParseCloud.beforeConnect = function (handler) { triggers.addConnectTrigger( triggers.Types.beforeConnect, @@ -461,6 +476,27 @@ ParseCloud.beforeConnect = function (handler) { ); }; +/** + * Registers a before live query subscription function. + * + * **Available in Cloud Code only.** + * + * If you want to use beforeSubscribe for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User}), you should pass the class itself and not the String for arg1. + * ``` + * Parse.Cloud.beforeSubscribe('MyCustomClass', (request) => { + * // code here + * }) + * + * Parse.Cloud.beforeSubscribe(Parse.User, (request) => { + * // code here + * }) + *``` + * + * @method beforeSubscribe + * @name Parse.Cloud.beforeSubscribe + * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before subscription function for. This can instead be a String that is the className of the subclass. + * @param {Function} func The function to run before a subscription. This function can be async and should take one parameter, a {@link Parse.Cloud.TriggerRequest}. + */ ParseCloud.beforeSubscribe = function (parseClass, handler) { var className = getClassName(parseClass); triggers.addTrigger( @@ -517,6 +553,16 @@ module.exports = ParseCloud; * @property {Object} log The current logger inside Parse Server. */ +/** + * @interface Parse.Cloud.ConnectTriggerRequest + * @property {String} installationId If set, the installationId triggering the request. + * @property {Boolean} useMasterKey If true, means the master key was used. + * @property {Parse.User} user If set, the user that made the request. + * @property {Integer} clients The number of clients connected. + * @property {Integer} subscriptions The number of subscriptions connected. + * @property {String} sessionToken If set, the session of the user that made the request. + */ + /** * @interface Parse.Cloud.BeforeFindRequest * @property {String} installationId If set, the installationId triggering the request. diff --git a/src/triggers.js b/src/triggers.js index 4764594053..96dcb65e47 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -154,7 +154,7 @@ export function removeTrigger(type, className, applicationId) { } export function _unregisterAll() { - Object.keys(_triggerStore).forEach((appId) => delete _triggerStore[appId]); + Object.keys(_triggerStore).forEach(appId => delete _triggerStore[appId]); } export function getTrigger(className, triggerType, applicationId) { @@ -187,7 +187,7 @@ export function getFunctionNames(applicationId) { {}; const functionNames = []; const extractFunctionNames = (namespace, store) => { - Object.keys(store).forEach((name) => { + Object.keys(store).forEach(name => { const value = store[name]; if (namespace) { name = `${namespace}.${name}`; @@ -314,7 +314,7 @@ export function getResponseObject(request, resolve, reject) { if (!response) { response = request.objects; } - response = response.map((object) => { + response = response.map(object => { return object.toJSON(); }); return resolve(response); @@ -425,10 +425,10 @@ export function maybeRunAfterFindTrigger( const request = getRequestObject(triggerType, auth, null, null, config); const { success, error } = getResponseObject( request, - (object) => { + object => { resolve(object); }, - (error) => { + error => { reject(error); } ); @@ -439,7 +439,7 @@ export function maybeRunAfterFindTrigger( JSON.stringify(objects), auth ); - request.objects = objects.map((object) => { + request.objects = objects.map(object => { //setting the class name to transform into parse object object.className = className; return Parse.Object.fromJSON(object); @@ -448,7 +448,7 @@ export function maybeRunAfterFindTrigger( .then(() => { const response = trigger(request); if (response && typeof response.then === 'function') { - return response.then((results) => { + return response.then(results => { if (!results) { throw new Parse.Error( Parse.Error.SCRIPT_FAILED, @@ -461,7 +461,7 @@ export function maybeRunAfterFindTrigger( return response; }) .then(success, error); - }).then((results) => { + }).then(results => { logTriggerAfterHook(triggerType, className, JSON.stringify(results), auth); return results; }); @@ -508,7 +508,7 @@ export function maybeRunQueryTrigger( return trigger(requestObject); }) .then( - (result) => { + result => { let queryResult = parseQuery; if (result && result instanceof Parse.Query) { queryResult = result; @@ -568,7 +568,7 @@ export function maybeRunQueryTrigger( restOptions, }; }, - (err) => { + err => { if (typeof err === 'string') { throw new Parse.Error(1, err); } else { @@ -611,7 +611,7 @@ export function maybeRunTrigger( ); var { success, error } = getResponseObject( request, - (object) => { + object => { logTriggerSuccessBeforeHook( triggerType, parseObject.className, @@ -629,7 +629,7 @@ export function maybeRunTrigger( } resolve(object); }, - (error) => { + error => { logTriggerErrorBeforeHook( triggerType, parseObject.className, @@ -664,7 +664,7 @@ export function maybeRunTrigger( // beforeSave is expected to return null (nothing) if (triggerType === Types.beforeSave) { if (promise && typeof promise.then === 'function') { - return promise.then((response) => { + return promise.then(response => { // response.object may come from express routing before hook if (response && response.object) { return response; @@ -702,7 +702,7 @@ export function runLiveQueryEventHandlers( ) { return; } - _triggerStore[applicationId].LiveQuery.forEach((handler) => handler(data)); + _triggerStore[applicationId].LiveQuery.forEach(handler => handler(data)); } export function getRequestFileObject(triggerType, auth, fileObject, config) { @@ -768,70 +768,39 @@ export async function maybeRunFileTrigger( return fileObject; } -export function maybeRunConnectTrigger(triggerType, request) { - return new Promise((resolve, reject) => { - const trigger = getTrigger( - ConnectClassName, - triggerType, - Parse.applicationId - ); - if (!trigger) { - resolve(); - } - let userPromise = Promise.resolve(); - if (request.sessionToken) { - userPromise = userForSessionToken(request.sessionToken); - } - userPromise - .then((user) => { - if (user) { - request.user = user; - } - return trigger(request); - }) - .then( - () => { - resolve(); - }, - (err) => { - reject(err); - } - ); - }); +export async function maybeRunConnectTrigger(triggerType, request) { + const trigger = getTrigger( + ConnectClassName, + triggerType, + Parse.applicationId + ); + if (!trigger) { + return; + } + request.user = await userForSessionToken(request.sessionToken); + return trigger(request); } -export function maybeRunSubscribeTrigger(triggerType, className, request) { - return new Promise((resolve, reject) => { - const trigger = getTrigger(className, triggerType, Parse.applicationId); - if (!trigger) { - resolve(); - } - const parseQuery = new Parse.Query(className); - parseQuery.withJSON(request.query); - request.query = parseQuery; - let userPromise = Promise.resolve(); - if (request.sessionToken) { - userPromise = userForSessionToken(request.sessionToken); - } - userPromise - .then((user) => { - if (user) { - request.user = user; - } - return trigger(request); - }) - .then( - () => { - resolve(); - }, - (err) => { - reject(err); - } - ); - }); +export async function maybeRunSubscribeTrigger( + triggerType, + className, + request +) { + const trigger = getTrigger(className, triggerType, Parse.applicationId); + if (!trigger) { + return; + } + const parseQuery = new Parse.Query(className); + parseQuery.withJSON(request.query); + request.query = parseQuery; + request.user = await userForSessionToken(request.sessionToken); + return trigger(request); } async function userForSessionToken(sessionToken) { + if (!sessionToken) { + return; + } const q = new Parse.Query('_Session'); q.equalTo('sessionToken', sessionToken); const session = await q.first({ useMasterKey: true }); From 718a871ca211bcdb1ad4acb7a958c2687c73086b Mon Sep 17 00:00:00 2001 From: dplewis Date: Wed, 15 Jul 2020 15:12:20 -0500 Subject: [PATCH 03/31] Add E2E tests --- package.json | 1 + spec/ParseLiveQuery.spec.js | 95 ++++++++++++++++++++++++++- src/LiveQuery/Client.js | 10 +-- src/LiveQuery/ParseLiveQueryServer.js | 8 ++- 4 files changed, 107 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 55115e3baa..4ba4c2c8d2 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=mmapv1} mongodb-runner stop", "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=mmapv1} TESTING=1 nyc jasmine", "start": "node ./bin/parse-server", + "prettier": "prettier --write {src,spec}/**/*.js", "prepare": "npm run build", "postinstall": "node -p 'require(\"./postinstall.js\")()'" }, diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index fa83588b9d..80eebaa733 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -1,6 +1,6 @@ 'use strict'; -describe('ParseLiveQuery', function() { +describe('ParseLiveQuery', function () { it('can subscribe to query', async done => { await reconfigureServer({ liveQuery: { @@ -24,6 +24,97 @@ describe('ParseLiveQuery', function() { await object.save(); }); + it('can handle beforeConnect / beforeSubscribe hooks', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.beforeSubscribe('TestObject', req => { + expect(req.op).toBe('subscribe'); + expect(req.requestId).toBe(1); + expect(req.query).toBeDefined(); + expect(req.user).toBeUndefined(); + }); + + Parse.Cloud.beforeConnect(req => { + expect(req.event).toBe('connect'); + expect(req.clients).toBe(0); + expect(req.subscriptions).toBe(0); + expect(req.useMasterKey).toBe(false); + expect(req.installationId).toBeDefined(); + expect(req.user).toBeUndefined(); + expect(req.sessionToken).toBeUndefined(); + expect(req.client).toBeDefined(); + }); + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', async object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can handle beforeConnect error', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.beforeConnect(() => { + throw new Error('You shall not pass!'); + }); + Parse.LiveQuery.on('error', error => { + expect(error).toBe('You shall not pass!'); + done(); + }); + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + await query.subscribe(); + }); + + it('can handle beforeSubscribe error', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.beforeSubscribe(TestObject, () => { + throw new Error('You shall not subscribe!'); + }); + Parse.LiveQuery.on('error', error => { + expect(error).toBe('You shall not subscribe!'); + }); + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('error', error => { + expect(error).toBe('You shall not subscribe!'); + done(); + }); + }); + it('handle invalid websocket payload length', async done => { await reconfigureServer({ liveQuery: { @@ -61,7 +152,7 @@ describe('ParseLiveQuery', function() { }, 1000); }); - afterEach(async function(done) { + afterEach(async function (done) { const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); client.close(); // Wait for live query client to disconnect diff --git a/src/LiveQuery/Client.js b/src/LiveQuery/Client.js index 26cd999834..253234b9bb 100644 --- a/src/LiveQuery/Client.js +++ b/src/LiveQuery/Client.js @@ -62,15 +62,17 @@ class Client { parseWebSocket: any, code: number, error: string, - reconnect: boolean = true + reconnect: boolean = true, + requestId: number | void = null ): void { Client.pushResponse( parseWebSocket, JSON.stringify({ op: 'error', - error: error, - code: code, - reconnect: reconnect, + error, + code, + reconnect, + requestId, }) ); } diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 405d473b1a..5d28367961 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -728,7 +728,13 @@ class ParseLiveQueryServer { installationId: client.installationId, }); } catch (e) { - Client.pushError(parseWebsocket, e.code || 101, e.message || e, false); + Client.pushError( + parseWebsocket, + e.code || 101, + e.message || e, + false, + request.requestId + ); logger.error( `Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` + JSON.stringify(e) From 97c08b2c570d86df8ef96ecf53b0e13beaa00416 Mon Sep 17 00:00:00 2001 From: dplewis Date: Thu, 16 Jul 2020 15:16:35 -0500 Subject: [PATCH 04/31] Bump parse to 2.15.0 --- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc58dd1a64..c94de248dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10028,31 +10028,31 @@ } }, "parse": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-2.14.0.tgz", - "integrity": "sha512-S4bbF80Aom/xDk4YNkzZG1xBHYbiFQGueJWyO4DpYlajfkEs3gp0oszFDnGadTARyCgoQGxNE4Qkege/QqNETA==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/parse/-/parse-2.15.0.tgz", + "integrity": "sha512-Aupg+qd6I4X5uTacpsxROg5GlhkVn2+qOHtyOhlGj/Woi75c5cPD8kn7qhhLKcVVpe2L+HoJ+yGkMdI8IjKBKA==", "requires": { - "@babel/runtime": "7.10.2", - "@babel/runtime-corejs3": "7.10.2", + "@babel/runtime": "7.10.3", + "@babel/runtime-corejs3": "7.10.3", "crypto-js": "4.0.0", "react-native-crypto-js": "1.0.0", - "uuid": "3.3.3", + "uuid": "3.4.0", "ws": "7.3.0", "xmlhttprequest": "1.8.0" }, "dependencies": { "@babel/runtime": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz", - "integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==", + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", + "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.2.tgz", - "integrity": "sha512-+a2M/u7r15o3dV1NEizr9bRi+KUVnrs/qYxF0Z06DAPx/4VCWaz1WA7EcbE+uqGgt39lp5akWGmHsTseIkHkHg==", + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz", + "integrity": "sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw==", "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" @@ -10064,9 +10064,9 @@ "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" }, "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" } } }, diff --git a/package.json b/package.json index 4ba4c2c8d2..2fa08872e1 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "lru-cache": "5.1.1", "mime": "2.4.6", "mongodb": "3.5.9", - "parse": "2.14.0", + "parse": "2.15.0", "pg-promise": "10.5.7", "pluralize": "8.0.0", "redis": "3.0.2", From 828c678a6995216b843a75f5b3c864aec063ba43 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 21:11:04 +1000 Subject: [PATCH 05/31] Create afterLiveQueryEvent --- spec/ParseLiveQuery.spec.js | 252 +++++++++++++++++++++++++- src/LiveQuery/ParseLiveQueryServer.js | 81 ++++++--- src/cloud-code/Parse.Cloud.js | 10 + src/triggers.js | 20 ++ 4 files changed, 333 insertions(+), 30 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 80eebaa733..899d9c85a3 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -16,7 +16,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', async object => { + subscription.on('update', object => { expect(object.get('foo')).toBe('bar'); done(); }); @@ -24,6 +24,254 @@ describe('ParseLiveQuery', function () { await object.save(); }); + it('expect afterEvent create', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Create'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + }); + + const query = new Parse.Query(TestObject); + const subscription = await query.subscribe(); + subscription.on('create', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent payload', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Update'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + done(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + await query.subscribe(); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('expect afterEvent enter', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Enter'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('enter', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent leave', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Leave'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBeUndefined(); + expect(req.original.get('foo')).toBe('bar'); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('leave', object => { + expect(object.get('foo')).toBeUndefined(); + done(); + }); + + object.unset('foo'); + await object.save(); + }); + + it('can handle afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.current; + current.set('foo', 'yolo'); + + const original = req.original; + original.set('yolo', 'foo'); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', (object, original) => { + expect(object.get('foo')).toBe('yolo'); + expect(original.get('yolo')).toBe('foo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can return different object in afterEvent', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', () => { + const object = new Parse.Object('Yolo'); + return object; + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.className).toBe('Yolo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can handle async afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const parent = new TestObject(); + const child = new TestObject(); + child.set('bar', 'foo'); + await Parse.Object.saveAll([parent, child]); + + Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { + const current = req.current; + const pointer = current.get('child'); + await pointer.fetch(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', parent.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.get('child')).toBeDefined(); + expect(object.get('child').get('bar')).toBe('foo'); + done(); + }); + parent.set('child', child); + await parent.save(); + }); + + it('can handle afterEvent throw', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.current; + const original = req.original; + + setTimeout(() => { + done(); + }, 2000); + + if (current.get('foo') != original.get('foo')) { + throw "Don't pass an update trigger, or message"; + } + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', () => { + fail('update should not have been called.'); + }); + subscription.on('error', () => { + fail('error should not have been called.'); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + it('can handle beforeConnect / beforeSubscribe hooks', async done => { await reconfigureServer({ liveQuery: { @@ -56,7 +304,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', async object => { + subscription.on('update', object => { expect(object.get('foo')).toBe('bar'); done(); }); diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 5d28367961..8123e21498 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -14,6 +14,7 @@ import { runLiveQueryEventHandlers, maybeRunConnectTrigger, maybeRunSubscribeTrigger, + maybeRunAfterEventTrigger, } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; @@ -193,7 +194,7 @@ class ParseLiveQueryServer { originalParseObject = message.originalParseObject.toJSON(); } const classLevelPermissions = message.classLevelPermissions; - const currentParseObject = message.currentParseObject.toJSON(); + let currentParseObject = message.currentParseObject.toJSON(); const className = currentParseObject.className; logger.verbose( 'ClassName: %s | ObjectId: %s', @@ -243,6 +244,7 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; + let res; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { @@ -267,35 +269,58 @@ class ParseLiveQueryServer { currentACLCheckingPromise, ]); }) - .then( - ([isOriginalMatched, isCurrentMatched]) => { - logger.verbose( - 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', - originalParseObject, - currentParseObject, - isOriginalSubscriptionMatched, - isCurrentSubscriptionMatched, - isOriginalMatched, - isCurrentMatched, - subscription.hash - ); - - // Decide event type - let type; - if (isOriginalMatched && isCurrentMatched) { - type = 'Update'; - } else if (isOriginalMatched && !isCurrentMatched) { - type = 'Leave'; - } else if (!isOriginalMatched && isCurrentMatched) { - if (originalParseObject) { - type = 'Enter'; - } else { - type = 'Create'; - } + .then(([isOriginalMatched, isCurrentMatched]) => { + logger.verbose( + 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', + originalParseObject, + currentParseObject, + isOriginalSubscriptionMatched, + isCurrentSubscriptionMatched, + isOriginalMatched, + isCurrentMatched, + subscription.hash + ); + + // Decide event type + let type; + if (isOriginalMatched && isCurrentMatched) { + type = 'Update'; + } else if (isOriginalMatched && !isCurrentMatched) { + type = 'Leave'; + } else if (!isOriginalMatched && isCurrentMatched) { + if (originalParseObject) { + type = 'Enter'; } else { - return null; + type = 'Create'; + } + } else { + return null; + } + message.event = type; + res = { + event: type, + sessionToken: client.sessionToken, + current: currentParseObject, + original: originalParseObject, + }; + return maybeRunAfterEventTrigger('afterEvent', className, res); + }) + .then( + newObj => { + if (res.current != currentParseObject) { + currentParseObject = res.current.toJSON(); + currentParseObject.className = className; } - const functionName = 'push' + type; + if (res.original != originalParseObject) { + originalParseObject = res.original.toJSON(); + originalParseObject.className = className; + } + if (newObj) { + currentParseObject = newObj.toJSON(); + currentParseObject.className = newObj.className; + } + + const functionName = 'push' + message.event; client[functionName]( requestId, currentParseObject, diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 088c4dc3c1..2ea210bccc 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -511,6 +511,16 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; +ParseCloud.afterLiveQueryEvent = function (parseClass, handler) { + const className = getClassName(parseClass); + triggers.addTrigger( + triggers.Types.afterEvent, + className, + handler, + Parse.applicationId + ); +}; + ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); }; diff --git a/src/triggers.js b/src/triggers.js index 96dcb65e47..0e614a5433 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -18,6 +18,7 @@ export const Types = { afterDeleteFile: 'afterDeleteFile', beforeConnect: 'beforeConnect', beforeSubscribe: 'beforeSubscribe', + afterEvent: 'afterEvent', }; const FileClassName = '@File'; @@ -781,6 +782,25 @@ export async function maybeRunConnectTrigger(triggerType, request) { return trigger(request); } +export async function maybeRunAfterEventTrigger( + triggerType, + className, + request +) { + const trigger = getTrigger(className, triggerType, Parse.applicationId); + if (!trigger) { + return; + } + if (request.current) { + request.current = Parse.Object.fromJSON(request.current); + } + if (request.original) { + request.original = Parse.Object.fromJSON(request.original); + } + request.user = await userForSessionToken(request.sessionToken); + return trigger(request); +} + export async function maybeRunSubscribeTrigger( triggerType, className, From 281926e4f4859ff7901ce4fd87d572cc29b8d3d7 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 21:23:50 +1000 Subject: [PATCH 06/31] Revert "Create afterLiveQueryEvent" This reverts commit 828c678a6995216b843a75f5b3c864aec063ba43. --- spec/ParseLiveQuery.spec.js | 252 +------------------------- src/LiveQuery/ParseLiveQueryServer.js | 81 +++------ src/cloud-code/Parse.Cloud.js | 10 - src/triggers.js | 20 -- 4 files changed, 30 insertions(+), 333 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 899d9c85a3..80eebaa733 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -16,7 +16,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', object => { + subscription.on('update', async object => { expect(object.get('foo')).toBe('bar'); done(); }); @@ -24,254 +24,6 @@ describe('ParseLiveQuery', function () { await object.save(); }); - it('expect afterEvent create', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('Create'); - expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); - }); - - const query = new Parse.Query(TestObject); - const subscription = await query.subscribe(); - subscription.on('create', object => { - expect(object.get('foo')).toBe('bar'); - done(); - }); - - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - }); - - it('expect afterEvent payload', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('Update'); - expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); - expect(req.original.get('foo')).toBeUndefined(); - done(); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - await query.subscribe(); - object.set({ foo: 'bar' }); - await object.save(); - }); - - it('expect afterEvent enter', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('Enter'); - expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); - expect(req.original.get('foo')).toBeUndefined(); - }); - - const object = new TestObject(); - await object.save(); - - const query = new Parse.Query(TestObject); - query.equalTo('foo', 'bar'); - const subscription = await query.subscribe(); - subscription.on('enter', object => { - expect(object.get('foo')).toBe('bar'); - done(); - }); - - object.set('foo', 'bar'); - await object.save(); - }); - - it('expect afterEvent leave', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('Leave'); - expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBeUndefined(); - expect(req.original.get('foo')).toBe('bar'); - }); - - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - - const query = new Parse.Query(TestObject); - query.equalTo('foo', 'bar'); - const subscription = await query.subscribe(); - subscription.on('leave', object => { - expect(object.get('foo')).toBeUndefined(); - done(); - }); - - object.unset('foo'); - await object.save(); - }); - - it('can handle afterEvent modification', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.current; - current.set('foo', 'yolo'); - - const original = req.original; - original.set('yolo', 'foo'); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', (object, original) => { - expect(object.get('foo')).toBe('yolo'); - expect(original.get('yolo')).toBe('foo'); - done(); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - - it('can return different object in afterEvent', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', () => { - const object = new Parse.Object('Yolo'); - return object; - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', object => { - expect(object.className).toBe('Yolo'); - done(); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - - it('can handle async afterEvent modification', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const parent = new TestObject(); - const child = new TestObject(); - child.set('bar', 'foo'); - await Parse.Object.saveAll([parent, child]); - - Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { - const current = req.current; - const pointer = current.get('child'); - await pointer.fetch(); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', parent.id); - const subscription = await query.subscribe(); - subscription.on('update', object => { - expect(object.get('child')).toBeDefined(); - expect(object.get('child').get('bar')).toBe('foo'); - done(); - }); - parent.set('child', child); - await parent.save(); - }); - - it('can handle afterEvent throw', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.current; - const original = req.original; - - setTimeout(() => { - done(); - }, 2000); - - if (current.get('foo') != original.get('foo')) { - throw "Don't pass an update trigger, or message"; - } - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', () => { - fail('update should not have been called.'); - }); - subscription.on('error', () => { - fail('error should not have been called.'); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - it('can handle beforeConnect / beforeSubscribe hooks', async done => { await reconfigureServer({ liveQuery: { @@ -304,7 +56,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', object => { + subscription.on('update', async object => { expect(object.get('foo')).toBe('bar'); done(); }); diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 8123e21498..5d28367961 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -14,7 +14,6 @@ import { runLiveQueryEventHandlers, maybeRunConnectTrigger, maybeRunSubscribeTrigger, - maybeRunAfterEventTrigger, } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; @@ -194,7 +193,7 @@ class ParseLiveQueryServer { originalParseObject = message.originalParseObject.toJSON(); } const classLevelPermissions = message.classLevelPermissions; - let currentParseObject = message.currentParseObject.toJSON(); + const currentParseObject = message.currentParseObject.toJSON(); const className = currentParseObject.className; logger.verbose( 'ClassName: %s | ObjectId: %s', @@ -244,7 +243,6 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; - let res; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { @@ -269,58 +267,35 @@ class ParseLiveQueryServer { currentACLCheckingPromise, ]); }) - .then(([isOriginalMatched, isCurrentMatched]) => { - logger.verbose( - 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', - originalParseObject, - currentParseObject, - isOriginalSubscriptionMatched, - isCurrentSubscriptionMatched, - isOriginalMatched, - isCurrentMatched, - subscription.hash - ); - - // Decide event type - let type; - if (isOriginalMatched && isCurrentMatched) { - type = 'Update'; - } else if (isOriginalMatched && !isCurrentMatched) { - type = 'Leave'; - } else if (!isOriginalMatched && isCurrentMatched) { - if (originalParseObject) { - type = 'Enter'; - } else { - type = 'Create'; - } - } else { - return null; - } - message.event = type; - res = { - event: type, - sessionToken: client.sessionToken, - current: currentParseObject, - original: originalParseObject, - }; - return maybeRunAfterEventTrigger('afterEvent', className, res); - }) .then( - newObj => { - if (res.current != currentParseObject) { - currentParseObject = res.current.toJSON(); - currentParseObject.className = className; - } - if (res.original != originalParseObject) { - originalParseObject = res.original.toJSON(); - originalParseObject.className = className; - } - if (newObj) { - currentParseObject = newObj.toJSON(); - currentParseObject.className = newObj.className; - } + ([isOriginalMatched, isCurrentMatched]) => { + logger.verbose( + 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', + originalParseObject, + currentParseObject, + isOriginalSubscriptionMatched, + isCurrentSubscriptionMatched, + isOriginalMatched, + isCurrentMatched, + subscription.hash + ); - const functionName = 'push' + message.event; + // Decide event type + let type; + if (isOriginalMatched && isCurrentMatched) { + type = 'Update'; + } else if (isOriginalMatched && !isCurrentMatched) { + type = 'Leave'; + } else if (!isOriginalMatched && isCurrentMatched) { + if (originalParseObject) { + type = 'Enter'; + } else { + type = 'Create'; + } + } else { + return null; + } + const functionName = 'push' + type; client[functionName]( requestId, currentParseObject, diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 2ea210bccc..088c4dc3c1 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -511,16 +511,6 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; -ParseCloud.afterLiveQueryEvent = function (parseClass, handler) { - const className = getClassName(parseClass); - triggers.addTrigger( - triggers.Types.afterEvent, - className, - handler, - Parse.applicationId - ); -}; - ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); }; diff --git a/src/triggers.js b/src/triggers.js index 0e614a5433..96dcb65e47 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -18,7 +18,6 @@ export const Types = { afterDeleteFile: 'afterDeleteFile', beforeConnect: 'beforeConnect', beforeSubscribe: 'beforeSubscribe', - afterEvent: 'afterEvent', }; const FileClassName = '@File'; @@ -782,25 +781,6 @@ export async function maybeRunConnectTrigger(triggerType, request) { return trigger(request); } -export async function maybeRunAfterEventTrigger( - triggerType, - className, - request -) { - const trigger = getTrigger(className, triggerType, Parse.applicationId); - if (!trigger) { - return; - } - if (request.current) { - request.current = Parse.Object.fromJSON(request.current); - } - if (request.original) { - request.original = Parse.Object.fromJSON(request.original); - } - request.user = await userForSessionToken(request.sessionToken); - return trigger(request); -} - export async function maybeRunSubscribeTrigger( triggerType, className, From 5fd63931c7cca03bb119d0a8432ef2dbbde054d9 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 21:36:03 +1000 Subject: [PATCH 07/31] afterLiveQueryEvent --- spec/ParseLiveQuery.spec.js | 251 +++++++++++++++++++++++++- src/LiveQuery/ParseLiveQueryServer.js | 80 +++++--- src/cloud-code/Parse.Cloud.js | 10 + src/triggers.js | 20 ++ 4 files changed, 331 insertions(+), 30 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 80eebaa733..de1d6b8bbd 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -16,10 +16,257 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', async object => { + subscription.on('update', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + it('expect afterEvent create', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Create'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + }); + + const query = new Parse.Query(TestObject); + const subscription = await query.subscribe(); + subscription.on('create', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent payload', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Update'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + done(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + await query.subscribe(); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('expect afterEvent enter', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Enter'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('enter', object => { expect(object.get('foo')).toBe('bar'); done(); }); + + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent leave', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Leave'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBeUndefined(); + expect(req.original.get('foo')).toBe('bar'); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('leave', object => { + expect(object.get('foo')).toBeUndefined(); + done(); + }); + + object.unset('foo'); + await object.save(); + }); + + it('can handle afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.current; + current.set('foo', 'yolo'); + + const original = req.original; + original.set('yolo', 'foo'); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', (object, original) => { + expect(object.get('foo')).toBe('yolo'); + expect(original.get('yolo')).toBe('foo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can return different object in afterEvent', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', () => { + const object = new Parse.Object('Yolo'); + return object; + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.className).toBe('Yolo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can handle async afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const parent = new TestObject(); + const child = new TestObject(); + child.set('bar', 'foo'); + await Parse.Object.saveAll([parent, child]); + + Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { + const current = req.current; + const pointer = current.get('child'); + await pointer.fetch(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', parent.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.get('child')).toBeDefined(); + expect(object.get('child').get('bar')).toBe('foo'); + done(); + }); + parent.set('child', child); + await parent.save(); + }); + + it('can handle afterEvent throw', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.current; + const original = req.original; + + setTimeout(() => { + done(); + }, 2000); + + if (current.get('foo') != original.get('foo')) { + throw "Don't pass an update trigger, or message"; + } + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', () => { + fail('update should not have been called.'); + }); + subscription.on('error', () => { + fail('error should not have been called.'); + }); object.set({ foo: 'bar' }); await object.save(); }); @@ -56,7 +303,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', async object => { + subscription.on('update', object => { expect(object.get('foo')).toBe('bar'); done(); }); diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 5d28367961..978fc09194 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -14,6 +14,7 @@ import { runLiveQueryEventHandlers, maybeRunConnectTrigger, maybeRunSubscribeTrigger, + maybeRunAfterEventTrigger, } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; @@ -193,7 +194,7 @@ class ParseLiveQueryServer { originalParseObject = message.originalParseObject.toJSON(); } const classLevelPermissions = message.classLevelPermissions; - const currentParseObject = message.currentParseObject.toJSON(); + let currentParseObject = message.currentParseObject.toJSON(); const className = currentParseObject.className; logger.verbose( 'ClassName: %s | ObjectId: %s', @@ -243,6 +244,7 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; + let res; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { @@ -267,35 +269,57 @@ class ParseLiveQueryServer { currentACLCheckingPromise, ]); }) - .then( - ([isOriginalMatched, isCurrentMatched]) => { - logger.verbose( - 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', - originalParseObject, - currentParseObject, - isOriginalSubscriptionMatched, - isCurrentSubscriptionMatched, - isOriginalMatched, - isCurrentMatched, - subscription.hash - ); - - // Decide event type - let type; - if (isOriginalMatched && isCurrentMatched) { - type = 'Update'; - } else if (isOriginalMatched && !isCurrentMatched) { - type = 'Leave'; - } else if (!isOriginalMatched && isCurrentMatched) { - if (originalParseObject) { - type = 'Enter'; - } else { - type = 'Create'; - } + .then(([isOriginalMatched, isCurrentMatched]) => { + logger.verbose( + 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', + originalParseObject, + currentParseObject, + isOriginalSubscriptionMatched, + isCurrentSubscriptionMatched, + isOriginalMatched, + isCurrentMatched, + subscription.hash + ); + + // Decide event type + let type; + if (isOriginalMatched && isCurrentMatched) { + type = 'Update'; + } else if (isOriginalMatched && !isCurrentMatched) { + type = 'Leave'; + } else if (!isOriginalMatched && isCurrentMatched) { + if (originalParseObject) { + type = 'Enter'; } else { - return null; + type = 'Create'; + } + } else { + return null; + } + message.event = type; + res = { + event: type, + sessionToken: client.sessionToken, + current: currentParseObject, + original: originalParseObject, + }; + return maybeRunAfterEventTrigger('afterEvent', className, res); + }) + .then( + newObj => { + if (res.current != currentParseObject) { + currentParseObject = res.current.toJSON(); + currentParseObject.className = className; + } + if (res.original != originalParseObject) { + originalParseObject = res.original.toJSON(); + originalParseObject.className = className; + } + if (newObj) { + currentParseObject = newObj.toJSON(); + currentParseObject.className = newObj.className; } - const functionName = 'push' + type; + const functionName = 'push' + message.event; client[functionName]( requestId, currentParseObject, diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 088c4dc3c1..2ea210bccc 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -511,6 +511,16 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; +ParseCloud.afterLiveQueryEvent = function (parseClass, handler) { + const className = getClassName(parseClass); + triggers.addTrigger( + triggers.Types.afterEvent, + className, + handler, + Parse.applicationId + ); +}; + ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); }; diff --git a/src/triggers.js b/src/triggers.js index 96dcb65e47..c231d742ee 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -18,6 +18,7 @@ export const Types = { afterDeleteFile: 'afterDeleteFile', beforeConnect: 'beforeConnect', beforeSubscribe: 'beforeSubscribe', + afterEvent: 'afterEvent', }; const FileClassName = '@File'; @@ -797,6 +798,25 @@ export async function maybeRunSubscribeTrigger( return trigger(request); } +export async function maybeRunAfterEventTrigger( + triggerType, + className, + request +) { + const trigger = getTrigger(className, triggerType, Parse.applicationId); + if (!trigger) { + return; + } + if (request.current) { + request.current = Parse.Object.fromJSON(request.current); + } + if (request.original) { + request.original = Parse.Object.fromJSON(request.original); + } + request.user = await userForSessionToken(request.sessionToken); + return trigger(request); +} + async function userForSessionToken(sessionToken) { if (!sessionToken) { return; From 55ac6cb2725232e2136013323d8bb6c5141c7a36 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 22:02:05 +1000 Subject: [PATCH 08/31] Add delete event --- spec/ParseLiveQuery.spec.js | 44 ++++++++++++++++++++++----- src/LiveQuery/ParseLiveQueryServer.js | 25 ++++++++++++--- src/triggers.js | 4 +-- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index de1d6b8bbd..460e490a7e 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -35,7 +35,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.afterLiveQueryEvent('TestObject', req => { expect(req.event).toBe('Create'); expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); + expect(req.object.get('foo')).toBe('bar'); }); const query = new Parse.Query(TestObject); @@ -65,7 +65,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.afterLiveQueryEvent('TestObject', req => { expect(req.event).toBe('Update'); expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); + expect(req.object.get('foo')).toBe('bar'); expect(req.original.get('foo')).toBeUndefined(); done(); }); @@ -89,7 +89,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.afterLiveQueryEvent('TestObject', req => { expect(req.event).toBe('Enter'); expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); + expect(req.object.get('foo')).toBe('bar'); expect(req.original.get('foo')).toBeUndefined(); }); @@ -120,7 +120,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.afterLiveQueryEvent('TestObject', req => { expect(req.event).toBe('Leave'); expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBeUndefined(); + expect(req.object.get('foo')).toBeUndefined(); expect(req.original.get('foo')).toBe('bar'); }); @@ -140,6 +140,36 @@ describe('ParseLiveQuery', function () { await object.save(); }); + it('expect afterEvent delete', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Delete'); + expect(req.user).toBeUndefined(); + req.object.set('foo', 'bar'); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + + const subscription = await query.subscribe(); + subscription.on('delete', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + await object.destroy(); + }); + it('can handle afterEvent modification', async done => { await reconfigureServer({ liveQuery: { @@ -153,7 +183,7 @@ describe('ParseLiveQuery', function () { await object.save(); Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.current; + const current = req.object; current.set('foo', 'yolo'); const original = req.original; @@ -215,7 +245,7 @@ describe('ParseLiveQuery', function () { await Parse.Object.saveAll([parent, child]); Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { - const current = req.current; + const current = req.object; const pointer = current.get('child'); await pointer.fetch(); }); @@ -246,7 +276,7 @@ describe('ParseLiveQuery', function () { await object.save(); Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.current; + const current = req.object; const original = req.original; setTimeout(() => { diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 978fc09194..ca45b9497b 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -125,7 +125,7 @@ class ParseLiveQueryServer { _onAfterDelete(message: any): void { logger.verbose(Parse.applicationId + 'afterDelete is triggered'); - const deletedParseObject = message.currentParseObject.toJSON(); + let deletedParseObject = message.currentParseObject.toJSON(); const classLevelPermissions = message.classLevelPermissions; const className = deletedParseObject.className; logger.verbose( @@ -159,6 +159,7 @@ class ParseLiveQueryServer { const acl = message.currentParseObject.getACL(); // Check CLP const op = this._getCLPOperation(subscription.query); + let res; this._matchesCLP( classLevelPermissions, message.currentParseObject, @@ -174,6 +175,22 @@ class ParseLiveQueryServer { if (!isMatched) { return null; } + res = { + event: 'Delete', + sessionToken: client.sessionToken, + object: deletedParseObject, + }; + return maybeRunAfterEventTrigger('afterEvent', className, res); + }) + .then(newObj => { + if (res.object != deletedParseObject) { + deletedParseObject = res.object.toJSON(); + deletedParseObject.className = className; + } + if (newObj) { + deletedParseObject = newObj.toJSON(); + deletedParseObject.className = newObj.className; + } client.pushDelete(requestId, deletedParseObject); }) .catch(error => { @@ -300,15 +317,15 @@ class ParseLiveQueryServer { res = { event: type, sessionToken: client.sessionToken, - current: currentParseObject, + object: currentParseObject, original: originalParseObject, }; return maybeRunAfterEventTrigger('afterEvent', className, res); }) .then( newObj => { - if (res.current != currentParseObject) { - currentParseObject = res.current.toJSON(); + if (res.object != currentParseObject) { + currentParseObject = res.object.toJSON(); currentParseObject.className = className; } if (res.original != originalParseObject) { diff --git a/src/triggers.js b/src/triggers.js index c231d742ee..75c26ff039 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -807,8 +807,8 @@ export async function maybeRunAfterEventTrigger( if (!trigger) { return; } - if (request.current) { - request.current = Parse.Object.fromJSON(request.current); + if (request.object) { + request.object = Parse.Object.fromJSON(request.object); } if (request.original) { request.original = Parse.Object.fromJSON(request.original); From 8a977d1aa4aefd5eb286bb8b87b4c679e172ae33 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 22:28:25 +1000 Subject: [PATCH 09/31] Fix failing tests --- src/LiveQuery/ParseLiveQueryServer.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index ca45b9497b..12af52a5a0 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -183,11 +183,12 @@ class ParseLiveQueryServer { return maybeRunAfterEventTrigger('afterEvent', className, res); }) .then(newObj => { - if (res.object != deletedParseObject) { + if (res.object && typeof res.object.toJSON === 'function') { deletedParseObject = res.object.toJSON(); deletedParseObject.className = className; } - if (newObj) { + + if (newObj && typeof newObj.toJSON === 'function') { deletedParseObject = newObj.toJSON(); deletedParseObject.className = newObj.className; } @@ -324,24 +325,29 @@ class ParseLiveQueryServer { }) .then( newObj => { - if (res.object != currentParseObject) { + if (res.object && typeof res.object.toJSON === 'function') { currentParseObject = res.object.toJSON(); currentParseObject.className = className; } - if (res.original != originalParseObject) { + + if (res.original && typeof res.original.toJSON === 'function') { originalParseObject = res.original.toJSON(); originalParseObject.className = className; } - if (newObj) { + + if (newObj && typeof newObj.toJSON === 'function') { currentParseObject = newObj.toJSON(); currentParseObject.className = newObj.className; } + const functionName = 'push' + message.event; - client[functionName]( - requestId, - currentParseObject, - originalParseObject - ); + if (client[functionName]) { + client[functionName]( + requestId, + currentParseObject, + originalParseObject + ); + } }, error => { logger.error('Matching ACL error : ', error); From 442e14b731bb6d469ca516757e509a4896172470 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 23:03:22 +1000 Subject: [PATCH 10/31] Fix lint --- spec/ParseLiveQuery.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 92a90b5064..460e490a7e 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -333,7 +333,6 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', object => { expect(object.get('foo')).toBe('bar'); done(); From 75f99a864b96cabfca9ab7bcf8c6d3379b7c81a2 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 23:22:08 +1000 Subject: [PATCH 11/31] Update ParseLiveQueryServer.js --- src/LiveQuery/ParseLiveQueryServer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 12af52a5a0..00f1d21c47 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -159,7 +159,7 @@ class ParseLiveQueryServer { const acl = message.currentParseObject.getACL(); // Check CLP const op = this._getCLPOperation(subscription.query); - let res; + let res = {}; this._matchesCLP( classLevelPermissions, message.currentParseObject, @@ -262,7 +262,7 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; - let res; + let res = {}; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { From a0ac8e0686d5e7fdfc166c4ea72a7016566ef9c0 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 20 Aug 2020 12:09:54 -0500 Subject: [PATCH 12/31] Remove Facebook AccountKit auth (#6870) * Remove Facebook AccountKit auth Account Kit services are no longer available. https://developers.facebook.com/blog/post/2019/09/09/account-kit-services-no-longer-available-starting-march/ https://www.sinch.com/blog/facebook-account-kit-is-closing-down-are-your-apps-covered/ * remove flaky test --- spec/AuthenticationAdapters.spec.js | 115 +-- spec/ParseGraphQLServer.spec.js | 1003 +++++++++++------------ src/Adapters/Auth/facebookaccountkit.js | 60 -- src/Adapters/Auth/index.js | 10 +- 4 files changed, 521 insertions(+), 667 deletions(-) delete mode 100644 src/Adapters/Auth/facebookaccountkit.js diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index a42017769b..2465ab1181 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -25,7 +25,6 @@ describe('AuthenticationProviders', function () { 'gcenter', 'gpgames', 'facebook', - 'facebookaccountkit', 'github', 'instagram', 'google', @@ -43,7 +42,7 @@ describe('AuthenticationProviders', function () { 'phantauth', 'microsoft', ].map(function (providerName) { - it('Should validate structure of ' + providerName, (done) => { + it('Should validate structure of ' + providerName, done => { const provider = require('../lib/Adapters/Auth/' + providerName); jequal(typeof provider.validateAuthData, 'function'); jequal(typeof provider.validateAppId, 'function'); @@ -71,7 +70,7 @@ describe('AuthenticationProviders', function () { return; } spyOn(require('../lib/Adapters/Auth/httpsRequest'), 'get').and.callFake( - (options) => { + options => { if ( options === 'https://oauth.vk.com/access_token?client_id=appId&client_secret=appSecret&v=5.59&grant_type=client_credentials' @@ -175,7 +174,7 @@ describe('AuthenticationProviders', function () { body: jsonBody, }; return request(options) - .then((response) => { + .then(response => { if (callback) { callback(null, response, response.data); } @@ -184,7 +183,7 @@ describe('AuthenticationProviders', function () { body: response.data, }; }) - .catch((error) => { + .catch(error => { if (callback) { callback(error); } @@ -192,7 +191,7 @@ describe('AuthenticationProviders', function () { }); }; - it('should create user with REST API', (done) => { + it('should create user with REST API', done => { createOAuthUser((error, response, body) => { expect(error).toBe(null); const b = body; @@ -203,7 +202,7 @@ describe('AuthenticationProviders', function () { const q = new Parse.Query('_Session'); q.equalTo('sessionToken', sessionToken); q.first({ useMasterKey: true }) - .then((res) => { + .then(res => { if (!res) { fail('should not fail fetching the session'); done(); @@ -219,7 +218,7 @@ describe('AuthenticationProviders', function () { }); }); - it('should only create a single user with REST API', (done) => { + it('should only create a single user with REST API', done => { let objectId; createOAuthUser((error, response, body) => { expect(error).toBe(null); @@ -239,9 +238,9 @@ describe('AuthenticationProviders', function () { }); }); - it("should fail to link if session token don't match user", (done) => { + it("should fail to link if session token don't match user", done => { Parse.User.signUp('myUser', 'password') - .then((user) => { + .then(user => { return createOAuthUserWithSessionToken(user.getSessionToken()); }) .then(() => { @@ -250,7 +249,7 @@ describe('AuthenticationProviders', function () { .then(() => { return Parse.User.signUp('myUser2', 'password'); }) - .then((user) => { + .then(user => { return createOAuthUserWithSessionToken(user.getSessionToken()); }) .then(fail, ({ data }) => { @@ -330,7 +329,7 @@ describe('AuthenticationProviders', function () { expect(typeof authAdapter.validateAppId).toBe('function'); } - it('properly loads custom adapter', (done) => { + it('properly loads custom adapter', done => { const validAuthData = { id: 'hello', token: 'world', @@ -370,14 +369,14 @@ describe('AuthenticationProviders', function () { expect(appIdSpy).not.toHaveBeenCalled(); done(); }, - (err) => { + err => { jfail(err); done(); } ); }); - it('properly loads custom adapter module object', (done) => { + it('properly loads custom adapter module object', done => { const authenticationHandler = authenticationLoader({ customAuthentication: path.resolve('./spec/support/CustomAuth.js'), }); @@ -394,14 +393,14 @@ describe('AuthenticationProviders', function () { () => { done(); }, - (err) => { + err => { jfail(err); done(); } ); }); - it('properly loads custom adapter module object (again)', (done) => { + it('properly loads custom adapter module object (again)', done => { const authenticationHandler = authenticationLoader({ customAuthentication: { module: path.resolve('./spec/support/CustomAuthFunction.js'), @@ -421,7 +420,7 @@ describe('AuthenticationProviders', function () { () => { done(); }, - (err) => { + err => { jfail(err); done(); } @@ -512,84 +511,6 @@ describe('AuthenticationProviders', function () { expect(appIds).toEqual(['a', 'b']); expect(providerOptions).toEqual(options.custom); }); - - it('properly loads Facebook accountkit adapter with options', () => { - const options = { - facebookaccountkit: { - appIds: ['a', 'b'], - appSecret: 'secret', - }, - }; - const { - adapter, - appIds, - providerOptions, - } = authenticationLoader.loadAuthAdapter('facebookaccountkit', options); - validateAuthenticationAdapter(adapter); - expect(appIds).toEqual(['a', 'b']); - expect(providerOptions.appSecret).toEqual('secret'); - }); - - it('should fail if Facebook appIds is not configured properly', (done) => { - const options = { - facebookaccountkit: { - appIds: [], - }, - }; - const { adapter, appIds } = authenticationLoader.loadAuthAdapter( - 'facebookaccountkit', - options - ); - adapter.validateAppId(appIds).then(done.fail, (err) => { - expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND); - done(); - }); - }); - - it('should fail to validate Facebook accountkit auth with bad token', (done) => { - const options = { - facebookaccountkit: { - appIds: ['a', 'b'], - }, - }; - const authData = { - id: 'fakeid', - access_token: 'badtoken', - }; - const { adapter } = authenticationLoader.loadAuthAdapter( - 'facebookaccountkit', - options - ); - adapter.validateAuthData(authData).then(done.fail, (err) => { - expect(err.code).toBe(190); - expect(err.type).toBe('OAuthException'); - done(); - }); - }); - - it('should fail to validate Facebook accountkit auth with bad token regardless of app secret proof', (done) => { - const options = { - facebookaccountkit: { - appIds: ['a', 'b'], - appSecret: 'badsecret', - }, - }; - const authData = { - id: 'fakeid', - access_token: 'badtoken', - }; - const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( - 'facebookaccountkit', - options - ); - adapter - .validateAuthData(authData, providerOptions) - .then(done.fail, (err) => { - expect(err.code).toBe(190); - expect(err.type).toBe('OAuthException'); - done(); - }); - }); }); describe('instagram auth adapter', () => { @@ -1653,13 +1574,13 @@ describe('microsoft graph auth adapter', () => { }); }); - it('should fail to validate Microsoft Graph auth with bad token', (done) => { + it('should fail to validate Microsoft Graph auth with bad token', done => { const authData = { id: 'fake-id', mail: 'fake@mail.com', access_token: 'very.long.bad.token', }; - microsoft.validateAuthData(authData).then(done.fail, (err) => { + microsoft.validateAuthData(authData).then(done.fail, err => { expect(err.code).toBe(101); expect(err.message).toBe( 'Microsoft Graph auth is invalid for this user.' diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index c1f23bc733..34cd8a8f11 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -2401,585 +2401,580 @@ describe('ParseGraphQLServer', () => { expect(nodeResult.data.node2.objectId).toBe(obj2.id); expect(nodeResult.data.node2.someField).toBe('some value 2'); }); - - it_only_db('mongo')( - 'Id inputs should work either with global id or object id', - async () => { - try { - await apolloClient.mutate({ - mutation: gql` - mutation CreateClasses { - secondaryObject: createClass( - input: { - name: "SecondaryObject" - schemaFields: { addStrings: [{ name: "someField" }] } - } - ) { - clientMutationId - } - primaryObject: createClass( - input: { - name: "PrimaryObject" - schemaFields: { - addStrings: [{ name: "stringField" }] - addArrays: [{ name: "arrayField" }] - addPointers: [ - { - name: "pointerField" - targetClassName: "SecondaryObject" - } - ] - addRelations: [ - { - name: "relationField" - targetClassName: "SecondaryObject" - } - ] - } + // TODO: (moumouls, davimacedo) Fix flaky test + xit('Id inputs should work either with global id or object id', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation CreateClasses { + secondaryObject: createClass( + input: { + name: "SecondaryObject" + schemaFields: { addStrings: [{ name: "someField" }] } + } + ) { + clientMutationId + } + primaryObject: createClass( + input: { + name: "PrimaryObject" + schemaFields: { + addStrings: [{ name: "stringField" }] + addArrays: [{ name: "arrayField" }] + addPointers: [ + { + name: "pointerField" + targetClassName: "SecondaryObject" + } + ] + addRelations: [ + { + name: "relationField" + targetClassName: "SecondaryObject" + } + ] } - ) { - clientMutationId } + ) { + clientMutationId } - `, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - await resetGraphQLCache(); + await resetGraphQLCache(); - const createSecondaryObjectsResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSecondaryObjects { - secondaryObject1: createSecondaryObject( - input: { fields: { someField: "some value 1" } } - ) { - secondaryObject { - id - objectId - someField - } + const createSecondaryObjectsResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSecondaryObjects { + secondaryObject1: createSecondaryObject( + input: { fields: { someField: "some value 1" } } + ) { + secondaryObject { + id + objectId + someField } - secondaryObject2: createSecondaryObject( - input: { fields: { someField: "some value 2" } } - ) { - secondaryObject { - id - someField - } + } + secondaryObject2: createSecondaryObject( + input: { fields: { someField: "some value 2" } } + ) { + secondaryObject { + id + someField } - secondaryObject3: createSecondaryObject( - input: { fields: { someField: "some value 3" } } - ) { - secondaryObject { - objectId - someField - } + } + secondaryObject3: createSecondaryObject( + input: { fields: { someField: "some value 3" } } + ) { + secondaryObject { + objectId + someField } - secondaryObject4: createSecondaryObject( - input: { fields: { someField: "some value 4" } } - ) { - secondaryObject { - id - objectId - } + } + secondaryObject4: createSecondaryObject( + input: { fields: { someField: "some value 4" } } + ) { + secondaryObject { + id + objectId } - secondaryObject5: createSecondaryObject( - input: { fields: { someField: "some value 5" } } - ) { - secondaryObject { - id - } + } + secondaryObject5: createSecondaryObject( + input: { fields: { someField: "some value 5" } } + ) { + secondaryObject { + id } - secondaryObject6: createSecondaryObject( - input: { fields: { someField: "some value 6" } } - ) { - secondaryObject { - objectId - } + } + secondaryObject6: createSecondaryObject( + input: { fields: { someField: "some value 6" } } + ) { + secondaryObject { + objectId } - secondaryObject7: createSecondaryObject( - input: { fields: { someField: "some value 7" } } - ) { - secondaryObject { - someField - } + } + secondaryObject7: createSecondaryObject( + input: { fields: { someField: "some value 7" } } + ) { + secondaryObject { + someField } } - `, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - const updateSecondaryObjectsResult = await apolloClient.mutate({ - mutation: gql` - mutation UpdateSecondaryObjects( - $id1: ID! - $id2: ID! - $id3: ID! - $id4: ID! - $id5: ID! - $id6: ID! - ) { - secondaryObject1: updateSecondaryObject( - input: { - id: $id1 - fields: { someField: "some value 11" } - } - ) { - secondaryObject { - id - objectId - someField - } + const updateSecondaryObjectsResult = await apolloClient.mutate({ + mutation: gql` + mutation UpdateSecondaryObjects( + $id1: ID! + $id2: ID! + $id3: ID! + $id4: ID! + $id5: ID! + $id6: ID! + ) { + secondaryObject1: updateSecondaryObject( + input: { + id: $id1 + fields: { someField: "some value 11" } } - secondaryObject2: updateSecondaryObject( - input: { - id: $id2 - fields: { someField: "some value 22" } - } - ) { - secondaryObject { - id - someField - } + ) { + secondaryObject { + id + objectId + someField } - secondaryObject3: updateSecondaryObject( - input: { - id: $id3 - fields: { someField: "some value 33" } - } - ) { - secondaryObject { - objectId - someField - } + } + secondaryObject2: updateSecondaryObject( + input: { + id: $id2 + fields: { someField: "some value 22" } } - secondaryObject4: updateSecondaryObject( - input: { - id: $id4 - fields: { someField: "some value 44" } - } - ) { - secondaryObject { - id - objectId - } + ) { + secondaryObject { + id + someField } - secondaryObject5: updateSecondaryObject( - input: { - id: $id5 - fields: { someField: "some value 55" } - } - ) { - secondaryObject { - id - } + } + secondaryObject3: updateSecondaryObject( + input: { + id: $id3 + fields: { someField: "some value 33" } } - secondaryObject6: updateSecondaryObject( - input: { - id: $id6 - fields: { someField: "some value 66" } - } - ) { - secondaryObject { - objectId - } + ) { + secondaryObject { + objectId + someField } } - `, - variables: { - id1: - createSecondaryObjectsResult.data.secondaryObject1 - .secondaryObject.id, - id2: - createSecondaryObjectsResult.data.secondaryObject2 - .secondaryObject.id, - id3: - createSecondaryObjectsResult.data.secondaryObject3 - .secondaryObject.objectId, - id4: - createSecondaryObjectsResult.data.secondaryObject4 - .secondaryObject.objectId, - id5: - createSecondaryObjectsResult.data.secondaryObject5 - .secondaryObject.id, - id6: - createSecondaryObjectsResult.data.secondaryObject6 - .secondaryObject.objectId, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, - }, - }); - - const deleteSecondaryObjectsResult = await apolloClient.mutate({ - mutation: gql` - mutation DeleteSecondaryObjects( - $id1: ID! - $id3: ID! - $id5: ID! - $id6: ID! + secondaryObject4: updateSecondaryObject( + input: { + id: $id4 + fields: { someField: "some value 44" } + } ) { - secondaryObject1: deleteSecondaryObject( - input: { id: $id1 } - ) { - secondaryObject { - id - objectId - someField - } + secondaryObject { + id + objectId } - secondaryObject3: deleteSecondaryObject( - input: { id: $id3 } - ) { - secondaryObject { - objectId - someField - } + } + secondaryObject5: updateSecondaryObject( + input: { + id: $id5 + fields: { someField: "some value 55" } } - secondaryObject5: deleteSecondaryObject( - input: { id: $id5 } - ) { - secondaryObject { - id - } + ) { + secondaryObject { + id } - secondaryObject6: deleteSecondaryObject( - input: { id: $id6 } - ) { - secondaryObject { - objectId - } + } + secondaryObject6: updateSecondaryObject( + input: { + id: $id6 + fields: { someField: "some value 66" } + } + ) { + secondaryObject { + objectId } } - `, - variables: { - id1: - updateSecondaryObjectsResult.data.secondaryObject1 - .secondaryObject.id, - id3: - updateSecondaryObjectsResult.data.secondaryObject3 - .secondaryObject.objectId, - id5: - updateSecondaryObjectsResult.data.secondaryObject5 - .secondaryObject.id, - id6: - updateSecondaryObjectsResult.data.secondaryObject6 - .secondaryObject.objectId, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + variables: { + id1: + createSecondaryObjectsResult.data.secondaryObject1 + .secondaryObject.id, + id2: + createSecondaryObjectsResult.data.secondaryObject2 + .secondaryObject.id, + id3: + createSecondaryObjectsResult.data.secondaryObject3 + .secondaryObject.objectId, + id4: + createSecondaryObjectsResult.data.secondaryObject4 + .secondaryObject.objectId, + id5: + createSecondaryObjectsResult.data.secondaryObject5 + .secondaryObject.id, + id6: + createSecondaryObjectsResult.data.secondaryObject6 + .secondaryObject.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - const getSecondaryObjectsResult = await apolloClient.query({ - query: gql` - query GetSecondaryObjects($id2: ID!, $id4: ID!) { - secondaryObject2: secondaryObject(id: $id2) { + const deleteSecondaryObjectsResult = await apolloClient.mutate({ + mutation: gql` + mutation DeleteSecondaryObjects( + $id1: ID! + $id3: ID! + $id5: ID! + $id6: ID! + ) { + secondaryObject1: deleteSecondaryObject( + input: { id: $id1 } + ) { + secondaryObject { id objectId someField } - secondaryObject4: secondaryObject(id: $id4) { + } + secondaryObject3: deleteSecondaryObject( + input: { id: $id3 } + ) { + secondaryObject { objectId someField } } - `, - variables: { - id2: - updateSecondaryObjectsResult.data.secondaryObject2 - .secondaryObject.id, - id4: - updateSecondaryObjectsResult.data.secondaryObject4 - .secondaryObject.objectId, + secondaryObject5: deleteSecondaryObject( + input: { id: $id5 } + ) { + secondaryObject { + id + } + } + secondaryObject6: deleteSecondaryObject( + input: { id: $id6 } + ) { + secondaryObject { + objectId + } + } + } + `, + variables: { + id1: + updateSecondaryObjectsResult.data.secondaryObject1 + .secondaryObject.id, + id3: + updateSecondaryObjectsResult.data.secondaryObject3 + .secondaryObject.objectId, + id5: + updateSecondaryObjectsResult.data.secondaryObject5 + .secondaryObject.id, + id6: + updateSecondaryObjectsResult.data.secondaryObject6 + .secondaryObject.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + }, + }); + + const getSecondaryObjectsResult = await apolloClient.query({ + query: gql` + query GetSecondaryObjects($id2: ID!, $id4: ID!) { + secondaryObject2: secondaryObject(id: $id2) { + id + objectId + someField + } + secondaryObject4: secondaryObject(id: $id4) { + objectId + someField + } + } + `, + variables: { + id2: + updateSecondaryObjectsResult.data.secondaryObject2 + .secondaryObject.id, + id4: + updateSecondaryObjectsResult.data.secondaryObject4 + .secondaryObject.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - const findSecondaryObjectsResult = await apolloClient.query({ - query: gql` - query FindSecondaryObjects( - $id1: ID! - $id2: ID! - $id3: ID! - $id4: ID! - $id5: ID! - $id6: ID! - ) { - secondaryObjects( - where: { - AND: [ - { - OR: [ - { id: { equalTo: $id2 } } - { - AND: [ - { id: { equalTo: $id4 } } - { objectId: { equalTo: $id4 } } - ] - } - ] - } - { id: { notEqualTo: $id1 } } - { id: { notEqualTo: $id3 } } - { objectId: { notEqualTo: $id2 } } - { objectId: { notIn: [$id5, $id6] } } - { id: { in: [$id2, $id4] } } - ] - } - order: [id_ASC, objectId_ASC] - ) { - edges { - node { - id - objectId - someField + const findSecondaryObjectsResult = await apolloClient.query({ + query: gql` + query FindSecondaryObjects( + $id1: ID! + $id2: ID! + $id3: ID! + $id4: ID! + $id5: ID! + $id6: ID! + ) { + secondaryObjects( + where: { + AND: [ + { + OR: [ + { id: { equalTo: $id2 } } + { + AND: [ + { id: { equalTo: $id4 } } + { objectId: { equalTo: $id4 } } + ] + } + ] } + { id: { notEqualTo: $id1 } } + { id: { notEqualTo: $id3 } } + { objectId: { notEqualTo: $id2 } } + { objectId: { notIn: [$id5, $id6] } } + { id: { in: [$id2, $id4] } } + ] + } + order: [id_ASC, objectId_ASC] + ) { + edges { + node { + id + objectId + someField } - count } + count } - `, - variables: { - id1: - deleteSecondaryObjectsResult.data.secondaryObject1 - .secondaryObject.objectId, - id2: getSecondaryObjectsResult.data.secondaryObject2.id, - id3: - deleteSecondaryObjectsResult.data.secondaryObject3 - .secondaryObject.objectId, - id4: - getSecondaryObjectsResult.data.secondaryObject4.objectId, - id5: - deleteSecondaryObjectsResult.data.secondaryObject5 - .secondaryObject.id, - id6: - deleteSecondaryObjectsResult.data.secondaryObject6 - .secondaryObject.objectId, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + variables: { + id1: + deleteSecondaryObjectsResult.data.secondaryObject1 + .secondaryObject.objectId, + id2: getSecondaryObjectsResult.data.secondaryObject2.id, + id3: + deleteSecondaryObjectsResult.data.secondaryObject3 + .secondaryObject.objectId, + id4: getSecondaryObjectsResult.data.secondaryObject4.objectId, + id5: + deleteSecondaryObjectsResult.data.secondaryObject5 + .secondaryObject.id, + id6: + deleteSecondaryObjectsResult.data.secondaryObject6 + .secondaryObject.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - expect( - findSecondaryObjectsResult.data.secondaryObjects.count - ).toEqual(2); - expect( - findSecondaryObjectsResult.data.secondaryObjects.edges - .map(value => value.node.someField) - .sort() - ).toEqual(['some value 22', 'some value 44']); - expect( - findSecondaryObjectsResult.data.secondaryObjects.edges[0].node - .id - ).toBeLessThan( - findSecondaryObjectsResult.data.secondaryObjects.edges[1].node - .id - ); - expect( - findSecondaryObjectsResult.data.secondaryObjects.edges[0].node - .objectId - ).toBeLessThan( - findSecondaryObjectsResult.data.secondaryObjects.edges[1].node - .objectId - ); - - const createPrimaryObjectResult = await apolloClient.mutate({ - mutation: gql` - mutation CreatePrimaryObject( - $pointer: Any - $secondaryObject2: ID! - $secondaryObject4: ID! - ) { - createPrimaryObject( - input: { - fields: { - stringField: "some value" - arrayField: [1, "abc", $pointer] - pointerField: { link: $secondaryObject2 } - relationField: { - add: [$secondaryObject2, $secondaryObject4] - } + expect( + findSecondaryObjectsResult.data.secondaryObjects.count + ).toEqual(2); + expect( + findSecondaryObjectsResult.data.secondaryObjects.edges + .map(value => value.node.someField) + .sort() + ).toEqual(['some value 22', 'some value 44']); + expect( + findSecondaryObjectsResult.data.secondaryObjects.edges[0].node + .id + ).toBeLessThan( + findSecondaryObjectsResult.data.secondaryObjects.edges[1].node + .id + ); + expect( + findSecondaryObjectsResult.data.secondaryObjects.edges[0].node + .objectId + ).toBeLessThan( + findSecondaryObjectsResult.data.secondaryObjects.edges[1].node + .objectId + ); + + const createPrimaryObjectResult = await apolloClient.mutate({ + mutation: gql` + mutation CreatePrimaryObject( + $pointer: Any + $secondaryObject2: ID! + $secondaryObject4: ID! + ) { + createPrimaryObject( + input: { + fields: { + stringField: "some value" + arrayField: [1, "abc", $pointer] + pointerField: { link: $secondaryObject2 } + relationField: { + add: [$secondaryObject2, $secondaryObject4] } } - ) { - primaryObject { - id - stringField - arrayField { - ... on Element { - value - } - ... on SecondaryObject { - someField - } + } + ) { + primaryObject { + id + stringField + arrayField { + ... on Element { + value } - pointerField { - id - objectId + ... on SecondaryObject { someField } - relationField { - edges { - node { - id - objectId - someField - } + } + pointerField { + id + objectId + someField + } + relationField { + edges { + node { + id + objectId + someField } } } } } - `, - variables: { - pointer: { - __type: 'Pointer', - className: 'SecondaryObject', - objectId: - getSecondaryObjectsResult.data.secondaryObject4 - .objectId, - }, - secondaryObject2: - getSecondaryObjectsResult.data.secondaryObject2.id, - secondaryObject4: + } + `, + variables: { + pointer: { + __type: 'Pointer', + className: 'SecondaryObject', + objectId: getSecondaryObjectsResult.data.secondaryObject4.objectId, }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + secondaryObject2: + getSecondaryObjectsResult.data.secondaryObject2.id, + secondaryObject4: + getSecondaryObjectsResult.data.secondaryObject4.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - const updatePrimaryObjectResult = await apolloClient.mutate({ - mutation: gql` - mutation UpdatePrimaryObject( - $id: ID! - $secondaryObject2: ID! - $secondaryObject4: ID! - ) { - updatePrimaryObject( - input: { - id: $id - fields: { - pointerField: { link: $secondaryObject4 } - relationField: { - remove: [$secondaryObject2, $secondaryObject4] - } + const updatePrimaryObjectResult = await apolloClient.mutate({ + mutation: gql` + mutation UpdatePrimaryObject( + $id: ID! + $secondaryObject2: ID! + $secondaryObject4: ID! + ) { + updatePrimaryObject( + input: { + id: $id + fields: { + pointerField: { link: $secondaryObject4 } + relationField: { + remove: [$secondaryObject2, $secondaryObject4] } } - ) { - primaryObject { - id - stringField - arrayField { - ... on Element { - value - } - ... on SecondaryObject { - someField - } + } + ) { + primaryObject { + id + stringField + arrayField { + ... on Element { + value } - pointerField { - id - objectId + ... on SecondaryObject { someField } - relationField { - edges { - node { - id - objectId - someField - } + } + pointerField { + id + objectId + someField + } + relationField { + edges { + node { + id + objectId + someField } } } } } - `, - variables: { - id: - createPrimaryObjectResult.data.createPrimaryObject - .primaryObject.id, - secondaryObject2: - getSecondaryObjectsResult.data.secondaryObject2.id, - secondaryObject4: - getSecondaryObjectsResult.data.secondaryObject4.objectId, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + variables: { + id: + createPrimaryObjectResult.data.createPrimaryObject + .primaryObject.id, + secondaryObject2: + getSecondaryObjectsResult.data.secondaryObject2.id, + secondaryObject4: + getSecondaryObjectsResult.data.secondaryObject4.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - expect( - createPrimaryObjectResult.data.createPrimaryObject - .primaryObject.stringField - ).toEqual('some value'); - expect( - createPrimaryObjectResult.data.createPrimaryObject - .primaryObject.arrayField - ).toEqual([ - { __typename: 'Element', value: 1 }, - { __typename: 'Element', value: 'abc' }, - { __typename: 'SecondaryObject', someField: 'some value 44' }, - ]); - expect( - createPrimaryObjectResult.data.createPrimaryObject - .primaryObject.pointerField.someField - ).toEqual('some value 22'); - expect( - createPrimaryObjectResult.data.createPrimaryObject.primaryObject.relationField.edges - .map(value => value.node.someField) - .sort() - ).toEqual(['some value 22', 'some value 44']); - expect( - updatePrimaryObjectResult.data.updatePrimaryObject - .primaryObject.stringField - ).toEqual('some value'); - expect( - updatePrimaryObjectResult.data.updatePrimaryObject - .primaryObject.arrayField - ).toEqual([ - { __typename: 'Element', value: 1 }, - { __typename: 'Element', value: 'abc' }, - { __typename: 'SecondaryObject', someField: 'some value 44' }, - ]); - expect( - updatePrimaryObjectResult.data.updatePrimaryObject - .primaryObject.pointerField.someField - ).toEqual('some value 44'); - expect( - updatePrimaryObjectResult.data.updatePrimaryObject - .primaryObject.relationField.edges - ).toEqual([]); - } catch (e) { - handleError(e); - } + expect( + createPrimaryObjectResult.data.createPrimaryObject.primaryObject + .stringField + ).toEqual('some value'); + expect( + createPrimaryObjectResult.data.createPrimaryObject.primaryObject + .arrayField + ).toEqual([ + { __typename: 'Element', value: 1 }, + { __typename: 'Element', value: 'abc' }, + { __typename: 'SecondaryObject', someField: 'some value 44' }, + ]); + expect( + createPrimaryObjectResult.data.createPrimaryObject.primaryObject + .pointerField.someField + ).toEqual('some value 22'); + expect( + createPrimaryObjectResult.data.createPrimaryObject.primaryObject.relationField.edges + .map(value => value.node.someField) + .sort() + ).toEqual(['some value 22', 'some value 44']); + expect( + updatePrimaryObjectResult.data.updatePrimaryObject.primaryObject + .stringField + ).toEqual('some value'); + expect( + updatePrimaryObjectResult.data.updatePrimaryObject.primaryObject + .arrayField + ).toEqual([ + { __typename: 'Element', value: 1 }, + { __typename: 'Element', value: 'abc' }, + { __typename: 'SecondaryObject', someField: 'some value 44' }, + ]); + expect( + updatePrimaryObjectResult.data.updatePrimaryObject.primaryObject + .pointerField.someField + ).toEqual('some value 44'); + expect( + updatePrimaryObjectResult.data.updatePrimaryObject.primaryObject + .relationField.edges + ).toEqual([]); + } catch (e) { + handleError(e); } - ); + }); }); }); diff --git a/src/Adapters/Auth/facebookaccountkit.js b/src/Adapters/Auth/facebookaccountkit.js deleted file mode 100644 index a650d23840..0000000000 --- a/src/Adapters/Auth/facebookaccountkit.js +++ /dev/null @@ -1,60 +0,0 @@ -const crypto = require('crypto'); -const httpsRequest = require('./httpsRequest'); -const Parse = require('parse/node').Parse; - -const graphRequest = path => { - return httpsRequest.get(`https://graph.accountkit.com/v1.1/${path}`); -}; - -function getRequestPath(authData, options) { - const access_token = authData.access_token, - appSecret = options && options.appSecret; - if (appSecret) { - const appsecret_proof = crypto - .createHmac('sha256', appSecret) - .update(access_token) - .digest('hex'); - return `me?access_token=${access_token}&appsecret_proof=${appsecret_proof}`; - } - return `me?access_token=${access_token}`; -} - -function validateAppId(appIds, authData, options) { - if (!appIds.length) { - return Promise.reject( - new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, - 'Facebook app id for Account Kit is not configured.' - ) - ); - } - return graphRequest(getRequestPath(authData, options)).then(data => { - if (data && data.application && appIds.indexOf(data.application.id) != -1) { - return; - } - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, - 'Facebook app id for Account Kit is invalid for this user.' - ); - }); -} - -function validateAuthData(authData, options) { - return graphRequest(getRequestPath(authData, options)).then(data => { - if (data && data.error) { - throw data.error; - } - if (data && data.id == authData.id) { - return; - } - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, - 'Facebook Account Kit auth is invalid for this user.' - ); - }); -} - -module.exports = { - validateAppId, - validateAuthData, -}; diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index e5e8c955bd..296013ce9a 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -4,7 +4,6 @@ const apple = require('./apple'); const gcenter = require('./gcenter'); const gpgames = require('./gpgames'); const facebook = require('./facebook'); -const facebookaccountkit = require('./facebookaccountkit'); const instagram = require('./instagram'); const linkedin = require('./linkedin'); const meetup = require('./meetup'); @@ -39,7 +38,6 @@ const providers = { gcenter, gpgames, facebook, - facebookaccountkit, instagram, linkedin, meetup, @@ -62,7 +60,7 @@ const providers = { }; function authDataValidator(adapter, appIds, options) { - return function(authData) { + return function (authData) { return adapter.validateAuthData(authData, options).then(() => { if (appIds) { return adapter.validateAppId(appIds, authData, options); @@ -117,13 +115,13 @@ function loadAuthAdapter(provider, authOptions) { return { adapter, appIds, providerOptions }; } -module.exports = function(authOptions = {}, enableAnonymousUsers = true) { +module.exports = function (authOptions = {}, enableAnonymousUsers = true) { let _enableAnonymousUsers = enableAnonymousUsers; - const setEnableAnonymousUsers = function(enable) { + const setEnableAnonymousUsers = function (enable) { _enableAnonymousUsers = enable; }; // To handle the test cases on configuration - const getValidatorForProvider = function(provider) { + const getValidatorForProvider = function (provider) { if (provider === 'anonymous' && !_enableAnonymousUsers) { return; } From dd419e8e502b0f58e75a1218cd2d73cccca28689 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 20 Aug 2020 20:29:32 +0300 Subject: [PATCH 13/31] fix: upgrade uuid from 8.2.0 to 8.3.0 (#6865) Snyk has created this PR to upgrade uuid from 8.2.0 to 8.3.0. See this package in npm: https://www.npmjs.com/package/uuid See this project in Snyk: https://app.snyk.io/org/acinader/project/8c1a9edb-c8f5-4dc1-b221-4d6030a323eb?utm_source=github&utm_medium=upgrade-pr Co-authored-by: Diamond Lewis --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9220cf3ae..5920b1b2cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12183,9 +12183,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", - "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" }, "v8-compile-cache": { "version": "2.1.0", diff --git a/package.json b/package.json index 3b4182e9f6..578d502b39 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "semver": "7.3.2", "subscriptions-transport-ws": "0.9.17", "tv4": "1.3.0", - "uuid": "8.2.0", + "uuid": "8.3.0", "winston": "3.2.1", "winston-daily-rotate-file": "4.5.0", "ws": "7.3.1" From 603cbe563a1d5b3680c51512f3870206c8d687f4 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 20 Aug 2020 21:16:38 +0300 Subject: [PATCH 14/31] fix: package.json & package-lock.json to reduce vulnerabilities (#6864) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-LODASH-590103 Co-authored-by: Diamond Lewis --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5920b1b2cf..ba043fa927 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8529,9 +8529,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash.assignin": { "version": "4.2.0", diff --git a/package.json b/package.json index 578d502b39..a57fbaef46 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "jsonwebtoken": "8.5.1", "jwks-rsa": "1.8.1", "ldapjs": "2.0.0", - "lodash": "4.17.19", + "lodash": "4.17.20", "lru-cache": "5.1.1", "mime": "2.4.6", "mongodb": "3.5.9", From cdd1afd9237e6ba0c77f37229be4b729917caf09 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 20 Aug 2020 22:15:59 +0300 Subject: [PATCH 15/31] fix: upgrade ldapjs from 2.0.0 to 2.1.0 (#6857) Snyk has created this PR to upgrade ldapjs from 2.0.0 to 2.1.0. See this package in npm: https://www.npmjs.com/package/ldapjs See this project in Snyk: https://app.snyk.io/org/acinader/project/8c1a9edb-c8f5-4dc1-b221-4d6030a323eb?utm_source=github&utm_medium=upgrade-pr Co-authored-by: Diamond Lewis --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba043fa927..d1cd40f8d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8244,9 +8244,9 @@ } }, "ldapjs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.0.0.tgz", - "integrity": "sha512-ZESQmVoG4a2ZX51pl/aRI+/kqiN2eRWMgHIsNZ2TYf37/S64OPnVJL5Vd5gdZR/qRPZVe5uuKW5p0GK2FUx/FQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.1.0.tgz", + "integrity": "sha512-ppGqhf2Jn7BTmCHgjrdQcPqq2ieR/x+CAvF1EZboTNwDt66T7h7A1tFbwQwJPna27s0F93H8jNWOIMt0DTKnvw==", "requires": { "abstract-logging": "^1.0.0", "asn1": "^0.2.4", diff --git a/package.json b/package.json index a57fbaef46..7dcae5c81e 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "intersect": "1.0.1", "jsonwebtoken": "8.5.1", "jwks-rsa": "1.8.1", - "ldapjs": "2.0.0", + "ldapjs": "2.1.0", "lodash": "4.17.20", "lru-cache": "5.1.1", "mime": "2.4.6", From 1b8b1e9e58b3e283131b8da42ff4160921941c49 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 20 Aug 2020 22:51:38 +0300 Subject: [PATCH 16/31] fix: upgrade apollo-server-express from 2.15.1 to 2.16.0 (#6851) Snyk has created this PR to upgrade apollo-server-express from 2.15.1 to 2.16.0. See this package in npm: https://www.npmjs.com/package/apollo-server-express See this project in Snyk: https://app.snyk.io/org/acinader/project/8c1a9edb-c8f5-4dc1-b221-4d6030a323eb?utm_source=github&utm_medium=upgrade-pr Co-authored-by: Diamond Lewis --- package-lock.json | 32 ++++++++++++++++---------------- package.json | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1cd40f8d5..00cbb6f3a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,9 +25,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.27.tgz", - "integrity": "sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg==" + "version": "10.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.28.tgz", + "integrity": "sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ==" } } }, @@ -3163,9 +3163,9 @@ } }, "@types/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.7.tgz", + "integrity": "sha512-sOdDRU3oRS7LBNTIqwDkPJyq0lpHYcbMTt0TrjzsXbk/e37hcLTH6eZX7CdbDeN0yJJvzw9hFBZkbtCSbk/jAQ==", "requires": { "@types/express": "*" } @@ -3667,9 +3667,9 @@ } }, "apollo-server-core": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.0.tgz", - "integrity": "sha512-mnvg2cPvsQtjFXIqIhEAbPqGyiSXDSbiBgNQ8rY8g7r2eRMhHKZePqGF03gP1/w87yVaSDRAZBDk6o+jiBXjVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.1.tgz", + "integrity": "sha512-nuwn5ZBbmzPwDetb3FgiFFJlNK7ZBFg8kis/raymrjd3eBGdNcOyMTJDl6J9673X9Xqp+dXQmFYDW/G3G8S1YA==", "requires": { "@apollographql/apollo-tools": "^0.4.3", "@apollographql/graphql-playground-html": "1.6.26", @@ -3731,9 +3731,9 @@ "integrity": "sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ==" }, "apollo-server-express": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.15.1.tgz", - "integrity": "sha512-anNb9HJo+KTpgvUqiPOjEl4wPq8y8NmWaIUz/QqPZlhIEDdf7wd/kQo3Sdbov++7J9JNJx6Ownnvw+wxfogUgA==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.16.0.tgz", + "integrity": "sha512-mBIvKcF8gApj7wbmqe0A4Tsy+Pw66mI6cmtD912bG59KhUBveSCZ21dDlRSvnXUyK+GOo2ItwcUEtmks+Z2Pqw==", "requires": { "@apollographql/graphql-playground-html": "1.6.26", "@types/accepts": "^1.3.5", @@ -3741,7 +3741,7 @@ "@types/cors": "^2.8.4", "@types/express": "4.17.4", "accepts": "^1.3.5", - "apollo-server-core": "^2.15.1", + "apollo-server-core": "^2.16.0", "apollo-server-types": "^0.5.1", "body-parser": "^1.18.3", "cors": "^2.8.4", @@ -7716,9 +7716,9 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "requires": { "has-symbols": "^1.0.1" } diff --git a/package.json b/package.json index 7dcae5c81e..0dbe6327f4 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@parse/push-adapter": "3.2.0", "@parse/s3-files-adapter": "1.4.0", "@parse/simple-mailgun-adapter": "1.1.0", - "apollo-server-express": "2.15.1", + "apollo-server-express": "2.16.0", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "commander": "5.1.0", From e6d69f98cc1855102bdc1bf33e879daf1f09f88c Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 20 Aug 2020 23:18:15 +0300 Subject: [PATCH 17/31] fix: upgrade @graphql-tools/stitch from 6.0.12 to 6.0.13 (#6845) Snyk has created this PR to upgrade @graphql-tools/stitch from 6.0.12 to 6.0.13. See this package in npm: https://www.npmjs.com/package/@graphql-tools/stitch See this project in Snyk: https://app.snyk.io/org/acinader/project/8c1a9edb-c8f5-4dc1-b221-4d6030a323eb?utm_source=github&utm_medium=upgrade-pr Co-authored-by: Diamond Lewis --- package-lock.json | 111 ++++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 68 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00cbb6f3a2..33c75768f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2719,21 +2719,38 @@ "to-fast-properties": "^2.0.0" } }, + "@graphql-tools/batch-delegate": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-delegate/-/batch-delegate-6.0.13.tgz", + "integrity": "sha512-My2voosSQLjNDOKs4RXev4v9kmqHK6LxzXGg2fdOO59UQJ00cSbY9VNZnIIRWcOi6+JdOCAcbeuVsmocLUd7Jg==", + "requires": { + "@graphql-tools/delegate": "6.0.13", + "dataloader": "2.0.0", + "tslib": "~2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", + "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==" + } + } + }, "@graphql-tools/delegate": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.12.tgz", - "integrity": "sha512-52bac1Ct1s0c8aSTVCbnc5FI2LC+NqUFSs+5/mP1k5hIEW2GROGBeZdbRs2GQaHir1vKUYIyHzlZIIBMzOZ/gA==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.13.tgz", + "integrity": "sha512-DoHzhQFQLSS0dw1afLz7h290INNSw3jmIU9CP+CTZ3Ikgfy+51abWjw5VDjgSvFOgnP0YbTR2r1Ett6JwUsryQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", - "@graphql-tools/schema": "6.0.12", - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/schema": "6.0.13", + "@graphql-tools/utils": "6.0.13", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -2747,19 +2764,19 @@ } }, "@graphql-tools/merge": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.12.tgz", - "integrity": "sha512-GGvdIoTad6PJk/d1omPlGQ25pCFWmjuGkARYZ71qWI/c4FEA8EdGoOoPz3shhaKXyLdRiu84S758z4ZtDQiYVw==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.13.tgz", + "integrity": "sha512-t8M1ytadf8JAKBSappzWqDzpCoufYPRPimadqxFxgeGZ/LrwzsJ7lCw8EIT1eZZYT7DbqYfAUalIQ124GazuEQ==", "requires": { - "@graphql-tools/schema": "6.0.12", - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/schema": "6.0.13", + "@graphql-tools/utils": "6.0.13", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -2773,18 +2790,18 @@ } }, "@graphql-tools/schema": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.12.tgz", - "integrity": "sha512-XUmKJ+ipENaxuXIX4GapsLAUl1dFQBUg+S4ZbgtKVlwrPhZJ9bkjIqnUHk3wg4S4VXqzLX97ol1e4g9N6XLkYg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.13.tgz", + "integrity": "sha512-Uy2J7L3rr8QeIr+SwWltcAlvAu3q0EKuq41qMhL23dgqpoTAg1BIWlvYSoOdoGaRZP95bvLCEiyf/X5Q1VMEFw==", "requires": { - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/utils": "6.0.13", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -2798,22 +2815,23 @@ } }, "@graphql-tools/stitch": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-6.0.12.tgz", - "integrity": "sha512-I+9l5Ws30Fn3nx0CIDUDMGP0nhexMEJyzfQn1t9DuOTy2QHPQ5YpaZ8hxv6y5+X23EJBU9AebqvNSvWNEO6XJQ==", - "requires": { - "@graphql-tools/delegate": "6.0.12", - "@graphql-tools/merge": "6.0.12", - "@graphql-tools/schema": "6.0.12", - "@graphql-tools/utils": "6.0.12", - "@graphql-tools/wrap": "6.0.12", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-6.0.13.tgz", + "integrity": "sha512-bbtGujHZTBZECUR7JlMp5a5tAm0PDeZIWnAGAcGrnOpHSdBq0kYP3NxrFwuXRP53EkV+C5eYT7zy5uQum1S34Q==", + "requires": { + "@graphql-tools/batch-delegate": "6.0.13", + "@graphql-tools/delegate": "6.0.13", + "@graphql-tools/merge": "6.0.13", + "@graphql-tools/schema": "6.0.13", + "@graphql-tools/utils": "6.0.13", + "@graphql-tools/wrap": "6.0.13", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -2836,21 +2854,21 @@ } }, "@graphql-tools/wrap": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.12.tgz", - "integrity": "sha512-x/t6004aNLzTbOFzZiau15fY2+TBy0wbFqP2du+I+yh8j6KmAU1YkPolBJ4bAI04WD3qcLNh7Rai+VhOxidOkw==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.13.tgz", + "integrity": "sha512-QJAHUpZN8fEupYNLDXtahlPH4/yC/AOXxOoaBUOpQTEFr7A3eMLGk9FgINWlGIEgWpJYH8G6bo9N0bE64LIsGw==", "requires": { - "@graphql-tools/delegate": "6.0.12", - "@graphql-tools/schema": "6.0.12", - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/delegate": "6.0.13", + "@graphql-tools/schema": "6.0.13", + "@graphql-tools/utils": "6.0.13", "aggregate-error": "3.0.1", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -5087,6 +5105,11 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==" }, + "dataloader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", + "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", diff --git a/package.json b/package.json index 0dbe6327f4..a9e8b6d830 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "license": "BSD-3-Clause", "dependencies": { "@apollographql/graphql-playground-html": "1.6.26", - "@graphql-tools/stitch": "6.0.12", + "@graphql-tools/stitch": "6.0.13", "@graphql-tools/utils": "6.0.12", "@parse/fs-files-adapter": "1.0.1", "@parse/push-adapter": "3.2.0", From d87dac1d098c489049b996fa84c53aa0f2df9304 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 20 Aug 2020 23:49:47 +0300 Subject: [PATCH 18/31] fix: upgrade @graphql-tools/utils from 6.0.12 to 6.0.13 (#6846) Snyk has created this PR to upgrade @graphql-tools/utils from 6.0.12 to 6.0.13. See this package in npm: https://www.npmjs.com/package/@graphql-tools/utils See this project in Snyk: https://app.snyk.io/org/acinader/project/8c1a9edb-c8f5-4dc1-b221-4d6030a323eb?utm_source=github&utm_medium=upgrade-pr Co-authored-by: Diamond Lewis --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33c75768f4..012a8fe48b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2845,9 +2845,9 @@ } }, "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" diff --git a/package.json b/package.json index a9e8b6d830..15e01a94e7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dependencies": { "@apollographql/graphql-playground-html": "1.6.26", "@graphql-tools/stitch": "6.0.13", - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/utils": "6.0.13", "@parse/fs-files-adapter": "1.0.1", "@parse/push-adapter": "3.2.0", "@parse/s3-files-adapter": "1.4.0", From bff0ea96b89f010588d5f6b882724d00e12386c5 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Fri, 21 Aug 2020 02:06:36 +0300 Subject: [PATCH 19/31] [Snyk] Upgrade winston from 3.2.1 to 3.3.2 (#6799) * fix: upgrade winston from 3.2.1 to 3.3.2 Snyk has created this PR to upgrade winston from 3.2.1 to 3.3.2. See this package in NPM: https://www.npmjs.com/package/winston See this project in Snyk: https://app.snyk.io/org/acinader/project/8c1a9edb-c8f5-4dc1-b221-4d6030a323eb?utm_source=github&utm_medium=upgrade-pr * fix tests Co-authored-by: Diamond Lewis --- package-lock.json | 142 +++++++++++++++++++++-------------- package.json | 2 +- spec/FilesController.spec.js | 22 +++--- 3 files changed, 99 insertions(+), 67 deletions(-) diff --git a/package-lock.json b/package-lock.json index 012a8fe48b..1e5243010f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2719,6 +2719,16 @@ "to-fast-properties": "^2.0.0" } }, + "@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "@graphql-tools/batch-delegate": { "version": "6.0.13", "resolved": "https://registry.npmjs.org/@graphql-tools/batch-delegate/-/batch-delegate-6.0.13.tgz", @@ -4831,11 +4841,6 @@ "simple-swizzle": "^0.2.2" } }, - "colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" - }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -5403,16 +5408,6 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "requires": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" - } - }, "dicer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", @@ -5601,12 +5596,9 @@ "dev": true }, "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "requires": { - "env-variable": "0.0.x" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, "encodeurl": { "version": "1.0.2", @@ -5637,11 +5629,6 @@ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", "dev": true }, - "env-variable": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", - "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -6425,9 +6412,9 @@ } }, "fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" }, "figures": { "version": "3.1.0", @@ -6624,6 +6611,11 @@ "integrity": "sha512-mX6qjJVi7aLqR9sDf8QIHt8yYEWQbkMLw7qFoC7sM/AbJwvqFm3pATPN96thsaL9o1rrshvxJpSgoj1PJSC3KA==", "dev": true }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "follow-redirects": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", @@ -8246,12 +8238,9 @@ } }, "kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "requires": { - "colornames": "^1.1.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, "lcov-parse": { "version": "1.0.0", @@ -8838,13 +8827,13 @@ } }, "logform": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", - "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", + "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", "requires": { "colors": "^1.2.1", "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", + "fecha": "^4.2.0", "ms": "^2.1.1", "triple-beam": "^1.3.0" } @@ -9893,9 +9882,12 @@ } }, "one-time": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", - "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } }, "onetime": { "version": "5.1.0", @@ -12278,30 +12270,70 @@ } }, "winston": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", - "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.2.tgz", + "integrity": "sha512-vTOrUZlyQPS8VpCcQ1JT8BumDAUe4awCHZ9nmGgO7LqkV4atj0dKa5suA7Trf7QKtBszE2yUs9d8744Kz9j4jQ==", "requires": { - "async": "^2.6.1", - "diagnostics": "^1.1.1", - "is-stream": "^1.1.0", - "logform": "^2.1.1", - "one-time": "0.0.4", - "readable-stream": "^3.1.1", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.3.0" + "winston-transport": "^4.4.0" }, "dependencies": { + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "requires": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } } } }, diff --git a/package.json b/package.json index 15e01a94e7..7a53adf36a 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "subscriptions-transport-ws": "0.9.17", "tv4": "1.3.0", "uuid": "8.3.0", - "winston": "3.2.1", + "winston": "3.3.2", "winston-daily-rotate-file": "4.5.0", "ws": "7.3.1" }, diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 00e01c052d..a6dc604c20 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -24,7 +24,7 @@ const mockAdapter = { // Small additional tests to improve overall coverage describe('FilesController', () => { - it('should properly expand objects', (done) => { + it('should properly expand objects', done => { const config = Config.get(Parse.applicationId); const gridStoreAdapter = new GridFSBucketAdapter( 'mongodb://localhost:27017/parse' @@ -48,7 +48,7 @@ describe('FilesController', () => { done(); }); - it('should create a server log on failure', (done) => { + it('should create a server log on failure', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); reconfigureServer({ filesAdapter: mockAdapter }) @@ -57,20 +57,20 @@ describe('FilesController', () => { () => done.fail('should not succeed'), () => setImmediate(() => Promise.resolve('done')) ) - .then(() => new Promise((resolve) => setTimeout(resolve, 200))) + .then(() => new Promise(resolve => setTimeout(resolve, 200))) .then(() => logController.getLogs({ from: Date.now() - 1000, size: 1000 }) ) - .then((logs) => { + .then(logs => { // we get two logs here: 1. the source of the failure to save the file // and 2 the message that will be sent back to the client. const log1 = logs.find( - (x) => x.message === 'Error creating a file: it failed with xyz' + x => x.message === 'Error creating a file: it failed with xyz' ); expect(log1.level).toBe('error'); - const log2 = logs.find((x) => x.message === 'it failed with xyz'); + const log2 = logs.find(x => x.message === 'it failed with xyz'); expect(log2.level).toBe('error'); expect(log2.code).toBe(130); @@ -78,7 +78,7 @@ describe('FilesController', () => { }); }); - it('should create a parse error when a string is returned', (done) => { + it('should create a parse error when a string is returned', done => { const mock2 = mockAdapter; mock2.validateFilename = () => { return 'Bad file! No biscuit!'; @@ -91,7 +91,7 @@ describe('FilesController', () => { done(); }); - it('should add a unique hash to the file name when the preserveFileName option is false', (done) => { + it('should add a unique hash to the file name when the preserveFileName option is false', done => { const config = Config.get(Parse.applicationId); const gridStoreAdapter = new GridFSBucketAdapter( 'mongodb://localhost:27017/parse' @@ -114,7 +114,7 @@ describe('FilesController', () => { done(); }); - it('should not add a unique hash to the file name when the preserveFileName option is true', (done) => { + it('should not add a unique hash to the file name when the preserveFileName option is true', done => { const config = Config.get(Parse.applicationId); const gridStoreAdapter = new GridFSBucketAdapter( 'mongodb://localhost:27017/parse' @@ -145,7 +145,7 @@ describe('FilesController', () => { expect(result).toEqual({}); }); - it('should reject slashes in file names', (done) => { + it('should reject slashes in file names', done => { const gridStoreAdapter = new GridFSBucketAdapter( 'mongodb://localhost:27017/parse' ); @@ -154,7 +154,7 @@ describe('FilesController', () => { done(); }); - it('should also reject slashes in file names', (done) => { + it('should also reject slashes in file names', done => { const gridStoreAdapter = new GridStoreAdapter( 'mongodb://localhost:27017/parse' ); From 0ed5e8a0d951d98f6cbf3c96efd9dbd6c5e8668f Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 21:36:03 +1000 Subject: [PATCH 20/31] afterLiveQueryEvent --- spec/ParseLiveQuery.spec.js | 251 +++++++++++++++++++++++++- src/LiveQuery/ParseLiveQueryServer.js | 80 +++++--- src/cloud-code/Parse.Cloud.js | 10 + src/triggers.js | 20 ++ 4 files changed, 331 insertions(+), 30 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 80eebaa733..de1d6b8bbd 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -16,10 +16,257 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', async object => { + subscription.on('update', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + it('expect afterEvent create', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Create'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + }); + + const query = new Parse.Query(TestObject); + const subscription = await query.subscribe(); + subscription.on('create', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent payload', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Update'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + done(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + await query.subscribe(); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('expect afterEvent enter', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Enter'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('enter', object => { expect(object.get('foo')).toBe('bar'); done(); }); + + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent leave', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Leave'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBeUndefined(); + expect(req.original.get('foo')).toBe('bar'); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('leave', object => { + expect(object.get('foo')).toBeUndefined(); + done(); + }); + + object.unset('foo'); + await object.save(); + }); + + it('can handle afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.current; + current.set('foo', 'yolo'); + + const original = req.original; + original.set('yolo', 'foo'); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', (object, original) => { + expect(object.get('foo')).toBe('yolo'); + expect(original.get('yolo')).toBe('foo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can return different object in afterEvent', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', () => { + const object = new Parse.Object('Yolo'); + return object; + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.className).toBe('Yolo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can handle async afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const parent = new TestObject(); + const child = new TestObject(); + child.set('bar', 'foo'); + await Parse.Object.saveAll([parent, child]); + + Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { + const current = req.current; + const pointer = current.get('child'); + await pointer.fetch(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', parent.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.get('child')).toBeDefined(); + expect(object.get('child').get('bar')).toBe('foo'); + done(); + }); + parent.set('child', child); + await parent.save(); + }); + + it('can handle afterEvent throw', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.current; + const original = req.original; + + setTimeout(() => { + done(); + }, 2000); + + if (current.get('foo') != original.get('foo')) { + throw "Don't pass an update trigger, or message"; + } + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', () => { + fail('update should not have been called.'); + }); + subscription.on('error', () => { + fail('error should not have been called.'); + }); object.set({ foo: 'bar' }); await object.save(); }); @@ -56,7 +303,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', async object => { + subscription.on('update', object => { expect(object.get('foo')).toBe('bar'); done(); }); diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 5d28367961..978fc09194 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -14,6 +14,7 @@ import { runLiveQueryEventHandlers, maybeRunConnectTrigger, maybeRunSubscribeTrigger, + maybeRunAfterEventTrigger, } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; @@ -193,7 +194,7 @@ class ParseLiveQueryServer { originalParseObject = message.originalParseObject.toJSON(); } const classLevelPermissions = message.classLevelPermissions; - const currentParseObject = message.currentParseObject.toJSON(); + let currentParseObject = message.currentParseObject.toJSON(); const className = currentParseObject.className; logger.verbose( 'ClassName: %s | ObjectId: %s', @@ -243,6 +244,7 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; + let res; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { @@ -267,35 +269,57 @@ class ParseLiveQueryServer { currentACLCheckingPromise, ]); }) - .then( - ([isOriginalMatched, isCurrentMatched]) => { - logger.verbose( - 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', - originalParseObject, - currentParseObject, - isOriginalSubscriptionMatched, - isCurrentSubscriptionMatched, - isOriginalMatched, - isCurrentMatched, - subscription.hash - ); - - // Decide event type - let type; - if (isOriginalMatched && isCurrentMatched) { - type = 'Update'; - } else if (isOriginalMatched && !isCurrentMatched) { - type = 'Leave'; - } else if (!isOriginalMatched && isCurrentMatched) { - if (originalParseObject) { - type = 'Enter'; - } else { - type = 'Create'; - } + .then(([isOriginalMatched, isCurrentMatched]) => { + logger.verbose( + 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', + originalParseObject, + currentParseObject, + isOriginalSubscriptionMatched, + isCurrentSubscriptionMatched, + isOriginalMatched, + isCurrentMatched, + subscription.hash + ); + + // Decide event type + let type; + if (isOriginalMatched && isCurrentMatched) { + type = 'Update'; + } else if (isOriginalMatched && !isCurrentMatched) { + type = 'Leave'; + } else if (!isOriginalMatched && isCurrentMatched) { + if (originalParseObject) { + type = 'Enter'; } else { - return null; + type = 'Create'; + } + } else { + return null; + } + message.event = type; + res = { + event: type, + sessionToken: client.sessionToken, + current: currentParseObject, + original: originalParseObject, + }; + return maybeRunAfterEventTrigger('afterEvent', className, res); + }) + .then( + newObj => { + if (res.current != currentParseObject) { + currentParseObject = res.current.toJSON(); + currentParseObject.className = className; + } + if (res.original != originalParseObject) { + originalParseObject = res.original.toJSON(); + originalParseObject.className = className; + } + if (newObj) { + currentParseObject = newObj.toJSON(); + currentParseObject.className = newObj.className; } - const functionName = 'push' + type; + const functionName = 'push' + message.event; client[functionName]( requestId, currentParseObject, diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 088c4dc3c1..2ea210bccc 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -511,6 +511,16 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; +ParseCloud.afterLiveQueryEvent = function (parseClass, handler) { + const className = getClassName(parseClass); + triggers.addTrigger( + triggers.Types.afterEvent, + className, + handler, + Parse.applicationId + ); +}; + ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); }; diff --git a/src/triggers.js b/src/triggers.js index 96dcb65e47..c231d742ee 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -18,6 +18,7 @@ export const Types = { afterDeleteFile: 'afterDeleteFile', beforeConnect: 'beforeConnect', beforeSubscribe: 'beforeSubscribe', + afterEvent: 'afterEvent', }; const FileClassName = '@File'; @@ -797,6 +798,25 @@ export async function maybeRunSubscribeTrigger( return trigger(request); } +export async function maybeRunAfterEventTrigger( + triggerType, + className, + request +) { + const trigger = getTrigger(className, triggerType, Parse.applicationId); + if (!trigger) { + return; + } + if (request.current) { + request.current = Parse.Object.fromJSON(request.current); + } + if (request.original) { + request.original = Parse.Object.fromJSON(request.original); + } + request.user = await userForSessionToken(request.sessionToken); + return trigger(request); +} + async function userForSessionToken(sessionToken) { if (!sessionToken) { return; From 5ed6af2ece480dfcfaf2b00d17f41fe4fa7d864d Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 22:02:05 +1000 Subject: [PATCH 21/31] Add delete event --- spec/ParseLiveQuery.spec.js | 44 ++++++++++++++++++++++----- src/LiveQuery/ParseLiveQueryServer.js | 25 ++++++++++++--- src/triggers.js | 4 +-- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index de1d6b8bbd..460e490a7e 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -35,7 +35,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.afterLiveQueryEvent('TestObject', req => { expect(req.event).toBe('Create'); expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); + expect(req.object.get('foo')).toBe('bar'); }); const query = new Parse.Query(TestObject); @@ -65,7 +65,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.afterLiveQueryEvent('TestObject', req => { expect(req.event).toBe('Update'); expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); + expect(req.object.get('foo')).toBe('bar'); expect(req.original.get('foo')).toBeUndefined(); done(); }); @@ -89,7 +89,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.afterLiveQueryEvent('TestObject', req => { expect(req.event).toBe('Enter'); expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); + expect(req.object.get('foo')).toBe('bar'); expect(req.original.get('foo')).toBeUndefined(); }); @@ -120,7 +120,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.afterLiveQueryEvent('TestObject', req => { expect(req.event).toBe('Leave'); expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBeUndefined(); + expect(req.object.get('foo')).toBeUndefined(); expect(req.original.get('foo')).toBe('bar'); }); @@ -140,6 +140,36 @@ describe('ParseLiveQuery', function () { await object.save(); }); + it('expect afterEvent delete', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Delete'); + expect(req.user).toBeUndefined(); + req.object.set('foo', 'bar'); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + + const subscription = await query.subscribe(); + subscription.on('delete', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + await object.destroy(); + }); + it('can handle afterEvent modification', async done => { await reconfigureServer({ liveQuery: { @@ -153,7 +183,7 @@ describe('ParseLiveQuery', function () { await object.save(); Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.current; + const current = req.object; current.set('foo', 'yolo'); const original = req.original; @@ -215,7 +245,7 @@ describe('ParseLiveQuery', function () { await Parse.Object.saveAll([parent, child]); Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { - const current = req.current; + const current = req.object; const pointer = current.get('child'); await pointer.fetch(); }); @@ -246,7 +276,7 @@ describe('ParseLiveQuery', function () { await object.save(); Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.current; + const current = req.object; const original = req.original; setTimeout(() => { diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 978fc09194..ca45b9497b 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -125,7 +125,7 @@ class ParseLiveQueryServer { _onAfterDelete(message: any): void { logger.verbose(Parse.applicationId + 'afterDelete is triggered'); - const deletedParseObject = message.currentParseObject.toJSON(); + let deletedParseObject = message.currentParseObject.toJSON(); const classLevelPermissions = message.classLevelPermissions; const className = deletedParseObject.className; logger.verbose( @@ -159,6 +159,7 @@ class ParseLiveQueryServer { const acl = message.currentParseObject.getACL(); // Check CLP const op = this._getCLPOperation(subscription.query); + let res; this._matchesCLP( classLevelPermissions, message.currentParseObject, @@ -174,6 +175,22 @@ class ParseLiveQueryServer { if (!isMatched) { return null; } + res = { + event: 'Delete', + sessionToken: client.sessionToken, + object: deletedParseObject, + }; + return maybeRunAfterEventTrigger('afterEvent', className, res); + }) + .then(newObj => { + if (res.object != deletedParseObject) { + deletedParseObject = res.object.toJSON(); + deletedParseObject.className = className; + } + if (newObj) { + deletedParseObject = newObj.toJSON(); + deletedParseObject.className = newObj.className; + } client.pushDelete(requestId, deletedParseObject); }) .catch(error => { @@ -300,15 +317,15 @@ class ParseLiveQueryServer { res = { event: type, sessionToken: client.sessionToken, - current: currentParseObject, + object: currentParseObject, original: originalParseObject, }; return maybeRunAfterEventTrigger('afterEvent', className, res); }) .then( newObj => { - if (res.current != currentParseObject) { - currentParseObject = res.current.toJSON(); + if (res.object != currentParseObject) { + currentParseObject = res.object.toJSON(); currentParseObject.className = className; } if (res.original != originalParseObject) { diff --git a/src/triggers.js b/src/triggers.js index c231d742ee..75c26ff039 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -807,8 +807,8 @@ export async function maybeRunAfterEventTrigger( if (!trigger) { return; } - if (request.current) { - request.current = Parse.Object.fromJSON(request.current); + if (request.object) { + request.object = Parse.Object.fromJSON(request.object); } if (request.original) { request.original = Parse.Object.fromJSON(request.original); From 85319e1fdac62f44562098ac7cf58f4f97f36e5e Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 22:28:25 +1000 Subject: [PATCH 22/31] Fix failing tests --- src/LiveQuery/ParseLiveQueryServer.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index ca45b9497b..12af52a5a0 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -183,11 +183,12 @@ class ParseLiveQueryServer { return maybeRunAfterEventTrigger('afterEvent', className, res); }) .then(newObj => { - if (res.object != deletedParseObject) { + if (res.object && typeof res.object.toJSON === 'function') { deletedParseObject = res.object.toJSON(); deletedParseObject.className = className; } - if (newObj) { + + if (newObj && typeof newObj.toJSON === 'function') { deletedParseObject = newObj.toJSON(); deletedParseObject.className = newObj.className; } @@ -324,24 +325,29 @@ class ParseLiveQueryServer { }) .then( newObj => { - if (res.object != currentParseObject) { + if (res.object && typeof res.object.toJSON === 'function') { currentParseObject = res.object.toJSON(); currentParseObject.className = className; } - if (res.original != originalParseObject) { + + if (res.original && typeof res.original.toJSON === 'function') { originalParseObject = res.original.toJSON(); originalParseObject.className = className; } - if (newObj) { + + if (newObj && typeof newObj.toJSON === 'function') { currentParseObject = newObj.toJSON(); currentParseObject.className = newObj.className; } + const functionName = 'push' + message.event; - client[functionName]( - requestId, - currentParseObject, - originalParseObject - ); + if (client[functionName]) { + client[functionName]( + requestId, + currentParseObject, + originalParseObject + ); + } }, error => { logger.error('Matching ACL error : ', error); From a51cfaa7d229a635fb66b7825565154cd3c73b6f Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 13 Jul 2020 21:25:04 +1000 Subject: [PATCH 23/31] Before Connect + Before Subscribe #1 --- spec/ParseLiveQueryServer.spec.js | 70 +++++++++++++++++++++++++------ src/triggers.js | 28 ++++++------- 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 6c1b831a5d..420e92d7c6 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -232,7 +232,7 @@ describe('ParseLiveQueryServer', function () { classNames: ['Yolo'], }, }) - .then(parseServer => { + .then((parseServer) => { saveSpy = spyOn(parseServer.config.liveQueryController, 'onAfterSave'); deleteSpy = spyOn( parseServer.config.liveQueryController, @@ -247,7 +247,7 @@ describe('ParseLiveQueryServer', function () { const obj = new Parse.Object('Yolo'); return obj.save(); }) - .then(obj => { + .then((obj) => { return obj.destroy(); }) .then(() => { @@ -1546,7 +1546,7 @@ describe('ParseLiveQueryServer', function () { }); describe('class level permissions', () => { - it('matches CLP when find is closed', done => { + it('matches CLP when find is closed', (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1571,13 +1571,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then(isMatched => { + .then((isMatched) => { expect(isMatched).toBe(false); done(); }); }); - it('matches CLP when find is open', done => { + it('matches CLP when find is open', (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1602,13 +1602,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then(isMatched => { + .then((isMatched) => { expect(isMatched).toBe(true); done(); }); }); - it('matches CLP when find is restricted to userIds', done => { + it('matches CLP when find is restricted to userIds', (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1633,13 +1633,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then(isMatched => { + .then((isMatched) => { expect(isMatched).toBe(true); done(); }); }); - it('matches CLP when find is restricted to userIds', done => { + it('matches CLP when find is restricted to userIds', (done) => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1664,7 +1664,7 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then(isMatched => { + .then((isMatched) => { expect(isMatched).toBe(false); done(); }); @@ -2001,7 +2001,7 @@ describe('LiveQueryController', () => { classNames: ['Yolo'], }, }) - .then(parseServer => { + .then((parseServer) => { saveSpy = spyOn( parseServer.config.liveQueryController, 'onAfterSave' @@ -2019,7 +2019,7 @@ describe('LiveQueryController', () => { const obj = new Parse.Object('Yolo'); return obj.save(); }) - .then(obj => { + .then((obj) => { return obj.destroy(); }) .then(() => { @@ -2099,3 +2099,49 @@ describe('LiveQueryController', () => { }); }); }); + +it('basic beforeConnect rejection', async () => { + Parse.Cloud.beforeConnect(function () { + throw new Error('You shall not pass!'); + }); + const parseLiveQueryServer = new ParseLiveQueryServer({}); + const parseWebSocket = { + clientId: -1, + }; + await parseLiveQueryServer._handleConnect(parseWebSocket, { + sessionToken: 'token', + }); + expect(parseLiveQueryServer.clients.size).toBe(0); + const Client = require('../lib/LiveQuery/Client').Client; + expect(Client.pushError).toHaveBeenCalled(); +}); + +it('basic beforeSubscribe rejection', async () => { + Parse.Cloud.beforeSubscribe('test', function () { + throw new Error('You shall not pass!'); + }); + const parseLiveQueryServer = new ParseLiveQueryServer({}); + const parseWebSocket = { + clientId: -1, + }; + await parseLiveQueryServer._handleConnect(parseWebSocket, { + sessionToken: 'token', + }); + const query = { + className: 'test', + where: { + key: 'value', + }, + fields: ['test'], + }; + const requestId = 2; + const request = { + query: query, + requestId: requestId, + sessionToken: 'sessionToken', + }; + await parseLiveQueryServer._handleSubscribe(parseWebSocket, request); + expect(parseLiveQueryServer.clients.size).toBe(0); + const Client = require('../lib/LiveQuery/Client').Client; + expect(Client.pushError).toHaveBeenCalled(); +}); diff --git a/src/triggers.js b/src/triggers.js index 75c26ff039..ddcc7fe489 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -155,7 +155,7 @@ export function removeTrigger(type, className, applicationId) { } export function _unregisterAll() { - Object.keys(_triggerStore).forEach(appId => delete _triggerStore[appId]); + Object.keys(_triggerStore).forEach((appId) => delete _triggerStore[appId]); } export function getTrigger(className, triggerType, applicationId) { @@ -188,7 +188,7 @@ export function getFunctionNames(applicationId) { {}; const functionNames = []; const extractFunctionNames = (namespace, store) => { - Object.keys(store).forEach(name => { + Object.keys(store).forEach((name) => { const value = store[name]; if (namespace) { name = `${namespace}.${name}`; @@ -315,7 +315,7 @@ export function getResponseObject(request, resolve, reject) { if (!response) { response = request.objects; } - response = response.map(object => { + response = response.map((object) => { return object.toJSON(); }); return resolve(response); @@ -426,10 +426,10 @@ export function maybeRunAfterFindTrigger( const request = getRequestObject(triggerType, auth, null, null, config); const { success, error } = getResponseObject( request, - object => { + (object) => { resolve(object); }, - error => { + (error) => { reject(error); } ); @@ -440,7 +440,7 @@ export function maybeRunAfterFindTrigger( JSON.stringify(objects), auth ); - request.objects = objects.map(object => { + request.objects = objects.map((object) => { //setting the class name to transform into parse object object.className = className; return Parse.Object.fromJSON(object); @@ -449,7 +449,7 @@ export function maybeRunAfterFindTrigger( .then(() => { const response = trigger(request); if (response && typeof response.then === 'function') { - return response.then(results => { + return response.then((results) => { if (!results) { throw new Parse.Error( Parse.Error.SCRIPT_FAILED, @@ -462,7 +462,7 @@ export function maybeRunAfterFindTrigger( return response; }) .then(success, error); - }).then(results => { + }).then((results) => { logTriggerAfterHook(triggerType, className, JSON.stringify(results), auth); return results; }); @@ -509,7 +509,7 @@ export function maybeRunQueryTrigger( return trigger(requestObject); }) .then( - result => { + (result) => { let queryResult = parseQuery; if (result && result instanceof Parse.Query) { queryResult = result; @@ -569,7 +569,7 @@ export function maybeRunQueryTrigger( restOptions, }; }, - err => { + (err) => { if (typeof err === 'string') { throw new Parse.Error(1, err); } else { @@ -612,7 +612,7 @@ export function maybeRunTrigger( ); var { success, error } = getResponseObject( request, - object => { + (object) => { logTriggerSuccessBeforeHook( triggerType, parseObject.className, @@ -630,7 +630,7 @@ export function maybeRunTrigger( } resolve(object); }, - error => { + (error) => { logTriggerErrorBeforeHook( triggerType, parseObject.className, @@ -665,7 +665,7 @@ export function maybeRunTrigger( // beforeSave is expected to return null (nothing) if (triggerType === Types.beforeSave) { if (promise && typeof promise.then === 'function') { - return promise.then(response => { + return promise.then((response) => { // response.object may come from express routing before hook if (response && response.object) { return response; @@ -703,7 +703,7 @@ export function runLiveQueryEventHandlers( ) { return; } - _triggerStore[applicationId].LiveQuery.forEach(handler => handler(data)); + _triggerStore[applicationId].LiveQuery.forEach((handler) => handler(data)); } export function getRequestFileObject(triggerType, auth, fileObject, config) { From 3eb11b5d7bdbd93430e4e115c40fddf40482f28d Mon Sep 17 00:00:00 2001 From: dplewis Date: Tue, 14 Jul 2020 13:49:19 -0500 Subject: [PATCH 24/31] Cleanup and Documentation --- spec/ParseLiveQueryServer.spec.js | 70 ++++++------------------------- src/triggers.js | 28 ++++++------- 2 files changed, 26 insertions(+), 72 deletions(-) diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 420e92d7c6..6c1b831a5d 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -232,7 +232,7 @@ describe('ParseLiveQueryServer', function () { classNames: ['Yolo'], }, }) - .then((parseServer) => { + .then(parseServer => { saveSpy = spyOn(parseServer.config.liveQueryController, 'onAfterSave'); deleteSpy = spyOn( parseServer.config.liveQueryController, @@ -247,7 +247,7 @@ describe('ParseLiveQueryServer', function () { const obj = new Parse.Object('Yolo'); return obj.save(); }) - .then((obj) => { + .then(obj => { return obj.destroy(); }) .then(() => { @@ -1546,7 +1546,7 @@ describe('ParseLiveQueryServer', function () { }); describe('class level permissions', () => { - it('matches CLP when find is closed', (done) => { + it('matches CLP when find is closed', done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1571,13 +1571,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then((isMatched) => { + .then(isMatched => { expect(isMatched).toBe(false); done(); }); }); - it('matches CLP when find is open', (done) => { + it('matches CLP when find is open', done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1602,13 +1602,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then((isMatched) => { + .then(isMatched => { expect(isMatched).toBe(true); done(); }); }); - it('matches CLP when find is restricted to userIds', (done) => { + it('matches CLP when find is restricted to userIds', done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1633,13 +1633,13 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then((isMatched) => { + .then(isMatched => { expect(isMatched).toBe(true); done(); }); }); - it('matches CLP when find is restricted to userIds', (done) => { + it('matches CLP when find is restricted to userIds', done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); const acl = new Parse.ACL(); acl.setReadAccess(testUserId, true); @@ -1664,7 +1664,7 @@ describe('ParseLiveQueryServer', function () { requestId, 'find' ) - .then((isMatched) => { + .then(isMatched => { expect(isMatched).toBe(false); done(); }); @@ -2001,7 +2001,7 @@ describe('LiveQueryController', () => { classNames: ['Yolo'], }, }) - .then((parseServer) => { + .then(parseServer => { saveSpy = spyOn( parseServer.config.liveQueryController, 'onAfterSave' @@ -2019,7 +2019,7 @@ describe('LiveQueryController', () => { const obj = new Parse.Object('Yolo'); return obj.save(); }) - .then((obj) => { + .then(obj => { return obj.destroy(); }) .then(() => { @@ -2099,49 +2099,3 @@ describe('LiveQueryController', () => { }); }); }); - -it('basic beforeConnect rejection', async () => { - Parse.Cloud.beforeConnect(function () { - throw new Error('You shall not pass!'); - }); - const parseLiveQueryServer = new ParseLiveQueryServer({}); - const parseWebSocket = { - clientId: -1, - }; - await parseLiveQueryServer._handleConnect(parseWebSocket, { - sessionToken: 'token', - }); - expect(parseLiveQueryServer.clients.size).toBe(0); - const Client = require('../lib/LiveQuery/Client').Client; - expect(Client.pushError).toHaveBeenCalled(); -}); - -it('basic beforeSubscribe rejection', async () => { - Parse.Cloud.beforeSubscribe('test', function () { - throw new Error('You shall not pass!'); - }); - const parseLiveQueryServer = new ParseLiveQueryServer({}); - const parseWebSocket = { - clientId: -1, - }; - await parseLiveQueryServer._handleConnect(parseWebSocket, { - sessionToken: 'token', - }); - const query = { - className: 'test', - where: { - key: 'value', - }, - fields: ['test'], - }; - const requestId = 2; - const request = { - query: query, - requestId: requestId, - sessionToken: 'sessionToken', - }; - await parseLiveQueryServer._handleSubscribe(parseWebSocket, request); - expect(parseLiveQueryServer.clients.size).toBe(0); - const Client = require('../lib/LiveQuery/Client').Client; - expect(Client.pushError).toHaveBeenCalled(); -}); diff --git a/src/triggers.js b/src/triggers.js index ddcc7fe489..75c26ff039 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -155,7 +155,7 @@ export function removeTrigger(type, className, applicationId) { } export function _unregisterAll() { - Object.keys(_triggerStore).forEach((appId) => delete _triggerStore[appId]); + Object.keys(_triggerStore).forEach(appId => delete _triggerStore[appId]); } export function getTrigger(className, triggerType, applicationId) { @@ -188,7 +188,7 @@ export function getFunctionNames(applicationId) { {}; const functionNames = []; const extractFunctionNames = (namespace, store) => { - Object.keys(store).forEach((name) => { + Object.keys(store).forEach(name => { const value = store[name]; if (namespace) { name = `${namespace}.${name}`; @@ -315,7 +315,7 @@ export function getResponseObject(request, resolve, reject) { if (!response) { response = request.objects; } - response = response.map((object) => { + response = response.map(object => { return object.toJSON(); }); return resolve(response); @@ -426,10 +426,10 @@ export function maybeRunAfterFindTrigger( const request = getRequestObject(triggerType, auth, null, null, config); const { success, error } = getResponseObject( request, - (object) => { + object => { resolve(object); }, - (error) => { + error => { reject(error); } ); @@ -440,7 +440,7 @@ export function maybeRunAfterFindTrigger( JSON.stringify(objects), auth ); - request.objects = objects.map((object) => { + request.objects = objects.map(object => { //setting the class name to transform into parse object object.className = className; return Parse.Object.fromJSON(object); @@ -449,7 +449,7 @@ export function maybeRunAfterFindTrigger( .then(() => { const response = trigger(request); if (response && typeof response.then === 'function') { - return response.then((results) => { + return response.then(results => { if (!results) { throw new Parse.Error( Parse.Error.SCRIPT_FAILED, @@ -462,7 +462,7 @@ export function maybeRunAfterFindTrigger( return response; }) .then(success, error); - }).then((results) => { + }).then(results => { logTriggerAfterHook(triggerType, className, JSON.stringify(results), auth); return results; }); @@ -509,7 +509,7 @@ export function maybeRunQueryTrigger( return trigger(requestObject); }) .then( - (result) => { + result => { let queryResult = parseQuery; if (result && result instanceof Parse.Query) { queryResult = result; @@ -569,7 +569,7 @@ export function maybeRunQueryTrigger( restOptions, }; }, - (err) => { + err => { if (typeof err === 'string') { throw new Parse.Error(1, err); } else { @@ -612,7 +612,7 @@ export function maybeRunTrigger( ); var { success, error } = getResponseObject( request, - (object) => { + object => { logTriggerSuccessBeforeHook( triggerType, parseObject.className, @@ -630,7 +630,7 @@ export function maybeRunTrigger( } resolve(object); }, - (error) => { + error => { logTriggerErrorBeforeHook( triggerType, parseObject.className, @@ -665,7 +665,7 @@ export function maybeRunTrigger( // beforeSave is expected to return null (nothing) if (triggerType === Types.beforeSave) { if (promise && typeof promise.then === 'function') { - return promise.then((response) => { + return promise.then(response => { // response.object may come from express routing before hook if (response && response.object) { return response; @@ -703,7 +703,7 @@ export function runLiveQueryEventHandlers( ) { return; } - _triggerStore[applicationId].LiveQuery.forEach((handler) => handler(data)); + _triggerStore[applicationId].LiveQuery.forEach(handler => handler(data)); } export function getRequestFileObject(triggerType, auth, fileObject, config) { From ac80539b2e189e8b33efac44441b088e1645d921 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 21:11:04 +1000 Subject: [PATCH 25/31] Create afterLiveQueryEvent --- spec/ParseLiveQuery.spec.js | 248 ++++++++++++++++++++++++++++++++++++ src/triggers.js | 19 +++ 2 files changed, 267 insertions(+) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 460e490a7e..4fd05523b4 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -301,6 +301,254 @@ describe('ParseLiveQuery', function () { await object.save(); }); + it('expect afterEvent create', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Create'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + }); + + const query = new Parse.Query(TestObject); + const subscription = await query.subscribe(); + subscription.on('create', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent payload', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Update'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + done(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + await query.subscribe(); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('expect afterEvent enter', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Enter'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('enter', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent leave', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Leave'); + expect(req.user).toBeUndefined(); + expect(req.current.get('foo')).toBeUndefined(); + expect(req.original.get('foo')).toBe('bar'); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('leave', object => { + expect(object.get('foo')).toBeUndefined(); + done(); + }); + + object.unset('foo'); + await object.save(); + }); + + it('can handle afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.current; + current.set('foo', 'yolo'); + + const original = req.original; + original.set('yolo', 'foo'); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', (object, original) => { + expect(object.get('foo')).toBe('yolo'); + expect(original.get('yolo')).toBe('foo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can return different object in afterEvent', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', () => { + const object = new Parse.Object('Yolo'); + return object; + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.className).toBe('Yolo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can handle async afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const parent = new TestObject(); + const child = new TestObject(); + child.set('bar', 'foo'); + await Parse.Object.saveAll([parent, child]); + + Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { + const current = req.current; + const pointer = current.get('child'); + await pointer.fetch(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', parent.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.get('child')).toBeDefined(); + expect(object.get('child').get('bar')).toBe('foo'); + done(); + }); + parent.set('child', child); + await parent.save(); + }); + + it('can handle afterEvent throw', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.current; + const original = req.original; + + setTimeout(() => { + done(); + }, 2000); + + if (current.get('foo') != original.get('foo')) { + throw "Don't pass an update trigger, or message"; + } + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', () => { + fail('update should not have been called.'); + }); + subscription.on('error', () => { + fail('error should not have been called.'); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + it('can handle beforeConnect / beforeSubscribe hooks', async done => { await reconfigureServer({ liveQuery: { diff --git a/src/triggers.js b/src/triggers.js index 75c26ff039..5cd60c7384 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -782,6 +782,25 @@ export async function maybeRunConnectTrigger(triggerType, request) { return trigger(request); } +export async function maybeRunAfterEventTrigger( + triggerType, + className, + request +) { + const trigger = getTrigger(className, triggerType, Parse.applicationId); + if (!trigger) { + return; + } + if (request.current) { + request.current = Parse.Object.fromJSON(request.current); + } + if (request.original) { + request.original = Parse.Object.fromJSON(request.original); + } + request.user = await userForSessionToken(request.sessionToken); + return trigger(request); +} + export async function maybeRunSubscribeTrigger( triggerType, className, From cca07d9e7eb3def3e79c6adbc0db82cc8d78b6a4 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 21:23:50 +1000 Subject: [PATCH 26/31] Revert "Create afterLiveQueryEvent" This reverts commit 828c678a6995216b843a75f5b3c864aec063ba43. --- spec/ParseLiveQuery.spec.js | 252 +------------------------- src/LiveQuery/ParseLiveQueryServer.js | 4 +- src/cloud-code/Parse.Cloud.js | 10 - src/triggers.js | 20 -- 4 files changed, 3 insertions(+), 283 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 4fd05523b4..5fd1eb0ff9 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -16,7 +16,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', object => { + subscription.on('update', async object => { expect(object.get('foo')).toBe('bar'); done(); }); @@ -301,254 +301,6 @@ describe('ParseLiveQuery', function () { await object.save(); }); - it('expect afterEvent create', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('Create'); - expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); - }); - - const query = new Parse.Query(TestObject); - const subscription = await query.subscribe(); - subscription.on('create', object => { - expect(object.get('foo')).toBe('bar'); - done(); - }); - - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - }); - - it('expect afterEvent payload', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('Update'); - expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); - expect(req.original.get('foo')).toBeUndefined(); - done(); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - await query.subscribe(); - object.set({ foo: 'bar' }); - await object.save(); - }); - - it('expect afterEvent enter', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('Enter'); - expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBe('bar'); - expect(req.original.get('foo')).toBeUndefined(); - }); - - const object = new TestObject(); - await object.save(); - - const query = new Parse.Query(TestObject); - query.equalTo('foo', 'bar'); - const subscription = await query.subscribe(); - subscription.on('enter', object => { - expect(object.get('foo')).toBe('bar'); - done(); - }); - - object.set('foo', 'bar'); - await object.save(); - }); - - it('expect afterEvent leave', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('Leave'); - expect(req.user).toBeUndefined(); - expect(req.current.get('foo')).toBeUndefined(); - expect(req.original.get('foo')).toBe('bar'); - }); - - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - - const query = new Parse.Query(TestObject); - query.equalTo('foo', 'bar'); - const subscription = await query.subscribe(); - subscription.on('leave', object => { - expect(object.get('foo')).toBeUndefined(); - done(); - }); - - object.unset('foo'); - await object.save(); - }); - - it('can handle afterEvent modification', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.current; - current.set('foo', 'yolo'); - - const original = req.original; - original.set('yolo', 'foo'); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', (object, original) => { - expect(object.get('foo')).toBe('yolo'); - expect(original.get('yolo')).toBe('foo'); - done(); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - - it('can return different object in afterEvent', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', () => { - const object = new Parse.Object('Yolo'); - return object; - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', object => { - expect(object.className).toBe('Yolo'); - done(); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - - it('can handle async afterEvent modification', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const parent = new TestObject(); - const child = new TestObject(); - child.set('bar', 'foo'); - await Parse.Object.saveAll([parent, child]); - - Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { - const current = req.current; - const pointer = current.get('child'); - await pointer.fetch(); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', parent.id); - const subscription = await query.subscribe(); - subscription.on('update', object => { - expect(object.get('child')).toBeDefined(); - expect(object.get('child').get('bar')).toBe('foo'); - done(); - }); - parent.set('child', child); - await parent.save(); - }); - - it('can handle afterEvent throw', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.current; - const original = req.original; - - setTimeout(() => { - done(); - }, 2000); - - if (current.get('foo') != original.get('foo')) { - throw "Don't pass an update trigger, or message"; - } - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', () => { - fail('update should not have been called.'); - }); - subscription.on('error', () => { - fail('error should not have been called.'); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - it('can handle beforeConnect / beforeSubscribe hooks', async done => { await reconfigureServer({ liveQuery: { @@ -581,7 +333,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', object => { + subscription.on('update', async object => { expect(object.get('foo')).toBe('bar'); done(); }); diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 12af52a5a0..b41db26996 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -14,7 +14,6 @@ import { runLiveQueryEventHandlers, maybeRunConnectTrigger, maybeRunSubscribeTrigger, - maybeRunAfterEventTrigger, } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; @@ -212,7 +211,7 @@ class ParseLiveQueryServer { originalParseObject = message.originalParseObject.toJSON(); } const classLevelPermissions = message.classLevelPermissions; - let currentParseObject = message.currentParseObject.toJSON(); + const currentParseObject = message.currentParseObject.toJSON(); const className = currentParseObject.className; logger.verbose( 'ClassName: %s | ObjectId: %s', @@ -262,7 +261,6 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; - let res; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 2ea210bccc..088c4dc3c1 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -511,16 +511,6 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; -ParseCloud.afterLiveQueryEvent = function (parseClass, handler) { - const className = getClassName(parseClass); - triggers.addTrigger( - triggers.Types.afterEvent, - className, - handler, - Parse.applicationId - ); -}; - ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); }; diff --git a/src/triggers.js b/src/triggers.js index 5cd60c7384..2d761a4ea1 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -18,7 +18,6 @@ export const Types = { afterDeleteFile: 'afterDeleteFile', beforeConnect: 'beforeConnect', beforeSubscribe: 'beforeSubscribe', - afterEvent: 'afterEvent', }; const FileClassName = '@File'; @@ -782,25 +781,6 @@ export async function maybeRunConnectTrigger(triggerType, request) { return trigger(request); } -export async function maybeRunAfterEventTrigger( - triggerType, - className, - request -) { - const trigger = getTrigger(className, triggerType, Parse.applicationId); - if (!trigger) { - return; - } - if (request.current) { - request.current = Parse.Object.fromJSON(request.current); - } - if (request.original) { - request.original = Parse.Object.fromJSON(request.original); - } - request.user = await userForSessionToken(request.sessionToken); - return trigger(request); -} - export async function maybeRunSubscribeTrigger( triggerType, className, From 58a401e358e7cf26edb3658b9d4ccb975c746326 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Aug 2020 23:22:08 +1000 Subject: [PATCH 27/31] Update ParseLiveQueryServer.js --- src/LiveQuery/ParseLiveQueryServer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index b41db26996..d411b490cc 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -158,7 +158,7 @@ class ParseLiveQueryServer { const acl = message.currentParseObject.getACL(); // Check CLP const op = this._getCLPOperation(subscription.query); - let res; + let res = {}; this._matchesCLP( classLevelPermissions, message.currentParseObject, @@ -261,6 +261,7 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; + let res = {}; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { From 840106508299d6becaba56222cb9820c75a33ecc Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 21 Aug 2020 13:11:02 +1000 Subject: [PATCH 28/31] Rebase --- src/LiveQuery/ParseLiveQueryServer.js | 3 ++- src/cloud-code/Parse.Cloud.js | 10 ++++++++++ src/triggers.js | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index d411b490cc..00f1d21c47 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -14,6 +14,7 @@ import { runLiveQueryEventHandlers, maybeRunConnectTrigger, maybeRunSubscribeTrigger, + maybeRunAfterEventTrigger, } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; @@ -211,7 +212,7 @@ class ParseLiveQueryServer { originalParseObject = message.originalParseObject.toJSON(); } const classLevelPermissions = message.classLevelPermissions; - const currentParseObject = message.currentParseObject.toJSON(); + let currentParseObject = message.currentParseObject.toJSON(); const className = currentParseObject.className; logger.verbose( 'ClassName: %s | ObjectId: %s', diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 088c4dc3c1..2ea210bccc 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -511,6 +511,16 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; +ParseCloud.afterLiveQueryEvent = function (parseClass, handler) { + const className = getClassName(parseClass); + triggers.addTrigger( + triggers.Types.afterEvent, + className, + handler, + Parse.applicationId + ); +}; + ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); }; diff --git a/src/triggers.js b/src/triggers.js index 2d761a4ea1..75c26ff039 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -18,6 +18,7 @@ export const Types = { afterDeleteFile: 'afterDeleteFile', beforeConnect: 'beforeConnect', beforeSubscribe: 'beforeSubscribe', + afterEvent: 'afterEvent', }; const FileClassName = '@File'; From ee836e6d8a9519e61722b6792dc9739d3eefafa8 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 15 Oct 2020 07:40:04 +1100 Subject: [PATCH 29/31] Remove return value / deduplicate tests --- spec/ParseLiveQuery.spec.js | 103 +------------------------- src/LiveQuery/ParseLiveQueryServer.js | 21 ++---- 2 files changed, 8 insertions(+), 116 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index d99614bce5..0e2b970e99 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -214,9 +214,9 @@ describe('ParseLiveQuery', function () { const object = new TestObject(); await object.save(); - Parse.Cloud.afterLiveQueryEvent('TestObject', () => { + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { const object = new Parse.Object('Yolo'); - return object; + req.object = object; }); const query = new Parse.Query(TestObject); @@ -230,38 +230,6 @@ describe('ParseLiveQuery', function () { await object.save(); }); - it('can handle async afterEvent modification', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const parent = new TestObject(); - const child = new TestObject(); - child.set('bar', 'foo'); - await Parse.Object.saveAll([parent, child]); - - Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { - const current = req.object; - const pointer = current.get('child'); - await pointer.fetch(); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', parent.id); - const subscription = await query.subscribe(); - subscription.on('update', object => { - expect(object.get('child')).toBeDefined(); - expect(object.get('child').get('bar')).toBe('foo'); - done(); - }); - parent.set('child', child); - await parent.save(); - }); - it('can handle afterEvent throw', async done => { await reconfigureServer({ liveQuery: { @@ -479,34 +447,6 @@ describe('ParseLiveQuery', function () { await object.save(); }); - it('can return different object in afterEvent', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', () => { - const object = new Parse.Object('Yolo'); - return object; - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', object => { - expect(object.className).toBe('Yolo'); - done(); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - it('can handle async afterEvent modification', async done => { await reconfigureServer({ liveQuery: { @@ -539,45 +479,6 @@ describe('ParseLiveQuery', function () { await parent.save(); }); - it('can handle afterEvent throw', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.object; - const original = req.original; - - setTimeout(() => { - done(); - }, 2000); - - if (current.get('foo') != original.get('foo')) { - throw "Don't pass an update trigger, or message"; - } - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', () => { - fail('update should not have been called.'); - }); - subscription.on('error', () => { - fail('error should not have been called.'); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - it('can handle beforeConnect / beforeSubscribe hooks', async done => { await reconfigureServer({ liveQuery: { diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 00f1d21c47..076b01cf73 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -182,16 +182,11 @@ class ParseLiveQueryServer { }; return maybeRunAfterEventTrigger('afterEvent', className, res); }) - .then(newObj => { + .then(() => { if (res.object && typeof res.object.toJSON === 'function') { deletedParseObject = res.object.toJSON(); deletedParseObject.className = className; } - - if (newObj && typeof newObj.toJSON === 'function') { - deletedParseObject = newObj.toJSON(); - deletedParseObject.className = newObj.className; - } client.pushDelete(requestId, deletedParseObject); }) .catch(error => { @@ -324,22 +319,18 @@ class ParseLiveQueryServer { return maybeRunAfterEventTrigger('afterEvent', className, res); }) .then( - newObj => { + () => { if (res.object && typeof res.object.toJSON === 'function') { currentParseObject = res.object.toJSON(); - currentParseObject.className = className; + currentParseObject.className = + res.object.className || className; } if (res.original && typeof res.original.toJSON === 'function') { originalParseObject = res.original.toJSON(); - originalParseObject.className = className; + originalParseObject.className = + res.original.className || className; } - - if (newObj && typeof newObj.toJSON === 'function') { - currentParseObject = newObj.toJSON(); - currentParseObject.className = newObj.className; - } - const functionName = 'push' + message.event; if (client[functionName]) { client[functionName]( From de291ab30549ae099f3e5e2b04c28a4b5e496a56 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 19 Oct 2020 16:55:30 +1100 Subject: [PATCH 30/31] Add docs --- src/cloud-code/Parse.Cloud.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 2ea210bccc..c3f0536c2b 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -511,6 +511,22 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; +/** + * Registers an after live query server event function. + * + * **Available in Cloud Code only.** + * + * ``` + * Parse.Cloud.afterLiveQueryEvent('MyCustomClass', (request) => { + * // code here + * }) + *``` + * + * @method afterLiveQueryEvent + * @name Parse.Cloud.afterLiveQueryEvent + * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after live query event function for. This can instead be a String that is the className of the subclass. + * @param {Function} func The function to run after a live query event. This function can be async and should take one parameter, a {@link Parse.Cloud.LiveQueryEventTrigger}. + */ ParseCloud.afterLiveQueryEvent = function (parseClass, handler) { const className = getClassName(parseClass); triggers.addTrigger( @@ -573,6 +589,19 @@ module.exports = ParseCloud; * @property {String} sessionToken If set, the session of the user that made the request. */ +/** + * @interface Parse.Cloud.LiveQueryEventTrigger + * @property {String} installationId If set, the installationId triggering the request. + * @property {Boolean} useMasterKey If true, means the master key was used. + * @property {Parse.User} user If set, the user that made the request. + * @property {String} sessionToken If set, the session of the user that made the request. + * @property {String} event The live query event that triggered the request. + * @property {Parse.Object} object The object triggering the hook. + * @property {Parse.Object} original If set, the object, as currently stored. + * @property {Integer} clients The number of clients connected. + * @property {Integer} subscriptions The number of subscriptions connected. + */ + /** * @interface Parse.Cloud.BeforeFindRequest * @property {String} installationId If set, the installationId triggering the request. From 16fbca839fa9079f7f9478903f6ec0577cf90c78 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 19 Oct 2020 16:57:37 +1100 Subject: [PATCH 31/31] Add additional data to trigger --- src/LiveQuery/ParseLiveQueryServer.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 076b01cf73..e535480a71 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -179,6 +179,10 @@ class ParseLiveQueryServer { event: 'Delete', sessionToken: client.sessionToken, object: deletedParseObject, + clients: this.clients.size, + subscriptions: this.subscriptions.size, + useMasterKey: client.hasMasterKey, + installationId: client.installationId }; return maybeRunAfterEventTrigger('afterEvent', className, res); }) @@ -315,6 +319,10 @@ class ParseLiveQueryServer { sessionToken: client.sessionToken, object: currentParseObject, original: originalParseObject, + clients: this.clients.size, + subscriptions: this.subscriptions.size, + useMasterKey: client.hasMasterKey, + installationId: client.installationId }; return maybeRunAfterEventTrigger('afterEvent', className, res); })