Skip to content

Commit d0184f4

Browse files
authored
Live Query basic monitoring (#4168)
* Adds uuid based client identification to prevent overflows * no-super * Adds cloud code monitoring * fixes test * nit
1 parent 7ecb36e commit d0184f4

File tree

5 files changed

+90
-16
lines changed

5 files changed

+90
-16
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"request": "2.81.0",
4040
"semver": "5.4.0",
4141
"tv4": "1.3.0",
42+
"uuid": "^3.1.0",
4243
"winston": "2.3.1",
4344
"winston-daily-rotate-file": "1.5.0",
4445
"ws": "3.2.0"

spec/ParseLiveQueryServer.spec.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('ParseLiveQueryServer', function() {
8181
var httpServer = {};
8282
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, httpServer);
8383

84-
expect(parseLiveQueryServer.clientId).toBe(0);
84+
expect(parseLiveQueryServer.clientId).toBeUndefined();
8585
expect(parseLiveQueryServer.clients.size).toBe(0);
8686
expect(parseLiveQueryServer.subscriptions.size).toBe(0);
8787
});
@@ -94,9 +94,11 @@ describe('ParseLiveQueryServer', function() {
9494
parseLiveQueryServer._validateKeys = jasmine.createSpy('validateKeys').and.returnValue(true);
9595
parseLiveQueryServer._handleConnect(parseWebSocket);
9696

97-
expect(parseLiveQueryServer.clientId).toBe(1);
98-
expect(parseWebSocket.clientId).toBe(0);
99-
var client = parseLiveQueryServer.clients.get(0);
97+
const clientKeys = parseLiveQueryServer.clients.keys();
98+
expect(parseLiveQueryServer.clients.size).toBe(1);
99+
const firstKey = clientKeys.next().value;
100+
expect(parseWebSocket.clientId).toBe(firstKey);
101+
var client = parseLiveQueryServer.clients.get(firstKey);
100102
expect(client).not.toBeNull();
101103
// Make sure we send connect response to the client
102104
expect(client.pushConnect).toHaveBeenCalled();
@@ -394,6 +396,28 @@ describe('ParseLiveQueryServer', function() {
394396
parseWebSocket.emit('disconnect');
395397
});
396398

399+
it('can forward event to cloud code', function() {
400+
const cloudCodeHandler = {
401+
handler: () => {}
402+
}
403+
const spy = spyOn(cloudCodeHandler, 'handler').and.callThrough();
404+
Parse.Cloud.onLiveQueryEvent(cloudCodeHandler.handler);
405+
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
406+
var EventEmitter = require('events');
407+
var parseWebSocket = new EventEmitter();
408+
parseWebSocket.clientId = 1;
409+
// Register message handlers for the parseWebSocket
410+
parseLiveQueryServer._onConnect(parseWebSocket);
411+
412+
// Make sure we do not crash
413+
// Trigger disconnect event
414+
parseWebSocket.emit('disconnect');
415+
expect(spy).toHaveBeenCalled();
416+
// call for ws_connect, another for ws_disconnect
417+
expect(spy.calls.count()).toBe(2);
418+
});
419+
420+
397421
// TODO: Test server can set disconnect command message handler for a parseWebSocket
398422

399423
it('has no subscription and can handle object delete command', function() {

src/LiveQuery/ParseLiveQueryServer.js

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import { matchesQuery, queryHash } from './QueryTools';
99
import { ParsePubSub } from './ParsePubSub';
1010
import { SessionTokenCache } from './SessionTokenCache';
1111
import _ from 'lodash';
12+
import uuid from 'uuid';
13+
import { runLiveQueryEventHandlers } from '../triggers';
1214

1315
class ParseLiveQueryServer {
14-
clientId: number;
15-
clients: Object;
16+
clients: Map;
1617
// className -> (queryHash -> subscription)
1718
subscriptions: Object;
1819
parseWebSocketServer: Object;
@@ -21,7 +22,6 @@ class ParseLiveQueryServer {
2122
subscriber: Object;
2223

2324
constructor(server: any, config: any) {
24-
this.clientId = 0;
2525
this.clients = new Map();
2626
this.subscriptions = new Map();
2727

@@ -269,10 +269,16 @@ class ParseLiveQueryServer {
269269
});
270270

271271
parseWebsocket.on('disconnect', () => {
272-
logger.info('Client disconnect: %d', parseWebsocket.clientId);
272+
logger.info(`Client disconnect: ${parseWebsocket.clientId}`);
273273
const clientId = parseWebsocket.clientId;
274274
if (!this.clients.has(clientId)) {
275-
logger.error('Can not find client %d on disconnect', clientId);
275+
runLiveQueryEventHandlers({
276+
event: 'ws_disconnect_error',
277+
clients: this.clients.size,
278+
subscriptions: this.subscriptions.size,
279+
error: `Unable to find client ${clientId}`
280+
});
281+
logger.error(`Can not find client ${clientId} on disconnect`);
276282
return;
277283
}
278284

@@ -298,6 +304,17 @@ class ParseLiveQueryServer {
298304

299305
logger.verbose('Current clients %d', this.clients.size);
300306
logger.verbose('Current subscriptions %d', this.subscriptions.size);
307+
runLiveQueryEventHandlers({
308+
event: 'ws_disconnect',
309+
clients: this.clients.size,
310+
subscriptions: this.subscriptions.size
311+
});
312+
});
313+
314+
runLiveQueryEventHandlers({
315+
event: 'ws_connect',
316+
clients: this.clients.size,
317+
subscriptions: this.subscriptions.size
301318
});
302319
}
303320

@@ -404,12 +421,17 @@ class ParseLiveQueryServer {
404421
return;
405422
}
406423
const hasMasterKey = this._hasMasterKey(request, this.keyPairs);
407-
const client = new Client(this.clientId, parseWebsocket, hasMasterKey);
408-
parseWebsocket.clientId = this.clientId;
409-
this.clientId += 1;
424+
const clientId = uuid();
425+
const client = new Client(clientId, parseWebsocket, hasMasterKey);
426+
parseWebsocket.clientId = clientId;
410427
this.clients.set(parseWebsocket.clientId, client);
411-
logger.info('Create new client: %d', parseWebsocket.clientId);
428+
logger.info(`Create new client: ${parseWebsocket.clientId}`);
412429
client.pushConnect();
430+
runLiveQueryEventHandlers({
431+
event: 'connect',
432+
clients: this.clients.size,
433+
subscriptions: this.subscriptions.size
434+
});
413435
}
414436

415437
_hasMasterKey(request: any, validKeyPairs: any): boolean {
@@ -481,8 +503,13 @@ class ParseLiveQueryServer {
481503

482504
client.pushSubscribe(request.requestId);
483505

484-
logger.verbose('Create client %d new subscription: %d', parseWebsocket.clientId, request.requestId);
506+
logger.verbose(`Create client ${parseWebsocket.clientId} new subscription: ${request.requestId}`);
485507
logger.verbose('Current client number: %d', this.clients.size);
508+
runLiveQueryEventHandlers({
509+
event: 'subscribe',
510+
clients: this.clients.size,
511+
subscriptions: this.subscriptions.size
512+
});
486513
}
487514

488515
_handleUpdateSubscription(parseWebsocket: any, request: any): any {
@@ -529,14 +556,19 @@ class ParseLiveQueryServer {
529556
if (classSubscriptions.size === 0) {
530557
this.subscriptions.delete(className);
531558
}
559+
runLiveQueryEventHandlers({
560+
event: 'unsubscribe',
561+
clients: this.clients.size,
562+
subscriptions: this.subscriptions.size
563+
});
532564

533565
if (!notifyClient) {
534566
return;
535567
}
536568

537569
client.pushUnsubscribe(request.requestId);
538570

539-
logger.verbose('Delete client: %d | subscription: %d', parseWebsocket.clientId, request.requestId);
571+
logger.verbose(`Delete client: ${parseWebsocket.clientId} | subscription: ${request.requestId}`);
540572
}
541573
}
542574

src/cloud-code/Parse.Cloud.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ ParseCloud.afterFind = function(parseClass, handler) {
5555
triggers.addTrigger(triggers.Types.afterFind, className, handler, Parse.applicationId);
5656
};
5757

58+
ParseCloud.onLiveQueryEvent = function(handler) {
59+
triggers.addLiveQueryEventHandler(handler, Parse.applicationId);
60+
};
61+
5862
ParseCloud._removeAllHooks = () => {
5963
triggers._unregisterAll();
6064
}

src/triggers.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const baseStore = function() {
1515
const Validators = {};
1616
const Functions = {};
1717
const Jobs = {};
18+
const LiveQuery = [];
1819
const Triggers = Object.keys(Types).reduce(function(base, key){
1920
base[key] = {};
2021
return base;
@@ -24,7 +25,8 @@ const baseStore = function() {
2425
Functions,
2526
Jobs,
2627
Validators,
27-
Triggers
28+
Triggers,
29+
LiveQuery,
2830
});
2931
};
3032

@@ -49,6 +51,12 @@ export function addTrigger(type, className, handler, applicationId) {
4951
_triggerStore[applicationId].Triggers[type][className] = handler;
5052
}
5153

54+
export function addLiveQueryEventHandler(handler, applicationId) {
55+
applicationId = applicationId || Parse.applicationId;
56+
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
57+
_triggerStore[applicationId].LiveQuery.push(handler);
58+
}
59+
5260
export function removeFunction(functionName, applicationId) {
5361
applicationId = applicationId || Parse.applicationId;
5462
delete _triggerStore[applicationId].Functions[functionName]
@@ -411,3 +419,8 @@ export function inflate(data, restObject) {
411419
}
412420
return Parse.Object.fromJSON(copy);
413421
}
422+
423+
export function runLiveQueryEventHandlers(data, applicationId = Parse.applicationId) {
424+
if (!_triggerStore || !_triggerStore[applicationId] || !_triggerStore[applicationId].LiveQuery) { return; }
425+
_triggerStore[applicationId].LiveQuery.forEach((handler) => handler(data));
426+
}

0 commit comments

Comments
 (0)