"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("./utils");
const global_1 = require("./global");
const Express = require("express");
const http_1 = require("./zipkin/outbound/http");
const https_1 = require("./zipkin/outbound/https");
let fs = require('fs');
const jsYaml = require("js-yaml");
const settings_1 = require("./settings");
let morgan = require('morgan');
let bodyParser = require('body-parser');
let swaggerJSDoc = require('swagger-jsdoc');
let rfs = require('rotating-file-stream');
let promBundle = require("express-prom-bundle");
const rest = require('rest');
const { Tracer, BatchRecorder, ConsoleRecorder, jsonEncoder: { JSON_V2 } } = require('zipkin');
const { HttpLogger } = require('zipkin-transport-http');
const CLSContext = require('zipkin-context-cls');
const { restInterceptor } = require('zipkin-instrumentation-cujojs-rest');
/**
 * get the inbound connections zipkin middleware
 */
const zipkinMiddleware = require('./zipkin/inbound/express');
class WebServer {
    constructor(token = "global") {
        this.express = require("express");
        this.allowedDomains = new RegExp("^$");
        this.swaggerOptions = settings_1.default.Instance.getSwagger(token);
        this.morganOptions = settings_1.default.Instance.getMorgan(token);
        this.zipkinOptions = settings_1.default.Instance.getZipkin(token);
        this.routeMetrics = settings_1.default.Instance.getRouteMetrics(token);
        this.app = Express();
        this.logFileGenerator = this.customGenerator.bind(this);
    }
    initialize() {
        this.createLogStream();
        this.includeMorgan();
        //Need to be included before any routes to include all routes
        this.includeMetrics();
        this.autorizeForSwagger();
        if (this.swaggerOptions != undefined && this.swaggerOptions.isActive) {
            this.includeSwagger();
        }
        this.includeHealthChecker();
        //http://expressjs.com/en/api.html
        this.app.use(bodyParser.json({ type: '*/*', limit: '50mb' }));
        this.app.use(function (error, req, res, next) {
            if (error instanceof SyntaxError) {
                res.status(400).send(JSON.stringify({ err: 'json parsing failed, check input params' }));
            }
            else {
                next();
            }
        });
        this.app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
        if (this.zipkinOptions != undefined && this.zipkinOptions.isActive) {
            this.includeZipkin();
        }
        return this.app;
    }
    getExpress() {
        return this.express;
    }
    setLogStream(logStream) {
        this.logStream = logStream;
    }
    setLogFileGenerator(logFileGenerator) {
        this.logFileGenerator = logFileGenerator;
    }
    activateMorgan(active) {
        this.morganOptions.isActive = active;
    }
    includeZipkin() {
        /**
         * from https://zipkin.io/pages/quickstart ->
         *
         * docker run -d -p 9411:9411 openzipkin/zipkin
         *
         */
        let self = this;
        const ctxImpl = new CLSContext();
        // const recorder = new ConsoleRecorder();
        const endpoint = self.zipkinOptions.serveradress + '/api/v2/spans';
        const recorder = new BatchRecorder({
            logger: new HttpLogger({
                endpoint: endpoint,
                jsonEncoder: JSON_V2,
                httpInterval: 6000,
                //headers: {'Authorization': 'secret'}, // Custom HTTP headers. Optional
                timeout: 5000,
            })
        });
        let localServiceName = self.zipkinOptions.localServicename;
        let tracer = new Tracer({ ctxImpl, recorder, localServiceName });
        // instrument the client
        this.zipkineExternalCaller = rest.wrap(restInterceptor, { tracer });
        // Allow cross-origin, traced requests. See http://enable-cors.org/server_expressjs.html
        this.app.use(function (req, res, next) {
            /**
             * add zipkin specific headers to the allow headers list
             */
            res.setHeader('Access-Control-Allow-Origin', '*');
            res.setHeader('Access-Control-Allow-Headers', [
                'Origin', 'Accept', 'X-Requested-With', 'X-B3-TraceId',
                'X-B3-ParentSpanId', 'X-B3-SpanId', 'X-B3-Sampled'
            ].join(', '));
            next();
        });
        /**
        *  Add the Zipkin middleware
        */
        this.app.use(zipkinMiddleware({ tracer }));
        new http_1.default(tracer, localServiceName, [endpoint]);
        new https_1.default(tracer, localServiceName, [endpoint]);
    }
    includeSwagger() {
        let self = this;
        if (self.swaggerOptions.apis != null && self.swaggerOptions.apis.length > 0) {
            let apisPath = [];
            let apis = self.swaggerOptions.apis;
            for (var i = 0, len = apis.length; i < len; i++) {
                let path = utils_1.default.getFullRouteFrom(apis[i]);
                if (path != null && path != "") {
                    apisPath.push(path);
                }
            }
            //self.swaggerOptions.apis = apisPath;
            //append new calculated paths to old paths
            self.swaggerOptions.apis = self.swaggerOptions.apis.concat(apisPath);
        }
        this.app.get('/swagger.json', function (req, res) {
            res.setHeader('Content-Type', 'application/json');
            if (self.swaggerOptions == undefined) {
                res.setHeader('Content-Type', 'application/json');
                res.status(500);
                res.send({ Error: "Swagger options are undefined." });
                return;
            }
            var options = {
                swaggerDefinition: self.swaggerOptions.swaggerDefinition,
                apis: self.swaggerOptions.apis,
            };
            // Initialize swagger-jsdoc -> returns validated swagger spec in json format
            let swaggerSpec = {};
            try {
                swaggerSpec = swaggerJSDoc(options);
                res.status(200);
            }
            catch (err) {
                if (err instanceof jsYaml.YAMLException) {
                    //put error YAMLException
                    swaggerSpec = err;
                }
                else {
                    //get message, drop it in {err:err.message}
                    swaggerSpec = { err: err != null && err.message ? err.message : err };
                }
                res.status(500);
            }
            res.send(swaggerSpec);
        });
    }
    includeHealthChecker() {
        let self = this;
        this.app.get('/health', function (req, res) {
            res.setHeader('Content-Type', 'application/json');
            let status = global_1.default.Instance.isFunctional() ? 200 : 500;
            res.status(status);
            res.send(JSON.stringify({ details: global_1.default.Instance.getStatus(), memory: self.getMemoryUse(), status: status }));
        });
    }
    getMemoryUse() {
        let memory = process.memoryUsage();
        return memory;
    }
    includeMetrics() {
        //promBundle is singleton, ensure we don't already have a default metrics registred
        promBundle.promClient.register.clear();
        const metricsMiddleware = promBundle({
            buckets: this.routeMetrics.buckets,
            includeMethod: this.routeMetrics.includeMethod,
            includePath: this.routeMetrics.includePath,
            autoregister: this.routeMetrics.autoregister,
            promClient: {
                collectDefaultMetrics: {
                    timeout: this.routeMetrics.timeout
                }
            }
        });
        this.app.use(/(?:\/[^\/]*)*\/[^\.]*$/, metricsMiddleware);
        global_1.default.Instance.Metrics.get(); //Inform "Metrics" module we actually use the global registry;
        this.app.get('/metrics', function (req, res) {
            res.set('Content-Type', promBundle.promClient.register.contentType);
            let response = global_1.default.Instance.getMergedMetrics().metrics();
            res.send(response);
        });
    }
    autorizeForSwagger() {
        this.allowedDomainsRegex();
        let self = this;
        this.app.all('/*', function (req, res, next) {
            if (req.headers.origin != undefined) {
                let origin = req.headers.origin;
                if (origin instanceof Array) {
                    origin = origin[0];
                }
                let match = null;
                if (origin != undefined)
                    match = self.allowedDomains.exec(origin);
                if (match != null && match.length > 0) {
                    res.setHeader('Access-Control-Allow-Origin', match[0]);
                }
                if (self.swaggerOptions.allowHeaders != null && self.swaggerOptions.allowHeaders != "")
                    res.setHeader('Access-Control-Allow-Headers', self.swaggerOptions.allowHeaders);
                if (self.swaggerOptions.allowMethods != null && self.swaggerOptions.allowMethods != "")
                    res.setHeader('Access-Control-Allow-Methods', self.swaggerOptions.allowMethods);
            }
            //options request need to return as soon as possible
            if (req.method == "OPTIONS") {
                res.status(200);
                res.send();
            }
            else
                next();
        });
    }
    includeMorgan() {
        let self = this;
        //get query token
        morgan.token('query', function getQueryMethod(req) {
            let query = {};
            if (req.method == 'POST') {
                query = req.parsed_body;
            }
            else {
                query = req.query;
            }
            ;
            return ((query != undefined) ? JSON.stringify(query) : "");
        });
        morgan.token('body', function (req, res) {
            let response = null;
            if (req != null) {
                let body = req.body;
                if (body != null) {
                    response = JSON.stringify(body);
                }
            }
            return (response != null && response != "") ? response : "{}";
        });
        /**
        * attempt to convert ipv6 to ipv4(it will remain ipv6 if it can't be done)
        */
        morgan.token('remote-addr', utils_1.default.getIp);
        //add a custom format
        morgan.format('custom-format', self.morganOptions.format);
        //tell express to use morgan access logger
        this.app.use(morgan('custom-format', {
            stream: this.logStream,
            immediate: false,
            skip: function (req, res) {
                return !self.morganOptions.isActive;
            }
        }));
    }
    createLogStream() {
        let use_stdout = null;
        //Get value from enviroment
        if (process.env != null) {
            if (process.env.MORGAN_USE_STDOUT == "true") {
                use_stdout = true;
            }
            else if (process.env.MORGAN_USE_STDOUT == "false") {
                use_stdout = false;
            }
        }
        //Get value from settings
        if (use_stdout == null) {
            use_stdout = this.morganOptions != null && typeof this.morganOptions.useStdout == "boolean" ? this.morganOptions.useStdout : true;
        }
        //If is not explicit set on false, will be considerated tru and log to process.stdout
        if (use_stdout != false) {
            this.logStream = process.stdout;
            return true;
        }
        //If is already set a logStream will be used
        if (this.logStream != null) {
            return false;
        }
        let logSettings = {
            path: this.morganOptions && this.morganOptions.logStream && this.morganOptions.logStream.path ? this.morganOptions.logStream.path : "/logs",
            interval: this.morganOptions && this.morganOptions.logStream && this.morganOptions.logStream.interval ? this.morganOptions.logStream.interval : "1d",
            maxFiles: this.morganOptions && this.morganOptions.logStream && this.morganOptions.logStream.maxFiles ? this.morganOptions.logStream.maxFiles : 7,
            compress: this.morganOptions && this.morganOptions.logStream && this.morganOptions.logStream.compress ? this.morganOptions.logStream.compress : "gzip",
        };
        logSettings.path = utils_1.default.getFullRouteFrom(logSettings.path);
        let logDirectory = logSettings.path;
        // ensure log directory exists
        fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory);
        // create a rotating write stream
        this.logStream = rfs(this.logFileGenerator, {
            interval: logSettings.interval,
            maxFiles: logSettings.maxFiles,
            compress: logSettings.compress,
            path: logDirectory
        });
        return true;
    }
    allowedDomainsRegex() {
        if (this.swaggerOptions == undefined) {
            return false;
        }
        let domains = this.swaggerOptions.allowedDomains;
        if (domains instanceof Array) {
            let arrayLength = domains.length;
            let escaped = [];
            for (var i = 0; i < arrayLength; i++) {
                escaped[i] = "(" + domains[i].replace(/\./g, '\.') + ")";
            }
            this.allowedDomains = new RegExp("^((https?:\/\/)?([^:\/]+\.)*(" + escaped.join("|") + ")(:[0-9]+)?)$");
        }
        else {
            this.allowedDomains = new RegExp("^$");
        }
        return true;
    }
    /**
     * rotate file name generator
     * @param {*} time
     * @param {*} index
     */
    customGenerator(time, index) {
        let filename = "access.log";
        if (this.morganOptions != undefined && typeof this.morganOptions.logStream == "object" && this.morganOptions.logStream.logFile != undefined) {
            filename = this.morganOptions.logStream.logFile;
        }
        if (!time)
            return filename;
        var month = time.getFullYear() + "" + utils_1.default.pad(time.getMonth() + 1);
        var day = utils_1.default.pad(time.getDate());
        var hour = utils_1.default.pad(time.getHours());
        var minute = utils_1.default.pad(time.getMinutes());
        return month + day + "-" + hour + minute + "-" + index + "-" + filename + ".gz";
    }
}
exports.default = WebServer;
//# sourceMappingURL=express.js.map