1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'master' into dev

This commit is contained in:
Nick O'Leary 2022-02-18 22:02:30 +00:00
commit 0533c08438
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
24 changed files with 357 additions and 110 deletions

View File

@ -1,3 +1,35 @@
#### 2.2.2: Maintenance Release
Nodes
- Fix "close timed out" error when performing full deploy or modifying broker node. (#3451) @Steve-Mcl
#### 2.2.1: Maintenance Release
Editor
- Handle mixed-cased filter terms in keyboard shortcut dialog (#3444) @knolleary
- Prevent duplicate links being added between nodes (#3442) @knolleary
- Fix to hide tooltip after removing subflow tab (#3391) @HiroyasuNishiyama
- Fix node validation to be applied to config node (#3397) @HiroyasuNishiyama
- Fix: Dont add wires to undo buffer twice (#3437) @Steve-Mcl
Runtime
- Improve module location parsing (of stack info) when adding hook (#3447) @Steve-Mcl
- Fix substitution of NR_NODE_PATH (#3445) @HiroyasuNishiyama
- Remove console.log when ignoring disabled module (#3439) @knolleary
- Improve "Unexpected Node Error" logging (#3446) @Steve-Mcl
Nodes
- Debug: Fix no-prototype-builtins bug in debug node and utils (#3394) @Alkarex
- Delay: Fix Japanese message of delay node (#3434)
- Allow nbRateUnits to be undefined when validating (#3443) @knolleary
- Coding help for recently added node-red Predefined Environment Variables (#3440) @Steve-Mcl
#### 2.2.0: Milestone Release #### 2.2.0: Milestone Release
Editor Editor

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "2.2.0", "version": "2.2.2",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -28,7 +28,7 @@
"dependencies": { "dependencies": {
"acorn": "8.7.0", "acorn": "8.7.0",
"acorn-walk": "8.2.0", "acorn-walk": "8.2.0",
"ajv": "8.9.0", "ajv": "8.10.0",
"async-mutex": "0.3.2", "async-mutex": "0.3.2",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
@ -36,7 +36,7 @@
"cheerio": "1.0.0-rc.10", "cheerio": "1.0.0-rc.10",
"clone": "2.1.2", "clone": "2.1.2",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie": "0.4.1", "cookie": "0.4.2",
"cookie-parser": "1.4.6", "cookie-parser": "1.4.6",
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
@ -50,32 +50,32 @@
"hash-sum": "2.0.0", "hash-sum": "2.0.0",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"https-proxy-agent": "5.0.0", "https-proxy-agent": "5.0.0",
"i18next": "21.6.10", "i18next": "21.6.11",
"iconv-lite": "0.6.3", "iconv-lite": "0.6.3",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.14.1", "js-yaml": "3.14.1",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.5", "jsonata": "1.8.6",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0", "media-typer": "1.1.0",
"memorystore": "1.6.6", "memorystore": "1.6.7",
"mime": "3.0.0", "mime": "3.0.0",
"moment-timezone": "0.5.34", "moment-timezone": "0.5.34",
"mqtt": "4.3.4", "mqtt": "4.3.5",
"multer": "1.4.4", "multer": "1.4.4",
"mustache": "4.2.0", "mustache": "4.2.0",
"node-red-admin": "^2.2.2", "node-red-admin": "^2.2.3",
"nopt": "5.0.0", "nopt": "5.0.0",
"oauth2orize": "1.11.1", "oauth2orize": "1.11.1",
"on-headers": "1.0.2", "on-headers": "1.0.2",
"passport": "0.5.2", "passport": "0.5.2",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"raw-body": "2.4.2", "raw-body": "2.4.3",
"semver": "7.3.5", "semver": "7.3.5",
"tar": "6.1.11", "tar": "6.1.11",
"tough-cookie": "4.0.0", "tough-cookie": "4.0.0",
"uglify-js": "3.15.0", "uglify-js": "3.15.1",
"uuid": "8.3.2", "uuid": "8.3.2",
"ws": "7.5.6", "ws": "7.5.6",
"xml2js": "0.4.23" "xml2js": "0.4.23"
@ -84,7 +84,7 @@
"bcrypt": "5.0.1" "bcrypt": "5.0.1"
}, },
"devDependencies": { "devDependencies": {
"dompurify": "2.3.4", "dompurify": "2.3.5",
"grunt": "1.4.1", "grunt": "1.4.1",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",
"grunt-cli": "~1.4.3", "grunt-cli": "~1.4.3",
@ -113,11 +113,11 @@
"node-red-node-test-helper": "^0.2.7", "node-red-node-test-helper": "^0.2.7",
"nodemon": "2.0.15", "nodemon": "2.0.15",
"proxy": "^1.0.2", "proxy": "^1.0.2",
"sass": "1.49.0", "sass": "1.49.7",
"should": "13.2.3", "should": "13.2.3",
"sinon": "11.1.2", "sinon": "11.1.2",
"stoppable": "^1.1.0", "stoppable": "^1.1.0",
"supertest": "6.2.1" "supertest": "6.2.2"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "2.2.0", "version": "2.2.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,15 +16,15 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "2.2.0", "@node-red/util": "2.2.2",
"@node-red/editor-client": "2.2.0", "@node-red/editor-client": "2.2.2",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.19.1", "body-parser": "1.19.1",
"clone": "2.1.2", "clone": "2.1.2",
"cors": "2.8.5", "cors": "2.8.5",
"express-session": "1.17.2", "express-session": "1.17.2",
"express": "4.17.2", "express": "4.17.2",
"memorystore": "1.6.6", "memorystore": "1.6.7",
"mime": "3.0.0", "mime": "3.0.0",
"multer": "1.4.4", "multer": "1.4.4",
"mustache": "4.2.0", "mustache": "4.2.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-client", "name": "@node-red/editor-client",
"version": "2.2.0", "version": "2.2.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -600,6 +600,14 @@ RED.nodes = (function() {
RED.events.emit('nodes:add',n); RED.events.emit('nodes:add',n);
} }
function addLink(l) { function addLink(l) {
if (nodeLinks[l.source.id]) {
const isUnique = nodeLinks[l.source.id].out.every(function(link) {
return link.sourcePort !== l.sourcePort || link.target.id !== l.target.id
})
if (!isUnique) {
return
}
}
links.push(l); links.push(l);
if (l.source) { if (l.source) {
// Possible the node hasn't been added yet // Possible the node hasn't been added yet

View File

@ -350,6 +350,15 @@ RED.popover = (function() {
} }
} }
target.on("remove", function (ev) {
if (timer) {
clearTimeout(timer);
}
if (active) {
active = false;
setTimeout(closePopup,delay.hide);
}
});
if (trigger === 'hover') { if (trigger === 'hover') {
target.on('mouseenter',function(e) { target.on('mouseenter',function(e) {
clearTimeout(timer); clearTimeout(timer);

View File

@ -334,6 +334,9 @@ RED.deploy = (function() {
var invalidNodes = []; var invalidNodes = [];
RED.nodes.eachConfig(function(node) { RED.nodes.eachConfig(function(node) {
if (node.valid === undefined) {
RED.editor.validateNode(node);
}
if (!node.valid && !node.d) { if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node)); invalidNodes.push(getNodeInfo(node));
} }

View File

@ -577,7 +577,7 @@ RED.editor.codeEditor.monaco = (function() {
createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range), createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range), createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range), createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
createMonacoCompletionItem("get (env)", 'env.get("${1:name}");','Get env variable value',range), createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME|}");','Get env variable value',range),
createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});', createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
["```typescript", ["```typescript",
"RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T", "RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",

View File

@ -625,7 +625,7 @@ RED.keyboard = (function() {
pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({ pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({
delay: 100, delay: 100,
change: function() { change: function() {
var filterValue = $(this).val().trim(); var filterValue = $(this).val().trim().toLowerCase();
if (filterValue === "") { if (filterValue === "") {
shortcutList.editableList('filter', null); shortcutList.editableList('filter', null);
} else { } else {

View File

@ -2338,14 +2338,21 @@ RED.view = (function() {
var removedSubflowStatus; var removedSubflowStatus;
var subflowInstances = []; var subflowInstances = [];
var historyEvents = []; var historyEvents = [];
var addToRemovedLinks = function(links) {
if(!links) { return; }
var _links = Array.isArray(links) ? links : [links];
_links.forEach(function(l) {
removedLinks.push(l);
selectedLinks.remove(l);
})
}
if (reconnectWires) { if (reconnectWires) {
var reconnectResult = RED.nodes.detachNodes(movingSet.nodes()) var reconnectResult = RED.nodes.detachNodes(movingSet.nodes())
var addedLinks = reconnectResult.newLinks; var addedLinks = reconnectResult.newLinks;
if (addedLinks.length > 0) { if (addedLinks.length > 0) {
historyEvents.push({ t:'add', links: addedLinks }) historyEvents.push({ t:'add', links: addedLinks })
} }
removedLinks = removedLinks.concat(reconnectResult.removedLinks) addToRemovedLinks(reconnectResult.removedLinks)
} }
var startDirty = RED.nodes.dirty(); var startDirty = RED.nodes.dirty();
@ -2377,7 +2384,7 @@ RED.view = (function() {
var removedEntities = RED.nodes.remove(node.id); var removedEntities = RED.nodes.remove(node.id);
removedNodes.push(node); removedNodes.push(node);
removedNodes = removedNodes.concat(removedEntities.nodes); removedNodes = removedNodes.concat(removedEntities.nodes);
removedLinks = removedLinks.concat(removedEntities.links); addToRemovedLinks(removedNodes.removedLinks);
if (node.g) { if (node.g) {
var group = RED.nodes.group(node.g); var group = RED.nodes.group(node.g);
if (selectedGroups.indexOf(group) === -1) { if (selectedGroups.indexOf(group) === -1) {
@ -2410,20 +2417,20 @@ RED.view = (function() {
if (removedSubflowOutputs.length > 0) { if (removedSubflowOutputs.length > 0) {
result = RED.subflow.removeOutput(removedSubflowOutputs); result = RED.subflow.removeOutput(removedSubflowOutputs);
if (result) { if (result) {
removedLinks = removedLinks.concat(result.links); addToRemovedLinks(result.links);
} }
} }
// Assume 0/1 inputs // Assume 0/1 inputs
if (removedSubflowInputs.length == 1) { if (removedSubflowInputs.length == 1) {
result = RED.subflow.removeInput(); result = RED.subflow.removeInput();
if (result) { if (result) {
removedLinks = removedLinks.concat(result.links); addToRemovedLinks(result.links);
} }
} }
if (removedSubflowStatus) { if (removedSubflowStatus) {
result = RED.subflow.removeStatus(); result = RED.subflow.removeStatus();
if (result) { if (result) {
removedLinks = removedLinks.concat(result.links); addToRemovedLinks(result.links);
} }
} }

View File

@ -263,6 +263,20 @@ declare class global {
static keys(store: string, callback: Function); static keys(store: string, callback: Function);
} }
declare class env { declare class env {
/** Get an environment variable value */ /**
static get(name:string); * Get an environment variable value
*
* Predefined node-red variables...
* * `NR_NODE_ID` - the ID of the node
* * `NR_NODE_NAME` - the Name of the node
* * `NR_NODE_PATH` - the Path of the node
* * `NR_GROUP_ID` - the ID of the containing group
* * `NR_GROUP_NAME` - the Name of the containing group
* * `NR_FLOW_ID` - the ID of the flow the node is on
* * `NR_FLOW_NAME` - the Name of the flow the node is on
* @param name Name of the environment variable to get
* @example
* ```const flowName = env.get("NR_FLOW_NAME");```
*/
static get(name:string) :string;
} }

View File

@ -115,7 +115,7 @@
timeoutUnits: {value:"seconds"}, timeoutUnits: {value:"seconds"},
rate: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }}, rate: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
nbRateUnits: {value:"1", required:false, nbRateUnits: {value:"1", required:false,
validate:function(v) { return RED.validators.number(v) && (v >= 0); }}, validate:function(v) { return v === undefined || (RED.validators.number(v) && (v >= 0)); }},
rateUnits: {value: "second"}, rateUnits: {value: "second"},
randomFirst: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }}, randomFirst: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
randomLast: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }}, randomLast: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},

View File

@ -637,24 +637,8 @@ module.exports = function(RED) {
node.deregister = function(mqttNode,done) { node.deregister = function(mqttNode,done) {
delete node.users[mqttNode.id]; delete node.users[mqttNode.id];
if (node.closing) { if (!node.closing && node.connected && Object.keys(node.users).length === 0) {
return done(); node.disconnect();
}
if (Object.keys(node.users).length === 0) {
if (node.client && node.client.connected) {
// Send close message
if (node.closeMessage) {
node.publish(node.closeMessage,function(err) {
node.client.end(done);
});
} else {
node.client.end(done);
}
return;
} else {
if (node.client) { node.client.end(); }
return done();
}
} }
done(); done();
}; };
@ -663,6 +647,7 @@ module.exports = function(RED) {
} }
node.connect = function (callback) { node.connect = function (callback) {
if (node.canConnect()) { if (node.canConnect()) {
node.closing = false;
node.connecting = true; node.connecting = true;
setStatusConnecting(node, true); setStatusConnecting(node, true);
try { try {
@ -672,6 +657,7 @@ module.exports = function(RED) {
let callbackDone = false; //prevent re-connects causing node.client.on('connect' firing callback multiple times let callbackDone = false; //prevent re-connects causing node.client.on('connect' firing callback multiple times
// Register successful connect or reconnect handler // Register successful connect or reconnect handler
node.client.on('connect', function (connack) { node.client.on('connect', function (connack) {
node.closing = false;
node.connecting = false; node.connecting = false;
node.connected = true; node.connected = true;
if(!callbackDone && typeof callback == "function") { if(!callbackDone && typeof callback == "function") {
@ -740,6 +726,7 @@ module.exports = function(RED) {
reasonCode: rc, reasonCode: rc,
reasonString: rs reasonString: rs
} }
node.connected = false;
node.log(RED._("mqtt.state.broker-disconnected", details)); node.log(RED._("mqtt.state.broker-disconnected", details));
setStatusDisconnected(node, true); setStatusDisconnected(node, true);
}); });
@ -764,25 +751,31 @@ module.exports = function(RED) {
} }
}; };
node.disconnect = function (callback) { node.disconnect = function (callback) {
const _callback = function () { const _callback = function (resetNodeConnectedState) {
setStatusDisconnected(node, true); setStatusDisconnected(node, true);
node.connecting = false; if(resetNodeConnectedState) {
node.connected = false; node.closing = true;
node.connecting = false;
node.connected = false;
}
callback && typeof callback == "function" && callback(); callback && typeof callback == "function" && callback();
}; };
if(node.client) { if(node.closing) {
if(node.client.connected && node.closeMessage) { return _callback(false);
node.publish(node.closeMessage, function (err) { }
node.client.end(_callback); var endCallBack = function endCallBack() {
}); }
} else if(node.client.connected || node.client.reconnecting) { if(node.connected && node.closeMessage) {
node.client.end(_callback); node.publish(node.closeMessage, function (err) {
} else if(node.client.disconnecting || node.client.connected === false) { node.client.end(endCallBack);
_callback(); _callback(true);
} });
} else if(node.connected) {
node.client.end(endCallBack);
_callback(true);
} else { } else {
_callback(); _callback(false);
} }
} }
node.subscriptionIds = {}; node.subscriptionIds = {};
@ -1074,6 +1067,8 @@ module.exports = function(RED) {
node.brokerConn.unsubscribe(node.topic,node.id, removed); node.brokerConn.unsubscribe(node.topic,node.id, removed);
} }
node.brokerConn.deregister(node, done); node.brokerConn.deregister(node, done);
} else {
done();
} }
}); });
} else { } else {
@ -1134,7 +1129,11 @@ module.exports = function(RED) {
} }
node.brokerConn.register(node); node.brokerConn.register(node);
node.on('close', function(done) { node.on('close', function(done) {
node.brokerConn.deregister(node,done); if (node.brokerConn) {
node.brokerConn.deregister(node,done);
} else {
done();
}
}); });
} else { } else {
node.error(RED._("mqtt.errors.missing-config")); node.error(RED._("mqtt.errors.missing-config"));

View File

@ -291,8 +291,8 @@
"hour": "時間", "hour": "時間",
"days": "日", "days": "日",
"day": "日", "day": "日",
"between": "頻度", "between": "範囲",
"and": "回/", "and": "",
"rate": "流量", "rate": "流量",
"msgper": "メッセージ/", "msgper": "メッセージ/",
"queuemsg": "中間メッセージをキューに追加", "queuemsg": "中間メッセージをキューに追加",

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/nodes", "name": "@node-red/nodes",
"version": "2.2.0", "version": "2.2.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
@ -17,12 +17,12 @@
"dependencies": { "dependencies": {
"acorn": "8.7.0", "acorn": "8.7.0",
"acorn-walk": "8.2.0", "acorn-walk": "8.2.0",
"ajv": "8.9.0", "ajv": "8.10.0",
"body-parser": "1.19.1", "body-parser": "1.19.1",
"cheerio": "1.0.0-rc.10", "cheerio": "1.0.0-rc.10",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie-parser": "1.4.6", "cookie-parser": "1.4.6",
"cookie": "0.4.1", "cookie": "0.4.2",
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
"denque": "2.0.1", "denque": "2.0.1",
@ -36,11 +36,11 @@
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.14.1", "js-yaml": "3.14.1",
"media-typer": "1.1.0", "media-typer": "1.1.0",
"mqtt": "4.3.4", "mqtt": "4.3.5",
"multer": "1.4.4", "multer": "1.4.4",
"mustache": "4.2.0", "mustache": "4.2.0",
"on-headers": "1.0.2", "on-headers": "1.0.2",
"raw-body": "2.4.2", "raw-body": "2.4.3",
"tough-cookie": "4.0.0", "tough-cookie": "4.0.0",
"uuid": "8.3.2", "uuid": "8.3.2",
"ws": "7.5.6", "ws": "7.5.6",

View File

@ -353,7 +353,6 @@ async function loadPluginConfig(fileInfo) {
*/ */
function loadNodeSet(node) { function loadNodeSet(node) {
if (!node.enabled) { if (!node.enabled) {
console.log("BAIL ON",node.id)
return Promise.resolve(node); return Promise.resolve(node);
} else { } else {
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/registry", "name": "@node-red/registry",
"version": "2.2.0", "version": "2.2.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,11 +16,11 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "2.2.0", "@node-red/util": "2.2.2",
"clone": "2.1.2", "clone": "2.1.2",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"semver": "7.3.5", "semver": "7.3.5",
"tar": "6.1.11", "tar": "6.1.11",
"uglify-js": "3.15.0" "uglify-js": "3.15.1"
} }
} }

View File

@ -77,15 +77,16 @@ function createNode(flow,config) {
if (typeof nodeTypeConstructor === "function") { if (typeof nodeTypeConstructor === "function") {
var conf = clone(config); var conf = clone(config);
delete conf.credentials; delete conf.credentials;
for (var p in conf) {
if (conf.hasOwnProperty(p)) {
mapEnvVarProperties(conf,p,flow,conf);
}
}
try { try {
Object.defineProperty(conf,'_module', {value: typeRegistry.getNodeInfo(type), enumerable: false, writable: true }) Object.defineProperty(conf,'_module', {value: typeRegistry.getNodeInfo(type), enumerable: false, writable: true })
Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true }) Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true })
Object.defineProperty(conf,'_path', {value: `${flow.path}/${config._alias||config.id}`, enumerable: false, writable: true }) Object.defineProperty(conf,'_path', {value: `${flow.path}/${config._alias||config.id}`, enumerable: false, writable: true })
for (var p in conf) {
if (conf.hasOwnProperty(p)) {
mapEnvVarProperties(conf,p,flow,conf);
}
}
newNode = new nodeTypeConstructor(conf); newNode = new nodeTypeConstructor(conf);
} catch (err) { } catch (err) {
Log.log({ Log.log({

View File

@ -503,10 +503,25 @@ function log_helper(self, level, msg) {
o.name = self.name; o.name = self.name;
} }
// See https://github.com/node-red/node-red/issues/3327 // See https://github.com/node-red/node-red/issues/3327
// See https://github.com/node-red/node-red/issues/3389
let srcError;
if (msg instanceof Error) {
srcError = msg;//use existing err object for actual stack
} else {
srcError = new Error(msg);//generate a new error for generate a stack
}
try { try {
self._flow.log(o); if(self instanceof Node && self._flow) {
self._flow.log(o);
} else {
//if self._flow is not present, this is not a node-red Node
//Set info to "Node object is not a node-red Node" to point out the `Node type` problem in log
logUnexpectedError(self, srcError, "Node object is not a node-red Node")
}
} catch(err) { } catch(err) {
logUnexpectedError(self, err) //build an unexpected error report indicating using the original error (for better stack trace)
logUnexpectedError(self, srcError, `An error occured attempting to make a log entry: ${err}`)
} }
} }
/** /**
@ -531,7 +546,7 @@ Node.prototype.error = function(logMessage,msg) {
logMessage = logMessage || ""; logMessage = logMessage || "";
} }
var handled = false; var handled = false;
if (msg && typeof msg === 'object') { if (this._flow && msg && typeof msg === 'object') {
handled = this._flow.handleError(this,logMessage,msg); handled = this._flow.handleError(this,logMessage,msg);
} }
if (!handled) { if (!handled) {
@ -619,27 +634,34 @@ function inspectObject(flow) {
} }
} }
function logUnexpectedError(node, error) { function logUnexpectedError(node, error, info) {
let moduleInfo = node._module?`${node._module.module}@${node._module.version}`:"undefined" const header = `
Log.error(`
******************************************************************** ********************************************************************
Unexpected Node Error Unexpected Node Error
${error.stack} ********************************************************************`;
Node:
Type: ${node.type}
Module: ${moduleInfo}
ID: ${node._alias||node.id}
Properties:
${inspectObject(node)}
Flow: ${node._flow?node._flow.path:'undefined'}
Type: ${node._flow?node._flow.TYPE:'undefined'}
Properties:
${node._flow?inspectObject(node._flow):'undefined'}
const footer = `
Please report this issue, including the information logged above: Please report this issue, including the information logged above:
https://github.com/node-red/node-red/issues/ https://github.com/node-red/node-red/issues/
******************************************************************** ********************************************************************`;
`)
let detail = [`Info:\n ${info || 'No additional info'}`];
//Include Error info?
if(error && error.stack){
detail.push(`Stack:\n ${error.stack}`)
}
//Include Node info?
if(node && (node._module || node.type)){
const moduleInfo = node._module?`${node._module.module}@${node._module.version}`:"undefined";
const id = node._alias||node.id||"undefined";
detail.push(`Node:\n Type: ${node.type}\n Module: ${moduleInfo}\n ID: ${id}\n Properties:\n ${inspectObject(node)}`)
}
//Include Flow info?
if(node && node._flow){
detail.push(`Flow: ${node._flow.path}\n Type: ${node._flow.TYPE}\n Properties:\n ${inspectObject(node._flow)}`)
}
Log.error(`${header}\n${detail.join("\n")}\n${footer}`);
} }
module.exports = Node; module.exports = Node;

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/runtime", "name": "@node-red/runtime",
"version": "2.2.0", "version": "2.2.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,8 +16,8 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/registry": "2.2.0", "@node-red/registry": "2.2.2",
"@node-red/util": "2.2.0", "@node-red/util": "2.2.2",
"async-mutex": "0.3.2", "async-mutex": "0.3.2",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.17.2", "express": "4.17.2",

View File

@ -67,8 +67,25 @@ function add(hookId, callback) {
throw new Error("Hook "+hookId+" already registered") throw new Error("Hook "+hookId+" already registered")
} }
// Get location of calling code // Get location of calling code
let callModule;
const stack = new Error().stack; const stack = new Error().stack;
const callModule = stack.split("\n")[2].split("(")[1].slice(0,-1); const stackEntries = stack.split("\n").slice(1);//drop 1st line (error message)
const stackEntry2 = stackEntries[1];//get 2nd stack entry
if (stackEntry2) {
try {
if (stackEntry2.indexOf(" (") >= 0) {
callModule = stackEntry2.split("(")[1].slice(0, -1);
} else {
callModule = stackEntry2.split(" ").slice(-1)[0];
}
} catch (error) {
Log.debug(`Unable to determined module when adding hook '${hookId}'. Stack:\n${stackEntries.join("\n")}`);
callModule = "unknown:0:0";
}
} else {
Log.debug(`Unable to determined module when adding hook '${hookId}'. Stack:\n${stackEntries.join("\n")}`);
callModule = "unknown:0:0";
}
Log.debug(`Adding hook '${hookId}' from ${callModule}`); Log.debug(`Adding hook '${hookId}' from ${callModule}`);
const hookItem = {cb:callback, location: callModule, previousHook: null, nextHook: null } const hookItem = {cb:callback, location: callModule, previousHook: null, nextHook: null }

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/util", "name": "@node-red/util",
"version": "2.2.0", "version": "2.2.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
@ -16,9 +16,9 @@
], ],
"dependencies": { "dependencies": {
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"i18next": "21.6.10", "i18next": "21.6.11",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.5", "jsonata": "1.8.6",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"moment-timezone": "0.5.34" "moment-timezone": "0.5.34"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "2.2.0", "version": "2.2.2",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -31,15 +31,15 @@
"flow" "flow"
], ],
"dependencies": { "dependencies": {
"@node-red/editor-api": "2.2.0", "@node-red/editor-api": "2.2.2",
"@node-red/runtime": "2.2.0", "@node-red/runtime": "2.2.2",
"@node-red/util": "2.2.0", "@node-red/util": "2.2.2",
"@node-red/nodes": "2.2.0", "@node-red/nodes": "2.2.2",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.17.2", "express": "4.17.2",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"node-red-admin": "^2.2.2", "node-red-admin": "^2.2.3",
"nopt": "5.0.0", "nopt": "5.0.0",
"semver": "7.3.5" "semver": "7.3.5"
}, },

View File

@ -242,6 +242,142 @@ describe('inject node', function() {
}); });
it('inject name of node as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}];
helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property("payload", "NAME");
done();
} catch (err) {
done(err);
}
});
n1.receive({});
});
});
it('inject id of node as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_ID}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}];
helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property("payload", "n1");
done();
} catch (err) {
done(err);
}
});
n1.receive({});
});
});
it('inject path of node as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_PATH}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}];
helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property("payload", "flow/n1");
done();
} catch (err) {
done(err);
}
});
n1.receive({});
});
});
it('inject name of flow as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"},
{id: "flow", type: "tab", label: "FLOW" },
];
helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property("payload", "FLOW");
done();
} catch (err) {
done(err);
}
});
n1.receive({});
});
});
it('inject id of flow as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_ID}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"},
{id: "flow", type: "tab", name: "FLOW" },
];
helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property("payload", "flow");
done();
} catch (err) {
done(err);
}
});
n1.receive({});
});
});
it('inject name of group as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_NAME}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"},
{id: "n2", type: "helper"},
{id: "g0", type: "group", name: "GROUP" },
];
helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property("payload", "GROUP");
done();
} catch (err) {
done(err);
}
});
n1.receive({});
});
});
it('inject id of group as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_ID}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"},
{id: "n2", type: "helper"},
{id: "g0", type: "group", name: "GROUP" },
];
helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property("payload", "g0");
done();
} catch (err) {
done(err);
}
});
n1.receive({});
});
});
it('sets the value of flow context property', function (done) { it('sets the value of flow context property', function (done) {
var flow = [{id: "n1", type: "inject", topic: "t1", payload: "flowValue", payloadType: "flow", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", topic: "t1", payload: "flowValue", payloadType: "flow", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}]; {id: "n2", type: "helper"}];