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 +6 -0
- package/nodes/remote-access.js +89 -34
- package/nodes/remote-commons.js +4 -1
- package/package.json +1 -1
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
|
|
package/nodes/remote-access.js
CHANGED
|
@@ -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}
|
|
15
|
-
node
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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: ${
|
|
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
|
-
//
|
|
222
|
-
if ( node.lastHeartbeatStatus === '
|
|
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
|
-
|
|
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
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
node.
|
|
254
|
-
|
|
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
|
-
|
|
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 {
|
package/nodes/remote-commons.js
CHANGED
|
@@ -88,7 +88,10 @@ module.exports = {
|
|
|
88
88
|
this.reportError(errorElem, node, caller);
|
|
89
89
|
});
|
|
90
90
|
} else {
|
|
91
|
-
node.error(`${caller}: ${error.code}: ${
|
|
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.
|
|
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",
|