@cuppet/core 1.2.5 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -132,8 +132,8 @@ For a detailed configuration and step definitions guide, see [GUIDE.MD](./GUIDE.
132
132
 
133
133
  This package requires the following peer dependencies:
134
134
 
135
- - `@cucumber/cucumber` ^11.0.0
136
- - `config` ^3.3.9
135
+ - `@cucumber/cucumber` ^12.0.0
136
+ - `config` ^4.1.0
137
137
 
138
138
  Make sure to install these in your project:
139
139
 
@@ -1,7 +1,5 @@
1
1
  const { BeforeAll, AfterAll, Before, After, AfterStep, Status } = require('@cucumber/cucumber');
2
- const BrowserManager = require('./browserManager');
3
- const AppiumManager = require('./appiumManager');
4
- const MqttManager = require('./mqttManager');
2
+ const { BrowserManager, AppiumManager, MqttManager, KafkaManager } = require('./managers');
5
3
  const fs = require('fs');
6
4
  const config = require('config');
7
5
  const dataStore = require('../../src/dataStorage');
@@ -68,6 +66,7 @@ Before(async function (testCase) {
68
66
  const appiumTag = arrayTags.find((item) => item.name === '@appium');
69
67
  const mqttTag = arrayTags.find((item) => item.name === '@mqtt');
70
68
  const apiTag = arrayTags.find((item) => item.name === '@api');
69
+ const kafkaTag = arrayTags.find((item) => item.name === '@kafka');
71
70
 
72
71
  // Initialize MQTT Manager if @mqtt tag is present
73
72
  if (mqttTag) {
@@ -76,8 +75,15 @@ Before(async function (testCase) {
76
75
  this.mqttManager = mqttManager;
77
76
  }
78
77
 
78
+ // Initialize Kafka Manager if @kafka tag is present
79
+ if (kafkaTag) {
80
+ const kafkaManager = new KafkaManager();
81
+ await kafkaManager.initialize();
82
+ this.kafkaManager = kafkaManager;
83
+ }
84
+
79
85
  // Initialize browser unless it's API-only or MQTT-only test
80
- if (!appiumTag && !apiTag && !mqttTag) {
86
+ if (!appiumTag && !apiTag && !mqttTag && !kafkaTag) {
81
87
  const browserManager = new BrowserManager(browserViewport, browserArgs, credentials);
82
88
  await browserManager.initialize();
83
89
 
@@ -112,4 +118,10 @@ After(async function (testCase) {
112
118
  } else if (this.appiumDriver) {
113
119
  await this.appiumManager.stop();
114
120
  }
121
+
122
+ // Cleanup Kafka connection if present
123
+ if (this.kafkaManager) {
124
+ await this.kafkaManager.stop();
125
+ this.kafkaManager = null;
126
+ }
115
127
  });
@@ -0,0 +1,7 @@
1
+ // features/app/managers/index.js
2
+ module.exports = {
3
+ BrowserManager: require('./browserManager'),
4
+ AppiumManager: require('./appiumManager'),
5
+ MqttManager: require('./mqttManager'),
6
+ KafkaManager: require('./kafkaManager'),
7
+ };
@@ -0,0 +1,180 @@
1
+ const { Kafka, logLevel } = require('kafkajs');
2
+ const config = require('config');
3
+
4
+ /**
5
+ * KafkaManager class for managing Kafka client lifecycle
6
+ * Follows the same pattern as MqttManager and BrowserManager
7
+ */
8
+ class KafkaManager {
9
+ constructor(customOptions = {}) {
10
+ this.kafka = null;
11
+ this.producer = null;
12
+ this.consumer = null;
13
+ this.options = this.prepareOptions(customOptions);
14
+ this.isInitialized = false;
15
+ }
16
+
17
+ /**
18
+ * Prepare Kafka connection options from config or provided options
19
+ * @param {Object} customOptions - Custom options to override config
20
+ * @returns {Object} - Kafka connection options
21
+ */
22
+ prepareOptions(customOptions) {
23
+ const defaultOptions = {
24
+ clientId: config.has('kafka.clientId')
25
+ ? config.get('kafka.clientId')
26
+ : `cuppet-test-${Math.random().toString(16).slice(2, 8)}`,
27
+ brokers: config.has('kafka.brokers') ? config.get('kafka.brokers') : ['localhost:9092'],
28
+ connectionTimeout: config.has('kafka.connectionTimeout') ? config.get('kafka.connectionTimeout') : 5000,
29
+ requestTimeout: config.has('kafka.requestTimeout') ? config.get('kafka.requestTimeout') : 30000,
30
+ logLevel: config.has('kafka.logLevel') ? Number(config.get('kafka.logLevel')) : logLevel.ERROR,
31
+ };
32
+
33
+ // Add SASL authentication if provided in config
34
+ if (config.has('kafka.sasl')) {
35
+ defaultOptions.sasl = config.get('kafka.sasl');
36
+ }
37
+
38
+ // Add SSL configuration if provided
39
+ if (config.has('kafka.ssl')) {
40
+ defaultOptions.ssl = config.get('kafka.ssl');
41
+ }
42
+
43
+ // Merge with custom options (custom options take precedence)
44
+ return { ...defaultOptions, ...customOptions };
45
+ }
46
+
47
+ /**
48
+ * Initialize Kafka client and test connection
49
+ * @returns {Promise<void>}
50
+ */
51
+ async initialize() {
52
+ try {
53
+ // Create Kafka instance
54
+ this.kafka = new Kafka(this.options);
55
+ console.log(`Successfully connected to Kafka`);
56
+ this.isInitialized = true;
57
+ } catch (error) {
58
+ throw new Error(`Failed to initialize Kafka client: ${error.message}`);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Create and connect a producer
64
+ * @param {Object} producerOptions - Producer configuration options
65
+ * @returns {Promise<Object>} - Connected producer instance
66
+ */
67
+ async createProducer(producerOptions = {}) {
68
+ if (!this.isInitialized) {
69
+ throw new Error('Kafka client not initialized. Call initialize() first.');
70
+ }
71
+ this.producer = this.kafka.producer(producerOptions);
72
+ await this.producer.connect();
73
+ return this.producer;
74
+ }
75
+
76
+ /**
77
+ * Create and connect a consumer
78
+ * @param {Object} consumerOptions - Consumer configuration options (must include groupId)
79
+ * @returns {Promise<Object>} - Connected consumer instance
80
+ */
81
+ async createConsumer(consumerOptions = {}) {
82
+ if (!this.isInitialized) {
83
+ throw new Error('Kafka client not initialized. Call initialize() first.');
84
+ }
85
+
86
+ if (!consumerOptions.groupId) {
87
+ consumerOptions.groupId = `cuppet-test-${Math.random().toString(16).slice(2, 8)}`;
88
+ }
89
+
90
+ this.consumer = this.kafka.consumer(consumerOptions);
91
+ await this.consumer.connect();
92
+ return this.consumer;
93
+ }
94
+
95
+ /**
96
+ * Check if Kafka client is initialized
97
+ * @returns {boolean}
98
+ */
99
+ isReady() {
100
+ return this.isInitialized;
101
+ }
102
+
103
+ /** Send a message to a topic (currently only supports one message at a time)
104
+ * @param {string} topic - Topic to send message to
105
+ * @param {Object} message - Message to send. An object with key and value properties.
106
+ * @returns {Promise<void>}
107
+ */
108
+ async sendMessage(topic, message = {}) {
109
+ if (!this.producer) {
110
+ await this.createProducer();
111
+ }
112
+ await this.producer.send({
113
+ topic,
114
+ messages: [message],
115
+ });
116
+ }
117
+
118
+ /** Subscribe to a topic
119
+ * @param {Array} topics - Array of topics to subscribe to
120
+ * @returns {Promise<Object>} - Object with topic, partition, and message properties
121
+ */
122
+ async subscribeToTopics(topics = []) {
123
+ if (!this.consumer) {
124
+ await this.createConsumer();
125
+ }
126
+ await this.consumer.subscribe({
127
+ topics: topics,
128
+ });
129
+ }
130
+
131
+ /** Consume a message from a topic
132
+ * @returns {Promise<Object>} - Object with topic, partition, and message properties
133
+ */
134
+ async consumeMessage() {
135
+ return new Promise((resolve) => {
136
+ this.consumer.run({
137
+ eachMessage: async ({ topic, partition, message }) => {
138
+ resolve({ topic, partition, message });
139
+ },
140
+ });
141
+ });
142
+ }
143
+
144
+ /** Disconnect from all topics
145
+ * @returns {Promise<void>}
146
+ */
147
+ async disconnect() {
148
+ await this.consumer.stop();
149
+ await this.consumer.disconnect();
150
+ }
151
+
152
+ /**
153
+ * Stop all Kafka connections and cleanup
154
+ * @returns {Promise<void>}
155
+ */
156
+ async stop() {
157
+ const disconnectPromises = [];
158
+ try {
159
+ if (this.producer) {
160
+ disconnectPromises.push(this.producer.disconnect());
161
+ }
162
+
163
+ if (this.consumer) {
164
+ disconnectPromises.push(this.consumer.disconnect());
165
+ }
166
+
167
+ await Promise.all(disconnectPromises);
168
+ console.log('Kafka client stopped successfully');
169
+ } catch (error) {
170
+ throw new Error(`Error during Kafka client cleanup: ${error.message}`);
171
+ } finally {
172
+ this.producer = null;
173
+ this.consumer = null;
174
+ this.kafka = null;
175
+ this.isInitialized = false;
176
+ }
177
+ }
178
+ }
179
+
180
+ module.exports = KafkaManager;
@@ -0,0 +1,71 @@
1
+ const { Given, When, Then } = require('@cucumber/cucumber');
2
+ const kafkaFunctions = require('../../../src/kafkaFunctions');
3
+
4
+ Given('I subscribe to Kafka topic/topics {string}', async function (topics) {
5
+ await kafkaFunctions.subscribeToTopics(this.kafkaManager, topics);
6
+ });
7
+
8
+ Given('I listen for a kafka message on the subscribed topics', async function () {
9
+ await kafkaFunctions.listenForMessage(this.kafkaManager);
10
+ });
11
+
12
+ Given('I unsubscribe from all Kafka topics', async function () {
13
+ await kafkaFunctions.unsubscribeFromAllTopics(this.kafkaManager);
14
+ });
15
+
16
+ Then('I should receive a kafka message with key {string} and value {string}', async function (key, value) {
17
+ await kafkaFunctions.validateSimpleMessage(value, key);
18
+ });
19
+
20
+ Then('I should receive a kafka message with value {string}', async function (value) {
21
+ await kafkaFunctions.validateSimpleMessage(value);
22
+ });
23
+
24
+ Then('I should receive a kafka message with property {string} and value {string}', async function (property, value) {
25
+ await kafkaFunctions.validateJsonMessageContains(property, value);
26
+ });
27
+
28
+ Then(
29
+ 'I should receive a kafka message with property {string} and value {string} and key {string}',
30
+ async function (property, value, key) {
31
+ await kafkaFunctions.validateJsonMessageContains(property, value, true, key);
32
+ }
33
+ );
34
+
35
+ Then(
36
+ 'I should receive a kafka message with property {string} and key {string} which value does not match {string}',
37
+ async function (property, key, value) {
38
+ await kafkaFunctions.validateJsonMessageContains(property, value, false, key);
39
+ }
40
+ );
41
+
42
+ Then(
43
+ 'I should receive a kafka message with property {string} which value does not match {string}',
44
+ async function (property, value) {
45
+ await kafkaFunctions.validateJsonMessageContains(property, value, false);
46
+ }
47
+ );
48
+
49
+ When('I send a kafka message to topic {string} with value {string}', async function (topic, message) {
50
+ await kafkaFunctions.sendMessage(this.kafkaManager, topic, message);
51
+ });
52
+
53
+ When(
54
+ 'I send a kafka message to topic {string} with value {string} and key {string}',
55
+ async function (topic, message, key) {
56
+ await kafkaFunctions.sendMessage(this.kafkaManager, topic, message, key);
57
+ }
58
+ );
59
+
60
+ When(
61
+ 'I send a kafka message to topic {string} with key {string} and JSON value',
62
+ async function (topic, key, docString) {
63
+ const message = JSON.stringify(docString);
64
+ await kafkaFunctions.sendMessage(this.kafkaManager, topic, message, key);
65
+ }
66
+ );
67
+
68
+ When('I send a kafka message to topic {string} with JSON value', async function (topic, docString) {
69
+ const message = JSON.stringify(docString);
70
+ await kafkaFunctions.sendMessage(this.kafkaManager, topic, message);
71
+ });
@@ -27,6 +27,10 @@ When('I unsubscribe from MQTT topic {string}', async function (topic) {
27
27
  await mqttFunctions.unsubscribeFromTopic(this.mqttManager, topic);
28
28
  });
29
29
 
30
+ When('I prepare MQTT message as JSON', async function (docString) {
31
+ await mqttFunctions.prepareMessage(docString, true);
32
+ });
33
+
30
34
  /**
31
35
  * Publish a message to an MQTT topic
32
36
  * @example When I publish "Hello World" to MQTT topic "test/message"
@@ -168,7 +172,7 @@ Then('I should have received {int} messages on MQTT topic {string}', async funct
168
172
  * These are not needed if using the @mqtt tag (connection is automatic)
169
173
  */
170
174
  Given('I connect to MQTT broker {string}', async function (brokerUrl) {
171
- const MqttManager = require('../mqttManager');
175
+ const { MqttManager } = require('../managers');
172
176
  const mqttManager = new MqttManager(brokerUrl);
173
177
  await mqttManager.initialize();
174
178
  this.mqttManager = mqttManager;
@@ -0,0 +1,16 @@
1
+ @kafka
2
+ Feature: Kafka Testing Examples
3
+ Test Kafka messaging functionality using Cucumber and kafka.js
4
+
5
+ Scenario: Simple message publish and receive
6
+ Given I subscribe to Kafka topic "testTopic"
7
+ Then I listen for a kafka message on the subscribed topics
8
+ Then I should receive a kafka message with property "message" and value "test"
9
+ And I unsubscribe from all Kafka topics
10
+ When I send a kafka message to topic "testTopic" with JSON value
11
+ """
12
+ {
13
+ "message": "test3"
14
+ }
15
+ """
16
+
package/index.js CHANGED
@@ -12,11 +12,10 @@ const appiumTesting = require('./src/appiumTesting');
12
12
  const accessibilityTesting = require('./src/accessibilityTesting');
13
13
  const lighthouse = require('./src/lighthouse');
14
14
  const visualRegression = require('./src/visualRegression');
15
+ const kafkaFunctions = require('./src/kafkaFunctions');
15
16
 
16
17
  // Export managers
17
- const BrowserManager = require('./features/app/browserManager');
18
- const AppiumManager = require('./features/app/appiumManager');
19
- const MqttManager = require('./features/app/mqttManager');
18
+ const { BrowserManager, AppiumManager, MqttManager, KafkaManager } = require('./features/app/managers');
20
19
 
21
20
  // Export step definitions
22
21
  const stepDefinitions = require('./stepDefinitions');
@@ -29,6 +28,7 @@ module.exports = {
29
28
  helperFunctions,
30
29
  apiFunctions,
31
30
  mqttFunctions,
31
+ kafkaFunctions,
32
32
  appiumTesting,
33
33
  accessibilityTesting,
34
34
  lighthouse,
@@ -38,6 +38,7 @@ module.exports = {
38
38
  BrowserManager,
39
39
  AppiumManager,
40
40
  MqttManager,
41
+ KafkaManager,
41
42
 
42
43
  // Step definitions
43
44
  stepDefinitions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuppet/core",
3
- "version": "1.2.5",
3
+ "version": "1.3.1",
4
4
  "description": "Core testing framework components for Cuppet - BDD framework based on Cucumber and Puppeteer",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -36,6 +36,7 @@
36
36
  "axios": "^1.11.0",
37
37
  "backstopjs": "^6.3.25",
38
38
  "chai": "6.0.1",
39
+ "kafkajs": "^2.2.4",
39
40
  "lighthouse": "^12.8.0",
40
41
  "mime": "^3.0.0",
41
42
  "mime-types": "^3.0.1",
@@ -65,10 +66,11 @@
65
66
  },
66
67
  "scripts": {
67
68
  "test": "cucumber-js features/tests/example.feature",
69
+ "run:kafka": "cucumber-js features/tests/example-kafka.feature",
68
70
  "run:visual": "cucumber-js features/tests/example-visual-test.feature",
69
71
  "run:pa11y": "cucumber-js features/tests/example-pa11y.feature",
70
72
  "run:lighthouse": "cucumber-js features/tests/example-lighthouse.feature",
71
- "run:mqtt": "cucumber-js features/tests/mqttExample.feature",
73
+ "run:mqtt": "cucumber-js features/tests/example-mqtt.feature",
72
74
  "run:mobile": "cucumber-js features/tests/example-mobile.feature",
73
75
  "run:accessibility": "cucumber-js features/tests/example-accessibility.feature",
74
76
  "run:performance": "cucumber-js features/tests/example-performance.feature",
@@ -0,0 +1,170 @@
1
+ const storage = require('./dataStorage');
2
+ const assert = require('chai').assert;
3
+ const helperFunctions = require('./helperFunctions');
4
+
5
+ module.exports = {
6
+ /** @type {Object} */
7
+ messageObject: null,
8
+
9
+ /**
10
+ * Subscribe to a topic
11
+ * @param {object} kafkaManager - Kafka manager instance
12
+ * @param {string} topics - String, comma separated list of topics to subscribe to
13
+ * @returns {Promise<void>}
14
+ */
15
+ subscribeToTopics: async function (kafkaManager, topics) {
16
+ const resolvedTopics = await this.prepareTopics(topics);
17
+ const topicsArray = resolvedTopics.split(',');
18
+ await kafkaManager.subscribeToTopics(topicsArray);
19
+ },
20
+
21
+ /**
22
+ * Listen for a message on the subscribed topics
23
+ * @param {object} kafkaManager - Kafka manager instance
24
+ * @returns {Promise<void>}
25
+ */
26
+ listenForMessage: async function (kafkaManager) {
27
+ this.messageObject = await kafkaManager.consumeMessage();
28
+ return this.messageObject;
29
+ },
30
+
31
+ /**
32
+ * Disconnect from all topics
33
+ * @param {object} kafkaManager - Kafka manager instance
34
+ * @returns {Promise<void>}
35
+ */
36
+ unsubscribeFromAllTopics: async function (kafkaManager) {
37
+ await kafkaManager.disconnect();
38
+ delete this.messageObject;
39
+ },
40
+
41
+ /**
42
+ * Prepare topic by replacing variables
43
+ * @param {string} topic - Topic with potential variables
44
+ * @returns {Promise<string>} - Resolved topic
45
+ */
46
+ prepareTopics: async function (topics) {
47
+ return await storage.checkForMultipleVariables(topics);
48
+ },
49
+
50
+ /**
51
+ * Prepare message by replacing variables
52
+ * @param {string} message - Message with potential variables
53
+ * @returns {Promise<Object>} - Resolved message as JSON object
54
+ */
55
+ prepareMessage: async function (message) {
56
+ const resolvedMessage = await storage.checkForMultipleVariables(message);
57
+ try {
58
+ const jsonObject = JSON.parse(resolvedMessage);
59
+ return jsonObject;
60
+ } catch {
61
+ // If the message is not a valid JSON object, return it as a string
62
+ return resolvedMessage;
63
+ }
64
+ },
65
+
66
+ /**
67
+ * Validate that the message key equals the expected value
68
+ * @param {string} key - Key to validate
69
+ * @returns {Promise<void>}
70
+ */
71
+ validateMessageKeyEquals: async function (key) {
72
+ const resolvedKey = await storage.checkForSavedVariable(key);
73
+ const messageKey = this.messageObject?.key?.toString();
74
+ if (!messageKey) {
75
+ throw new Error(`Message key is not present in the message object`);
76
+ }
77
+ assert.strictEqual(
78
+ messageKey,
79
+ resolvedKey,
80
+ `Message key does not match. Expected: ${resolvedKey}, Actual: ${messageKey}`
81
+ );
82
+ },
83
+
84
+ /**
85
+ * Validate that the message object has a value property containing the actual message
86
+ * @returns {Promise<string>} - Message value
87
+ */
88
+ validateMessageHasValue: function () {
89
+ const messageValue = this.messageObject?.message?.value?.toString();
90
+ if (!messageValue) {
91
+ throw new Error(`Message value is not present in the message object`);
92
+ }
93
+ return messageValue;
94
+ },
95
+
96
+ /**
97
+ * Read the message from the message object
98
+ * @param {string} key - Key to validate
99
+ * @param {string} value - Value to validate
100
+ * @returns {Promise<void>}
101
+ */
102
+ validateSimpleMessage: async function (value, key = null) {
103
+ if (key) {
104
+ await this.validateMessageKeyEquals(key);
105
+ }
106
+ const resolvedValue = await storage.checkForMultipleVariables(value);
107
+ const messageValue = this.validateMessageHasValue();
108
+ assert.strictEqual(
109
+ messageValue,
110
+ resolvedValue,
111
+ `Message does not match. Expected: ${resolvedValue}, Actual: ${messageValue}`
112
+ );
113
+ },
114
+
115
+ /**
116
+ * Validate that a JSON message contains a property with a specific value
117
+ * @param {string} property - Property path to validate (e.g., "eventType" or "payload.metadata.userId")
118
+ * @param {string} value - Expected value (or null to just check property exists)
119
+ * @param {boolean} contains - Whether the property should match (true) or NOT match (false) the value
120
+ * @param {string} key - Key to validate (optional)
121
+ * @returns {Promise<void>}
122
+ */
123
+ validateJsonMessageContains: async function (property, value, contains = true, key = null) {
124
+ if (key) {
125
+ await this.validateMessageKeyEquals(key);
126
+ }
127
+
128
+ const messageValue = this.validateMessageHasValue();
129
+ // Parse the message value to a JSON object
130
+ let jsonData;
131
+ try {
132
+ jsonData = JSON.parse(messageValue);
133
+ } catch (error) {
134
+ throw new Error(`The message value is not a valid JSON object: ${error.message}`);
135
+ }
136
+ // Get the property value using helperFunctions (handles nested properties)
137
+ const actualValue = helperFunctions.getPropertyValue(jsonData, property);
138
+ const resolvedValue = await storage.checkForMultipleVariables(value);
139
+
140
+ if (contains) {
141
+ assert.strictEqual(
142
+ actualValue?.toString(),
143
+ resolvedValue,
144
+ `Value of property "${property}" does not match. Expected: ${resolvedValue}, Actual: ${actualValue}`
145
+ );
146
+ } else {
147
+ assert.notStrictEqual(
148
+ actualValue?.toString(),
149
+ resolvedValue,
150
+ `Value of property "${property}" should NOT match but it does. Value: ${resolvedValue}`
151
+ );
152
+ }
153
+ },
154
+
155
+ /**
156
+ * Send a message to a topic
157
+ * @param {object} kafkaManager - Kafka manager instance
158
+ * @param {string} topic - Topic to send message to
159
+ * @param {object|string} message - Message to send. Can be a JSON object or a string.
160
+ * @returns {Promise<void>}
161
+ */
162
+ sendMessage: async function (kafkaManager, topic, message, key = null) {
163
+ const resolvedMessage = await this.prepareMessage(message);
164
+ const messageObject = {
165
+ ...(key && { key: await storage.checkForSavedVariable(key) }),
166
+ value: typeof resolvedMessage === 'object' ? JSON.stringify(resolvedMessage) : resolvedMessage,
167
+ };
168
+ await kafkaManager.sendMessage(topic, messageObject);
169
+ },
170
+ };
@@ -7,6 +7,9 @@ const helper = require('./helperFunctions');
7
7
  * Provides core MQTT testing operations following the same pattern as Puppeteer and Appium functions
8
8
  */
9
9
  module.exports = {
10
+ /** @type {Object} */
11
+ messageObject: null,
12
+
10
13
  /**
11
14
  * Prepare topic by replacing variables
12
15
  * @param {string} topic - Topic with potential variables
@@ -21,8 +24,17 @@ module.exports = {
21
24
  * @param {string} message - Message with potential variables
22
25
  * @returns {Promise<string>} - Resolved message
23
26
  */
24
- prepareMessage: async function (message) {
25
- return await storage.checkForMultipleVariables(message);
27
+ prepareMessage: async function (message, json = false) {
28
+ const resolvedMessage = await storage.checkForMultipleVariables(message);
29
+ if (json) {
30
+ try {
31
+ this.messageObject = JSON.parse(resolvedMessage);
32
+ return this.messageObject;
33
+ } catch (error) {
34
+ throw new Error(`Invalid JSON message: ${error.message}`);
35
+ }
36
+ }
37
+ return resolvedMessage;
26
38
  },
27
39
 
28
40
  /**
@@ -59,8 +71,9 @@ module.exports = {
59
71
  */
60
72
  publishMessage: async function (mqttManager, message, topic, qos = 0, retain = false) {
61
73
  const resolvedTopic = await this.prepareTopic(topic);
62
- const resolvedMessage = await this.prepareMessage(message);
74
+ const resolvedMessage = this.messageObject || (await this.prepareMessage(message));
63
75
  await mqttManager.publish(resolvedTopic, resolvedMessage, { qos, retain });
76
+ delete this.messageObject;
64
77
  },
65
78
 
66
79
  /**
@@ -74,7 +87,7 @@ module.exports = {
74
87
  */
75
88
  publishJsonMessage: async function (mqttManager, jsonString, topic, qos = 0, retain = false) {
76
89
  const resolvedTopic = await this.prepareTopic(topic);
77
- const resolvedJson = await this.prepareMessage(jsonString);
90
+ const resolvedJson = this.messageObject || (await this.prepareMessage(jsonString));
78
91
 
79
92
  // Validate JSON
80
93
  try {
@@ -84,6 +97,7 @@ module.exports = {
84
97
  }
85
98
 
86
99
  await mqttManager.publish(resolvedTopic, resolvedJson, { qos, retain });
100
+ delete this.messageObject;
87
101
  },
88
102
 
89
103
  /**