node-red-contrib-remote 2.0.2 → 2.0.4

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
@@ -36,6 +36,12 @@ You will find more information on [www.remote-red-com](https://www.remote-red.co
36
36
 
37
37
  ## Version History
38
38
 
39
+ Version 2.0.4
40
+ - Fix for 'No endpoint detected' message
41
+
42
+ Version 2.0.3
43
+ - More stable handling of connection losses
44
+
39
45
  Version 2.0.2
40
46
  - Crash on Node-RED startup fixed, extended error reporting
41
47
 
@@ -5,19 +5,14 @@ module.exports = function(RED) {
5
5
  const internalIp = require('internal-ip');
6
6
  const instancehashToAccessNode = {};
7
7
 
8
- function startSSH(node, server, port) {
8
+ async function startSSH(node, server, port) {
9
9
  try {
10
10
  node.log("starting ssh process");
11
11
 
12
12
  // Is there a old node.sshprocess object?
13
13
  if (node.sshprocess !== undefined) {
14
- node.log(`ssh process with pid ${node.sshprocess.pid} already existing`);
15
- node.sshprocess.removeAllListeners('close');
16
- node.sshprocess.removeAllListeners('exit');
17
- node.sshprocess.removeAllListeners('error');
18
- node.sshprocess.stdout.removeAllListeners('data');
19
- node.sshprocess.stderr.removeAllListeners('data');
20
- node.sshprocess = undefined;
14
+ node.log(`checking old ssh process with pid ${node.sshprocess.pid}`);
15
+ await killSSHProcess(node);
21
16
  }
22
17
 
23
18
  // Reset heartbeat status
@@ -35,7 +30,8 @@ module.exports = function(RED) {
35
30
  sshparameters.push('-v');
36
31
  }
37
32
  node.sshprocess = child_process.spawn("ssh", sshparameters);
38
- node.log(`ssh process with pid ${node.sshprocess.pid} started`);
33
+ const currentPid = node.sshprocess.pid;
34
+ node.log(`ssh process with pid ${currentPid} started`);
39
35
 
40
36
  // Set serving.. if not working, the process will exit or close
41
37
  setStatus(node, {fill:"green",shape:"dot",text:"remote-access.status.serving"});
@@ -54,23 +50,29 @@ module.exports = function(RED) {
54
50
  });
55
51
 
56
52
  node.sshprocess.on('close', (code, signal) => {
57
- if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
58
- setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopped"});
53
+ node.log(`ssh process close (pid: ${currentPid} code: ${code} signal: ${signal})`);
54
+ // Only update status if this is still the current process
55
+ if (node.sshprocess && node.sshprocess.pid === currentPid) {
56
+ if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
57
+ setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopped"});
58
+ }
59
+ node.serving = false
59
60
  }
60
- node.serving = false
61
- node.log(`ssh process close (pid: ${node.sshprocess.pid} code: ${code} signal: ${signal})`);
62
61
  });
63
62
 
64
63
  node.sshprocess.on('exit', (code, signal) => {
65
- if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
66
- setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopped"});
64
+ node.log(`ssh process exit (pid: ${currentPid} code: ${code} signal: ${signal})`);
65
+ // Only update status if this is still the current process
66
+ if (node.sshprocess && node.sshprocess.pid === currentPid) {
67
+ if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
68
+ setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopped"});
69
+ }
70
+ node.serving = false
67
71
  }
68
- node.serving = false
69
- node.log(`ssh process exit (pid: ${node.sshprocess.pid} code: ${code} signal: ${signal})`);
70
72
  });
71
73
 
72
74
  node.sshprocess.on('error', (err) => {
73
- node.log(`ssh process error (pid: ${node.sshprocess.pid}): ${err.name} : ${err.message}`);
75
+ node.log(`ssh process error (pid: ${currentPid}): ${err.name} : ${err.message}`);
74
76
  });
75
77
  } catch (e) {
76
78
  // TODO: Error: socket hang up
@@ -103,6 +105,7 @@ module.exports = function(RED) {
103
105
  // Is this instance banned?
104
106
  if ( node.instanceIsBanned ) {
105
107
  reject(new Error('Instance banned'));
108
+ return;
106
109
  }
107
110
 
108
111
  // Create object with config values to send to server
@@ -120,7 +123,7 @@ module.exports = function(RED) {
120
123
  const configString = JSON.stringify(config);
121
124
  const configStringBuffer = Buffer.from(configString);
122
125
  const configStringBase64 = configStringBuffer.toString('base64');
123
- node.log(`Sending config to server: ${configString}`);
126
+ // node.log(`Sending config to server: ${configString}`);
124
127
 
125
128
  // Call API to retrive server and port.
126
129
  const axiosInstance = commons.createAxiosInstance();
@@ -153,6 +156,8 @@ module.exports = function(RED) {
153
156
  if ( error.response.status === 403 ) {
154
157
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.banned"});
155
158
  node.instanceIsBanned = true;
159
+ reject(new Error('Instance banned'));
160
+ return;
156
161
  }
157
162
  }
158
163
 
@@ -218,8 +223,11 @@ module.exports = function(RED) {
218
223
  }
219
224
  node.lastHeartbeatStatus = response.data.status
220
225
 
221
- // If the communication is not ok...
222
- if ( node.lastHeartbeatStatus === 'NOTFOUND' ) {
226
+ // Based on the status...
227
+ if ( node.lastHeartbeatStatus === 'OK' ) {
228
+ node.initialHeartbeatStatus = node.lastHeartbeatStatus; // Wenn es jetzt OK ist, zählt es auch wie initial OK
229
+ setStatus(node, {fill:"green",shape:"dot",text:"remote-access.status.serving"});
230
+ } else if ( node.lastHeartbeatStatus === 'NOTFOUND' ) {
223
231
  // The local endpoint responed a 404...
224
232
  setStatus(node, {fill:"yellow",shape:"dot",text:"remote-access.status.heartbeaterrornotfound"});
225
233
  node.log(`Heartbeat detected no valid endpoint, got a 404 response. Please check the base URL in the connection settings.`);
@@ -227,8 +235,9 @@ module.exports = function(RED) {
227
235
  if ( node.initialHeartbeatStatus === 'OK' ) {
228
236
  // If the status before was ok > Restart communication
229
237
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.heartbeaterror"});
230
- killSSHProcess(node)
231
238
  node.log(`Heartbeat error. Reconnecting soon.`);
239
+ // Set serving to false, checkServing will trigger reconnect and startSSH will kill old process
240
+ node.serving = false;
232
241
  } else {
233
242
  // If the status before had also an error > Just change the label.
234
243
  setStatus(node, {fill:"yellow",shape:"dot",text:"remote-access.status.heartbeaterroractive"});
@@ -238,7 +247,7 @@ module.exports = function(RED) {
238
247
  })
239
248
  .catch((error) => {
240
249
  // Error on api call > Log error
241
- node.error('heartbeat: ' + commons.getNetworkErrorString(error))
250
+ commons.reportError(error, node, 'heartbeat');
242
251
  if ( commons.getNetworkErrorCustomString(error) !== undefined) {
243
252
  node.error(commons.getNetworkErrorCustomString(error));
244
253
  }
@@ -247,16 +256,62 @@ module.exports = function(RED) {
247
256
  }
248
257
 
249
258
  function killSSHProcess(node) {
250
- // Kill process
251
- try {
252
- if (node.sshprocess !== undefined) {
253
- node.log(`Killing process ${node.sshprocess.pid}`);
254
- node.sshprocess.kill();
259
+ // Returns a promise that kills the current process. Resolves when process is dead or after 2 seconds.
260
+ return new Promise((resolve) => {
261
+ try {
262
+ if (node.sshprocess !== undefined) {
263
+ const pidToKill = node.sshprocess.pid;
264
+ const processToKill = node.sshprocess;
265
+
266
+ // Remove existing listeners
267
+ processToKill.removeAllListeners('close');
268
+ processToKill.removeAllListeners('exit');
269
+ processToKill.removeAllListeners('error');
270
+ processToKill.stdout.removeAllListeners('data');
271
+ processToKill.stderr.removeAllListeners('data');
272
+
273
+ // Check if process is already dead (exitCode is set when process has exited)
274
+ if (processToKill.exitCode !== null || processToKill.killed) {
275
+ node.log(`Process ${pidToKill} already dead (exitCode: ${processToKill.exitCode}, killed: ${processToKill.killed})`);
276
+ node.sshprocess = undefined;
277
+ node.serving = false;
278
+ resolve();
279
+ return;
280
+ }
281
+
282
+ node.log(`Killing process ${pidToKill}`);
283
+
284
+ // Safety timeout: resolve after 2 seconds even if exit event doesn't fire
285
+ const safetyTimeout = setTimeout(() => {
286
+ node.log(`Process ${pidToKill} kill timeout after 2 seconds`);
287
+ node.serving = false;
288
+ resolve();
289
+ }, 2000);
290
+
291
+ // Set up one-time exit handler to know when process is really dead
292
+ const exitHandler = () => {
293
+ clearTimeout(safetyTimeout);
294
+ node.log(`Process ${pidToKill} confirmed killed`);
295
+ node.serving = false;
296
+ resolve();
297
+ };
298
+
299
+ // Add one-time exit handler
300
+ processToKill.once('exit', exitHandler);
301
+
302
+ // Send kill signal
303
+ processToKill.kill();
304
+ node.sshprocess = undefined;
305
+ } else {
306
+ node.serving = false;
307
+ resolve();
308
+ }
309
+ } catch (error) {
310
+ node.error(`Error in killSSHProcess: ${error}`);
311
+ node.serving = false;
312
+ resolve();
255
313
  }
256
- node.serving = false
257
- } catch (error) {
258
- node.error(`Error in killSSHProcess: ${error}`);
259
- }
314
+ });
260
315
  }
261
316
 
262
317
  function RemoteAccessNode(config) {
@@ -369,8 +424,8 @@ module.exports = function(RED) {
369
424
  // Set status
370
425
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopping"});
371
426
 
372
- // Kill process
373
- killSSHProcess(node)
427
+ // Kill process (don't wait as Node-RED has a timeout for close handlers)
428
+ killSSHProcess(node).catch(err => node.error(`Error killing SSH process on close: ${err}`))
374
429
 
375
430
  // Cancel timeouts
376
431
  try {
@@ -88,7 +88,10 @@ module.exports = {
88
88
  this.reportError(errorElem, node, caller);
89
89
  });
90
90
  } else {
91
- node.error(`${caller}: ${error.code}: ${(error.stack !== undefined) ? error.stack : error.message}`);
91
+ node.error(`${caller}: ${error.code}: ${error.message}`);
92
+ if ( error.stack !== undefined ) {
93
+ node.debug(error.stack);
94
+ }
92
95
  }
93
96
  }
94
97
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-remote",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Remote-RED is an ecosystem to bring remote access, push notification and geofencing to Node-RED. There are also additional functions like directly answer on a notification and homescreen widgets.",
5
5
  "author": "Thorsten Heilmann",
6
6
  "license": "GPL-3.0",