"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.HotTesterServer = void 0;
const http = __importStar(require("http"));
const https = __importStar(require("https"));
const fs = __importStar(require("fs"));
const constants_1 = require("constants");
const express_1 = __importDefault(require("express"));
const HotServer_1 = require("./HotServer");
const HotStaq_1 = require("./HotStaq");
const HotRouteMethod_1 = require("./HotRouteMethod");
const HotTesterAPI_1 = require("./HotTesterAPI");
const Hot_1 = require("./Hot");
const HotLog_1 = require("./HotLog");
const HotAPI_1 = require("./HotAPI");
const HotHTTPServer_1 = require("./HotHTTPServer");
/**
 * An API server for use during testing.
 */
class HotTesterServer extends HotServer_1.HotServer {
    constructor(processor, httpPort = null, httpsPort = null) {
        super(processor);
        this.listenAddress = "127.0.0.1";
        this.expressApp = (0, express_1.default)();
        this.httpListener = null;
        this.httpsListener = null;
        this.routes = [];
        if (process.env.TESTER_LISTEN_ADDR != null) {
            if (process.env.TESTER_LISTEN_ADDR !== "")
                this.listenAddress = process.env.TESTER_LISTEN_ADDR;
        }
        if (process.env.TESTER_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.TESTER_HTTP_PORT != null) {
            if (process.env.TESTER_HTTP_PORT !== "")
                this.ports.http = parseInt(process.env.TESTER_HTTP_PORT);
        }
        if (process.env.TESTER_HTTPS_PORT != null) {
            if (process.env.TESTER_HTTPS_PORT !== "")
                this.ports.https = parseInt(process.env.TESTER_HTTPS_PORT);
        }
        if (process.env.TESTER_HTTPS_SSL_CERT != null) {
            if (process.env.TESTER_HTTPS_SSL_CERT !== "")
                this.ssl.cert = process.env.TESTER_HTTPS_SSL_CERT;
            if (process.env.TESTER_HTTPS_SSL_KEY !== "")
                this.ssl.key = process.env.TESTER_HTTPS_SSL_KEY;
            if (process.env.TESTER_HTTPS_SSL_CA !== "")
                this.ssl.ca = process.env.TESTER_HTTPS_SSL_CA;
        }
        let testerJSONLimit = "1mb";
        if (process.env.TESTER_JSON_LIMIT != null) {
            if (process.env.TESTER_JSON_LIMIT !== "")
                testerJSONLimit = process.env.TESTER_JSON_LIMIT;
        }
        this.expressApp.options("*", (req, res, next) => {
            res.header("Access-Control-Allow-Origin", "*");
            res.header("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE");
            res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            res.statusCode = 204;
            res.setHeader("Content-Length", "0");
            res.end();
        });
        this.expressApp.use((req, res, next) => {
            res.header("Access-Control-Allow-Origin", "*");
            res.header("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE");
            res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            next();
        });
        this.expressApp.use(express_1.default.urlencoded({ "extended": true }));
        this.expressApp.use(express_1.default.json({ "limit": testerJSONLimit }));
    }
    /**
     * Add a tester for use later.
     */
    addTester(tester) {
        this.processor.logger.verbose(`Added tester ${tester.name}`);
        this.processor.addTester(tester);
    }
    /**
     * Execute tests.
     */
    executeTests(testerName, mapName) {
        return __awaiter(this, void 0, void 0, function* () {
            this.processor.logger.verbose(`Executing tests ${testerName} using map name ${mapName}`);
            return (this.processor.executeTests(testerName, mapName));
        });
    }
    /**
     * Execute all web tests for all maps in the HotSite testing web object.
     */
    executeAllWebTests(testerName) {
        return __awaiter(this, void 0, void 0, function* () {
            return (this.processor.executeAllWebTests(testerName));
        });
    }
    /**
     * Execute all api tests for all maps in the HotSite testing api object.
     */
    executeAllAPITests(testerName) {
        return __awaiter(this, void 0, void 0, function* () {
            return (this.processor.executeAllAPITests(testerName));
        });
    }
    /**
     * 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.processor.logger.verbose(`Added route ${route}`);
        this.routes.push(newRoute);
    }
    /**
     * Register a route.
     */
    registerRoute(route) {
        return __awaiter(this, void 0, void 0, function* () {
            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;
                }
                let methodName = "/";
                if (route.version !== "")
                    methodName += `${route.version}/`;
                if (route.prefix !== "")
                    methodName += `${route.prefix}/`;
                if (route.route !== "")
                    methodName += `${route.route}/`;
                methodName += method.name;
                method.isRegistered = true;
                const expressType = HotHTTPServer_1.HotHTTPServer.getExpressMethodName(method.type);
                /// @ts-ignore
                this.expressApp[expressType](methodName, (req, res) => __awaiter(this, void 0, void 0, function* () {
                    let hasAuthorization = true;
                    let authorizationValue = null;
                    let jsonObj = req.body;
                    let queryObj = req.query;
                    let api = route.connection.api;
                    let thisObj = route;
                    if (api.executeEventsUsing === HotAPI_1.EventExecutionType.HotAPI)
                        thisObj = api;
                    if (api.executeEventsUsing === HotAPI_1.EventExecutionType.HotMethod)
                        thisObj = method;
                    this.logger.verbose(() => `${req.method} ${methodName}, JSON: ${JSON.stringify(jsonObj)}, Query: ${JSON.stringify(queryObj)}`);
                    if (method.onServerAuthorize != null) {
                        try {
                            let request = new HotRouteMethod_1.ServerRequest({
                                req: req,
                                res: res,
                                authorizedValue: null,
                                jsonObj: jsonObj,
                                queryObj: queryObj,
                                files: null
                            });
                            authorizationValue = yield method.onServerAuthorize.call(thisObj, request);
                        }
                        catch (ex) {
                            this.logger.verbose(`Authorization error: ${ex.message}`);
                            res.json({ error: ex.message });
                            hasAuthorization = false;
                            return;
                        }
                        if (authorizationValue === undefined)
                            hasAuthorization = false;
                    }
                    else {
                        if (route.onAuthorizeUser != null) {
                            authorizationValue = yield route.onAuthorizeUser(new HotRouteMethod_1.ServerRequest({ req: req, res: res }));
                            if (authorizationValue === undefined)
                                hasAuthorization = false;
                        }
                    }
                    this.logger.verbose(`${req.method} ${methodName}, Authorized: ${hasAuthorization}`);
                    if (hasAuthorization === true) {
                        if (method.onServerExecute != null) {
                            try {
                                let request = new HotRouteMethod_1.ServerRequest({
                                    req: req,
                                    res: res,
                                    authorizedValue: null,
                                    jsonObj: jsonObj,
                                    queryObj: queryObj,
                                    files: null
                                });
                                let result = yield method.onServerExecute.call(thisObj, request);
                                this.logger.verbose((result2) => {
                                    let resultStr = "";
                                    if (this.logger.showResponses === true)
                                        resultStr = `, Response: ${JSON.stringify(result2)}`;
                                    return (`${req.method} ${methodName}${resultStr}`);
                                }, result);
                                if (result !== undefined)
                                    res.json(result);
                            }
                            catch (ex) {
                                this.logger.error(`HTTP Tester Execution Error: ${ex.message}`);
                                res.json({ error: ex.message });
                            }
                        }
                        else {
                            res.json(route.errors["no_server_execute_function"]);
                            this.logger.verbose(`${req.method} ${methodName}, no_server_execute_function`);
                        }
                    }
                    else {
                        res.json(route.errors["not_authorized"]);
                        this.logger.verbose(`${req.method} ${methodName}, not_authorized`);
                    }
                }));
            }
            this.setErrorHandlingRoutes();
        });
    }
    /**
     * Check if a file exists.
     */
    static checkIfFileExists(filepath) {
        return __awaiter(this, void 0, void 0, function* () {
            return (new Promise((resolve, reject) => {
                fs.access(filepath, constants_1.F_OK, (err) => {
                    if (err != null)
                        resolve(false);
                    resolve(true);
                });
            }));
        });
    }
    /**
     * The routes to add before registering a route.
     */
    preregisterRoute() {
        this.expressApp.use((req, res, next) => {
            const url = `${req.protocol}://${req.hostname}${req.originalUrl}`;
            this.logger.verbose(`Tester Server Requested: ${req.method} ${req.httpVersion} ${url}`);
            next();
        });
        for (let iIdx = 0; iIdx < this.routes.length; iIdx++) {
            let route = this.routes[iIdx];
            const expressType = HotHTTPServer_1.HotHTTPServer.getExpressMethodName(route.type);
            /// @ts-ignore
            this.expressApp[expressType](route.route, route.method);
        }
    }
    /**
     * 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(handle404 = null, handleOther = null) {
        if (handle404 == null) {
            handle404 = (req, res, next) => {
                this.logger.verbose(`404 ${JSON.stringify(req.url)} Method: ${req.method}`);
                res.status(404).send({ error: "404" });
            };
        }
        if (handleOther == null) {
            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)}`);
                res.status(500).send({ error: `Server error: ${msg}` });
            };
        }
        this.expressApp.use(handle404);
        this.expressApp.use(handleOther);
    }
    /**
     * 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* () {
                let completedSetup = () => {
                    let protocol = "http";
                    let port = this.ports.http;
                    if (this.ssl.cert !== "") {
                        protocol = "https";
                        port = this.ports.https;
                    }
                    this.logger.info(`Tester server running at ${protocol}://${this.listenAddress}:${port}/`);
                    resolve();
                };
                if (this.api != null) {
                    if (this.api.onPreRegister != null) {
                        let continueRegistering = yield this.api.onPreRegister();
                        if (continueRegistering === false)
                            return;
                    }
                    yield this.api.registerRoutes();
                    if (this.api.onPostRegister != null) {
                        let continueOn = yield this.api.onPostRegister();
                        if (continueOn === false)
                            return;
                    }
                }
                if (this.ssl.cert === "") {
                    this.httpListener = http.createServer(this.expressApp);
                    this.httpListener.listen(this.ports.http, this.listenAddress, completedSetup);
                }
                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.httpListener.listen(this.ports.http, this.listenAddress, () => {
                            this.logger.info(`Redirecting HTTP(${this.ports.http}) traffic to HTTPS(${this.ports.https})`);
                        });
                    }
                    this.httpsListener = https.createServer({
                        cert: this.ssl.cert,
                        key: this.ssl.key,
                        ca: this.ssl.ca
                    }, this.expressApp);
                    this.httpsListener.listen(this.ports.https, this.listenAddress, completedSetup);
                }
            }));
            return (promise);
        });
    }
    /**
     * Setup the tester api.
     */
    setupTesterAPI(baseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            let api = new HotTesterAPI_1.HotTesterAPI(baseUrl, this);
            this.processor.testerAPI = api;
            this.api = api;
            yield this.api.registerRoutes();
        });
    }
    /**
     * Start the server.
     *
     * @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* (baseUrl = `http://127.0.0.1:8182`, httpPort = 8182, httpsPort = 4142, processor = null, logLevel = HotLog_1.HotLogLevel.All) {
            if (processor == null) {
                processor = new HotStaq_1.HotStaq();
                processor.mode = Hot_1.DeveloperMode.Development;
            }
            let webServer = new HotTesterServer(processor, httpPort, httpsPort);
            webServer.logger.logLevel = logLevel;
            processor.logger.verbose(`Starting tester server...`);
            yield webServer.setupTesterAPI(baseUrl);
            yield webServer.listen();
            return ({ processor: processor, server: webServer });
        });
    }
    /**
     * Shutdown the server.
     */
    shutdown() {
        return __awaiter(this, void 0, void 0, function* () {
            this.logger.verbose(`Shutting down HTTP tester server...`);
            yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                let promises = [];
                if (this.httpListener != null) {
                    promises.push(new Promise((resolve2, reject) => {
                        this.httpListener.close((err) => {
                            this.expressApp = null;
                            if (err != null)
                                throw err;
                            this.logger.verbose(`HTTP tester 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)
                                throw err;
                            this.logger.verbose(`HTTPS tester server has shut down.`);
                            resolve2();
                        });
                    }));
                }
                yield Promise.all(promises);
                this.logger.verbose(`All tester servers have shut down.`);
                resolve();
            }));
        });
    }
}
exports.HotTesterServer = HotTesterServer;
//# sourceMappingURL=HotTesterServer.js.map