From edaf032bfe1b7540a023d01e190f6fd2e9d005cf Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 13 Jul 2020 21:25:04 +1000 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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",