@mdf.js/mqtt-provider
Version:
MMS - MQTT Port for Javascript/Typescript
186 lines • 7.33 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Port = void 0;
/**
* Copyright 2024 Mytra Control S.L. All rights reserved.
*
* Use of this source code is governed by an MIT-style license that can be found in the LICENSE file
* or at https://opensource.org/licenses/MIT.
*/
const core_1 = require("@mdf.js/core");
const crash_1 = require("@mdf.js/crash");
const mqtt_1 = require("mqtt");
const config_1 = require("../config");
const DEFAULT_PING_CHECK_INTERVAL = 10000;
/** MQTT port implementation */
class Port extends core_1.Layer.Provider.Port {
/**
* Implementation of functionalities of an MQTT port instance.
* @param config - Port configuration options
* @param logger - Port logger, to be used internally
*/
constructor(config, logger) {
super(config, logger, typeof config.clientId === 'string' ? config.clientId : config_1.CONFIG_PROVIDER_BASE_NAME);
/** Update the state of the connection*/
this.checkConnectionStatus = () => {
const shouldBeHealthy = this.state;
if (this.isHealthy === shouldBeHealthy) {
return;
}
else if (shouldBeHealthy) {
this.emit('healthy');
}
else {
// @ts-ignore - Test environment
this.emit('unhealthy', this.updateLastError(new crash_1.Crash('Ping response not received')));
}
this.isHealthy = shouldBeHealthy;
};
/**
* Manage the event of a new connection or reconnection to the MQTT broker.
* @param connectACK - MQTT connection acknowledgement packet
*/
this.onConnect = (connectACK) => {
// Stryker disable next-line all
this.logger.debug(`Port connected`, this.uuid, this.name, connectACK);
this.isConnected = true;
};
/** Manage the event of a new reconnection to the MQTT broker. */
this.onReconnect = () => {
// Stryker disable next-line all
this.logger.debug(`Port reconnecting`, this.uuid, this.name);
};
/** Manage the event of a new disconnection to the MQTT broker. */
this.onClose = () => {
// Stryker disable next-line all
this.logger.debug(`Port disconnected`, this.uuid, this.name);
this.isConnected = false;
};
/**
* Manage the event of a new disconnection to the MQTT broker.
* @param packet - MQTT disconnection packet
*/
this.onDisconnect = (packet) => {
// Stryker disable next-line all
this.logger.debug(`Port disconnection request from broker: ${JSON.stringify(packet, null, 2)}`, this.uuid, this.name, packet);
};
/** Manage the event of a new offline state in the MQTT connection. */
this.onOffline = () => {
// Stryker disable next-line all
this.logger.debug(`Port offline`, this.uuid, this.name);
};
/**
* Update the last error in the port instance.
* @param rawError - Error object
* @returns Crash or Multi object
*/
this.updateLastError = (rawError) => {
const cause = crash_1.Crash.from(rawError, this.uuid);
this.logger.crash(cause, this.name);
this.addCheck('lastError', {
componentId: this.uuid,
observedValue: cause.message,
observedUnit: 'Last error',
status: 'pass',
output: cause.message,
time: new Date().toISOString(),
});
return cause;
};
/**
* Manage the event of a new error in the MQTT connection.
* @param rawError - Error object
*/
this.onError = (rawError) => {
const cause = this.updateLastError(rawError);
if (this.isConnected) {
this.emit('error', cause);
}
return cause;
};
const cleanedOptions = { ...this.config, url: undefined, manualConnect: true };
this.instance = (0, mqtt_1.connect)(this.config.url, cleanedOptions);
// Stryker disable next-line all
this.logger.debug(`New instance of MQTT port created: ${this.uuid}`);
this.isConnected = false;
this.isHealthy = false;
}
/** Return the underlying port instance */
get client() {
return this.instance;
}
/** Return the port state as a boolean value, true if the port is available, false in otherwise */
get state() {
return this.isConnected && this.client.connected;
}
/** Initialize the port instance */
async start() {
if (this.isConnected) {
// Stryker disable next-line all
this.logger.warn(`Port already started`, this.uuid, this.name);
return;
}
return new Promise((resolve, reject) => {
const onConnect = (connectACK) => {
this.instance.removeListener('error', onError);
this.onConnect(connectACK);
this.eventsWrapping(this.instance);
this.connectionChecker = setInterval(this.checkConnectionStatus, this.config.keepalive || DEFAULT_PING_CHECK_INTERVAL);
resolve();
};
const onError = (error) => {
this.instance.removeListener('connect', onConnect);
this.instance.end(true, () => {
reject(this.onError(error));
});
};
this.instance.once('connect', onConnect);
this.instance.once('error', onError);
this.instance.connect();
});
}
/** Stop the port instance */
async stop() {
if (!this.isConnected) {
// Stryker disable next-line all
this.logger.warn(`Port already stopped`, this.uuid, this.name);
return;
}
await this.instance.endAsync();
this.onClose();
this.eventsUnwrapping(this.instance);
if (this.connectionChecker) {
clearInterval(this.connectionChecker);
}
}
/** Close the port instance */
async close() {
await this.stop();
}
/**
* Attach all the events and log for debugging
* @param instance - client where the event managers will be attached
*/
eventsWrapping(instance) {
instance.on('connect', this.onConnect);
instance.on('reconnect', this.onReconnect);
instance.on('close', this.onClose);
instance.on('disconnect', this.onDisconnect);
instance.on('offline', this.onOffline);
instance.on('error', this.onError);
}
/**
* Remove all the events listeners
* @param instance - client where the event managers will be unattached
*/
eventsUnwrapping(instance) {
instance.off('connect', this.onConnect);
instance.off('reconnect', this.onReconnect);
instance.off('close', this.onClose);
instance.off('disconnect', this.onDisconnect);
instance.off('offline', this.onOffline);
instance.off('error', this.onError);
}
}
exports.Port = Port;
//# sourceMappingURL=Port.js.map