"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HotHTTPServer = void 0;
const http = __importStar(require("http"));
const https = __importStar(require("https"));
const ppath = __importStar(require("path"));
const fs = __importStar(require("fs"));
const perf_hooks_1 = require("perf_hooks");
const constants_1 = require("constants");
const express_1 = __importDefault(require("express"));
const mime_types_1 = __importDefault(require("mime-types"));
const formidable_1 = __importStar(require("formidable"));
const express_rate_limit_1 = require("express-rate-limit");
const rate_limit_redis_1 = require("rate-limit-redis");
const ioredis_1 = __importDefault(require("ioredis"));
const swaggerUI = __importStar(require("swagger-ui-express"));
const HotServer_1 = require("./HotServer");
const HotStaq_1 = require("./HotStaq");
const HotRouteMethod_1 = require("./HotRouteMethod");
const HotHTTPServerProcessRequest_1 = require("./HotHTTPServerProcessRequest");
const HotIO_1 = require("./HotIO");
const HotWebSocketServer_1 = require("./HotWebSocketServer");
const HotMCPServer_1 = require("./HotMCPServer");
var Worker = null;
/**
 * A HTTP server.
 */
class HotHTTPServer extends HotServer_1.HotServer {
    constructor(processor, httpPort = null, httpsPort = null) {
        super(processor);
        this.expressApp = (0, express_1.default)();
        this.httpListener = null;
        this.httpsListener = null;
        this.useWebsocketServer = false;
        this.websocketServer = null;
        this.staticRoutes = [{
                "localPath": process.cwd(),
                "route": "/"
            }];
        this.routes = [];
        this.serveFileExtensions = HotHTTPServer.getDefaultServableExtensions();
        this.ignoreHottFiles = {};
        this.hottFilesAssociatedInfo = {
            name: "",
            url: "./",
            jsSrcPath: "./js/HotStaq.min.js"
        };
        this.uploads = {};
        this.autoDeleteUploadOptions = {
            afterUploadIdUse: true
        };
        this.requestReporterInterval = null;
        this.numRequestsServed = 0;
        this.activeRequests = {};
        this.useWorkerThreads = false;
        this.swaggerUI = {
            filepath: "",
            route: "/swagger",
            jsonObj: null
        };
        this.mcpServer = {
            enabled: false,
            route: "/mcp",
            server: null
        };
        this.options = {
            onCall: null,
            headers: [
                { type: "Access-Control-Allow-Origin", value: "*" },
                { type: "Access-Control-Allow-Methods", value: "GET,HEAD,PUT,PATCH,POST,DELETE" },
                { type: "Access-Control-Allow-Headers", value: "Origin, X-Requested-With, Content-Type, Accept, Authorization" }
            ]
        };
        this.cors = {
            onCORSCall: null,
            origin: "*",
            allowedHeaders: ["Origin", "X-Requested-With", "Content-Type", "Accept"]
        };
        this.rateLimiter = {
            enabled: false,
            windowLength: 60000,
            limit: 500,
            store: null
        };
        this.handle404 = null;
        this.handleOther = null;
        this.errorHandlingResponseCode = 500;
        if (process.env.LISTEN_ADDR != null) {
            if (process.env.LISTEN_ADDR !== "")
                this.listenAddress = process.env.LISTEN_ADDR;
        }
        if (process.env.USE_HTTP != null) {
            this.ssl = {
                cert: "",
                key: "",
                ca: ""
            };
        }
        if (httpPort != null)
            this.ports.http = httpPort;
        if (httpsPort != null)
            this.ports.https = httpsPort;
        if (process.env.HTTP_PORT != null) {
            if (process.env.HTTP_PORT !== "")
                this.ports.http = parseInt(process.env.HTTP_PORT);
        }
        if (process.env.HTTPS_PORT != null) {
            if (process.env.HTTPS_PORT !== "")
                this.ports.https = parseInt(process.env.HTTPS_PORT);
        }
        if (process.env.HTTPS_SSL_CERT != null) {
            if (process.env.HTTPS_SSL_CERT !== "")
                this.ssl.cert = process.env.HTTPS_SSL_CERT;
            if (process.env.HTTPS_SSL_KEY !== "")
                this.ssl.key = process.env.HTTPS_SSL_KEY;
            if (process.env.HTTPS_SSL_CA !== "")
                this.ssl.ca = process.env.HTTPS_SSL_CA;
        }
        let JSONLimit = "1mb";
        if (process.env.JSON_LIMIT != null) {
            if (process.env.JSON_LIMIT !== "")
                JSONLimit = process.env.JSON_LIMIT;
        }
        this.expressApp.options("*", (req, res, next) => __awaiter(this, void 0, void 0, function* () {
            if (this.options.onCall != null) {
                yield this.options.onCall(req, res, next);
                return;
            }
            for (let iIdx = 0; iIdx < this.options.headers.length; iIdx++) {
                let header = this.options.headers[iIdx];
                res.header(header.type, header.value);
            }
            res.statusCode = 204;
            res.setHeader("Content-Length", "0");
            res.end();
        }));
        this.expressApp.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
            if (this.cors.onCORSCall != null) {
                yield this.cors.onCORSCall(req, res, next);
                return;
            }
            res.header("Access-Control-Allow-Origin", this.cors.origin);
            res.header("Access-Control-Allow-Headers", this.cors.allowedHeaders);
            next();
        }));
        this.expressApp.use(express_1.default.urlencoded({ "extended": true }));
        this.expressApp.use(express_1.default.json({ "limit": JSONLimit }));
        if (this.api != null) {
            if (this.rateLimiter.enabled === true) {
                const limiterObj = {
                    windowMs: this.rateLimiter.windowLength,
                    limit: this.rateLimiter.limit,
                    standardHeaders: "draft-7"
                };
                if (this.rateLimiter.store != null) {
                    let client = this.rateLimiter.store.redisClient;
                    if ((process.env["RATE_LIMITER_REDIS_HOST"] != null) ||
                        (process.env["RATE_LIMITER_REDIS_PORT"] != null)) {
                        if (this.rateLimiter.store.redisConfig == null)
                            this.rateLimiter.store.redisConfig = {};
                    }
                    this.rateLimiter.store.redisConfig.host = process.env["RATE_LIMITER_REDIS_HOST"] || "localhost";
                    this.rateLimiter.store.redisConfig.port = parseInt(process.env["RATE_LIMITER_REDIS_PORT"]) || 6379;
                    this.rateLimiter.store.redisConfig.username = process.env["RATE_LIMITER_REDIS_USERNAME"];
                    this.rateLimiter.store.redisConfig.password = process.env["RATE_LIMITER_REDIS_PASSWORD"];
                    this.rateLimiter.store.redisConfig.tls = false;
                    this.rateLimiter.store.redisConfig.tlsInsecure = false;
                    const tlsStr = process.env["RATE_LIMITER_REDIS_TLS"] || "0";
                    const tlsInsecureStr = process.env["RATE_LIMITER_REDIS_TLS_INSECURE"] || "0";
                    if (tlsStr === "1")
                        this.rateLimiter.store.redisConfig.tls = true;
                    if (tlsInsecureStr === "1")
                        this.rateLimiter.store.redisConfig.tlsInsecure = true;
                    let tlsObj = undefined;
                    if (this.rateLimiter.store.redisConfig.tls === true) {
                        tlsObj = {
                            "rejectUnauthorized": !this.rateLimiter.store.redisConfig.tlsInsecure
                        };
                    }
                    if (this.rateLimiter.store.redisConfig != null) {
                        let redisOptions = {
                            "host": this.rateLimiter.store.redisConfig.host,
                            "port": this.rateLimiter.store.redisConfig.port
                        };
                        if (this.rateLimiter.store.redisConfig.username != null)
                            redisOptions.username = this.rateLimiter.store.redisConfig.username;
                        if (this.rateLimiter.store.redisConfig.password != null)
                            redisOptions.password = this.rateLimiter.store.redisConfig.password;
                        if (this.rateLimiter.store.redisConfig.tls != null)
                            redisOptions.tls = tlsObj;
                        client = new ioredis_1.default(redisOptions);
                    }
                    limiterObj.store = new rate_limit_redis_1.RedisStore({
                        // @ts-ignore
                        sendCommand: (...args) => client.call(...args)
                    });
                }
                const limiter = (0, express_rate_limit_1.rateLimit)(limiterObj);
                this.expressApp.use(limiter);
                this.logger.info(`Rate limiter enabled with window length ${this.rateLimiter.windowLength} and limit ${this.rateLimiter.limit}`);
            }
            else
                this.logger.info(`No rate limiter set!`);
        }
    }
    /**
     * Get the method name to be used with Express.
     */
    static getExpressMethodName(methodType) {
        let expressMethod = "post";
        if (methodType === HotRouteMethod_1.HotEventMethod.GET)
            expressMethod = "get";
        if (methodType === HotRouteMethod_1.HotEventMethod.POST)
            expressMethod = "post";
        if (methodType === HotRouteMethod_1.HotEventMethod.FILE_UPLOAD)
            expressMethod = "post";
        if (methodType === HotRouteMethod_1.HotEventMethod.SSE_SUB_EVENT)
            expressMethod = "post";
        if (methodType === HotRouteMethod_1.HotEventMethod.WEBSOCKET_CLIENT_PUB_EVENT)
            expressMethod = "ws_client_pub_event";
        if (methodType === HotRouteMethod_1.HotEventMethod.POST_AND_WEBSOCKET_CLIENT_PUB_EVENT)
            expressMethod = "post";
        return (expressMethod);
    }
    /**
     * Get the default servable file extensions.
     */
    static getDefaultServableExtensions() {
        return ([{ fileExtension: ".hott", generateContent: true },
            { fileExtension: ".htm", generateContent: false },
            { fileExtension: ".html", generateContent: false }]);
    }
    /**
     * Add a static route.
     */
    addStaticRoute(route, localPath = ".") {
        let staticRoute = null;
        if (typeof (route) === "string") {
            staticRoute = {
                "route": route,
                "localPath": localPath
            };
        }
        else
            staticRoute = route;
        this.staticRoutes.push(staticRoute);
        this.registerStaticRoute(staticRoute);
    }
    /**
     * Add a route. This will be registered before any APIs are registered.
     */
    addRoute(route, method, type = HotRouteMethod_1.HotEventMethod.GET) {
        let newRoute = {
            type: type,
            route: route,
            method: method
        };
        this.routes.push(newRoute);
    }
    /**
     * Serve a directory. This is an alias for addStaticRoute.
     */
    serveDirectory(route, localPath = ".") {
        this.addStaticRoute(route, localPath);
    }
    /**
     * Register a static route with Express.
     */
    registerStaticRoute(route) {
        this.clearErrorHandlingRoutes();
        this.preregisterRoute();
        this.expressApp.use(route.route, express_1.default.static(ppath.normalize(route.localPath), {
            setHeaders: (res, path, stat) => {
                for (let iJdx = 0; iJdx < this.serveFileExtensions.length; iJdx++) {
                    let servableFile = this.serveFileExtensions[iJdx];
                    if (typeof (servableFile) === "string")
                        continue;
                    if (servableFile.headers == null)
                        continue;
                    if (servableFile.headers.length === 0)
                        continue;
                    if (path.indexOf(servableFile.fileExtension) > -1) {
                        for (let iIdx = 0; iIdx < servableFile.headers.length; iIdx++) {
                            let httpHeader = servableFile.headers[iIdx];
                            if (httpHeader.type == null)
                                throw new Error(`Missing HTTP header type on file extension ${servableFile.fileExtension}`);
                            if (httpHeader.value == null)
                                throw new Error(`Missing HTTP header value on file extension ${servableFile.fileExtension}`);
                            res.header(httpHeader.type, httpHeader.value);
                        }
                    }
                    this.logger.verbose(() => `Found file ${path} with headers ${JSON.stringify(res.getHeaders())}`);
                }
            }
        }));
        this.logger.verbose(`Adding static route ${route.route} at path ${route.localPath}`);
        this.setErrorHandlingRoutes();
    }
    /**
     * Get a static route.
     */
    getStaticRoute(route) {
        let foundRoute = null;
        for (let iIdx = 0; iIdx < this.staticRoutes.length; iIdx++) {
            let tempRoute = this.staticRoutes[iIdx];
            if (tempRoute.route === route) {
                foundRoute = tempRoute;
                break;
            }
        }
        return (foundRoute);
    }
    /**
     * Register a route.
     */
    registerRoute(route) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (route.onRegister != null) {
                    if ((yield route.onRegister()) === false)
                        return;
                }
                this.clearErrorHandlingRoutes();
                this.preregisterRoute();
                for (let iIdx = 0; iIdx < route.methods.length; iIdx++) {
                    let method = route.methods[iIdx];
                    if (method.isRegistered === true)
                        continue;
                    if (method.onRegister != null) {
                        if ((yield method.onRegister()) === false)
                            continue;
                    }
                    if ((method.type === HotRouteMethod_1.HotEventMethod.POST_AND_WEBSOCKET_CLIENT_PUB_EVENT) ||
                        (method.type === HotRouteMethod_1.HotEventMethod.WEBSOCKET_CLIENT_PUB_EVENT)) {
                        if (this.useWebsocketServer === true)
                            this.websocketServer.addRoute(route, method);
                        if (method.type === HotRouteMethod_1.HotEventMethod.WEBSOCKET_CLIENT_PUB_EVENT) {
                            method.isRegistered = true;
                            continue;
                        }
                    }
                    let methodName = method.getRouteUrl();
                    method.isRegistered = true;
                    if (typeof (method.type) !== "string")
                        throw new Error(`When registering HTTP route, received invalid method type ${JSON.stringify(method.type)}`);
                    if (methodName == null)
                        throw new Error(`When registering HTTP route, a null/undefined method name was given.`);
                    this.logger.verbose(`Adding HTTP route ${method.type} ${methodName}`);
                    const expressType = HotHTTPServer.getExpressMethodName(method.type);
                    const asyncHandler = (fn) => {
                        return ((req, res, next) => {
                            Promise.resolve(fn(req, res, next)).catch((ex) => {
                                this.logger.error(`HTTP Error: ${ex.message}`);
                                next(ex);
                            });
                        });
                    };
                    // @ts-ignore
                    this.expressApp[expressType](methodName, asyncHandler((req, res) => __awaiter(this, void 0, void 0, function* () {
                        let sendResponse = null;
                        try {
                            if (method.type === HotRouteMethod_1.HotEventMethod.SSE_SUB_EVENT) {
                                this.logger.verbose(`Created SSE event for method: ${method.name}`);
                                res.set({
                                    'Content-Type': 'text/event-stream',
                                    'Cache-Control': 'no-cache',
                                    'Connection': 'keep-alive'
                                });
                                res.flushHeaders();
                            }
                            sendResponse = (value, requestNum) => {
                                let start = this.activeRequests[requestNum];
                                let end = perf_hooks_1.performance.now();
                                let diff = (end - start);
                                delete this.activeRequests[requestNum];
                                this.logger.verbose(`${methodName} took ${diff}ms`);
                                if (value !== undefined) {
                                    if (value != null) {
                                        if (value.error != null) {
                                            let statusCode = this.errorHandlingResponseCode;
                                            if (value.errorCode != null)
                                                statusCode = value.errorCode;
                                            res.status(statusCode).json(value);
                                            return;
                                        }
                                    }
                                    res.status(200).json(value);
                                }
                            };
                            if (this.useWorkerThreads === false) {
                                if (this.numRequestsServed >= (Number.MAX_SAFE_INTEGER - 1))
                                    this.numRequestsServed = 0;
                                const requestNum = this.numRequestsServed++;
                                this.activeRequests[requestNum] = perf_hooks_1.performance.now();
                                let response = yield (0, HotHTTPServerProcessRequest_1.processRequest)(this, this.logger, route, method, methodName, req, res);
                                if (method.type !== HotRouteMethod_1.HotEventMethod.SSE_SUB_EVENT)
                                    sendResponse(response, requestNum);
                                return;
                            }
                        }
                        catch (ex) {
                            this.logger.error(`Critical Error: ${ex.message}`);
                            throw ex;
                        }
                        yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                            let resolveIt = () => {
                                resolve();
                            };
                            try {
                                if (this.numRequestsServed >= (Number.MAX_SAFE_INTEGER - 1))
                                    this.numRequestsServed = 0;
                                const requestNum = this.numRequestsServed++;
                                this.activeRequests[requestNum] = perf_hooks_1.performance.now();
                                // workerData.logger, workerData.route, workerData.method, workerData.methodName, workerData.req, workerData.res
                                let worker = new Worker(`${__dirname}/HotHTTPServerThread.js`, {
                                    "workerData": {
                                        "logger": this.logger,
                                        "methodName": methodName
                                    }
                                });
                                worker.on("message", function (requestNum2, value) {
                                    if (method.type !== HotRouteMethod_1.HotEventMethod.SSE_SUB_EVENT)
                                        sendResponse(value, requestNum2);
                                    resolveIt();
                                }.bind(this, requestNum));
                                worker.on("error", (err) => {
                                    this.logger.error(`Error in worker: ${err.message}`);
                                    resolveIt();
                                });
                                worker.on("exit", function (requestNum2, code) {
                                    if (this.activeRequests[requestNum2] != null)
                                        delete this.activeRequests[requestNum2];
                                    this.logger.verbose(`Worker exited with code: ${code}`);
                                    resolveIt();
                                }.bind(this, requestNum));
                                worker.postMessage({});
                            }
                            catch (ex) {
                                this.logger.error(`Unable to start worker thread ${ex.message}`);
                                resolveIt();
                            }
                        }));
                    })));
                }
                this.setErrorHandlingRoutes();
            }
            catch (ex) {
                let msg = ex.message;
                if (ex.stack != null)
                    msg = ex.stack;
                this.logger.error(`HotHTTPServer error: ${msg}`);
                throw ex;
            }
        });
    }
    /**
     * Check if a file exists.
     */
    static checkIfFileExists(filepath) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield new Promise((resolve, reject) => {
                fs.access(filepath, constants_1.F_OK, (err) => {
                    if (err != null)
                        resolve(false);
                    resolve(true);
                });
            }));
        });
    }
    /**
     * Delete the uploads for a given upload id.
     */
    deleteUploads(uploadId) {
        return __awaiter(this, void 0, void 0, function* () {
            let uploads = this.uploads[uploadId];
            if (uploads == null)
                return;
            for (let key in uploads) {
                let upload = uploads[key];
                const uploadPath = ppath.normalize(upload.path);
                if ((yield HotIO_1.HotIO.exists(uploadPath)) === true)
                    yield HotIO_1.HotIO.rm(uploadPath);
            }
            delete this.uploads[uploadId];
        });
    }
    /**
     * The routes to add before registering a route.
     */
    preregisterRoute() {
        this.expressApp.use((req, res, next) => {
            const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
            this.logger.verbose(`IP ${req.ip} initial request: ${req.method} ${req.httpVersion} ${url}`);
            next();
        });
        for (let iIdx = 0; iIdx < this.routes.length; iIdx++) {
            let route = this.routes[iIdx];
            const expressType = HotHTTPServer.getExpressMethodName(route.type);
            /// @ts-ignore
            this.expressApp[expressType](route.route, route.method);
            this.logger.verbose(`Added route ${route.type} ${route.route}`);
        }
        if (this.processor.hotSite != null) {
            if (this.processor.hotSite.server != null) {
                if (this.processor.hotSite.server.serveFileExtensions != null)
                    this.serveFileExtensions = this.processor.hotSite.server.serveFileExtensions;
            }
            if (this.processor.hotSite.routes != null) {
                // Ignore any hott files from any routes. The routes will return the 
                // hott files themselves.
                for (let key in this.processor.hotSite.routes) {
                    let route = this.processor.hotSite.routes[key];
                    let filename = ppath.basename(route.url);
                    this.ignoreHottFiles[filename] = true;
                    this.logger.verbose(`Ignoring Hott file ${filename}`);
                }
            }
        }
        if (this.serveFileExtensions.length > 0) {
            if (this.isAPIServerOnly() === true)
                return;
            this.logger.verbose(() => `Set to serve files: ${JSON.stringify(this.serveFileExtensions)}`);
            this.expressApp.use((req, res, next) => {
                (() => __awaiter(this, void 0, void 0, function* () {
                    try {
                        let bodyObj = req.body;
                        let queryObj = req.query;
                        this.logger.verbose(() => `Method: ${req.method} Requested URL: ${req.originalUrl} Body: ${JSON.stringify(bodyObj)}, Query: ${JSON.stringify(queryObj)}`);
                        let requestedUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
                        let foundUrl = HotStaq_1.HotStaq.getValueFromHotSiteObj(this.processor.hotSite, ["server", "url"]);
                        if (foundUrl != null) {
                            if (foundUrl[(foundUrl.length - 1)] === "/")
                                foundUrl = foundUrl.substr(0, (foundUrl.length - 1));
                            let addSlash = "/";
                            if (req.originalUrl[0] === "/")
                                addSlash = "";
                            requestedUrl = `${foundUrl}${addSlash}${req.originalUrl}`;
                        }
                        let url = new URL(requestedUrl);
                        const urlFilepath = url.pathname;
                        const filepath = ppath.basename(urlFilepath);
                        const extname = ppath.extname(filepath).toLowerCase();
                        let skipSecretFiles = true;
                        // Skip any files that could contain secrets.
                        if (this.processor.hotSite != null) {
                            if (this.processor.hotSite.server != null) {
                                if (this.processor.hotSite.server.serveSecretFiles === false)
                                    skipSecretFiles = false;
                            }
                        }
                        if (skipSecretFiles === true) {
                            let skipCurrentFile = false;
                            const lowerFilePath = filepath.toLowerCase();
                            if (lowerFilePath === ".env")
                                skipCurrentFile = true;
                            if ((lowerFilePath === ".npmrc") || (lowerFilePath === ".yarnrc"))
                                skipCurrentFile = true;
                            if (lowerFilePath === "hotsite.json")
                                skipCurrentFile = true;
                            if (extname === ".pem")
                                skipCurrentFile = true;
                            if (skipCurrentFile === true) {
                                const errMsg = `Refused to serve file ${filepath}. Could contain secrets.`;
                                this.handleOther(new Error(errMsg), req, res, next);
                                return;
                            }
                        }
                        // This if statement ensures the requested file is not on the ignore list.
                        if (this.ignoreHottFiles[filepath] != null) {
                            // Ignore the file.
                        }
                        else {
                            let sendHottContent = (fullUrl, route) => __awaiter(this, void 0, void 0, function* () {
                                // Appending hstqserve ensures that the content will not be resent.
                                fullUrl.searchParams.append("hstqserve", "nahfam");
                                const content = yield this.processor.generateContent(route, this.hottFilesAssociatedInfo.name, fullUrl.toString(), this.hottFilesAssociatedInfo.jsSrcPath);
                                // The content will be generated and sent to the client. The client 
                                // will then request the real page that contains the file.
                                res.setHeader("Content-Type", "text/html");
                                this.logger.verbose(() => `Sending generated hott content with headers ${JSON.stringify(res.getHeaders())}`);
                                res.status(200).send(content);
                            });
                            let sendFileContent = (path, fileExt, iServableFile) => {
                                if (iServableFile != null) {
                                    if (iServableFile.fileExtension != null) {
                                        if (path.indexOf(iServableFile.fileExtension) > -1) {
                                            if (iServableFile.headers != null) {
                                                if (iServableFile.headers.length > 0) {
                                                    for (let iIdx = 0; iIdx < iServableFile.headers.length; iIdx++) {
                                                        let httpHeader = iServableFile.headers[iIdx];
                                                        if (httpHeader.type == null)
                                                            throw new Error(`Missing HTTP header type on file extension ${fileExt}`);
                                                        if (httpHeader.value == null)
                                                            throw new Error(`Missing HTTP header value on file extension ${fileExt}`);
                                                        res = res.header(httpHeader.type, httpHeader.value);
                                                    }
                                                    if (ppath.isAbsolute(path) === false)
                                                        path = ppath.normalize(`${process.cwd()}/${path}`);
                                                    this.logger.verbose(() => `Sending file ${path} with headers ${JSON.stringify(res.getHeaders())}`);
                                                    res = res.status(200);
                                                    res.sendFile(path);
                                                    return;
                                                }
                                            }
                                        }
                                    }
                                }
                                if (fileExt === ".hott")
                                    res.setHeader("Content-Type", "text/html");
                                else {
                                    let mimeType = mime_types_1.default.lookup(fileExt);
                                    if (typeof (mimeType) === "string")
                                        res.setHeader("Content-Type", mimeType);
                                }
                                if (ppath.isAbsolute(path) === false)
                                    path = ppath.normalize(`${process.cwd()}/${path}`);
                                this.logger.verbose(() => `Sending file ${path} with headers ${JSON.stringify(res.getHeaders())}`);
                                res.status(200).sendFile(path);
                            };
                            let result = "";
                            let hstqserve = url.searchParams.get("hstqserve");
                            if (hstqserve != null)
                                result = hstqserve;
                            // Make sure we're not accidentally resending the content.
                            if (result !== "nahfam") {
                                for (let iIdx = 0; iIdx < this.staticRoutes.length; iIdx++) {
                                    let staticRoute = this.staticRoutes[iIdx];
                                    let checkDir = staticRoute.localPath;
                                    const route = urlFilepath;
                                    let tempFilepath = urlFilepath;
                                    let sendContentFlag = false;
                                    let generateContent = false;
                                    let foundServableFile = null;
                                    if (checkDir === "")
                                        checkDir = process.cwd();
                                    if ((checkDir === ".") || (checkDir === "./"))
                                        checkDir = process.cwd();
                                    for (let iJdx = 0; iJdx < this.serveFileExtensions.length; iJdx++) {
                                        let servableFile = this.serveFileExtensions[iJdx];
                                        let serveFileExt = "";
                                        if (typeof (servableFile) === "string")
                                            serveFileExt = servableFile;
                                        else {
                                            serveFileExt = servableFile.fileExtension;
                                            if (servableFile.generateContent != null)
                                                generateContent = servableFile.generateContent;
                                        }
                                        if (tempFilepath.indexOf(serveFileExt) > -1) {
                                            if ((yield HotHTTPServer.checkIfFileExists(ppath.normalize(`${checkDir}/${tempFilepath}`))) === true) {
                                                sendContentFlag = true;
                                                if (typeof (servableFile) !== "string")
                                                    foundServableFile = servableFile;
                                                break;
                                            }
                                        }
                                        if (sendContentFlag === false) {
                                            if ((yield HotHTTPServer.checkIfFileExists(ppath.normalize(`${checkDir}/${tempFilepath}${serveFileExt}`))) === true) {
                                                sendContentFlag = true;
                                                if (typeof (servableFile) !== "string")
                                                    foundServableFile = servableFile;
                                                url.pathname += serveFileExt;
                                                break;
                                            }
                                        }
                                        if (sendContentFlag === false) {
                                            if ((yield HotHTTPServer.checkIfFileExists(ppath.normalize(`${checkDir}/${tempFilepath}index${serveFileExt}`))) === true) {
                                                // If no content has been found, send the index.
                                                tempFilepath += `index${serveFileExt}`;
                                                sendContentFlag = true;
                                                if (typeof (servableFile) !== "string")
                                                    foundServableFile = servableFile;
                                                break;
                                            }
                                        }
                                    }
                                    if (tempFilepath !== urlFilepath)
                                        url.pathname = tempFilepath;
                                    if (sendContentFlag === true) {
                                        if (generateContent === true) {
                                            // SPA-index fallback. When `server.indexFile` is set, a
                                            // full-page navigation to a .hott (a browser GET without the
                                            // `hstqserve` param — that param is stripped by the early
                                            // `result === "nahfam"` guard) serves the configured shell
                                            // host (index.html) instead of the per-route page. The
                                            // client then boots the app shell and fetches the route's
                                            // content (with ?hstqserve, served raw) into its outlet.
                                            // This mirrors nginx's `try_files $uri /index.html` so the
                                            // dev/test server matches production for layout-mode apps.
                                            let spaIndexFile = HotStaq_1.HotStaq.getValueFromHotSiteObj(this.processor.hotSite, ["server", "indexFile"]);
                                            if ((spaIndexFile != null) && (spaIndexFile !== "")) {
                                                let idxPath = spaIndexFile;
                                                if (ppath.isAbsolute(idxPath) === false)
                                                    idxPath = ppath.normalize(`${process.cwd()}/${idxPath}`);
                                                res.setHeader("Content-Type", "text/html");
                                                this.logger.verbose(() => `SPA-index: serving ${idxPath} for navigation ${route}`);
                                                res.status(200).sendFile(idxPath);
                                                return;
                                            }
                                            yield sendHottContent(url, route);
                                        }
                                        else {
                                            sendFileContent(ppath.normalize(`${checkDir}/${tempFilepath}`), extname, foundServableFile);
                                        }
                                        return;
                                    }
                                }
                            }
                            else
                                this.logger.verbose(`Received nahfam, content has already been sent.`);
                        }
                    }
                    catch (ex) {
                        this.logger.error(`Middleware error: ${ex.message}`);
                    }
                    next();
                }))();
            });
        }
    }
    /**
     * Get all files uploaded.
     */
    static getFileUploads(logger_1, req_1) {
        return __awaiter(this, arguments, void 0, function* (logger, req, options = { multiples: true }) {
            let files = {};
            if (req.headers["content-type"] != null) {
                if (req.headers["content-type"].indexOf("multipart/form-data") > -1) {
                    try {
                        const form = (0, formidable_1.default)(options);
                        const [fields, foundFiles] = yield form.parse(req);
                        files = foundFiles;
                    }
                    catch (ex) {
                        if (ex != null) {
                            /// @fixme Is it ok to ignore this error?
                            if (ex.code !== formidable_1.errors.noParser) {
                                logger.error(`Formidable: ${ex.message}`);
                                throw ex;
                            }
                        }
                    }
                }
            }
            return (files);
        });
    }
    /**
     * Set the error handlers. This will create two express routes at the bottom of the
     * route stack. The first will be to capture any 404 errors, the second would be to
     * catch any remaining errors.
     */
    setErrorHandlingRoutes() {
        if (this.handle404 == null) {
            this.handle404 = (req, res, next) => {
                this.logger.verbose(() => `404 ${JSON.stringify(req.url)}`);
                let on404 = HotStaq_1.HotStaq.getValueFromHotSiteObj(this.processor.hotSite, ["server", "errors", "on404"]);
                if (on404 != null)
                    res.status(404).sendFile(on404);
                else
                    res.status(404).send({ error: "404" });
            };
        }
        if (this.handleOther == null) {
            this.handleOther = (err, req, res, next) => {
                let stack = "";
                let msg = "";
                if (err != null) {
                    stack = err.stack;
                    msg = err.message;
                }
                this.logger.verbose(() => `500 Server error ${JSON.stringify(stack)}`);
                let onOther = HotStaq_1.HotStaq.getValueFromHotSiteObj(this.processor.hotSite, ["server", "errors", "onOther"]);
                if (onOther != null)
                    res.status(500).sendFile(onOther);
                else
                    res.status(500).send({ error: `Server error: ${msg}` });
            };
        }
        // This is a hack to force the function names, so we can find them and delete them later.
        Object.defineProperty(this.handle404, "name", {
            value: "handle404"
        });
        Object.defineProperty(this.handleOther, "name", {
            value: "handleOther"
        });
        this.expressApp.use(this.handle404);
        this.expressApp.use(this.handleOther);
        this.expressApp.use((err, req, res, next) => {
            res.status(500).json({ error: err.message });
        });
    }
    /**
     * Clear the last two express routes, which are reserved for the
     * error handlers.
     */
    clearErrorHandlingRoutes() {
        if (this.expressApp._router == null)
            return;
        let stackCount = this.expressApp._router.stack.length;
        for (let iIdx = 0; iIdx < stackCount; iIdx++) {
            let elm = this.expressApp._router.stack[iIdx];
            if (elm != null) {
                let name = elm.name;
                if ((name === "handle404") || (name === "handleOther")) {
                    this.expressApp._router.stack.splice(iIdx, 1);
                    iIdx--;
                    stackCount = this.expressApp._router.stack.length;
                }
            }
        }
    }
    /**
     * Start listening for requests.
     */
    listen() {
        return __awaiter(this, void 0, void 0, function* () {
            let promise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                try {
                    if (this.swaggerUI.filepath !== "") {
                        const fileExt = ppath.extname(this.swaggerUI.filepath).toLowerCase();
                        this.logger.info(`Starting Swagger UI on route "${this.swaggerUI.route}" from ${this.swaggerUI.filepath}`);
                        if ((fileExt === ".yml") || (fileExt === ".yaml")) {
                            this.logger.verbose(() => `Reading YAML file ${this.swaggerUI.filepath}`);
                            this.swaggerUI.jsonObj = yield HotIO_1.HotIO.readYAMLFile(this.swaggerUI.filepath);
                        }
                        else {
                            this.logger.verbose(() => `Reading JSON file ${this.swaggerUI.filepath}`);
                            this.swaggerUI.jsonObj = yield HotIO_1.HotIO.readJSONFile(this.swaggerUI.filepath);
                        }
                        this.logger.verbose(() => `Finished parsing file ${this.swaggerUI.filepath}`);
                        let protocol = "http";
                        let port = this.ports.http;
                        if (this.ssl.cert !== "") {
                            protocol = "https";
                            port = this.ports.https;
                        }
                        let listenStr = this.listenAddress;
                        if (listenStr === "0.0.0.0")
                            listenStr = "localhost";
                        let baseUrl = `${protocol}://${listenStr}:${port}/`;
                        if (this.swaggerUI.jsonObj.servers == null)
                            this.swaggerUI.jsonObj.servers = [];
                        if (process.env["BASE_API_URL"] != null) {
                            if (process.env["BASE_API_URL"] !== "")
                                baseUrl = process.env["BASE_API_URL"];
                        }
                        this.swaggerUI.jsonObj.servers.unshift({ url: baseUrl });
                        this.expressApp.use(this.swaggerUI.route, swaggerUI.serve, swaggerUI.setup(this.swaggerUI.jsonObj));
                    }
                    let JSONLimit = "1mb";
                    if (process.env.JSON_LIMIT != null) {
                        if (process.env.JSON_LIMIT !== "")
                            JSONLimit = process.env.JSON_LIMIT;
                    }
                    this.logger.info(`Access-Control-Allow-Origin: ${this.cors.origin}`);
                    this.logger.info(`Access-Control-Allow-Headers: ${this.cors.allowedHeaders}`);
                    this.logger.info(`JSON limit: ${JSONLimit}`);
                    if (this.useWorkerThreads === true) {
                        Worker = require("node:worker_threads").Worker;
                        this.logger.info(`WARNING: WORKER THREADS ENABLED. THIS IS AN EXPERIMENTAL FEATURE AND IS MOST LIKELY NOT WORKING YET.`);
                    }
                    if (this.useWebsocketServer === true)
                        this.websocketServer = new HotWebSocketServer_1.HotWebSocketServer(this);
                    let completedSetup = () => {
                        let protocol = "http";
                        let port = this.ports.http;
                        if (this.ssl.cert !== "") {
                            protocol = "https";
                            port = this.ports.https;
                        }
                        this.logger.info(`HotStaq Version ${HotStaq_1.HotStaq.version}`);
                        this.logger.info(`${this.serverType} listening on ${protocol}://${this.listenAddress}:${port}/`);
                        if (this.processor.hotSite != null)
                            this.logger.verbose(() => `Using HotSite object: ${JSON.stringify(this.processor.hotSite)}`);
                        resolve();
                    };
                    if (this.processor.hotSite != null) {
                        let hotsiteServer = this.processor.hotSite.server;
                        if (hotsiteServer != null) {
                            if (hotsiteServer.serveDirectories != null) {
                                if (this.isAPIServerOnly() === false) {
                                    for (let iIdx = 0; iIdx < hotsiteServer.serveDirectories.length; iIdx++) {
                                        let directory = hotsiteServer.serveDirectories[iIdx];
                                        this.staticRoutes.push({
                                            "route": directory.route,
                                            "localPath": ppath.normalize(directory.localPath)
                                        });
                                    }
                                }
                            }
                            if (hotsiteServer.ports != null) {
                                if (hotsiteServer.ports.http != null)
                                    this.ports.http = hotsiteServer.ports.http;
                                if (hotsiteServer.ports.https != null)
                                    this.ports.https = hotsiteServer.ports.https;
                                if (hotsiteServer.ports.redirectHTTPtoHTTPS != null)
                                    this.redirectHTTPtoHTTPS = hotsiteServer.ports.redirectHTTPtoHTTPS;
                            }
                        }
                    }
                    if (this.isAPIServerOnly() === false) {
                        yield this.processor.createExpressRoutes(this.expressApp);
                        for (let iIdx = 0; iIdx < this.staticRoutes.length; iIdx++) {
                            let staticRoute = this.staticRoutes[iIdx];
                            this.registerStaticRoute(staticRoute);
                        }
                    }
                    if (this.api != null) {
                        if (this.api.onPreRegister != null) {
                            let continueRegistering = yield this.api.onPreRegister();
                            if (continueRegistering === false)
                                return;
                        }
                        // Process pre registration for the routes and methods.
                        for (let key in this.api.routes) {
                            let route = this.api.routes[key];
                            if (route.onPreRegister != null)
                                yield route.onPreRegister();
                            for (let iIdx = 0; iIdx < route.methods.length; iIdx++) {
                                let method = route.methods[iIdx];
                                if (method.onPreRegister != null)
                                    yield method.onPreRegister();
                            }
                            this.api.createAPIRouteMethods(route.route);
                        }
                        // Register all the routes.
                        yield this.api.registerRoutes();
                        // Process post registration for the API.
                        if (this.api.onPostRegister != null) {
                            let continueOn = yield this.api.onPostRegister();
                            if (continueOn === false)
                                return;
                        }
                        // Process post registration for the routes and methods.
                        for (let key in this.api.routes) {
                            let route = this.api.routes[key];
                            if (route.onPostRegister != null)
                                yield route.onPostRegister();
                            for (let iIdx = 0; iIdx < route.methods.length; iIdx++) {
                                let method = route.methods[iIdx];
                                if (method.onPostRegister != null)
                                    yield method.onPostRegister();
                            }
                        }
                    }
                    // Create and attach the MCP server AFTER all routes have run their
                    // onPreRegister callbacks (where HotRouteMethod entries are added).
                    // Doing this earlier causes buildToolDefinitions() to see empty method
                    // arrays for all routes that register methods in onPreRegister.
                    // Must clear error handling routes first so the MCP routes are
                    // registered above the 404 catch-all, then re-add them after.
                    if (this.mcpServer.enabled && this.api != null) {
                        this.clearErrorHandlingRoutes();
                        this.mcpServer.server = new HotMCPServer_1.HotMCPServer(this.api, this.mcpServer.route);
                        yield this.mcpServer.server.attach(this.expressApp);
                        this.setErrorHandlingRoutes();
                        this.logger.info(`MCP server listening on route "${this.mcpServer.route}"`);
                    }
                    // Track whether a shutdown is already in progress so a
                    // second Ctrl-C / SIGTERM can hard-quit instead of
                    // waiting on a hung close().
                    let shutdownInFlight = false;
                    let forceQuitTimer = null;
                    // Hard-quit after this many ms even if listener.close()
                    // hasn't returned. Default 5s; override via the
                    // HOTSTAQ_SHUTDOWN_FORCE_MS env var (any positive int)
                    // or set to 0 to wait forever (the old behaviour).
                    const forceQuitMsRaw = parseInt(process.env["HOTSTAQ_SHUTDOWN_FORCE_MS"] || "5000", 10);
                    const forceQuitMs = (Number.isFinite(forceQuitMsRaw) && forceQuitMsRaw >= 0) ? forceQuitMsRaw : 5000;
                    let requestReporter = () => {
                        let numRequests = Object.keys(this.activeRequests).length;
                        if (numRequests < 1) {
                            clearInterval(this.requestReporterInterval);
                            this.logger.info(`Finished processing all requests...`);
                            // listener.close() resolved AND the in-flight
                            // counter is empty. We're done — let the
                            // process exit instead of hanging on stray
                            // keep-alive sockets / DB clients / etc.
                            if (forceQuitTimer != null)
                                clearTimeout(forceQuitTimer);
                            process.exit(0);
                        }
                        this.logger.info(`Still processing ${numRequests} requests...`);
                    };
                    let sigHandler = (typeReceived, serverType, listener) => {
                        // Second Ctrl-C / SIGTERM during a shutdown that
                        // the framework can't drain on its own — give
                        // the developer an escape hatch instead of
                        // stranding them in the terminal.
                        if (shutdownInFlight === true) {
                            this.logger.info(`${typeReceived} received again; forcing immediate exit.`);
                            process.exit(130);
                        }
                        shutdownInFlight = true;
                        this.logger.info(`${typeReceived} signal received: Stopping ${serverType} server. Data loss and partial database writes can occur if this is stopped prematurely.`);
                        // Force-quit if graceful shutdown stalls. Common
                        // cause is open HTTP/1.1 keep-alive sockets that
                        // listener.close() refuses to terminate, but it
                        // also catches DB clients, Garnet pub/sub,
                        // websocket holds, etc.
                        if (forceQuitMs > 0) {
                            forceQuitTimer = setTimeout(() => {
                                this.logger.info(`Graceful shutdown exceeded ${forceQuitMs}ms; forcing exit.`);
                                process.exit(1);
                            }, forceQuitMs);
                            // Don't keep the event loop alive just for
                            // this timer — if the only thing left is
                            // the timer, we want Node to exit naturally.
                            if (typeof forceQuitTimer.unref === "function")
                                forceQuitTimer.unref();
                        }
                        listener.close(() => {
                            this.logger.info(`${serverType} server stopped. No longer listening for any new incoming requests...`);
                            this.requestReporterInterval = setInterval(() => {
                                requestReporter();
                            }, 2000);
                        });
                        // Aggressive: immediately destroy any open
                        // keep-alive sockets that haven't been claimed
                        // by an in-flight request. listener.close()
                        // politely waits for them to time out (default
                        // 65s); we'd rather drop them and exit on time.
                        if (typeof listener.closeIdleConnections === "function")
                            listener.closeIdleConnections();
                    };
                    if (this.ssl.cert === "") {
                        this.httpListener = http.createServer(this.expressApp);
                        this.logger.info(`HTTP Listening on address: ${this.listenAddress}`);
                        this.httpListener.listen(this.ports.http, this.listenAddress, completedSetup);
                        process.on('SIGTERM', sigHandler.bind(this, "SIGTERM", "HTTP", this.httpListener));
                        process.on('SIGINT', sigHandler.bind(this, "SIGINT", "HTTP", this.httpListener));
                    }
                    else {
                        if (this.redirectHTTPtoHTTPS === true) {
                            this.httpListener = http.createServer((req, res) => {
                                let host = req.headers["host"];
                                res.writeHead(301, {
                                    "Location": `https://${host}${req.url}`
                                });
                                res.end();
                            });
                            this.logger.info(`HTTP Listening on address: ${this.listenAddress}`);
                            this.httpListener.listen(this.ports.http, this.listenAddress, () => {
                                this.logger.info(`Redirecting HTTP(${this.ports.http}) traffic to HTTPS(${this.ports.https})`);
                            });
                            let stopRedirectServer = () => {
                                this.logger.info(`SIGTERM signal received: Stopping HTTP redirect server. Data loss and partial database writes can occur if this is stopped prematurely.`);
                                this.httpListener.close(() => {
                                    this.logger.info(`HTTP redirect server stopped`);
                                });
                            };
                            process.on('SIGTERM', stopRedirectServer);
                            process.on('SIGINT', stopRedirectServer);
                        }
                        this.httpsListener = https.createServer({
                            cert: this.ssl.cert,
                            key: this.ssl.key,
                            ca: this.ssl.ca
                        }, this.expressApp);
                        this.logger.info(`HTTPS Listening on address: ${this.listenAddress}`);
                        this.httpsListener.listen(this.ports.https, this.listenAddress, completedSetup);
                        process.on('SIGTERM', sigHandler.bind(this, "SIGTERM", "HTTPS", this.httpsListener));
                        process.on('SIGINT', sigHandler.bind(this, "SIGINT", "HTTPS", this.httpsListener));
                    }
                    if (this.useWebsocketServer === true)
                        this.websocketServer.setup();
                }
                catch (ex) {
                    let msg = "";
                    if (ex != null) {
                        msg = ex.message;
                        if (ex.stack != null)
                            msg = ex.stack;
                    }
                    this.logger.error(`HotHTTPServer Error: ${msg}`);
                    if (ex != null)
                        throw (ex);
                }
            }))
                .catch((reason) => {
                let msg = "";
                if (reason != null) {
                    if (typeof (reason) === "string")
                        msg = reason;
                    if (reason.message != null)
                        msg = reason.message;
                    if (reason.stack != null)
                        msg = reason.stack;
                }
                this.logger.error(`HotHTTPServer Error: ${msg}`);
                if (reason != null)
                    throw reason;
            });
            return (promise);
        });
    }
    /**
     * Start the server.
     *
     * @param localStaticPath The public path that contains the HTML, Hott files, images, and
     * all public content. This can also be an array of StaticRoutes.
     * @param httpPort The HTTP port to listen on .
     * @param httpsPort The HTTPS port to listen on.
     * @param processor The HotStaq or parent server being used for communication.
     */
    static startServer() {
        return __awaiter(this, arguments, void 0, function* (localStaticPath = null, httpPort = 5000, httpsPort = 443, processor = null) {
            if (processor == null)
                processor = new HotStaq_1.HotStaq();
            let webServer = new HotHTTPServer(processor, httpPort, httpsPort);
            if (localStaticPath == null)
                localStaticPath = process.cwd();
            if (typeof (localStaticPath) === "string")
                webServer.addStaticRoute("/", localStaticPath);
            else {
                for (let iIdx = 0; iIdx < localStaticPath.length; iIdx++) {
                    let staticRoute = localStaticPath[iIdx];
                    webServer.addStaticRoute(staticRoute);
                }
            }
            yield webServer.listen();
            return ({ processor: processor, server: webServer });
        });
    }
    /**
     * Is this server running with an API server?
     */
    isAPIServerOnly() {
        if (this.serverType === "API Server")
            return (true);
        return (false);
    }
    /**
     * Is this server running with an API server?
     */
    isAPIServerRunning() {
        if ((this.serverType === "Web-API Server") ||
            (this.serverType === "API Server")) {
            return (true);
        }
        return (false);
    }
    /**
     * Shutdown the server.
     */
    shutdown() {
        return __awaiter(this, void 0, void 0, function* () {
            this.logger.verbose(`Shutting down HTTP server...`);
            yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                let promises = [];
                if (this.websocketServer != null)
                    promises.push(this.websocketServer.stop());
                if (this.httpListener != null) {
                    promises.push(new Promise((resolve2, reject) => {
                        this.httpListener.close((err) => {
                            this.expressApp = null;
                            if (err != null) {
                                if (err.code != null) {
                                    if (err.code !== "ERR_SERVER_NOT_RUNNING")
                                        throw err;
                                }
                            }
                            this.logger.verbose(`HTTP server has shut down.`);
                            resolve2();
                        });
                    }));
                }
                if (this.httpsListener != null) {
                    promises.push(new Promise((resolve2, reject) => {
                        this.httpsListener.close((err) => {
                            this.expressApp = null;
                            if (err != null) {
                                if (err.code != null) {
                                    if (err.code !== "ERR_SERVER_NOT_RUNNING")
                                        throw err;
                                }
                            }
                            this.logger.verbose(`HTTPS server has shut down.`);
                            resolve2();
                        });
                    }));
                }
                yield Promise.all(promises);
                this.logger.verbose(`All servers have shut down.`);
                resolve();
            }));
        });
    }
}
exports.HotHTTPServer = HotHTTPServer;
//# sourceMappingURL=HotHTTPServer.js.map