"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MQTTServer = void 0;
const mqtt_connection_1 = __importDefault(require("mqtt-connection"));
const ws_1 = __importDefault(require("ws"));
const steed_1 = __importDefault(require("steed"));
const pino_1 = __importDefault(require("pino"));
const lodash_1 = require("lodash");
const nanoid_1 = require("nanoid");
const trie_listener_1 = require("./trie-listener");
const client_1 = require("./client");
const events_1 = require("events");
const DEFAULT_OPTIONS = {
    maxConnections: 10000000,
    backend: {
        wildcardOne: '+',
        wildcardSome: '#',
    },
    stats: false,
    publishNewClient: true,
    publishClientDisconnect: true,
    publishSubscriptions: true,
    maxInflightMessages: 1024,
    onQoS2publish: 'noack',
    logger: {
        name: 'amplify-mqtt-server',
        level: 'warn',
    },
};
const nop = () => { };
class MQTTServer extends events_1.EventEmitter {
    constructor(options = {}, callback = (err, data) => { }) {
        super();
        this.callback = callback;
        this.options = (0, lodash_1.defaultsDeep)(options, DEFAULT_OPTIONS);
        this._dedupId = 0;
        this.clients = {};
        this.closed = false;
        this.closed = false;
        this.logger = (0, pino_1.default)(this.options.logger);
        this.onQoS2publish = this.options.onQoS2publish;
        this.id = this.options.id || (0, nanoid_1.nanoid)(7);
        new Promise(resolve => {
            const listener = new trie_listener_1.TrieListener(this.options.backend);
            listener.once('ready', function () {
                resolve(listener);
            });
        })
            .then(listener => {
            this.listener = listener;
            this.init();
        })
            .catch(err => {
            callback(err);
        });
    }
    init() {
        this.on('clientConnected', client => {
            if (this.options.publishNewClient) {
                this.publish({
                    topic: `$SYS/${this.id}/new/clients`,
                    payload: client.id,
                });
            }
            this.clients[client.id] = client;
        });
        this.once('ready', () => {
            this.callback(null, this);
        });
        this.on('ready', () => {
            this.listener.subscribe('$SYS/+/new/clients', (topic, payload) => {
                const serverId = topic.split('/')[1];
                const clientId = payload;
                if (this.clients[clientId] && serverId !== this.id) {
                    this.clients[clientId].close(null, 'new connection request');
                }
            });
        });
        if (this.options.publishSubscriptions) {
            this.on('subscribed', (topic, client) => {
                this.publish({
                    topic: `$SYS/${this.id}/new/subscribes`,
                    payload: JSON.stringify({
                        clientId: client.id,
                        topic: topic,
                    }),
                });
            });
            this.on('unsubscribed', (topic, client) => {
                this.publish({
                    topic: '$SYS/' + this.id + '/new/unsubscribes',
                    payload: JSON.stringify({
                        clientId: client.id,
                        topic: topic,
                    }),
                });
            });
        }
        this.on('clientDisconnected', client => {
            if (this.options.publishClientDisconnect) {
                this.publish({
                    topic: '$SYS/' + this.id + '/disconnect/clients',
                    payload: client.id,
                });
            }
            delete this.clients[client.id];
        });
    }
    toString() {
        return 'AmplifyMQTTServer.Server';
    }
    subscribe(topic, callback, done) {
        this.listener.subscribe(topic, callback, done);
    }
    publish(packet, client, callback) {
        let logger = this.logger;
        if (typeof client === 'function') {
            callback = client;
            client = null;
        }
        else if (client) {
            logger = client.logger;
        }
        if (!callback) {
            callback = nop;
        }
        const newPacket = {
            topic: packet.topic,
            payload: packet.payload,
            messageId: (0, nanoid_1.nanoid)(7),
            qos: packet.qos,
            retain: packet.retain,
        };
        const opts = {
            qos: packet.qos,
            messageId: newPacket.messageId,
        };
        if (client) {
            opts.clientId = client.id;
        }
        if (this.closed) {
            logger.debug({ packet: newPacket }, 'not delivering because we are closed');
            return;
        }
        this.listener.publish(newPacket.topic, newPacket.payload, opts, () => {
            if (newPacket.topic.indexOf('$SYS') >= 0) {
                logger.trace({ packet: newPacket }, 'published packet');
            }
            else {
                logger.debug({ packet: newPacket }, 'published packet');
            }
            this.emit('published', newPacket, client);
            callback(undefined, newPacket);
        });
    }
    close(callback = nop) {
        const stuffToClose = [];
        if (this.closed) {
            return callback();
        }
        this.closed = true;
        Object.keys(this.clients).forEach(i => {
            stuffToClose.push(this.clients[i]);
        });
        steed_1.default.each(stuffToClose, (toClose, cb) => {
            try {
                toClose.close(cb, 'server closed');
            }
            catch (e) { }
        }, () => {
            this.listener.close(() => {
                this.logger.info('server closed');
                this.emit('closed');
                callback();
            });
        });
    }
    updateOfflinePacket(client, originMessageId, packet, callback) {
        if (callback) {
            callback(null, packet);
        }
    }
    attachHttpServer(server, path) {
        const opt = { server: server };
        if (path) {
            opt.path = path;
        }
        const wss = new ws_1.default.Server(opt);
        wss.on('connection', socket => {
            const stream = ws_1.default.createWebSocketStream(socket, {});
            const conn = new mqtt_connection_1.default(stream);
            new client_1.Client(conn, this);
        });
    }
    nextDedupId() {
        return this._dedupId++;
    }
}
exports.MQTTServer = MQTTServer;
//# sourceMappingURL=server.js.map