"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const { Request, Annotation } = require('zipkin');
const settings_1 = require("../../settings");
class HttpOutBound {
    constructor(tracer, serviceName = "test-http", ignoreHostList = [], protocol = 'http') {
        this.tracer = tracer;
        this.serviceName = serviceName;
        this.ignoreHostList = ignoreHostList;
        this.protocol = protocol;
        this.methods = [];
        let _protocol = require(protocol);
        this.methods.push(_protocol.request);
        this.boundModule(_protocol);
    }
    beforeHook(methodArgs, res, context) {
        this.tracer.scoped(() => {
            //if express context, overwrite the id
            let sessionName = this.tracer['_ctxImpl']._session.name;
            if (settings_1.default.ZIPKIN_CONTEXT_NAME === sessionName) {
                context = Object.assign(context, { traceId: this.tracer.id });
            }
            let { host, method, path, port, hostname } = methodArgs;
            let prettyHost = host || hostname;
            if (port != undefined) {
                prettyHost = `${prettyHost}:${port}`;
            }
            let completeUrl = `${this.protocol}://${prettyHost}${path}`;
            if (!methodArgs.headers)
                methodArgs.headers = {};
            //don't report yourself
            if (this.ignoreHostList.indexOf(completeUrl) >= 0) {
                context = Object.assign(context, { ignored: true });
                return;
            }
            /**
            * account for in-flight traces
            */
            let { traceId } = context;
            //pass the current in-flight trace to the tracer
            this.tracer.setId(traceId);
            // create a new trace either way(dependent on the parent trace or a new one)
            let _traceId = this.tracer.createChildId();
            let { headers } = Request.addZipkinHeaders(methodArgs, _traceId);
            Object.assign(methodArgs.headers, headers);
            this.tracer.setId(_traceId);
            context = Object.assign(context, { traceId: _traceId });
            this.tracer.recordServiceName(this.serviceName);
            this.tracer.recordRpc(method);
            this.tracer.recordBinary('http.url', completeUrl);
            this.tracer.recordBinary('http.method', method);
            this.tracer.recordAnnotation(new Annotation.ClientSend());
        });
    }
    ;
    afterHook(clientReq, second, context) {
        let { traceId, ignored } = context;
        if (ignored)
            return;
        let { res } = clientReq;
        let self = this;
        this.tracer.scoped(() => {
            self.tracer.setId(traceId);
            let code = res.statusCode;
            let message = res.statusMessage;
            self.tracer.recordBinary('http.status_code', code.toString());
            if (code != 200) {
                self.tracer.recordBinary('error', new Error(message || 'error').toString());
            }
            self.tracer.recordAnnotation(new Annotation.ClientRecv());
        });
    }
    boundModule(protocol) {
        let self = this;
        this.methods.forEach(function (existing) {
            let newFunc = function () {
                let context = { traceId: undefined, ignored: false };
                //call this to generate a new trace
                self.beforeHook.bind(self).apply(self, [arguments[0], arguments[1], context]);
                let secondSelf = this;
                //add hooks
                self.attach(secondSelf, arguments, context, undefined, self.afterHook.bind(self));
                return existing.apply(this, arguments);
            };
            newFunc.prototype = existing.prototype;
            protocol[existing.name] = newFunc;
        });
    }
    ;
    attach(obj, args, probeData, hookBefore = undefined, hookAfter = undefined) {
        var position = this.findCallbackArg(args);
        if (position == undefined)
            return;
        var orig = args[position];
        if (orig == undefined || orig.__zipkin_intercepted__)
            return;
        orig.__zipkin_intercepted__ = true;
        args[position] = function () {
            if (hookBefore) {
                hookBefore(this, arguments, probeData);
            }
            var ret = orig.apply(this, arguments);
            if (hookAfter) {
                hookAfter(this, arguments, probeData, ret);
            }
            return ret;
        };
    }
    findCallbackArg(args) {
        var position;
        for (var i = 0; i < args.length; i++) {
            if ((typeof args[i] === 'function')) {
                position = i;
                break;
            }
        }
        return position;
    }
    ;
}
exports.default = HttpOutBound;
;
//# sourceMappingURL=http.js.map