@machhub-dev/node-red-nodes 1.0.8 → 1.0.9-test

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.
@@ -15,9 +15,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.MQTTService = void 0;
16
16
  const mqtt_1 = __importDefault(require("mqtt"));
17
17
  class MQTTService {
18
- constructor() {
19
- this.subscribedTopics = [];
20
- }
21
18
  static getInstance(settings, statusCallback) {
22
19
  return __awaiter(this, void 0, void 0, function* () {
23
20
  // If we already have an instance, return it
@@ -109,8 +106,17 @@ class MQTTService {
109
106
  clientId: settings.clientId,
110
107
  username: settings.clientId,
111
108
  password: settings.clientSecret,
109
+ reconnectPeriod: 0, // disable MQTT.js auto-reconnect; we manage reconnection via getInstance()
112
110
  });
113
111
  instance.attachMessageListener();
112
+ // Re-subscribe any topics registered before this connection was established
113
+ for (const sub of MQTTService.subscribedTopics) {
114
+ instance.client.subscribe(sub.topic, { qos: 2 }, (err) => {
115
+ if (err) {
116
+ console.error(`Failed to re-subscribe to topic ${sub.topic}:`, err);
117
+ }
118
+ });
119
+ }
114
120
  // console.log("MQTT connection established successfully");
115
121
  if (statusCallback) {
116
122
  statusCallback({ fill: "green", shape: "dot", text: "MQTT Connected" });
@@ -128,19 +134,22 @@ class MQTTService {
128
134
  return instance;
129
135
  });
130
136
  }
131
- // Method to reset the instance
137
+ // Method to reset the instance and all state (full shutdown / redeploy)
132
138
  static resetInstance() {
133
- if (this.instance && this.instance.client) {
134
- this.instance.client.end();
135
- }
139
+ const inst = this.instance;
140
+ // Clear references first so the 'close' event handler guard doesn't re-null unnecessarily
136
141
  this.instance = null;
137
142
  this.instancePromise = null;
138
143
  this.stopConnecting = true;
144
+ this.subscribedTopics = [];
145
+ if (inst && inst.client) {
146
+ inst.client.end();
147
+ }
139
148
  }
140
149
  // Adds a topic and handler to the subscribed list
141
150
  addTopicHandler(topic, handler) {
142
151
  try {
143
- this.subscribedTopics.push({ topic, handler });
152
+ MQTTService.subscribedTopics.push({ topic, handler });
144
153
  // console.log("New Subscription Handler : ", topic)
145
154
  this.client.subscribe(topic, { qos: 2 }, (err) => {
146
155
  if (err) {
@@ -152,6 +161,23 @@ class MQTTService {
152
161
  console.error(`Failed to subscribe to topic ${topic}:`, e);
153
162
  }
154
163
  }
164
+ // Removes a topic handler and unsubscribes from the broker if no other handler covers the same topic
165
+ removeTopicHandler(topic, handler) {
166
+ MQTTService.subscribedTopics = MQTTService.subscribedTopics.filter(st => !(st.topic === topic && st.handler === handler));
167
+ const stillSubscribed = MQTTService.subscribedTopics.some(st => st.topic === topic);
168
+ if (!stillSubscribed) {
169
+ try {
170
+ this.client.unsubscribe(topic, (err) => {
171
+ if (err) {
172
+ console.error(`Failed to unsubscribe from topic ${topic}:`, err);
173
+ }
174
+ });
175
+ }
176
+ catch (e) {
177
+ console.error(`Failed to unsubscribe from topic ${topic}:`, e);
178
+ }
179
+ }
180
+ }
155
181
  // Publishes a message to a specific topic
156
182
  publish(topic, message) {
157
183
  try {
@@ -176,7 +202,7 @@ class MQTTService {
176
202
  }
177
203
  attachMessageListener() {
178
204
  this.client.on('message', (topic, message, packet) => {
179
- for (const subscribedTopic of this.subscribedTopics) {
205
+ for (const subscribedTopic of MQTTService.subscribedTopics) {
180
206
  if (this.matchesTopic(subscribedTopic.topic, topic)) {
181
207
  const parsedMessage = this.parseMessage(message, topic);
182
208
  subscribedTopic.handler(parsedMessage, packet.retain);
@@ -184,6 +210,14 @@ class MQTTService {
184
210
  }
185
211
  }
186
212
  });
213
+ // When the broker closes the connection (e.g. session expired), self-reset so the
214
+ // next getInstance() call creates a fresh client with an empty in-flight store.
215
+ this.client.on('close', () => {
216
+ if (MQTTService.instance === this) {
217
+ MQTTService.instance = null;
218
+ MQTTService.instancePromise = null;
219
+ }
220
+ });
187
221
  }
188
222
  // Matches MQTT topic patterns with wildcards (+ for single level, # for multi-level)
189
223
  matchesTopic(pattern, topic) {
@@ -232,3 +266,4 @@ class MQTTService {
232
266
  exports.MQTTService = MQTTService;
233
267
  MQTTService.instancePromise = null;
234
268
  MQTTService.stopConnecting = false;
269
+ MQTTService.subscribedTopics = [];
@@ -56,7 +56,6 @@ function default_1(RED) {
56
56
  }
57
57
  }
58
58
  node.on('close', () => {
59
- mqtt_service_1.MQTTService.resetInstance();
60
59
  mqttService = null;
61
60
  initializationPromise = null;
62
61
  });
@@ -64,18 +63,13 @@ function default_1(RED) {
64
63
  return __awaiter(this, void 0, void 0, function* () {
65
64
  try {
66
65
  node.status({ fill: "orange", shape: "dot", text: "Sending Payload" });
67
- // Wait for initialization if still in progress
68
- if (isInitializing && initializationPromise) {
69
- yield initializationPromise;
70
- }
71
- // Check if mqttService is initialized
72
- if (!mqttService) {
73
- node.status({ fill: "red", shape: "dot", text: "MQTT service not initialized" });
74
- return;
75
- }
66
+ // Always fetch the current instance so we recover transparently after reconnects
67
+ const service = yield mqtt_service_1.MQTTService.getInstance(machhubConfig, (status) => {
68
+ node.status(status);
69
+ });
76
70
  Object.keys(msg.payload).forEach(function (key) {
77
71
  try {
78
- mqttService.publish(key, msg.payload[key]);
72
+ service.publish(key, msg.payload[key]);
79
73
  }
80
74
  catch (error) {
81
75
  node.status({ fill: "red", shape: "dot", text: "Failed to publish to '" + key + "': " + error.toString() });
@@ -61,14 +61,19 @@ function default_1(RED) {
61
61
  isRetained: false,
62
62
  payload: undefined
63
63
  };
64
+ let mqttService = null;
65
+ let topicHandler = null;
64
66
  node.on('close', function () {
65
- mqtt_service_1.MQTTService.resetInstance();
67
+ if (topicHandler && mqttService) {
68
+ mqttService.removeTopicHandler(node.selectedTag, topicHandler);
69
+ }
70
+ mqttService = null;
71
+ topicHandler = null;
66
72
  });
67
73
  (() => __awaiter(this, void 0, void 0, function* () {
68
74
  try {
69
75
  node.status({ fill: "yellow", shape: "dot", text: "Connecting..." });
70
76
  // Use MACHHUB config from machhub.env.json to get MQTT service with status callback
71
- let mqttService;
72
77
  while (true) {
73
78
  // console.log("Getting MQTT instance");
74
79
  mqttService = yield mqtt_service_1.MQTTService.getInstance(machhubConfig, (status) => {
@@ -79,12 +84,13 @@ function default_1(RED) {
79
84
  break;
80
85
  }
81
86
  }
82
- mqttService.addTopicHandler(node.selectedTag, (message, isRetained) => {
87
+ topicHandler = (message, isRetained) => {
83
88
  msg.payload = message;
84
89
  msg.isRetained = isRetained;
85
90
  node.send(msg);
86
91
  node.status({ fill: "green", shape: "dot", text: "Payload Received" });
87
- });
92
+ };
93
+ mqttService.addTopicHandler(node.selectedTag, topicHandler);
88
94
  node.status({ fill: "green", shape: "dot", text: "MQTT Listening" });
89
95
  }
90
96
  catch (error) {
@@ -58,23 +58,17 @@ function default_1(RED) {
58
58
  }
59
59
  }
60
60
  node.on('close', () => {
61
- mqtt_service_1.MQTTService.resetInstance();
62
61
  mqttService = null;
63
62
  initializationPromise = null;
64
63
  });
65
64
  node.on('input', (msg) => __awaiter(this, void 0, void 0, function* () {
66
65
  try {
67
66
  node.status({ fill: "orange", shape: "dot", text: "Sending Payload" });
68
- // Wait for initialization if still in progress
69
- if (isInitializing && initializationPromise) {
70
- yield initializationPromise;
71
- }
72
- // Check if mqttService is initialized
73
- if (!mqttService) {
74
- node.status({ fill: "red", shape: "dot", text: "MQTT service not initialized" });
75
- return;
76
- }
77
- yield mqttService.publish(node.selectedTag, msg.payload);
67
+ // Always fetch the current instance so we recover transparently after reconnects
68
+ const service = yield mqtt_service_1.MQTTService.getInstance(machhubConfig, (status) => {
69
+ node.status(status);
70
+ });
71
+ yield service.publish(node.selectedTag, msg.payload);
78
72
  node.status({ fill: "green", shape: "dot", text: "Payload Sent" });
79
73
  }
80
74
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@machhub-dev/node-red-nodes",
3
- "version": "1.0.8",
3
+ "version": "1.0.9-test",
4
4
  "description": "Node-RED API for MACHHUB EDGE",
5
5
  "scripts": {
6
6
  "build": "yarn clean && npx tsc && yarn copy-files",