node-red-contrib-remote 1.6.1 → 2.0.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
@@ -36,6 +36,22 @@ 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.1
40
+ - Possible bug on network connection fixed
41
+
42
+ Version 2.0.0
43
+ - Enormously faster access to your Node-RED via the Internet
44
+ - macOS app for Apple Silicon (M-Chips)#
45
+ - Status display whether the instances are connected
46
+ - Changes to the configuration are applied without reconnecting the app
47
+ - Connection loss after switching from Wifi to mobile fixed
48
+ - Notifications with full UTF-8 support
49
+ - New instances automatically recognize http/https and the use of Dashboard 2
50
+ - Android: Local network is used again if possible
51
+ - Android: Delete an instance by long press and pull to refresh
52
+ - Reconnecting the instance to my server improved
53
+ - Multiple use of a config node in several access nodes is now recognized
54
+
39
55
  Version 1.6.1
40
56
  - Adding instances on Android is be possible again.
41
57
 
@@ -15,8 +15,8 @@
15
15
  "region_sg": "Asien (Singapur)",
16
16
  "region_dev": "Entwicklung",
17
17
  "region_desc": "Der Standort kann nicht mehr geändert werden, sobald eine App registriert wurde.",
18
- "change_info": "Wenn Du Einstellungen änderst, musst Du die App neu verbinden.",
19
- "backup_info": "Diese Konfig-Node wurde aus einem Backup wiederhergestellt. Daher sind sicherheitsrelevante Informationen nicht enthalten. Bitte löschen diese Konfig-Node und die Verbindungen in Deinen Remote-RED Apps. Lege eine neue Konfig-Node an, verbinde diese neu mit den Apps und wähle diese neue Konfig-Node in allen davon abhängigen Nodes aus."
18
+ "change_info": "Damit Änderungen aktiv werden führe einen Redeploy durch. Starte danach die App neu oder aktualisiere die Liste der Instanzen, indem Du die Liste nach unten ziehst.",
19
+ "backup_info": "Diese Konfig-Node wurde aus einem Backup wiederhergestellt. Daher sind sicherheitsrelevante Informationen nicht enthalten. Bitte löschen diese Konfig-Node (über das Zahnrad Icon rechts oben) und die Verbindungen in Deinen Remote-RED Apps. Lege eine neue Konfig-Node an, verbinde diese neu mit den Apps und wähle diese neue Konfig-Node in allen davon abhängigen Nodes aus."
20
20
  },
21
21
  "actions" : {
22
22
  "register_app": "Remote-RED App verbinden",
@@ -15,8 +15,8 @@
15
15
  "region_sg": "Asia (Singapur)",
16
16
  "region_dev": "Development host",
17
17
  "region_desc": "The region can´t be changed after registering an app.",
18
- "change_info": "If you change settings, you need to reconnect the app.",
19
- "backup_info": "This config node was restored from a backup. Therefore security relevant information are not included. Please delete this config-node and the connections in your Remote-RED apps. Create a new config-node, connect it to the apps and select this new config node in all dependent nodes."
18
+ "change_info": "To apply changes, first carry out a redeploy. Then restart the app or refresh the list of instances by dragging the list down.",
19
+ "backup_info": "This config node was restored from a backup. Therefore security relevant information are not included. Please delete this config-node (via the gear icon at the top right) and the connections in your Remote-RED apps. Create a new config-node, connect it to the apps and select this new config node in all dependent nodes."
20
20
  },
21
21
  "actions" : {
22
22
  "register_app": "Connect Remote-RED App",
@@ -2,14 +2,24 @@ module.exports = function(RED) {
2
2
  const commons = require('./remote-commons');
3
3
  const child_process = require('child_process');
4
4
  const os = require("os");
5
- let sshprocess = undefined
6
- let statustext = ''
7
- let instanceIsBanned = false
5
+ const internalIp = require('internal-ip');
6
+ const instancehashToAccessNode = {};
8
7
 
9
8
  function startSSH(node, server, port) {
10
9
  try {
11
10
  node.log("starting ssh process");
12
11
 
12
+ // Is there a old node.sshprocess object?
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;
21
+ }
22
+
13
23
  // Reset heartbeat status
14
24
  node.initialHeartbeatStatus = ''
15
25
  node.lastHeartbeatStatus = ''
@@ -24,48 +34,43 @@ module.exports = function(RED) {
24
34
  if ( node.verbose ) {
25
35
  sshparameters.push('-v');
26
36
  }
27
- sshprocess = child_process.spawn("ssh", sshparameters);
37
+ node.sshprocess = child_process.spawn("ssh", sshparameters);
38
+ node.log(`ssh process with pid ${node.sshprocess.pid} started`);
28
39
 
29
40
  // Set serving.. if not working, the process will exit or close
30
41
  setStatus(node, {fill:"green",shape:"dot",text:"remote-access.status.serving"});
31
42
  node.serving = true
32
43
 
33
44
  // Attach to process events
34
- sshprocess.stdout.on('data', (data) => {
45
+ node.sshprocess.stdout.on('data', (data) => {
35
46
  logSSHBuffer(node, data, 'ssh process stdout');
36
47
  });
37
48
 
38
- sshprocess.stderr.on('data', (data) => {
49
+ node.sshprocess.stderr.on('data', (data) => {
39
50
  logSSHBuffer(node, data, 'ssh process stderr');
40
51
  if ( data.toString().includes('Connection timed out')) {
41
52
  node.error(`SSH connection timeout. Please check if port 22 is open for outgoing connections.`);
42
53
  }
43
54
  });
44
55
 
45
- sshprocess.on('close', (code, signal) => {
46
- if ( statustext !== "remote-access.status.heartbeaterror" ) {
56
+ node.sshprocess.on('close', (code, signal) => {
57
+ if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
47
58
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopped"});
48
59
  }
49
60
  node.serving = false
50
- node.log("ssh process stopped (close: " + code + " / " + signal + ")");
61
+ node.log(`ssh process close (pid: ${node.sshprocess.pid} code: ${code} signal: ${signal})`);
51
62
  });
52
63
 
53
- sshprocess.on('exit', (code, signal) => {
54
- if ( statustext !== "remote-access.status.heartbeaterror" ) {
64
+ node.sshprocess.on('exit', (code, signal) => {
65
+ if ( node.statustext !== "remote-access.status.heartbeaterror" ) {
55
66
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopped"});
56
67
  }
57
68
  node.serving = false
58
- node.log("ssh process stopped (exit: " + code + " / " + signal + ")");
59
- });
60
-
61
- sshprocess.on('error', (err) => {
62
- node.log("ssh process error:" + err.name + ": " + err.message);
69
+ node.log(`ssh process exit (pid: ${node.sshprocess.pid} code: ${code} signal: ${signal})`);
63
70
  });
64
71
 
65
- node.on('close', function() {
66
- setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopping"});
67
- node.log("stopping ssh process");
68
- sshprocess.kill();
72
+ node.sshprocess.on('error', (err) => {
73
+ node.log(`ssh process error (pid: ${node.sshprocess.pid}): ${err.name} : ${err.message}`);
69
74
  });
70
75
  } catch (e) {
71
76
  // TODO: Error: socket hang up
@@ -87,57 +92,78 @@ module.exports = function(RED) {
87
92
  function setStatus(node, options) {
88
93
  // Set the status, remember text
89
94
  if ( options !== undefined && options.text !== undefined ) {
90
- statustext = options.text
95
+ node.statustext = options.text
91
96
  }
92
97
  node.status(options);
93
98
  }
94
99
 
95
100
  function requestInstanceSlot(node) {
96
- // Is this instance banned?
97
- if ( instanceIsBanned ) {
98
- return;
99
- }
101
+ // Asnyc because https call can take some seconds..
102
+ return new Promise((resolve, reject) => {
103
+ // Is this instance banned?
104
+ if ( node.instanceIsBanned ) {
105
+ reject();
106
+ }
100
107
 
101
- // Call API to retrive server and port.
102
- const axiosInstance = commons.createAxiosInstance();
103
- axiosInstance.post(`https://api-${node.confignode.server}/instanceSlotRequest`, {
104
- 'instancehash': node.confignode.instancehash,
105
- 'instanceauth': node.confignode.instanceauth,
106
- 'protocol': node.confignode.protocol,
107
- 'mountpath': RED.httpNode.mountpath,
108
- 'version': commons.getNodeVersion()
109
- })
110
- .then(response => {
111
- // Start SSH
112
- const port = response.data.port;
113
- node.log(`Using ${node.confignode.server} on port ${port}`);
114
- startSSH(node, node.confignode.server, port);
115
- })
116
- .catch((error) => {
117
- // Log error
118
- node.error('requestInstanceSlot: ' + commons.getNetworkErrorString(error))
119
- if ( commons.getNetworkErrorCustomString(error) !== undefined) {
120
- node.error(commons.getNetworkErrorCustomString(error));
108
+ // Create object with config values to send to server
109
+ var localip = node.confignode.host;
110
+ if (localip.toLowerCase() == 'localhost') localip = internalIp.v4.sync();
111
+ if (localip === undefined) localip = "";
112
+ const config = {
113
+ 'name': node.confignode.name,
114
+ 'localip': localip,
115
+ 'localport': node.confignode.port,
116
+ 'localprotocol': node.confignode.protocol,
117
+ 'baseurl': node.confignode.baseurl,
118
+ 'timestamp': Date.now(),
121
119
  }
120
+ const configString = JSON.stringify(config);
121
+ const configStringBuffer = Buffer.from(configString);
122
+ const configStringBase64 = configStringBuffer.toString('base64');
123
+ node.log(`Sending config to server: ${configString}`);
124
+
125
+ // Call API to retrive server and port.
126
+ const axiosInstance = commons.createAxiosInstance();
127
+ axiosInstance.post(`https://api-${node.confignode.server}/instanceSlotRequest`, {
128
+ 'instancehash': node.confignode.instancehash,
129
+ 'instanceauth': node.confignode.instanceauth,
130
+ 'protocol': node.confignode.protocol,
131
+ 'mountpath': RED.httpNode.mountpath,
132
+ 'config': configStringBase64,
133
+ 'version': commons.getNodeVersion()
134
+ })
135
+ .then(response => {
136
+ // Start SSH
137
+ const port = response.data.port;
138
+ node.log(`Using ${node.confignode.server} on port ${port}`);
139
+ startSSH(node, node.confignode.server, port);
140
+ resolve();
141
+ })
142
+ .catch((error) => {
143
+ // Log error
144
+ node.error('requestInstanceSlot: ' + commons.getNetworkErrorString(error))
145
+ if ( commons.getNetworkErrorCustomString(error) !== undefined) {
146
+ node.error(commons.getNetworkErrorCustomString(error));
147
+ }
122
148
 
123
- // Wenn gebannt -> Merken
124
- if ( error.response && error.response.status ) {
125
- if ( error.response.status === 403 ) {
126
- setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.banned"});
127
- instanceIsBanned = true;
128
- return;
149
+ // Wenn gebannt -> Merken
150
+ if ( error.response && error.response.status ) {
151
+ if ( error.response.status === 403 ) {
152
+ setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.banned"});
153
+ node.instanceIsBanned = true;
154
+ }
129
155
  }
130
- }
131
156
 
132
- // Set status
133
- setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.commerror"});
134
- return;
157
+ // Set status
158
+ if (!node.instanceIsBanned) setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.commerror"});
159
+ reject();
160
+ });
135
161
  });
136
162
  }
137
163
 
138
- function tryConnect(node) {
164
+ async function tryConnect(node) {
139
165
  // Request new instance slot and connect ssh
140
- requestInstanceSlot(node)
166
+ await requestInstanceSlot(node)
141
167
 
142
168
  // Create timer to check if ssh process still serving in 10 seconds
143
169
  node.checkservingtimeout = setTimeout(checkServing, 1000*10, node);
@@ -193,8 +219,7 @@ module.exports = function(RED) {
193
219
  if ( node.initialHeartbeatStatus === 'OK' ) {
194
220
  // If the status before was ok > Restart communication
195
221
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.heartbeaterror"});
196
- sshprocess.kill();
197
- node.serving = false
222
+ killSSHProcess(node)
198
223
  node.log(`Heartbeat error. Reconnecting soon.`);
199
224
  } else {
200
225
  // If the status before had also an error > Just change the label.
@@ -213,12 +238,28 @@ module.exports = function(RED) {
213
238
  }
214
239
  }
215
240
 
241
+ function killSSHProcess(node) {
242
+ // Kill process
243
+ try {
244
+ if (node.sshprocess !== undefined) {
245
+ node.log(`Killing process ${node.sshprocess.pid}`);
246
+ node.sshprocess.kill();
247
+ }
248
+ node.serving = false
249
+ } catch (error) {
250
+ node.error(`Error in killSSHProcess: ${error}`);
251
+ }
252
+ }
253
+
216
254
  function RemoteAccessNode(config) {
217
255
  RED.nodes.createNode(this,config);
218
256
  const node = this;
257
+ node.sshprocess = undefined;
258
+ node.statustext = '';
219
259
  node.serving = false;
220
260
  node.verbose = config.verbose;
221
261
  node.errorcounter = 0;
262
+ node.instanceIsBanned = false;
222
263
 
223
264
  // Status
224
265
  setStatus(node, {fill:"orange",shape:"dot",text:"remote-access.status.starting"});
@@ -238,18 +279,29 @@ module.exports = function(RED) {
238
279
  return;
239
280
  }
240
281
  if ( node.confignode.instancehash !== undefined && node.confignode.instanceauth === undefined ) {
241
- node.log("Configuration has no instanceauth. Maybe restored backup!");
282
+ node.error("Configuration has no instanceauth. Maybe restored backup!");
242
283
  setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.backupconfig"});
243
284
  return;
244
285
  }
245
286
 
287
+ // Check if there are more access nodes for this config node
288
+ const accessNodeObject = instancehashToAccessNode[node.confignode.instancehash];
289
+ if (accessNodeObject !== undefined && accessNodeObject['id'] !== node.id) {
290
+ const errorText = `The config node '${node.confignode.name}' is already used in the access node '${accessNodeObject['name']}'. Use only one access node for a config node.`;
291
+ node.error(errorText);
292
+ setStatus(node, {fill:"red",shape:"dot",text:errorText});
293
+ return;
294
+ } else {
295
+ instancehashToAccessNode[node.confignode.instancehash] = { id: node.id, name: node.name }
296
+ }
297
+
246
298
  // Init heartbeat
247
299
  node.initialHeartbeatStatus = ''
248
300
  node.lastHeartbeatStatus = ''
249
301
  node.heartbeatinterval = setInterval(heartbeat, 5*60*1000, node);
250
302
 
251
303
  // Call API for announce the instacehash and authentication, retrive server and port.
252
- tryConnect(node);
304
+ tryConnect(node)
253
305
 
254
306
  // Post URL for action
255
307
  const postUrlAction = `/contrib-remote/action/${node.confignode.instancehash}`;
@@ -305,31 +357,48 @@ module.exports = function(RED) {
305
357
  });
306
358
 
307
359
  // Clean up on close
308
- node.on('close', function() {
360
+ node.on('close', function(removed, done) {
361
+ // Set status
362
+ setStatus(node, {fill:"red",shape:"dot",text:"remote-access.status.stopping"});
363
+
364
+ // Kill process
365
+ killSSHProcess(node)
366
+
309
367
  // Cancel timeouts
310
- clearTimeout(node.checkservingtimeout);
311
- clearTimeout(node.tryconnecttimeout);
312
- clearInterval(node.heartbeatinterval);
368
+ try {
369
+ clearTimeout(node.checkservingtimeout);
370
+ clearTimeout(node.tryconnecttimeout);
371
+ clearInterval(node.heartbeatinterval);
372
+ } catch (error) {
373
+ node.error(`Error in close > clearTimeout: ${error}`);
374
+ }
313
375
 
314
376
  // Remove old routes, without a new deploy would break it..
315
- RED.httpNode._router.stack.forEach(function(route,i,routes) {
316
- if (route.route && route.route.path === postUrlAction) {
317
- node.log(` -> Remove Route '${route.route.path}'`);
318
- routes.splice(i,1);
319
- }
320
- });
321
- RED.httpNode._router.stack.forEach(function(route,i,routes) {
322
- if (route.route && route.route.path === postUrlGeofence) {
323
- node.log(` -> Remove Route '${route.route.path}'`);
324
- routes.splice(i,1);
325
- }
326
- });
327
- RED.httpNode._router.stack.forEach(function(route,i,routes) {
328
- if (route.route && route.route.path === getUrl) {
329
- node.log(` -> Remove Route '${route.route.path}'`);
330
- routes.splice(i,1);
331
- }
332
- });
377
+ try {
378
+ RED.httpNode._router.stack.forEach(function(route,i,routes) {
379
+ if (route.route && route.route.path === postUrlAction) {
380
+ node.log(` -> Remove Route '${route.route.path}'`);
381
+ routes.splice(i,1);
382
+ }
383
+ });
384
+ RED.httpNode._router.stack.forEach(function(route,i,routes) {
385
+ if (route.route && route.route.path === postUrlGeofence) {
386
+ node.log(` -> Remove Route '${route.route.path}'`);
387
+ routes.splice(i,1);
388
+ }
389
+ });
390
+ RED.httpNode._router.stack.forEach(function(route,i,routes) {
391
+ if (route.route && route.route.path === getUrl) {
392
+ node.log(` -> Remove Route '${route.route.path}'`);
393
+ routes.splice(i,1);
394
+ }
395
+ });
396
+ } catch (error) {
397
+ node.error(`Error in close > remove routs: ${error}`);
398
+ }
399
+
400
+ // Signal close done
401
+ done();
333
402
  });
334
403
 
335
404
  }
@@ -1,5 +1,5 @@
1
- const https = require('https')
2
- const axios = require('axios')
1
+ const https = require('https');
2
+ const axios = require('axios');
3
3
  const fs = require('fs');
4
4
  const crypto = require('crypto');
5
5
  const path = require('path');
@@ -18,7 +18,26 @@ module.exports = {
18
18
  }
19
19
  }
20
20
  });
21
- return axios.create({ httpsAgent: httpsAgent, timeout: 5000 });
21
+ return axios.create({
22
+ httpsAgent: httpsAgent,
23
+ timeout: 30000,
24
+ headers: {
25
+ 'Content-Type': 'application/json'
26
+ }
27
+ });
28
+ },
29
+
30
+ testUrlAvailable: async function(url) {
31
+ const httpsAgent = new https.Agent({
32
+ rejectUnauthorized: false,
33
+ });
34
+ const axiosInstance = axios.create({ httpsAgent: httpsAgent, timeout: 2000 });
35
+ try {
36
+ let responseUi = await axiosInstance.get(url);
37
+ if (responseUi.status >= 200 && responseUi.status <= 399) return true;
38
+ } catch (err) {
39
+ }
40
+ return false;
22
41
  },
23
42
 
24
43
  getNodeVersion: function() {
@@ -5,7 +5,7 @@
5
5
  defaults: {
6
6
  name: {value:"Node-RED UI"},
7
7
  host: {value:"localhost"},
8
- protocol: {value:"http"},
8
+ protocol: {value:""},
9
9
  port: {value:0, validate:RED.validators.number()},
10
10
  baseurl: {value:""},
11
11
  instancehash: {value:""},
@@ -48,11 +48,21 @@
48
48
  if (document.getElementById('node-config-input-port').value === '') {
49
49
  document.getElementById('node-config-input-port').value = data.port
50
50
  }
51
+ if (document.getElementById('node-config-input-protocol').value === '') {
52
+ document.getElementById('node-config-input-protocol').value = data.protocol
53
+ }
54
+
51
55
  });
52
56
 
53
- // Show backup info?
57
+ // Show change info / backup info?
54
58
  const instancehash = document.getElementById('node-config-input-instancehash').value;
55
59
  const instanceauth = document.getElementById('node-config-input-instanceauth').value;
60
+ if (instancehash !== "" && instanceauth !== "") {
61
+ document.getElementById('changeInfo').style.visibility = 'visible';
62
+ document.getElementById('backupInfo').style.marginBottom = '20px';
63
+ document.getElementById('changeInfo').style.padding = '10px';
64
+ document.getElementById('changeInfo').style.removeProperty('max-height');
65
+ }
56
66
  if (instancehash !== "" && instanceauth === "") {
57
67
  document.getElementById('backupInfo').style.visibility = 'visible';
58
68
  document.getElementById('backupInfo').style.marginTop = '40px';
@@ -119,6 +129,7 @@
119
129
  const server = document.getElementById('node-config-input-server').value;
120
130
  const name = document.getElementById('node-config-input-name').value;
121
131
  const host = document.getElementById('node-config-input-host').value;
132
+ const protocol = document.getElementById('node-config-input-protocol').value;
122
133
  const localport = document.getElementById('node-config-input-port').value;
123
134
  const baseurl = document.getElementById('node-config-input-baseurl').value;
124
135
 
@@ -128,6 +139,7 @@
128
139
  'server': server,
129
140
  'name': name,
130
141
  'host': host,
142
+ 'protocol': protocol,
131
143
  'localport': localport,
132
144
  'baseurl': baseurl
133
145
  }
@@ -168,21 +180,30 @@
168
180
  }
169
181
  }
170
182
 
171
- function testNetworkButtonClicked() {
183
+ function registerButtonClicked() {
184
+ // Get maybe existing values
185
+ const instancehash = document.getElementById('node-config-input-instancehash').value;
186
+ const instanceauth = document.getElementById('node-config-input-instanceauth').value;
187
+
188
+ // Check for backup
189
+ if (instancehash !== "" && instanceauth === "") {
190
+ alert(document.getElementById('backupInfoLabel').innerHTML);
191
+ return;
192
+ }
193
+
172
194
  // Disable Button
173
- document.getElementById('testNetworkButton').disabled = true;
195
+ document.getElementById('registerRemoteAppButton').disabled = true;
174
196
 
175
- $.get(`contrib-remote/testNetwork`, function(data) {
176
- if ( data.error == undefined ) {
177
- alert(data);
178
- } else {
179
- // Show error
180
- alert('Error while registering app: ' + data.error);
181
- }
197
+ // Remove last QR-Code
198
+ document.getElementById('registerRemoteAppQRCode').src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==";
182
199
 
183
- // Enable button
184
- document.getElementById('testNetworkButton').disabled = false;
185
- });
200
+ // Request InstanceHash when empty
201
+ if ( instancehash === undefined || instancehash === '' ) {
202
+ registerInstance();
203
+ } else {
204
+ // Register app
205
+ registerApp();
206
+ }
186
207
  }
187
208
 
188
209
  function openSupportMailButtonClicked() {
@@ -195,8 +216,12 @@
195
216
  </script>
196
217
 
197
218
  <script type="text/html" data-template-name="remote-config">
198
- <div id="backupInfo" style="visibility: collapse; max-height: 0px; max-width: 460px; border-width: 1px; border-style: dashed; border-color: #666;">
199
- <label data-i18n="remote-config.label.backup_info" style="font-weight: bold;"></label>
219
+ <div id="changeInfo" style="visibility: collapse; max-height: 0px; max-width: 460px; border-width: 1px; border-style: dashed; border-color: #666;">
220
+ <label data-i18n="remote-config.label.change_info"></label>
221
+ </div>
222
+
223
+ <div id="backupInfo" style="visibility: collapse; max-height: 0px; max-width: 460px; border-width: 1px; border-style: dashed; border-color: red;">
224
+ <label id="backupInfoLabel" data-i18n="remote-config.label.backup_info" style="font-weight: bold; color: red;"></label>
200
225
  </div>
201
226
 
202
227
  <div class="form-row">
@@ -218,8 +243,8 @@
218
243
  <div class="form-row">
219
244
  <label for="node-config-input-protocol"><i class="fa fa-tag"></i> <span data-i18n="remote-config.label.protocol"></label>
220
245
  <select id="node-config-input-protocol">
221
- <option value="http">http</option>
222
- <option value="https">https</option>
246
+ <option value="http">http</option>
247
+ <option value="https">https</option>
223
248
  </select>
224
249
  </div>
225
250
  <div class="form-row">
@@ -242,8 +267,6 @@
242
267
  </div>
243
268
  <label><i class="fa fa-info-circle"></i> <span data-i18n="remote-config.label.region_desc"></label>
244
269
 
245
- <label><i class="fa fa-info-circle"></i> <span data-i18n="remote-config.label.change_info"></label>
246
-
247
270
  <br>
248
271
  <button id="registerRemoteAppButton" type="button" class="red-ui-button" onclick="registerButtonClicked()">
249
272
  <span id="registerRemoteAppButtonText" data-i18n="remote-config.actions.register_app">
@@ -94,14 +94,24 @@ module.exports = function(RED) {
94
94
  res.json(testResults);
95
95
  });
96
96
 
97
- RED.httpAdmin.get("/contrib-remote/connectionData", RED.auth.needsPermission('remote-config.read'), function(req,res) {
98
- // Return the internal IP and the mountpath
97
+ RED.httpAdmin.get("/contrib-remote/connectionData", RED.auth.needsPermission('remote-config.read'), async function(req,res) {
98
+ // Return the internal IP, the port, the protocol and the calculated baseurl
99
99
  const ipData = {
100
- 'ipv4': internalIp.v4.sync(),
101
- 'baseurl': RED.httpNode.mountpath + ((RED.settings.ui !== undefined && RED.settings.ui.path !== undefined) ? RED.settings.ui.path : 'ui'),
102
- 'port': (RED.settings.uiPort !== undefined) ? String(RED.settings.uiPort) : '1880'
100
+ ipv4: internalIp.v4.sync(),
101
+ port: (RED.settings.uiPort !== undefined) ? String(RED.settings.uiPort) : '1880',
102
+ protocol: (RED.settings.https !== undefined) ? 'https' : 'http',
103
103
  }
104
- console.log(`${JSON.stringify(ipData)}`);
104
+
105
+ // Check Dashbaord 2 url
106
+ const availableDashbaord = await commons.testUrlAvailable(`${ipData.protocol}://${ipData.ipv4}:${ipData.port}${RED.httpNode.mountpath}dashboard`);
107
+ if (availableDashbaord) {
108
+ ipData.baseurl = `${RED.httpNode.mountpath}dashboard`;
109
+ } else {
110
+ // Dashbaord 2 not founnd, use UI settings instead
111
+ ipData.baseurl = RED.httpNode.mountpath + ((RED.settings.ui !== undefined && RED.settings.ui.path !== undefined) ? RED.settings.ui.path : 'ui');
112
+ }
113
+
114
+ // console.log(`${JSON.stringify(ipData)}`);
105
115
  res.json(ipData);
106
116
  });
107
117
 
@@ -152,24 +162,22 @@ module.exports = function(RED) {
152
162
  })
153
163
  .then(response => {
154
164
  var localip = req.body.host;
155
- if (localip.toLowerCase() == 'localhost') {
156
- localip = internalIp.v4.sync();
157
- }
158
- if (localip === undefined) {
159
- localip = "";
160
- }
165
+ if (localip.toLowerCase() == 'localhost') localip = internalIp.v4.sync();
166
+ if (localip === undefined) localip = "";
161
167
 
162
168
  const qrCodeData = {
163
169
  'name': req.body.name,
164
170
  'server': req.body.server,
165
171
  'localip': localip,
166
172
  'localport': req.body.localport,
173
+ 'localprotocol': req.body.protocol,
167
174
  'baseurl': req.body.baseurl,
168
175
  'instancehash': response.data.instancehash,
169
176
  'apphash': response.data.apphash,
170
177
  'password': response.data.password,
171
178
  'customerhash': response.data.customerhash,
172
- 'nodeversion': 1.1
179
+ 'nodeversion': 2.0,
180
+ 'timestamp': Date.now()
173
181
  };
174
182
  const qrCodeString = JSON.stringify(qrCodeData);
175
183
  const qrCodeStringBuffer = Buffer.from(qrCodeString);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-remote",
3
- "version": "1.6.1",
3
+ "version": "2.0.1",
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",
@@ -13,7 +13,7 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "axios": "^0.21.0",
16
+ "axios": "^1.7.2",
17
17
  "internal-ip": "^6.1.0",
18
18
  "limiter": "^1.1.5",
19
19
  "path": "^0.12.7",