Merge branch 'dev' into export-module-info

This commit is contained in:
Nick O'Leary
2024-07-08 16:02:28 +01:00
committed by GitHub
290 changed files with 50494 additions and 6125 deletions

View File

@@ -227,34 +227,42 @@
name: {value:""},
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) {
if (!v || v.length === 0) { return true }
const errors = []
for (var i=0;i<v.length;i++) {
if (/^\${[^}]+}$/.test(v[i].v)) {
// Allow ${ENV_VAR} value
continue
}
if (/msg|flow|global/.test(v[i].vt)) {
if (!RED.utils.validatePropertyExpression(v[i].v)) {
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v });
errors.push(RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }))
}
} else if (v[i].vt === "jsonata") {
try{ jsonata(v[i].v); }
catch(e){
return RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message });
errors.push(RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message }))
}
} else if (v[i].vt === "json") {
try{ JSON.parse(v[i].v); }
catch(e){
return RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message });
errors.push(RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message }))
}
} else if (v[i].vt === "num"){
if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) {
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v });
errors.push(RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }))
}
}
}
if (errors.length > 0) {
return errors
}
return true;
}
},
repeat: {
value:"", validate: function(v, opt) {
if ((v === "") ||
(RED.validators.number(v) &&
(RED.validators.number()(v) &&
(v >= 0) && (v <= 2147483))) {
return true;
}
@@ -263,7 +271,7 @@
},
crontab: {value:""},
once: {value:false},
onceDelay: {value:0.1},
onceDelay: {value:0.1, validate: RED.validators.number(true)},
topic: {value:""},
payload: {value:"", validate: RED.validators.typedInput("payloadType", false) },
payloadType: {value:"date"},

View File

@@ -378,7 +378,7 @@
return { id: id, label: RED.nodes.workspace(id).label } //flow id + name
} else {
const instanceNode = RED.nodes.node(id)
const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name)
const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8))?.name || instanceNode.type)
return { id: id, label: pathLabel }
}
})

View File

@@ -194,27 +194,46 @@
nodeMap[node.links[i]].new = true;
}
}
var n;
for (var id in nodeMap) {
let editHistories = []
let n;
for (let id in nodeMap) {
if (nodeMap.hasOwnProperty(id)) {
n = RED.nodes.node(id);
if (n) {
editHistories.push({
t:'edit',
node: n,
changes: {
links: [...n.links]
},
changed: n.changed
})
if (nodeMap[id].old && !nodeMap[id].new) {
// Removed id
i = n.links.indexOf(node.id);
if (i > -1) {
n.links.splice(i,1);
n.changed = true
n.dirty = true
}
} else if (!nodeMap[id].old && nodeMap[id].new) {
// Added id
i = n.links.indexOf(id);
if (i === -1) {
n.links.push(node.id);
n.changed = true
n.dirty = true
}
}
}
}
}
if (editHistories.length > 0) {
return {
history: editHistories
}
}
}
function onAdd() {
@@ -254,13 +273,14 @@
onEditPrepare(this,"link out");
},
oneditsave: function() {
onEditSave(this);
const result = onEditSave(this);
// In case the name has changed, ensure any link call nodes on this
// tab are redrawn with the updated name
var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"});
localCallNodes.forEach(function(node) {
node.dirty = true;
});
return result
},
onadd: onAdd,
oneditresize: resizeNodeList
@@ -329,7 +349,7 @@
onEditPrepare(this,"link in");
},
oneditsave: function() {
onEditSave(this);
return onEditSave(this);
},
oneditresize: resizeNodeList
});
@@ -373,7 +393,7 @@
},
oneditsave: function() {
onEditSave(this);
return onEditSave(this);
},
onadd: onAdd,
oneditresize: resizeNodeList

View File

@@ -25,19 +25,19 @@ module.exports = function(RED) {
function sendResults(node,send,_msgid,msgs,cloneFirstMessage) {
if (msgs == null) {
return;
} else if (!util.isArray(msgs)) {
} else if (!Array.isArray(msgs)) {
msgs = [msgs];
}
var msgCount = 0;
for (var m=0; m<msgs.length; m++) {
if (msgs[m]) {
if (!util.isArray(msgs[m])) {
if (!Array.isArray(msgs[m])) {
msgs[m] = [msgs[m]];
}
for (var n=0; n < msgs[m].length; n++) {
var msg = msgs[m][n];
if (msg !== null && msg !== undefined) {
if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) {
if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !Array.isArray(msg)) {
if (msgCount === 0 && cloneFirstMessage !== false) {
msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
msg = msgs[m][n];
@@ -47,7 +47,7 @@ module.exports = function(RED) {
} else {
var type = typeof msg;
if (type === 'object') {
type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
type = Buffer.isBuffer(msg)?'Buffer':(Array.isArray(msg)?'Array':'Date');
}
node.error(RED._("function.error.non-message-returned",{ type: type }));
}
@@ -374,7 +374,7 @@ module.exports = function(RED) {
iniOpt.breakOnSigint = true;
}
}
node.script = vm.createScript(functionText, createVMOpt(node, ""));
node.script = new vm.Script(functionText, createVMOpt(node, ""));
if (node.fin && (node.fin !== "")) {
var finText = `(function () {
var node = {
@@ -438,10 +438,9 @@ module.exports = function(RED) {
//store the error in msg to be used in flows
msg.error = err;
var line = 0;
var errorMessage;
if (stack.length > 0) {
let line = 0;
let errorMessage;
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++;
}
@@ -455,11 +454,13 @@ module.exports = function(RED) {
errorMessage += " (line "+lineno+", col "+cha+")";
}
}
if (errorMessage) {
err.message = errorMessage
}
}
if (!errorMessage) {
errorMessage = err.toString();
}
done(errorMessage);
// Pass the whole error object so any additional properties
// (such as cause) are preserved
done(err);
}
else if (typeof err === "string") {
done(err);

View File

@@ -233,9 +233,12 @@ module.exports = function(RED) {
// only replace if they match exactly
RED.util.setMessageProperty(msg,property,value);
} else {
// if target is boolean then just replace it
if (rule.tot === "bool") { current = value; }
else { current = current.replace(fromRE,value); }
current = current.replace(fromRE,value);
if (rule.tot === "bool" && current === ""+value) {
// If the target type is boolean, and the replace call has resulted in "true"/"false",
// convert to boolean type (which 'value' already is)
current = value
}
RED.util.setMessageProperty(msg,property,current);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {

View File

@@ -20,6 +20,7 @@ module.exports = function(RED) {
var exec = require('child_process').exec;
var fs = require('fs');
var isUtf8 = require('is-utf8');
const isWindows = process.platform === 'win32'
function ExecNode(n) {
RED.nodes.createNode(this,n);
@@ -85,9 +86,12 @@ module.exports = function(RED) {
}
});
var cmd = arg.shift();
// Since 18.20.2/20.12.2, it is invalid to call spawn on Windows with a .bat/.cmd file
// without using shell: true.
const opts = isWindows ? { ...node.spawnOpt, shell: true } : node.spawnOpt
/* istanbul ignore else */
node.debug(cmd+" ["+arg+"]");
child = spawn(cmd,arg,node.spawnOpt);
child = spawn(cmd,arg,opts);
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
var unknownCommand = (child.pid === undefined);
if (node.timer !== 0) {

View File

@@ -16,6 +16,7 @@
module.exports = function(RED) {
"use strict";
const { getProxyForUrl } = require('./lib/proxyHelper');
var mqtt = require("mqtt");
var isUtf8 = require('is-utf8');
var HttpsProxyAgent = require('https-proxy-agent');
@@ -617,17 +618,8 @@ module.exports = function(RED) {
// Only for ws or wss, check if proxy env var for additional configuration
if (node.brokerurl.indexOf("wss://") > -1 || node.brokerurl.indexOf("ws://") > -1) {
// check if proxy is set in env
let prox, noprox, noproxy;
if (process.env.http_proxy) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
if (noprox) {
for (var i = 0; i < noprox.length; i += 1) {
if (node.brokerurl.indexOf(noprox[i].trim()) !== -1) { noproxy = true; }
}
}
if (prox && !noproxy) {
const prox = getProxyForUrl(node.brokerurl, RED.settings.proxyOptions);
if (prox) {
var parsedUrl = url.parse(node.brokerurl);
var proxyOpts = url.parse(prox);
// true for wss

View File

@@ -16,6 +16,7 @@
module.exports = async function(RED) {
"use strict";
const { getProxyForUrl, parseUrl } = require('./lib/proxyHelper');
const { got } = await import('got')
const {CookieJar} = require("tough-cookie");
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
@@ -101,18 +102,18 @@ in your Node-RED user directory (${RED.settings.userDir}).
node.insecureHTTPParser = n.insecureHTTPParser
var prox, noprox;
if (process.env.http_proxy) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
var proxyConfig = null;
if (n.proxy) {
proxyConfig = RED.nodes.getNode(n.proxy);
prox = proxyConfig.url;
noprox = proxyConfig.noproxy;
let proxyConfig = n.proxy ? RED.nodes.getNode(n.proxy) || {} : null
const getProxy = (url) => {
const proxyOptions = Object.assign({}, RED.settings.proxyOptions);
if (n.proxy && proxyConfig) {
proxyOptions.env = {
no_proxy: (proxyConfig.noproxy || []).join(','),
http_proxy: (proxyConfig.url)
}
}
return getProxyForUrl(url, proxyOptions)
}
let prox = getProxy(nodeUrl || '')
let timingLog = false;
if (RED.settings.hasOwnProperty("httpRequestTimingLog")) {
@@ -141,7 +142,15 @@ in your Node-RED user directory (${RED.settings.userDir}).
});
}
}
/**
* @param {Object} headersObject
* @param {string} name
* @return {any} value
*/
const getHeaderValue = (headersObject, name) => {
const asLowercase = name.toLowercase();
return headersObject[Object.keys(headersObject).find(k => k.toLowerCase() === asLowercase)];
}
this.on("input",function(msg,nodeSend,nodeDone) {
checkNodeAgentPatch();
//reset redirectList on each request
@@ -177,7 +186,11 @@ in your Node-RED user directory (${RED.settings.userDir}).
url = "http://"+url;
}
}
// before any parameters are appended to the `url`, lets check see if the proxy needs a refresh
let proxyUrl = prox; // The proxyUrl determined for `nodeUrl`
if(url !== nodeUrl) {
proxyUrl = getProxy(url)
}
// The Request module used in Node-RED 1.x was tolerant of query strings that
// were partially encoded. For example - "?a=hello%20there&b=20%"
// The GOT module doesn't like that.
@@ -521,49 +534,43 @@ in your Node-RED user directory (${RED.settings.userDir}).
opts.headers[clSet] = opts.headers['content-length'];
delete opts.headers['content-length'];
}
var noproxy;
if (noprox) {
for (var i = 0; i < noprox.length; i += 1) {
if (url.indexOf(noprox[i]) !== -1) { noproxy=true; }
}
if (!opts.headers.hasOwnProperty('user-agent')) {
opts.headers['user-agent'] = 'Mozilla/5.0 (Node-RED)';
}
if (prox && !noproxy) {
var match = prox.match(/^(https?:\/\/)?(.+)?:([0-9]+)?/i);
if (proxyUrl) {
const match = proxyUrl.match(/^(https?:\/\/)?(.+)?:([0-9]+)?/i);
if (match) {
let proxyAgent;
let proxyURL = new URL(prox);
const proxyURL = parseUrl(proxyUrl)
//set username/password to null to stop empty creds header
/** @type {import('hpagent').HttpProxyAgentOptions} */
let proxyOptions = {
proxy: {
protocol: proxyURL.protocol,
hostname: proxyURL.hostname,
port: proxyURL.port,
username: null,
password: null
},
proxy: proxyUrl,
scheduling: 'lifo',
maxFreeSockets: 256,
maxSockets: 256,
keepAlive: true
}
if (proxyConfig && proxyConfig.credentials) {
let proxyUsername = proxyConfig.credentials.username || '';
let proxyPassword = proxyConfig.credentials.password || '';
const proxyUsername = proxyConfig.credentials.username || '';
const proxyPassword = proxyConfig.credentials.password || '';
if (proxyUsername || proxyPassword) {
proxyOptions.proxy = proxyURL
proxyOptions.proxy.username = proxyUsername;
proxyOptions.proxy.password = proxyPassword;
}
} else if (proxyURL.username || proxyURL.password){
proxyOptions.proxy.username = proxyURL.username;
proxyOptions.proxy.password = proxyURL.password;
proxyOptions.proxy = proxyURL
// proxyOptions.proxy.username = proxyURL.username;
// proxyOptions.proxy.password = proxyURL.password;
}
//need both incase of http -> https redirect
opts.agent = {
http: new HttpProxyAgent(proxyOptions),
https: new HttpsProxyAgent(proxyOptions)
}
https: new HttpProxyAgent(proxyOptions)
};
} else {
node.warn("Bad proxy url: "+ prox);
node.warn("Bad proxy url: " + proxyUrl);
}
}
if (useKeepAlive && !opts.agent) {

View File

@@ -40,6 +40,99 @@
(function() {
const headerTypes = [
/*
{ value: "Accept", label: "Accept", hasValue: false },
{ value: "Accept-Encoding", label: "Accept-Encoding", hasValue: false },
{ value: "Accept-Language", label: "Accept-Language", hasValue: false },
*/
{ value: "Authorization", label: "Authorization", hasValue: false },
/*
{ value: "Content-Type", label: "Content-Type", hasValue: false },
{ value: "Cache-Control", label: "Cache-Control", hasValue: false },
*/
{ value: "User-Agent", label: "User-Agent", hasValue: false },
/*
{ value: "Location", label: "Location", hasValue: false },
*/
{ value: "other", label: RED._("node-red:httpin.label.other"),
hasValue: true, icon: "red/images/typedInput/az.svg" },
]
const headerOptions = {};
const defaultOptions = [
{ value: "other", label: RED._("node-red:httpin.label.other"),
hasValue: true, icon: "red/images/typedInput/az.svg" },
"env",
];
/*
headerOptions["accept"] = [
{ value: "text/plain", label: "text/plain", hasValue: false },
{ value: "text/html", label: "text/html", hasValue: false },
{ value: "application/json", label: "application/json", hasValue: false },
{ value: "application/xml", label: "application/xml", hasValue: false },
...defaultOptions,
];
headerOptions["accept-encoding"] = [
{ value: "gzip", label: "gzip", hasValue: false },
{ value: "deflate", label: "deflate", hasValue: false },
{ value: "compress", label: "compress", hasValue: false },
{ value: "br", label: "br", hasValue: false },
{ value: "gzip, deflate", label: "gzip, deflate", hasValue: false },
{ value: "gzip, deflate, br", label: "gzip, deflate, br", hasValue: false },
...defaultOptions,
];
headerOptions["accept-language"] = [
{ value: "*", label: "*", hasValue: false },
{ value: "en-GB, en-US, en;q=0.9", label: "en-GB, en-US, en;q=0.9", hasValue: false },
{ value: "de-AT, de-DE;q=0.9, en;q=0.5", label: "de-AT, de-DE;q=0.9, en;q=0.5", hasValue: false },
{ value: "es-mx,es,en;q=0.5", label: "es-mx,es,en;q=0.5", hasValue: false },
{ value: "fr-CH, fr;q=0.9, en;q=0.8", label: "fr-CH, fr;q=0.9, en;q=0.8", hasValue: false },
{ value: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", label: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", hasValue: false },
{ value: "ja-JP, jp", label: "ja-JP, jp", hasValue: false },
...defaultOptions,
];
headerOptions["content-type"] = [
{ value: "text/css", label: "text/css", hasValue: false },
{ value: "text/plain", label: "text/plain", hasValue: false },
{ value: "text/html", label: "text/html", hasValue: false },
{ value: "application/json", label: "application/json", hasValue: false },
{ value: "application/octet-stream", label: "application/octet-stream", hasValue: false },
{ value: "application/pdf", label: "application/pdf", hasValue: false },
{ value: "application/xml", label: "application/xml", hasValue: false },
{ value: "application/zip", label: "application/zip", hasValue: false },
{ value: "multipart/form-data", label: "multipart/form-data", hasValue: false },
{ value: "audio/aac", label: "audio/aac", hasValue: false },
{ value: "audio/ac3", label: "audio/ac3", hasValue: false },
{ value: "audio/basic", label: "audio/basic", hasValue: false },
{ value: "audio/mp4", label: "audio/mp4", hasValue: false },
{ value: "audio/ogg", label: "audio/ogg", hasValue: false },
{ value: "image/bmp", label: "image/bmp", hasValue: false },
{ value: "image/gif", label: "image/gif", hasValue: false },
{ value: "image/jpeg", label: "image/jpeg", hasValue: false },
{ value: "image/png", label: "image/png", hasValue: false },
{ value: "image/tiff", label: "image/tiff", hasValue: false },
...defaultOptions,
];
headerOptions["cache-control"] = [
{ value: "max-age=0", label: "max-age=0", hasValue: false },
{ value: "max-age=86400", label: "max-age=86400", hasValue: false },
{ value: "no-cache", label: "no-cache", hasValue: false },
...defaultOptions,
];
*/
headerOptions["user-agent"] = [
{ value: "Mozilla/5.0", label: "Mozilla/5.0", hasValue: false },
...defaultOptions,
];
function getHeaderOptions(headerName) {
const lc = (headerName || "").toLowerCase();
let opts = headerOptions[lc];
return opts || defaultOptions;
}
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#node-input-mode").on("change", function() {
@@ -192,7 +285,8 @@
value: "",
label:RED._("node-red:websocket.sendheartbeat"),
validate: RED.validators.number(/*blank allowed*/true) },
subprotocol: {value:"",required: false}
subprotocol: {value:"",required: false},
headers: { value: [] }
},
inputs:0,
outputs:0,
@@ -200,6 +294,9 @@
return this.path;
},
oneditprepare: function() {
const node = this;
$("#node-config-input-path").on("change keyup paste",function() {
$(".node-config-row-tls").toggle(/^wss:/i.test($(this).val()))
});
@@ -214,14 +311,114 @@
if (!heartbeatActive) {
$("#node-config-input-hb").val("");
}
const hasMatch = function (arr, value) {
return arr.some(function (ht) {
return ht.value === value
});
}
const headerList = $("#node-input-headers-container").css('min-height', '150px').css('min-width', '450px').editableList({
addItem: function (container, i, header) {
const row = $('<div/>').css({
overflow: 'hidden',
whiteSpace: 'nowrap',
display: 'flex'
}).appendTo(container);
const propertNameCell = $('<div/>').css({ 'flex-grow': 1 }).appendTo(row);
const propertyName = $('<input/>', { class: "node-input-header-name", type: "text", style: "width: 100%" })
.appendTo(propertNameCell)
.typedInput({ types: headerTypes });
const propertyValueCell = $('<div/>').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row);
const propertyValue = $('<input/>', { class: "node-input-header-value", type: "text", style: "width: 100%" })
.appendTo(propertyValueCell)
.typedInput({
types: getHeaderOptions(header.keyType)
});
const setup = function(_header) {
const headerTypeIsAPreset = function(h) {return hasMatch(headerTypes, h) };
const headerValueIsAPreset = function(h, v) {return hasMatch(getHeaderOptions(h), v) };
const {keyType, keyValue, valueType, valueValue} = header;
if(keyType == "other") {
propertyName.typedInput('type', keyType);
propertyName.typedInput('value', keyValue);
} else if (headerTypeIsAPreset(keyType)) {
propertyName.typedInput('type', keyType);
} else {
propertyName.typedInput('type', "other");
propertyName.typedInput('value', keyValue);
}
if(valueType == "other" || valueType == "env" ) {
propertyValue.typedInput('type', valueType);
propertyValue.typedInput('value', valueValue);
} else if (headerValueIsAPreset(propertyName.typedInput('type'), valueType)) {
propertyValue.typedInput('type', valueType);
} else {
propertyValue.typedInput('type', "other");
propertyValue.typedInput('value', valueValue);
}
}
setup(header);
propertyName.on('change', function (event) {
propertyValue.typedInput('types', getHeaderOptions(propertyName.typedInput('type')));
});
},
sortable: true,
removable: true
});
if (node.headers) {
for (let index = 0; index < node.headers.length; index++) {
const element = node.headers[index];
headerList.editableList('addItem', node.headers[index]);
}
}
},
oneditsave: function() {
const node = this;
if (!/^wss:/i.test($("#node-config-input-path").val())) {
$("#node-config-input-tls").val("_ADD_");
}
if (!$("#node-config-input-hb-cb").prop("checked")) {
$("#node-config-input-hb").val("0");
}
const headers = $("#node-input-headers-container").editableList('items');
node.headers = [];
headers.each(function(i) {
const header = $(this);
const keyType = header.find(".node-input-header-name").typedInput('type');
const keyValue = header.find(".node-input-header-name").typedInput('value');
const valueType = header.find(".node-input-header-value").typedInput('type');
const valueValue = header.find(".node-input-header-value").typedInput('value');
node.headers.push({
keyType, keyValue, valueType, valueValue
})
});
},
oneditresize: function(size) {
const dlg = $("#dialog-form");
const expandRow = dlg.find('.node-input-headers-container-row');
let height = dlg.height() - 5;
if(expandRow && expandRow.length){
const siblingRows = dlg.find('> .form-row:not(.node-input-headers-container-row)');
for (let i = 0; i < siblingRows.size(); i++) {
const cr = $(siblingRows[i]);
if(cr.is(":visible"))
height -= cr.outerHeight(true);
}
$("#node-input-headers-container").editableList('height',height);
}
}
});
@@ -299,8 +496,15 @@
<span data-i18n="inject.seconds"></span>
</span>
</div>
<div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> <span data-i18n="httpin.label.headers"></span></label>
</div>
<div class="form-row node-input-headers-container-row">
<ol id="node-input-headers-container"></ol>
</div>
<div class="form-tips">
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
<span data-i18n="[html]websocket.tip.url2"></span>
<p><span data-i18n="[html]websocket.tip.url2"></span></p>
<span data-i18n="[html]websocket.tip.headers"></span>
</div>
</script>

View File

@@ -20,7 +20,7 @@ module.exports = function(RED) {
var inspect = require("util").inspect;
var url = require("url");
var HttpsProxyAgent = require('https-proxy-agent');
const { getProxyForUrl } = require('./lib/proxyHelper');
var serverUpgradeAdded = false;
function handleServerUpgrade(request, socket, head) {
@@ -58,6 +58,7 @@ module.exports = function(RED) {
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
node.closing = false;
node.tls = n.tls;
node.upgradeHeaders = n.headers
if (n.hb) {
var heartbeat = parseInt(n.hb);
@@ -68,21 +69,9 @@ module.exports = function(RED) {
function startconn() { // Connect to remote endpoint
node.tout = null;
var prox, noprox;
if (process.env.http_proxy) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
var noproxy = false;
if (noprox) {
for (var i in noprox) {
if (node.path.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
}
}
var agent = undefined;
if (prox && !noproxy) {
const prox = getProxyForUrl(node.brokerurl, RED.settings.proxyOptions);
let agent = undefined;
if (prox) {
agent = new HttpsProxyAgent(prox);
}
@@ -96,6 +85,42 @@ module.exports = function(RED) {
tlsNode.addTLSOptions(options);
}
}
// We need to check if undefined, to guard against previous installs, that will not have had this property set (applies to 3.1.x setups)
// Else this will be breaking potentially
if(node.upgradeHeaders !== undefined && node.upgradeHeaders.length > 0){
options.headers = {};
for(let i = 0;i<node.upgradeHeaders.length;i++){
const header = node.upgradeHeaders[i];
const keyType = header.keyType;
const keyValue = header.keyValue;
const valueType = header.valueType;
const valueValue = header.valueValue;
const headerName = keyType === 'other' ? keyValue : keyType;
let headerValue;
switch(valueType){
case 'other':
headerValue = valueValue;
break;
case 'env':
headerValue = RED.util.evaluateNodeProperty(valueValue,valueType,node);
break;
default:
headerValue = valueType;
break;
}
if(headerName && headerValue){
options.headers[headerName] = headerValue
}
}
}
var socket = new ws(node.path,node.subprotocol,options);
socket.setMaxListeners(0);
node.server = socket; // keep for closing

View File

@@ -1,257 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var mqtt = require("mqtt");
var events = require("events");
util.log("[warn] nodes/core/io/lib/mqtt.js is deprecated and will be removed in a future release of Node-RED. Please report this usage to the Node-RED mailing list.");
//var inspect = require("util").inspect;
//var Client = module.exports.Client = function(
var port = 1883;
var host = "localhost";
function MQTTClient(port,host) {
this.port = port||1883;
this.host = host||"localhost";
this.messageId = 1;
this.pendingSubscriptions = {};
this.inboundMessages = {};
this.lastOutbound = (new Date()).getTime();
this.lastInbound = (new Date()).getTime();
this.connected = false;
this._nextMessageId = function() {
this.messageId += 1;
if (this.messageId > 0xFFFF) {
this.messageId = 1;
}
return this.messageId;
}
events.EventEmitter.call(this);
}
util.inherits(MQTTClient, events.EventEmitter);
MQTTClient.prototype.connect = function(options) {
if (!this.connected) {
var self = this;
options = options||{};
self.options = options;
self.options.keepalive = options.keepalive||15;
self.options.clean = self.options.clean||true;
self.options.protocolId = 'MQIsdp';
self.options.protocolVersion = 3;
self.client = mqtt.createConnection(this.port,this.host,function(err,client) {
if (err) {
self.connected = false;
clearInterval(self.watchdog);
self.connectionError = true;
//util.log('[mqtt] ['+self.uid+'] connection error 1 : '+inspect(err));
self.emit('connectionlost',err);
return;
}
client.on('close',function(e) {
//util.log('[mqtt] ['+self.uid+'] on close');
clearInterval(self.watchdog);
if (!self.connectionError) {
if (self.connected) {
self.connected = false;
self.emit('connectionlost',e);
} else {
self.emit('disconnect');
}
}
});
client.on('error',function(e) {
//util.log('[mqtt] ['+self.uid+'] on error : '+inspect(e));
clearInterval(self.watchdog);
if (self.connected) {
self.connected = false;
self.emit('connectionlost',e);
}
});
client.on('connack',function(packet) {
if (packet.returnCode === 0) {
self.watchdog = setInterval(function(self) {
var now = (new Date()).getTime();
//util.log('[mqtt] ['+self.uid+'] watchdog '+inspect({connected:self.connected,connectionError:self.connectionError,pingOutstanding:self.pingOutstanding,now:now,lastOutbound:self.lastOutbound,lastInbound:self.lastInbound}));
if (now - self.lastOutbound > self.options.keepalive*500 || now - self.lastInbound > self.options.keepalive*500) {
if (self.pingOutstanding) {
//util.log('[mqtt] ['+self.uid+'] watchdog pingOustanding - disconnect');
try {
self.client.disconnect();
} catch (err) {
}
} else {
//util.log('[mqtt] ['+self.uid+'] watchdog pinging');
self.lastOutbound = (new Date()).getTime();
self.lastInbound = (new Date()).getTime();
self.pingOutstanding = true;
self.client.pingreq();
}
}
},self.options.keepalive*500,self);
self.pingOutstanding = false;
self.lastInbound = (new Date()).getTime()
self.lastOutbound = (new Date()).getTime()
self.connected = true;
self.connectionError = false;
self.emit('connect');
} else {
self.connected = false;
self.emit('connectionlost');
}
});
client.on('suback',function(packet) {
self.lastInbound = (new Date()).getTime()
var topic = self.pendingSubscriptions[packet.messageId];
self.emit('subscribe',topic,packet.granted[0]);
delete self.pendingSubscriptions[packet.messageId];
});
client.on('unsuback',function(packet) {
self.lastInbound = (new Date()).getTime()
var topic = self.pendingSubscriptions[packet.messageId];
self.emit('unsubscribe',topic);
delete self.pendingSubscriptions[packet.messageId];
});
client.on('publish',function(packet) {
self.lastInbound = (new Date()).getTime();
if (packet.qos < 2) {
var p = packet;
self.emit('message',p.topic,p.payload,p.qos,p.retain);
} else {
self.inboundMessages[packet.messageId] = packet;
this.lastOutbound = (new Date()).getTime()
self.client.pubrec(packet);
}
if (packet.qos == 1) {
this.lastOutbound = (new Date()).getTime()
self.client.puback(packet);
}
});
client.on('pubrel',function(packet) {
self.lastInbound = (new Date()).getTime()
var p = self.inboundMessages[packet.messageId];
if (p) {
self.emit('message',p.topic,p.payload,p.qos,p.retain);
delete self.inboundMessages[packet.messageId];
}
self.lastOutbound = (new Date()).getTime()
self.client.pubcomp(packet);
});
client.on('puback',function(packet) {
self.lastInbound = (new Date()).getTime()
// outbound qos-1 complete
});
client.on('pubrec',function(packet) {
self.lastInbound = (new Date()).getTime()
self.lastOutbound = (new Date()).getTime()
self.client.pubrel(packet);
});
client.on('pubcomp',function(packet) {
self.lastInbound = (new Date()).getTime()
// outbound qos-2 complete
});
client.on('pingresp',function(packet) {
//util.log('[mqtt] ['+self.uid+'] received pingresp');
self.lastInbound = (new Date()).getTime()
self.pingOutstanding = false;
});
this.lastOutbound = (new Date()).getTime()
this.connectionError = false;
client.connect(self.options);
});
}
}
MQTTClient.prototype.subscribe = function(topic,qos) {
var self = this;
if (self.connected) {
var options = {
subscriptions:[{topic:topic,qos:qos}],
messageId: self._nextMessageId()
};
this.pendingSubscriptions[options.messageId] = topic;
this.lastOutbound = (new Date()).getTime();
self.client.subscribe(options);
self.client.setPacketEncoding('binary');
}
}
MQTTClient.prototype.unsubscribe = function(topic) {
var self = this;
if (self.connected) {
var options = {
unsubscriptions:[topic],
messageId: self._nextMessageId()
};
this.pendingSubscriptions[options.messageId] = topic;
this.lastOutbound = (new Date()).getTime()
self.client.unsubscribe(options);
}
}
MQTTClient.prototype.publish = function(topic,payload,qos,retain) {
var self = this;
if (self.connected) {
if (!Buffer.isBuffer(payload)) {
if (typeof payload === "object") {
payload = JSON.stringify(payload);
} else if (typeof payload !== "string") {
payload = ""+payload;
}
}
var options = {
topic: topic,
payload: payload,
qos: qos||0,
retain:retain||false
};
if (options.qos !== 0) {
options.messageId = self._nextMessageId();
}
this.lastOutbound = (new Date()).getTime()
self.client.publish(options);
}
}
MQTTClient.prototype.disconnect = function() {
var self = this;
if (this.connected) {
this.connected = false;
try {
this.client.disconnect();
} catch(err) {
}
}
}
MQTTClient.prototype.isConnected = function() {
return this.connected;
}
module.exports.createClient = function(port,host) {
var mqtt_client = new MQTTClient(port,host);
return mqtt_client;
}

View File

@@ -0,0 +1,219 @@
/*
The MIT License
Copyright (C) 2016-2018 Rob Wu <rob@robwu.nl>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
/*
This proxy helper is heavily based on the proxy helper from Rob Wu as detailed above.
It has been modified to work with the Node-RED runtime environment.
The license for the original code is reproduced above.
*/
/**
* Parse a URL into its components.
* @param {String} url The URL to parse
* @returns {URL}
*/
const parseUrl = (url) => {
let parsedUrl = {
protocol: null,
host: null,
port: null,
hostname: null,
query: null,
href: null
}
try {
if (!url) { return parsedUrl }
parsedUrl = new URL(url)
} catch (error) {
// dont throw error
}
return parsedUrl
}
const DEFAULT_PORTS = {
ftp: 21,
gopher: 70,
http: 80,
https: 443,
ws: 80,
wss: 443,
mqtt: 1880,
mqtts: 8883
}
const modeOverride = getEnv('NR_PROXY_MODE', {})
/**
* @typedef {Object} ProxyOptions
* @property {'strict'|'legacy'} [mode] - Legacy mode is for non-strict previous proxy determination logic (for node-red <= v3.1 compatibility) (default 'strict')
* @property {boolean} [favourUpperCase] - Favour UPPER_CASE *_PROXY env vars (default false)
* @property {boolean} [lowerCaseOnly] - Prevent UPPER_CASE *_PROXY env vars being used. (default false)
* @property {boolean} [excludeNpm] - Prevent npm_config_*_proxy env vars being used. (default false)
* @property {object} [env] - The environment object to use (defaults to process.env)
*/
/**
* Get the proxy URL for a given URL.
* @param {string|URL} url - The URL, or the result from url.parse.
* @param {ProxyOptions} [options] - The options object (optional)
* @return {string} The URL of the proxy that should handle the request to the
* given URL. If no proxy is set, this will be an empty string.
*/
function getProxyForUrl(url, options) {
url = url || ''
const defaultOptions = {
mode: 'strict',
lowerCaseOnly: false,
favourUpperCase: false,
excludeNpm: false,
}
options = Object.assign({}, defaultOptions, options)
if (modeOverride === 'legacy' || modeOverride === 'strict') {
options.mode = modeOverride
}
if (options.mode === 'legacy') {
return legacyGetProxyForUrl(url, options.env || process.env)
}
const parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {}
let proto = parsedUrl.protocol
let hostname = parsedUrl.host
let port = parsedUrl.port
if (typeof hostname !== 'string' || !hostname || typeof proto !== 'string') {
return '' // Don't proxy URLs without a valid scheme or host.
}
proto = proto.split(':', 1)[0]
// Stripping ports in this way instead of using parsedUrl.hostname to make
// sure that the brackets around IPv6 addresses are kept.
hostname = hostname.replace(/:\d*$/, '')
port = parseInt(port) || DEFAULT_PORTS[proto] || 0
if (!shouldProxy(hostname, port, options)) {
return '' // Don't proxy URLs that match NO_PROXY.
}
let proxy =
getEnv('npm_config_' + proto + '_proxy', options) ||
getEnv(proto + '_proxy', options) ||
getEnv('npm_config_proxy', options) ||
getEnv('all_proxy', options)
if (proxy && proxy.indexOf('://') === -1) {
// Missing scheme in proxy, default to the requested URL's scheme.
proxy = proto + '://' + proxy
}
return proxy
}
/**
* Get the proxy URL for a given URL.
* For node-red < v3.1 or compatibility mode
* @param {string} url The URL to check for proxying
* @param {object} [env] The environment object to use (default process.env)
* @returns
*/
function legacyGetProxyForUrl(url, env) {
env = env || process.env
let prox, noprox;
if (env.http_proxy) { prox = env.http_proxy; }
if (env.HTTP_PROXY) { prox = env.HTTP_PROXY; }
if (env.no_proxy) { noprox = env.no_proxy.split(","); }
if (env.NO_PROXY) { noprox = env.NO_PROXY.split(","); }
let noproxy = false;
if (noprox) {
for (let i in noprox) {
if (url.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
}
}
if (prox && !noproxy) {
return prox
}
return ""
}
/**
* Determines whether a given URL should be proxied.
*
* @param {string} hostname - The host name of the URL.
* @param {number} port - The effective port of the URL.
* @returns {boolean} Whether the given URL should be proxied.
* @private
*/
function shouldProxy(hostname, port, options) {
const NO_PROXY =
(getEnv('npm_config_no_proxy', options) || getEnv('no_proxy', options)).toLowerCase()
if (!NO_PROXY) {
return true // Always proxy if NO_PROXY is not set.
}
if (NO_PROXY === '*') {
return false // Never proxy if wildcard is set.
}
return NO_PROXY.split(/[,\s]/).every(function (proxy) {
if (!proxy) {
return true // Skip zero-length hosts.
}
const parsedProxy = proxy.match(/^(.+):(\d+)$/)
let parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy
const parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0
if (parsedProxyPort && parsedProxyPort !== port) {
return true // Skip if ports don't match.
}
if (!/^[.*]/.test(parsedProxyHostname)) {
// No wildcards, so stop proxying if there is an exact match.
return hostname !== parsedProxyHostname
}
if (parsedProxyHostname.charAt(0) === '*') {
// Remove leading wildcard.
parsedProxyHostname = parsedProxyHostname.slice(1)
}
// Stop proxying if the hostname ends with the no_proxy host.
return !hostname.endsWith(parsedProxyHostname)
})
}
/**
* Get the value for an environment constiable.
*
* @param {string} key - The name of the environment constiable.
* @param {ProxyOptions} options - The name of the environment constiable.
* @return {string} The value of the environment constiable.
* @private
*/
function getEnv(key, options) {
const env = (options && options.env) || process.env
if (options && options.excludeNpm === true) {
if (key.startsWith('npm_config_')) {
return ''
}
}
if (options && options.lowerCaseOnly === true) {
return env[key.toLowerCase()] || ''
} else if (options && options.favourUpperCase === true) {
return env[key.toUpperCase()] || env[key.toLowerCase()] || ''
}
return env[key.toLowerCase()] || env[key.toUpperCase()] || ''
}
module.exports = {
getProxyForUrl,
parseUrl
}

View File

@@ -516,7 +516,8 @@
"path1": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Empfänger (Listener) kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt.",
"path2": "Dieser Pfad ist relativ zu <code>__path__</code>.",
"url1": "URL sollte ws:&#47;&#47; oder wss:&#47;&#47; Schema verwenden und auf einen vorhandenen WebSocket-Listener verweisen.",
"url2": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt."
"url2": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt.",
"headers": "Header werden nur während des Protokollaktualisierungsmechanismus übermittelt, von HTTP auf das WS/WSS-Protokoll."
},
"status": {
"connected": "Verbunden __count__",

View File

@@ -586,7 +586,8 @@
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
"path2": "This path will be relative to <code>__path__</code>.",
"url1": "URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.",
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string.",
"headers": "Headers are only submitted during the Protocol upgrade mechanism, from HTTP to the WS/WSS Protocol."
},
"status": {
"connected": "connected __count__",

View File

@@ -103,7 +103,7 @@
<h4>Automatic mode</h4>
<p>Automatic mode uses the <code>parts</code> property of incoming messages to
determine how the sequence should be joined. This allows it to automatically
reverse the action of a <b>split</b> node.
reverse the action of a <b>split</b> node.</p>
<h4>Manual mode</h4>
<p>When configured to join in manual mode, the node is able to join sequences

View File

@@ -0,0 +1,3 @@
<script type="text/html" data-help-name="global-config">
<p>Un noeud pour contenir la configuration globale des flux.</p>
</script>

View File

@@ -94,6 +94,7 @@
},
"catch": {
"catch": "catch : tout",
"catchGroup": "catch: groupe",
"catchNodes": "catch : __number__",
"catchUncaught": "catch : non capturé",
"label": {
@@ -109,6 +110,7 @@
},
"status": {
"status": "statut : tout",
"statusGroup": "statut: groupe",
"statusNodes": "statut : __number__",
"label": {
"source": "Signaler l'état de",
@@ -250,7 +252,8 @@
"initialize": "Au démarrage",
"finalize": "À l'arrêt",
"outputs": "Sorties",
"modules": "Modules"
"modules": "Modules",
"timeout": "Délai d'attente"
},
"text": {
"initialize": "// Le code ajouté ici sera exécuté une fois\n// à chaque démarrage du noeud.\n",
@@ -583,7 +586,8 @@
"path1": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. L'écouteur peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON.",
"path2": "Ce chemin sera relatif à <code>__path__</code>.",
"url1": "L'URL doit utiliser le schéma ws:&#47;&#47; ou wss:&#47;&#47; et pointer vers un écouteur websocket existant.",
"url2": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. Le client peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON."
"url2": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. Le client peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON.",
"headers": "Les en-têtes ne sont soumis que lors du mécanisme de mise à niveau du protocole, de HTTP vers le protocole WS/WSS."
},
"status": {
"connected": "__count__ connecté",
@@ -846,7 +850,13 @@
"newline": "Nouvelle ligne",
"usestrings": "Analyser les valeurs numériques",
"include_empty_strings": "Inclure les chaînes vides",
"include_null_values": "Inclure les valeurs nulles"
"include_null_values": "Inclure les valeurs nulles",
"spec": "Analyseur"
},
"spec": {
"rfc": "RFC4180",
"legacy": "Hérité (Legacy)",
"legacy_warning": "Le mode hérité sera supprimé dans une prochaine version."
},
"placeholder": {
"columns": "noms de colonnes séparés par des virgules"
@@ -875,6 +885,7 @@
"once": "envoyer les en-têtes une fois, jusqu'à msg.reset"
},
"errors": {
"bad_template": "Colonnes du modèle mal formées.",
"csv_js": "Ce noeud ne gère que les chaînes CSV ou les objets js.",
"obj_csv": "Aucun modèle de colonnes spécifié pour l'objet -> CSV.",
"bad_csv": "Données CSV mal formées - sortie probablement corrompue."
@@ -884,12 +895,14 @@
"label": {
"select": "Sélecteur",
"output": "Sortie",
"in": "dans"
"in": "dans",
"prefix": "Nom de la propriété pour le contenu HTML"
},
"output": {
"html": "le contenu html des éléments",
"text": "uniquement le contenu textuel des éléments",
"attr": "un objet de n'importe quel attribut des éléments"
"attr": "un objet de n'importe quel attribut des éléments",
"compl": "un objet pour tous les attributs de tous les éléments ainsi que du contenu HTML"
},
"format": {
"single": "comme un seul message contenant un tableau",

View File

@@ -30,6 +30,8 @@
avant d'être envoyé.</p>
<p>Si <code>msg._session</code> n'est pas présent, la charge utile est
envoyé à <b>tous</b> les clients connectés.</p>
<p>En mode Répondre à, définir <code>msg.reset = true</code> réinitialisera la connexion
spécifiée par _session.id ou toutes les connexions si aucun _session.id n'est spécifié.</p>
<p><b>Remarque</b> : Sur certains systèmes, vous aurez peut-être besoin d'un accès root ou administrateur
pour accéder aux ports inférieurs à 1024.</p>
</script>
@@ -40,6 +42,8 @@
caractères renvoyés dans un tampon fixe, correspondant à un caractère spécifié avant de revenir,
attendre un délai fixe à partir de la première réponse, puis revenir, s'installer et attender les données, ou envoie puis ferme la connexion
immédiatement, sans attendre de réponse.</p>
<p>Dans le cas du mode veille (maintien de la connexion), vous pouvez envoyer <code>msg.reset = true</code> ou <code>msg.reset = "host:port"</code> pour forcer une interruption
de la connexion et une reconnexion automatique.</p>
<p>La réponse sortira dans <code>msg.payload</code> en tant que tampon, vous pouvez alors utiliser la fonction .toString().</p>
<p>Si vous laissez l'hôte ou le port tcp vide, ils doivent être définis à l'aide des propriétés <code>msg.host</code> et <code>msg.port</code> dans chaque message envoyé au noeud.</ p>
</script>

View File

@@ -36,7 +36,9 @@
</dl>
<h3>Détails</h3>
<p>Le modèle de colonne peut contenir une liste ordonnée de noms de colonnes. Lors de la conversion de CSV en objet, les noms de colonne
seront utilisés comme noms de propriété. Alternativement, les noms de colonne peuvent être tirés de la première ligne du CSV.</p>
seront utilisés comme noms de propriété. Alternativement, les noms de colonne peuvent être tirés de la première ligne du CSV.
<p>Lorsque l'analyseur RFC est sélectionné, le modèle de colonne doit être conforme à la norme RFC4180.</p>
</p>
<p>Lors de la conversion au format CSV, le modèle de colonnes est utilisé pour identifier les propriétés à extraire de l'objet et dans quel ordre.</p>
<p>Si le modèle de colonnes est vide, vous pouvez utiliser une simple liste de propriétés séparées par des virgules fournies dans <code>msg.columns</code> pour
déterminer quoi extraire et dans quel ordre. Si ni l'un ni l'autre n'est présent, toutes les propriétés de l'objet sont sorties dans l'ordre

View File

@@ -1,3 +1,3 @@
<script type="text/html" data-help-name="global-config">
<p>大域的なフローの設定を保持するノード大域的な環境変数の定義を含みます</p>
</script>p
<p>大域的なフローの設定を保持するノードグローバル環境変数の定義を含みます</p>
</script>

View File

@@ -586,7 +586,8 @@
"path1": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
"path2": "このパスは <code>__path__</code> の相対パスになります。",
"url1": "URLには ws:&#47;&#47; または wss:&#47;&#47; スキーマを使用して、存在するwebsocketリスナを設定してください。",
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。"
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
"headers": "ヘッダーは、HTTP から WS/WSS プロトコルへのプロトコル アップグレード メカニズム中にのみ送信されます。"
},
"status": {
"connected": "接続数 __count__",
@@ -849,7 +850,13 @@
"newline": "改行コード",
"usestrings": "数値を変換する",
"include_empty_strings": "空の文字を含む",
"include_null_values": "null値を含む"
"include_null_values": "null値を含む",
"spec": "パーサ"
},
"spec": {
"rfc": "RFC4180",
"legacy": "従来",
"legacy_warning": "従来モードは将来のリリースで削除される予定です"
},
"placeholder": {
"columns": "コンマ区切りで列名を入力"
@@ -878,6 +885,7 @@
"once": "ヘッダを一度だけ送信する(msg.resetの受け付けると再送)"
},
"errors": {
"bad_template": "不正な列テンプレート",
"csv_js": "本ードが処理できる形式は、CSV文字列またはJSONのみです",
"obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません",
"bad_csv": "不正なCSVデータ - 出力の修正を試みました"
@@ -887,12 +895,14 @@
"label": {
"select": "抽出する要素",
"output": "出力",
"in": "対象:"
"in": "対象:",
"prefix": "HTMLコンテンツのプロパティ名"
},
"output": {
"html": "要素内のHTML",
"text": "要素のテキストのみ",
"attr": "要素の全ての属性"
"attr": "要素の全ての属性",
"compl": "要素やHTMLコンテンツの属性オブジェクト"
},
"format": {
"single": "配列化した1つのメッセージ",

View File

@@ -24,12 +24,14 @@
<p><code>msg.payload</code></p>
<p><code>msg.payload</code>Base64Base64</p>
<p><code>msg._session</code><b></b></p>
<p>応答モードでは<code>msg.reset = true</code>_session.id_session.id</p>
<p><b>: </b>1024rootadministrator</p>
</script>
<script type="text/html" data-help-name="tcp request">
<p>シンプルなTCPリクエストード<code>msg.payload</code>TCP</p>
<p>サーバに接続"リクエスト"送信"レスポンス"受信を行います固定長の文字数指定文字へのマッチ最初のリプライの到着から指定した時間待つデータの到着待ちデータ送信を行いリプライを待たず接続を即時解除などから動作を選択できます</p>
<p>待機モード(接続を維持)の場合は<code>msg.reset = true</code><code>msg.reset = "host:port"</code></p>
<p>レスポンスはバッファ形式で<code>msg.payload</code>.toString()使</p>
<p>TCPホストのポート番号設定を空にした場合本ノードに送信される全てのメッセージにおいて<code>msg.host</code><code>msg.port</code></p>
</script>

View File

@@ -34,7 +34,9 @@
</dd>
</dl>
<h3>詳細</h3>
<p>列名にカラム名のリストを指定することができますCSVからオブジェクトに変換を行う際カラム名をプロパティ名として使用します列名の代わりにCSVデータの1行目にカラム名を含めることもできます</p>
<p>列名にカラム名のリストを指定することができますCSVからオブジェクトに変換を行う際カラム名をプロパティ名として使用します列名の代わりにCSVデータの1行目にカラム名を含めることもできます
<p>RFCパーサが選択されている場合列のテンプレートはRFC4180に準拠する必要があります</p>
</p>
<p>CSVへの変換を行う際にはオブジェクトから取り出すべきプロパティとその順序を列名を参照して決めます</p>
<p>列名がない場合本ノードは<code>msg.columns</code>使</p>
<p>入力が配列の場合には列名はカラム名を表す行の出力指定がされた場合だけ用います</p>

View File

@@ -451,7 +451,8 @@
"path1": "표준으로는 <code>payload</code> 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
"path2": "This path will be relative to <code>__path__</code>.",
"url1": "URL에는 ws:&#47;&#47; 또는 wss:&#47;&#47; 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.",
"url2": "표준으로는 <code>payload</code> 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다."
"url2": "표준으로는 <code>payload</code> 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
"headers": "헤더는 HTTP에서 WS/WSS 프로토콜로 프로토콜 업그레이드 메커니즘 중에만 제출됩니다."
},
"status": {
"connected": "접속 수 __count__",

View File

@@ -573,7 +573,8 @@
"path1": "Por padrão, a <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O ouvinte pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON.",
"path2": "Este caminho será relativo a <code>__path__</code>.",
"url1": "A URL deve usar o esquema ws:&#47;&#47; ou wss:&#47;&#47; e apontar para um ouvinte de websocket existente.",
"url2": "Por padrão, <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O cliente pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON."
"url2": "Por padrão, <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O cliente pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON.",
"headers": "Os cabeçalhos são enviados apenas durante o mecanismo de atualização do protocolo, do HTTP para o protocolo WS/WSS."
},
"status": {
"connected": "conectado __count__",

View File

@@ -475,7 +475,8 @@
"path1": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Слушатель может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.",
"path2": "Путь будет относительно <code>__path__</code>.",
"url1": "URL должен использовать схему ws:&#47;&#47; или wss:&#47;&#47; и указывать на существующего слушателя websocket.",
"url2": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Клиент может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON."
"url2": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Клиент может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.",
"headers": "Заголовки передаются только во время механизма обновления протокола с HTTP на протокол WS/WSS."
},
"status": {
"connected": "подключен __count__",

View File

@@ -23,7 +23,7 @@
<dt class="optional">template <span class="property-type">string</span></dt>
<dd><code>msg.payload</code>msg</dd>
</dl>
<h3>Outputs</h3>
<h3>输出</h3>
<dl class="message-properties">
<dt>msg <span class="property-type">object</span></dt>
<dd>由来自传入msg的属性来填充已配置的模板后输出的带有属性的msg</dd>
@@ -32,7 +32,7 @@
<p>默认情况下使用<i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>格式如有需要也可以切换其他格式</p>
<p>例如:
<pre>Hello {{payload.name}}. Today is {{date}}</pre>
<p>receives a message containing:
<p>接收一条消息其中包含:
<pre>{
date: "Monday",
payload: {

View File

@@ -576,7 +576,8 @@
"path1": "默认情况下,<code>payload</code>将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
"path2": "这条路径将相对于 <code>__path__</code>.",
"url1": "URL 应该使用ws:&#47;&#47;或者wss:&#47;&#47;方案并指向现有的websocket侦听器.",
"url2": "默认情况下,<code>payload</code> 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象."
"url2": "默认情况下,<code>payload</code> 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象.",
"headers": "标头仅在协议升级机制期间提交,从 HTTP 到 WS/WSS 协议."
},
"status": {
"connected": "已连接数量 __count__",

View File

@@ -471,7 +471,8 @@
"path1": "預設情況下,<code>payload</code>將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.",
"path2": "這條路徑將相對於 <code>__path__</code>.",
"url1": "URL 應該使用ws:&#47;&#47;或者wss:&#47;&#47;方案並指向現有的websocket監聽器.",
"url2": "預設情況下,<code>payload</code> 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件."
"url2": "預設情況下,<code>payload</code> 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件.",
"headers": "標頭僅在協定升級機制期間提交,從 HTTP 到 WS/WSS 協定."
},
"status": {
"connected": "連接數 __count__",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "4.0.0-dev",
"version": "4.0.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -15,19 +15,19 @@
}
],
"dependencies": {
"acorn": "8.8.2",
"acorn-walk": "8.2.0",
"ajv": "8.12.0",
"acorn": "8.11.3",
"acorn-walk": "8.3.2",
"ajv": "8.14.0",
"body-parser": "1.20.2",
"cheerio": "1.0.0-rc.10",
"content-type": "1.0.5",
"cookie-parser": "1.4.6",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cors": "2.8.5",
"cronosjs": "1.7.1",
"denque": "2.1.0",
"form-data": "4.0.0",
"fs-extra": "11.1.1",
"fs-extra": "11.2.0",
"got": "12.6.0",
"hash-sum": "2.0.0",
"hpagent": "1.2.0",
@@ -35,15 +35,15 @@
"is-utf8": "0.2.1",
"js-yaml": "4.1.0",
"media-typer": "1.1.0",
"mqtt": "4.3.7",
"mqtt": "5.7.0",
"multer": "1.4.5-lts.1",
"mustache": "4.2.0",
"node-watch": "0.7.4",
"on-headers": "1.0.2",
"raw-body": "2.5.2",
"tough-cookie": "4.1.3",
"uuid": "9.0.0",
"ws": "7.5.6",
"tough-cookie": "4.1.4",
"uuid": "9.0.1",
"ws": "7.5.10",
"xml2js": "0.6.2",
"iconv-lite": "0.6.3"
}