Compare commits

..

89 Commits

Author SHA1 Message Date
Nick O'Leary
2ae2ec2578 Combine existing env vars when merging groups
Closes #4101
2023-05-22 16:07:14 +01:00
Nick O'Leary
b904c23e4d Merge pull request #4180 from node-red/4168-make-module-install-synchronous
Ensure external modules are installed synchronously
2023-05-22 14:39:26 +01:00
Nick O'Leary
ece3eb2e7b Merge pull request #4155 from node-red/update-deps
Update dependecies include got
2023-05-22 14:29:30 +01:00
Nick O'Leary
18610bb540 Ensure external modules are installed synchronously
Fixes #4168
2023-05-22 14:24:11 +01:00
Nick O'Leary
69d643942c Merge branch 'dev' into update-deps 2023-05-22 13:57:47 +01:00
Nick O'Leary
6e1b298282 Reconstruct xml2js output as proper object 2023-05-22 13:57:12 +01:00
Nick O'Leary
c2387777c9 Merge branch 'master' into dev 2023-05-22 12:52:07 +01:00
Nick O'Leary
32a49a1ef1 Merge pull request #4172 from wooferguy/Zombie-Junctions-Fix
Check for group
2023-05-22 12:51:46 +01:00
Nick O'Leary
4b88775183 Merge pull request #4166 from node-red/fix-RBE-for-missing-payload
Fix RBE for missing "payload"
2023-05-22 12:49:07 +01:00
Nick O'Leary
29db82625f Merge pull request #4162 from kazuhitoyokoi/master-fixdownload4ipad
Fix content type for downloading flows.json
2023-05-22 12:48:05 +01:00
Nick O'Leary
2b6c9e3439 Merge pull request #4157 from kazuhitoyokoi/master-addjpn
Add Japanese translation for keyboard shortcut scope
2023-05-22 12:47:46 +01:00
Nick O'Leary
a7e0444e92 Merge pull request #4158 from kazuhitoyokoi/dev-addjpn
Add Japanese translations for v3.1 beta.2
2023-05-22 11:36:32 +01:00
Nick O'Leary
e9e32550df Merge pull request #4178 from node-red/4169-remove-express-header
Ensure express server options are applied consistently
2023-05-22 11:35:32 +01:00
Nick O'Leary
59b059f06f Merge pull request #4179 from node-red/4170-remove-version-from-theme
Remove version info from theme endpoint
2023-05-22 11:35:20 +01:00
Nick O'Leary
42166f5fc4 Merge pull request #4153 from node-red/remove-empty-global-config
Avoid creating empty global-config node if not needed
2023-05-22 11:33:07 +01:00
Nick O'Leary
eefe69d136 Merge branch 'dev' into 4170-remove-version-from-theme 2023-05-22 11:29:54 +01:00
Nick O'Leary
aabaf7c5e2 Merge branch 'dev' into 4169-remove-express-header 2023-05-22 11:29:45 +01:00
Nick O'Leary
9ea4853c89 Merge branch 'master' into Zombie-Junctions-Fix 2023-05-22 11:29:31 +01:00
Nick O'Leary
3b5e21761b Merge branch 'master' into fix-RBE-for-missing-payload 2023-05-22 11:29:19 +01:00
Nick O'Leary
2d76bf29cf Merge branch 'master' into master-fixdownload4ipad 2023-05-22 11:29:02 +01:00
Nick O'Leary
a684ec235f Merge branch 'dev' into dev-addjpn 2023-05-22 11:28:50 +01:00
Nick O'Leary
c21f7abe4e Merge branch 'master' into master-addjpn 2023-05-22 11:28:40 +01:00
Nick O'Leary
47005043a5 Merge branch 'dev' into remove-empty-global-config 2023-05-22 11:27:43 +01:00
Nick O'Leary
1e36ba8429 Merge branch 'master' into dev 2023-05-22 11:26:55 +01:00
Nick O'Leary
2679ff277c Merge pull request #4173 from wooferguy/Inject-Node-Test-Fix
Invalid JSONata Inject node test passing condition
2023-05-22 11:26:38 +01:00
Nick O'Leary
0e52271ba9 Remove version info from theme endpoint
Fixes #4170
2023-05-22 11:00:15 +01:00
Nick O'Leary
57359d1659 Ensure express server options are applied consistently
Fixes #4169
2023-05-22 10:54:37 +01:00
wooferguy
9e3f148273 Invalid JSONata Inject node test passing condition
This test would sometimes run twice, causing the author to increase its catch count to 2 before considering the test complete. However even one pass proves the node is behaving as expected, and it always runs at least once. I have left the conditional statement in so it can be changed in future.
2023-05-17 18:56:07 +12:00
wooferguy
7e9042e9f7 Check for group
Remove junction from groups node list if it is present.
2023-05-17 05:14:18 +12:00
Dave Conway-Jones
67c5a248ad Fix RBE for missing "payload"
To close #4165
2023-05-08 09:28:35 +01:00
Kazuhito Yokoi
e8ddee24a9 Use correct content type for downloading flows.json 2023-05-06 21:10:49 +09:00
Kazuhito Yokoi
be4eab65f6 Fix content type for downloading flows.json 2023-05-06 20:00:20 +09:00
Kazuhito Yokoi
fb5ffa1c31 Add Japanese translation for keyboard shortcut scope 2023-05-01 14:44:14 +09:00
Kazuhito Yokoi
aff0bd3f6a Add Japanese translation for auto unsubscribe in MQTT node 2023-05-01 14:28:02 +09:00
Kazuhito Yokoi
c0650cc0f5 Add Japanese translation for keyboard shortcut scope 2023-05-01 13:53:54 +09:00
Nick O'Leary
e29479fd25 Merge branch 'dev' into remove-empty-global-config 2023-04-28 21:49:16 +01:00
Nick O'Leary
46ae66c8b2 Bump test helper version 2023-04-28 21:42:36 +01:00
Nick O'Leary
20abe4a40c Update dependecies include got 2023-04-28 21:37:03 +01:00
Nick O'Leary
55a9a29f76 Merge branch 'master' into dev 2023-04-28 18:49:03 +01:00
Nick O'Leary
67dd7e30fa Merge pull request #4154 from node-red/add-editor-scope
Add editor scope to keyboard shortcut scope select
2023-04-28 18:47:25 +01:00
Nick O'Leary
e9a08af73b Merge pull request #4148 from GerwinvBeek/Bugs/not-loading-missing-subflow
Handle missing subflow when loading flows into the editor
2023-04-28 18:47:10 +01:00
Nick O'Leary
08b1ef2766 Merge branch 'master' into add-editor-scope 2023-04-28 18:07:47 +01:00
Nick O'Leary
667d8673d4 Merge branch 'master' into Bugs/not-loading-missing-subflow 2023-04-28 18:07:28 +01:00
Nick O'Leary
d44ea9d558 Merge pull request #4152 from node-red/remove-coveralls
Remove coveralls reporting as it is failing builds
2023-04-28 18:07:10 +01:00
Nick O'Leary
86dfe86813 Add editor scope to keyboard shortcut scope select 2023-04-28 17:47:16 +01:00
Nick O'Leary
b129e11c8f Avoid creating empty global-config node if not needed 2023-04-28 17:36:55 +01:00
Nick O'Leary
246409970d Remove coveralls reporting as it is failing builds 2023-04-28 17:17:40 +01:00
Nick O'Leary
bd7b3bb4d7 Merge pull request #4108 from node-red/fix-group-select-delete
Fix group selection when using lasso
2023-04-28 15:28:04 +01:00
Nick O'Leary
841f1849c8 Update packages/node_modules/@node-red/runtime/lib/flows/util.js 2023-04-28 15:25:29 +01:00
Nick O'Leary
00e7e4d43c Merge pull request #4147 from kazuhitoyokoi/master-selection2subflow
Add node width and height to boundingBox
2023-04-28 15:23:02 +01:00
Nick O'Leary
ee43a845aa Merge pull request #4128 from kazuhitoyokoi/master-fixquickadddialog
Fix broken subflow icon and overflowed name in quick add dialog
2023-04-28 15:22:25 +01:00
Nick O'Leary
a7cc66af93 Merge pull request #4130 from kazuhitoyokoi/master-fixoverflow
Wrap long node name in tooltip, info sidebar, and node property
2023-04-28 15:21:37 +01:00
Nick O'Leary
f8701cfed0 Merge pull request #4135 from kazuhitoyokoi/master-fixactionlist
Support uppercase in keyword when searching action list
2023-04-28 15:20:56 +01:00
Nick O'Leary
6b205bf303 Merge pull request #4141 from bonanitech/palette-search-background
Fix palette filter background
2023-04-28 15:20:38 +01:00
Nick O'Leary
07729247ac Merge pull request #4145 from kazuhitoyokoi/dev-addjpn4tour
Add Japanese translations for welcome tour of 3.1.0 beta.2
2023-04-28 15:16:13 +01:00
Nick O'Leary
792b310fad Merge pull request #4151 from mw75/master
Use editor path in generating localStorage keys
2023-04-28 15:15:55 +01:00
Mario Wolff
ed2c9d24e8 use replace instead of replaceAll to support node14 2023-04-28 14:53:18 +02:00
Mario Wolff
f917212d67 a simple approach to fix #2657 2023-04-28 12:35:19 +02:00
Gerwin van Beek
6fbcec8b98 Solved node red not loading without error when subflow is missing 2023-04-24 11:51:06 +02:00
Kazuhito Yokoi
c30e57c31d Add node width and height to boundingBox 2023-04-23 20:47:17 +09:00
Kazuhito Yokoi
674c9f0405 Add Japanese translations for welcome tour of 3.1.0 beta.2 2023-04-22 21:26:02 +09:00
Mauricio Bonani
df3dc36874 Fix palette filter background 2023-04-17 10:57:40 -04:00
Kazuhito Yokoi
7b71d8d212 Support uppercase in keyword when searching action list 2023-04-10 01:25:34 +09:00
Kazuhito Yokoi
2eaae4b83f Wrap long node name in info sidebar 2023-04-05 00:56:27 +09:00
Kazuhito Yokoi
3c66af9506 Wrap long node name in tooltip and node property 2023-04-05 00:45:46 +09:00
Nick O'Leary
e5d579c1bb Merge pull request #4120 from kazuhitoyokoi/master-fixbuildstatus
Use build status icon of GitHub Actions
2023-04-01 17:29:41 +01:00
Kazuhito Yokoi
ee811ca89b Use build status icon of GitHub Actions 2023-04-02 00:39:33 +09:00
Kazuhito Yokoi
8e4933041d Wrap text of label in quick add dialog 2023-04-01 02:34:11 +09:00
Kazuhito Yokoi
1f3559e14f Set label width in quick add dialog to prevent broken node icon 2023-04-01 02:28:48 +09:00
Nick O'Leary
53f99ecc23 Merge pull request #4112 from node-red/editor-cred-export
Ensure no node credentials are included when exporting to clipboard
2023-03-24 09:50:14 +00:00
Nick O'Leary
347410f744 Do not include credentials when exporting to clipboard 2023-03-24 09:44:10 +00:00
Nick O'Leary
4667e76c6b Merge pull request #4078 from flying7eleven/option-to-disable-mqtt-ubsubscribe-on-disconnect
Option to disable MQTT topic unsubscribe on disconnect
2023-03-20 20:32:08 +00:00
Nick O'Leary
a10d07d1dc Merge pull request #4100 from sroebert/feature/sha-digest-algorithms
Added SHA-256 and SHA-512-256 digest authentication
2023-03-20 20:30:48 +00:00
Nick O'Leary
9f1ac733b7 Merge pull request #4103 from Steve-Mcl/add-missing-timer-types
add "timers" types to known types
2023-03-20 20:29:36 +00:00
Nick O'Leary
33ea25922d Update packages/node_modules/@node-red/editor-client/src/js/ui/view.js 2023-03-20 20:29:10 +00:00
Nick O'Leary
b56bd7bb5e Fix group selection when using lasso 2023-03-20 17:15:45 +00:00
Tim Janke
e7617de1ee Replace a var with a const since its value won't be modified further on 2023-03-13 13:22:51 +01:00
Steve-Mcl
97fbad4dc5 add "timers" types to known types 2023-03-13 10:02:46 +00:00
Steven Roebert
ddf6023983 Added unit tests for digest authentication 2023-03-12 10:08:32 +01:00
Steven Roebert
daa84c9415 Added SHA-256 and SHA-512-256 algorithms to http digest authentication 2023-03-11 12:07:54 +01:00
Stephen McLaughlin
586006de4d Merge pull request #4097 from node-red/fix-json-expr-test
Fix jsonata expression test ui
2023-03-07 17:13:50 +00:00
Stephen McLaughlin
af8ec9f02b Merge pull request #4096 from node-red/fix-search-repeat
Fix search button in palette popover
2023-03-07 17:00:50 +00:00
Nick O'Leary
dc6abb691e Fix jsonata expression test ui 2023-03-07 16:59:13 +00:00
Nick O'Leary
d273c38194 Fix search button in palette popover 2023-03-07 16:47:44 +00:00
Nick O'Leary
940a246160 Merge pull request #4095 from node-red/310-b2
Bump for beta.2
2023-03-06 17:35:28 +00:00
Tim Janke
c94f0896e1 Ensure that a client id is set if autoUnsubscribe is disabled 2023-02-27 14:11:22 +01:00
Tim Janke
182361c176 Re-enable the tests for the autoUnsubscribe property 2023-02-27 12:43:04 +01:00
Tim Janke
8dcc530f44 Refactor to use the already existing autoUnsubscribe property
The tests had an unused property called autoUnsubscribe. I refactored
the code to use this wording too.
2023-02-27 12:41:29 +01:00
Tim Janke
b5dfd62c99 Add an option to not unsubscribe topics on disconnect 2023-02-27 12:21:39 +01:00
54 changed files with 674 additions and 286 deletions

View File

@@ -29,8 +29,8 @@ jobs:
- name: Run tests
run: |
npm run test
- name: Publish to coveralls.io
if: ${{ matrix.node-version == 16 }}
uses: coverallsapp/github-action@v1.1.2
with:
github-token: ${{ github.token }}
# - name: Publish to coveralls.io
# if: ${{ matrix.node-version == 16 }}
# uses: coverallsapp/github-action@v1.1.2
# with:
# github-token: ${{ github.token }}

View File

@@ -2,8 +2,7 @@
http://nodered.org
[![Build Status](https://travis-ci.org/node-red/node-red.svg?branch=master)](https://travis-ci.org/node-red/node-red)
[![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.svg?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
[![Build Status](https://github.com/node-red/node-red/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/node-red/node-red/actions?query=branch%3Amaster)
Low-code programming for event-driven applications.

View File

@@ -44,8 +44,8 @@
"express": "4.18.2",
"express-session": "1.17.3",
"form-data": "4.0.0",
"fs-extra": "10.1.0",
"got": "11.8.6",
"fs-extra": "11.1.1",
"got": "12.6.0",
"hash-sum": "2.0.0",
"hpagent": "1.2.0",
"https-proxy-agent": "5.0.1",
@@ -60,7 +60,7 @@
"memorystore": "1.6.7",
"mime": "3.0.0",
"moment": "2.29.4",
"moment-timezone": "0.5.41",
"moment-timezone": "0.5.43",
"mqtt": "4.3.7",
"multer": "1.4.5-lts.1",
"mustache": "4.2.0",
@@ -73,13 +73,13 @@
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.5.2",
"semver": "7.3.8",
"semver": "7.5.0",
"tar": "6.1.13",
"tough-cookie": "4.1.2",
"uglify-js": "3.17.4",
"uuid": "9.0.0",
"ws": "7.5.6",
"xml2js": "0.4.23"
"xml2js": "0.5.0"
},
"optionalDependencies": {
"bcrypt": "5.1.0"
@@ -108,14 +108,14 @@
"i18next-http-backend": "1.4.1",
"jquery-i18next": "1.2.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "4.2.12",
"mermaid": "^9.3.0",
"marked": "4.3.0",
"mermaid": "^9.4.3",
"minami": "1.2.3",
"mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.0",
"node-red-node-test-helper": "^0.3.1",
"nodemon": "2.0.20",
"proxy": "^1.0.2",
"sass": "1.58.3",
"sass": "1.62.1",
"should": "13.2.3",
"sinon": "11.1.2",
"stoppable": "^1.1.0",

View File

@@ -14,8 +14,6 @@
* limitations under the License.
**/
var express = require("express");
var nodes = require("./nodes");
var flows = require("./flows");
var flow = require("./flow");
@@ -37,18 +35,9 @@ module.exports = {
plugins.init(runtimeAPI);
diagnostics.init(settings, runtimeAPI);
var needsPermission = auth.needsPermission;
var adminApp = express();
var defaultServerSettings = {
"x-powered-by": false
}
var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
for (var eOption in serverSettings) {
adminApp.set(eOption, serverSettings[eOption]);
}
const needsPermission = auth.needsPermission;
const adminApp = apiUtil.createExpressApp(settings)
// Flows
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);

View File

@@ -46,14 +46,15 @@ module.exports = {
runtimeAPI = _runtimeAPI;
needsPermission = auth.needsPermission;
if (!settings.disableEditor) {
info.init(runtimeAPI);
info.init(settings, runtimeAPI);
comms.init(server,settings,runtimeAPI);
var ui = require("./ui");
ui.init(runtimeAPI);
var editorApp = express();
const editorApp = apiUtil.createExpressApp(settings)
if (settings.requireHttps === true) {
editorApp.enable('trust proxy');
editorApp.use(function (req, res, next) {
@@ -86,7 +87,7 @@ module.exports = {
//Projects
var projects = require("./projects");
projects.init(runtimeAPI);
projects.init(settings, runtimeAPI);
editorApp.use("/projects",projects.app());
// Locales

View File

@@ -14,9 +14,9 @@
* limitations under the License.
**/
var express = require("express");
var apiUtils = require("../util");
var settings;
var runtimeAPI;
var needsPermission = require("../auth").needsPermission;
@@ -77,11 +77,12 @@ function getProjectRemotes(req,res) {
})
}
module.exports = {
init: function(_runtimeAPI) {
init: function(_settings, _runtimeAPI) {
settings = _settings;
runtimeAPI = _runtimeAPI;
},
app: function() {
var app = express();
var app = apiUtils.createExpressApp(settings)
app.use(function(req,res,next) {
runtimeAPI.projects.available().then(function(available) {

View File

@@ -18,9 +18,9 @@ var runtimeAPI;
var sshkeys = require("./sshkeys");
module.exports = {
init: function(_runtimeAPI) {
init: function(settings, _runtimeAPI) {
runtimeAPI = _runtimeAPI;
sshkeys.init(runtimeAPI);
sshkeys.init(settings, runtimeAPI);
},
userSettings: function(req, res) {
var opts = {

View File

@@ -17,13 +17,15 @@
var apiUtils = require("../util");
var express = require("express");
var runtimeAPI;
var settings;
module.exports = {
init: function(_runtimeAPI) {
init: function(_settings, _runtimeAPI) {
runtimeAPI = _runtimeAPI;
settings = _settings;
},
app: function() {
var app = express();
const app = apiUtils.createExpressApp(settings);
// List all SSH keys
app.get("/", function(req,res) {

View File

@@ -19,6 +19,7 @@ var util = require("util");
var path = require("path");
var fs = require("fs");
var clone = require("clone");
const apiUtil = require("../util")
var defaultContext = {
page: {
@@ -27,8 +28,7 @@ var defaultContext = {
tabicon: {
icon: "red/images/node-red-icon-black.svg",
colour: "#8f0000"
},
version: require(path.join(__dirname,"../../package.json")).version
}
},
header: {
title: "Node-RED",
@@ -40,6 +40,7 @@ var defaultContext = {
vendorMonaco: ""
}
};
var settings;
var theme = null;
var themeContext = clone(defaultContext);
@@ -92,7 +93,8 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
}
module.exports = {
init: function(settings, _runtimeAPI) {
init: function(_settings, _runtimeAPI) {
settings = _settings;
runtimeAPI = _runtimeAPI;
themeContext = clone(defaultContext);
if (process.env.NODE_ENV == "development") {
@@ -113,7 +115,15 @@ module.exports = {
var url;
themeSettings = {};
themeApp = express();
themeApp = apiUtil.createExpressApp(settings);
const defaultServerSettings = {
"x-powered-by": false
}
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
for (const eOption in serverSettings) {
themeApp.set(eOption, serverSettings[eOption]);
}
if (theme.page) {

View File

@@ -37,7 +37,6 @@ var adminApp;
var server;
var editor;
/**
* Initialise the module.
* @param {Object} settings The runtime settings
@@ -49,7 +48,7 @@ var editor;
function init(settings,_server,storage,runtimeAPI) {
server = _server;
if (settings.httpAdminRoot !== false) {
adminApp = express();
adminApp = apiUtil.createExpressApp(settings);
var cors = require('cors');
var corsHandler = cors({
@@ -64,14 +63,6 @@ function init(settings,_server,storage,runtimeAPI) {
}
}
var defaultServerSettings = {
"x-powered-by": false
}
var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
for (var eOption in serverSettings) {
adminApp.set(eOption, serverSettings[eOption]);
}
auth.init(settings,storage);
var maxApiRequestSize = settings.apiMaxLength || '5mb';
@@ -136,10 +127,11 @@ async function stop() {
editor.stop();
}
}
module.exports = {
init: init,
start: start,
stop: stop,
init,
start,
stop,
/**
* @memberof @node-red/editor-api

View File

@@ -14,10 +14,9 @@
* limitations under the License.
**/
const express = require("express");
var log = require("@node-red/util").log; // TODO: separate module
var i18n = require("@node-red/util").i18n; // TODO: separate module
const { log, i18n } = require("@node-red/util");
module.exports = {
errorHandler: function(err,req,res,next) {
@@ -64,5 +63,17 @@ module.exports = {
path: req.path,
ip: (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined
}
},
createExpressApp: function(settings) {
const app = express();
const defaultServerSettings = {
"x-powered-by": false
}
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
for (let eOption in serverSettings) {
app.set(eOption, serverSettings[eOption]);
}
return app
}
}

View File

@@ -504,6 +504,7 @@
"unassigned": "Unassigned",
"global": "global",
"workspace": "workspace",
"editor": "edit dialog",
"selectAll": "Select all",
"selectNone": "Select none",
"selectAllConnected": "Select connected",

View File

@@ -504,6 +504,7 @@
"unassigned": "未割当",
"global": "グローバル",
"workspace": "ワークスペース",
"editor": "編集ダイアログ",
"selectAll": "全てのノードを選択",
"selectNone": "選択を外す",
"selectAllConnected": "接続されたノードを選択",
@@ -1203,7 +1204,7 @@
"fr": "フランス語",
"ja": "日本語",
"ko": "韓国語",
"pt-BR":"ポルトガル語",
"pt-BR": "ポルトガル語",
"ru": "ロシア語",
"zh-CN": "中国語(簡体)",
"zh-TW": "中国語(繁体)"

View File

@@ -1468,7 +1468,7 @@ RED.nodes = (function() {
}
}
if (node.type !== "subflow") {
var convertedNode = RED.nodes.convertNode(node);
var convertedNode = RED.nodes.convertNode(node, { credentials: false });
for (var d in node._def.defaults) {
if (node._def.defaults[d].type) {
var nodeList = node[d];
@@ -1501,7 +1501,7 @@ RED.nodes = (function() {
nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
}
} else {
var convertedSubflow = convertSubflow(node);
var convertedSubflow = convertSubflow(node, { credentials: false });
nns.push(convertedSubflow);
}
}
@@ -2201,16 +2201,27 @@ RED.nodes = (function() {
} else if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds || options.importMap[n.id] === "copy") {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);
delete node.i;
if (!subflow){
node._def = {
color:"#fee",
defaults: {},
label: "unknown: "+n.type,
labelStyle: "red-ui-flow-node-label-italic",
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
set: registry.getNodeSet("node-red/unknown")
}
} else {
if (createNewIds || options.importMap[n.id] === "copy") {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);
delete node.i;
}
node.name = n.name;
node.outputs = subflow.out.length;
node.inputs = subflow.in.length;
node.env = n.env;
}
node.name = n.name;
node.outputs = subflow.out.length;
node.inputs = subflow.in.length;
node.env = n.env;
} else if (n.type === 'junction') {
node._def = {defaults:{}}
node._config.x = node.x

View File

@@ -33,8 +33,8 @@ RED.settings = (function () {
if (!hasLocalStorage()) {
return;
}
if (key === "auth-tokens") {
localStorage.setItem(key, JSON.stringify(value));
if (key.startsWith("auth-tokens")) {
localStorage.setItem(key+this.authTokensSuffix, JSON.stringify(value));
} else {
RED.utils.setMessageProperty(userSettings,key,value);
saveUserSettings();
@@ -52,8 +52,8 @@ RED.settings = (function () {
if (!hasLocalStorage()) {
return undefined;
}
if (key === "auth-tokens") {
return JSON.parse(localStorage.getItem(key));
if (key.startsWith("auth-tokens")) {
return JSON.parse(localStorage.getItem(key+this.authTokensSuffix));
} else {
var v;
try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
@@ -71,8 +71,8 @@ RED.settings = (function () {
if (!hasLocalStorage()) {
return;
}
if (key === "auth-tokens") {
localStorage.removeItem(key);
if (key.startsWith("auth-tokens")) {
localStorage.removeItem(key+this.authTokensSuffix);
} else {
delete userSettings[key];
saveUserSettings();
@@ -99,6 +99,8 @@ RED.settings = (function () {
var init = function (options, done) {
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
var path=window.location.pathname.slice(0,-1);
RED.settings.authTokensSuffix=path.replace(/\//g, '-');
if (accessTokenMatch) {
var accessToken = accessTokenMatch[1];
RED.settings.set("auth-tokens",{access_token: accessToken});

View File

@@ -47,7 +47,7 @@ RED.actionList = (function() {
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
searchInput = $('<input type="text" data-i18n="[placeholder]keyboard.filterActions">').appendTo(searchDiv).searchBox({
change: function() {
filterTerm = $(this).val().trim();
filterTerm = $(this).val().trim().toLowerCase();
filterTerms = filterTerm.split(" ");
searchResults.editableList('filter');
searchResults.find("li.selected").removeClass("selected");

View File

@@ -37,13 +37,13 @@ RED.clipboard = (function() {
// IE11 workaround
// IE does not support data uri scheme for downloading data
var blob = new Blob([data], {
type: "data:text/plain;charset=utf-8"
type: "data:application/json;charset=utf-8"
});
navigator.msSaveBlob(blob, file);
}
else {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(data));
element.setAttribute('download', file);
element.style.display = 'none';
document.body.appendChild(element);
@@ -731,7 +731,7 @@ RED.clipboard = (function() {
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);
} else if (type === 'full') {
nodes = RED.nodes.createCompleteNodeSet(false);
nodes = RED.nodes.createCompleteNodeSet({ credentials: false });
}
if (nodes !== null) {
if (format === "red-ui-clipboard-dialog-export-fmt-full") {

View File

@@ -45,11 +45,13 @@ RED.editor = (function() {
var hasChanged;
if (node.type.indexOf("subflow:")===0) {
subflow = RED.nodes.subflow(node.type.substring(8));
isValid = subflow.valid;
hasChanged = subflow.changed;
if (isValid === undefined) {
isValid = validateNode(subflow);
if (subflow){
isValid = subflow.valid;
hasChanged = subflow.changed;
if (isValid === undefined) {
isValid = validateNode(subflow);
hasChanged = subflow.changed;
}
}
validationErrors = validateNodeProperties(node, node._def.defaults, node);
node.valid = isValid && validationErrors.length === 0;

View File

@@ -108,7 +108,7 @@ RED.editor.codeEditor.monaco = (function() {
"node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
"node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
}
const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] , knownModules["util"] ];
const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"], knownModules["timers"] , knownModules["util"] ];
const modulesCache = {};

View File

@@ -294,7 +294,7 @@
}
try {
expr.evaluate(legacyMode?{msg:parsedData}:parsedData, (err, result) => {
expr.evaluate(legacyMode?{msg:parsedData}:parsedData, null, (err, result) => {
if (err) {
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
} else {

View File

@@ -50,7 +50,11 @@ RED.envVar = (function() {
var new_env = [];
var items = list.editableList('items');
var credentials = gconf ? gconf.credentials : null;
if (!gconf && list.editableList('length') === 0) {
// No existing global-config node and nothing in the list,
// so no need to do anything more
return
}
if (!credentials) {
credentials = {
_ : {},
@@ -78,6 +82,12 @@ RED.envVar = (function() {
if (gconf === null) {
gconf = getGlobalConf(true);
}
if (!gconf.credentials) {
gconf.credentials = {
_ : {},
map: {}
};
}
if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
(JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
gconf.env = new_env;

View File

@@ -401,7 +401,7 @@ RED.group = (function() {
}
}
var existingGroup;
var mergedEnv = {}
// Second pass, ungroup any groups in the selection and add their contents
// to the selection
for (var i=0; i<selection.nodes.length; i++) {
@@ -410,6 +410,11 @@ RED.group = (function() {
if (!existingGroup) {
existingGroup = n;
}
if (n.env && n.env.length > 0) {
n.env.forEach(env => {
mergedEnv[env.name] = env
})
}
ungroupHistoryEvent.groups.push(n);
nodes = nodes.concat(ungroup(n));
} else {
@@ -427,6 +432,7 @@ RED.group = (function() {
group.style = existingGroup.style;
group.name = existingGroup.name;
}
group.env = Object.values(mergedEnv)
RED.view.select({nodes:[group]})
}
historyEvent.events.push({

View File

@@ -491,7 +491,11 @@ RED.keyboard = (function() {
okButton.attr("disabled",!valid);
});
var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="red-ui-workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
var scopeSelect = $('<select>'+
'<option value="*" data-i18n="keyboard.global"></option>'+
'<option value="red-ui-workspace" data-i18n="keyboard.workspace"></option>'+
'<option value="red-ui-editor-stack" data-i18n="keyboard.editor"></option>'+
'</select>').appendTo(scope);
scopeSelect.i18n();
if (object.scope === "workspace") {
object.scope = "red-ui-workspace";

View File

@@ -171,23 +171,15 @@ RED.palette = (function() {
}
metaData += type;
const safeType = type.replace(/'/g,"\\'");
const searchType = type.indexOf(' ') > -1 ? '&quot;' + type + '&quot;' : type
if (/^subflow:/.test(type)) {
$('<button type="button" onclick="RED.workspaces.show(\''+type.substring(8).replace(/'/g,"\\'")+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-pencil"></i></button>').appendTo(popOverContent)
}
const safeType = type.replace(/'/g,"\\'");
const wrapStr = function (str) {
if(str.indexOf(' ') >= 0) {
return '"' + str + '"'
}
return str
}
$('<button type="button" onclick="RED.search.show(\'type:'+searchType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>').appendTo(popOverContent)
$('<button type="button"; return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>')
.appendTo(popOverContent)
.on('click', function() {
RED.search.show('type:' + wrapStr(safeType))
})
$('<button type="button" onclick="RED.sidebar.help.show(\''+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-book"></i></button>').appendTo(popOverContent)
$('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);

View File

@@ -681,24 +681,23 @@ RED.subflow = (function() {
var candidateOutputs = [];
var candidateInputNodes = {};
var boundingBox = [nodeList[0].x,
nodeList[0].y,
nodeList[0].x,
nodeList[0].y];
var boundingBox = [nodeList[0].x-(nodeList[0].w/2),
nodeList[0].y-(nodeList[0].h/2),
nodeList[0].x+(nodeList[0].w/2),
nodeList[0].y+(nodeList[0].h/2)];
for (i=0;i<nodeList.length;i++) {
n = nodeList[i];
nodes[n.id] = {n:n,outputs:{}};
boundingBox = [
Math.min(boundingBox[0],n.x),
Math.min(boundingBox[1],n.y),
Math.max(boundingBox[2],n.x),
Math.max(boundingBox[3],n.y)
Math.min(boundingBox[0],n.x-(n.w/2)),
Math.min(boundingBox[1],n.y-(n.h/2)),
Math.max(boundingBox[2],n.x+(n.w/2)),
Math.max(boundingBox[3],n.y+(n.h/2))
]
}
var offsetX = snapToGrid(boundingBox[0] - 200);
var offsetY = snapToGrid(boundingBox[1] - 80);
var offsetX = snapToGrid(boundingBox[0] - 140);
var offsetY = snapToGrid(boundingBox[1] - 60);
var center = [
snapToGrid((boundingBox[2]+boundingBox[0]) / 2),

View File

@@ -189,7 +189,13 @@ RED.view = (function() {
set.unshift(...removed)
}
},
find: function(func) { return set.find(func) }
find: function(func) { return set.find(func) },
dump: function () {
console.log('MovingSet Contents')
api.forEach((n, i) => {
console.log(`${i+1}\t${n.n.id}\t${n.n.type}`)
})
}
}
return api;
})();
@@ -226,6 +232,63 @@ RED.view = (function() {
return api
})();
const selectedGroups = (function() {
let groups = new Set()
const api = {
add: function(g, includeNodes, addToMovingSet) {
groups.add(g)
if (!g.selected) {
g.selected = true;
g.dirty = true;
}
if (addToMovingSet !== false) {
movingSet.add(g);
}
if (includeNodes) {
var currentSet = new Set(movingSet.nodes());
var allNodes = RED.group.getNodes(g,true);
allNodes.forEach(function(n) {
if (!currentSet.has(n)) {
movingSet.add(n)
}
n.dirty = true;
})
}
selectedLinks.clearUnselected()
},
remove: function(g) {
groups.delete(g)
if (g.selected) {
g.selected = false;
g.dirty = true;
}
const allNodes = RED.group.getNodes(g,true);
const nodeSet = new Set(allNodes);
nodeSet.add(g);
for (let i = movingSet.length()-1; i >= 0; i -= 1) {
const msn = movingSet.get(i);
if (nodeSet.has(msn.n) || msn.n === g) {
msn.n.selected = false;
msn.n.dirty = true;
movingSet.remove(msn.n,i)
}
}
selectedLinks.clearUnselected()
},
length: () => groups.length,
forEach: (func) => { groups.forEach(func) },
toArray: () => [...groups],
clear: function () {
groups.forEach(g => {
g.selected = false
g.dirty = true
})
groups.clear()
}
}
return api
})()
function init() {
@@ -1130,7 +1193,7 @@ RED.view = (function() {
var touchTrigger = options.touchTrigger;
if (targetGroup) {
selectGroup(targetGroup,false);
selectedGroups.add(targetGroup,false);
RED.view.redraw();
}
@@ -1456,7 +1519,7 @@ RED.view = (function() {
clearSelection();
nn.selected = true;
if (targetGroup) {
selectGroup(targetGroup,false);
selectedGroups.add(targetGroup,false);
}
movingSet.add(nn);
updateActiveNodes();
@@ -1920,10 +1983,7 @@ RED.view = (function() {
if (!movingSet.has(n) && !n.selected) {
// group entirely within lasso
if (n.x > x && n.y > y && n.x + n.w < x2 && n.y + n.h < y2) {
n.selected = true
n.dirty = true
var groupNodes = RED.group.getNodes(n,true);
groupNodes.forEach(gn => movingSet.add(gn))
selectedGroups.add(n, true)
}
}
})
@@ -2273,7 +2333,7 @@ RED.view = (function() {
clearSelection();
activeGroups.forEach(function(g) {
if (!g.g) {
selectGroup(g, true);
selectedGroups.add(g, true);
if (!g.selected) {
g.selected = true;
g.dirty = true;
@@ -2343,10 +2403,7 @@ RED.view = (function() {
}
movingSet.clear();
selectedLinks.clear();
activeGroups.forEach(function(g) {
g.selected = false;
g.dirty = true;
})
selectedGroups.clear();
}
var lastSelection = null;
@@ -2603,6 +2660,16 @@ RED.view = (function() {
var result = RED.nodes.removeJunction(node)
removedJunctions.push(node);
removedLinks = removedLinks.concat(result.links);
if (node.g) {
var group = RED.nodes.group(node.g);
if (selectedGroups.indexOf(group) === -1) {
// Don't use RED.group.removeFromGroup as that emits
// a change event on the node - but we're deleting it
var index = group.nodes.indexOf(node);
group.nodes.splice(index,1);
RED.group.markDirty(group);
}
}
} else {
if (node.direction === "out") {
removedSubflowOutputs.push(node);
@@ -3425,7 +3492,7 @@ RED.view = (function() {
if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) {
clearSelection();
selectGroup(RED.nodes.group(d.g), false);
selectedGroups.add(RED.nodes.group(d.g), false);
mousedown_node.selected = true;
movingSet.add(mousedown_node);
@@ -3846,14 +3913,14 @@ RED.view = (function() {
lastClickNode = g;
if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
deselectGroup(g);
selectedGroups.remove(g);
d3.event.stopPropagation();
} else {
if (!g.selected) {
if (!d3.event.ctrlKey && !d3.event.metaKey) {
clearSelection();
}
selectGroup(g,true);//!wasSelected);
selectedGroups.add(g,true);//!wasSelected);
}
if (d3.event.button != 2) {
@@ -3869,45 +3936,6 @@ RED.view = (function() {
d3.event.stopPropagation();
}
function selectGroup(g, includeNodes, addToMovingSet) {
if (!g.selected) {
g.selected = true;
g.dirty = true;
}
if (addToMovingSet !== false) {
movingSet.add(g);
}
if (includeNodes) {
var currentSet = new Set(movingSet.nodes());
var allNodes = RED.group.getNodes(g,true);
allNodes.forEach(function(n) {
if (!currentSet.has(n)) {
movingSet.add(n)
}
n.dirty = true;
})
}
selectedLinks.clearUnselected()
}
function deselectGroup(g) {
if (g.selected) {
g.selected = false;
g.dirty = true;
}
const allNodes = RED.group.getNodes(g,true);
const nodeSet = new Set(allNodes);
nodeSet.add(g);
for (let i = movingSet.length()-1; i >= 0; i -= 1) {
const msn = movingSet.get(i);
if (nodeSet.has(msn.n) || msn.n === g) {
msn.n.selected = false;
msn.n.dirty = true;
movingSet.remove(msn.n,i)
}
}
selectedLinks.clearUnselected()
}
function getGroupAt(x, y, ignoreSelected) {
// x,y expected to be in node-co-ordinate space
var candidateGroups = {};
@@ -5888,11 +5916,10 @@ RED.view = (function() {
if (movingSet.length() > 0) {
movingSet.forEach(function(n) {
if (n.n.type !== 'group') {
allNodes.add(n.n);
allNodes.add(n.n);
}
});
}
var selectedGroups = activeGroups.filter(function(g) { return g.selected });
selectedGroups.forEach(function(g) {
var groupNodes = RED.group.getNodes(g,true);
groupNodes.forEach(function(n) {
@@ -6101,7 +6128,7 @@ RED.view = (function() {
n.dirty = true;
movingSet.add(n);
} else {
selectGroup(n,true);
selectedGroups.add(n,true);
}
})
}

View File

@@ -126,7 +126,7 @@
list-style-type: none;
margin: 0;
padding:0;
overflow-wrap: anywhere;
li {
display: inline-block;
padding:0;

View File

@@ -55,7 +55,7 @@
.red-ui-palette-search {
position: relative;
overflow: hidden;
background: var(--red-ui-secondary-background);
background: var(--red-ui-form-input-background);
text-align: center;
height: 35px;
padding: 3px;

View File

@@ -35,6 +35,7 @@
padding: 8px;
border-radius: 2px;
background: var(--red-ui-popover-background);
overflow-wrap: anywhere;
}
.red-ui-popover:after, .red-ui-popover:before {
border: solid transparent;

View File

@@ -108,6 +108,8 @@
}
.red-ui-search-result-node-label {
color: var(--red-ui-secondary-text-color);
width: 240px;
overflow-wrap: anywhere;
}
}

View File

@@ -31,6 +31,7 @@
> span {
display: inline-block;
margin-left: 5px;
overflow-wrap: anywhere;
}
border-bottom: 1px solid var(--red-ui-secondary-border-color);
}

View File

@@ -5,16 +5,17 @@ export default {
titleIcon: "fa fa-map-o",
title: {
"en-US": "Welcome to Node-RED 3.1 Beta 2!",
"ja": "Node-RED 3.1 ベータ1へようこそ!"
"ja": "Node-RED 3.1 ベータ2へようこそ!"
},
description: {
"en-US": "<p>This is the second beta release for 3.1.0 and we have a few new features to tell you about.</p>",
// "ja": "<p>これは3.1.0の最初のベータリリースです。いくつかの新機能について説明します。</p>"
"ja": "<p>これは3.1.0の2回目のベータリリースです。いくつかの新機能について説明します。</p>"
}
},
{
title: {
"en-US": "New ways to work with groups",
"ja": "グループの新たな操作方法"
},
description: {
"en-US": `<p>We have changed how you interact with groups in the editor.</p>
@@ -23,49 +24,67 @@ export default {
<li>They can be reordered using the Moving Forwards and Move Backwards actions</li>
<li>Multiple nodes can be dragged into a group in one go</li>
<li>Holding <code>Alt</code> when dragging a node will *remove* it from its group</li>
</ul>`,
"ja": `<p>エディタ上のグループの操作が変更されました。</p>
<ul>
<li>グループ内のノードをクリックする時に、グループが邪魔をすることが無くなりました。</li>
<li>「前面へ移動」と「背面へ移動」の動作を用いて、複数のグループの表示順序を変えることができます。</li>
<li>グループ内へ一度に複数のノードをドラッグできるようになりました。</li>
<li><code>Alt</code> を押したまま、グループ内のノードをドラッグすると、そのグループから *除く* ことができます。</li>
</ul>`
}
},
{
title: {
"en-US": "Change notification on tabs",
"ja": "タブ上の変更通知"
},
image: 'images/tab-changes.png',
description: {
"en-US": `<p>When a tab contains undeployed changes it now shows the
same style of change icon used by nodes.</p>
<p>This will make it much easier to track down changes when you're
working across multiple flows.</p>`
working across multiple flows.</p>`,
"ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p>
<p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`
}
},
{
title: {
"en-US": "A bigger canvas to work with",
"ja": "より広くなった作業キャンバス"
},
description: {
"en-US": `<p>The default canvas size has been increased so you can fit more
into one flow.</p>
<p>We still recommend using tools such as subflows and Link Nodes to help
keep things organised, but now you have more room to work in.</p>`
keep things organised, but now you have more room to work in.</p>`,
"ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p>
<p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`
}
},
{
title: {
"en-US": "Finding help",
"ja": "ヘルプを見つける"
},
image: 'images/node-help.png',
description: {
"en-US": `<p>All node edit dialogs now include a link to that node's help
in the footer.</p>
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`,
"ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p>
<p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`
}
},
{
title: {
"en-US": "And lots more...",
"ja": "そしてさらに沢山あります..."
},
description: {
"en-US": `<p>Of course we have everything from 3.1.0-beta.1 as well....</p>`
"en-US": `<p>Of course we have everything from 3.1.0-beta.1 as well....</p>`,
"ja": `<p>もちろん3.1.0 ベータ1の全ての機能があります....</p>`
}
},
{
@@ -134,13 +153,13 @@ export default {
{
title: {
"en-US": "Adding Mermaid Diagrams",
"ja": "Mermaid図を追加"
"ja": "Mermaid図を追加"
},
image: 'images/mermaid.png',
description: {
"en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
<p>This gives you much richer options for documenting your flows.</p>`,
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
<p>これによって、フローを説明する文書作成の選択肢がより多くなります。</p>`
},
},

View File

@@ -35,7 +35,11 @@ module.exports = function(RED) {
}
else { node.previous = {}; }
}
var value = RED.util.getMessageProperty(msg,node.property);
var value;
try {
value = RED.util.getMessageProperty(msg,node.property);
}
catch(e) { }
if (value !== undefined) {
var t = "_no_topic";
if (node.septopics) { t = topic || t; }

View File

@@ -249,6 +249,12 @@
<span id="node-config-input-cleansession-label" data-i18n="mqtt.label.cleansession"></span>
</label>
</div>
<div class="form-row mqtt-persistence">
<label for="node-config-input-autoUnsubscribe" style="width: auto;">
<input type="checkbox" id="node-config-input-autoUnsubscribe" style="position: relative;vertical-align: bottom; top: -2px; width: 15px;height: 15px;">
<span id="node-config-input-autoUnsubscribe-label" data-i18n="mqtt.label.autoUnsubscribe"></span>
</label>
</div>
<div class="form-row mqtt5">
<label style="width:auto" for="node-config-input-sessionExpiry"><span data-i18n="mqtt.label.sessionExpiry"></span></label>
<input type="number" min="0" id="node-config-input-sessionExpiry" style="width: 100px" >
@@ -483,17 +489,23 @@
tls: {type:"tls-config",required: false,
label:RED._("node-red:mqtt.label.use-tls") },
clientid: {value:"", validate: function(v, opt) {
var ok = false;
let ok = true;
if ($("#node-config-input-clientid").length) {
// Currently editing the node
ok = $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
let needClientId = !$("#node-config-input-cleansession").is(":checked") || !$("#node-config-input-autoUnsubscribe").is(":checked")
if (needClientId) {
ok = (v||"").length > 0;
}
} else {
ok = (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
let needClientId = !(this.cleansession===undefined || this.cleansession) || this.autoUnsubscribe;
if (needClientId) {
ok = (v||"").length > 0;
}
}
if (ok) {
return ok;
if (!ok) {
return RED._("node-red:mqtt.errors.invalid-client-id");
}
return RED._("node-red:mqtt.errors.invalid-client-id");
return true;
}},
autoConnect: {value: true},
usetls: {value: false},
@@ -505,6 +517,7 @@
label: RED._("node-red:mqtt.label.keepalive"),
validate:RED.validators.number(false)},
cleansession: {value: true},
autoUnsubscribe: {value: true},
birthTopic: {value:"", validate:validateMQTTPublishTopic},
birthQos: {value:"0"},
birthRetain: {value:"false"},
@@ -620,6 +633,10 @@
this.cleansession = true;
$("#node-config-input-cleansession").prop("checked",true);
}
if (typeof this.autoUnsubscribe === 'undefined') {
this.autoUnsubscribe = true;
$("#node-config-input-autoUnsubscribe").prop("checked",true);
}
if (typeof this.usetls === 'undefined') {
this.usetls = false;
$("#node-config-input-usetls").prop("checked",false);
@@ -635,6 +652,14 @@
if (typeof this.protocolVersion === 'undefined') {
this.protocolVersion = 4;
}
$("#node-config-input-cleansession").on("change", function() {
const useCleanSession = $("#node-config-input-cleansession").is(':checked');
if(useCleanSession) {
$("div.form-row.mqtt-persistence").hide();
} else {
$("div.form-row.mqtt-persistence").show();
}
});
$("#node-config-input-protocolVersion").on("change", function() {
var v5 = $("#node-config-input-protocolVersion").val() == "5";
if(v5) {

View File

@@ -482,6 +482,7 @@ module.exports = function(RED) {
setIfHasProperty(opts, node, "protocolVersion", init);
setIfHasProperty(opts, node, "keepalive", init);
setIfHasProperty(opts, node, "cleansession", init);
setIfHasProperty(opts, node, "autoUnsubscribe", init);
setIfHasProperty(opts, node, "topicAliasMaximum", init);
setIfHasProperty(opts, node, "maximumPacketSize", init);
setIfHasProperty(opts, node, "receiveMaximum", init);
@@ -590,6 +591,9 @@ module.exports = function(RED) {
if (typeof node.cleansession === 'undefined') {
node.cleansession = true;
}
if (typeof node.autoUnsubscribe === 'undefined') {
node.autoUnsubscribe = true;
}
//use url or build a url from usetls://broker:port
if (node.url && node.brokerurl !== node.url) {
@@ -660,6 +664,7 @@ module.exports = function(RED) {
node.options.password = node.password;
node.options.keepalive = node.keepalive;
node.options.clean = node.cleansession;
node.options.autoUnsubscribe = node.autoUnsubscribe;
node.options.clientId = node.clientid || 'nodered_' + RED.util.generateId();
node.options.reconnectPeriod = RED.settings.mqttReconnectTime||5000;
delete node.options.protocolId; //V4+ default
@@ -1228,12 +1233,16 @@ module.exports = function(RED) {
node.on('close', function(removed, done) {
if (node.brokerConn) {
if(node.isDynamic) {
Object.keys(node.dynamicSubs).forEach(function (topic) {
node.brokerConn.unsubscribe(topic, node.id, removed);
});
node.dynamicSubs = {};
if (node.brokerConn.options.autoUnsubscribe) {
Object.keys(node.dynamicSubs).forEach(function (topic) {
node.brokerConn.unsubscribe(topic, node.id, removed);
});
node.dynamicSubs = {};
}
} else {
node.brokerConn.unsubscribe(node.topic,node.id, removed);
if (node.brokerConn.options.autoUnsubscribe) {
node.brokerConn.unsubscribe(node.topic, node.id, removed);
}
}
node.brokerConn.deregister(node, done, removed);
node.brokerConn = null;

View File

@@ -14,9 +14,9 @@
* limitations under the License.
**/
module.exports = function(RED) {
module.exports = async function(RED) {
"use strict";
const got = require("got");
const { got } = await import('got')
const {CookieJar} = require("tough-cookie");
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
const FormData = require('form-data');
@@ -210,24 +210,24 @@ in your Node-RED user directory (${RED.settings.userDir}).
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
// Had to remove this to get http->https redirect to work
// opts.defaultPort = isHttps?443:80;
opts.timeout = node.reqTimeout;
opts.timeout = { request: node.reqTimeout || 5000 };
opts.throwHttpErrors = false;
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
opts.decompress = false;
opts.method = method;
opts.retry = 0;
opts.retry = { limit: 0 };
opts.responseType = 'buffer';
opts.maxRedirects = 21;
opts.cookieJar = new CookieJar();
opts.ignoreInvalidCookies = true;
opts.forever = nodeHTTPPersistent;
// opts.forever = nodeHTTPPersistent;
if (msg.requestTimeout !== undefined) {
if (isNaN(msg.requestTimeout)) {
node.warn(RED._("httpin.errors.timeout-isnan"));
} else if (msg.requestTimeout < 1) {
node.warn(RED._("httpin.errors.timeout-isnegative"));
} else {
opts.timeout = msg.requestTimeout;
opts.timeout = { request: msg.requestTimeout };
}
}
const originalHeaderMap = {};
@@ -245,9 +245,12 @@ in your Node-RED user directory (${RED.settings.userDir}).
delete options.headers[h];
}
})
if (node.insecureHTTPParser) {
options.insecureHTTPParser = true
// Setting the property under _unixOptions as pretty
// much the only hack available to get got to apply
// a core http option it doesn't think we should be
// allowed to set
options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
}
}
],
@@ -403,15 +406,16 @@ in your Node-RED user directory (${RED.settings.userDir}).
return response
}
const requestUrl = new URL(response.request.requestUrl);
const options = response.request.options;
const options = { headers: {} }
const normalisedHeaders = {};
Object.keys(response.headers).forEach(k => {
normalisedHeaders[k.toLowerCase()] = response.headers[k]
})
if (normalisedHeaders['www-authenticate']) {
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
options.headers.Authorization = authHeader;
}
// response.request.options.merge(options)
sentCreds = true;
return retry(options);
}
@@ -699,25 +703,43 @@ in your Node-RED user directory (${RED.settings.userDir}).
});
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
const sha256 = (value) => { return crypto.createHash('sha256').update(value).digest('hex') }
const sha512 = (value) => { return crypto.createHash('sha512').update(value).digest('hex') }
function digestCompute(algorithm, value) {
var lowercaseAlgorithm = ""
if (algorithm) {
lowercaseAlgorithm = algorithm.toLowerCase().replace(/-sess$/, '')
}
if (lowercaseAlgorithm === "sha-256") {
return sha256(value)
} else if (lowercaseAlgorithm === "sha-512-256") {
var hash = sha512(value)
return hash.slice(0, 64) // Only use the first 256 bits
} else {
return md5(value)
}
}
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
/**
* RFC 2617: handle both MD5 and MD5-sess algorithms.
* RFC 2617: handle both standard and -sess algorithms.
*
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
* HA1=MD5(username:realm:password)
* If the algorithm directive's value is "MD5-sess", then HA1 is
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
* If the algorithm directive's value ends with "-sess", then HA1 is
* HA1=digestCompute(digestCompute(username:realm:password):nonce:cnonce)
*
* If the algorithm directive's value does not end with "-sess", then HA1 is
* HA1=digestCompute(username:realm:password)
*/
var ha1 = md5(user + ':' + realm + ':' + pass)
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
return md5(ha1 + ':' + nonce + ':' + cnonce)
var ha1 = digestCompute(algorithm, user + ':' + realm + ':' + pass)
if (algorithm && /-sess$/i.test(algorithm)) {
return digestCompute(algorithm, ha1 + ':' + nonce + ':' + cnonce)
} else {
return ha1
}
}
function buildDigestHeader(user, pass, method, path, authHeader) {
var challenge = {}
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
@@ -732,10 +754,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
var nc = qop && '00000001'
var cnonce = qop && uuid().replace(/-/g, '')
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
var ha2 = md5(method + ':' + path)
var ha2 = digestCompute(challenge.algorithm, method + ':' + path)
var digestResponse = qop
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
? digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
: digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + ha2)
var authValues = {
username: user,
realm: challenge.realm,

View File

@@ -33,7 +33,13 @@ module.exports = function(RED) {
parseString(value, options, function (err, result) {
if (err) { done(err); }
else {
value = result;
// TODO: With xml2js@0.5.0, they return an object with
// a null prototype. This could cause unexpected
// issues. So for now, we have to reconstruct
// the object with a proper prototype.
// Once https://github.com/Leonidas-from-XIV/node-xml2js/pull/674
// is merged, we can revisit and hopefully remove this hack
value = fixObj(result)
RED.util.setMessageProperty(msg,node.property,value);
send(msg);
done();
@@ -46,4 +52,18 @@ module.exports = function(RED) {
});
}
RED.nodes.registerType("xml",XMLNode);
function fixObj(obj) {
const res = {}
const keys = Object.keys(obj)
keys.forEach(k => {
if (typeof obj[k] === 'object' && obj[k]) {
res[k] = fixObj(obj[k])
} else {
res[k] = obj[k]
}
})
return res
}
}

View File

@@ -362,6 +362,7 @@
"port": "Port",
"keepalive": "Keep-Alive",
"cleansession": "Bereinigte Sitzung (clean session) verwenden",
"autoUnsubscribe": "Abonnement bei Verbindungsende automatisch beenden",
"cleanstart": "Verwende bereinigten Start",
"use-tls": "TLS",
"tls-config": "TLS-Konfiguration",

View File

@@ -414,6 +414,7 @@
"port": "Port",
"keepalive": "Keep Alive",
"cleansession": "Use clean session",
"autoUnsubscribe": "Automatically unsubscribe when disconnecting",
"cleanstart": "Use clean start",
"use-tls": "Use TLS",
"tls-config": "TLS Configuration",

View File

@@ -414,6 +414,7 @@
"port": "ポート",
"keepalive": "キープアライブ時間",
"cleansession": "セッションの初期化",
"autoUnsubscribe": "切断時に購読を自動解除",
"cleanstart": "クリーンスタート",
"use-tls": "TLSを使用",
"tls-config": "TLS設定",

View File

@@ -27,8 +27,8 @@
"cronosjs": "1.7.1",
"denque": "2.1.0",
"form-data": "4.0.0",
"fs-extra": "10.1.0",
"got": "11.8.6",
"fs-extra": "11.1.1",
"got": "12.6.0",
"hash-sum": "2.0.0",
"hpagent": "1.2.0",
"https-proxy-agent": "5.0.1",
@@ -44,7 +44,7 @@
"tough-cookie": "4.1.2",
"uuid": "9.0.0",
"ws": "7.5.6",
"xml2js": "0.4.23",
"xml2js": "0.5.0",
"iconv-lite": "0.6.3"
}
}

View File

@@ -242,63 +242,68 @@ async function ensureModuleDir() {
}
}
let installLock = Promise.resolve()
async function installModule(moduleDetails) {
let installSpec = moduleDetails.module;
if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
const e = new Error("Install not allowed");
e.code = "install_not_allowed";
throw e;
}
if (moduleDetails.version) {
installSpec = installSpec+"@"+moduleDetails.version;
}
log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"}));
const installDir = getInstallDir();
await ensureModuleDir();
let triggerPayload = {
"module": moduleDetails.module,
"version": moduleDetails.version,
"dir": installDir,
"args": ["--production","--engine-strict"]
}
return hooks.trigger("preInstall", triggerPayload).then((result) => {
// preInstall passed
// - run install
if (result !== false) {
let extraArgs = triggerPayload.args || [];
let args = ['install', ...extraArgs, installSpec]
log.trace(NPM_COMMAND + JSON.stringify(args));
return exec.run(NPM_COMMAND, args, { cwd: installDir },true)
} else {
log.trace("skipping npm install");
}
}).then(() => {
return hooks.trigger("postInstall", triggerPayload)
}).then(() => {
log.info(log._("server.install.installed", { name: installSpec }));
const runtimeInstalledModules = settings.get("modules") || {};
runtimeInstalledModules[moduleDetails.module] = moduleDetails;
settings.set("modules",runtimeInstalledModules)
}).catch(result => {
var output = result.stderr || result.toString();
var e;
if (/E404/.test(output) || /ETARGET/.test(output)) {
log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
e = new Error("Module not found");
e.code = 404;
throw e;
} else {
log.error(log._("server.install.install-failed-long",{name:installSpec}));
log.error("------------------------------------------");
log.error(output);
log.error("------------------------------------------");
e = new Error(log._("server.install.install-failed"));
e.code = "unexpected_error";
const result = installLock.then(async () => {
let installSpec = moduleDetails.module;
if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
const e = new Error("Install not allowed");
e.code = "install_not_allowed";
throw e;
}
if (moduleDetails.version) {
installSpec = installSpec+"@"+moduleDetails.version;
}
log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"}));
const installDir = getInstallDir();
await ensureModuleDir();
let triggerPayload = {
"module": moduleDetails.module,
"version": moduleDetails.version,
"dir": installDir,
"args": ["--production","--engine-strict"]
}
return hooks.trigger("preInstall", triggerPayload).then((result) => {
// preInstall passed
// - run install
if (result !== false) {
let extraArgs = triggerPayload.args || [];
let args = ['install', ...extraArgs, installSpec]
log.trace(NPM_COMMAND + JSON.stringify(args));
return exec.run(NPM_COMMAND, args, { cwd: installDir },true)
} else {
log.trace("skipping npm install");
}
}).then(() => {
return hooks.trigger("postInstall", triggerPayload)
}).then(() => {
log.info(log._("server.install.installed", { name: installSpec }));
const runtimeInstalledModules = settings.get("modules") || {};
runtimeInstalledModules[moduleDetails.module] = moduleDetails;
settings.set("modules",runtimeInstalledModules)
}).catch(result => {
var output = result.stderr || result.toString();
var e;
if (/E404/.test(output) || /ETARGET/.test(output)) {
log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
e = new Error("Module not found");
e.code = 404;
throw e;
} else {
log.error(log._("server.install.install-failed-long",{name:installSpec}));
log.error("------------------------------------------");
log.error(output);
log.error("------------------------------------------");
e = new Error(log._("server.install.install-failed"));
e.code = "unexpected_error";
throw e;
}
})
})
installLock = result.catch(() => {})
return result
}
module.exports = {

View File

@@ -18,8 +18,8 @@
"dependencies": {
"@node-red/util": "3.1.0-beta.2",
"clone": "2.1.2",
"fs-extra": "10.1.0",
"semver": "7.3.8",
"fs-extra": "11.1.1",
"semver": "7.5.0",
"tar": "6.1.13",
"uglify-js": "3.17.4"
}

View File

@@ -201,7 +201,9 @@ function parseConfig(config) {
if (subflowDetails) {
var subflowType = subflowDetails[1]
n.subflow = subflowType;
flow.subflows[subflowType].instances.push(n)
if (flow.subflows[subflowType]) {
flow.subflows[subflowType].instances.push(n)
}
}
if (container) {
container.nodes[n.id] = n;

View File

@@ -89,6 +89,15 @@ function init(userSettings,httpServer,_adminApi) {
nodeApp = express();
adminApp = express();
const defaultServerSettings = {
"x-powered-by": false
}
const serverSettings = Object.assign({},defaultServerSettings,userSettings.httpServerOptions||{});
for (let eOption in serverSettings) {
nodeApp.set(eOption, serverSettings[eOption]);
adminApp.set(eOption, serverSettings[eOption]);
}
if (_adminApi) {
adminApi = _adminApi;

View File

@@ -21,7 +21,7 @@
"async-mutex": "0.4.0",
"clone": "2.1.2",
"express": "4.18.2",
"fs-extra": "10.1.0",
"fs-extra": "11.1.1",
"json-stringify-safe": "5.0.1"
}
}

View File

@@ -15,12 +15,12 @@
}
],
"dependencies": {
"fs-extra": "10.1.0",
"fs-extra": "11.1.1",
"i18next": "21.10.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.6",
"lodash.clonedeep": "^4.5.0",
"moment": "2.29.4",
"moment-timezone": "0.5.41"
"moment-timezone": "0.5.43"
}
}

View File

@@ -38,10 +38,10 @@
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.18.2",
"fs-extra": "10.1.0",
"fs-extra": "11.1.1",
"node-red-admin": "^3.0.0",
"nopt": "5.0.0",
"semver": "7.3.8"
"semver": "7.5.0"
},
"optionalDependencies": {
"bcrypt": "5.1.0"

View File

@@ -854,7 +854,7 @@ describe('inject node', function() {
});
n1.on("call:error", function(err) {
count++;
if (count == 2) {
if (count == 1) {
done();
}
});

View File

@@ -31,6 +31,7 @@ var multer = require("multer");
var RED = require("nr-test-utils").require("node-red/lib/red");
var fs = require('fs-extra');
var auth = require('basic-auth');
var crypto = require("crypto");
const { version } = require("os");
const net = require('net')
@@ -163,6 +164,100 @@ describe('HTTP Request Node', function() {
delete process.env.NO_PROXY;
}
function getDigestPassword() {
return 'digest-test-password';
}
function getDigest(algorithm, value) {
var hash;
if (algorithm === 'SHA-256') {
hash = crypto.createHash('sha256');
} else if (algorithm === 'SHA-512-256') {
hash = crypto.createHash('sha512');
} else {
hash = crypto.createHash('md5');
}
var hex = hash.update(value).digest('hex');
if (algorithm === 'SHA-512-256') {
hex = hex.slice(0, 64);
}
return hex;
}
function getDigestResponse(req, algorithm, sess, realm, username, nonce, nc, cnonce, qop) {
var ha1 = getDigest(algorithm, username + ':' + realm + ':' + getDigestPassword());
if (sess) {
ha1 = getDigest(algorithm, ha1 + ':' + nonce + ':' + cnonce)
}
let ha2 = getDigest(algorithm, req.method + ':' + req.path);
return qop
? getDigest(algorithm, ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
: getDigest(algorithm, ha1 + ':' + nonce + ':' + ha2);
}
function handleDigestResponse(req, res, algorithm, sess, qop) {
let realm = "node-red";
let nonce = "123456";
let nc = '00000001';
let algorithmValue = sess ? `${algorithm}-sess` : algorithm;
let authHeader = req.headers['authorization'];
if (!authHeader) {
let qopField = qop ? `qop="${qop}", ` : '';
res.setHeader(
'WWW-Authenticate',
`Digest ${qopField}realm="${realm}", nonce="${nonce}", algorithm="${algorithmValue}"`
);
res.status(401).end();
return;
}
var authFields = {};
let re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi;
for (;;) {
var match = re.exec(authHeader);
if (!match) {
break;
}
authFields[match[1]] = match[2] || match[3];
}
// console.log(JSON.stringify(authFields));
if (qop && authFields['qop'] != qop) {
console.log('test1');
res.status(401).end();
return;
}
if (
!authFields['username'] ||
!authFields['response'] ||
authFields['realm'] != realm ||
authFields['nonce'] != nonce ||
authFields['algorithm'] != algorithmValue
) {
console.log('test2');
res.status(401).end();
return;
}
let username = authFields['username'];
let response = authFields['response'];
let cnonce = authFields['cnonce'] || '';
let expectedResponse = getDigestResponse(
req, algorithm, sess, realm, username, nonce, nc, cnonce, qop
);
if (!response || expectedResponse.toLowerCase() !== response.toLowerCase()) {
console.log('test3', response, expectedResponse);
res.status(401).end();
return;
}
res.status(201).end();
}
before(function(done) {
testApp = express();
@@ -222,6 +317,21 @@ describe('HTTP Request Node', function() {
}
res.json(result);
});
testApp.get('/authenticate-digest-md5', function(req, res){
handleDigestResponse(req, res, "MD5", false, false);
});
testApp.get('/authenticate-digest-md5-sess', function(req, res){
handleDigestResponse(req, res, "MD5", true, 'auth');
});
testApp.get('/authenticate-digest-md5-qop', function(req, res){
handleDigestResponse(req, res, "MD5", false, 'auth');
});
testApp.get('/authenticate-digest-sha-256', function(req, res){
handleDigestResponse(req, res, "SHA-256", false, 'auth');
});
testApp.get('/authenticate-digest-sha-512-256', function(req, res){
handleDigestResponse(req, res, "SHA-512-256", false, 'auth');
});
testApp.get('/proxyAuthenticate', function(req, res){
// var user = auth.parse(req.headers['proxy-authorization']);
var result = {
@@ -2018,6 +2128,100 @@ describe('HTTP Request Node', function() {
});
*/
it('should authenticate on server - digest MD5', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should authenticate on server - digest MD5 sess', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5-sess')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should authenticate on server - digest MD5 qop', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5-qop')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should authenticate on server - digest SHA-256', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-sha-256')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should authenticate on server - digest SHA-512-256', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-sha-512-256')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
});
describe('file-upload', function() {

View File

@@ -53,7 +53,7 @@ describe('MQTT Nodes', function () {
mqttBroker.should.have.property('broker', BROKER_HOST);
mqttBroker.should.have.property('port', BROKER_PORT);
mqttBroker.should.have.property('brokerurl');
// mqttBroker.should.have.property('autoUnsubscribe', true);//default: true
mqttBroker.should.have.property('autoUnsubscribe', true); //default: true
mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions
mqttBroker.should.have.property('options');
mqttBroker.options.should.have.property('clean', true);
@@ -96,8 +96,8 @@ describe('MQTT Nodes', function () {
mqttBroker.should.have.property('broker', BROKER_HOST);
mqttBroker.should.have.property('port', BROKER_PORT);
mqttBroker.should.have.property('brokerurl');
// mqttBroker.should.have.property('autoUnsubscribe', true);//default: true
mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions
mqttBroker.should.have.property('autoUnsubscribe', true);
mqttBroker.should.have.property('autoConnect', false); //Set "autoConnect:false" in brokerOptions
mqttBroker.should.have.property('options');
mqttBroker.options.should.have.property('clean', false);
mqttBroker.options.should.have.property('clientId', 'clientid');

View File

@@ -61,12 +61,14 @@ describe("api/editor/index", function() {
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m),"init").callsFake(function(){});
});
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"),"app").callsFake(function(){ return express()});
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/settings"),"sshkeys").callsFake(function(){ return express()});
});
after(function() {
mockList.forEach(function(m) {
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m).init.restore();
})
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme").app.restore();
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/settings").sshkeys.restore();
auth.needsPermission.restore();
log.error.restore();
});

View File

@@ -41,7 +41,7 @@ describe("api/editor/settings", function() {
});
it('returns the user settings', function(done) {
info.init({
info.init({}, {
settings: {
getUserSettings: function(opts) {
if (opts.user !== "fred") {
@@ -67,7 +67,7 @@ describe("api/editor/settings", function() {
});
it('updates the user settings', function(done) {
var update;
info.init({
info.init({}, {
settings: {
updateUserSettings: function(opts) {
if (opts.user !== "fred") {

View File

@@ -34,7 +34,7 @@ describe("api/editor/sshkeys", function() {
}
}
before(function() {
sshkeys.init(mockRuntime);
sshkeys.init({}, mockRuntime);
app = express();
app.use(bodyParser.json());
app.use("/settings/user/keys", sshkeys.app());