node-red-contrib-remote 2.0.3 → 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,9 @@ 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
+
39
42
  Version 2.0.3
40
43
  - More stable handling of connection losses
41
44
 
@@ -5,14 +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
- killSSHProcess(node);
14
+ node.log(`checking old ssh process with pid ${node.sshprocess.pid}`);
15
+ await killSSHProcess(node);
16
16
  }
17
17
 
18
18
  // Reset heartbeat status
@@ -30,7 +30,8 @@ module.exports = function(RED) {
30
30
  sshparameters.push('-v');
31
31
  }
32
32
  node.sshprocess = child_process.spawn("ssh", sshparameters);
33
- 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`);
34
35
 
35
36
  // Set serving.. if not working, the process will exit or close
36
37
  setStatus(node, {fill:"green",shape:"dot",text:"remote-access.status.serving"});
@@ -49,23 +50,29 @@ module.exports = function(RED) {
49
50
  });
50
51
 
51
52
  node.sshprocess.on('close', (code, signal) => {
52
- if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
53
- 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
54
60
  }
55
- node.serving = false
56
- node.log(`ssh process close (pid: ${node.sshprocess.pid} code: ${code} signal: ${signal})`);
57
61
  });
58
62
 
59
63
  node.sshprocess.on('exit', (code, signal) => {
60
- if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
61
- 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
62
71
  }
63
- node.serving = false
64
- node.log(`ssh process exit (pid: ${node.sshprocess.pid} code: ${code} signal: ${signal})`);
65
72
  });
66
73
 
67
74
  node.sshprocess.on('error', (err) => {
68
- 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}`);
69
76
  });
70
77
  } catch (e) {
71
78
  // TODO: Error: socket hang up
@@ -229,7 +236,8 @@ module.exports = function(RED) {
229
236
  // If the status before was ok > Restart communication
230
237
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.heartbeaterror"});
231
238
  node.log(`Heartbeat error. Reconnecting soon.`);
232
- killSSHProcess(node)
239
+ // Set serving to false, checkServing will trigger reconnect and startSSH will kill old process
240
+ node.serving = false;
233
241
  } else {
234
242
  // If the status before had also an error > Just change the label.
235
243
  setStatus(node, {fill:"yellow",shape:"dot",text:"remote-access.status.heartbeaterroractive"});
@@ -248,22 +256,62 @@ module.exports = function(RED) {
248
256
  }
249
257
 
250
258
  function killSSHProcess(node) {
251
- // Kill process
252
- try {
253
- if (node.sshprocess !== undefined) {
254
- node.log(`Killing process ${node.sshprocess.pid}`);
255
- node.sshprocess.removeAllListeners('close');
256
- node.sshprocess.removeAllListeners('exit');
257
- node.sshprocess.removeAllListeners('error');
258
- node.sshprocess.stdout.removeAllListeners('data');
259
- node.sshprocess.stderr.removeAllListeners('data');
260
- node.sshprocess.kill();
261
- node.sshprocess = undefined;
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();
262
313
  }
263
- node.serving = false
264
- } catch (error) {
265
- node.error(`Error in killSSHProcess: ${error}`);
266
- }
314
+ });
267
315
  }
268
316
 
269
317
  function RemoteAccessNode(config) {
@@ -376,8 +424,8 @@ module.exports = function(RED) {
376
424
  // Set status
377
425
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopping"});
378
426
 
379
- // Kill process
380
- 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}`))
381
429
 
382
430
  // Cancel timeouts
383
431
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-remote",
3
- "version": "2.0.3",
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",