refactor(MQTT Node): Refactor, fix duplicate triggers, and add Unit tests (#9847)

Co-authored-by: Elias Meire <elias@meire.dev>
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2024-06-24 18:51:59 +02:00
committed by GitHub
parent e51de9d391
commit 164ec72c0d
8 changed files with 420 additions and 271 deletions

View File

@@ -1,6 +1,6 @@
import type { IClientPublishOptions } from 'mqtt';
import type {
IExecuteFunctions,
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
INodeCredentialTestResult,
@@ -8,10 +8,10 @@ import type {
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { randomString } from 'n8n-workflow';
import * as mqtt from 'mqtt';
import { formatPrivateKey } from '@utils/utilities';
import { createClient, type MqttCredential } from './GenericFunctions';
type PublishOption = Pick<IClientPublishOptions, 'qos' | 'retain'>;
export class Mqtt implements INodeType {
description: INodeTypeDescription = {
@@ -110,63 +110,11 @@ export class Mqtt implements INodeType {
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
const credentials = credential.data as unknown as MqttCredential;
try {
const protocol = (credentials.protocol as string) || 'mqtt';
const host = credentials.host as string;
const brokerUrl = `${protocol}://${host}`;
const port = (credentials.port as number) || 1883;
const clientId =
(credentials.clientId as string) || `mqttjs_${randomString(8).toLowerCase()}`;
const clean = credentials.clean as boolean;
const ssl = credentials.ssl as boolean;
const ca = formatPrivateKey(credentials.ca as string);
const cert = formatPrivateKey(credentials.cert as string);
const key = formatPrivateKey(credentials.key as string);
const rejectUnauthorized = credentials.rejectUnauthorized as boolean;
let client: mqtt.MqttClient;
if (!ssl) {
const clientOptions: mqtt.IClientOptions = {
port,
clean,
clientId,
};
if (credentials.username && credentials.password) {
clientOptions.username = credentials.username as string;
clientOptions.password = credentials.password as string;
}
client = mqtt.connect(brokerUrl, clientOptions);
} else {
const clientOptions: mqtt.IClientOptions = {
port,
clean,
clientId,
ca,
cert,
key,
rejectUnauthorized,
};
if (credentials.username && credentials.password) {
clientOptions.username = credentials.username as string;
clientOptions.password = credentials.password as string;
}
client = mqtt.connect(brokerUrl, clientOptions);
}
await new Promise((resolve, reject) => {
client.on('connect', (test) => {
resolve(test);
client.end();
});
client.on('error', (error) => {
client.end();
reject(error);
});
});
const client = await createClient(credentials);
client.end();
} catch (error) {
return {
status: 'Error',
@@ -182,87 +130,27 @@ export class Mqtt implements INodeType {
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = (await this.getCredentials('mqtt')) as unknown as MqttCredential;
const client = await createClient(credentials);
const publishPromises = [];
const items = this.getInputData();
const length = items.length;
const credentials = await this.getCredentials('mqtt');
const protocol = (credentials.protocol as string) || 'mqtt';
const host = credentials.host as string;
const brokerUrl = `${protocol}://${host}`;
const port = (credentials.port as number) || 1883;
const clientId = (credentials.clientId as string) || `mqttjs_${randomString(8).toLowerCase()}`;
const clean = credentials.clean as boolean;
const ssl = credentials.ssl as boolean;
const ca = credentials.ca as string;
const cert = credentials.cert as string;
const key = credentials.key as string;
const rejectUnauthorized = credentials.rejectUnauthorized as boolean;
let client: mqtt.MqttClient;
if (!ssl) {
const clientOptions: mqtt.IClientOptions = {
port,
clean,
clientId,
};
if (credentials.username && credentials.password) {
clientOptions.username = credentials.username as string;
clientOptions.password = credentials.password as string;
}
client = mqtt.connect(brokerUrl, clientOptions);
} else {
const clientOptions: mqtt.IClientOptions = {
port,
clean,
clientId,
ca,
cert,
key,
rejectUnauthorized,
};
if (credentials.username && credentials.password) {
clientOptions.username = credentials.username as string;
clientOptions.password = credentials.password as string;
}
client = mqtt.connect(brokerUrl, clientOptions);
for (let i = 0; i < items.length; i++) {
const topic = this.getNodeParameter('topic', i) as string;
const options = this.getNodeParameter('options', i) as unknown as PublishOption;
const sendInputData = this.getNodeParameter('sendInputData', i) as boolean;
const message = sendInputData
? JSON.stringify(items[i].json)
: (this.getNodeParameter('message', i) as string);
publishPromises.push(client.publishAsync(topic, message, options));
}
const sendInputData = this.getNodeParameter('sendInputData', 0) as boolean;
await Promise.all(publishPromises);
const data = await new Promise((resolve, reject) => {
client.on('connect', () => {
for (let i = 0; i < length; i++) {
let message;
const topic = this.getNodeParameter('topic', i) as string;
const options = this.getNodeParameter('options', i);
// wait for the in-flight messages to be acked.
// needed for messages with QoS 1 & 2
await client.endAsync();
try {
if (sendInputData) {
message = JSON.stringify(items[i].json);
} else {
message = this.getNodeParameter('message', i) as string;
}
client.publish(topic, message, options);
} catch (e) {
reject(e);
}
}
//wait for the in-flight messages to be acked.
//needed for messages with QoS 1 & 2
client.end(false, {}, () => {
resolve([items]);
});
client.on('error', (e) => {
reject(e);
});
});
});
return data as INodeExecutionData[][];
return [items];
}
}