"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 discovery_1 = require("./discovery");
const Realm = require("realm");
const uuidv4 = require("uuid/v4");
const _ = require("lodash");
const Logger_1 = require("./shared/Logger");
const Token_1 = require("./shared/Token");
const errors = require("./errors");
const RealmFactory_1 = require("./RealmFactory");
const stats_1 = require("./stats");
const pathMatcher_1 = require("./shared/pathMatcher");
const shared_1 = require("./shared");
const Service_1 = require("./Service");
const Express = require("express");
const http = require("http");
const https = require("https");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const cors = require("cors");
const fs = require("fs-extra");
const path = require("path");
const url = require("url");
const URI = require("urijs");
const NodeRSA = require("node-rsa");
const httpUtil = require("./shared/httpUtil");
const openFiles = require("./shared/openFiles");
const AuthClient_1 = require("./service-clients/AuthClient");
const PermissionsClient_1 = require("./service-clients/PermissionsClient");
const RealmDirectoryClient_1 = require("./service-clients/RealmDirectoryClient");
const HealthClient_1 = require("./service-clients/HealthClient");
const AdminRealm_1 = require("./realms/AdminRealm");
class ServerValidationError extends Error {
}
exports.ServerValidationError = ServerValidationError;
class ServerStartError extends Error {
}
exports.ServerStartError = ServerStartError;
class Server {
    constructor() {
        this.services = [];
        this.version = require('../package.json').version;
        this.numberOfOpenFiles = 0;
    }
    get started() {
        return this._started;
    }
    get dataPath() {
        return this.serverConfig.dataPath;
    }
    get discovery() {
        return this.serverConfig.discovery;
    }
    get logger() {
        return this.serverConfig ? this.serverConfig.logger : null;
    }
    get publicKeyPath() {
        return this.serverConfig.publicKeyPath;
    }
    get privateKeyPath() {
        return this.serverConfig.privateKeyPath;
    }
    start(config) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.serverConfig !== undefined) {
                throw new Error('The server has already been started.');
            }
            const validatedConfig = yield this.validateStartConfig(config);
            this.serverConfig = validatedConfig.config;
            this.privateKey = validatedConfig.privateKey;
            this.statsSink = config.statsSink;
            this.statsStorage = config.statsStorage;
            Realm.Sync.setSyncLogger((level, message) => {
                const syncLogLevel = Logger_1.SyncLogLevel[level];
                const mapping = ["all", "trace", "debug", "detail", "detail", "detail", "detail", "detail", "detail"];
                const realLevel = mapping[level];
                if (this.serverConfig) {
                    this.logger[realLevel](`sync-client: ${message}`, { level: syncLogLevel });
                }
            });
            this.logger.info(`Realm Object Server version ${this.version} is starting`);
            this.publicKey = yield fs.readFile(config.publicKeyPath, 'utf-8');
            this.adminToken = Token_1.generateAdminToken({
                privateKey: this.privateKey,
                canSkipRevocationCheck: true,
            });
            this.authClient = new AuthClient_1.AuthClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName);
            this.permissionsClient = new PermissionsClient_1.PermissionsClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName);
            this.realmDirectoryClient = new RealmDirectoryClient_1.RealmDirectoryClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName);
            this.healthClient = new HealthClient_1.HealthClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName);
            this.realmFactory = new RealmFactory_1.RealmFactory(this);
            this.tokenValidator = new shared_1.TokenValidator({
                logger: this.logger,
                publicKey: this.publicKey,
                realmFactory: this.realmFactory,
                disableRevocation: this.serverConfig.disableTokenRevocation,
            });
            yield this.injectLogger();
            this.expressApp = Express();
            this.expressApp.disable('x-powered-by');
            this.expressApp.use((req, res, next) => {
                req['id'] = uuidv4();
                next();
            });
            this.expressApp.use(bodyParser.json({
                limit: config.jsonBodyLimit || '100kb'
            }));
            this.expressApp.use((_, res, next) => {
                res.header("Server", httpUtil.makeResponseHeaderServer(this.version));
                next();
            });
            this.expressApp.use((req, res, next) => {
                if (Logger_1.SyncLogLevel[this.serverConfig.logLevel] <= Logger_1.SyncLogLevel.trace) {
                    const msg = "\nRequest:  " + req['id'] + "\n" + httpUtil.stringifyRequest(req);
                    this.httpAccessLogger.trace(msg);
                }
                next();
            });
            if (config.middlewares) {
                this.expressApp.use(config.middlewares);
            }
            this.httpBytesSentCounter = this.statsSink.counter({
                name: "ros_http_bytes_sent",
                help: "Realm Object Server Sent HTTP size counter in bytes",
                labelNames: [],
            });
            this.httpBytesReceivedCounter = this.statsSink.counter({
                name: "ros_http_bytes_received",
                help: "Realm Object Server Received HTTP size counter in bytes",
                labelNames: [],
            });
            this.httpServer = http.createServer(this.expressApp);
            this.httpServer.on('upgrade', this.upgradeHandler.bind(this));
            this.httpServer.on('connection', this.connectionHandler.bind(this));
            if (config.https) {
                const key = yield fs.readFile(config.httpsKeyPath);
                try {
                    new NodeRSA(key);
                }
                catch (err) {
                    throw new ServerStartError(`Could not create HTTPS server: Private key is malformed.`);
                }
                const cert = yield fs.readFile(config.httpsCertChainPath, "utf-8");
                const regex = /-+BEGIN CERTIFICATE-+([\s\S]+?)-+END CERTIFICATE-+/;
                if (!regex.test(cert)) {
                    throw new ServerStartError(`Could not create HTTPS server: Certificate is malformed.`);
                }
                try {
                    this.httpsServer = https.createServer({ key, cert }, this.expressApp);
                }
                catch (err) {
                    throw new ServerStartError(`Could not create HTTPS server: ${err.stack}`);
                }
                this.httpsServer.on("upgrade", this.upgradeHandler.bind(this));
                this.httpsServer.on("connection", this.connectionHandler.bind(this));
            }
            this.httpAccessLogger = this.logger.withContext({ service: 'http' });
            const ACCESS_LOG_FORMAT = 'short';
            this.expressApp.use(morgan(ACCESS_LOG_FORMAT, {
                skip: (req, res) => res.statusCode >= 400,
                stream: {
                    write: (msg) => {
                        this.httpAccessLogger.debug(msg.trim());
                    }
                }
            }));
            this.expressApp.use(morgan(ACCESS_LOG_FORMAT, {
                skip: (req, res) => res.statusCode < 400,
                stream: {
                    write: (msg) => {
                        this.httpAccessLogger.detail(msg.trim());
                    }
                }
            }));
            this.expressApp.use(this.authenticationMiddleware.bind(this));
            this.expressApp.set('view engine', 'html');
            this.expressApp.engine('html', require('hbs').__express);
            yield shared_1.Promisify(this.httpServer.listen.bind(this.httpServer), config.port, config.address);
            if (this.httpsServer) {
                yield shared_1.Promisify(this.httpsServer.listen.bind(this.httpsServer), config.httpsPort, config.httpsAddress);
            }
            yield this.startServices();
            this.logger.detail("All services have started");
            yield this.tokenValidator.start().catch((err) => {
                this.logger.error(`Token Validator failed to start: ${err}`);
            });
            this.expressApp.use(this.errorHandler.bind(this));
            this._started = true;
            const address = this.httpServer.address();
            for (const service of this.services) {
                const serverStartedFunctionName = service.serverStartedFunctionName;
                if (serverStartedFunctionName) {
                    yield Promise.resolve(service[serverStartedFunctionName](this));
                }
            }
            const listeningOn = [`http://${address.address}:${address.port}`];
            if (this.httpsServer) {
                const httpsAddress = this.httpsServer.address();
                listeningOn.push(`https://${httpsAddress.address}:${httpsAddress.port}`);
            }
            this.periodicLoggingOfOpenFiles();
            this.logger.info(`Realm Object Server has started and is listening on ${listeningOn.join(" and ")}`);
        });
    }
    get address() {
        if (this.httpServer) {
            const address = this.httpServer.address();
            return `${address.address}:${address.port}`;
        }
        return undefined;
    }
    get secureAddress() {
        if (this.httpsServer) {
            const address = this.httpsServer.address();
            return `${address.address}:${address.port}`;
        }
        return undefined;
    }
    shutdown() {
        return __awaiter(this, void 0, void 0, function* () {
            yield Promise.all(this.services
                .filter(service => service.stoppingFunctionName)
                .map(service => shared_1.timeout(Promise.resolve(service[service.stoppingFunctionName]()), 30000).catch()));
            if (this.tokenValidator) {
                this.tokenValidator.stop();
                delete this.tokenValidator;
            }
            yield Promise.all(this.services.map((service) => __awaiter(this, void 0, void 0, function* () {
                service.state = Service_1.ServiceState.Stopping;
                const serviceName = service.constructor.serviceName;
                if (serviceName) {
                    this.logger.detail(`Stopping service ${serviceName}`);
                }
                yield this.discovery.deregisterService(service);
                if (service.stopFunctionName) {
                    yield Promise.resolve(service[service.stopFunctionName]());
                }
                service.state = Service_1.ServiceState.Stopped;
                if (serviceName) {
                    this.logger.detail(`Stopped service ${serviceName}`);
                }
            })));
            this.services = [];
            if (this.httpServer) {
                yield shared_1.Promisify(this.httpServer.close.bind(this.httpServer)).catch((err) => { });
                delete this.httpServer;
            }
            if (this.httpsServer) {
                yield shared_1.Promisify(this.httpsServer.close.bind(this.httpsServer)).catch((err) => { });
                delete this.httpsServer;
            }
            if (this.realmFactory) {
                yield this.realmFactory.close();
                delete this.realmFactory;
            }
            if (this.logger) {
                this.logger.info("Realm Object Server has stopped");
            }
            delete this.serverConfig;
            delete this.expressApp;
            this._started = false;
        });
    }
    addServices(...services) {
        services.forEach(s => this.addService(s));
        return this;
    }
    addService(service, config) {
        if (this.started) {
            throw new Error('Services can only be added before starting the server.');
        }
        let instance;
        if (typeof (service) === 'string') {
            const Services = require("./services");
            instance = new Services[service](config);
        }
        else {
            instance = service;
        }
        this.services.push(instance);
        return this;
    }
    removeService(service) {
        if (this.started) {
            throw new Error('Services can only be removed before starting the server.');
        }
        if (typeof service === 'string') {
            const survivingServices = [];
            for (const instance of this.services) {
                if (instance.constructor.serviceName !== service) {
                    survivingServices.push(service);
                }
            }
            this.services = survivingServices;
        }
        else {
            const index = this.services.indexOf(service);
            if (index > -1) {
                this.services.splice(index, 1);
            }
        }
        return this;
    }
    getService(name) {
        return this.services.find((s) => s.constructor.serviceName === name);
    }
    upgradeHandler(req, socket, head) {
        return __awaiter(this, void 0, void 0, function* () {
            const requestPath = url.parse(req.url).path;
            req['id'] = uuidv4();
            if (Logger_1.SyncLogLevel[this.serverConfig.logLevel] <= Logger_1.SyncLogLevel.trace) {
                const msg = "\nRequest:  " + req['id'] + "\n" + httpUtil.stringifyRequest(req);
                this.httpAccessLogger.trace(msg);
            }
            let numberOfMatches = 0;
            for (const service of this.services) {
                const baseRoute = service.constructor.baseRoute;
                if (!baseRoute) {
                    continue;
                }
                for (const upgradeRoute of (service.upgradeRoutes || [])) {
                    let concatenatedServicePath = baseRoute;
                    if (upgradeRoute.path !== '/') {
                        concatenatedServicePath = URI.joinPaths(baseRoute, upgradeRoute.path).toString();
                    }
                    const params = pathMatcher_1.pathMatcher(concatenatedServicePath, requestPath);
                    if (params === false) {
                        continue;
                    }
                    req.params = params;
                    numberOfMatches = numberOfMatches + 1;
                    let parameterArguments = service.parameterArguments || [];
                    parameterArguments = parameterArguments.filter(p => p.functionName === upgradeRoute.functionName);
                    const handleError = (err) => {
                        const problem = this.processException(err);
                        const headers = {
                            "Server": httpUtil.makeResponseHeaderServer(this.version)
                        };
                        const response = httpUtil.makeResponse(problem.status, headers);
                        const msg = "HTTP upgrade failed (service did not respond properly) " +
                            JSON.stringify(err) + "\n" +
                            "Request:  " + req['id'] + "\n" +
                            httpUtil.stringifyRequest(req) + "\nResponse:\n" + response;
                        this.httpAccessLogger.error(msg);
                        socket.write(response);
                        socket.end();
                    };
                    try {
                        if (parameterArguments.length > 0) {
                            throw new Error('Decorator Annotations for @Upgrade are not yet implemented');
                        }
                        if (service.state !== Service_1.ServiceState.Running) {
                            throw new errors.realm.ServiceUnavailable();
                        }
                        const promise = service[upgradeRoute.functionName](req, socket, head);
                        if (promise instanceof Promise) {
                            yield promise.catch(handleError);
                        }
                    }
                    catch (err) {
                        handleError(err);
                    }
                }
            }
            if (numberOfMatches === 0) {
                const headers = {
                    "Server": httpUtil.makeResponseHeaderServer(this.version)
                };
                const response = httpUtil.makeResponse(404, headers);
                const msg = "HTTP upgrade failed(no service found)\nRequest:  " + req['id'] + "\n" +
                    httpUtil.stringifyRequest(req) + "\nResponse:\n" + response;
                this.httpAccessLogger.error(msg);
                socket.write(response);
                socket.end();
            }
        });
    }
    connectionHandler(socket) {
        let sent = 0;
        let received = 0;
        const flushStats = () => {
            this.httpBytesReceivedCounter.inc({}, socket.bytesRead - received);
            received = socket.bytesRead;
            this.httpBytesSentCounter.inc({}, socket.bytesWritten - sent);
            sent = socket.bytesWritten;
        };
        const timer = setInterval(flushStats, 30 * 1000);
        socket.on("close", () => {
            clearInterval(timer);
            flushStats();
        });
    }
    injectLogger() {
        return __awaiter(this, void 0, void 0, function* () {
            const promises = this.services.map((service) => {
                const promise = Promise.resolve();
                let functionName = service.unmuteFunctionName;
                if (functionName && service[functionName]) {
                    promise.then(service[functionName](this.logger));
                }
                functionName = service.unmuteContextualFunctionName;
                if (functionName && service[functionName]) {
                    const serviceName = service.constructor.serviceName;
                    promise.then(service[functionName](this.logger.withContext({ service: serviceName })));
                }
                return promise;
            });
            yield Promise.all(promises);
        });
    }
    addServiceEndpoints(service) {
        const baseRoute = service.constructor.baseRoute;
        const routes = service.routes || [];
        const serviceStaticRoutes = service.serveStaticRoutes || [];
        const corsPaths = service.constructor.corsPaths || {};
        if (serviceStaticRoutes.length === 0 && routes.length === 0) {
            return;
        }
        else if (!baseRoute) {
            throw new Error(`${service.constructor.name} does not have a base route`);
        }
        const router = Express.Router();
        const middlewares = service.constructor.middlewaresBeforeService;
        if (middlewares) {
            router.use(middlewares);
        }
        router.use((req, res, next) => {
            if (service.state !== Service_1.ServiceState.Running) {
                throw new errors.realm.ServiceUnavailable();
            }
            next();
        });
        for (const serviceStaticRoute of serviceStaticRoutes) {
            router.use(serviceStaticRoute.path, Express.static(serviceStaticRoute.staticRoot));
        }
        for (const path in corsPaths) {
            const options = corsPaths[path] || {};
            router.use(path, cors(options));
        }
        for (const route of routes) {
            const m = service.middlewaresBeforeFunction || [];
            const middlewaresForThisFunction = m.filter(m => m.functionName === route.functionName);
            const middlewares = [].concat.apply([], middlewaresForThisFunction.map(x => x.middlewares));
            const handler = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
                const functionName = route.functionName;
                let parameterArgumentMetaData = service.parameterArguments || [];
                parameterArgumentMetaData = parameterArgumentMetaData.filter(x => x.functionName === functionName);
                try {
                    let result;
                    if (parameterArgumentMetaData.length > 0) {
                        const args = [];
                        for (const p of parameterArgumentMetaData) {
                            switch (p.type) {
                                case 'body':
                                    args[p.argumentIndex] = p.keyPath ? _.get(req.body, p.keyPath) : req.body;
                                    break;
                                case 'query':
                                    args[p.argumentIndex] = p.keyPath ? _.get(req.query, p.keyPath) : req.query;
                                    break;
                                case 'headers':
                                    args[p.argumentIndex] = p.keyPath ? req.headers[p.keyPath.toLowerCase()] : req.headers;
                                    break;
                                case 'params':
                                    args[p.argumentIndex] = p.keyPath ? _.get(req.params, p.keyPath) : req.params;
                                    break;
                                case 'request':
                                    args[p.argumentIndex] = req;
                                    break;
                                case 'response':
                                    args[p.argumentIndex] = res;
                                    break;
                                case 'next':
                                    args[p.argumentIndex] = next;
                                    break;
                            }
                        }
                        result = yield Promise.resolve(service[functionName](...args));
                        if (result) {
                            if (result.toJSON) {
                                res.json(result.toJSON());
                            }
                            else {
                                res.json(result);
                            }
                        }
                    }
                    else {
                        result = yield Promise.resolve(service[functionName](req, res, next));
                        if (result) {
                            res.json(result);
                        }
                    }
                    if (Logger_1.SyncLogLevel[this.serverConfig.logLevel] <= Logger_1.SyncLogLevel.trace) {
                        let msg = '\n' + 'Response: ' + req['id'] + '\n' + httpUtil.stringifyResponse(res);
                        if (result) {
                            msg += '\n';
                            if (result.toJSON) {
                                msg += JSON.stringify(result.toJSON());
                            }
                            else {
                                msg += JSON.stringify(result);
                            }
                        }
                        this.httpAccessLogger.trace(msg);
                    }
                }
                catch (err) {
                    const msg = "HTTP response: " + req['id'] + "\n" + JSON.stringify(err);
                    this.httpAccessLogger.error(msg);
                    next(err);
                }
            });
            router[route.httpMethod](route.path, middlewares, handler);
        }
        this.expressApp.use(baseRoute, router);
    }
    startServices() {
        return __awaiter(this, void 0, void 0, function* () {
            yield Promise.all(this.services.map(this.startService.bind(this)));
        });
    }
    startService(service) {
        return __awaiter(this, void 0, void 0, function* () {
            service.state = Service_1.ServiceState.Starting;
            service.stats = this.statsSink;
            service.statsStorage = this.statsStorage;
            const serviceName = service.constructor.serviceName;
            if (serviceName) {
                this.logger.detail(`Starting service ${serviceName}`);
            }
            const startFunctionName = service.startFunctionName;
            if (startFunctionName && service[startFunctionName]) {
                yield Promise.resolve(service[startFunctionName](this));
            }
            this.addServiceEndpoints(service);
            service.state = Service_1.ServiceState.Running;
            let address;
            const addressFunctionName = service.addressFunctionName;
            if (addressFunctionName) {
                address = service[addressFunctionName]();
            }
            else {
                address = this.httpServer.address();
            }
            yield this.discovery.registerService(service, address.address, address.port);
            if (serviceName) {
                this.logger.detail(`Started service ${serviceName}`);
            }
        });
    }
    authenticationMiddleware(req, res, next) {
        try {
            const authorization = req.headers[this.serverConfig.authorizationHeaderName] || req.headers['authorization'] || req.headers['x-realm-access-token'];
            if (authorization) {
                const tokenData = authorization;
                req.authToken = this.tokenValidator.parse(tokenData);
            }
            next();
        }
        catch (err) {
            if (!(err instanceof errors.realm.InvalidCredentials) &&
                !(err instanceof errors.realm.AccessDenied)) {
                err = new errors.realm.InvalidCredentials({
                    detail: err.message,
                });
            }
            next(err);
        }
    }
    processException(error) {
        if (error instanceof errors.JSONError) {
            return error;
        }
        else {
            const status = error.status || error.statusCode;
            if (status) {
                return new errors.JSONError({
                    status: status,
                    stack: error.stack,
                    title: error.message,
                });
            }
            this.logger.error(`Internal server error: ${error.stack}`);
            return new errors.JSONError({
                status: 500,
                stack: error.stack,
                title: 'Internal Server Error',
            });
        }
    }
    errorHandler(error, req, res, next) {
        if (res.headersSent) {
            next(error);
            return;
        }
        const jsonError = this.processException(error);
        res.setHeader('Content-Type', 'application/json');
        res.status(jsonError.status).json(jsonError.toJSON());
    }
    validateStartConfig(config) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!config.discovery) {
                config.discovery = new discovery_1.SingleProcessDiscovery();
            }
            if (!config.logger) {
                config.logger = new Logger_1.ConsoleLogger(config.logLevel || "info");
            }
            if (!config.address) {
                config.address = "0.0.0.0";
            }
            if (!config.httpsAddress) {
                config.httpsAddress = config.address;
            }
            if (config.port === undefined) {
                config.port = 9080;
            }
            if (config.httpsPort === undefined) {
                config.httpsPort = 9443;
            }
            if (config.autoKeyGen === undefined) {
                config.autoKeyGen = true;
            }
            if (config.writeAdminTokenToJson === undefined) {
                config.writeAdminTokenToJson = true;
            }
            if (fs.existsSync(config.dataPath)) {
                const stat = fs.statSync(config.dataPath);
                if (!stat.isDirectory()) {
                    throw new ServerValidationError(`The data directory '${config.dataPath}' is not a directory`);
                }
                try {
                    fs.accessSync(config.dataPath, fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK);
                }
                catch (e) {
                    throw new ServerValidationError(`The data directory '${config.dataPath}' does not have 'rwx' permissions`);
                }
            }
            else {
                try {
                    fs.mkdirSync(config.dataPath, 0o700);
                }
                catch (e) {
                    throw new ServerValidationError(`The data directory '${config.dataPath}' cannot be created.`);
                }
            }
            if (!config.privateKeyPath) {
                config.privateKeyPath = path.join(config.dataPath, "keys", "auth.key");
            }
            let privateKey;
            try {
                privateKey = yield fs.readFile(config.privateKeyPath, 'utf-8');
            }
            catch (e) {
                if (config.autoKeyGen) {
                    const key = new NodeRSA({ b: config.autoKeyGenBits || 2048 });
                    privateKey = key.exportKey("pkcs8-private-pem");
                    yield fs.mkdirp(path.dirname(config.privateKeyPath));
                    yield fs.writeFile(config.privateKeyPath, privateKey);
                }
                else {
                    throw new ServerValidationError(`The private key '${config.privateKeyPath}' does not exist or is not readable.`);
                }
            }
            const adminJsonPath = path.join(config.dataPath, 'keys', 'admin.json');
            if (config.writeAdminTokenToJson && !fs.pathExistsSync(adminJsonPath)) {
                const adminToken = Token_1.generateAdminToken({
                    privateKey: privateKey,
                });
                const stringifiedJSON = JSON.stringify({
                    'ADMIN_TOKEN': adminToken
                }, null, 4);
                yield fs.mkdirp(path.dirname(adminJsonPath));
                yield fs.writeFile(adminJsonPath, stringifiedJSON);
            }
            if (!config.publicKeyPath) {
                config.publicKeyPath = path.join(config.dataPath, 'keys', 'auth.pub');
            }
            try {
                fs.openSync(config.publicKeyPath, 'r');
            }
            catch (e) {
                if (config.autoKeyGen) {
                    const privateKeyData = yield fs.readFile(config.privateKeyPath);
                    const privateKey = new NodeRSA(privateKeyData);
                    const keyData = privateKey.exportKey("pkcs8-public-pem");
                    yield fs.mkdirp(path.dirname(config.publicKeyPath));
                    yield fs.writeFile(config.publicKeyPath, keyData);
                }
                else {
                    throw new ServerValidationError(`The private key '${config.publicKeyPath}' does not exist or is not readable.`);
                }
            }
            if (config.https) {
                if (!config.httpsKeyPath) {
                    throw new ServerValidationError("HTTPS was enabled but a path to the key was not provided.");
                }
                try {
                    fs.openSync(config.httpsKeyPath, 'r');
                }
                catch (e) {
                    throw new ServerValidationError("The HTTPS key could not be read.");
                }
                if (!config.httpsCertChainPath) {
                    throw new ServerValidationError("HTTPS was enabled but a path to the certificate chain was not provided.");
                }
                try {
                    fs.openSync(config.httpsCertChainPath, 'r');
                }
                catch (e) {
                    throw new ServerValidationError("The HTTPS certificate chain could not be read.");
                }
            }
            if ((config.statsSink && !config.statsStorage) || (config.statsStorage && !config.statsSink)) {
                throw new ServerValidationError("You must supply statsSink and statsStorage together");
            }
            if (!config.statsSink && !config.statsStorage) {
                config.statsSink = config.statsStorage = new stats_1.SimpleStats();
            }
            if (!config.authorizationHeaderName) {
                config.authorizationHeaderName = "authorization";
            }
            else {
                config.authorizationHeaderName = config.authorizationHeaderName.toLowerCase();
            }
            return { config, privateKey };
        });
    }
    periodicLoggingOfOpenFiles() {
        const server = this;
        setInterval(function () {
            return __awaiter(this, void 0, void 0, function* () {
                const summary = yield openFiles.getSummary();
                if (summary.total !== server.numberOfOpenFiles) {
                    server.numberOfOpenFiles = summary.total;
                    if (server.logger) {
                        server.logger.debug(`Open files in Realm Object Server: ${JSON.stringify(summary)}`);
                    }
                }
            });
        }, 10000);
    }
    openRealm(pathOrDefinition, schema) {
        if (typeof pathOrDefinition === 'string') {
            return this.realmFactory.open({
                remotePath: pathOrDefinition,
                schema: schema
            });
        }
        return this.realmFactory.open(pathOrDefinition);
    }
    applyPermissions(condition, realmPath, accessLevel) {
        const accessLevels = ['none', 'read', 'write', 'admin'];
        if (!realmPath) {
            throw new errors.realm.MissingParameters('realmPath');
        }
        if (!condition) {
            throw new errors.realm.MissingParameters('condition');
        }
        if (accessLevels.indexOf(accessLevel) === -1) {
            throw new errors.realm.InvalidParameters('accessLevel');
        }
        return this.permissionsClient.applyPermissions({
            condition,
            realmPath,
            accessLevel
        });
    }
    ensureRealmExists(realmPath, ownerId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!realmPath) {
                throw new errors.realm.MissingParameters('realmPath');
            }
            if (ownerId) {
                const homeFolder = new URI(realmPath).segment(0);
                if (homeFolder !== ownerId && homeFolder !== '~') {
                    throw new errors.realm.InvalidParameters({
                        name: 'realmPath',
                        reason: "realmPath must point to a Realm in the owner's home directory (/ownerId/some-realm)."
                    });
                }
            }
            else if (realmPath.indexOf('~') !== -1) {
                throw new errors.realm.InvalidParameters({
                    name: 'realmPath',
                    reason: "realmPath must not contain ~ when ownerId is not specified"
                });
            }
            const shouldCreate = true;
            const realmType = AdminRealm_1.RealmType.full;
            return yield this.realmDirectoryClient.findByPath({
                realmPath: realmPath,
                shouldCreate: shouldCreate,
                realmType: realmType,
                ownerId: ownerId
            });
        });
    }
}
exports.Server = Server;
//# sourceMappingURL=Server.js.map