UNPKG

realm-object-server

Version:

Realm Object Server

320 lines 16.4 kB
"use strict"; 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