Before Connect + Before Subscribe help required (#6793)
* Before Connect + Before Subscribe #1 * Cleanup and Documentation * Add E2E tests * Bump parse to 2.15.0 Co-authored-by: Diamond Lewis <findlewis@gmail.com>
This commit is contained in:
30
package-lock.json
generated
30
package-lock.json
generated
@@ -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=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
@@ -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\")()'"
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,62 @@ describe('ParseLiveQueryServer', function() {
|
||||
expect(client.pushConnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can handle subscribe command without clientId', function() {
|
||||
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 = {};
|
||||
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 +384,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 +409,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 +428,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
},
|
||||
fields: ['test'],
|
||||
};
|
||||
addMockSubscription(
|
||||
await addMockSubscription(
|
||||
parseLiveQueryServer,
|
||||
clientId,
|
||||
requestId,
|
||||
@@ -401,7 +447,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
fields: ['testAgain'],
|
||||
};
|
||||
const requestIdAgain = 1;
|
||||
addMockSubscription(
|
||||
await addMockSubscription(
|
||||
parseLiveQueryServer,
|
||||
clientIdAgain,
|
||||
requestIdAgain,
|
||||
@@ -447,7 +493,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 +508,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 +518,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
clientId: 1,
|
||||
};
|
||||
const requestId = 2;
|
||||
const subscription = addMockSubscription(
|
||||
const subscription = await addMockSubscription(
|
||||
parseLiveQueryServer,
|
||||
clientId,
|
||||
requestId,
|
||||
@@ -696,7 +742,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,7 +760,7 @@ 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 () {
|
||||
@@ -729,7 +775,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,7 +792,7 @@ 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 () {
|
||||
@@ -765,7 +811,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
}, 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 +819,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,7 +828,7 @@ 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 () {
|
||||
return false;
|
||||
@@ -804,7 +850,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,7 +859,7 @@ 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
|
||||
@@ -841,7 +887,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,7 +896,7 @@ 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) {
|
||||
if (!parseObject) {
|
||||
@@ -874,7 +920,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,7 +929,7 @@ 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
|
||||
@@ -911,7 +957,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,7 +976,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
// Add mock subscription
|
||||
const requestId = 2;
|
||||
|
||||
addMockSubscription(
|
||||
await addMockSubscription(
|
||||
parseLiveQueryServer,
|
||||
clientId,
|
||||
requestId,
|
||||
@@ -961,7 +1007,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,7 +1016,7 @@ 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) {
|
||||
if (!parseObject) {
|
||||
@@ -994,7 +1040,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 +1065,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
},
|
||||
fields: ['test'],
|
||||
};
|
||||
addMockSubscription(
|
||||
await addMockSubscription(
|
||||
parseLiveQueryServer,
|
||||
clientId,
|
||||
requestId,
|
||||
@@ -1842,7 +1888,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
return client;
|
||||
}
|
||||
|
||||
function addMockSubscription(
|
||||
async function addMockSubscription(
|
||||
parseLiveQueryServer,
|
||||
clientId,
|
||||
requestId,
|
||||
@@ -1870,7 +1916,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
requestId: requestId,
|
||||
sessionToken: 'sessionToken',
|
||||
};
|
||||
parseLiveQueryServer._handleSubscribe(parseWebSocket, request);
|
||||
await parseLiveQueryServer._handleSubscribe(parseWebSocket, request);
|
||||
|
||||
// Make mock subscription
|
||||
const subscription = parseLiveQueryServer.subscriptions
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +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 {
|
||||
runLiveQueryEventHandlers,
|
||||
maybeRunConnectTrigger,
|
||||
maybeRunSubscribeTrigger,
|
||||
} from '../triggers';
|
||||
import { getAuthForSessionToken, Auth } from '../Auth';
|
||||
import { getCacheController } from '../Controllers';
|
||||
import LRU from 'lru-cache';
|
||||
@@ -574,7 +578,7 @@ class ParseLiveQueryServer {
|
||||
return false;
|
||||
}
|
||||
|
||||
_handleConnect(parseWebsocket: any, request: any): any {
|
||||
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');
|
||||
@@ -589,11 +593,8 @@ class ParseLiveQueryServer {
|
||||
request.sessionToken,
|
||||
request.installationId
|
||||
);
|
||||
parseWebsocket.clientId = clientId;
|
||||
this.clients.set(parseWebsocket.clientId, client);
|
||||
logger.info(`Create new client: ${parseWebsocket.clientId}`);
|
||||
client.pushConnect();
|
||||
runLiveQueryEventHandlers({
|
||||
try {
|
||||
const req = {
|
||||
client,
|
||||
event: 'connect',
|
||||
clients: this.clients.size,
|
||||
@@ -601,7 +602,25 @@ class ParseLiveQueryServer {
|
||||
sessionToken: request.sessionToken,
|
||||
useMasterKey: client.hasMasterKey,
|
||||
installationId: request.installationId,
|
||||
});
|
||||
};
|
||||
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 {
|
||||
@@ -636,7 +655,7 @@ 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
|
||||
if (!Object.prototype.hasOwnProperty.call(parseWebsocket, 'clientId')) {
|
||||
Client.pushError(
|
||||
@@ -650,11 +669,14 @@ class ParseLiveQueryServer {
|
||||
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
|
||||
const className = request.query.className;
|
||||
|
||||
if (!this.subscriptions.has(className)) {
|
||||
this.subscriptions.set(className, new Map());
|
||||
}
|
||||
@@ -705,6 +727,19 @@ class ParseLiveQueryServer {
|
||||
useMasterKey: client.hasMasterKey,
|
||||
installationId: client.installationId,
|
||||
});
|
||||
} catch (e) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_handleUpdateSubscription(parseWebsocket: any, request: any): any {
|
||||
|
||||
@@ -453,6 +453,60 @@ 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,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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(
|
||||
triggers.Types.beforeSubscribe,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
);
|
||||
};
|
||||
|
||||
ParseCloud.onLiveQueryEvent = function (handler) {
|
||||
triggers.addLiveQueryEventHandler(handler, Parse.applicationId);
|
||||
};
|
||||
@@ -499,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.
|
||||
|
||||
@@ -16,9 +16,12 @@ export const Types = {
|
||||
afterSaveFile: 'afterSaveFile',
|
||||
beforeDeleteFile: 'beforeDeleteFile',
|
||||
afterDeleteFile: 'afterDeleteFile',
|
||||
beforeConnect: 'beforeConnect',
|
||||
beforeSubscribe: 'beforeSubscribe',
|
||||
};
|
||||
|
||||
const FileClassName = '@File';
|
||||
const ConnectClassName = '@Connect';
|
||||
|
||||
const baseStore = function () {
|
||||
const Validators = {};
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,57 @@ export async function maybeRunFileTrigger(triggerType, fileObject, config, auth)
|
||||
'Parse.File',
|
||||
{ ...fileObject.file.toJSON(), fileSize: fileObject.fileSize },
|
||||
auth,
|
||||
error,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return fileObject;
|
||||
}
|
||||
|
||||
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 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 });
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
const user = session.get('user');
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
await user.fetch({ useMasterKey: true });
|
||||
return user;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user