"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_fs_1 = __importDefault(require("node:fs"));
const bind_decorator_1 = __importDefault(require("bind-decorator"));
const mqtt_1 = require("mqtt");
const logger_1 = __importDefault(require("./util/logger"));
const settings = __importStar(require("./util/settings"));
const utils_1 = __importDefault(require("./util/utils"));
const NS = "z2m:mqtt";
class Mqtt {
    publishedTopics = new Set();
    connectionTimer;
    client;
    eventBus;
    republishRetainedTimer;
    defaultPublishOptions;
    retainedMessages = {};
    get info() {
        return {
            version: this.client.options.protocolVersion,
            server: `${this.client.options.protocol}://${this.client.options.host}:${this.client.options.port}`,
        };
    }
    get stats() {
        return {
            connected: this.isConnected(),
            queued: this.client.queue.length,
        };
    }
    constructor(eventBus) {
        this.eventBus = eventBus;
        this.defaultPublishOptions = {
            clientOptions: {},
            baseTopic: settings.get().mqtt.base_topic,
            skipLog: false,
            skipReceive: true,
            meta: {},
        };
    }
    async connect() {
        const mqttSettings = settings.get().mqtt;
        logger_1.default.info(`Connecting to MQTT server at ${mqttSettings.server}`);
        const options = {
            will: {
                topic: `${settings.get().mqtt.base_topic}/bridge/state`,
                payload: Buffer.from(JSON.stringify({ state: "offline" })),
                retain: !settings.get().mqtt.force_disable_retain,
                qos: 1,
            },
            properties: { maximumPacketSize: mqttSettings.maximum_packet_size },
        };
        if (mqttSettings.version) {
            options.protocolVersion = mqttSettings.version;
        }
        if (mqttSettings.keepalive) {
            logger_1.default.debug(`Using MQTT keepalive: ${mqttSettings.keepalive}`);
            options.keepalive = mqttSettings.keepalive;
        }
        if (mqttSettings.ca) {
            logger_1.default.debug(`MQTT SSL/TLS: Path to CA certificate = ${mqttSettings.ca}`);
            options.ca = node_fs_1.default.readFileSync(mqttSettings.ca);
        }
        if (mqttSettings.key && mqttSettings.cert) {
            logger_1.default.debug(`MQTT SSL/TLS: Path to client key = ${mqttSettings.key}`);
            logger_1.default.debug(`MQTT SSL/TLS: Path to client certificate = ${mqttSettings.cert}`);
            options.key = node_fs_1.default.readFileSync(mqttSettings.key);
            options.cert = node_fs_1.default.readFileSync(mqttSettings.cert);
        }
        if (mqttSettings.user && mqttSettings.password) {
            logger_1.default.debug(`Using MQTT login with username: ${mqttSettings.user}`);
            options.username = mqttSettings.user;
            options.password = mqttSettings.password;
        }
        else if (mqttSettings.user) {
            logger_1.default.debug(`Using MQTT login with username only: ${mqttSettings.user}`);
            options.username = mqttSettings.user;
        }
        else {
            logger_1.default.debug("Using MQTT anonymous login");
        }
        if (mqttSettings.client_id) {
            logger_1.default.debug(`Using MQTT client ID: '${mqttSettings.client_id}'`);
            options.clientId = mqttSettings.client_id;
        }
        if (mqttSettings.reject_unauthorized !== undefined && !mqttSettings.reject_unauthorized) {
            logger_1.default.debug("MQTT reject_unauthorized set false, ignoring certificate warnings.");
            options.rejectUnauthorized = false;
        }
        if (mqttSettings.server_name) {
            logger_1.default.debug(`MQTT SSL/TLS: SNI server name = ${mqttSettings.server_name}`);
            options.servername = mqttSettings.server_name;
        }
        this.client = await (0, mqtt_1.connectAsync)(mqttSettings.server, options);
        // https://github.com/Koenkk/zigbee2mqtt/issues/9822
        this.client.stream.setMaxListeners(0);
        this.client.on("error", (err) => {
            logger_1.default.error(`MQTT error: ${err.message}`);
        });
        if (mqttSettings.version != null && mqttSettings.version >= 5) {
            this.client.on("disconnect", (packet) => {
                logger_1.default.error(`MQTT disconnect: reason ${packet.reasonCode} (${packet.properties?.reasonString})`);
            });
        }
        this.client.on("message", this.onMessage);
        await this.onConnect();
        this.client.on("connect", this.onConnect);
        this.republishRetainedTimer = setTimeout(async () => {
            // Republish retained messages in case MQTT broker does not persist them.
            // https://github.com/Koenkk/zigbee2mqtt/issues/9629
            for (const msg of Object.values(this.retainedMessages)) {
                await this.publish(msg.topic, msg.payload, msg.options);
            }
        }, 2000);
        // Set timer at interval to check if connected to MQTT server.
        this.connectionTimer = setInterval(() => {
            if (!this.isConnected()) {
                logger_1.default.error("Not connected to MQTT server!");
            }
        }, utils_1.default.seconds(10));
    }
    async disconnect() {
        clearTimeout(this.connectionTimer);
        clearTimeout(this.republishRetainedTimer);
        const stateData = { state: "offline" };
        // prevent undesirable error when receiving SIGTERM/SIGINT during startup
        if (this.client) {
            await this.publish("bridge/state", JSON.stringify(stateData), { clientOptions: { retain: true } });
        }
        this.eventBus.removeListeners(this);
        logger_1.default.info("Disconnecting from MQTT server");
        await this.client?.endAsync();
    }
    async subscribe(topic) {
        await this.client.subscribeAsync(topic);
    }
    async unsubscribe(topic) {
        await this.client.unsubscribeAsync(topic);
    }
    async onConnect() {
        logger_1.default.info("Connected to MQTT server");
        const stateData = { state: "online" };
        await this.publish("bridge/state", JSON.stringify(stateData), { clientOptions: { retain: true, qos: 1 } });
        await this.subscribe(`${settings.get().mqtt.base_topic}/#`);
    }
    onMessage(topic, message) {
        // Since we subscribe to zigbee2mqtt/# we also receive the message we send ourselves, skip these.
        if (!this.publishedTopics.has(topic)) {
            logger_1.default.debug(() => `Received MQTT message on '${topic}' with data '${message.toString()}'`, NS);
            this.eventBus.emitMQTTMessage({ topic, message: message.toString() });
        }
        if (this.republishRetainedTimer && topic === `${settings.get().mqtt.base_topic}/bridge/info`) {
            clearTimeout(this.republishRetainedTimer);
            this.republishRetainedTimer = undefined;
        }
    }
    isConnected() {
        return this.client && !this.client.reconnecting && !this.client.disconnecting && !this.client.disconnected;
    }
    async publish(topic, payload, options = {}) {
        // TODO: add `options.validateTopic: boolean` to bypass these checks when topic is "controlled"
        if (topic.includes("+") || topic.includes("#")) {
            // https://github.com/Koenkk/zigbee2mqtt/issues/26939#issuecomment-2772309646
            logger_1.default.error(`Topic '${topic}' includes wildcard characters, skipping publish.`);
            return;
        }
        const finalOptions = { ...this.defaultPublishOptions, ...options };
        topic = `${finalOptions.baseTopic}/${topic}`;
        if (finalOptions.skipReceive) {
            this.publishedTopics.add(topic);
        }
        if (finalOptions.clientOptions.retain) {
            if (payload) {
                this.retainedMessages[topic] = { payload, options: finalOptions, topic: topic.substring(finalOptions.baseTopic.length + 1) };
            }
            else {
                delete this.retainedMessages[topic];
            }
        }
        this.eventBus.emitMQTTMessagePublished({ topic, payload, options: finalOptions });
        if (!this.isConnected()) {
            if (!finalOptions.skipLog) {
                logger_1.default.error("Not connected to MQTT server!");
                logger_1.default.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
            }
            return;
        }
        let clientOptions = finalOptions.clientOptions;
        if (settings.get().mqtt.force_disable_retain) {
            clientOptions = { ...finalOptions.clientOptions, retain: false };
        }
        if (!finalOptions.skipLog) {
            logger_1.default.info(() => `MQTT publish: topic '${topic}', payload '${payload}'`, NS);
        }
        try {
            await this.client.publishAsync(topic, payload, clientOptions);
        }
        catch (error) {
            if (!finalOptions.skipLog) {
                logger_1.default.error(`MQTT server error: ${error.message}`);
                logger_1.default.error(`Could not send message: topic: '${topic}', payload: '${payload}`);
            }
        }
    }
}
exports.default = Mqtt;
__decorate([
    bind_decorator_1.default
], Mqtt.prototype, "onConnect", null);
__decorate([
    bind_decorator_1.default
], Mqtt.prototype, "onMessage", null);
//# sourceMappingURL=data:application/json;base64, 