mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
62 Commits
delay-queu
...
2.0.1
Author | SHA1 | Date | |
---|---|---|---|
|
c9597b9447 | ||
|
b2dc1d8b23 | ||
|
859c0c7f6c | ||
|
aaf18e2416 | ||
|
fd679ef117 | ||
|
6cc611b3f1 | ||
|
77ee726f66 | ||
|
dc603b76a4 | ||
|
bcb3371acc | ||
|
d14ce7e476 | ||
|
47f7b43bcc | ||
|
77fd8c120c | ||
|
a1a6f40158 | ||
|
ed09cd7489 | ||
|
eb3330d145 | ||
|
5ba0588c7b | ||
|
d4a199f0e1 | ||
|
32dd186f4d | ||
|
81f0fb3c74 | ||
|
972c83cd52 | ||
|
66a704af55 | ||
|
31c5d6e1c1 | ||
|
bf0ab95c09 | ||
|
c1d85f760d | ||
|
88ad2f4c18 | ||
|
be9f9e7b0c | ||
|
2cc1973f62 | ||
|
eb4625a0b9 | ||
|
5bfb01254b | ||
|
bb80fa4a2d | ||
|
ddb715d88d | ||
|
395b499856 | ||
|
cce6a47f11 | ||
|
7fd17b4ec0 | ||
|
e16ab2a0fd | ||
|
15f5364c30 | ||
|
65081767bf | ||
|
c7c595e5fa | ||
|
5b24e8b69c | ||
|
e6a845e606 | ||
|
ec8b8a7b87 | ||
|
51a9205105 | ||
|
ed5567fc73 | ||
|
4b3f5d74a0 | ||
|
b01c5a05e7 | ||
|
36eddabc1c | ||
|
e7efa76e6d | ||
|
4624079be7 | ||
|
c6f6042271 | ||
|
e9e3b9b7c6 | ||
|
becbb09a29 | ||
|
6f6ab50995 | ||
|
d8ee766860 | ||
|
108c26d8af | ||
|
ed8d3088ca | ||
|
46c4e2d212 | ||
|
94891d45f9 | ||
|
7448ad109e | ||
|
6211dfe024 | ||
|
9b85200954 | ||
|
94ee739d91 | ||
|
e81a6db9a3 |
35
.github/ISSUE_TEMPLATE.md
vendored
35
.github/ISSUE_TEMPLATE.md
vendored
@@ -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:
|
39
.github/ISSUE_TEMPLATE/--bug_report.md
vendored
39
.github/ISSUE_TEMPLATE/--bug_report.md
vendored
@@ -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:
|
17
.github/ISSUE_TEMPLATE/-anything-else.md
vendored
17
.github/ISSUE_TEMPLATE/-anything-else.md
vendored
@@ -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
61
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
|
79
CHANGELOG.md
79
CHANGELOG.md
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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",
|
||||
|
@@ -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;
|
||||
|
@@ -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",
|
||||
|
@@ -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",
|
||||
|
BIN
packages/node_modules/@node-red/editor-client/src/images/grip-horizontal.png
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/images/grip-horizontal.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 B |
@@ -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);
|
||||
}
|
||||
|
@@ -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:[]})
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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 {
|
||||
|
@@ -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%;
|
||||
}
|
||||
}
|
||||
|
@@ -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 & {
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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");
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
@@ -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");
|
||||
|
@@ -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") {
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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>.",
|
||||
|
@@ -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>
|
||||
|
@@ -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> の相対パスになります。",
|
||||
|
@@ -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",
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 {
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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",{
|
||||
|
@@ -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"
|
||||
|
@@ -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) {
|
||||
|
@@ -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__",
|
||||
|
@@ -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__",
|
||||
|
@@ -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",
|
||||
|
@@ -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",
|
||||
|
12
packages/node_modules/node-red/package.json
vendored
12
packages/node_modules/node-red/package.json
vendored
@@ -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"
|
||||
},
|
||||
|
2
packages/node_modules/node-red/settings.js
vendored
2
packages/node_modules/node-red/settings.js
vendored
@@ -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.
|
||||
|
@@ -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);
|
||||
|
@@ -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");
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
|
Reference in New Issue
Block a user