realm-object-server
Version:
320 lines • 16.4 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander = require("commander");
const fs = require("fs-extra");
const path = require("path");
const tmp = require("tmp");
const resolve_1 = require("resolve");
const resolve_bin_1 = require("resolve-bin");
const child_process_1 = require("child_process");
const services = require("./services");
const Logger_1 = require("./shared/Logger");
const Server_1 = require("./Server");
const project_1 = require("./project");
const checkEngine_1 = require("./shared/checkEngine");
const ProcessUtil_1 = require("./shared/ProcessUtil");
class BasicServer extends Server_1.Server {
constructor() {
super();
this.commander = new commander.Command();
this.commander.version(require("../package.json").version);
this.commander.on("--help", () => {
console.log("");
console.log(" Help for individual commands:");
console.log("");
console.log(" ros init --help");
console.log(" ros start --help");
console.log(" ros backup --help");
console.log("");
});
this.commander.command("start")
.description("Start Realm Object Server")
.option("-d, --data <path>", "Specify the data path to use (default ./data)", "./data")
.option("-a, --address <address>", "Specify the address to listen on (default 0.0.0.0)", "0.0.0.0")
.option("-p, --port <port>", "Specify the port to listen on (default 9080)", "9080")
.option("-l, --loglevel <level>", "The log level (all | trace | debug | detail | info | warn | error | fatal | off) (default info)", "info")
.option("-f, --logfile <filename>", "The file to log to. If the option is absent, logging will be directed to stdout")
.option("--no-auto-keygen", "Do not autogenerate keys on startup")
.option("--private-key <privateKeyPath>", "Alternative path to the private key (default <data>/keys/auth.key)")
.option("--public-key <publicKeyPath>", "Alternative path to the public key (default <data>/keys/auth.pub)")
.option("--refresh-token-ttl <refreshTokenTtl>", "TTL of issued refresh tokens in seconds", String(10 * 60 * 60 * 24 * 365 * 10))
.option("--access-token-ttl <accessTokenTtl>", "TTL of issued access tokens in seconds", String(10 * 60))
.option("--auth <provider1[,provider2 ...]>", "A list of auth providers to load", "password,anonymous,nickname")
.option("--https", "Enable HTTPS", false)
.option("--https-key <keyPath>", "Path to the HTTPS private key")
.option("--https-cert <certPath>", "Path to the HTTPS certificate chain")
.option("--https-address <port>", "HTTPS listen address", "0.0.0.0")
.option("--https-port <port>", "HTTPS listen port", 9443)
.option("--realm-size-reporting", "Enable periodic calculation of the size of realms", false)
.action((command) => {
command.parent.handler = this.runStart(command);
});
this.commander.command("backup")
.description("Create a backup of the Realm Object Server.")
.option("-f, --from <path>", "The source data path")
.option("-t, --to <path>", "The destination data path")
.option("--overwrite", "Overwrite the destination", false)
.action((command) => {
command.parent.handler = this.runBackup(command);
command.parent.terminate = true;
});
this.commander.command("init [projectName]")
.description("Create a new ROS project")
.option("-t --template <template>", "template type either js or ts. the default is ts", /^(ts|js)$/i, "ts")
.option("-l --latest", "Use latest version of ROS rather than the currently globally installed one", false)
.action((projectName, command) => {
command.parent.handler = this.runInit(projectName, command);
command.parent.terminate = true;
});
}
runInit(projectName, options) {
return __awaiter(this, void 0, void 0, function* () {
const version = options.latest ? "latest" : this.version;
if (!projectName) {
projectName = ".";
}
yield project_1.initProject(projectName, version, options.template);
});
}
run(argv) {
return __awaiter(this, void 0, void 0, function* () {
checkEngine_1.checkEngine();
const command = this.commander.parse(argv);
if (command.handler) {
try {
yield Promise.resolve(command.handler);
if (command.terminate) {
ProcessUtil_1.ProcessUtil.terminate({ success: true });
}
}
catch (err) {
ProcessUtil_1.ProcessUtil.terminate({ success: false, message: err.message });
}
}
else {
this.commander.outputHelp();
ProcessUtil_1.ProcessUtil.terminate({ success: false });
}
});
}
start(params) {
const _super = Object.create(null, {
start: { get: () => super.start }
});
return __awaiter(this, void 0, void 0, function* () {
if (!params.services) {
this.addService(new services.SyncProxyService());
const syncConfig = {
dataPath: path.join(params.dataPath, "sync"),
publicKeyPath: params.publicKeyPath || path.join(params.dataPath, "keys", "auth.pub"),
logLevel: params.logLevel,
enableDownloadLogCompaction: params.enableDownloadLogCompaction,
enableRealmStateSizeReporting: params.enableRealmSizeReporting,
maxDownloadSize: params.maxDownloadSize,
featureToken: params.featureToken,
historyTtl: params.historyTtl,
};
if (params.syncServiceConfigOverride) {
params.syncServiceConfigOverride(syncConfig);
}
this.addService(new services.SyncService(syncConfig));
const authService = new services.AuthService({
refreshTokenTtl: params.refreshTokenTtl,
accessTokenTtl: params.accessTokenTtl
});
if (params.authProviders && params.authProviders.length > 0) {
for (const provider of params.authProviders) {
authService.addProvider(provider);
}
}
else {
params.defaultProviders = params.defaultProviders || [
{ type: "PasswordAuthProvider", config: { autoCreateAdminUser: true } },
{ type: "AnonymousAuthProvider" },
{ type: "NicknameAuthProvider" }
];
authService.setDefaultProviders(params.defaultProviders);
}
this.addService(authService);
this.addService(new services.RealmDirectoryService());
this.addService(new services.LogService());
const permissionsServiceConfig = {};
if (params.permissionServiceConfigOverride) {
params.permissionServiceConfigOverride(permissionsServiceConfig);
}
this.addService(new services.PermissionsService(permissionsServiceConfig));
this.addService(new services.HealthService());
this.addService(new services.WelcomeService());
this.addService(new services.PasswordAuthUIService());
this.addService(new services.StatsService());
const graphQLServiceConfig = {};
if (params.graphQLServiceConfigOverride) {
params.graphQLServiceConfigOverride(graphQLServiceConfig);
}
this.addService(new services.GraphQLService(graphQLServiceConfig));
}
else {
this.addServices(...params.services);
}
this.addService(new services.MixpanelService({
serverVersion: this.version
}));
process.removeAllListeners("SIGINT");
process.on("SIGINT", () => __awaiter(this, void 0, void 0, function* () {
this.logger.info("Got signal SIGINT. Shutting down");
this.shutdown()
.then(() => ProcessUtil_1.ProcessUtil.terminate({ success: true }))
.catch((err) => {
ProcessUtil_1.ProcessUtil.terminate({ success: false, message: err.stack });
});
}));
process.removeAllListeners("SIGTERM");
process.on("SIGTERM", () => __awaiter(this, void 0, void 0, function* () {
this.logger.info("Got signal SIGTERM. Shutting down.");
this.shutdown()
.then(() => ProcessUtil_1.ProcessUtil.terminate({ success: true }))
.catch((err) => {
ProcessUtil_1.ProcessUtil.terminate({ success: false, message: err.stack });
});
}));
this.unhandledRejectionCallback = (reason, promise) => {
console.error("Detected an unhandled promise rejection at: ", promise, ", reason: ", reason);
};
process.on("unhandledRejection", this.unhandledRejectionCallback);
const startupDeadline = params.startupTimeout || 30000;
const startupTimeout = setTimeout(() => {
this.logger.fatal(`BasicServer didn't come up in ${startupDeadline}ms. Bailing.`);
ProcessUtil_1.ProcessUtil.terminate({ success: false });
}, startupDeadline);
try {
yield _super.start.call(this, params);
}
finally {
clearTimeout(startupTimeout);
}
});
}
shutdown() {
const _super = Object.create(null, {
shutdown: { get: () => super.shutdown }
});
return __awaiter(this, void 0, void 0, function* () {
yield _super.shutdown.call(this);
process.removeListener("unhandledRejection", this.unhandledRejectionCallback);
process.removeAllListeners("SIGTERM");
process.removeAllListeners("SIGINT");
});
}
runStart(command) {
return __awaiter(this, void 0, void 0, function* () {
const authProviders = [];
const providerNames = command.auth.split(",");
for (const providerName of providerNames) {
let providerClass;
try {
providerClass = require(`./auth/${providerName}`).default;
}
catch (e) {
ProcessUtil_1.ProcessUtil.terminate({ success: false, message: `The auth provider '${providerName}' cannot be found or does not have a default export` });
}
const options = {};
if (providerName === "password") {
options.autoCreateAdminUser = true;
}
authProviders.push(new providerClass(options));
}
const logLevel = Logger_1.SyncLogLevel[command.loglevel];
if (logLevel === undefined) {
throw new Server_1.ServerValidationError("log-level must be one of: all, trace, debug, detail, info, warn, error, fatal, off");
}
if (command.logfile) {
try {
fs.openSync(command.logfile, "a");
}
catch (e) {
ProcessUtil_1.ProcessUtil.terminate({ success: false, message: `The log file '${command.logfile}' cannot be opened for writing. Aborting` });
}
}
const refreshTokenTtl = parseInt(command.refreshTokenTtl, 10);
if (String(refreshTokenTtl) !== command.refreshTokenTtl || refreshTokenTtl <= 0) {
throw new Server_1.ServerValidationError("refresh-token-ttl must be a positive number.");
}
const accessTokenTtl = parseInt(command.accessTokenTtl, 10);
if (String(accessTokenTtl) !== command.accessTokenTtl || accessTokenTtl <= 0) {
throw new Server_1.ServerValidationError("access-token-ttl must be a positive number.");
}
const port = parseInt(command.port, 10);
if (!(port > 0 && port <= 65535)) {
throw new Server_1.ServerValidationError(`Invalid port number (1-65535): '${command.port}'`);
}
const params = {
publicKeyPath: command.publicKey,
privateKeyPath: command.privateKey,
accessTokenTtl: command.accessTokenTtl,
refreshTokenTtl: command.refreshTokenTtl,
dataPath: path.resolve(command.data),
address: command.address,
port: command.port,
logLevel: command.loglevel,
enableRealmSizeReporting: command.realmSizeReporting,
authProviders,
};
if (command.https) {
params.https = true;
params.httpsPort = parseInt(command.httpsPort, 10);
if (!(params.httpsPort > 0 && params.httpsPort <= 65535)) {
throw new Server_1.ServerValidationError(`Invalid HTTPS port number (1-65535): '${command.httpsPort}'`);
}
params.httpsAddress = command.httpsAddress;
params.httpsKeyPath = command.httpsKey;
params.httpsCertChainPath = command.httpsCert;
}
if (command.logfile) {
params.logger = new Logger_1.FileConsoleLogger(command.logfile, logLevel);
}
yield this.start(params);
});
}
runBackup(args, logger) {
return __awaiter(this, void 0, void 0, function* () {
logger = logger || new Logger_1.FileConsoleLogger();
if (!args.from || !args.to) {
throw new Error("Please specify both --from and --to. Aborting.");
}
const syncServerPath = resolve_1.sync("realm-sync-server");
const cmd = resolve_bin_1.sync(path.resolve(syncServerPath, "..", ".."), { executable: "realm-backup" });
const tmpdir = tmp.dirSync({ unsafeCleanup: true });
try {
if (args.overwrite) {
fs.emptyDirSync(path.resolve(args.to));
}
const child = child_process_1.spawn(cmd, [path.resolve(args.from), path.resolve(args.to)], {
cwd: tmpdir.name,
});
child.stdout.on("data", (data) => logger.info(data.toString().trim()));
child.stderr.on("data", (data) => logger.error(data.toString().trim()));
yield new Promise((resolve, reject) => {
child.on("exit", (code) => {
if (code) {
reject(new Error(`command failed: ${cmd}`));
}
resolve();
});
});
}
finally {
tmpdir.removeCallback();
}
});
}
}
exports.BasicServer = BasicServer;
//# sourceMappingURL=BasicServer.js.map