Compare commits

..

62 Commits

Author SHA1 Message Date
Nick O'Leary
c9597b9447 Bump for 2.0.1 2021-07-20 14:44:47 +01:00
Nick O'Leary
b2dc1d8b23 Ensure default module export is exposed in Function node 2021-07-20 14:42:43 +01:00
Nick O'Leary
859c0c7f6c Update to latest node-red-admin 2021-07-20 11:19:13 +01:00
Nick O'Leary
aaf18e2416 Merge branch 'master' into dev 2021-07-20 11:11:28 +01:00
Nick O'Leary
fd679ef117 Update contributing docs 2021-07-20 11:10:41 +01:00
Nick O'Leary
6cc611b3f1 Merge branch 'master' into dev 2021-07-20 11:04:25 +01:00
Nick O'Leary
77ee726f66 Fix focus outline overlap in button-groups
Fixes #3070
2021-07-20 11:03:28 +01:00
Nick O'Leary
dc603b76a4 Fix focus outline overlap in button-groups
Fixes #3070
2021-07-20 11:02:24 +01:00
Nick O'Leary
bcb3371acc Fix another typo in issue template 2021-07-16 12:34:54 +01:00
Nick O'Leary
d14ce7e476 Fix typo in issue template 2021-07-16 12:30:21 +01:00
Nick O'Leary
47f7b43bcc Update bug_report.yml 2021-07-16 10:21:51 +01:00
Nick O'Leary
77fd8c120c Update bug_report.yml 2021-07-16 10:20:23 +01:00
Nick O'Leary
a1a6f40158 Update issue templates to use GH forms 2021-07-16 10:16:09 +01:00
Nick O'Leary
ed09cd7489 Merge pull request #3068 from hardillb/http-auth-fix
Fix for #3067
2021-07-16 09:47:57 +01:00
Nick O'Leary
eb3330d145 Fix vertical sizing of function node edit dialog 2021-07-16 09:44:56 +01:00
Nick O'Leary
5ba0588c7b Merge branch 'master' into dev 2021-07-16 08:53:35 +01:00
Nick O'Leary
d4a199f0e1 Avoid prototype pollution in RED.view.calculateTextDimensions 2021-07-16 08:52:00 +01:00
Ben Hardill
32dd186f4d Prevent Overwite of configured creds 2021-07-16 08:47:30 +01:00
Ben Hardill
81f0fb3c74 Fix creds in URL 2021-07-15 18:32:51 +01:00
Ben Hardill
972c83cd52 Fix for #3067
Check if there are any content to the credential object
2021-07-15 17:35:03 +01:00
Nick O'Leary
66a704af55 Fallback to 'require' if on node < 12.17 2021-07-15 15:38:07 +01:00
Nick O'Leary
31c5d6e1c1 Update changelog 2021-07-15 15:38:02 +01:00
Nick O'Leary
bf0ab95c09 Merge pull request #3066 from node-red-hitachi/update-message-jp
Update Japanese message catalogue
2021-07-15 13:17:41 +01:00
Hiroyasu Nishiyama
c1d85f760d update Japanese message catalogue 2021-07-15 21:09:20 +09:00
Nick O'Leary
88ad2f4c18 Merge pull request #3065 from node-red/funcExtMod-default
Enable functionExternalModules by default
2021-07-15 10:14:28 +01:00
Nick O'Leary
be9f9e7b0c Change default functionExternalModules test 2021-07-15 10:13:21 +01:00
Nick O'Leary
2cc1973f62 Enable functionExternalModules by default 2021-07-15 10:07:52 +01:00
Nick O'Leary
eb4625a0b9 Merge pull request #3064 from node-red/revert-external-modules-dir
Move externalModules back into the user dir
2021-07-15 09:56:23 +01:00
Nick O'Leary
5bfb01254b Add unit tests for externalModules.import 2021-07-15 09:52:53 +01:00
Nick O'Leary
bb80fa4a2d Fix externalModules tests 2021-07-14 23:38:46 +01:00
Nick O'Leary
ddb715d88d Record runtime-installed modules in .config.modules.json 2021-07-14 23:13:52 +01:00
Nick O'Leary
395b499856 Fix async loading of modules in function node 2021-07-14 21:10:59 +01:00
Nick O'Leary
cce6a47f11 Rework function module screen 2021-07-14 20:23:01 +01:00
Nick O'Leary
7fd17b4ec0 Add RED.import to support importing ES6 modules 2021-07-14 19:18:39 +01:00
Nick O'Leary
e16ab2a0fd Bump for 2.0.0 2021-07-13 11:51:23 +01:00
Nick O'Leary
15f5364c30 Update changelog 2021-07-13 11:50:38 +01:00
Nick O'Leary
65081767bf Fix externalModules tests 2021-07-13 11:44:12 +01:00
Nick O'Leary
c7c595e5fa Merge pull request #3063 from kazuhitoyokoi/dev-addjpn
Add Japanese translations for Node-RED v2.0
2021-07-13 11:37:23 +01:00
Nick O'Leary
5b24e8b69c Merge pull request #3059 from node-red/delay-queue
Delay node updates to added numeric flush control for releasing things from queue
2021-07-13 11:37:13 +01:00
Nick O'Leary
e6a845e606 Move externalModules back into the user dir 2021-07-13 11:24:10 +01:00
Kazuhito Yokoi
ec8b8a7b87 Change translation to be same as other usages 2021-07-13 14:15:37 +09:00
Kazuhito Yokoi
51a9205105 Add Japanese translations for Node-RED v2.0 2021-07-13 14:05:44 +09:00
Nick O'Leary
ed5567fc73 Merge pull request #3056 from node-red/ws-ping
Allow websocket client node to send pings
2021-07-12 19:40:57 +01:00
Nick O'Leary
4b3f5d74a0 Merge pull request #3058 from kazuhitoyokoi/master-fixdependencies
Add necessary modules and remove unnecessary module in dependencies
2021-07-12 19:40:40 +01:00
Nick O'Leary
b01c5a05e7 Fix reporting of type_already_registered error 2021-07-12 16:09:25 +01:00
Nick O'Leary
36eddabc1c Ensure node.types is defined if node html file missing 2021-07-12 16:09:02 +01:00
Nick O'Leary
e7efa76e6d Add a slight fade to tab labels that overflow 2021-07-08 12:23:15 +01:00
Nick O'Leary
4624079be7 Merge pull request #3057 from kazuhitoyokoi/dev-fixdependencies
Add necessary modules and remove unnecessary module in dependencies
2021-07-08 12:07:07 +01:00
Nick O'Leary
c6f6042271 Show config node details when selected in outliner 2021-07-08 12:03:20 +01:00
Nick O'Leary
e9e3b9b7c6 Fix layout of info outliner for subflow entries 2021-07-08 12:02:11 +01:00
Kazuhito Yokoi
becbb09a29 Add necessary modules and remove unnecessary module in dependencies 2021-07-08 19:50:26 +09:00
Kazuhito Yokoi
6f6ab50995 Add necessary modules and remove unnecessary module in dependencies 2021-07-08 19:14:53 +09:00
Nick O'Leary
d8ee766860 Allow websocket client node to send pings 2021-07-08 10:51:36 +01:00
Nick O'Leary
108c26d8af Merge pull request #3055 from bonanitech/grip-horizontal
Fix grip on panels separator
2021-07-08 08:41:48 +01:00
Nick O'Leary
ed8d3088ca Merge pull request #3054 from bonanitech/scriptFiles
Fix scriptFiles
2021-07-08 08:41:18 +01:00
Nick O'Leary
46c4e2d212 Merge branch 'master' into dev 2021-07-08 08:40:44 +01:00
Nick O'Leary
94891d45f9 Merge pull request #3052 from kazuhitoyokoi/dev-fixdependencies
Add fs-extra module to dependencies in @node-red/util module
2021-07-08 08:39:51 +01:00
Nick O'Leary
7448ad109e Add missing dependency to @node-red/utils module 2021-07-08 08:39:20 +01:00
Mauricio Bonani
6211dfe024 Fix grip on horizontally displayed panels separator 2021-07-07 09:52:47 -04:00
Mauricio Bonani
9b85200954 Fix grip on panels separator 2021-07-07 09:24:14 -04:00
Mauricio Bonani
94ee739d91 Fix scriptFiles 2021-07-07 09:12:58 -04:00
Kazuhito Yokoi
e81a6db9a3 Add fs-extra module to dependencies in @node-red/util module 2021-07-05 13:34:56 +09:00
43 changed files with 676 additions and 418 deletions

View File

@@ -1,35 +0,0 @@
<!--
## Before you hit that Submit button....
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
If your issue is:
- a general 'how-to' type question,
- a feature request or suggestion for a change,
- or problems with 3rd party (`node-red-contrib-`) nodes
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
That way the whole Node-RED user community can help, rather than rely on the core development team.
## So you have a real issue to raise...
To help us understand the issue, please fill-in as much of the following information as you can:
-->
### What are the steps to reproduce?
### What happens?
### What do you expect to happen?
### Please tell us about your environment:
- [ ] Node-RED version:
- [ ] Node.js version:
- [ ] npm version:
- [ ] Platform/OS:
- [ ] Browser:
- [ ] running in Docker:

View File

@@ -1,39 +0,0 @@
---
name: Bug report
about: Reproducible software issues in the core of Node-RED
title: ''
labels: ''
assignees: ''
---
<!--
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
If your issue is:
- a general 'how-to' type question,
- a feature request or suggestion for a change,
- or problems with 3rd party (`node-red-contrib-`) nodes
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
That way the whole Node-RED user community can help, rather than rely on the core development team.
To help us understand the issue, please fill-in as much of the following information as you can:
-->
### What are the steps to reproduce?
### What happens?
### What do you expect to happen?
### Please tell us about your environment:
- [ ] Node-RED version:
- [ ] Node.js version:
- [ ] npm version:
- [ ] Platform/OS:
- [ ] Browser:

View File

@@ -1,17 +0,0 @@
---
name: Anything Else
about: Something that is not a bug report
title: ''
labels: ''
assignees: ''
---
Please DO NOT raise an issue.
We DO NOT use the issue tracker for general support or feature requests. Only bug reports should be raised here using the 'Bug report' template.
For general support, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack). You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
That way the whole Node-RED user community can help, rather than rely on the core development team.
For feature requests, please use the Node-RED Forum](https://discourse.nodered.org). Many ideas have already been discussed there and you should search that for your request before starting a new discussion.

61
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: 🐞 Report a bug
description: File a bug/issue on the core of Node-RED
labels: [needs-triage]
body:
- type: markdown
attributes:
value: |
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
If your issue is:
- a general 'how-to' type question,
- a feature request or suggestion for a change,
- or problems with 3rd party (`node-red-contrib-`) nodes
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
That way the whole Node-RED user community can help, rather than rely on the core development team.
To help us understand the issue, please fill-in as much of the following information as you can:
- type: textarea
attributes:
label: Current Behavior
description: A clear & concise description of what you're experiencing.
validations:
required: false
- type: textarea
attributes:
label: Expected Behavior
description: A clear & concise description of what you expected to happen.
validations:
required: false
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
validations:
required: false
- type: textarea
attributes:
label: Example flow
description: If you have a minimal example flow that demonstrates the issue, share it here.
value: |
```
paste your flow here
```
validations:
required: false
- type: textarea
attributes:
label: Environment
description: Please tell us about your environment. Include any relevant information on how you are running Node-RED.
value: |
- Node-RED version:
- Node.js version:
- npm version:
- Platform/OS:
- Browser:
validations:
required: false

14
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
blank_issues_enabled: true
contact_links:
- name: ❓ Questions
url: https://discourse.nodered.org
about: Ask your question on the Node-RED forum
- name: ⭐️ Feature Request
url: https://discourse.nodered.org/c/development/feature-requests
about: Discuss your request with the community
- name: 🗂 Documentation
url: https://nodered.org/docs
about: Go straight to the documentation
- name: 💬 Slack
url: https://nodered.org/slack
about: Chat about the project on our slack team

View File

@@ -1,6 +1,62 @@
#### 2.0.0-beta.2: Beta Release
#### 2.0.1: Maintenance Release
**See 2.0.0-beta.1 for migration notes**
Nodes
- Function: Ensure default module export is exposed in Function node
#### 2.0.0: Milestone Release
**Migration from 1.x**
- Node-RED now requires Node.js 12.x or later.
- The following nodes have had significant dependency updates. Unless stated,
they should be fully backward compatible.
- RBE: Relabelled as 'filter' to make it more discoverable and made part of
the core palette, rather than as a separate module.
- Tail: This node has been removed from the default palette. You can reinstall it
from node-red-node-tail
- HTTP Request: Reimplemented with a different underlying module. We have
tried to maintain 100% functional compatibility, but it is possible
some edge cases remain.
- JSON: The schema validation option no longer supports JSON-Schema draft-04
- HTML: Its underlying module has had a major version update. Should be fully
backward compatible.
- `functionExternalModules` is now enabled by default for new installs.
If you have an existing settings file that contains this setting, you will
need to set it to `true` yourself.
The external modules will now get installed in your Node-RED user directory,
(`~/.node-red`) rather than in a subdirectory. This means all dependencies will
be listed in your top-level `package.json`. If you have existing external modules,
they will get reinstalled to the new location when you first run Node-RED 2.0.
Runtime
- Fix missing dependencies (#3052, #2057) @kazuhitoyokoi
- Ensure node.types is defined if node html file missing
- Fix reporting of type_already_registered error
- Move install location of external modules (#3064) @knolleary
Editor
- Update translations (#3063) @kazuhitoyokoi
- Add a slight fade to tab labels that overflow
- Show config node details when selected in outliner
- Fix layout of info outliner for subflow entries
Nodes
- Delay: let `msg.flush` specify how many messages to flush from node (#3059) @dceejay
- Function: external modules is now enabled by default (#3065) @knolleary
- Function: external modules now supports both ES6 and CJS modules (#3065) @knolleary
- WebSocket: add option for client node to send automatic pings (#3056) @knolleary
##### 2.0.0-beta.2: Beta Release
Runtime
@@ -39,26 +95,9 @@ Nodes
- Exec: add windowsHide option to hide windows under Windows (#3026) @natcl
- Support loading external module sub path Fixes #3023
#### 2.0.0-beta.1: Beta Release
##### 2.0.0-beta.1: Beta Release
Migration from 1.x
- Node-RED now requires Node.js 12.x or later.
- The following nodes have had significant dependency updates. Unless stated,
they should be fully backward compatible.
- RBE: Relabelled as 'filter' to make it more discoverable and made part of
the core palette, rather than as a separate module.
- Tail: This node has been removed from the default palette. You can reinstall it
from node-red-node-tail
- HTTP Request: Reimplemented with a different underlying module. We have
tried to maintain 100% functional compatibility, but it is possible
some edge cases remain. In particular, if you are using http proxies in
your environment.
- JSON: The schema validation option no longer supports JSON-Schema draft-04
- HTML: Its underlying module has had a major version update. Should be fully
backward compatible.
Runtime

View File

@@ -46,6 +46,14 @@ If you raise a pull-request without having signed the CLA, you will be prompted
to do so automatically.
### Code Branches
When raising a PR for a fix or a new feature, it is important to target the right branch.
- `master` - this is the main branch for the latest stable release of Node-RED. All bug fixes for that release should target this branch.
- `v1.x` - this is the maintenance branch for the 1.x stream. If a fix *only* applies to 1.x, then it should target this branch. If it applies to the current stable release as well, target `master` first. We will then decide if it needs to be back ported to the 1.x stream.
- `dev` - this is the branch for new feature development targeting the next milestone release.
### Coding standards
Please ensure you follow the coding standards used through-out the existing

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "2.0.0-beta.2",
"version": "2.0.1",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -64,7 +64,7 @@
"mqtt": "4.2.8",
"multer": "1.4.2",
"mustache": "4.2.0",
"node-red-admin": "^2.1.0",
"node-red-admin": "^2.2.0",
"nopt": "5.0.0",
"oauth2orize": "1.11.0",
"on-headers": "1.0.2",

View File

@@ -244,7 +244,7 @@ module.exports = {
)
themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
theme.page = theme.page || {_:{}}
theme.page._.scripts = cssFiles.concat(theme.page._.scripts || [])
theme.page._.scripts = scriptFiles.concat(theme.page._.scripts || [])
}
}
activeThemeInitialised = true;

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "2.0.0-beta.2",
"version": "2.0.1",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/util": "2.0.0-beta.2",
"@node-red/editor-client": "2.0.0-beta.2",
"@node-red/util": "2.0.1",
"@node-red/editor-client": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
"clone": "2.1.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "2.0.0-beta.2",
"version": "2.0.1",
"license": "Apache-2.0",
"repository": {
"type": "git",

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

View File

@@ -674,11 +674,15 @@ RED.tabs = (function() {
});
}
$('<span class="red-ui-tabs-fade"></span>').appendTo(li);
var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
if (options.onselect) {
$('<i class="red-ui-tabs-badge-changed fa fa-circle"></i>').appendTo(badges);
$('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
}
if (options.onadd) {
options.onadd(tab);
}

View File

@@ -279,6 +279,8 @@ RED.sidebar.info.outliner = (function() {
if (node) {
if (node.type === 'group' || node._def.category !== "config") {
RED.view.select({nodes:[node]})
} else if (node._def.category === "config") {
RED.sidebar.info.refresh(node);
} else {
RED.view.select({nodes:[]})
}

View File

@@ -2369,6 +2369,7 @@ RED.view = (function() {
var textDimensionPlaceholder = {};
var textDimensionCache = {};
function calculateTextDimensions(str,className) {
var cacheKey = "!"+str;
if (!textDimensionPlaceholder[className]) {
textDimensionPlaceholder[className] = document.createElement("span");
textDimensionPlaceholder[className].className = className;
@@ -2377,15 +2378,15 @@ RED.view = (function() {
document.getElementById("red-ui-editor").appendChild(textDimensionPlaceholder[className]);
textDimensionCache[className] = {};
} else {
if (textDimensionCache[className][str]) {
return textDimensionCache[className][str]
if (textDimensionCache[className][cacheKey]) {
return textDimensionCache[className][cacheKey]
}
}
textDimensionPlaceholder[className].textContent = (str||"");
var w = textDimensionPlaceholder[className].offsetWidth;
var h = textDimensionPlaceholder[className].offsetHeight;
textDimensionCache[className][str] = [w,h];
return textDimensionCache[className][str];
textDimensionCache[className][cacheKey] = [w,h];
return textDimensionCache[className][cacheKey];
}
function convertLineBreakCharacter(str) {

View File

@@ -99,6 +99,9 @@
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.button-group &:focus {
position: relative;
}
.button-row &:not(:first-child) {
margin-left: 15px;
@@ -106,6 +109,7 @@
&:focus {
outline: 1px solid $workspace-button-color-focus-outline;
outline-offset: 1px;
}
&.primary {

View File

@@ -40,7 +40,7 @@
height: 7px;
box-sizing: border-box;
cursor: ns-resize;
background: $primary-background url(images/grip.png) no-repeat 50% 50%;
background: $primary-background url(images/grip-horizontal.png) no-repeat 50% 50%;
}
@@ -70,5 +70,6 @@
width: 7px;
display: inline-block;
cursor: ew-resize;
background: $primary-background url(images/grip.png) no-repeat 50% 50%;
}
}

View File

@@ -332,7 +332,7 @@ div.red-ui-info-table {
.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list, #red-ui-clipboard-dialog-export-tab-clipboard-preview
{
.red-ui-info-outline-item {
display: inline-block;
display: inline-flex;
padding: 0;
font-size: 13px;
border: none;
@@ -402,8 +402,7 @@ div.red-ui-info-table {
.red-ui-info-outline-gutter {
display:none;
button {
position: absolute;
top: 1px;
position: relative;
left: 2px;
}
.red-ui-treeList-label:hover & {

View File

@@ -102,10 +102,21 @@
img.red-ui-tab-icon {
opacity: 0.2;
}
.red-ui-tabs-fade {
background-image: linear-gradient(to right, transparent, $tab-background-active);
}
}
&.selected {
&:not(.active) {
background: $tab-background-selected;
.red-ui-tabs-fade {
background-image: linear-gradient(to right, transparent, $tab-background-selected);
}
.red-ui-tabs-badge-selected {
background: $tab-background-selected;
}
}
font-weight: bold;
.red-ui-tabs-badge-selected {
@@ -291,6 +302,15 @@
.red-ui-tabs.red-ui-tabs-add.red-ui-tabs-search .red-ui-tabs-add {
right: 38px;
}
.red-ui-tabs-fade {
position: absolute;
bottom: 0;
top: 0;
right: 0;
width: 15px;
background-image: linear-gradient(to right, transparent, $tab-background-inactive);
pointer-events: none;
}
img.red-ui-tab-icon {

View File

@@ -69,7 +69,8 @@
.red-ui-treeList-label {
@include disable-selection;
padding: 6px 0;
display: block;
display: flex;
align-items: center;
color: $list-item-color;
text-decoration: none;
cursor: pointer;

View File

@@ -7,36 +7,53 @@
padding: 0px;
}
#node-input-libs-container-row .red-ui-editableList-container li {
padding:5px;
padding:0px;
}
#node-input-libs-container-row .red-ui-editableList-item-remove {
right: 5px;
}
#node-input-libs-container-row .red-ui-editableList-header {
display: flex;
background: var(--red-ui-tertiary-background);
padding-right: 75px;
}
#node-input-libs-container-row .red-ui-editableList-header > div {
flex-grow: 1;
}
.node-libs-entry {
display: flex;
}
.node-libs-entry .node-input-libs-var, .node-libs-entry .red-ui-typedInput-container {
flex-grow: 1;
}
.node-libs-entry > code,.node-libs-entry > span {
line-height: 30px;
}
.node-libs-entry > input[type=text] {
border-radius: 0;
border-left: none;
border-top: none;
border-right: none;
padding-top: 2px;
padding-bottom: 2px;
margin-top: 4px;
margin-bottom: 2px;
height: 26px;
}
.node-libs-entry > span > i {
.node-libs-entry .red-ui-typedInput-container {
border-radius: 0;
border: none;
}
.node-libs-entry .red-ui-typedInput-type-select {
border-radius: 0 !important;
height: 34px;
}
.node-libs-entry > span > input[type=text] {
border-radius: 0;
border-top-color: var(--red-ui-form-background);
border-bottom-color: var(--red-ui-form-background);
border-right-color: var(--red-ui-form-background);
}
.node-libs-entry > span > input[type=text].input-error {
}
.node-libs-entry > span {
flex-grow: 1;
width: 50%;
position: relative;
}
.node-libs-entry span .node-input-libs-var, .node-libs-entry span .red-ui-typedInput-container {
width: 100%;
}
.node-libs-entry > span > span > i {
display: none;
}
.node-libs-entry > span.input-error > i {
.node-libs-entry > span > span.input-error > i {
display: inline;
}
@@ -209,47 +226,24 @@
})
var libList = $("#node-input-libs-container").css('min-height','100px').css('min-width','450px').editableList({
header: $('<div><div data-i18n="node-red:function.require.moduleName"></div><div data-i18n="node-red:function.require.importAs"></div></div>'),
addItem: function(container,i,opt) {
var parent = container.parent();
var row0 = $("<div/>").addClass("node-libs-entry").appendTo(container);
var fieldWidth = "260px";
$('<code>const </code>').appendTo(row0);
var fvar = $("<input/>", {
class: "node-input-libs-var red-ui-font-code",
placeholder: RED._("node-red:function.require.var"),
type: "text"
}).css({
width: "120px",
"margin-left": "5px"
}).appendTo(row0).val(opt.var);
var vnameWarning = $('<span style="display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(row0);
RED.popover.tooltip(vnameWarning.find("i"),function() {
var val = fvar.val();
if (invalidModuleVNames.indexOf(val) !== -1) {
return RED._("node-red:function.error.moduleNameReserved",{name:val})
} else {
return RED._("node-red:function.error.moduleNameError",{name:val})
}
})
$('<code> = require(</code>').appendTo(row0);
var fmoduleSpan = $("<span>").appendTo(row0);
var fmodule = $("<input/>", {
class: "node-input-libs-val",
placeholder: RED._("node-red:function.require.module"),
type: "text"
}).css({
width: "180px",
}).appendTo(row0).typedInput({
}).appendTo(fmoduleSpan).typedInput({
types: typedModules,
default: usedModules.indexOf(opt.module) > -1 ? opt.module : "_custom_"
});
if (usedModules.indexOf(opt.module) === -1) {
fmodule.typedInput('value', opt.module);
}
$('<code>)</code>').appendTo(row0);
var moduleWarning = $('<span style="display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(row0);
var moduleWarning = $('<span style="position: absolute;right:2px;top:7px; display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fmoduleSpan);
RED.popover.tooltip(moduleWarning.find("i"),function() {
var val = fmodule.typedInput("type");
if (val === "_custom_") {
@@ -264,6 +258,26 @@
}
})
var fvarSpan = $("<span>").appendTo(row0);
var fvar = $("<input/>", {
class: "node-input-libs-var red-ui-font-code",
placeholder: RED._("node-red:function.require.var"),
type: "text"
}).css({
}).appendTo(fvarSpan).val(opt.var);
var vnameWarning = $('<span style="position: absolute; right:2px;top:7px;display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fvarSpan);
RED.popover.tooltip(vnameWarning.find("i"),function() {
var val = fvar.val();
if (invalidModuleVNames.indexOf(val) !== -1) {
return RED._("node-red:function.error.moduleNameReserved",{name:val})
} else {
return RED._("node-red:function.error.moduleNameError",{name:val})
}
})
fvar.on("change keyup paste", function (e) {
var v = $(this).val().trim();
if (v === "" || / /.test(v) || invalidModuleVNames.indexOf(v) !== -1) {
@@ -280,7 +294,7 @@
if (val === "_custom_") {
val = $(this).val();
}
var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/]./g, function(v) { return v[1].toUpperCase() });
var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/].?/g, function(v) { return v[1]?v[1].toUpperCase():"" });
fvar.val(varName);
fvar.trigger("change");
@@ -315,7 +329,7 @@
function getLibsList() {
var _libs = [];
if (RED.settings.functionExternalModules === true) {
if (RED.settings.functionExternalModules !== false) {
var libs = $("#node-input-libs-container").editableList("items");
libs.each(function(i) {
var item = $(this);
@@ -580,15 +594,15 @@
$("#dialog-form .node-text-editor").css("height",height+"px");
var height = size.height;
$("#node-input-init-editor").css("height", (height -45-48)+"px");
$("#node-input-func-editor").css("height", (height -45-48)+"px");
$("#node-input-finalize-editor").css("height", (height -45-48)+"px");
$("#node-input-init-editor").css("height", (height - 83)+"px");
$("#node-input-func-editor").css("height", (height - 83)+"px");
$("#node-input-finalize-editor").css("height", (height - 83)+"px");
this.initEditor.resize();
this.editor.resize();
this.finalizeEditor.resize();
$("#node-input-libs-container").css("height", (height - 185)+"px");
$("#node-input-libs-container").css("height", (height - 192)+"px");
}
});
})();

View File

@@ -100,7 +100,7 @@ module.exports = function(RED) {
node.fin = n.finalize ? n.finalize.trim() : "";
node.libs = n.libs || [];
if (RED.settings.functionExternalModules !== true && node.libs.length > 0) {
if (RED.settings.functionExternalModules === false && node.libs.length > 0) {
throw new Error(RED._("function.error.externalModuleNotAllowed"));
}
@@ -290,6 +290,7 @@ module.exports = function(RED) {
};
sandbox.promisify = util.promisify;
}
const moduleLoadPromises = [];
if (node.hasOwnProperty("libs")) {
let moduleErrors = false;
@@ -303,25 +304,21 @@ module.exports = function(RED) {
return;
}
sandbox[vname] = null;
try {
var spec = module.module;
if (spec && (spec !== "")) {
var lib = RED.require(module.module);
sandbox[vname] = lib;
}
} catch (e) {
//TODO: NLS error message
node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:e.toString()}))
moduleErrors = true;
var spec = module.module;
if (spec && (spec !== "")) {
moduleLoadPromises.push(RED.import(module.module).then(lib => {
sandbox[vname] = lib.default;
}).catch(err => {
node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:err.toString()}))
throw err;
}));
}
}
});
if (moduleErrors) {
throw new Error(RED._("function.error.externalModuleLoadError"));
}
throw new Error(RED._("function.error.externalModuleLoadError"));
}
}
const RESOLVING = 0;
const RESOLVED = 1;
const ERROR = 2;
@@ -337,170 +334,173 @@ module.exports = function(RED) {
processMessage(msg, send, done);
}
});
var context = vm.createContext(sandbox);
try {
var iniScript = null;
var iniOpt = null;
if (node.ini && (node.ini !== "")) {
var iniText = `
(async function(__send__) {
var node = {
id:__node__.id,
name:__node__.name,
outputCount:__node__.outputCount,
log:__node__.log,
error:__node__.error,
warn:__node__.warn,
debug:__node__.debug,
trace:__node__.trace,
status:__node__.status,
send: function(msgs, cloneMsg) {
__node__.send(__send__, RED.util.generateId(), msgs, cloneMsg);
}
};
`+ node.ini +`
})(__initSend__);`;
iniOpt = createVMOpt(node, " setup");
iniScript = new vm.Script(iniText, iniOpt);
}
node.script = vm.createScript(functionText, createVMOpt(node, ""));
if (node.fin && (node.fin !== "")) {
var finText = `(function () {
var node = {
id:__node__.id,
name:__node__.name,
outputCount:__node__.outputCount,
log:__node__.log,
error:__node__.error,
warn:__node__.warn,
debug:__node__.debug,
trace:__node__.trace,
status:__node__.status,
send: function(msgs, cloneMsg) {
__node__.error("Cannot send from close function");
}
};
`+node.fin +`
})();`;
finOpt = createVMOpt(node, " cleanup");
finScript = new vm.Script(finText, finOpt);
}
var promise = Promise.resolve();
if (iniScript) {
context.__initSend__ = function(msgs) { node.send(msgs); };
promise = iniScript.runInContext(context, iniOpt);
}
processMessage = function (msg, send, done) {
var start = process.hrtime();
context.msg = msg;
context.__send__ = send;
context.__done__ = done;
node.script.runInContext(context);
context.results.then(function(results) {
sendResults(node,send,msg._msgid,results,false);
if (handleNodeDoneCall) {
done();
}
var duration = process.hrtime(start);
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
node.metric("duration", msg, converted);
if (process.env.NODE_RED_FUNCTION_TIME) {
node.status({fill:"yellow",shape:"dot",text:""+converted});
}
}).catch(err => {
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
//remove unwanted part
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
var stack = err.stack.split(/\r?\n/);
//store the error in msg to be used in flows
msg.error = err;
var line = 0;
var errorMessage;
if (stack.length > 0) {
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++;
Promise.all(moduleLoadPromises).then(() => {
var context = vm.createContext(sandbox);
try {
var iniScript = null;
var iniOpt = null;
if (node.ini && (node.ini !== "")) {
var iniText = `
(async function(__send__) {
var node = {
id:__node__.id,
name:__node__.name,
outputCount:__node__.outputCount,
log:__node__.log,
error:__node__.error,
warn:__node__.warn,
debug:__node__.debug,
trace:__node__.trace,
status:__node__.status,
send: function(msgs, cloneMsg) {
__node__.send(__send__, RED.util.generateId(), msgs, cloneMsg);
}
};
`+ node.ini +`
})(__initSend__);`;
iniOpt = createVMOpt(node, " setup");
iniScript = new vm.Script(iniText, iniOpt);
}
node.script = vm.createScript(functionText, createVMOpt(node, ""));
if (node.fin && (node.fin !== "")) {
var finText = `(function () {
var node = {
id:__node__.id,
name:__node__.name,
outputCount:__node__.outputCount,
log:__node__.log,
error:__node__.error,
warn:__node__.warn,
debug:__node__.debug,
trace:__node__.trace,
status:__node__.status,
send: function(msgs, cloneMsg) {
__node__.error("Cannot send from close function");
}
};
`+node.fin +`
})();`;
finOpt = createVMOpt(node, " cleanup");
finScript = new vm.Script(finText, finOpt);
}
var promise = Promise.resolve();
if (iniScript) {
context.__initSend__ = function(msgs) { node.send(msgs); };
promise = iniScript.runInContext(context, iniOpt);
}
if (line < stack.length) {
errorMessage = stack[line];
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
if (m) {
var lineno = Number(m[1])-1;
var cha = m[2];
errorMessage += " (line "+lineno+", col "+cha+")";
processMessage = function (msg, send, done) {
var start = process.hrtime();
context.msg = msg;
context.__send__ = send;
context.__done__ = done;
node.script.runInContext(context);
context.results.then(function(results) {
sendResults(node,send,msg._msgid,results,false);
if (handleNodeDoneCall) {
done();
}
var duration = process.hrtime(start);
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
node.metric("duration", msg, converted);
if (process.env.NODE_RED_FUNCTION_TIME) {
node.status({fill:"yellow",shape:"dot",text:""+converted});
}
}).catch(err => {
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
//remove unwanted part
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
var stack = err.stack.split(/\r?\n/);
//store the error in msg to be used in flows
msg.error = err;
var line = 0;
var errorMessage;
if (stack.length > 0) {
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++;
}
if (line < stack.length) {
errorMessage = stack[line];
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
if (m) {
var lineno = Number(m[1])-1;
var cha = m[2];
errorMessage += " (line "+lineno+", col "+cha+")";
}
}
}
if (!errorMessage) {
errorMessage = err.toString();
}
done(errorMessage);
}
if (!errorMessage) {
errorMessage = err.toString();
else if (typeof err === "string") {
done(err);
}
else {
done(JSON.stringify(err));
}
});
}
node.on("close", function() {
if (finScript) {
try {
finScript.runInContext(context, finOpt);
}
catch (err) {
node.error(err);
}
done(errorMessage);
}
else if (typeof err === "string") {
done(err);
while (node.outstandingTimers.length > 0) {
clearTimeout(node.outstandingTimers.pop());
}
else {
done(JSON.stringify(err));
while (node.outstandingIntervals.length > 0) {
clearInterval(node.outstandingIntervals.pop());
}
if (node.clearStatus) {
node.status({});
}
});
}
node.on("close", function() {
if (finScript) {
try {
finScript.runInContext(context, finOpt);
}
catch (err) {
node.error(err);
}
}
while (node.outstandingTimers.length > 0) {
clearTimeout(node.outstandingTimers.pop());
}
while (node.outstandingIntervals.length > 0) {
clearInterval(node.outstandingIntervals.pop());
}
if (node.clearStatus) {
node.status({});
}
});
promise.then(function (v) {
var msgs = messages;
messages = [];
while (msgs.length > 0) {
msgs.forEach(function (s) {
processMessage(s.msg, s.send, s.done);
});
msgs = messages;
promise.then(function (v) {
var msgs = messages;
messages = [];
}
state = RESOLVED;
}).catch((error) => {
messages = [];
state = ERROR;
node.error(error);
});
while (msgs.length > 0) {
msgs.forEach(function (s) {
processMessage(s.msg, s.send, s.done);
});
msgs = messages;
messages = [];
}
state = RESOLVED;
}).catch((error) => {
messages = [];
state = ERROR;
node.error(error);
});
}
catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this
updateErrorInfo(err);
node.error(err);
}
}
catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this
updateErrorInfo(err);
node.error(err);
}
}).catch(err => {
node.error(RED._("function.error.externalModuleLoadError"));
});
}
RED.nodes.registerType("function",FunctionNode, {
dynamicModuleList: "libs",
settings: {
functionExternalModules: { value: false, exportable: true }
functionExternalModules: { value: true, exportable: true }
}
});
RED.library.register("functions");

View File

@@ -205,23 +205,26 @@ module.exports = function(RED) {
}
}
}
if (this.credentials) {
var parsedURL = new URL(url)
this.credentials = this.credentials || {}
if (parsedURL.username && !this.credentials.user) {
this.credentials.user = parsedURL.username
}
if (parsedURL.password && !this.credentials.password) {
this.credentials.password = parsedURL.password
}
if (Object.keys(this.credentials).length != 0) {
if (this.authType === "basic") {
// Workaround for https://github.com/sindresorhus/got/issues/1169
var cred = ""
var parsedURL = new URL(url)
if (this.credentials.user) {
// opts.username = this.credentials.user;
cred = this.credentials.user
} else if (parsedURL.username) {
cred = parsedURL.username
}
}
if (this.credentials.password) {
// opts.password = this.credentials.password;
cred += ":" + this.credentials.password
} else if (parsedURL.password) {
cred += ":" + parsedURL.password
}
}
// build own basic auth header
opts.headers.Authorization = "Basic " + Buffer.from(cred).toString("base64");
} else if (this.authType === "digest") {

View File

@@ -176,7 +176,8 @@
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
tls: {type:"tls-config",required: false},
wholemsg: {value:"false"}
wholemsg: {value:"false"},
hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) }
},
inputs:0,
outputs:0,
@@ -188,11 +189,24 @@
$(".node-config-row-tls").toggle(/^wss:/i.test($(this).val()))
});
$("#node-config-input-path").change();
var heartbeatActive = (this.hb && this.hb != "0");
$("#node-config-input-hb-cb").prop("checked",heartbeatActive);
$("#node-config-input-hb-cb").on("change", function(evt) {
$("#node-config-input-hb-row").toggle(this.checked);
})
$("#node-config-input-hb-cb").trigger("change");
if (!heartbeatActive) {
$("#node-config-input-hb").val("");
}
},
oneditsave: function() {
if (!/^wss:/i.test($("#node-config-input-path").val())) {
$("#node-config-input-tls").val("_ADD_");
}
if (!$("#node-config-input-hb-cb").prop("checked")) {
$("#node-config-input-hb").val("0");
}
}
});
@@ -259,6 +273,14 @@
<option value="true" data-i18n="websocket.message"></option>
</select>
</div>
<div class="form-row" style="display: flex; align-items: center; min-height: 34px">
<label for="node-config-input-hb-cb" data-i18n="websocket.sendheartbeat"></label>
<input type="checkbox" style="margin: 0 8px; width:auto" id="node-config-input-hb-cb">
<span id="node-config-input-hb-row" class="hide" >
<input type="text" style="width: 70px; margin-right: 3px" id="node-config-input-hb">
<span data-i18n="inject.seconds"></span>
</span>
</div>
<div class="form-tips">
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
<span data-i18n="[html]websocket.tip.url2"></span>

View File

@@ -55,6 +55,13 @@ module.exports = function(RED) {
node.closing = false;
node.tls = n.tls;
if (n.hb) {
var heartbeat = parseInt(n.hb);
if (heartbeat > 0) {
node.heartbeat = heartbeat * 1000;
}
}
function startconn() { // Connect to remote endpoint
node.tout = null;
var prox, noprox;
@@ -93,9 +100,24 @@ module.exports = function(RED) {
function handleConnection(/*socket*/socket) {
var id = RED.util.generateId();
socket.nrId = id;
socket.nrPendingHeartbeat = false;
if (node.isServer) {
node._clients[id] = socket;
node.emit('opened',{count:Object.keys(node._clients).length,id:id});
} else {
if (node.heartbeat) {
node.heartbeatInterval = setInterval(function() {
if (socket.nrPendingHeartbeat) {
// No pong received
socket.terminate();
socket.nrErrorHandler(new Error("timeout"));
return;
}
socket.nrPendingHeartbeat = true;
socket.ping();
},node.heartbeat);
}
}
socket.on('open',function() {
if (!node.isServer) {
@@ -103,6 +125,7 @@ module.exports = function(RED) {
}
});
socket.on('close',function() {
clearInterval(node.heartbeatInterval);
if (node.isServer) {
delete node._clients[id];
node.emit('closed',{count:Object.keys(node._clients).length,id:id});
@@ -117,13 +140,21 @@ module.exports = function(RED) {
socket.on('message',function(data,flags) {
node.handleEvent(id,socket,'message',data,flags);
});
socket.on('error', function(err) {
socket.nrErrorHandler = function(err) {
clearInterval(node.heartbeatInterval);
node.emit('erro',{err:err,id:id});
if (!node.closing && !node.isServer) {
clearTimeout(node.tout);
node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
}
});
}
socket.on('error',socket.nrErrorHandler);
socket.on('ping', function() {
socket.nrPendingHeartbeat = false;
})
socket.on('pong', function() {
socket.nrPendingHeartbeat = false;
})
}
if (node.isServer) {
@@ -152,6 +183,19 @@ module.exports = function(RED) {
node.server = new ws.Server(serverOptions);
node.server.setMaxListeners(0);
node.server.on('connection', handleConnection);
// Not adding server-initiated heartbeats yet
// node.heartbeatInterval = setInterval(function() {
// node.server.clients.forEach(function(ws) {
// if (ws.nrPendingHeartbeat) {
// // No pong received
// ws.terminate();
// ws.nrErrorHandler(new Error("timeout"));
// return;
// }
// ws.nrPendingHeartbeat = true;
// ws.ping();
// });
// })
}
else {
node.closing = false;
@@ -159,6 +203,9 @@ module.exports = function(RED) {
}
node.on("close", function() {
if (node.heartbeatInterval) {
clearInterval(node.heartbeatInterval);
}
if (node.isServer) {
delete listenerNodes[node.fullPath];
node.server.close();
@@ -168,8 +215,6 @@ module.exports = function(RED) {
// RED.server.removeListener('upgrade', handleServerUpgrade);
// serverUpgradeAdded = false;
// }
}
else {
node.closing = true;

View File

@@ -226,7 +226,9 @@
},
"require": {
"var": "variable",
"module": "module"
"module": "module",
"moduleName": "Module name",
"importAs": "Import as"
},
"error": {
"externalModuleNotAllowed": "Function node not allowed to load external modules",
@@ -512,6 +514,7 @@
"sendrec": "Send/Receive",
"payload": "payload",
"message": "entire message",
"sendheartbeat": "Send heartbeat",
"tip": {
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
"path2": "This path will be relative to <code>__path__</code>.",

View File

@@ -66,6 +66,7 @@
<p>コマンドもしくはパラメータが空白を含む場合には引用符で囲みます- <code>"これは一つのパラメータです"</code></p>
<p>返却する<code>payload</code>は通常<i>文字列</i>ですがUTF8文字以外が存在すると<i>バッファ</i></p>
<p>ノードが実行中の場合ステータスアイコンとPIDを表示しますこの状態変化は<code>Status</code></p>
<p><code>コンソールを非表示</code>Windows</p>
<h4>プロセスの停止</h4>
<p><code>msg.kill</code><code>msg.kill</code><code>SIGINT</code><code>SIGQUIT</code><code>SIGHUP</code><code>SIGTERM</code></p>
<p>ードが1つ以上のプロセスを実行している場合<code>msg.pid</code>PID</p>

View File

@@ -198,7 +198,8 @@
"seconds": "秒",
"stdout": "標準出力",
"stderr": "標準エラー出力",
"retcode": "返却コード"
"retcode": "返却コード",
"winHide": "コンソールを非表示"
},
"placeholder": {
"extraparams": "追加引数"
@@ -225,7 +226,9 @@
},
"require": {
"var": "変数",
"module": "モジュール"
"module": "モジュール",
"moduleName": "モジュール名",
"importAs": "インポート名"
},
"error": {
"externalModuleNotAllowed": "Functionードは、外部モジュールを読み込みできません",
@@ -511,6 +514,7 @@
"sendrec": "送信/受信",
"payload": "ペイロードを送信/受信",
"message": "メッセージ全体を送信/受信",
"sendheartbeat": "ハートビートを送信",
"tip": {
"path1": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
"path2": "このパスは <code>__path__</code> の相対パスになります。",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "2.0.0-beta.2",
"version": "2.0.1",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@@ -12,7 +12,6 @@ const log = require("@node-red/util").log;
const hooks = require("@node-red/util").hooks;
const BUILTIN_MODULES = require('module').builtinModules;
const EXTERNAL_MODULES_DIR = "externalModules";
// TODO: outsource running npm to a plugin
const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";
@@ -27,16 +26,37 @@ let installEnabled = true;
let installAllowList = ['*'];
let installDenyList = [];
function getInstallDir() {
return path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", "externalModules"));
let IMPORT_SUPPORTED = true;
const nodeVersionParts = process.versions.node.split(".").map(v => parseInt(v));
if (nodeVersionParts[0] < 12 || (nodeVersionParts[0] === 12 && nodeVersionParts[1] < 17)) {
IMPORT_SUPPORTED = false;
}
function getInstallDir() {
return path.resolve(settings.userDir || process.env.NODE_RED_HOME || ".");
}
let loggedLegacyWarning = false;
async function refreshExternalModules() {
const externalModuleDir = path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", EXTERNAL_MODULES_DIR));
if (!loggedLegacyWarning) {
loggedLegacyWarning = true;
const oldExternalModulesDir = path.join(path.resolve(settings.userDir || process.env.NODE_RED_HOME || "."),"externalModules");
if (fs.existsSync(oldExternalModulesDir)) {
try {
log.warn(log._("server.install.old-ext-mod-dir-warning",{oldDir:oldExternalModulesDir, newDir:getInstallDir()}))
} catch(err) {console.log(err)}
}
}
const externalModuleDir = getInstallDir();
try {
const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
knownExternalModules = pkgFile.dependencies;
knownExternalModules = pkgFile.dependencies || {};
} catch(err) {
knownExternalModules = {};
}
}
@@ -44,6 +64,7 @@ function init(_settings) {
settings = _settings;
knownExternalModules = {};
installEnabled = true;
if (settings.externalModules && settings.externalModules.modules) {
if (settings.externalModules.modules.allowList || settings.externalModules.modules.denyList) {
installAllowList = settings.externalModules.modules.allowList;
@@ -82,10 +103,45 @@ function requireModule(module) {
e.code = "module_not_allowed";
throw e;
}
const externalModuleDir = path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", EXTERNAL_MODULES_DIR));
const externalModuleDir = getInstallDir();
const moduleDir = path.join(externalModuleDir,"node_modules",module);
return require(moduleDir);
}
function importModule(module) {
if (!IMPORT_SUPPORTED) {
// On Node < 12.17 - fall back to try a require
return new Promise((resolve, reject) => {
try {
const mod = requireModule(module);
resolve(mod);
} catch(err) {
reject(err);
}
});
}
if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
const e = new Error("Module not allowed");
e.code = "module_not_allowed";
throw e;
}
const parsedModule = parseModuleName(module);
if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
return import(parsedModule.module);
}
if (!knownExternalModules[parsedModule.module]) {
const e = new Error("Module not allowed");
e.code = "module_not_allowed";
throw e;
}
const externalModuleDir = getInstallDir();
const moduleDir = path.join(externalModuleDir,"node_modules",module);
// Import needs the full path to the module's main .js file
const moduleFile = require.resolve(moduleDir);
return import(moduleFile);
}
function parseModuleName(module) {
var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
@@ -214,6 +270,9 @@ async function installModule(moduleDetails) {
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;
@@ -235,9 +294,10 @@ async function installModule(moduleDetails) {
}
module.exports = {
init: init,
register: register,
registerSubflow: registerSubflow,
checkFlowDependencies: checkFlowDependencies,
require: requireModule
init,
register,
registerSubflow,
checkFlowDependencies,
require: requireModule,
import: importModule
}

View File

@@ -223,8 +223,8 @@ async function loadNodeTemplate(node) {
// ENOENT means no html file. We can live with that. But any other error
// should be fatal
// node.err = "Error: "+node.template+" does not exist";
node.types = node.types || [];
if (err.code !== 'ENOENT') {
node.types = [];
node.err = err.toString();
}
return node;
@@ -263,7 +263,7 @@ async function loadNodeConfig(fileInfo) {
if (info.hasOwnProperty("loaded")) {
throw new Error(file+" already loaded");
}
isEnabled = info.enabled;
isEnabled = !(info.enabled === false);
}
var node = {
@@ -353,6 +353,7 @@ async function loadPluginConfig(fileInfo) {
*/
function loadNodeSet(node) {
if (!node.enabled) {
console.log("BAIL ON",node.id)
return Promise.resolve(node);
} else {
}

View File

@@ -194,7 +194,7 @@ function addModule(module) {
if (!set.err) {
set.types.forEach(function(t) {
if (nodeTypeToId.hasOwnProperty(t)) {
set.err = "Type already registered";
set.err = new Error("Type already registered");
set.err.code = "type_already_registered";
set.err.details = {
type: t,

View File

@@ -50,6 +50,16 @@ function requireModule(name) {
return require("./externalModules").require(name);
}
}
function importModule(name) {
var moduleInfo = require("./index").getModuleInfo(name);
if (moduleInfo && moduleInfo.path) {
var relPath = path.relative(__dirname, moduleInfo.path);
return import(relPath);
} else {
// Require it here to avoid the circular dependency
return require("./externalModules").import(name);
}
}
function createNodeApi(node) {
var red = {
@@ -61,6 +71,7 @@ function createNodeApi(node) {
util: runtime.util,
version: runtime.version,
require: requireModule,
import: importModule,
comms: {
publish: function(topic,data,retain) {
events.emit("comms",{

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "2.0.0-beta.2",
"version": "2.0.1",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,7 +16,9 @@
}
],
"dependencies": {
"@node-red/util": "2.0.0-beta.2",
"@node-red/util": "2.0.1",
"clone": "2.1.2",
"fs-extra": "10.0.0",
"semver": "7.3.5",
"tar": "6.1.0",
"uglify-js": "3.13.10"

View File

@@ -20,7 +20,7 @@ const fspath = require("path");
const log = require("@node-red/util").log;
const util = require("./util");
const configSections = ['nodes','users','projects'];
const configSections = ['nodes','users','projects','modules'];
const settingsCache = {};
@@ -59,6 +59,7 @@ async function migrateToMultipleConfigFiles() {
* - .config.nodes.json - the node registry
* - .config.users.json - user specific settings (eg editor settings)
* - .config.projects.json - project settings, including the active project
* - .config.modules.json - external modules installed by the runtime
* - .config.runtime.json - everything else - most notable _credentialSecret
*/
function writeSettings(data) {

View File

@@ -41,7 +41,8 @@
"uninstalling": "Uninstalling module: __name__",
"uninstall-failed": "Uninstall failed",
"uninstall-failed-long": "Uninstall of module __name__ failed:",
"uninstalled": "Uninstalled module: __name__"
"uninstalled": "Uninstalled module: __name__",
"old-ext-mod-dir-warning": "\n\n---------------------------------------------------------------------\nNode-RED 1.3 external modules directory detected:\n __oldDir__\nThis directory is no longer used. External Modules will be\nreinstalled in your Node-RED user directory:\n __newDir__\nDelete the old externalModules directory to stop this message.\n---------------------------------------------------------------------\n"
},
"deprecatedOption": "Use of __old__ is DEPRECATED. Use __new__ instead",
"unable-to-listen": "Unable to listen on __listenpath__",

View File

@@ -40,7 +40,8 @@
"uninstalling": "モジュールをアンインストールします: __name__",
"uninstall-failed": "アンインストールに失敗しました",
"uninstall-failed-long": "モジュール __name__ のアンインストールに失敗しました:",
"uninstalled": "モジュール __name__ をアンインストールしました"
"uninstalled": "モジュール __name__ をアンインストールしました",
"old-ext-mod-dir-warning": "\n\n---------------------------------------------------------------------\nNode-RED 1.3の外部モジュールディレクトリが存在します:\n __oldDir__\nこのディレクトリは使用しなくなりました。外部モジュールは\nNode-REDユーザディレクトリにインストールされます:\n __newDir__\nこのメッセージ出力を止めるには、古いexternalModules\nディレクトリを削除してください。\n---------------------------------------------------------------------\n"
},
"deprecatedOption": "__old__ の利用は非推奨です。代わりに __new__ を使用してください",
"unable-to-listen": "__listenpath__ に対してlistenできません",
@@ -84,7 +85,8 @@
"settings": {
"user-not-available": "ユーザ設定を保存できません: __message__",
"not-available": "設定が利用できません",
"property-read-only": "プロパティ '__prop__' は読み出し専用です"
"property-read-only": "プロパティ '__prop__' は読み取り専用です",
"readonly-mode": "ランタイムは読み取り専用モードです。変更は保存されません。"
},
"library": {
"unknownLibrary": "不明なライブラリ: __library__",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "2.0.0-beta.2",
"version": "2.0.1",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/registry": "2.0.0-beta.2",
"@node-red/util": "2.0.0-beta.2",
"@node-red/registry": "2.0.1",
"@node-red/util": "2.0.1",
"async-mutex": "0.3.1",
"clone": "2.1.2",
"express": "4.17.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "2.0.0-beta.2",
"version": "2.0.1",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -15,7 +15,7 @@
}
],
"dependencies": {
"clone": "2.1.2",
"fs-extra": "10.0.0",
"i18next": "20.3.2",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.4",

View File

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

View File

@@ -396,7 +396,7 @@ module.exports = {
//fileWorkingDirectory: "",
/** Allow the Function node to load additional npm modules directly */
functionExternalModules: false,
functionExternalModules: true,
/** The following property can be used to set predefined values in Global Context.
* This allows extra node modules to be made available with in Function node.

View File

@@ -1461,11 +1461,12 @@ describe('function node', function() {
afterEach(function() {
delete RED.settings.functionExternalModules;
})
it('should fail if using OS module without functionExternalModules set to true', function(done) {
it('should fail if using OS module with functionExternalModules set to false', function(done) {
var flow = [
{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = os.type(); return msg;", "libs": [{var:"os", module:"os"}]},
{id:"n2", type:"helper"}
];
RED.settings.functionExternalModules = false;
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
should.not.exist(n1);
@@ -1478,7 +1479,6 @@ describe('function node', function() {
{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = os.type(); return msg;"},
{id:"n2", type:"helper"}
];
RED.settings.functionExternalModules = true;
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -1502,7 +1502,6 @@ describe('function node', function() {
{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = os.type(); return msg;", "libs": [{var:"os", module:"os"}]},
{id:"n2", type:"helper"}
];
RED.settings.functionExternalModules = true;
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -1523,7 +1522,6 @@ describe('function node', function() {
{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = os.type(); return msg;", "libs": [{var:"flow", module:"os"}]},
{id:"n2", type:"helper"}
];
RED.settings.functionExternalModules = true;
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
should.not.exist(n1);

View File

@@ -26,8 +26,7 @@ async function createUserDir() {
}
async function setupExternalModulesPackage(dependencies) {
await fs.ensureDir(path.join(homeDir,"externalModules"))
await fs.writeFile(path.join(homeDir,"externalModules","package.json"),`{
await fs.writeFile(path.join(homeDir,"package.json"),`{
"name": "Node-RED-External-Modules",
"description": "These modules are automatically installed by Node-RED to use in Function nodes.",
"version": "1.0.0",
@@ -68,7 +67,7 @@ describe("externalModules api", function() {
exec.run.restore();
})
it("does nothing when no types are registered",async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]}
])
@@ -76,7 +75,7 @@ describe("externalModules api", function() {
});
it("skips install for modules already installed", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
await setupExternalModulesPackage({"foo": "1.2.3", "bar":"2.3.4"});
await externalModules.checkFlowDependencies([
@@ -86,7 +85,7 @@ describe("externalModules api", function() {
})
it("skips install for built-in modules", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "fs"}]}
@@ -95,19 +94,17 @@ describe("externalModules api", function() {
})
it("installs missing modules", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
fs.existsSync(path.join(homeDir,"externalModules")).should.be.false();
await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]}
])
exec.run.called.should.be.true();
fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
})
it("calls pre/postInstall hooks", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
let receivedPreEvent,receivedPostEvent;
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
@@ -122,11 +119,10 @@ describe("externalModules api", function() {
receivedPreEvent.should.have.property("version")
receivedPreEvent.should.have.property("dir")
receivedPreEvent.should.eql(receivedPostEvent)
fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
})
it("skips npm install if preInstall returns false", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
let receivedPreEvent,receivedPostEvent;
hooks.add("preInstall", function(event) { receivedPreEvent = event; return false })
@@ -140,12 +136,11 @@ describe("externalModules api", function() {
receivedPreEvent.should.have.property("version")
receivedPreEvent.should.have.property("dir")
receivedPreEvent.should.eql(receivedPostEvent)
fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
})
it("installs missing modules from inside subflow module", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
externalModules.registerSubflow("sf", {"flow":[{type: "function", libs:[{module: "foo"}]}]});
await externalModules.checkFlowDependencies([
@@ -155,7 +150,7 @@ describe("externalModules api", function() {
})
it("reports install fail - 404", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
@@ -174,7 +169,7 @@ describe("externalModules api", function() {
}
})
it("reports install fail - target", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
@@ -193,7 +188,7 @@ describe("externalModules api", function() {
})
it("reports install fail - unexpected", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
@@ -211,7 +206,7 @@ describe("externalModules api", function() {
}
})
it("reports install fail - multiple", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
externalModules.register("function", "libs");
try {
await externalModules.checkFlowDependencies([
@@ -238,7 +233,7 @@ describe("externalModules api", function() {
}
})
it("reports install fail - install disabled", async function() {
externalModules.init({userDir: homeDir, externalModules: {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
allowInstall: false
}
@@ -262,7 +257,7 @@ describe("externalModules api", function() {
})
it("reports install fail - module disallowed", async function() {
externalModules.init({userDir: homeDir, externalModules: {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
denyList: ['foo']
}
@@ -287,7 +282,7 @@ describe("externalModules api", function() {
})
it("reports install fail - built-in module disallowed", async function() {
externalModules.init({userDir: homeDir, externalModules: {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
denyList: ['fs']
}
@@ -313,12 +308,12 @@ describe("externalModules api", function() {
})
describe("require", async function() {
it("requires built-in modules", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
const result = externalModules.require("fs")
result.should.eql(require("fs"));
})
it("rejects unknown modules", async function() {
externalModules.init({userDir: homeDir});
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
try {
externalModules.require("foo")
throw new Error("require did not reject after fail")
@@ -328,7 +323,7 @@ describe("externalModules api", function() {
})
it("rejects disallowed modules", async function() {
externalModules.init({userDir: homeDir, externalModules: {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
denyList: ['fs']
}
@@ -341,4 +336,36 @@ describe("externalModules api", function() {
}
})
})
describe("import", async function() {
it("import built-in modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
const result = await externalModules.import("fs")
// `result` won't have the `should` property
should.exist(result);
should.exist(result.existsSync);
})
it("rejects unknown modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
try {
await externalModules.import("foo")
throw new Error("import did not reject after fail")
} catch(err) {
err.should.have.property("code","module_not_allowed");
}
})
it("rejects disallowed modules", async function() {
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
modules: {
denyList: ['fs']
}
}});
try {
await externalModules.import("fs")
throw new Error("import did not reject after fail")
} catch(err) {
err.should.have.property("code","module_not_allowed");
}
})
})
});