"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 });
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
// @ts-ignore
const spdy_1 = __importDefault(require("spdy"));
const grpc = __importStar(require("@grpc/grpc-js"));
const protoLoader = __importStar(require("@grpc/proto-loader"));
const logger_1 = __importDefault(require("../logger"));
const GrpcParser_1 = __importDefault(require("../parser/GrpcParser"));
const WebsocketParser_1 = __importDefault(require("../parser/WebsocketParser"));
// @ts-ignore
const uuid_1 = require("uuid");
const clients = [];
/**
 * Defines all protocols:
 * Currently active:
 * - HTTP
 * - HTTPS
 * - HTTP2
 * - gRPC
 * - Websocket
 * @param {string} grpcMocksDir location of grpc mocks, not initialized as constructor variable, because grpc is an optional protocol
 *                              instead it will be initialized in initGRPC method, if called.
 */
class Protocols {
    /**
     *
     * @param {express.Application} app Express application to form the listener for http and https server
     * @param {number} port HTTP server port
     * @param {number} httpsPort HTTPs server port - currently initialized in constructor optionally but in future it'll be initialized if https is enabled
     */
    constructor(app, port, httpsPort) {
        /**
         * Initialize HTTP server at specified port
         * @returns {void}
         */
        this.initHttp = () => {
            http_1.default.createServer(this.app).listen(this.port, () => {
                logger_1.default.info(`Worker sharing HTTP server at http://localhost:${this.port} ⛳`);
                this.app.emit("server-started");
            });
        };
        /**
         * Initialize HTTPs server at specified port
         * @param {string} key location of server.key file
         * @param {string} cert location of server.cert file
         * @returns {void}
         */
        this.initHttps = (key, cert) => {
            let privateKey = fs_1.default.readFileSync(key, "utf8");
            let certificate = fs_1.default.readFileSync(cert, "utf8");
            let credentials = { key: privateKey, cert: certificate };
            https_1.default.createServer(credentials, this.app).listen(this.httpsPort, () => {
                logger_1.default.info(`Worker sharing HTTPs server at https://localhost:${this.httpsPort} ⛳`);
            });
        };
        /**
         * Initializes a gRPC server at specified host and port
         * - Set location of gRPC mocks to be used by metod camouflageMock
         * - Get an array of all .protofile in specified protos directory
         * - Run forEach on the array and read and load package definition for each protofile in protos dir
         * - For each definition, get the package details from all .proto files and store in a master packages object
         * - Initialize a grpcServer
         * - Create an insecure binding to given grpc host and port, and start the server
         * - For each package, filter out objects with service definition, discard rest
         * - For each method in the service definition, attach a generic handler, finally add service to running server
         * - Handlers will vary based on the type of request, i.e. unary, bidi streams or one sided streams
         * - Finally add all services to the server
         * @param {string} grpcProtosDir location of proto files
         * @param {string} grpcMocksDir location of mock files for grpc
         * @param {string} grpcHost grpc host
         * @param {number} grpcPort grpc port
         */
        this.initGrpc = (grpcProtosDir, grpcMocksDir, grpcHost, grpcPort) => {
            this.grpcMocksDir = grpcMocksDir;
            const grpcParser = new GrpcParser_1.default(this.grpcMocksDir);
            const availableProtoFiles = fs_1.default.readdirSync(grpcProtosDir);
            let grpcObjects = [];
            let packages = [];
            availableProtoFiles.forEach((availableProtoFile) => {
                let packageDef = protoLoader.loadSync(path_1.default.join(grpcProtosDir, availableProtoFile), {});
                let definition = grpc.loadPackageDefinition(packageDef);
                grpcObjects.push(definition);
            });
            grpcObjects.forEach((grpcObject) => {
                Object.keys(grpcObject).forEach((availablePackage) => {
                    packages.push(grpcObject[`${availablePackage}`]);
                });
            });
            const server = new grpc.Server();
            server.bindAsync(`${grpcHost}:${grpcPort}`, grpc.ServerCredentials.createInsecure(), (err) => {
                if (err)
                    logger_1.default.error(err.message);
                logger_1.default.info(`Worker sharing gRPC server at ${grpcHost}:${grpcPort} ⛳`);
                server.start();
            });
            packages.forEach((entry) => {
                let keys = Object.keys(entry);
                keys = keys.filter((key) => {
                    return entry[key]["service"] !== undefined;
                });
                keys.forEach((key) => {
                    let service = entry[key]["service"];
                    let methods = Object.keys(service);
                    let methodDefinition = {};
                    methods.forEach((method) => {
                        if (!service[method]["responseStream"] && !service[method]["requestStream"]) {
                            logger_1.default.debug(`Registering Unary method: ${method}`);
                            methodDefinition[method] = grpcParser.camouflageMock;
                        }
                        if (service[method]["responseStream"] && !service[method]["requestStream"]) {
                            logger_1.default.debug(`Registering method with server side streaming: ${method}`);
                            methodDefinition[method] = grpcParser.camouflageMockServerStream;
                        }
                        if (!service[method]["responseStream"] && service[method]["requestStream"]) {
                            logger_1.default.debug(`Registering method with client side streaming: ${method}`);
                            methodDefinition[method] = grpcParser.camouflageMockClientStream;
                        }
                        if (service[method]["responseStream"] && service[method]["requestStream"]) {
                            logger_1.default.debug(`Registering method with BIDI streaming: ${method}`);
                            methodDefinition[method] = grpcParser.camouflageMockBidiStream;
                        }
                    });
                    server.addService(service, methodDefinition);
                });
            });
        };
        /**
         * Initializes an HTTP2 server
         * @param {number} http2Port
         * @param {string} http2key
         * @param {string} http2cert
         */
        this.initHttp2 = (http2Port, http2key, http2cert) => {
            spdy_1.default
                .createServer({
                key: fs_1.default.readFileSync(http2key),
                cert: fs_1.default.readFileSync(http2cert),
            }, this.app)
                .listen(http2Port, (err) => {
                if (err)
                    logger_1.default.error(err.message);
                logger_1.default.info(`Worker sharing HTTP2 server at https://localhost:${http2Port} ⛳`);
            });
        };
        /**
         * Initializes a WebSocketserver
         * @param {number} wsPort
         * @param {string} wsMockDir
         */
        this.initws = (wsPort, wsMockDir) => {
            const WebSocket = require("ws");
            const wss = new WebSocket.Server({ port: wsPort });
            logger_1.default.info(`Worker sharing WS server at ws://localhost:${wsPort} ⛳`);
            const websocketParser = new WebsocketParser_1.default(wss);
            wss.on("connection", (ws, request) => {
                const clientId = uuid_1.v4();
                clients.push(clientId);
                // @ts-ignore
                ws["clientId"] = clientId;
                let mockFile = path_1.default.join(wsMockDir, ...request.url.substring(1).split("/"), "connection.mock");
                if (fs_1.default.existsSync(mockFile)) {
                    websocketParser.sendConnect(ws, request, clients, clientId, "joining", mockFile);
                }
                else {
                    websocketParser.sendConnect(ws, request, clients, clientId, "joining");
                }
                ws.on("message", (message) => {
                    logger_1.default.debug(`Client sent message ${message}`);
                    mockFile = path_1.default.join(wsMockDir, ...request.url.substring(1).split("/"), "message.mock");
                    if (fs_1.default.existsSync(mockFile)) {
                        websocketParser.send(mockFile, ws, request, message);
                    }
                    else {
                        logger_1.default.error(`No suitable message.mock file found for ${request.url}`);
                    }
                });
                ws.on("close", () => {
                    clients.splice(clients.indexOf(clientId), 1);
                    websocketParser.sendConnect(ws, request, clients, clientId, "leaving");
                });
            });
        };
        this.app = app;
        this.port = port;
        this.httpsPort = httpsPort;
    }
}
exports.default = Protocols;
//# sourceMappingURL=index.js.map