@node-red/nodes 3.1.0-beta.2 → 3.1.0-beta.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/core/common/25-catch.html +8 -1
- package/core/common/25-status.html +14 -1
- package/core/common/60-link.html +1 -1
- package/core/function/10-function.html +27 -1
- package/core/function/10-function.js +21 -3
- package/core/function/10-switch.html +1 -0
- package/core/function/89-delay.js +1 -0
- package/core/function/rbe.js +5 -1
- package/core/network/10-mqtt.html +31 -6
- package/core/network/10-mqtt.js +243 -69
- package/core/network/21-httprequest.html +1 -1
- package/core/network/21-httprequest.js +70 -33
- package/core/parsers/70-XML.js +1 -2
- package/core/sequence/17-split.js +3 -0
- package/core/storage/10-file.js +10 -3
- package/locales/de/messages.json +5 -1
- package/locales/en-US/common/20-inject.html +1 -1
- package/locales/en-US/messages.json +7 -1
- package/locales/fr/messages.json +3 -0
- package/locales/ja/common/20-inject.html +1 -1
- package/locales/ja/messages.json +7 -1
- package/locales/ru/messages.json +2 -1
- package/package.json +4 -4
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
|
|
5
5
|
<select id="node-input-scope-select">
|
|
6
6
|
<option value="all" data-i18n="catch.scope.all"></option>
|
|
7
|
+
<option value="group" data-i18n="catch.scope.group"></option>
|
|
7
8
|
<option value="target" data-i18n="catch.scope.selected"></option>
|
|
8
9
|
</select>
|
|
9
10
|
</div>
|
|
@@ -40,7 +41,9 @@
|
|
|
40
41
|
if (this.name) {
|
|
41
42
|
return this.name;
|
|
42
43
|
}
|
|
43
|
-
if (this.scope) {
|
|
44
|
+
if (this.scope === "group") {
|
|
45
|
+
return this._("catch.catchGroup");
|
|
46
|
+
} else if (Array.isArray(this.scope)) {
|
|
44
47
|
return this._("catch.catchNodes",{number:this.scope.length});
|
|
45
48
|
}
|
|
46
49
|
return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch")
|
|
@@ -170,6 +173,8 @@
|
|
|
170
173
|
});
|
|
171
174
|
if (this.scope === null) {
|
|
172
175
|
$("#node-input-scope-select").val("all");
|
|
176
|
+
} else if(this.scope === "group"){
|
|
177
|
+
$("#node-input-scope-select").val("group");
|
|
173
178
|
} else {
|
|
174
179
|
$("#node-input-scope-select").val("target");
|
|
175
180
|
}
|
|
@@ -179,6 +184,8 @@
|
|
|
179
184
|
var scope = $("#node-input-scope-select").val();
|
|
180
185
|
if (scope === 'all') {
|
|
181
186
|
this.scope = null;
|
|
187
|
+
} else if(scope === 'group') {
|
|
188
|
+
this.scope = "group";
|
|
182
189
|
} else {
|
|
183
190
|
$("#node-input-uncaught").prop("checked",false);
|
|
184
191
|
this.scope = $("#node-input-catch-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
|
|
5
5
|
<select id="node-input-scope-select">
|
|
6
6
|
<option value="all" data-i18n="status.scope.all"></option>
|
|
7
|
+
<option value="group" data-i18n="status.scope.group"></option>
|
|
7
8
|
<option value="target" data-i18n="status.scope.selected"></option>
|
|
8
9
|
</select>
|
|
9
10
|
</div>
|
|
@@ -32,7 +33,15 @@
|
|
|
32
33
|
outputs:1,
|
|
33
34
|
icon: "status.svg",
|
|
34
35
|
label: function() {
|
|
35
|
-
|
|
36
|
+
if (this.name) {
|
|
37
|
+
return this.name;
|
|
38
|
+
}
|
|
39
|
+
if (this.scope === "group") {
|
|
40
|
+
return this._("status.statusGroup");
|
|
41
|
+
} else if (Array.isArray(this.scope)) {
|
|
42
|
+
return this._("status.statusNodes",{number:this.scope.length});
|
|
43
|
+
}
|
|
44
|
+
return this._("status.status")
|
|
36
45
|
},
|
|
37
46
|
labelStyle: function() {
|
|
38
47
|
return this.name?"node_label_italic":"";
|
|
@@ -157,6 +166,8 @@
|
|
|
157
166
|
});
|
|
158
167
|
if (this.scope === null) {
|
|
159
168
|
$("#node-input-scope-select").val("all");
|
|
169
|
+
} else if(this.scope === "group"){
|
|
170
|
+
$("#node-input-scope-select").val("group");
|
|
160
171
|
} else {
|
|
161
172
|
$("#node-input-scope-select").val("target");
|
|
162
173
|
}
|
|
@@ -166,6 +177,8 @@
|
|
|
166
177
|
var scope = $("#node-input-scope-select").val();
|
|
167
178
|
if (scope === 'all') {
|
|
168
179
|
this.scope = null;
|
|
180
|
+
} else if(scope === 'group') {
|
|
181
|
+
this.scope = "group";
|
|
169
182
|
} else {
|
|
170
183
|
this.scope = $("#node-input-status-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
|
171
184
|
}
|
package/core/common/60-link.html
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
|
29
29
|
</div>
|
|
30
30
|
<div class="form-row">
|
|
31
|
-
<label for="node-input-timeout"><span data-i18n="exec.label.timeout"></span></label>
|
|
31
|
+
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="exec.label.timeout"></span></label>
|
|
32
32
|
<input type="text" id="node-input-timeout" placeholder="30" style="width: 70px; margin-right: 5px;"><span data-i18n="inject.seconds"></span>
|
|
33
33
|
</div>
|
|
34
34
|
<div class="form-row">
|
|
@@ -82,6 +82,11 @@
|
|
|
82
82
|
<input id="node-input-outputs" style="width: 60px;" value="1">
|
|
83
83
|
</div>
|
|
84
84
|
|
|
85
|
+
<div class="form-row">
|
|
86
|
+
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="function.label.timeout"></span></label>
|
|
87
|
+
<input id="node-input-timeout" style="width: 60px;" data-i18n="[placeholder]join.seconds">
|
|
88
|
+
</div>
|
|
89
|
+
|
|
85
90
|
<div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
|
|
86
91
|
<label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label>
|
|
87
92
|
</div>
|
|
@@ -360,6 +365,7 @@
|
|
|
360
365
|
name: {value:"_DEFAULT_"},
|
|
361
366
|
func: {value:"\nreturn msg;"},
|
|
362
367
|
outputs: {value:1},
|
|
368
|
+
timeout:{value:0},
|
|
363
369
|
noerr: {value:0,required:true,
|
|
364
370
|
validate: function(v, opt) {
|
|
365
371
|
if (!v) {
|
|
@@ -464,6 +470,26 @@
|
|
|
464
470
|
}
|
|
465
471
|
});
|
|
466
472
|
|
|
473
|
+
// 4294967 is max in node.js timeout.
|
|
474
|
+
$( "#node-input-timeout" ).spinner({
|
|
475
|
+
min: 0,
|
|
476
|
+
max: 4294967,
|
|
477
|
+
change: function(event, ui) {
|
|
478
|
+
var value = this.value;
|
|
479
|
+
if(value == ""){
|
|
480
|
+
value = 0;
|
|
481
|
+
}
|
|
482
|
+
else
|
|
483
|
+
{
|
|
484
|
+
value = parseInt(value);
|
|
485
|
+
}
|
|
486
|
+
value = isNaN(value) ? 1 : value;
|
|
487
|
+
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
|
|
488
|
+
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
|
|
489
|
+
if (value !== this.value) { $(this).spinner("value", value); }
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
467
493
|
var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) {
|
|
468
494
|
var editor = RED.editor.createEditor({
|
|
469
495
|
id: id,
|
|
@@ -503,7 +529,7 @@
|
|
|
503
529
|
editor:this.editor, // the field name the main text body goes to
|
|
504
530
|
mode:"ace/mode/nrjavascript",
|
|
505
531
|
fields:[
|
|
506
|
-
'name', 'outputs',
|
|
532
|
+
'name', 'outputs', 'timeout',
|
|
507
533
|
{
|
|
508
534
|
name: 'initialize',
|
|
509
535
|
get: function() {
|
|
@@ -96,6 +96,13 @@ module.exports = function(RED) {
|
|
|
96
96
|
node.name = n.name;
|
|
97
97
|
node.func = n.func;
|
|
98
98
|
node.outputs = n.outputs;
|
|
99
|
+
node.timeout = n.timeout*1000;
|
|
100
|
+
if(node.timeout>0){
|
|
101
|
+
node.timeoutOptions = {
|
|
102
|
+
timeout:node.timeout,
|
|
103
|
+
breakOnSigint:true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
99
106
|
node.ini = n.initialize ? n.initialize.trim() : "";
|
|
100
107
|
node.fin = n.finalize ? n.finalize.trim() : "";
|
|
101
108
|
node.libs = n.libs || [];
|
|
@@ -362,6 +369,10 @@ module.exports = function(RED) {
|
|
|
362
369
|
})(__initSend__);`;
|
|
363
370
|
iniOpt = createVMOpt(node, " setup");
|
|
364
371
|
iniScript = new vm.Script(iniText, iniOpt);
|
|
372
|
+
if(node.timeout>0){
|
|
373
|
+
iniOpt.timeout = node.timeout;
|
|
374
|
+
iniOpt.breakOnSigint = true;
|
|
375
|
+
}
|
|
365
376
|
}
|
|
366
377
|
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
|
367
378
|
if (node.fin && (node.fin !== "")) {
|
|
@@ -385,6 +396,10 @@ module.exports = function(RED) {
|
|
|
385
396
|
})();`;
|
|
386
397
|
finOpt = createVMOpt(node, " cleanup");
|
|
387
398
|
finScript = new vm.Script(finText, finOpt);
|
|
399
|
+
if(node.timeout>0){
|
|
400
|
+
finOpt.timeout = node.timeout;
|
|
401
|
+
finOpt.breakOnSigint = true;
|
|
402
|
+
}
|
|
388
403
|
}
|
|
389
404
|
var promise = Promise.resolve();
|
|
390
405
|
if (iniScript) {
|
|
@@ -396,9 +411,12 @@ module.exports = function(RED) {
|
|
|
396
411
|
var start = process.hrtime();
|
|
397
412
|
context.msg = msg;
|
|
398
413
|
context.__send__ = send;
|
|
399
|
-
context.__done__ = done;
|
|
400
|
-
|
|
401
|
-
node.
|
|
414
|
+
context.__done__ = done;
|
|
415
|
+
var opts = {};
|
|
416
|
+
if (node.timeout>0){
|
|
417
|
+
opts = node.timeoutOptions;
|
|
418
|
+
}
|
|
419
|
+
node.script.runInContext(context,opts);
|
|
402
420
|
context.results.then(function(results) {
|
|
403
421
|
sendResults(node,send,msg._msgid,results,false);
|
|
404
422
|
if (handleNodeDoneCall) {
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
} else if (type === "istype") {
|
|
104
104
|
r.v = rule.find(".node-input-rule-type-value").typedInput('type');
|
|
105
105
|
r.vt = rule.find(".node-input-rule-type-value").typedInput('type');
|
|
106
|
+
r.vt = (r.vt === "number") ? "num" : "str";
|
|
106
107
|
} else if (type === "jsonata_exp") {
|
|
107
108
|
r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
|
|
108
109
|
r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');
|
|
@@ -229,6 +229,7 @@ module.exports = function(RED) {
|
|
|
229
229
|
node.on("input", function(msg, send, done) {
|
|
230
230
|
if (!node.drop) {
|
|
231
231
|
var m = RED.util.cloneMessage(msg);
|
|
232
|
+
delete m.flush;
|
|
232
233
|
if (Object.keys(m).length > 1) {
|
|
233
234
|
if (node.intervalID !== -1) {
|
|
234
235
|
if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate)) && node.rate !== m.rate) {
|
package/core/function/rbe.js
CHANGED
|
@@ -35,7 +35,11 @@ module.exports = function(RED) {
|
|
|
35
35
|
}
|
|
36
36
|
else { node.previous = {}; }
|
|
37
37
|
}
|
|
38
|
-
var value
|
|
38
|
+
var value;
|
|
39
|
+
try {
|
|
40
|
+
value = RED.util.getMessageProperty(msg,node.property);
|
|
41
|
+
}
|
|
42
|
+
catch(e) { }
|
|
39
43
|
if (value !== undefined) {
|
|
40
44
|
var t = "_no_topic";
|
|
41
45
|
if (node.septopics) { t = topic || t; }
|
|
@@ -249,6 +249,12 @@
|
|
|
249
249
|
<span id="node-config-input-cleansession-label" data-i18n="mqtt.label.cleansession"></span>
|
|
250
250
|
</label>
|
|
251
251
|
</div>
|
|
252
|
+
<div class="form-row mqtt-persistence">
|
|
253
|
+
<label for="node-config-input-autoUnsubscribe" style="width: auto;">
|
|
254
|
+
<input type="checkbox" id="node-config-input-autoUnsubscribe" style="position: relative;vertical-align: bottom; top: -2px; width: 15px;height: 15px;">
|
|
255
|
+
<span id="node-config-input-autoUnsubscribe-label" data-i18n="mqtt.label.autoUnsubscribe"></span>
|
|
256
|
+
</label>
|
|
257
|
+
</div>
|
|
252
258
|
<div class="form-row mqtt5">
|
|
253
259
|
<label style="width:auto" for="node-config-input-sessionExpiry"><span data-i18n="mqtt.label.sessionExpiry"></span></label>
|
|
254
260
|
<input type="number" min="0" id="node-config-input-sessionExpiry" style="width: 100px" >
|
|
@@ -483,17 +489,23 @@
|
|
|
483
489
|
tls: {type:"tls-config",required: false,
|
|
484
490
|
label:RED._("node-red:mqtt.label.use-tls") },
|
|
485
491
|
clientid: {value:"", validate: function(v, opt) {
|
|
486
|
-
|
|
492
|
+
let ok = true;
|
|
487
493
|
if ($("#node-config-input-clientid").length) {
|
|
488
494
|
// Currently editing the node
|
|
489
|
-
|
|
495
|
+
let needClientId = !$("#node-config-input-cleansession").is(":checked") || !$("#node-config-input-autoUnsubscribe").is(":checked")
|
|
496
|
+
if (needClientId) {
|
|
497
|
+
ok = (v||"").length > 0;
|
|
498
|
+
}
|
|
490
499
|
} else {
|
|
491
|
-
|
|
500
|
+
let needClientId = !(this.cleansession===undefined || this.cleansession) || this.autoUnsubscribe;
|
|
501
|
+
if (needClientId) {
|
|
502
|
+
ok = (v||"").length > 0;
|
|
503
|
+
}
|
|
492
504
|
}
|
|
493
|
-
if (ok) {
|
|
494
|
-
return
|
|
505
|
+
if (!ok) {
|
|
506
|
+
return RED._("node-red:mqtt.errors.invalid-client-id");
|
|
495
507
|
}
|
|
496
|
-
return
|
|
508
|
+
return true;
|
|
497
509
|
}},
|
|
498
510
|
autoConnect: {value: true},
|
|
499
511
|
usetls: {value: false},
|
|
@@ -505,6 +517,7 @@
|
|
|
505
517
|
label: RED._("node-red:mqtt.label.keepalive"),
|
|
506
518
|
validate:RED.validators.number(false)},
|
|
507
519
|
cleansession: {value: true},
|
|
520
|
+
autoUnsubscribe: {value: true},
|
|
508
521
|
birthTopic: {value:"", validate:validateMQTTPublishTopic},
|
|
509
522
|
birthQos: {value:"0"},
|
|
510
523
|
birthRetain: {value:"false"},
|
|
@@ -620,6 +633,10 @@
|
|
|
620
633
|
this.cleansession = true;
|
|
621
634
|
$("#node-config-input-cleansession").prop("checked",true);
|
|
622
635
|
}
|
|
636
|
+
if (typeof this.autoUnsubscribe === 'undefined') {
|
|
637
|
+
this.autoUnsubscribe = true;
|
|
638
|
+
$("#node-config-input-autoUnsubscribe").prop("checked",true);
|
|
639
|
+
}
|
|
623
640
|
if (typeof this.usetls === 'undefined') {
|
|
624
641
|
this.usetls = false;
|
|
625
642
|
$("#node-config-input-usetls").prop("checked",false);
|
|
@@ -635,6 +652,14 @@
|
|
|
635
652
|
if (typeof this.protocolVersion === 'undefined') {
|
|
636
653
|
this.protocolVersion = 4;
|
|
637
654
|
}
|
|
655
|
+
$("#node-config-input-cleansession").on("change", function() {
|
|
656
|
+
const useCleanSession = $("#node-config-input-cleansession").is(':checked');
|
|
657
|
+
if(useCleanSession) {
|
|
658
|
+
$("div.form-row.mqtt-persistence").hide();
|
|
659
|
+
} else {
|
|
660
|
+
$("div.form-row.mqtt-persistence").show();
|
|
661
|
+
}
|
|
662
|
+
});
|
|
638
663
|
$("#node-config-input-protocolVersion").on("change", function() {
|
|
639
664
|
var v5 = $("#node-config-input-protocolVersion").val() == "5";
|
|
640
665
|
if(v5) {
|
package/core/network/10-mqtt.js
CHANGED
|
@@ -219,8 +219,10 @@ module.exports = function(RED) {
|
|
|
219
219
|
* Handle the payload / packet recieved in MQTT In and MQTT Sub nodes
|
|
220
220
|
*/
|
|
221
221
|
function subscriptionHandler(node, datatype ,topic, payload, packet) {
|
|
222
|
-
const
|
|
223
|
-
|
|
222
|
+
const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
|
223
|
+
const v5 = (node && node.brokerConn)
|
|
224
|
+
? node.brokerConn.v5()
|
|
225
|
+
: Object.prototype.hasOwnProperty.call(packet, "properties");
|
|
224
226
|
if(v5 && packet.properties) {
|
|
225
227
|
setStrProp(packet.properties, msg, "responseTopic");
|
|
226
228
|
setBufferProp(packet.properties, msg, "correlationData");
|
|
@@ -300,7 +302,7 @@ module.exports = function(RED) {
|
|
|
300
302
|
//}
|
|
301
303
|
}
|
|
302
304
|
msg.payload = payload;
|
|
303
|
-
if ((node.brokerConn.broker === "localhost"
|
|
305
|
+
if (node.brokerConn && (node.brokerConn.broker === "localhost" || node.brokerConn.broker === "127.0.0.1")) {
|
|
304
306
|
msg._topic = topic;
|
|
305
307
|
}
|
|
306
308
|
node.send(msg);
|
|
@@ -412,6 +414,12 @@ module.exports = function(RED) {
|
|
|
412
414
|
}
|
|
413
415
|
}
|
|
414
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Perform the connect action
|
|
419
|
+
* @param {MQTTInNode|MQTTOutNode} node
|
|
420
|
+
* @param {Object} msg
|
|
421
|
+
* @param {Function} done
|
|
422
|
+
*/
|
|
415
423
|
function handleConnectAction(node, msg, done) {
|
|
416
424
|
let actionData = typeof msg.broker === 'object' ? msg.broker : null;
|
|
417
425
|
if (node.brokerConn.canConnect()) {
|
|
@@ -442,12 +450,17 @@ module.exports = function(RED) {
|
|
|
442
450
|
}
|
|
443
451
|
}
|
|
444
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Perform the disconnect action
|
|
455
|
+
* @param {MQTTInNode|MQTTOutNode} node
|
|
456
|
+
* @param {Function} done
|
|
457
|
+
*/
|
|
445
458
|
function handleDisconnectAction(node, done) {
|
|
446
459
|
node.brokerConn.disconnect(function () {
|
|
447
460
|
done();
|
|
448
461
|
});
|
|
449
462
|
}
|
|
450
|
-
|
|
463
|
+
const unsubscribeCandidates = {}
|
|
451
464
|
//#endregion "Supporting functions"
|
|
452
465
|
|
|
453
466
|
//#region "Broker node"
|
|
@@ -482,6 +495,7 @@ module.exports = function(RED) {
|
|
|
482
495
|
setIfHasProperty(opts, node, "protocolVersion", init);
|
|
483
496
|
setIfHasProperty(opts, node, "keepalive", init);
|
|
484
497
|
setIfHasProperty(opts, node, "cleansession", init);
|
|
498
|
+
setIfHasProperty(opts, node, "autoUnsubscribe", init);
|
|
485
499
|
setIfHasProperty(opts, node, "topicAliasMaximum", init);
|
|
486
500
|
setIfHasProperty(opts, node, "maximumPacketSize", init);
|
|
487
501
|
setIfHasProperty(opts, node, "receiveMaximum", init);
|
|
@@ -590,7 +604,9 @@ module.exports = function(RED) {
|
|
|
590
604
|
if (typeof node.cleansession === 'undefined') {
|
|
591
605
|
node.cleansession = true;
|
|
592
606
|
}
|
|
593
|
-
|
|
607
|
+
if (typeof node.autoUnsubscribe !== 'boolean') {
|
|
608
|
+
node.autoUnsubscribe = true;
|
|
609
|
+
}
|
|
594
610
|
//use url or build a url from usetls://broker:port
|
|
595
611
|
if (node.url && node.brokerurl !== node.url) {
|
|
596
612
|
node.brokerurl = node.url;
|
|
@@ -697,7 +713,8 @@ module.exports = function(RED) {
|
|
|
697
713
|
node.options.rejectUnauthorized = (node.verifyservercert == "true" || node.verifyservercert === true);
|
|
698
714
|
}
|
|
699
715
|
}
|
|
700
|
-
|
|
716
|
+
node.v5 = () => node.options && node.options.protocolVersion == 5
|
|
717
|
+
node.subscriptionIdentifiersAvailable = () => node.v5() && node.serverProperties && node.serverProperties.subscriptionIdentifiersAvailable
|
|
701
718
|
n.autoConnect = n.autoConnect === "false" || n.autoConnect === false ? false : true;
|
|
702
719
|
node.setOptions(n, true);
|
|
703
720
|
|
|
@@ -779,18 +796,11 @@ module.exports = function(RED) {
|
|
|
779
796
|
// Re-subscribe to stored topics
|
|
780
797
|
for (var s in node.subscriptions) {
|
|
781
798
|
if (node.subscriptions.hasOwnProperty(s)) {
|
|
782
|
-
let topic = s;
|
|
783
|
-
let qos = 0;
|
|
784
|
-
let _options = {};
|
|
785
799
|
for (var r in node.subscriptions[s]) {
|
|
786
800
|
if (node.subscriptions[s].hasOwnProperty(r)) {
|
|
787
|
-
|
|
788
|
-
_options = node.subscriptions[s][r].options;
|
|
789
|
-
node._clientOn('message',node.subscriptions[s][r].handler);
|
|
801
|
+
node.subscribe(node.subscriptions[s][r])
|
|
790
802
|
}
|
|
791
803
|
}
|
|
792
|
-
_options.qos = _options.qos || qos;
|
|
793
|
-
node.client.subscribe(topic, _options);
|
|
794
804
|
}
|
|
795
805
|
}
|
|
796
806
|
|
|
@@ -852,22 +862,28 @@ module.exports = function(RED) {
|
|
|
852
862
|
if(!node.client) { return _callback(); }
|
|
853
863
|
if(node.closing) { return _callback(); }
|
|
854
864
|
|
|
865
|
+
/**
|
|
866
|
+
* Call end and wait for the client to end (or timeout)
|
|
867
|
+
* @param {mqtt.MqttClient} client The broker client
|
|
868
|
+
* @param {number} ms The time to wait for the client to end
|
|
869
|
+
* @returns
|
|
870
|
+
*/
|
|
855
871
|
let waitEnd = (client, ms) => {
|
|
856
872
|
return new Promise( (resolve, reject) => {
|
|
857
873
|
node.closing = true;
|
|
858
|
-
if(!client) {
|
|
874
|
+
if (!client) {
|
|
859
875
|
resolve();
|
|
860
|
-
|
|
876
|
+
} else {
|
|
861
877
|
const t = setTimeout(() => {
|
|
862
878
|
//clean end() has exceeded WAIT_END, lets force end!
|
|
863
879
|
client && client.end(true);
|
|
864
|
-
|
|
880
|
+
resolve();
|
|
865
881
|
}, ms);
|
|
866
882
|
client.end(() => {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
883
|
+
clearTimeout(t);
|
|
884
|
+
resolve()
|
|
885
|
+
});
|
|
886
|
+
}
|
|
871
887
|
});
|
|
872
888
|
};
|
|
873
889
|
if(node.connected && node.closeMessage) {
|
|
@@ -888,64 +904,222 @@ module.exports = function(RED) {
|
|
|
888
904
|
}
|
|
889
905
|
node.subscriptionIds = {};
|
|
890
906
|
node.subid = 1;
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
907
|
+
|
|
908
|
+
//typedef for subscription object:
|
|
909
|
+
/**
|
|
910
|
+
* @typedef {Object} Subscription
|
|
911
|
+
* @property {String} topic - topic to subscribe to
|
|
912
|
+
* @property {Object} [options] - options object
|
|
913
|
+
* @property {Number} [options.qos] - quality of service
|
|
914
|
+
* @property {Number} [options.nl] - no local
|
|
915
|
+
* @property {Number} [options.rap] - retain as published
|
|
916
|
+
* @property {Number} [options.rh] - retain handling
|
|
917
|
+
* @property {Number} [options.properties] - MQTT 5.0 properties
|
|
918
|
+
* @property {Number} [options.properties.subscriptionIdentifier] - MQTT 5.0 subscription identifier
|
|
919
|
+
* @property {Number} [options.properties.userProperties] - MQTT 5.0 user properties
|
|
920
|
+
* @property {Function} callback
|
|
921
|
+
* @property {String} ref - reference to the node that created the subscription
|
|
922
|
+
*/
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Create a subscription object
|
|
926
|
+
* @param {String} _topic - topic to subscribe to
|
|
927
|
+
* @param {Object} _options - options object
|
|
928
|
+
* @param {String} _ref - reference to the node that created the subscription
|
|
929
|
+
* @returns {Subscription}
|
|
930
|
+
*/
|
|
931
|
+
function createSubscriptionObject(_topic, _options, _ref, _brokerId) {
|
|
932
|
+
/** @type {Subscription} */
|
|
933
|
+
const subscription = {};
|
|
934
|
+
const ref = _ref || 0;
|
|
935
|
+
let options
|
|
936
|
+
let qos = 1 // default to QoS 1 (AWS and several other brokers don't support QoS 2)
|
|
937
|
+
|
|
938
|
+
// if options is an object, then clone it
|
|
939
|
+
if (typeof _options == "object") {
|
|
940
|
+
options = RED.util.cloneMessage(_options || {})
|
|
941
|
+
qos = _options.qos;
|
|
942
|
+
} else if (typeof _options == "number") {
|
|
943
|
+
qos = _options;
|
|
944
|
+
}
|
|
945
|
+
options = options || {};
|
|
946
|
+
|
|
947
|
+
// sanitise qos
|
|
948
|
+
if (typeof qos === "number" && qos >= 0 && qos <= 2) {
|
|
949
|
+
options.qos = qos;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
subscription.topic = _topic;
|
|
953
|
+
subscription.qos = qos;
|
|
954
|
+
subscription.options = RED.util.cloneMessage(options);
|
|
955
|
+
subscription.ref = ref;
|
|
956
|
+
subscription.brokerId = _brokerId;
|
|
957
|
+
return subscription;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* If topic is a subscription object, then use that, otherwise look up the topic in
|
|
962
|
+
* the subscriptions object. If the topic is not found, then create a new subscription
|
|
963
|
+
* object and add it to the subscriptions object.
|
|
964
|
+
* @param {Subscription|String} topic
|
|
965
|
+
* @param {*} options
|
|
966
|
+
* @param {*} callback
|
|
967
|
+
* @param {*} ref
|
|
968
|
+
*/
|
|
969
|
+
node.subscribe = function (topic, options, callback, ref) {
|
|
970
|
+
/** @type {Subscription} */
|
|
971
|
+
let subscription
|
|
972
|
+
let doCompare = false
|
|
973
|
+
let changesFound = false
|
|
974
|
+
|
|
975
|
+
// function signature 1: subscribe(subscription: Subscription)
|
|
976
|
+
if (typeof topic === "object" && topic !== null) {
|
|
977
|
+
subscription = topic
|
|
978
|
+
topic = subscription.topic
|
|
979
|
+
options = subscription.options
|
|
980
|
+
ref = subscription.ref
|
|
981
|
+
callback = subscription.callback
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// function signature 2: subscribe(topic: String, options: Object, callback: Function, ref: String)
|
|
985
|
+
else if (typeof topic === "string") {
|
|
986
|
+
// since this is a call where all params are provided, it might be
|
|
987
|
+
// a node change (modification) so we need to check for changes
|
|
988
|
+
doCompare = true
|
|
989
|
+
subscription = node.subscriptions[topic] && node.subscriptions[topic][ref]
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// bad function call
|
|
993
|
+
else {
|
|
994
|
+
console.warn('Invalid call to node.subscribe')
|
|
995
|
+
return
|
|
899
996
|
}
|
|
900
|
-
|
|
997
|
+
const thisBrokerId = node.type === 'mqtt-broker' ? node.id : node.broker
|
|
998
|
+
|
|
999
|
+
// unsubscribe topics where the broker has changed
|
|
1000
|
+
const oldBrokerSubs = (unsubscribeCandidates[ref] || []).filter(sub => sub.brokerId !== thisBrokerId)
|
|
1001
|
+
oldBrokerSubs.forEach(sub => {
|
|
1002
|
+
/** @type {MQTTBrokerNode} */
|
|
1003
|
+
const _brokerConn = RED.nodes.getNode(sub.brokerId)
|
|
1004
|
+
if (_brokerConn) {
|
|
1005
|
+
_brokerConn.unsubscribe(sub.topic, sub.ref, true)
|
|
1006
|
+
}
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
// if subscription is found (or sent in as a parameter), then check for changes.
|
|
1010
|
+
// if there are any changes requested, tidy up the old subscription
|
|
1011
|
+
if (subscription) {
|
|
1012
|
+
if (doCompare) {
|
|
1013
|
+
// compare the current sub to the passed in parameters. Use RED.util.compareObjects against
|
|
1014
|
+
// only the minimal set of properties to identify if the subscription has changed
|
|
1015
|
+
const currentSubscription = createSubscriptionObject(subscription.topic, subscription.options, subscription.ref)
|
|
1016
|
+
const newSubscription = createSubscriptionObject(topic, options, ref)
|
|
1017
|
+
changesFound = RED.util.compareObjects(currentSubscription, newSubscription) === false
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (changesFound) {
|
|
1022
|
+
if (subscription.handler) {
|
|
1023
|
+
node._clientRemoveListeners('message', subscription.handler)
|
|
1024
|
+
subscription.handler = null
|
|
1025
|
+
}
|
|
1026
|
+
const _brokerConn = RED.nodes.getNode(subscription.brokerId)
|
|
1027
|
+
if (_brokerConn) {
|
|
1028
|
+
_brokerConn.unsubscribe(subscription.topic, subscription.ref, true)
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// clean up the unsubscribe candidate list
|
|
1033
|
+
delete unsubscribeCandidates[ref]
|
|
1034
|
+
|
|
1035
|
+
// determine if this is an existing subscription
|
|
1036
|
+
const existingSubscription = typeof subscription === "object" && subscription !== null
|
|
1037
|
+
|
|
1038
|
+
// if existing subscription is not found or has changed, create a new subscription object
|
|
1039
|
+
if (existingSubscription === false || changesFound) {
|
|
1040
|
+
subscription = createSubscriptionObject(topic, options, ref, node.id)
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// setup remainder of subscription properties and event handling
|
|
1044
|
+
node.subscriptions[topic] = node.subscriptions[topic] || {};
|
|
1045
|
+
node.subscriptions[topic][ref] = subscription
|
|
901
1046
|
if (!node.subscriptionIds[topic]) {
|
|
902
1047
|
node.subscriptionIds[topic] = node.subid++;
|
|
903
1048
|
}
|
|
904
|
-
options
|
|
905
|
-
options.properties
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
qos:qos,
|
|
911
|
-
options:options,
|
|
912
|
-
handler:function(mtopic,mpayload, mpacket) {
|
|
913
|
-
if(mpacket.properties && options.properties && mpacket.properties.subscriptionIdentifier && options.properties.subscriptionIdentifier && (mpacket.properties.subscriptionIdentifier !== options.properties.subscriptionIdentifier) ) {
|
|
914
|
-
//do nothing as subscriptionIdentifier does not match
|
|
915
|
-
} else if (matchTopic(topic,mtopic)) {
|
|
916
|
-
callback(mtopic,mpayload, mpacket);
|
|
917
|
-
}
|
|
918
|
-
},
|
|
919
|
-
ref: ref
|
|
920
|
-
};
|
|
921
|
-
node.subscriptions[topic][ref] = sub;
|
|
1049
|
+
subscription.options = subscription.options || {};
|
|
1050
|
+
subscription.options.properties = options.properties || {};
|
|
1051
|
+
subscription.options.properties.subscriptionIdentifier = node.subscriptionIds[topic];
|
|
1052
|
+
subscription.callback = callback;
|
|
1053
|
+
|
|
1054
|
+
// if the client is connected, then setup the handler and subscribe
|
|
922
1055
|
if (node.connected) {
|
|
923
|
-
node.
|
|
924
|
-
|
|
1056
|
+
const subIdsAvailable = node.subscriptionIdentifiersAvailable()
|
|
1057
|
+
|
|
1058
|
+
if (!subscription.handler) {
|
|
1059
|
+
subscription.handler = function (mtopic, mpayload, mpacket) {
|
|
1060
|
+
const sops = subscription.options ? subscription.options.properties : {}
|
|
1061
|
+
const pops = mpacket.properties || {}
|
|
1062
|
+
if (subIdsAvailable && pops.subscriptionIdentifier && sops.subscriptionIdentifier && (pops.subscriptionIdentifier !== sops.subscriptionIdentifier)) {
|
|
1063
|
+
//do nothing as subscriptionIdentifier does not match
|
|
1064
|
+
} else if (matchTopic(topic, mtopic)) {
|
|
1065
|
+
subscription.callback && subscription.callback(mtopic, mpayload, mpacket)
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
node._clientOn('message', subscription.handler)
|
|
1070
|
+
// if the broker doesn't support subscription identifiers, then don't send them (AWS support)
|
|
1071
|
+
if (subscription.options.properties && subscription.options.properties.subscriptionIdentifier && subIdsAvailable !== true) {
|
|
1072
|
+
delete subscription.options.properties.subscriptionIdentifier
|
|
1073
|
+
}
|
|
1074
|
+
node.client.subscribe(topic, subscription.options)
|
|
925
1075
|
}
|
|
926
|
-
};
|
|
927
1076
|
|
|
928
|
-
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
node.unsubscribe = function (topic, ref, removeClientSubscription) {
|
|
929
1080
|
ref = ref||0;
|
|
930
|
-
|
|
1081
|
+
const unsub = removeClientSubscription || node.autoUnsubscribe !== false
|
|
1082
|
+
const sub = node.subscriptions[topic];
|
|
1083
|
+
let brokerId = node.id
|
|
931
1084
|
if (sub) {
|
|
932
1085
|
if (sub[ref]) {
|
|
933
|
-
|
|
934
|
-
|
|
1086
|
+
brokerId = sub[ref].brokerId || brokerId
|
|
1087
|
+
if(node.client && sub[ref].handler) {
|
|
1088
|
+
node._clientRemoveListeners('message', sub[ref].handler);
|
|
1089
|
+
sub[ref].handler = null
|
|
1090
|
+
}
|
|
1091
|
+
if (unsub) {
|
|
1092
|
+
delete sub[ref]
|
|
935
1093
|
}
|
|
936
|
-
delete sub[ref];
|
|
937
1094
|
}
|
|
938
|
-
//
|
|
939
|
-
|
|
940
|
-
|
|
1095
|
+
// if instructed to remove the actual MQTT client subscription
|
|
1096
|
+
if (unsub) {
|
|
1097
|
+
// if there are no more subscriptions for the topic, then remove the topic
|
|
941
1098
|
if (Object.keys(sub).length === 0) {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1099
|
+
try {
|
|
1100
|
+
node.client.unsubscribe(topic)
|
|
1101
|
+
} catch (_err) {
|
|
1102
|
+
// do nothing
|
|
1103
|
+
} finally {
|
|
1104
|
+
// remove unsubscribe candidate as it is now REALLY unsubscribed
|
|
1105
|
+
delete node.subscriptions[topic];
|
|
1106
|
+
delete node.subscriptionIds[topic];
|
|
1107
|
+
if (unsubscribeCandidates[ref]) {
|
|
1108
|
+
unsubscribeCandidates[ref] = unsubscribeCandidates[ref].filter(sub => sub.topic !== topic)
|
|
1109
|
+
}
|
|
946
1110
|
}
|
|
947
1111
|
}
|
|
948
|
-
|
|
1112
|
+
} else {
|
|
1113
|
+
// if instructed to not remove the client subscription, then add it to the candidate list
|
|
1114
|
+
// of subscriptions to be removed when the the same ref is used in a subsequent subscribe
|
|
1115
|
+
// and the topic has changed
|
|
1116
|
+
unsubscribeCandidates[ref] = unsubscribeCandidates[ref] || [];
|
|
1117
|
+
unsubscribeCandidates[ref].push({
|
|
1118
|
+
topic: topic,
|
|
1119
|
+
ref: ref,
|
|
1120
|
+
brokerId: brokerId
|
|
1121
|
+
})
|
|
1122
|
+
}
|
|
949
1123
|
}
|
|
950
1124
|
};
|
|
951
1125
|
node.topicAliases = {};
|
|
@@ -983,7 +1157,7 @@ module.exports = function(RED) {
|
|
|
983
1157
|
setStrProp(msg, options.properties, "contentType");
|
|
984
1158
|
setIntProp(msg, options.properties, "messageExpiryInterval", 0);
|
|
985
1159
|
setUserProperties(msg.userProperties, options.properties);
|
|
986
|
-
setIntProp(msg, options.properties, "topicAlias", 1,
|
|
1160
|
+
setIntProp(msg, options.properties, "topicAlias", 1, bsp.topicAliasMaximum || 0);
|
|
987
1161
|
setBoolProp(msg, options.properties, "payloadFormatIndicator");
|
|
988
1162
|
//FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
|
|
989
1163
|
|
|
@@ -1119,7 +1293,7 @@ module.exports = function(RED) {
|
|
|
1119
1293
|
if(node.rap === "true" || node.rap === true) options.rap = true;
|
|
1120
1294
|
else if(node.rap === "false" || node.rap === false) options.rap = false;
|
|
1121
1295
|
}
|
|
1122
|
-
|
|
1296
|
+
node._topic = node.topic; // store the original topic incase node is later changed
|
|
1123
1297
|
node.brokerConn.subscribe(node.topic,options,function(topic, payload, packet) {
|
|
1124
1298
|
subscriptionHandler(node, node.datatype, topic, payload, packet);
|
|
1125
1299
|
},node.id);
|
|
@@ -1172,7 +1346,7 @@ module.exports = function(RED) {
|
|
|
1172
1346
|
}
|
|
1173
1347
|
if (action === Actions.UNSUBSCRIBE) {
|
|
1174
1348
|
subscriptions.forEach(function (sub) {
|
|
1175
|
-
node.brokerConn.unsubscribe(sub.topic, node.id);
|
|
1349
|
+
node.brokerConn.unsubscribe(sub.topic, node.id, true);
|
|
1176
1350
|
delete node.dynamicSubs[sub.topic];
|
|
1177
1351
|
})
|
|
1178
1352
|
//user can access current subscriptions through the complete node is so desired
|
|
@@ -1182,7 +1356,7 @@ module.exports = function(RED) {
|
|
|
1182
1356
|
subscriptions.forEach(function (sub) {
|
|
1183
1357
|
//always unsubscribe before subscribe to prevent multiple subs to same topic
|
|
1184
1358
|
if (node.dynamicSubs[sub.topic]) {
|
|
1185
|
-
node.brokerConn.unsubscribe(sub.topic, node.id);
|
|
1359
|
+
node.brokerConn.unsubscribe(sub.topic, node.id, true);
|
|
1186
1360
|
delete node.dynamicSubs[sub.topic];
|
|
1187
1361
|
}
|
|
1188
1362
|
|
|
@@ -1233,7 +1407,7 @@ module.exports = function(RED) {
|
|
|
1233
1407
|
});
|
|
1234
1408
|
node.dynamicSubs = {};
|
|
1235
1409
|
} else {
|
|
1236
|
-
node.brokerConn.unsubscribe(node.topic,node.id, removed);
|
|
1410
|
+
node.brokerConn.unsubscribe(node.topic, node.id, removed);
|
|
1237
1411
|
}
|
|
1238
1412
|
node.brokerConn.deregister(node, done, removed);
|
|
1239
1413
|
node.brokerConn = null;
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
|
|
94
94
|
<div class="form-row">
|
|
95
95
|
<input type="checkbox" id="node-input-insecureHTTPParser" style="display: inline-block; width: auto; vertical-align: top;">
|
|
96
|
-
<label for="node-input-insecureHTTPParser"
|
|
96
|
+
<label for="node-input-insecureHTTPParser" style="width: auto;" data-i18n="httpin.insecureHTTPParser"></label>
|
|
97
97
|
</div>
|
|
98
98
|
|
|
99
99
|
|
|
@@ -14,15 +14,17 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
|
-
module.exports = function(RED) {
|
|
17
|
+
module.exports = async function(RED) {
|
|
18
18
|
"use strict";
|
|
19
|
-
const got =
|
|
19
|
+
const { got } = await import('got')
|
|
20
20
|
const {CookieJar} = require("tough-cookie");
|
|
21
21
|
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
|
|
22
22
|
const FormData = require('form-data');
|
|
23
23
|
const { v4: uuid } = require('uuid');
|
|
24
24
|
const crypto = require('crypto');
|
|
25
25
|
const URL = require("url").URL
|
|
26
|
+
const http = require("http")
|
|
27
|
+
const https = require("https")
|
|
26
28
|
var mustache = require("mustache");
|
|
27
29
|
var querystring = require("querystring");
|
|
28
30
|
var cookie = require("cookie");
|
|
@@ -65,16 +67,27 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
65
67
|
function HTTPRequest(n) {
|
|
66
68
|
RED.nodes.createNode(this,n);
|
|
67
69
|
checkNodeAgentPatch();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
const node = this;
|
|
71
|
+
const nodeUrl = n.url;
|
|
72
|
+
const isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
|
73
|
+
const nodeMethod = n.method || "GET";
|
|
74
|
+
let paytoqs = false;
|
|
75
|
+
let paytobody = false;
|
|
76
|
+
let redirectList = [];
|
|
77
|
+
const sendErrorsToCatch = n.senderr;
|
|
76
78
|
node.headers = n.headers || [];
|
|
77
|
-
|
|
79
|
+
const useKeepAlive = n["persist"];
|
|
80
|
+
let agents = null
|
|
81
|
+
if (useKeepAlive) {
|
|
82
|
+
agents = {
|
|
83
|
+
http: new http.Agent({ keepAlive: true }),
|
|
84
|
+
https: new https.Agent({ keepAlive: true })
|
|
85
|
+
}
|
|
86
|
+
node.on('close', function () {
|
|
87
|
+
agents.http.destroy()
|
|
88
|
+
agents.https.destroy()
|
|
89
|
+
})
|
|
90
|
+
}
|
|
78
91
|
if (n.tls) {
|
|
79
92
|
var tlsNode = RED.nodes.getNode(n.tls);
|
|
80
93
|
}
|
|
@@ -210,24 +223,24 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
210
223
|
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
|
|
211
224
|
// Had to remove this to get http->https redirect to work
|
|
212
225
|
// opts.defaultPort = isHttps?443:80;
|
|
213
|
-
opts.timeout = node.reqTimeout;
|
|
226
|
+
opts.timeout = { request: node.reqTimeout || 5000 };
|
|
214
227
|
opts.throwHttpErrors = false;
|
|
215
228
|
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
|
|
216
229
|
opts.decompress = false;
|
|
217
230
|
opts.method = method;
|
|
218
|
-
opts.retry = 0;
|
|
231
|
+
opts.retry = { limit: 0 };
|
|
219
232
|
opts.responseType = 'buffer';
|
|
220
233
|
opts.maxRedirects = 21;
|
|
221
234
|
opts.cookieJar = new CookieJar();
|
|
222
235
|
opts.ignoreInvalidCookies = true;
|
|
223
|
-
opts.forever = nodeHTTPPersistent;
|
|
236
|
+
// opts.forever = nodeHTTPPersistent;
|
|
224
237
|
if (msg.requestTimeout !== undefined) {
|
|
225
238
|
if (isNaN(msg.requestTimeout)) {
|
|
226
239
|
node.warn(RED._("httpin.errors.timeout-isnan"));
|
|
227
240
|
} else if (msg.requestTimeout < 1) {
|
|
228
241
|
node.warn(RED._("httpin.errors.timeout-isnegative"));
|
|
229
242
|
} else {
|
|
230
|
-
opts.timeout = msg.requestTimeout;
|
|
243
|
+
opts.timeout = { request: msg.requestTimeout };
|
|
231
244
|
}
|
|
232
245
|
}
|
|
233
246
|
const originalHeaderMap = {};
|
|
@@ -245,9 +258,12 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
245
258
|
delete options.headers[h];
|
|
246
259
|
}
|
|
247
260
|
})
|
|
248
|
-
|
|
249
261
|
if (node.insecureHTTPParser) {
|
|
250
|
-
|
|
262
|
+
// Setting the property under _unixOptions as pretty
|
|
263
|
+
// much the only hack available to get got to apply
|
|
264
|
+
// a core http option it doesn't think we should be
|
|
265
|
+
// allowed to set
|
|
266
|
+
options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
|
|
251
267
|
}
|
|
252
268
|
}
|
|
253
269
|
],
|
|
@@ -403,15 +419,16 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
403
419
|
return response
|
|
404
420
|
}
|
|
405
421
|
const requestUrl = new URL(response.request.requestUrl);
|
|
406
|
-
const options =
|
|
422
|
+
const options = { headers: {} }
|
|
407
423
|
const normalisedHeaders = {};
|
|
408
424
|
Object.keys(response.headers).forEach(k => {
|
|
409
425
|
normalisedHeaders[k.toLowerCase()] = response.headers[k]
|
|
410
426
|
})
|
|
411
427
|
if (normalisedHeaders['www-authenticate']) {
|
|
412
|
-
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
|
428
|
+
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
|
413
429
|
options.headers.Authorization = authHeader;
|
|
414
430
|
}
|
|
431
|
+
// response.request.options.merge(options)
|
|
415
432
|
sentCreds = true;
|
|
416
433
|
return retry(options);
|
|
417
434
|
}
|
|
@@ -556,12 +573,14 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
556
573
|
opts.agent = {
|
|
557
574
|
http: new HttpProxyAgent(proxyOptions),
|
|
558
575
|
https: new HttpsProxyAgent(proxyOptions)
|
|
559
|
-
}
|
|
560
|
-
|
|
576
|
+
}
|
|
561
577
|
} else {
|
|
562
578
|
node.warn("Bad proxy url: "+ prox);
|
|
563
579
|
}
|
|
564
580
|
}
|
|
581
|
+
if (useKeepAlive && !opts.agent) {
|
|
582
|
+
opts.agent = agents
|
|
583
|
+
}
|
|
565
584
|
if (tlsNode) {
|
|
566
585
|
opts.https = {};
|
|
567
586
|
tlsNode.addTLSOptions(opts.https);
|
|
@@ -699,25 +718,43 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
699
718
|
});
|
|
700
719
|
|
|
701
720
|
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
|
|
721
|
+
const sha256 = (value) => { return crypto.createHash('sha256').update(value).digest('hex') }
|
|
722
|
+
const sha512 = (value) => { return crypto.createHash('sha512').update(value).digest('hex') }
|
|
723
|
+
|
|
724
|
+
function digestCompute(algorithm, value) {
|
|
725
|
+
var lowercaseAlgorithm = ""
|
|
726
|
+
if (algorithm) {
|
|
727
|
+
lowercaseAlgorithm = algorithm.toLowerCase().replace(/-sess$/, '')
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (lowercaseAlgorithm === "sha-256") {
|
|
731
|
+
return sha256(value)
|
|
732
|
+
} else if (lowercaseAlgorithm === "sha-512-256") {
|
|
733
|
+
var hash = sha512(value)
|
|
734
|
+
return hash.slice(0, 64) // Only use the first 256 bits
|
|
735
|
+
} else {
|
|
736
|
+
return md5(value)
|
|
737
|
+
}
|
|
738
|
+
}
|
|
702
739
|
|
|
703
740
|
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
|
704
741
|
/**
|
|
705
|
-
* RFC 2617: handle both
|
|
742
|
+
* RFC 2617: handle both standard and -sess algorithms.
|
|
706
743
|
*
|
|
707
|
-
* If the algorithm directive's value
|
|
708
|
-
* HA1=
|
|
709
|
-
*
|
|
710
|
-
*
|
|
744
|
+
* If the algorithm directive's value ends with "-sess", then HA1 is
|
|
745
|
+
* HA1=digestCompute(digestCompute(username:realm:password):nonce:cnonce)
|
|
746
|
+
*
|
|
747
|
+
* If the algorithm directive's value does not end with "-sess", then HA1 is
|
|
748
|
+
* HA1=digestCompute(username:realm:password)
|
|
711
749
|
*/
|
|
712
|
-
var ha1 =
|
|
713
|
-
if (algorithm &&
|
|
714
|
-
return
|
|
750
|
+
var ha1 = digestCompute(algorithm, user + ':' + realm + ':' + pass)
|
|
751
|
+
if (algorithm && /-sess$/i.test(algorithm)) {
|
|
752
|
+
return digestCompute(algorithm, ha1 + ':' + nonce + ':' + cnonce)
|
|
715
753
|
} else {
|
|
716
754
|
return ha1
|
|
717
755
|
}
|
|
718
756
|
}
|
|
719
757
|
|
|
720
|
-
|
|
721
758
|
function buildDigestHeader(user, pass, method, path, authHeader) {
|
|
722
759
|
var challenge = {}
|
|
723
760
|
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
|
@@ -732,10 +769,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
732
769
|
var nc = qop && '00000001'
|
|
733
770
|
var cnonce = qop && uuid().replace(/-/g, '')
|
|
734
771
|
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
|
|
735
|
-
var ha2 =
|
|
772
|
+
var ha2 = digestCompute(challenge.algorithm, method + ':' + path)
|
|
736
773
|
var digestResponse = qop
|
|
737
|
-
?
|
|
738
|
-
:
|
|
774
|
+
? digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
|
775
|
+
: digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + ha2)
|
|
739
776
|
var authValues = {
|
|
740
777
|
username: user,
|
|
741
778
|
realm: challenge.realm,
|
package/core/parsers/70-XML.js
CHANGED
|
@@ -33,8 +33,7 @@ module.exports = function(RED) {
|
|
|
33
33
|
parseString(value, options, function (err, result) {
|
|
34
34
|
if (err) { done(err); }
|
|
35
35
|
else {
|
|
36
|
-
|
|
37
|
-
RED.util.setMessageProperty(msg,node.property,value);
|
|
36
|
+
RED.util.setMessageProperty(msg,node.property,result);
|
|
38
37
|
send(msg);
|
|
39
38
|
done();
|
|
40
39
|
}
|
|
@@ -629,6 +629,9 @@ module.exports = function(RED) {
|
|
|
629
629
|
joinChar = node.joiner;
|
|
630
630
|
if (n.count === "" && msg.hasOwnProperty('parts')) {
|
|
631
631
|
targetCount = msg.parts.count || 0;
|
|
632
|
+
if (msg.parts.hasOwnProperty('id')) {
|
|
633
|
+
partId = msg.parts.id;
|
|
634
|
+
}
|
|
632
635
|
}
|
|
633
636
|
if (node.build === 'object') {
|
|
634
637
|
propertyKey = RED.util.getMessageProperty(msg,node.key);
|
package/core/storage/10-file.js
CHANGED
|
@@ -68,9 +68,12 @@ module.exports = function(RED) {
|
|
|
68
68
|
node.error(err,msg);
|
|
69
69
|
return done();
|
|
70
70
|
} else {
|
|
71
|
-
|
|
71
|
+
processMsg2(msg,nodeSend,value,done);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function processMsg2(msg,nodeSend,filename,done) {
|
|
74
77
|
filename = filename || "";
|
|
75
78
|
msg.filename = filename;
|
|
76
79
|
var fullFilename = filename;
|
|
@@ -311,9 +314,12 @@ module.exports = function(RED) {
|
|
|
311
314
|
node.error(err,msg);
|
|
312
315
|
return done();
|
|
313
316
|
} else {
|
|
314
|
-
|
|
317
|
+
processMsg2(msg, nodeSend, (value || "").replace(/\t|\r|\n/g,''), nodeDone);
|
|
315
318
|
}
|
|
316
319
|
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
function processMsg2(msg, nodeSend, filename, nodeDone) {
|
|
317
323
|
filename = filename || "";
|
|
318
324
|
var fullFilename = filename;
|
|
319
325
|
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
|
@@ -434,7 +440,8 @@ module.exports = function(RED) {
|
|
|
434
440
|
nodeDone();
|
|
435
441
|
});
|
|
436
442
|
}
|
|
437
|
-
}
|
|
443
|
+
}
|
|
444
|
+
|
|
438
445
|
this.on('close', function() {
|
|
439
446
|
node.status({});
|
|
440
447
|
});
|
package/locales/de/messages.json
CHANGED
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
"scope": {
|
|
100
100
|
"all": "allen Nodes",
|
|
101
|
+
"group": "in gleicher Gruppe",
|
|
101
102
|
"selected": "ausgewählten Nodes"
|
|
102
103
|
}
|
|
103
104
|
},
|
|
@@ -110,6 +111,7 @@
|
|
|
110
111
|
},
|
|
111
112
|
"scope": {
|
|
112
113
|
"all": "allen Nodes",
|
|
114
|
+
"group": "in gleicher Gruppe",
|
|
113
115
|
"selected": "ausgewählten Nodes"
|
|
114
116
|
}
|
|
115
117
|
},
|
|
@@ -214,7 +216,8 @@
|
|
|
214
216
|
"initialize": "Start",
|
|
215
217
|
"finalize": "Stopp",
|
|
216
218
|
"outputs": "Ausgänge",
|
|
217
|
-
"modules": "Module"
|
|
219
|
+
"modules": "Module",
|
|
220
|
+
"timeout": "Timeout"
|
|
218
221
|
},
|
|
219
222
|
"text": {
|
|
220
223
|
"initialize": "// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\n",
|
|
@@ -362,6 +365,7 @@
|
|
|
362
365
|
"port": "Port",
|
|
363
366
|
"keepalive": "Keep-Alive",
|
|
364
367
|
"cleansession": "Bereinigte Sitzung (clean session) verwenden",
|
|
368
|
+
"autoUnsubscribe": "Abonnement bei Verbindungsende automatisch beenden",
|
|
365
369
|
"cleanstart": "Verwende bereinigten Start",
|
|
366
370
|
"use-tls": "TLS",
|
|
367
371
|
"tls-config": "TLS-Konfiguration",
|
|
@@ -36,5 +36,5 @@ greater than one day you should consider using a scheduler node that can cope wi
|
|
|
36
36
|
<p><b>Note</b>: The <i>"Interval between times"</i> and <i>"at a specific time"</i> options use the standard cron system.
|
|
37
37
|
This means that 20 minutes will be at the next hour, 20 minutes past and 40 minutes past - not in 20 minutes time.
|
|
38
38
|
If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
|
|
39
|
-
<p><b>Note</b>: To include a newline in a string you must use
|
|
39
|
+
<p><b>Note</b>: To include a newline in a string you must use the Function or Template node to create the payload.</p>
|
|
40
40
|
</script>
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
},
|
|
95
95
|
"catch": {
|
|
96
96
|
"catch": "catch: all",
|
|
97
|
+
"catchGroup": "catch: group",
|
|
97
98
|
"catchNodes": "catch: __number__",
|
|
98
99
|
"catchUncaught": "catch: uncaught",
|
|
99
100
|
"label": {
|
|
@@ -103,11 +104,13 @@
|
|
|
103
104
|
},
|
|
104
105
|
"scope": {
|
|
105
106
|
"all": "all nodes",
|
|
107
|
+
"group": "in same group",
|
|
106
108
|
"selected": "selected nodes"
|
|
107
109
|
}
|
|
108
110
|
},
|
|
109
111
|
"status": {
|
|
110
112
|
"status": "status: all",
|
|
113
|
+
"statusGroup": "status: group",
|
|
111
114
|
"statusNodes": "status: __number__",
|
|
112
115
|
"label": {
|
|
113
116
|
"source": "Report status from",
|
|
@@ -115,6 +118,7 @@
|
|
|
115
118
|
},
|
|
116
119
|
"scope": {
|
|
117
120
|
"all": "all nodes",
|
|
121
|
+
"group": "in same group",
|
|
118
122
|
"selected": "selected nodes"
|
|
119
123
|
}
|
|
120
124
|
},
|
|
@@ -248,7 +252,8 @@
|
|
|
248
252
|
"initialize": "On Start",
|
|
249
253
|
"finalize": "On Stop",
|
|
250
254
|
"outputs": "Outputs",
|
|
251
|
-
"modules": "Modules"
|
|
255
|
+
"modules": "Modules",
|
|
256
|
+
"timeout": "Timeout"
|
|
252
257
|
},
|
|
253
258
|
"text": {
|
|
254
259
|
"initialize": "// Code added here will be run once\n// whenever the node is started.\n",
|
|
@@ -414,6 +419,7 @@
|
|
|
414
419
|
"port": "Port",
|
|
415
420
|
"keepalive": "Keep Alive",
|
|
416
421
|
"cleansession": "Use clean session",
|
|
422
|
+
"autoUnsubscribe": "Automatically unsubscribe when disconnecting",
|
|
417
423
|
"cleanstart": "Use clean start",
|
|
418
424
|
"use-tls": "Use TLS",
|
|
419
425
|
"tls-config": "TLS Configuration",
|
package/locales/fr/messages.json
CHANGED
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
},
|
|
104
104
|
"scope": {
|
|
105
105
|
"all": "tous les noeuds",
|
|
106
|
+
"group": "dans le même groupe",
|
|
106
107
|
"selected": "noeuds sélectionnés"
|
|
107
108
|
}
|
|
108
109
|
},
|
|
@@ -115,6 +116,7 @@
|
|
|
115
116
|
},
|
|
116
117
|
"scope": {
|
|
117
118
|
"all": "tous les noeuds",
|
|
119
|
+
"group": "dans le même groupe",
|
|
118
120
|
"selected": "noeuds sélectionnés"
|
|
119
121
|
}
|
|
120
122
|
},
|
|
@@ -414,6 +416,7 @@
|
|
|
414
416
|
"port": "Port",
|
|
415
417
|
"keepalive": "Rester en vie",
|
|
416
418
|
"cleansession": "Utiliser une session propre",
|
|
419
|
+
"autoUnsubscribe": "Se désabonner automatiquement lors de la déconnexion",
|
|
417
420
|
"cleanstart": "Utiliser un démarrage propre",
|
|
418
421
|
"use-tls": "Utiliser TLS",
|
|
419
422
|
"tls-config": "Configuration TLS",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
<p>また、フロー開始の際に一度だけメッセージを送出させることもできます。</p>
|
|
31
31
|
<p>「<i>時間間隔</i>」に指定可能な値の最大値は、約596時間(もしくは24日)です。一日より長い間隔を扱いたい場合は、電源停止や再起動にも対応可能なスケジューラノードの利用を検討すると良いでしょう。</p>
|
|
32
32
|
<p><b>注</b>:「<i>指定した時間間隔、日時</i>」と「<i>指定した日時</i>」オプションは標準的なcronシステムを内部で利用します。したがって「20分」という指定は、その時点から20分後ではなく、毎時きっかり、20分、40分を意味します。現時刻から20分毎を指定するには「<i>指定した時間間隔</i>」オプションを用います。</p>
|
|
33
|
-
<p><b>注</b>: 文字列に改行を含めたい場合は、functionノードを使ってペイロードを設定してください。</p>
|
|
33
|
+
<p><b>注</b>: 文字列に改行を含めたい場合は、functionノードまたはtemplateノードを使ってペイロードを設定してください。</p>
|
|
34
34
|
</script>
|
package/locales/ja/messages.json
CHANGED
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
},
|
|
95
95
|
"catch": {
|
|
96
96
|
"catch": "catch: 全て",
|
|
97
|
+
"catchGroup": "catch: グループ",
|
|
97
98
|
"catchNodes": "catch: __number__",
|
|
98
99
|
"catchUncaught": "catch: 未補足",
|
|
99
100
|
"label": {
|
|
@@ -103,11 +104,13 @@
|
|
|
103
104
|
},
|
|
104
105
|
"scope": {
|
|
105
106
|
"all": "全てのノード",
|
|
107
|
+
"group": "同一グループ内",
|
|
106
108
|
"selected": "選択したノード"
|
|
107
109
|
}
|
|
108
110
|
},
|
|
109
111
|
"status": {
|
|
110
112
|
"status": "status: 全て",
|
|
113
|
+
"statusGroup": "status: グループ",
|
|
111
114
|
"statusNodes": "status: __number__",
|
|
112
115
|
"label": {
|
|
113
116
|
"source": "ステータス取得元",
|
|
@@ -115,6 +118,7 @@
|
|
|
115
118
|
},
|
|
116
119
|
"scope": {
|
|
117
120
|
"all": "全てのノード",
|
|
121
|
+
"group": "同一グループ内",
|
|
118
122
|
"selected": "選択したノード"
|
|
119
123
|
}
|
|
120
124
|
},
|
|
@@ -248,7 +252,8 @@
|
|
|
248
252
|
"initialize": "初期化処理",
|
|
249
253
|
"finalize": "終了処理",
|
|
250
254
|
"outputs": "出力数",
|
|
251
|
-
"modules": "モジュール"
|
|
255
|
+
"modules": "モジュール",
|
|
256
|
+
"timeout": "タイムアウト"
|
|
252
257
|
},
|
|
253
258
|
"text": {
|
|
254
259
|
"initialize": "// ここに記述したコードは、ノードをデプロイした時に\n// 一度だけ実行されます。\n",
|
|
@@ -414,6 +419,7 @@
|
|
|
414
419
|
"port": "ポート",
|
|
415
420
|
"keepalive": "キープアライブ時間",
|
|
416
421
|
"cleansession": "セッションの初期化",
|
|
422
|
+
"autoUnsubscribe": "切断時に購読を自動解除",
|
|
417
423
|
"cleanstart": "クリーンスタート",
|
|
418
424
|
"use-tls": "TLSを使用",
|
|
419
425
|
"tls-config": "TLS設定",
|
package/locales/ru/messages.json
CHANGED
|
@@ -212,7 +212,8 @@
|
|
|
212
212
|
"function": "Функция",
|
|
213
213
|
"initialize": "Настройка",
|
|
214
214
|
"finalize": "Закрытие",
|
|
215
|
-
"outputs": "Выходы"
|
|
215
|
+
"outputs": "Выходы",
|
|
216
|
+
"timeout":"Время ожидания"
|
|
216
217
|
},
|
|
217
218
|
"text": {
|
|
218
219
|
"initialize": "// Добавленный здесь код будет исполняться\n// однократно при развертывании узла.\n",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-red/nodes",
|
|
3
|
-
"version": "3.1.0-beta.
|
|
3
|
+
"version": "3.1.0-beta.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"cronosjs": "1.7.1",
|
|
28
28
|
"denque": "2.1.0",
|
|
29
29
|
"form-data": "4.0.0",
|
|
30
|
-
"fs-extra": "
|
|
31
|
-
"got": "
|
|
30
|
+
"fs-extra": "11.1.1",
|
|
31
|
+
"got": "12.6.0",
|
|
32
32
|
"hash-sum": "2.0.0",
|
|
33
33
|
"hpagent": "1.2.0",
|
|
34
34
|
"https-proxy-agent": "5.0.1",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"tough-cookie": "4.1.2",
|
|
45
45
|
"uuid": "9.0.0",
|
|
46
46
|
"ws": "7.5.6",
|
|
47
|
-
"xml2js": "0.
|
|
47
|
+
"xml2js": "0.6.0",
|
|
48
48
|
"iconv-lite": "0.6.3"
|
|
49
49
|
}
|
|
50
50
|
}
|