"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DfnsBus = void 0;
const amqplib_1 = __importDefault(require("amqplib"));
const api_1 = require("@opentelemetry/api");
const logging_1 = require("../logging");
const constants_1 = require("./constants");
const message_signing_1 = require("./message-signing");
const tracing_1 = require("../tracing");
class DfnsBus {
    constructor(originator, exchangeName, connectionProps) {
        this.originator = originator;
        this.exchangeName = exchangeName;
        this.connectionProps = connectionProps;
        // we want connections to clean themselves up quickly if there is a sudden burst of lambda concurrency
        // heartbeat = 3s, means after 6 seconds of connection not sending heartbeats, rabbitmq server will close connection
        this.defaultConnectionHeartbeat = 3;
        this.defaultConnectionVhost = '/';
    }
    async init() {
        this.connection = await amqplib_1.default.connect({
            protocol: this.connectionProps.protocol,
            port: this.connectionProps.port,
            hostname: this.connectionProps.hostname,
            username: this.connectionProps.username,
            password: this.connectionProps.password,
            heartbeat: this.connectionProps.heartbeat || this.defaultConnectionHeartbeat,
            vhost: this.connectionProps.vhost || this.defaultConnectionVhost,
        }, {
            clientProperties: { connection_name: this.getNewConnectionName() },
        });
        this.channel = await this.connection.createConfirmChannel();
        this.connection.on('error', (error) => {
            // we add a error event listener on connection, otherwise default error handling will
            // trigger the last-resort 'uncaughtException' event of the process (see https://amqp-node.github.io/amqplib/channel_api.html#failure_callbacks)
            // which will make the lambda crash, even though we actually handle the closed connection error in the publish method.
            if (error.message !== constants_1.AmpqErrorMessage.AmpqUnexpectedClose && error.message !== constants_1.AmpqErrorMessage.EconnReset) {
                throw error;
            }
        });
        return this;
    }
    get isInit() {
        return !!this.connection && !!this.channel;
    }
    getNewConnectionName() {
        const timeSeconds = Math.trunc(new Date().getTime() / 1000);
        return `${this.originator}-${timeSeconds}`;
    }
    async close() {
        var _a, _b;
        await ((_a = this.channel) === null || _a === void 0 ? void 0 : _a.close());
        await ((_b = this.connection) === null || _b === void 0 ? void 0 : _b.close());
    }
    async publishToExchangeOrQueue(props) {
        return (0, tracing_1.withTracing)(props.topic, async (traceContext) => {
            const { messageBuffer, signature, topic, options } = props;
            const { redeliveredCount = 0, queueName } = options;
            const publishOptions = {
                type: topic,
                appId: this.originator,
                headers: {
                    Signature: signature,
                    [constants_1.REDELIVERED_MESSAGE_HEADER]: redeliveredCount,
                },
            };
            if (traceContext)
                api_1.propagation.inject(traceContext, publishOptions.headers);
            if (!this.channel) {
                throw Error('Channel should exist');
            }
            if (queueName) {
                return this.channel.sendToQueue(queueName, messageBuffer, publishOptions);
            }
            else {
                return this.channel.publish(this.exchangeName, topic, messageBuffer, publishOptions);
            }
        }, api_1.context.active());
    }
    async publish(dfnsMessage, options = {}) {
        const { disableConnectionRestore } = options;
        logging_1.logger.info(`Event bus publishing from ${this.originator} message topic ${dfnsMessage === null || dfnsMessage === void 0 ? void 0 : dfnsMessage.topic}`, {
            payload: { message: dfnsMessage },
        });
        if (!this.isInit) {
            await this.init();
        }
        if (!this.channel) {
            throw new Error('Cannot publish message. Channel is not initialed.');
        }
        const messageBuffer = (0, message_signing_1.getMessageAsBuffer)(dfnsMessage);
        const signature = await (0, message_signing_1.signDfnsMessage)(dfnsMessage);
        try {
            await this.channel.assertExchange(this.exchangeName, 'topic');
            await this.publishToExchangeOrQueue({
                topic: dfnsMessage.topic,
                messageBuffer,
                signature,
                options,
            });
        }
        catch (error) {
            if ((error.message === constants_1.AmpqErrorMessage.AmpqChannelClosed || error.message === constants_1.AmpqErrorMessage.EconnReset) &&
                !disableConnectionRestore) {
                // restore connection and retry
                await this.init();
                await this.publishToExchangeOrQueue({
                    topic: dfnsMessage.topic,
                    messageBuffer,
                    signature,
                    options: Object.assign(Object.assign({}, options), { disableConnectionRestore: true }),
                });
            }
            else {
                throw error;
            }
        }
        await this.channel.waitForConfirms();
    }
}
exports.DfnsBus = DfnsBus;
//# sourceMappingURL=DfnsBus.js.map