"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiServer = void 0;
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const tls_1 = __importDefault(require("tls"));
const http_1 = require("http");
const https_1 = require("https");
const run_time_error_1 = require("run-time-error");
const semver_1 = require("semver");
const lmify_1 = __importDefault(require("lmify"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const express_http_proxy_1 = __importDefault(require("express-http-proxy"));
const grpc_js_1 = require("@grpc/grpc-js");
const grpc_js_2 = require("@grpc/grpc-js");
const express_1 = __importDefault(require("express"));
const express_openapi_validator_1 = require("express-openapi-validator");
const compression_1 = __importDefault(require("compression"));
const body_parser_1 = __importDefault(require("body-parser"));
const cors_1 = __importDefault(require("cors"));
const socket_io_1 = require("socket.io");
const socketio_jwt_1 = require("@thream/socketio-jwt");
const cactus_core_api_1 = require("@hyperledger/cactus-core-api");
const cactus_core_1 = require("@hyperledger/cactus-core");
const cactus_common_1 = require("@hyperledger/cactus-common");
const openapi_json_1 = __importDefault(require("../json/openapi.json"));
const prometheus_exporter_1 = require("./prometheus-exporter/prometheus-exporter");
const authorizer_factory_1 = require("./authzn/authorizer-factory");
const typescript_axios_1 = require("./generated/openapi/typescript-axios");
const watch_healthcheck_v1_endpoint_1 = require("./web-services/watch-healthcheck-v1-endpoint");
const default_service = __importStar(require("./generated/proto/protoc-gen-ts/services/default_service"));
const grpc_server_api_server_1 = require("./web-services/grpc/grpc-server-api-server");
const determine_address_family_1 = require("./common/determine-address-family");
class ApiServer {
    constructor(options) {
        this.options = options;
        if (!options) {
            throw new Error(`ApiServer#ctor options was falsy`);
        }
        if (!options.config) {
            throw new Error(`ApiServer#ctor options.config was falsy`);
        }
        cactus_common_1.LoggerProvider.setLogLevel(options.config.logLevel);
        if (this.options.httpServerApi) {
            this.httpServerApi = this.options.httpServerApi;
        }
        else if (this.options.config.apiTlsEnabled) {
            this.httpServerApi = https_1.createServer({
                key: this.options.config.apiTlsKeyPem,
                cert: this.options.config.apiTlsCertPem,
            });
        }
        else {
            this.httpServerApi = http_1.createServer();
        }
        if (this.options.httpServerCockpit) {
            this.httpServerCockpit = this.options.httpServerCockpit;
        }
        else if (this.options.config.cockpitTlsEnabled) {
            this.httpServerCockpit = https_1.createServer({
                key: this.options.config.cockpitTlsKeyPem,
                cert: this.options.config.cockpitTlsCertPem,
            });
        }
        else {
            this.httpServerCockpit = http_1.createServer();
        }
        this.grpcServer = this.options.grpcServer || new grpc_js_1.Server({});
        this.wsApi = new socket_io_1.Server();
        this.expressApi = express_1.default();
        this.expressCockpit = express_1.default();
        if (this.options.prometheusExporter) {
            this.prometheusExporter = this.options.prometheusExporter;
        }
        else {
            this.prometheusExporter = new prometheus_exporter_1.PrometheusExporter({
                pollingIntervalInMin: 1,
            });
        }
        this.prometheusExporter.startMetricsCollection();
        this.prometheusExporter.setTotalPluginImports(this.getPluginImportsCount());
        this.log = cactus_common_1.LoggerProvider.getOrCreate({
            label: "api-server",
            level: options.config.logLevel,
        });
        const defaultPluginsPath = path_1.default.join(os_1.default.tmpdir(), "org", "hyperledger", "cactus", "plugins");
        const { pluginsPath } = Object.assign(Object.assign({ pluginsPath: defaultPluginsPath }, JSON.parse(this.options.config.pluginManagerOptionsJson)), this.options.pluginManagerOptions);
        this.pluginsPath = pluginsPath;
        this.log.debug("pluginsPath: %o", pluginsPath);
    }
    get className() {
        return ApiServer.CLASS_NAME;
    }
    getPrometheusExporter() {
        return this.prometheusExporter;
    }
    async getPrometheusExporterMetrics() {
        this.prometheusExporter.setTotalPluginImports(this.getPluginImportsCount());
        const res = await this.prometheusExporter.getPrometheusMetrics();
        this.log.debug(`getPrometheusExporterMetrics() response: %o`, res);
        return res;
    }
    getPluginImportsCount() {
        var _a;
        return ((_a = this.pluginRegistry) === null || _a === void 0 ? void 0 : _a.plugins.length) || 0;
    }
    async start() {
        this.checkNodeVersion();
        const tlsMaxVersion = this.options.config.tlsDefaultMaxVersion;
        this.log.info("Setting tls.DEFAULT_MAX_VERSION to %s...", tlsMaxVersion);
        tls_1.default.DEFAULT_MAX_VERSION = tlsMaxVersion;
        try {
            const { cockpitTlsEnabled, apiTlsEnabled } = this.options.config;
            const addressInfoCockpit = await this.startCockpitFileServer();
            const addressInfoApi = await this.startApiServer();
            const addressInfoGrpc = await this.startGrpcServer();
            {
                const { port, address } = addressInfoGrpc;
                const grpcUrl = `${address}:${port}`;
                this.log.info(`Cactus gRPC reachable ${grpcUrl}`);
            }
            {
                const { apiHost: host } = this.options.config;
                const { port } = addressInfoApi;
                const protocol = apiTlsEnabled ? "https:" : "http:";
                const httpUrl = `${protocol}//${host}:${port}`;
                this.log.info(`Cactus API reachable ${httpUrl}`);
            }
            {
                const { cockpitHost: host } = this.options.config;
                const { port } = addressInfoCockpit;
                const protocol = cockpitTlsEnabled ? "https:" : "http:";
                const httpUrl = `${protocol}//${host}:${port}`;
                this.log.info(`Cactus Cockpit reachable ${httpUrl}`);
            }
            return { addressInfoCockpit, addressInfoApi, addressInfoGrpc };
        }
        catch (ex) {
            const errorMessage = `Failed to start ApiServer: ${ex.stack}`;
            this.log.error(errorMessage);
            this.log.error(`Attempting shutdown...`);
            try {
                await this.shutdown();
                this.log.info(`Server shut down after crash OK`);
            }
            catch (ex) {
                this.log.error(ApiServer.E_POST_CRASH_SHUTDOWN, ex);
            }
            throw new Error(errorMessage);
        }
    }
    /**
     * Verifies that the currently running NodeJS process is at least of a certain
     * NodeJS version as specified by the configuration.
     *
     * @throws {Error} if the version contraint is not satisfied by the runtime.
     */
    checkNodeVersion(currentVersion = process.version) {
        if (semver_1.gte(this.options.config.minNodeVersion, currentVersion)) {
            const msg = `ApiServer#checkNodeVersion() detected NodeJS ` +
                `v${process.version} that is outdated as per the configuration. ` +
                `If you must run on this NodeJS version you can override the minimum ` +
                `acceptable version via config parameters of the API server. ` +
                `Though doing so may lead to vulnerabilities in your deployment. ` +
                `You've been warned.`;
            throw new Error(msg);
        }
    }
    getHttpServerApi() {
        return this.httpServerApi;
    }
    getHttpServerCockpit() {
        return this.httpServerCockpit;
    }
    async getOrInitPluginRegistry() {
        if (!this.pluginRegistry) {
            if (!this.options.pluginRegistry) {
                this.log.info(`getOrInitPluginRegistry() initializing a new one...`);
                this.pluginRegistry = await this.initPluginRegistry();
            }
            else {
                this.log.info(`getOrInitPluginRegistry() re-using injected one...`);
                this.pluginRegistry = this.options.pluginRegistry;
            }
        }
        await this.prometheusExporter.setTotalPluginImports(await this.getPluginImportsCount());
        return this.pluginRegistry;
    }
    async initPluginRegistry() {
        const registry = new cactus_core_1.PluginRegistry({ plugins: [] });
        const { plugins } = this.options.config;
        this.log.info(`Instantiated empty registry, invoking plugin factories...`);
        for (const pluginImport of plugins) {
            const plugin = await this.instantiatePlugin(pluginImport, registry);
            registry.add(plugin);
        }
        return registry;
    }
    async instantiatePlugin(pluginImport, registry) {
        const { logLevel } = this.options.config;
        const { packageName, options } = pluginImport;
        this.log.info(`Creating plugin from package: ${packageName}`, options);
        const pluginOptions = Object.assign(Object.assign({}, options), { logLevel, pluginRegistry: registry });
        await this.installPluginPackage(pluginImport);
        const packagePath = path_1.default.join(this.pluginsPath, options.instanceId, "node_modules", packageName);
        this.log.debug("Package path: %o", packagePath);
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const pluginPackage = require(/* webpackIgnore: true */ packagePath);
        const createPluginFactory = pluginPackage.createPluginFactory;
        const pluginFactoryOptions = {
            pluginImportType: pluginImport.type,
        };
        const pluginFactory = await createPluginFactory(pluginFactoryOptions);
        const plugin = await pluginFactory.create(pluginOptions);
        return plugin;
    }
    async installPluginPackage(pluginImport) {
        const fnTag = `ApiServer#installPluginPackage()`;
        const { packageName: pkgName } = pluginImport;
        const instanceId = pluginImport.options.instanceId;
        const pluginPackageDir = path_1.default.join(this.pluginsPath, instanceId);
        try {
            await fs_extra_1.default.mkdirp(pluginPackageDir);
            this.log.debug(`${pkgName} plugin package dir: %o`, pluginPackageDir);
        }
        catch (ex) {
            const errorMessage = "Could not create plugin installation directory, check the file-system permissions.";
            throw new run_time_error_1.RuntimeError(errorMessage, ex);
        }
        try {
            lmify_1.default.setPackageManager("npm");
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-expect-error
            lmify_1.default.setRootDir(pluginPackageDir);
            this.log.debug(`Installing ${pkgName} for plugin import`, pluginImport);
            const out = await lmify_1.default.install([
                pkgName,
                "--production",
                "--audit=false",
                "--progress=false",
                "--fund=false",
                `--prefix=${pluginPackageDir}`,
                // "--ignore-workspace-root-check",
            ]);
            this.log.debug("%o install result: %o", pkgName, out);
            if (out.exitCode !== 0) {
                throw new run_time_error_1.RuntimeError("Non-zero exit code: ", JSON.stringify(out));
            }
            this.log.info(`Installed ${pkgName} OK`);
        }
        catch (ex) {
            throw new run_time_error_1.RuntimeError(`${fnTag} plugin install fail: ${pkgName}`, ex);
        }
    }
    async shutdown() {
        var _a, _b;
        this.log.info(`Shutting down API server ...`);
        const registry = await this.getOrInitPluginRegistry();
        const webServicesShutdown = registry
            .getPlugins()
            .filter((pluginInstance) => cactus_core_api_1.isIPluginWebService(pluginInstance))
            .map((pluginInstance) => {
            return pluginInstance.shutdown();
        });
        this.log.info(`Stopping ${webServicesShutdown.length} WS plugin(s)...`);
        await Promise.all(webServicesShutdown);
        this.log.info(`Stopped ${webServicesShutdown.length} WS plugin(s) OK`);
        if ((_a = this.httpServerApi) === null || _a === void 0 ? void 0 : _a.listening) {
            this.log.info(`Closing HTTP server of the API...`);
            await cactus_common_1.Servers.shutdown(this.httpServerApi);
            this.log.info(`Close HTTP server of the API OK`);
        }
        if ((_b = this.httpServerCockpit) === null || _b === void 0 ? void 0 : _b.listening) {
            this.log.info(`Closing HTTP server of the cockpit ...`);
            await cactus_common_1.Servers.shutdown(this.httpServerCockpit);
            this.log.info(`Close HTTP server of the cockpit OK`);
        }
        if (this.grpcServer) {
            this.log.info(`Closing gRPC server ...`);
            await new Promise((resolve, reject) => {
                this.grpcServer.tryShutdown((ex) => {
                    if (ex) {
                        this.log.error("Failed to shut down gRPC server: ", ex);
                        reject(ex);
                    }
                    else {
                        resolve();
                    }
                });
            });
            this.log.info(`Close gRPC server OK`);
        }
    }
    async startCockpitFileServer() {
        const { expressCockpit: app } = this;
        const cockpitWwwRoot = this.options.config.cockpitWwwRoot;
        this.log.info(`wwwRoot: ${cockpitWwwRoot}`);
        const resolvedWwwRoot = path_1.default.resolve(process.cwd(), cockpitWwwRoot);
        this.log.info(`resolvedWwwRoot: ${resolvedWwwRoot}`);
        const resolvedIndexHtml = path_1.default.resolve(resolvedWwwRoot + "/index.html");
        this.log.info(`resolvedIndexHtml: ${resolvedIndexHtml}`);
        const cockpitCorsDomainCsv = this.options.config.cockpitCorsDomainCsv;
        const allowedDomains = cockpitCorsDomainCsv.split(",");
        const corsMiddleware = this.createCorsMiddleware(allowedDomains);
        const { apiHost, apiPort, cockpitApiProxyRejectUnauthorized: rejectUnauthorized, } = this.options.config;
        const protocol = this.options.config.apiTlsEnabled ? "https:" : "http:";
        const apiHttpUrl = `${protocol}//${apiHost}:${apiPort}`;
        const apiProxyMiddleware = express_http_proxy_1.default(apiHttpUrl, {
            // preserve the path whatever it was. Without this the proxy just uses /
            proxyReqPathResolver: (srcReq) => srcReq.originalUrl,
            proxyReqOptDecorator: (proxyReqOpts, srcReq) => {
                const { originalUrl: thePath } = srcReq;
                const srcHost = srcReq.header("host");
                const { host: destHostname, port: destPort } = proxyReqOpts;
                const destHost = `${destHostname}:${destPort}`;
                this.log.debug(`PROXY ${srcHost} => ${destHost} :: ${thePath}`);
                // make sure self signed certs are accepted if it was configured as such by the user
                proxyReqOpts.rejectUnauthorized = rejectUnauthorized;
                return proxyReqOpts;
            },
        });
        app.use("/api/v*", apiProxyMiddleware);
        app.use(compression_1.default());
        app.use(corsMiddleware);
        app.use(express_1.default.static(resolvedWwwRoot));
        app.get("/*", (_, res) => res.sendFile(resolvedIndexHtml));
        const cockpitPort = this.options.config.cockpitPort;
        const cockpitHost = this.options.config.cockpitHost;
        if (!this.httpServerCockpit.listening) {
            await new Promise((resolve, reject) => {
                this.httpServerCockpit.once("error", reject);
                this.httpServerCockpit.once("listening", resolve);
                this.httpServerCockpit.listen(cockpitPort, cockpitHost);
            });
        }
        this.httpServerCockpit.on("request", app);
        // the address() method returns a string for unix domain sockets and null
        // if the server is not listening but we don't car about any of those cases
        // so the casting here should be safe. Famous last words... I know.
        const addressInfo = this.httpServerCockpit.address();
        this.log.info(`Cactus Cockpit net.AddressInfo`, addressInfo);
        return addressInfo;
    }
    /**
     * Installs the own endpoints of the API server such as the ones providing
     * healthcheck and monitoring information.
     * @param app
     */
    async getOrCreateWebServices(app) {
        const { log } = this;
        const { logLevel } = this.options.config;
        const healthcheckHandler = (req, res) => {
            res.json({
                success: true,
                createdAt: new Date(),
                memoryUsage: process.memoryUsage(),
            });
        };
        const { "/api/v1/api-server/healthcheck": oasPath } = openapi_json_1.default.paths;
        const { http } = oasPath.get["x-hyperledger-cactus"];
        const { path: httpPath, verbLowerCase: httpVerb } = http;
        app[httpVerb](httpPath, healthcheckHandler);
        this.wsApi.on("connection", (socket) => {
            const { id } = socket;
            const transport = socket.conn.transport.name; // in most cases, "polling"
            log.debug(`Socket connected. ID=${id} transport=%o`, transport);
            socket.conn.on("upgrade", () => {
                const upgradedTransport = socket.conn.transport.name; // in most cases, "websocket"
                log.debug(`Socket upgraded ID=${id} transport=%o`, upgradedTransport);
            });
            socket.on(typescript_axios_1.WatchHealthcheckV1.Subscribe, () => new watch_healthcheck_v1_endpoint_1.WatchHealthcheckV1Endpoint({
                process,
                socket,
                logLevel,
            }).subscribe());
        });
        const prometheusExporterHandler = (req, res) => {
            this.getPrometheusExporterMetrics().then((resBody) => {
                res.status(200);
                res.send(resBody);
            });
        };
        const { "/api/v1/api-server/get-prometheus-exporter-metrics": oasPathPrometheus, } = openapi_json_1.default.paths;
        const { http: httpPrometheus } = oasPathPrometheus.get["x-hyperledger-cactus"];
        const { path: httpPathPrometheus, verbLowerCase: httpVerbPrometheus, } = httpPrometheus;
        app[httpVerbPrometheus](httpPathPrometheus, prometheusExporterHandler);
    }
    async startGrpcServer() {
        return new Promise((resolve, reject) => {
            // const grpcHost = "0.0.0.0"; // FIXME - make this configurable (config-service.ts)
            const grpcHost = "127.0.0.1"; // FIXME - make this configurable (config-service.ts)
            const grpcHostAndPort = `${grpcHost}:${this.options.config.grpcPort}`;
            const grpcTlsCredentials = this.options.config.grpcMtlsEnabled
                ? grpc_js_2.ServerCredentials.createSsl(Buffer.from(this.options.config.apiTlsCertPem), [
                    {
                        cert_chain: Buffer.from(this.options.config.apiTlsCertPem),
                        private_key: Buffer.from(this.options.config.apiTlsKeyPem),
                    },
                ], true)
                : grpc_js_2.ServerCredentials.createInsecure();
            this.grpcServer.bindAsync(grpcHostAndPort, grpcTlsCredentials, (error, port) => {
                if (error) {
                    return reject(new run_time_error_1.RuntimeError("Binding gRPC failed: ", error));
                }
                this.grpcServer.addService(default_service.org.hyperledger.cactus.cmd_api_server
                    .UnimplementedDefaultServiceService.definition, new grpc_server_api_server_1.GrpcServerApiServer());
                this.grpcServer.start();
                const family = determine_address_family_1.determineAddressFamily(grpcHost);
                resolve({ address: grpcHost, port, family });
            });
        });
    }
    async startApiServer() {
        const { options, expressApi: app, wsApi } = this;
        const { config } = options;
        const { authorizationConfigJson: authzConf, authorizationProtocol: authzProtocol, logLevel, } = config;
        const apiServerOptions = config;
        const pluginRegistry = await this.getOrInitPluginRegistry();
        app.use(compression_1.default());
        const apiCorsDomainCsv = this.options.config.apiCorsDomainCsv;
        const allowedDomains = apiCorsDomainCsv.split(",");
        const corsMiddleware = this.createCorsMiddleware(allowedDomains);
        app.use(corsMiddleware);
        app.use(body_parser_1.default.json({ limit: "50mb" }));
        const authzFactoryOptions = { apiServerOptions, pluginRegistry, logLevel };
        const authzFactory = new authorizer_factory_1.AuthorizerFactory(authzFactoryOptions);
        await authzFactory.initOnce();
        const authorizerO = await authzFactory.createMiddleware(authzProtocol, authzConf);
        if (authorizerO.isPresent()) {
            const authorizer = authorizerO.get();
            this.checkNonExemptUnprotectedEps(authzFactory);
            app.use(authorizer);
            this.log.info(`Authorization request handler configured OK.`);
        }
        const openApiValidator = this.createOpenApiValidator();
        await openApiValidator.install(app);
        this.getOrCreateWebServices(app); // The API server's own endpoints
        this.log.info(`Starting to install web services...`);
        const webServicesInstalled = pluginRegistry
            .getPlugins()
            .filter((pluginInstance) => cactus_core_api_1.isIPluginWebService(pluginInstance))
            .map(async (plugin) => {
            const p = plugin;
            await p.getOrCreateWebServices();
            const webSvcs = await p.registerWebServices(app, wsApi);
            return webSvcs;
        });
        const endpoints2D = await Promise.all(webServicesInstalled);
        this.log.info(`Installed ${webServicesInstalled.length} web service(s) OK`);
        const endpoints = endpoints2D.reduce((acc, val) => acc.concat(val), []);
        endpoints.forEach((ep) => this.log.info(`Endpoint={path=${ep.getPath()}}`));
        const apiPort = this.options.config.apiPort;
        const apiHost = this.options.config.apiHost;
        if (!this.httpServerApi.listening) {
            await new Promise((resolve, reject) => {
                this.httpServerApi.once("error", reject);
                this.httpServerApi.once("listening", resolve);
                this.httpServerApi.listen(apiPort, apiHost);
            });
        }
        this.httpServerApi.on("request", app);
        // the address() method returns a string for unix domain sockets and null
        // if the server is not listening but we don't care about any of those cases
        // so the casting here should be safe. Famous last words... I know.
        const addressInfo = this.httpServerApi.address();
        this.log.info(`Cactus API net.AddressInfo`, addressInfo);
        const wsOptions = Object.assign({ path: cactus_core_api_1.Constants.SocketIoConnectionPathV1, serveClient: false }, this.options.wsOptions);
        this.wsApi.attach(this.httpServerApi, wsOptions);
        const socketIoAuthorizer = socketio_jwt_1.authorize(Object.assign(Object.assign({}, authzConf.socketIoJwtOptions), { onAuthentication: (decodedToken) => {
                this.log.debug("Socket authorized OK: %o", decodedToken);
            } }));
        this.wsApi.use(socketIoAuthorizer);
        return addressInfo;
    }
    checkNonExemptUnprotectedEps(factory) {
        const { config } = this.options;
        const { authorizationConfigJson } = config;
        const { unprotectedEndpointExemptions } = authorizationConfigJson;
        const nonExempts = factory.unprotectedEndpoints.filter((nse) => !unprotectedEndpointExemptions.some((pt) => nse.getPath().match(pt)));
        if (nonExempts.length > 0) {
            const csv = nonExempts.join(", ");
            const { E_NON_EXEMPT_UNPROTECTED_ENDPOINTS } = ApiServer;
            throw new Error(`${E_NON_EXEMPT_UNPROTECTED_ENDPOINTS} ${csv}`);
        }
    }
    createOpenApiValidator() {
        return new express_openapi_validator_1.OpenApiValidator({
            apiSpec: openapi_json_1.default,
            validateRequests: true,
            validateResponses: false,
        });
    }
    createCorsMiddleware(allowedDomains) {
        const allDomainsOk = allowedDomains.includes("*");
        const corsOptionsDelegate = (req, callback) => {
            const origin = req.header("Origin");
            const isDomainOk = origin && allowedDomains.includes(origin);
            // this.log.debug("CORS %j %j %s", allDomainsOk, isDomainOk, req.originalUrl);
            let corsOptions;
            if (allDomainsOk) {
                corsOptions = { origin: "*" }; // reflect (enable) the all origins in the CORS response
            }
            else if (isDomainOk) {
                corsOptions = { origin }; // reflect (enable) the requested origin in the CORS response
            }
            else {
                corsOptions = { origin: false }; // disable CORS for this request
            }
            callback(null, corsOptions); // callback expects two parameters: error and options
        };
        return cors_1.default(corsOptionsDelegate);
    }
}
exports.ApiServer = ApiServer;
ApiServer.CLASS_NAME = "ApiServer";
ApiServer.E_POST_CRASH_SHUTDOWN = "API server failed to shut itself down, will ignore this because we were already crashing anyway...";
ApiServer.E_NON_EXEMPT_UNPROTECTED_ENDPOINTS = `Non-exempt unprotected endpoints found. ` +
    `You can allow them as unprotected via the configuration of the ` +
    `API server by specifying an array of patterns in the property ` +
    `"unprotectedEndpointExemptions" of "authorizationConfigJson". ` +
    `This mechanism is meant to foster DevOps where both dev & ops ` +
    `work together in making secure application deployments a reality. ` +
    `The comma separated list of unprotected endpoints that were not marked as exempt: `;
//# sourceMappingURL=data:application/json;base64, 