Skip to content

Commit 2183b84

Browse files
authored
CLI for parse-live-query-server (#2765)
* adds CLI for parse-live-query-server, adds ability to start parse-server with live-query server * Don't crash when the message is badly formatted
1 parent a41cbcb commit 2183b84

9 files changed

+279
-113
lines changed

bin/parse-live-query-server

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env node
2+
3+
require("../lib/cli/parse-live-query-server");

src/LiveQuery/ParseLiveQueryServer.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ class ParseLiveQueryServer {
6262
// to the subscribers and the handler will be called.
6363
this.subscriber.on('message', (channel, messageStr) => {
6464
logger.verbose('Subscribe messsage %j', messageStr);
65-
let message = JSON.parse(messageStr);
65+
let message;
66+
try {
67+
message = JSON.parse(messageStr);
68+
} catch(e) {
69+
logger.error('unable to parse message', messageStr, e);
70+
return;
71+
}
6672
this._inflateParseObject(message);
6773
if (channel === 'afterSave') {
6874
this._onAfterSave(message);
@@ -229,7 +235,12 @@ class ParseLiveQueryServer {
229235
_onConnect(parseWebsocket: any): void {
230236
parseWebsocket.on('message', (request) => {
231237
if (typeof request === 'string') {
232-
request = JSON.parse(request);
238+
try {
239+
request = JSON.parse(request);
240+
} catch(e) {
241+
logger.error('unable to parse request', request, e);
242+
return;
243+
}
233244
}
234245
logger.verbose('Request: %j', request);
235246

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
numberParser,
3+
numberOrBoolParser,
4+
objectParser,
5+
arrayParser,
6+
moduleOrObjectParser,
7+
booleanParser,
8+
nullParser
9+
} from '../utils/parsers';
10+
11+
12+
export default {
13+
"appId": {
14+
required: true,
15+
help: "Required. This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId."
16+
},
17+
"masterKey": {
18+
required: true,
19+
help: "Required. This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey."
20+
},
21+
"serverURL": {
22+
required: true,
23+
help: "Required. This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL."
24+
},
25+
"redisURL": {
26+
help: "Optional. This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey."
27+
},
28+
"keyPairs": {
29+
help: "Optional. A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details."
30+
},
31+
"websocketTimeout": {
32+
help: "Optional. Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients. Defaults to 10 * 1000 ms (10 s).",
33+
action: numberParser("websocketTimeout")
34+
},
35+
"cacheTimeout": {
36+
help: "Optional. Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details. Defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).",
37+
action: numberParser("cacheTimeout")
38+
},
39+
"logLevel": {
40+
help: "Optional. This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE. Defaults to INFO.",
41+
},
42+
"port": {
43+
env: "PORT",
44+
help: "The port to run the ParseServer. defaults to 1337.",
45+
default: 1337,
46+
action: numberParser("port")
47+
},
48+
};

src/cli/cli-definitions.js renamed to src/cli/definitions/parse-server.js

+30-52
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,13 @@
1-
function numberParser(key) {
2-
return function(opt) {
3-
opt = parseInt(opt);
4-
if (!Number.isInteger(opt)) {
5-
throw new Error(`The ${key} is invalid`);
6-
}
7-
return opt;
8-
}
9-
}
1+
import {
2+
numberParser,
3+
numberOrBoolParser,
4+
objectParser,
5+
arrayParser,
6+
moduleOrObjectParser,
7+
booleanParser,
8+
nullParser
9+
} from '../utils/parsers';
1010

11-
function numberOrBoolParser(key) {
12-
return function(opt) {
13-
if (typeof opt === 'boolean') {
14-
return opt;
15-
}
16-
return numberParser(key)(opt);
17-
}
18-
}
19-
20-
function objectParser(opt) {
21-
if (typeof opt == 'object') {
22-
return opt;
23-
}
24-
return JSON.parse(opt)
25-
}
26-
27-
function moduleOrObjectParser(opt) {
28-
if (typeof opt == 'object') {
29-
return opt;
30-
}
31-
try {
32-
return JSON.parse(opt);
33-
} catch(e) {}
34-
return opt;
35-
}
36-
37-
function booleanParser(opt) {
38-
if (opt == true || opt == "true" || opt == "1") {
39-
return true;
40-
}
41-
return false;
42-
}
43-
44-
function nullParser(opt) {
45-
if (opt == 'null') {
46-
return null;
47-
}
48-
return opt;
49-
}
5011

5112
export default {
5213
"appId": {
@@ -128,9 +89,7 @@ export default {
12889
env: "PARSE_SERVER_FACEBOOK_APP_IDS",
12990
help: "Comma separated list for your facebook app Ids",
13091
type: "list",
131-
action: function(opt) {
132-
return opt.split(",")
133-
}
92+
action: arrayParser
13493
},
13594
"enableAnonymousUsers": {
13695
env: "PARSE_SERVER_ENABLE_ANON_USERS",
@@ -239,5 +198,24 @@ export default {
239198
"cluster": {
240199
help: "Run with cluster, optionally set the number of processes default to os.cpus().length",
241200
action: numberOrBoolParser("cluster")
242-
}
201+
},
202+
"liveQuery.classNames": {
203+
help: "parse-server's LiveQuery configuration object",
204+
action: objectParser
205+
},
206+
"liveQuery.classNames": {
207+
help: "parse-server's LiveQuery classNames",
208+
action: arrayParser
209+
},
210+
"liveQuery.redisURL": {
211+
help: "parse-server's LiveQuery redisURL",
212+
},
213+
"startLiveQueryServer": {
214+
help: "Starts the liveQuery server",
215+
action: booleanParser
216+
},
217+
"liveQueryServerOptions": {
218+
help: "Live query server configuration options (will start the liveQuery server)",
219+
action: objectParser
220+
},
243221
};

src/cli/parse-live-query-server.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import definitions from './definitions/parse-live-query-server';
2+
import runner from './utils/runner';
3+
import { ParseServer } from '../index';
4+
import express from 'express';
5+
6+
runner({
7+
definitions,
8+
start: function(program, options, logOptions) {
9+
logOptions();
10+
var app = express();
11+
var httpServer = require('http').createServer(app);
12+
httpServer.listen(options.port);
13+
ParseServer.createLiveQueryServer(httpServer, options);
14+
}
15+
})

src/cli/parse-server.js

+60-58
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
import path from 'path';
22
import express from 'express';
33
import { ParseServer } from '../index';
4-
import definitions from './cli-definitions';
5-
import program from './utils/commander';
6-
import { mergeWithOptions } from './utils/commander';
4+
import definitions from './definitions/parse-server';
75
import cluster from 'cluster';
86
import os from 'os';
7+
import runner from './utils/runner';
98

10-
program.loadDefinitions(definitions);
11-
12-
program
13-
.usage('[options] <path/to/configuration.json>');
14-
15-
program.on('--help', function(){
9+
const help = function(){
1610
console.log(' Get Started guide:');
1711
console.log('');
1812
console.log(' Please have a look at the get started guide!')
@@ -32,41 +26,17 @@ program.on('--help', function(){
3226
console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL');
3327
console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL');
3428
console.log('');
35-
});
36-
37-
program.parse(process.argv, process.env);
38-
39-
let options = program.getOptions();
40-
41-
if (!options.serverURL) {
42-
options.serverURL = `http://localhost:${options.port}${options.mountPath}`;
43-
}
44-
45-
if (!options.appId || !options.masterKey || !options.serverURL) {
46-
program.outputHelp();
47-
console.error("");
48-
console.error('\u001b[31mERROR: appId and masterKey are required\u001b[0m');
49-
console.error("");
50-
process.exit(1);
51-
}
52-
53-
function logStartupOptions(options) {
54-
for (let key in options) {
55-
let value = options[key];
56-
if (key == "masterKey") {
57-
value = "***REDACTED***";
58-
}
59-
console.log(`${key}: ${value}`);
60-
}
61-
}
29+
};
6230

6331
function startServer(options, callback) {
6432
const app = express();
6533
const api = new ParseServer(options);
6634
app.use(options.mountPath, api);
6735

6836
var server = app.listen(options.port, callback);
69-
37+
if (options.startLiveQueryServer || options.liveQueryServerOptions) {
38+
ParseServer.createLiveQueryServer(server, options.liveQueryServerOptions);
39+
}
7040
var handleShutdown = function() {
7141
console.log('Termination signal received. Shutting down.');
7242
server.close(function () {
@@ -77,27 +47,59 @@ function startServer(options, callback) {
7747
process.on('SIGINT', handleShutdown);
7848
}
7949

80-
if (options.cluster) {
81-
const numCPUs = typeof options.cluster === 'number' ? options.cluster : os.cpus().length;
82-
if (cluster.isMaster) {
83-
logStartupOptions(options);
84-
for(var i = 0; i < numCPUs; i++) {
85-
cluster.fork();
50+
51+
runner({
52+
definitions,
53+
help,
54+
usage: '[options] <path/to/configuration.json>',
55+
start: function(program, options, logOptions) {
56+
if (!options.serverURL) {
57+
options.serverURL = `http://localhost:${options.port}${options.mountPath}`;
58+
}
59+
60+
if (!options.appId || !options.masterKey || !options.serverURL) {
61+
program.outputHelp();
62+
console.error("");
63+
console.error('\u001b[31mERROR: appId and masterKey are required\u001b[0m');
64+
console.error("");
65+
process.exit(1);
66+
}
67+
68+
if (options["liveQuery.classNames"]) {
69+
options.liveQuery = options.liveQuery || {};
70+
options.liveQuery.classNames = options["liveQuery.classNames"];
71+
delete options["liveQuery.classNames"];
72+
}
73+
if (options["liveQuery.redisURL"]) {
74+
options.liveQuery = options.liveQuery || {};
75+
options.liveQuery.redisURL = options["liveQuery.redisURL"];
76+
delete options["liveQuery.redisURL"];
77+
}
78+
79+
if (options.cluster) {
80+
const numCPUs = typeof options.cluster === 'number' ? options.cluster : os.cpus().length;
81+
if (cluster.isMaster) {
82+
for(var i = 0; i < numCPUs; i++) {
83+
cluster.fork();
84+
}
85+
cluster.on('exit', (worker, code, signal) => {
86+
console.log(`worker ${worker.process.pid} died... Restarting`);
87+
cluster.fork();
88+
});
89+
} else {
90+
startServer(options, () => {
91+
console.log('['+process.pid+'] parse-server running on '+options.serverURL);
92+
});
93+
}
94+
} else {
95+
startServer(options, () => {
96+
logOptions();
97+
console.log('');
98+
console.log('['+process.pid+'] parse-server running on '+options.serverURL);
99+
});
86100
}
87-
cluster.on('exit', (worker, code, signal) => {
88-
console.log(`worker ${worker.process.pid} died... Restarting`);
89-
cluster.fork();
90-
});
91-
} else {
92-
startServer(options, () => {
93-
console.log('['+process.pid+'] parse-server running on '+options.serverURL);
94-
});
95101
}
96-
} else {
97-
startServer(options, () => {
98-
logStartupOptions(options);
99-
console.log('');
100-
console.log('['+process.pid+'] parse-server running on '+options.serverURL);
101-
});
102-
}
102+
})
103+
104+
103105

0 commit comments

Comments
 (0)