broker-lib
Version:
Multi-Broker Message Bus with Multi-Topic Support
182 lines • 6.79 kB
JavaScript
;
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