UNPKG

broker-lib

Version:

Multi-Broker Message Bus with Multi-Topic Support

182 lines 6.79 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MqttBroker = void 0; const mqtt_1 = __importDefault(require("mqtt")); const events_1 = require("events"); class MqttBroker extends events_1.EventEmitter { constructor(config) { super(); this.connected = false; this.subscribedTopics = new Set(); const options = { clean: config.clean ?? true, reconnectPeriod: config.reconnectPeriod ?? 1000, connectTimeout: config.connectTimeout ?? 30000, rejectUnauthorized: config.rejectUnauthorized ?? true, }; if (config.username) { options.username = config.username; } if (config.password) { options.password = config.password; } if (config.clientId) { options.clientId = config.clientId; } this.client = mqtt_1.default.connect(config.url, options); // Increase max listeners to prevent warnings this.client.setMaxListeners(20); this.client.on('connect', () => { this.connected = true; this.emit('connect'); }); this.client.on('error', (error) => { this.connected = false; // Don't emit error for connection-related issues that will be handled by reconnection if (!this.isConnectionError(error)) { this.emit('error', error); } }); this.client.on('close', () => { this.connected = false; this.emit('disconnect'); }); this.client.on('reconnect', () => { this.emit('reconnect'); }); // Set up a single message handler that uses the stored messageHandler this.client.on('message', async (topic, message) => { if (this.messageHandler) { try { await this.messageHandler(topic, message); } catch (error) { console.error(`Error handling message from topic ${topic}:`, error); this.emit('error', error); } } }); } async connect() { return new Promise((resolve, reject) => { if (this.connected) { resolve(); return; } const timeout = setTimeout(() => { // Clean up listeners on timeout this.client.removeAllListeners('connect'); this.client.removeAllListeners('error'); reject(new Error('MQTT connection timeout')); }, 30000); const connectHandler = () => { clearTimeout(timeout); // Clean up listeners on successful connection this.client.removeListener('connect', connectHandler); this.client.removeListener('error', errorHandler); resolve(); }; const errorHandler = (error) => { clearTimeout(timeout); // Clean up listeners on error this.client.removeListener('connect', connectHandler); this.client.removeListener('error', errorHandler); reject(new Error(`MQTT connection failed: ${error.message}`)); }; this.client.once('connect', connectHandler); this.client.once('error', errorHandler); }); } async publish(topic, message, options) { return new Promise((resolve, reject) => { if (!this.connected) { reject(new Error('MQTT broker is not connected. Call connect() first.')); return; } const publishOptions = { qos: (options?.qos ?? 0), retain: options?.retain ?? false, }; this.client.publish(topic, message, publishOptions, (error) => { if (error) { reject(new Error(`Failed to publish message to topic ${topic}: ${error.message}`)); } else { resolve(); } }); }); } async subscribe(topics, handler, options) { return new Promise((resolve, reject) => { if (!this.connected) { reject(new Error('MQTT broker is not connected. Call connect() first.')); return; } // Store the message handler this.messageHandler = handler; // Subscribe to new topics only const newTopics = topics.filter(topic => !this.subscribedTopics.has(topic)); if (newTopics.length === 0) { // All topics are already subscribed, just update the handler resolve(); return; } const subscribeOptions = { qos: (options?.qos ?? 0), }; this.client.subscribe(newTopics, subscribeOptions, (error) => { if (error) { reject(new Error(`Failed to subscribe to topics ${newTopics.join(', ')}: ${error.message}`)); } else { // Add new topics to the subscribed set for (const topic of newTopics) { this.subscribedTopics.add(topic); } resolve(); } }); }); } async disconnect() { return new Promise((resolve, reject) => { if (this.connected) { this.client.end(true, {}, (error) => { if (error) { reject(new Error(`Failed to disconnect: ${error.message}`)); } else { this.connected = false; resolve(); } }); } else { resolve(); } }); } isConnected() { return this.connected; } isConnectionError(error) { const errorMessage = error?.message || ''; const connectionErrors = [ 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ETIMEDOUT', 'connection lost', 'connection closed', 'network error', 'socket error' ]; return connectionErrors.some(connError => errorMessage.toLowerCase().includes(connError.toLowerCase())); } } exports.MqttBroker = MqttBroker; //# sourceMappingURL=mqtt.js.map