'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@opencensus/core");
const shimmer = require("shimmer");
const semver = require("semver");
exports.kMiddlewareStack = Symbol('express-middleware-stack');
class ExpressPlugin extends core_1.BasePlugin {
    constructor(moduleName) {
        super(moduleName);
        this.kPatched = Symbol('express-layer-patched');
    }
    applyPatch() {
        this.logger.debug('Patched express');
        if (!semver.satisfies(this.version, '^4.0.0')) {
            this.logger.debug('express version %s not supported - aborting...', this.version);
            return this.moduleExports;
        }
        if (this.moduleExports) {
            const routerProto = semver.satisfies(this.version, '^5')
                ? (this.moduleExports.Router && this.moduleExports.Router.prototype)
                : this.moduleExports.Router;
            const plugin = this;
            this.logger.debug('patching express.Router.prototype.route');
            shimmer.wrap(routerProto, 'route', (original) => {
                return function route_trace(path) {
                    const route = original.apply(this, arguments);
                    const layer = this.stack[this.stack.length - 1];
                    plugin.applyLayerPatch(layer, path);
                    return route;
                };
            });
            this.logger.debug('patching express.Router.prototype.use');
            shimmer.wrap(routerProto, 'use', (original) => {
                return function use(path) {
                    const route = original.apply(this, arguments);
                    const layer = this.stack[this.stack.length - 1];
                    plugin.applyLayerPatch(layer, path);
                    return route;
                };
            });
            this.logger.debug('patching express.Application.use');
            shimmer.wrap(this.moduleExports.application, 'use', (original) => {
                return function use(path) {
                    const route = original.apply(this, arguments);
                    const layer = this._router.stack[this._router.stack.length - 1];
                    plugin.applyLayerPatch(layer, path);
                    return route;
                };
            });
        }
        return this.moduleExports;
    }
    applyUnpatch() {
        if (!semver.satisfies(this.version, '^4.0.0')) {
            return this.moduleExports;
        }
        const routerProto = semver.satisfies(this.version, '^5')
            ? (this.moduleExports.Router && this.moduleExports.Router.prototype)
            : this.moduleExports.Router;
        shimmer.unwrap(routerProto, 'use');
        shimmer.unwrap(routerProto, 'route');
        shimmer.unwrap(this.moduleExports.application, 'use');
    }
    applyLayerPatch(layer, layerPath) {
        const plugin = this;
        if (layer[this.kPatched] === true)
            return;
        layer[this.kPatched] = true;
        plugin.logger.debug('patching express.Router.Layer.handle');
        shimmer.wrap(layer, 'handle', function (original) {
            if (original.length !== 4) {
                return function (req, res, next) {
                    plugin.safePush(req, exports.kMiddlewareStack, layerPath);
                    let spanName = `Middleware - ${layer.name}`;
                    if (layer.route) {
                        spanName = `Route - ${layer.route.path}`;
                    }
                    else if (layer.name === 'router') {
                        spanName = `Router - ${layerPath}`;
                    }
                    const span = plugin.tracer.startChildSpan(spanName, core_1.SpanKind.CLIENT);
                    if (span === null)
                        return original.apply(this, arguments);
                    arguments[2] = function () {
                        if (!(req.route && arguments[0] instanceof Error)) {
                            req[exports.kMiddlewareStack].pop();
                        }
                        return plugin.patchEnd(span, next)();
                    };
                    return original.apply(this, arguments);
                };
            }
            return function (_err, req, res, next) {
                return original.apply(this, arguments);
            };
        });
    }
    safePush(obj, prop, value) {
        if (!obj[prop])
            obj[prop] = [];
        obj[prop].push(value);
    }
    patchEnd(span, callback) {
        const plugin = this;
        const patchedEnd = function (err) {
            if (plugin.options.detailedCommands === true && err instanceof Error) {
                span.addAttribute('error', err.message);
            }
            if (span.ended === false) {
                span.end();
            }
            return callback.apply(this, arguments);
        };
        return this.tracer.wrap(patchedEnd);
    }
}
exports.ExpressPlugin = ExpressPlugin;
const plugin = new ExpressPlugin('express');
exports.plugin = plugin;
//# sourceMappingURL=data:application/json;base64,[encoded data, 6332 characters]