"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@opencensus/core");
const http_1 = require("./http");
const shimmer = require("shimmer");
const url = require("url");
const uuid = require("uuid");
class Http2Plugin extends http_1.HttpPlugin {
    constructor() {
        super('http2');
    }
    applyPatch() {
        shimmer.wrap(this.moduleExports, 'createServer', this.getPatchCreateServerFunction());
        shimmer.wrap(this.moduleExports, 'createSecureServer', this.getPatchCreateServerFunction());
        shimmer.wrap(this.moduleExports, 'connect', this.getPatchConnectFunction());
        return this.moduleExports;
    }
    applyUnpatch() {
        shimmer.unwrap(this.moduleExports, 'createServer');
        shimmer.unwrap(this.moduleExports, 'createSecureServer');
        shimmer.unwrap(this.moduleExports, 'connect');
    }
    getPatchConnectFunction() {
        const plugin = this;
        return (original) => {
            return function patchedConnect(authority) {
                const client = original.apply(this, arguments);
                shimmer.wrap(client, 'request', (original) => (plugin.getPatchRequestFunction())(original, authority));
                shimmer.unwrap(plugin.moduleExports, 'connect');
                return client;
            };
        };
    }
    getPatchRequestFunction() {
        const plugin = this;
        return (original, authority) => {
            return function patchedRequest(headers) {
                if (headers['x-opencensus-outgoing-request']) {
                    return original.apply(this, arguments);
                }
                const request = original.apply(this, arguments);
                plugin.tracer.wrapEmitter(request);
                const traceOptions = {
                    name: `http2-${(headers[':method'] || 'GET').toLowerCase()}`,
                    kind: core_1.SpanKind.CLIENT
                };
                if (!plugin.tracer.currentRootSpan) {
                    return plugin.tracer.startRootSpan(traceOptions, plugin.getMakeHttp2RequestTraceFunction(request, headers, authority, plugin));
                }
                else {
                    const span = plugin.tracer.startChildSpan(traceOptions.name, traceOptions.kind);
                    return (plugin.getMakeHttp2RequestTraceFunction(request, headers, authority, plugin))(span);
                }
            };
        };
    }
    getMakeHttp2RequestTraceFunction(request, headers, authority, plugin) {
        return (span) => {
            if (!span)
                return request;
            const setter = {
                setHeader(name, value) {
                    headers[name] = value;
                }
            };
            const propagation = plugin.tracer.propagation;
            if (propagation) {
                propagation.inject(setter, span.spanContext);
            }
            request.on('response', (responseHeaders) => {
                const status = `${responseHeaders[':status']}`;
                span.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_STATUS_CODE, status);
                span.setStatus(Http2Plugin.convertTraceStatus(parseInt(status, 10)));
            });
            request.on('end', () => {
                const userAgent = headers['user-agent'] || headers['User-Agent'] || null;
                span.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_HOST, `${url.parse(authority).host}`);
                span.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_METHOD, `${headers[':method']}`);
                span.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_PATH, `${headers[':path']}`);
                span.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_ROUTE, `${headers[':path']}`);
                if (userAgent) {
                    span.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_USER_AGENT, `${userAgent}`);
                }
                span.addMessageEvent(core_1.MessageEventType.SENT, uuid.v4().split('-').join(''));
                span.end();
            });
            request.on('error', (err) => {
                span.addAttribute(http_1.HttpPlugin.ATTRIBUTE_HTTP_ERROR_NAME, err.name);
                span.addAttribute(http_1.HttpPlugin.ATTRIBUTE_HTTP_ERROR_MESSAGE, err.message);
                span.setStatus(core_1.CanonicalCode.UNKNOWN, err.message);
                span.end();
            });
            return request;
        };
    }
    getPatchCreateServerFunction() {
        const plugin = this;
        return (original) => {
            return function patchedCreateServer() {
                const server = original.apply(this, arguments);
                shimmer.wrap(server.constructor.prototype, 'emit', plugin.getPatchEmitFunction());
                shimmer.unwrap(plugin.moduleExports, 'createServer');
                shimmer.unwrap(plugin.moduleExports, 'createSecureServer');
                return server;
            };
        };
    }
    getPatchEmitFunction() {
        const plugin = this;
        return (original) => {
            return function patchedEmit(event, stream, headers) {
                if (event !== 'stream') {
                    return original.apply(this, arguments);
                }
                const propagation = plugin.tracer.propagation;
                const getter = {
                    getHeader(name) {
                        return headers[name];
                    }
                };
                const traceOptions = {
                    name: headers[':path'],
                    kind: core_1.SpanKind.SERVER,
                    spanContext: propagation ? propagation.extract(getter) : null
                };
                let statusCode = 0;
                const originalRespond = stream.respond;
                stream.respond = function () {
                    stream.respond = originalRespond;
                    statusCode = arguments[0][':status'];
                    return stream.respond.apply(this, arguments);
                };
                return plugin.tracer.startRootSpan(traceOptions, rootSpan => {
                    if (!rootSpan)
                        return original.apply(this, arguments);
                    plugin.tracer.wrapEmitter(stream);
                    const originalEnd = stream.end;
                    stream.end = function () {
                        stream.end = originalEnd;
                        const returned = stream.end.apply(this, arguments);
                        const userAgent = (headers['user-agent'] || headers['User-Agent'] ||
                            null);
                        rootSpan.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_HOST, `${headers[':authority']}`);
                        rootSpan.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_METHOD, `${headers[':method']}`);
                        rootSpan.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_PATH, `${headers[':path']}`);
                        rootSpan.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_ROUTE, `${headers[':path']}`);
                        rootSpan.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_USER_AGENT, userAgent);
                        rootSpan.addAttribute(Http2Plugin.ATTRIBUTE_HTTP_STATUS_CODE, `${statusCode}`);
                        rootSpan.setStatus(Http2Plugin.convertTraceStatus(statusCode));
                        rootSpan.addMessageEvent(core_1.MessageEventType.RECEIVED, uuid.v4().split('-').join(''));
                        rootSpan.end();
                        return returned;
                    };
                    return original.apply(this, arguments);
                });
            };
        };
    }
}
exports.Http2Plugin = Http2Plugin;
const plugin = new Http2Plugin();
exports.plugin = plugin;
//# sourceMappingURL=data:application/json;base64,[encoded data, 8972 characters]