mirror of https://github.com/node-red/node-red.git
Merge branch 'master' into master-fixselectconfig
commit
bbef4e2a79
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
- name: Run tests
|
||||
run: |
|
||||
npm run test
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ docs
|
|||
.vscode
|
||||
.nyc_output
|
||||
sync.ffs_db
|
||||
package-lock.json
|
||||
.editorconfig
|
||||
test/resources/50-file-test-file.txt
|
||||
test/unit/@node-red/runtime/lib/nodes/.testUserHome
|
||||
|
|
|
|||
50
CHANGELOG.md
50
CHANGELOG.md
|
|
@ -1,5 +1,53 @@
|
|||
#### 4.1.2: Maintenance Release
|
||||
#### 4.1.5: Maintenance Release
|
||||
|
||||
- chore: bump tar to 7.5.7 (#5472) @bryopsida
|
||||
- Update node-red-admin dependency @knolleary
|
||||
|
||||
#### 4.1.4: Maintenance Release
|
||||
|
||||
- Update tar dependency @knolleary
|
||||
- Revert overflow fix in editableList (#5467) @knolleary
|
||||
- registry: fix importModule base dir for exports subpaths (#5465) @yuan-cloud
|
||||
- fix: prevent race condition in localfilesystem context store during shutdown (#5462) @Dennis-SEG
|
||||
- fix: prevent double resolve in node close callback (#5461) @Dennis-SEG
|
||||
- fix: prevent incorrect array modification in delay node (#5457) @Dennis-SEG
|
||||
- fix: prevent uncaught exceptions in core node event handlers (#5438) @Dennis-SEG
|
||||
|
||||
#### 4.1.3: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- 5343/Editor/Bug: Node help tab resets focus when arrow keys are used to switch between nodes (#5406) @piotrbogun
|
||||
- Ensure quick-add filter is applied properly when retriggering add (#5427) @knolleary
|
||||
- TreeList: Fix widget treeList keyboard navigation scroll behavior (#5421) @piotrbogun
|
||||
- Editor: Flow & subflow names are changed to all lowercase in search dialog #5348 (#5401) @n-lark
|
||||
- Allow actions show-next-tab and previous to loop (#5355) @GogoVega
|
||||
- 5404/Editor/Bug: Junction error in Quick Add dialog (#5407) @piotrbogun
|
||||
- Add tooltip to delete button in node property UI (#5410) @kazuhitoyokoi
|
||||
- Fix invalid node size in quick add dialog (#5403) @kazuhitoyokoi
|
||||
- Expand folder to avoid error in library (#5399) @kazuhitoyokoi
|
||||
- Stricter validator for flow file name in project feature (#5398) @kazuhitoyokoi
|
||||
- Fix size and scrolling in Git config UI (#5396) @kazuhitoyokoi
|
||||
- Reveal node in search results via mouseover (#5368) @gorenje
|
||||
|
||||
Runtime
|
||||
|
||||
- Add package-lock.json for reproducible dependency chains (#5426) @dimitrieh
|
||||
- Readme markdown refactor for legibility in IDE's (#5423) @dimitrieh
|
||||
- Update body-parser (#5418) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- fix(http-request): prevent uncaught exceptions in async hooks (#5392) @Dennis-SEG
|
||||
- Fix flushing when in variable delay mode (#5382) @dceejay
|
||||
- File node TypedInput width fix (#5425) @knolleary
|
||||
- Use TextDecoder() to decode UTF-8 characters (#5416) @kazuhitoyokoi
|
||||
- Support source information in complete node (#5414) @kazuhitoyokoi
|
||||
- Fix status node to retrieve status from all nodes (#5412) @kazuhitoyokoi
|
||||
- Decrement count of http requests after error (#5409) @kazuhitoyokoi
|
||||
- Fix debug tab to copy displayed value (#5400) @kazuhitoyokoi
|
||||
|
||||
#### 4.1.2: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
|
|
|
|||
77
README.md
77
README.md
|
|
@ -1,44 +1,18 @@
|
|||
<p align="center">
|
||||
<a href="https://opensource.org/license/apache-2-0"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: Apache 2.0"></a>
|
||||
<a href="https://github.com/node-red/node-red/actions?query=branch%3Amaster">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/node-red/node-red/tests.yml?branch=master&label=Build%20Status&style=for-the-badge" alt="Build Status"/>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<a href="https://discourse.nodered.org">
|
||||
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscourse.nodered.org%2Fabout.json&query=%24.about.stats.users_count&suffix=%20members&label=Forum&logo=discourse&style=for-the-badge&color=0e76b2&logoColor=0e76b2&labelColor=ececec" alt="Node-RED Forum on Discourse"/>
|
||||
</a>
|
||||
<a href="https://github.com/node-red">
|
||||
<img src="https://img.shields.io/badge/dynamic/json?url=https://api.github.com/orgs/node-red&query=$.followers&suffix=%20followers&label=GitHub%20org&style=for-the-badge&logo=github&logoColor=24292F&labelColor=ececec&color=24292F" alt="Node-RED GitHub Organisation Followers"/>
|
||||
</a>
|
||||
<a href="https://nodered.org/slack">
|
||||
<img src="https://img.shields.io/badge/Slack-Join%20Us-4A154B.svg?logo=slack&style=for-the-badge&logoColor=4A154B&labelColor=ececec" alt="Slack"/>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
<div align="center">
|
||||
|
||||
<p align="center">
|
||||
<img src="https://nodered.org/about/resources/media/node-red-icon-2.svg" width="200" title="Node-RED Logo">
|
||||
</p>
|
||||
<h3 align="center">Low-code programming for event-driven applications</h3>
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="https://flows.nodered.org/search?type=node">
|
||||
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dnode&query=$.meta.results.count&label=Nodes&style=for-the-badge&labelColor=ececec&color=8f0000" alt="Node-RED Library Nodes"/>
|
||||
</a>
|
||||
<a href="https://flows.nodered.org/search?type=flow">
|
||||
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dflow&query=$.meta.results.count&label=Flows&style=for-the-badge&labelColor=ececec&color=8f0000" alt="Node-RED Library Flows"/>
|
||||
</a>
|
||||
<a href="https://flows.nodered.org/search?type=collection">
|
||||
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dcollection&query=$.meta.results.count&label=Collections&style=for-the-badge&labelColor=ececec&color=8f0000" alt="Node-RED Library Collections"/>
|
||||
</a>
|
||||
</p>
|
||||
<br/>
|
||||
<a href="https://nodered.org/">
|
||||
<img src="https://nodered.org/images/node-red-screenshot.png" title="Node-RED: Low-code programming for event-driven applications">
|
||||
</a>
|
||||
<br/>
|
||||
[![License][badge-license]][link-license] [![Build Status][badge-build]][link-build]
|
||||
|
||||
<br/><img src="https://nodered.org/about/resources/media/node-red-icon-2.svg" width="150" title="Node-RED Logo"><br/>
|
||||
|
||||
[![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections]
|
||||
|
||||
### Low-code programming for event-driven applications
|
||||
|
||||
<br/><img src="https://nodered.org/images/node-red-screenshot.png" width="600" alt="Node-RED Screenshot"><br/>
|
||||
|
||||
[![Forum][badge-forum]][link-forum] [![GitHub][badge-github]][link-github] [![Slack][badge-slack]][link-slack]
|
||||
|
||||
</div>
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
|
@ -78,7 +52,7 @@ If you want to run the latest code from git, here's how to get started:
|
|||
|
||||
2. Install the node-red dependencies
|
||||
|
||||
npm install
|
||||
npm ci
|
||||
|
||||
3. Build the code
|
||||
|
||||
|
|
@ -124,3 +98,24 @@ Node-RED is a project of the [OpenJS Foundation](http://openjsf.org). Copyright
|
|||
<a href="http://openjsf.org">
|
||||
<img src="https://raw.githubusercontent.com/node-red/community-survey/refs/heads/main/public/openjs-foundation-logo.svg" width="240" title="OpenJS Foundation Logo">
|
||||
</a>
|
||||
|
||||
<!-- Badge images -->
|
||||
[badge-license]: https://img.shields.io/badge/License-Apache_2.0-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec
|
||||
[badge-build]: https://img.shields.io/github/actions/workflow/status/node-red/node-red/tests.yml?branch=master&label=Build%20Status&style=for-the-badge
|
||||
[badge-forum]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscourse.nodered.org%2Fabout.json&query=%24.about.stats.users_count&suffix=%20members&label=Forum&logo=discourse&style=for-the-badge&color=0e76b2&logoColor=0e76b2&labelColor=ececec
|
||||
[badge-github]: https://img.shields.io/badge/dynamic/json?url=https://api.github.com/orgs/node-red&query=$.followers&suffix=%20followers&label=GitHub%20org&style=for-the-badge&logo=github&logoColor=24292F&labelColor=ececec&color=24292F
|
||||
[badge-slack]: https://img.shields.io/badge/Slack-Join%20Us-4A154B.svg?logo=slack&style=for-the-badge&logoColor=4A154B&labelColor=ececec
|
||||
[badge-nodes]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dnode&query=$.meta.results.count&label=Nodes&style=for-the-badge&labelColor=ececec&color=8f0000
|
||||
[badge-flows]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dflow&query=$.meta.results.count&label=Flows&style=for-the-badge&labelColor=ececec&color=8f0000
|
||||
[badge-collections]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dcollection&query=$.meta.results.count&label=Collections&style=for-the-badge&labelColor=ececec&color=8f0000
|
||||
|
||||
<!-- Badge links -->
|
||||
[link-license]: https://opensource.org/license/apache-2-0
|
||||
[link-build]: https://github.com/node-red/node-red/actions?query=branch%3Amaster
|
||||
[link-forum]: https://discourse.nodered.org
|
||||
[link-github]: https://github.com/node-red
|
||||
[link-slack]: https://nodered.org/slack
|
||||
[link-nodes]: https://flows.nodered.org/search?type=node
|
||||
[link-flows]: https://flows.nodered.org/search?type=flow
|
||||
[link-collections]: https://flows.nodered.org/search?type=collection
|
||||
[link-nodered]: https://nodered.org/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "node-red",
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.5",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "https://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
"async-mutex": "0.5.0",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "3.0.2",
|
||||
"body-parser": "1.20.3",
|
||||
"body-parser": "1.20.4",
|
||||
"chalk": "^4.1.2",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"clone": "2.1.2",
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
"mqtt": "5.11.0",
|
||||
"multer": "2.0.2",
|
||||
"mustache": "4.2.0",
|
||||
"node-red-admin": "^4.1.2",
|
||||
"node-red-admin": "^4.1.3",
|
||||
"node-watch": "0.7.4",
|
||||
"nopt": "5.0.0",
|
||||
"oauth2orize": "1.12.0",
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
"raw-body": "3.0.0",
|
||||
"rfdc": "^1.3.1",
|
||||
"semver": "7.7.1",
|
||||
"tar": "7.4.3",
|
||||
"tar": "7.5.7",
|
||||
"tough-cookie": "5.1.2",
|
||||
"uglify-js": "3.19.3",
|
||||
"uuid": "9.0.1",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@node-red/editor-api",
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.5",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
|
|
@ -16,10 +16,10 @@
|
|||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "4.1.2",
|
||||
"@node-red/editor-client": "4.1.2",
|
||||
"@node-red/util": "4.1.5",
|
||||
"@node-red/editor-client": "4.1.5",
|
||||
"bcryptjs": "3.0.2",
|
||||
"body-parser": "1.20.3",
|
||||
"body-parser": "1.20.4",
|
||||
"clone": "2.1.2",
|
||||
"cors": "2.8.5",
|
||||
"express-session": "1.18.2",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@node-red/editor-client",
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.5",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -340,8 +340,10 @@
|
|||
var deleteButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(li);
|
||||
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
|
||||
li.addClass("red-ui-editableList-item-removable");
|
||||
var removeTip = RED.popover.tooltip(deleteButton, RED._("common.label.delete"));
|
||||
deleteButton.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
removeTip.close();
|
||||
var data = row.data('data');
|
||||
li.addClass("red-ui-editableList-item-deleting")
|
||||
li.fadeOut(300, function() {
|
||||
|
|
|
|||
|
|
@ -443,12 +443,18 @@ RED.tabs = (function() {
|
|||
}
|
||||
function activatePreviousTab() {
|
||||
var previous = findPreviousVisibleTab();
|
||||
if (previous.length === 0) {
|
||||
previous = ul.find("li.red-ui-tab:not(.hide-tab)").last();
|
||||
}
|
||||
if (previous.length > 0) {
|
||||
activateTab(previous.find("a"));
|
||||
}
|
||||
}
|
||||
function activateNextTab() {
|
||||
var next = findNextVisibleTab();
|
||||
if (next.length === 0) {
|
||||
next = ul.find("li.red-ui-tab:not(.hide-tab)").first();
|
||||
}
|
||||
if (next.length > 0) {
|
||||
activateTab(next.find("a"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,7 +188,10 @@
|
|||
} else {
|
||||
that._topList.find(".focus").removeClass("focus")
|
||||
}
|
||||
target.treeList.label.addClass('focus')
|
||||
if (target.treeList.label) {
|
||||
target.treeList.label.addClass('focus')
|
||||
}
|
||||
that.reveal(target);
|
||||
}
|
||||
});
|
||||
this._data = [];
|
||||
|
|
@ -879,6 +882,10 @@
|
|||
}
|
||||
|
||||
that._topList.find(".focus").removeClass("focus");
|
||||
|
||||
if (item.treeList.label) {
|
||||
item.treeList.label.addClass("focus");
|
||||
}
|
||||
|
||||
if (triggerEvent !== false) {
|
||||
this._trigger("select",null,item)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ RED.keyboard = (function() {
|
|||
"-":189,
|
||||
".":190,
|
||||
"/":191,
|
||||
"§":192, // <- top left key MacOS
|
||||
"\\":220,
|
||||
"'":222,
|
||||
"?":191, // <- QWERTY specific
|
||||
|
|
|
|||
|
|
@ -327,7 +327,7 @@ RED.library = (function() {
|
|||
icon: 'fa fa-cube',
|
||||
label: options.type,
|
||||
path: "",
|
||||
expanded: false,
|
||||
expanded: true,
|
||||
children: function(done, item) {
|
||||
loadLibraryFolder(lib.id, options.url, "", function(children) {
|
||||
item.children = children;
|
||||
|
|
|
|||
|
|
@ -720,7 +720,7 @@ RED.projects = (function() {
|
|||
var validateForm = function() {
|
||||
var valid = true;
|
||||
var flowFile = projectFlowFileInput.val();
|
||||
if (flowFile === "" || !/\.json$/.test(flowFile)) {
|
||||
if (flowFile === "" || !/^[a-zA-Z0-9\-_]+\.json$/.test(flowFile)) {
|
||||
valid = false;
|
||||
if (!projectFlowFileInput.hasClass("input-error")) {
|
||||
projectFlowFileInput.addClass("input-error");
|
||||
|
|
@ -1142,7 +1142,7 @@ RED.projects = (function() {
|
|||
|
||||
} else if (projectType === 'empty') {
|
||||
var flowFile = projectFlowFileInput.val();
|
||||
if (flowFile === "" || !/\.json$/.test(flowFile)) {
|
||||
if (flowFile === "" || !/^[a-zA-Z0-9\-_]+\.json$/.test(flowFile)) {
|
||||
valid = false;
|
||||
if (!projectFlowFileInput.hasClass("input-error")) {
|
||||
projectFlowFileInput.addClass("input-error");
|
||||
|
|
|
|||
|
|
@ -49,9 +49,13 @@ RED.search = (function() {
|
|||
function indexNode(n) {
|
||||
var l = RED.utils.getNodeLabel(n);
|
||||
if (l) {
|
||||
l = (""+l).toLowerCase();
|
||||
index[l] = index[l] || {};
|
||||
index[l][n.id] = {node:n,label:l}
|
||||
const originalLabel = "" + l;
|
||||
const indexLabel = originalLabel.toLowerCase();
|
||||
index[indexLabel] = index[indexLabel] || {};
|
||||
index[indexLabel][n.id] = {
|
||||
node: n,
|
||||
label: originalLabel
|
||||
};
|
||||
}
|
||||
l = l||n.label||n.name||n.id||"";
|
||||
|
||||
|
|
@ -683,7 +687,17 @@ RED.search = (function() {
|
|||
show: show,
|
||||
hide: hide,
|
||||
search: search,
|
||||
getSearchOptions: getSearchOptions
|
||||
getSearchOptions: getSearchOptions,
|
||||
// Expose internals for testing
|
||||
_indexNode: indexNode,
|
||||
get _index() { return index; },
|
||||
set _index(val) { index = val; }
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
|
||||
// Allow CommonJS import for testing
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = RED.search;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,7 +265,13 @@ RED.utils = (function() {
|
|||
var copyPayload = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-clipboard"></i></button>').appendTo(copyTools).on("click", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
|
||||
var payloadToCopy;
|
||||
if (typeof msg === "number") {
|
||||
payloadToCopy = obj.find(".red-ui-debug-msg-type-number").first().text();
|
||||
} else {
|
||||
payloadToCopy = msg;
|
||||
}
|
||||
RED.clipboard.copyText(payloadToCopy, copyPayload, "clipboard.copyMessageValue");
|
||||
})
|
||||
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
|
||||
if (enablePinning && strippedKey !== undefined && strippedKey !== '') {
|
||||
|
|
@ -593,7 +599,7 @@ RED.utils = (function() {
|
|||
var sr = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(stringRow);
|
||||
var stringEncoding = "";
|
||||
try {
|
||||
stringEncoding = String.fromCharCode.apply(null, new Uint16Array(data))
|
||||
stringEncoding = new TextDecoder().decode(new Uint8Array(data));
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1378,6 +1378,13 @@ RED.view = (function() {
|
|||
quickAddLink.virtualLink = true;
|
||||
}
|
||||
hideDragLines();
|
||||
} else if (quickAddLink) {
|
||||
// continuing an existing quick add - set the filter accordingly
|
||||
if (quickAddLink.portType === PORT_TYPE_OUTPUT) {
|
||||
filter = {input:true}
|
||||
} else {
|
||||
filter = {output:true}
|
||||
}
|
||||
}
|
||||
if (linkToSplice || spliceMultipleLinks) {
|
||||
filter = {
|
||||
|
|
@ -6545,6 +6552,9 @@ RED.view = (function() {
|
|||
suggestedNodes = [suggestedNodes]
|
||||
}
|
||||
suggestedNodes = suggestedNodes.filter(n => {
|
||||
if (n.type === 'junction') {
|
||||
return true
|
||||
}
|
||||
const def = RED.nodes.getType(n.type)
|
||||
if (def?.set && def.set.enabled === false) {
|
||||
// Exclude disabled node set
|
||||
|
|
|
|||
|
|
@ -755,6 +755,7 @@ div.red-ui-projects-dialog-ssh-public-key {
|
|||
}
|
||||
|
||||
.red-ui-projects-dialog-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
.red-ui-editableList-container {
|
||||
padding: 1px;
|
||||
|
|
@ -830,7 +831,13 @@ div.red-ui-projects-dialog-ssh-public-key {
|
|||
}
|
||||
|
||||
#red-ui-settings-tab-gitconfig {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
padding: 8px 20px 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.red-ui-settings-section-description {
|
||||
color: var(--red-ui-secondary-text-color);
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@
|
|||
width: 18px;
|
||||
height: 15px;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.red-ui-search-result-node-port {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -20,13 +20,15 @@ module.exports = function(RED) {
|
|||
function StatusNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.scope = n.scope || [];
|
||||
this.scope = n.scope;
|
||||
|
||||
// auto-filter out any directly connected nodes to avoid simple loopback
|
||||
const w = this.wires.flat();
|
||||
for (let i=0; i < this.scope.length; i++) {
|
||||
if (w.includes(this.scope[i])) {
|
||||
this.scope.splice(i, 1);
|
||||
if (Array.isArray(this.scope)) {
|
||||
const w = this.wires.flat();
|
||||
for (let i = 0; i < this.scope.length; i++) {
|
||||
if (w.includes(this.scope[i])) {
|
||||
this.scope.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ module.exports = function(RED) {
|
|||
if (node.pauseType === "delay") {
|
||||
node.on("input", function(msg, send, done) {
|
||||
var id = ourTimeout(function() {
|
||||
node.idList.splice(node.idList.indexOf(id),1);
|
||||
var idx = node.idList.indexOf(id);
|
||||
if (idx !== -1) { node.idList.splice(idx, 1); }
|
||||
if (node.timeout > 1000) {
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
}
|
||||
|
|
@ -184,7 +185,8 @@ module.exports = function(RED) {
|
|||
}
|
||||
if (delayvar < 0) { delayvar = 0; }
|
||||
var id = ourTimeout(function() {
|
||||
node.idList.splice(node.idList.indexOf(id),1);
|
||||
var idx = node.idList.indexOf(id);
|
||||
if (idx !== -1) { node.idList.splice(idx, 1); }
|
||||
if (node.idList.length === 0) { node.status({}); }
|
||||
send(msg);
|
||||
if (delayvar >= 0) {
|
||||
|
|
@ -192,7 +194,8 @@ module.exports = function(RED) {
|
|||
}
|
||||
done();
|
||||
}, delayvar, () => done());
|
||||
node.idList.push(id);
|
||||
if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); }
|
||||
else { node.idList.push(id); }
|
||||
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
|
||||
if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); }
|
||||
if (delayvar >= 0) {
|
||||
|
|
@ -206,7 +209,8 @@ module.exports = function(RED) {
|
|||
node.on("input", function(msg, send, done) {
|
||||
var wait = node.randomFirst + (node.diff * Math.random());
|
||||
var id = ourTimeout(function() {
|
||||
node.idList.splice(node.idList.indexOf(id),1);
|
||||
var idx = node.idList.indexOf(id);
|
||||
if (idx !== -1) { node.idList.splice(idx, 1); }
|
||||
send(msg);
|
||||
if (node.timeout >= 1000) {
|
||||
node.status({fill:"blue",shape:"dot",text:node.idList.length});
|
||||
|
|
|
|||
|
|
@ -105,18 +105,26 @@ module.exports = function(RED) {
|
|||
}
|
||||
node.activeProcesses[child.pid] = child;
|
||||
child.stdout.on('data', function (data) {
|
||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||
// console.log('[exec] stdout: ' + data,child.pid);
|
||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||
else { msg.payload = data; }
|
||||
nodeSend([RED.util.cloneMessage(msg),null,null]);
|
||||
try {
|
||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||
// console.log('[exec] stdout: ' + data,child.pid);
|
||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||
else { msg.payload = data; }
|
||||
nodeSend([RED.util.cloneMessage(msg),null,null]);
|
||||
}
|
||||
} catch (err) {
|
||||
node.error(err.toString());
|
||||
}
|
||||
});
|
||||
child.stderr.on('data', function (data) {
|
||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||
else { msg.payload = data; }
|
||||
nodeSend([null,RED.util.cloneMessage(msg),null]);
|
||||
try {
|
||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||
else { msg.payload = data; }
|
||||
nodeSend([null,RED.util.cloneMessage(msg),null]);
|
||||
}
|
||||
} catch (err) {
|
||||
node.error(err.toString());
|
||||
}
|
||||
});
|
||||
child.on('close', function (code,signal) {
|
||||
|
|
|
|||
|
|
@ -167,12 +167,15 @@
|
|||
$("#tls-config-button-cert-clear").on("click", function() {
|
||||
clearNameData("cert");
|
||||
});
|
||||
RED.popover.tooltip($("#tls-config-button-cert-clear"), RED._("common.label.delete"));
|
||||
$("#tls-config-button-key-clear").on("click", function() {
|
||||
clearNameData("key");
|
||||
});
|
||||
RED.popover.tooltip($("#tls-config-button-key-clear"), RED._("common.label.delete"));
|
||||
$("#tls-config-button-ca-clear").on("click", function() {
|
||||
clearNameData("ca");
|
||||
});
|
||||
RED.popover.tooltip($("#tls-config-button-ca-clear"), RED._("common.label.delete"));
|
||||
|
||||
if (RED.settings.tlsConfigDisableLocalFiles) {
|
||||
$("#node-config-row-uselocalfiles").hide();
|
||||
|
|
|
|||
|
|
@ -227,6 +227,7 @@ module.exports = function(RED) {
|
|||
* Handle the payload / packet recieved in MQTT In and MQTT Sub nodes
|
||||
*/
|
||||
function subscriptionHandler(node, datatype ,topic, payload, packet) {
|
||||
if (!packet) { packet = {}; }
|
||||
const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
||||
const v5 = (node && node.brokerConn)
|
||||
? node.brokerConn.v5()
|
||||
|
|
@ -1074,12 +1075,16 @@ module.exports = function(RED) {
|
|||
|
||||
if (!subscription.handler) {
|
||||
subscription.handler = function (mtopic, mpayload, mpacket) {
|
||||
const sops = subscription.options ? subscription.options.properties : {}
|
||||
const pops = mpacket.properties || {}
|
||||
if (subIdsAvailable && pops.subscriptionIdentifier && sops.subscriptionIdentifier && (pops.subscriptionIdentifier !== sops.subscriptionIdentifier)) {
|
||||
//do nothing as subscriptionIdentifier does not match
|
||||
} else if (matchTopic(topic, mtopic)) {
|
||||
subscription.callback && subscription.callback(mtopic, mpayload, mpacket)
|
||||
try {
|
||||
const sops = subscription.options ? subscription.options.properties : {}
|
||||
const pops = (mpacket && mpacket.properties) || {}
|
||||
if (subIdsAvailable && pops.subscriptionIdentifier && sops.subscriptionIdentifier && (pops.subscriptionIdentifier !== sops.subscriptionIdentifier)) {
|
||||
//do nothing as subscriptionIdentifier does not match
|
||||
} else if (matchTopic(topic, mtopic)) {
|
||||
subscription.callback && subscription.callback(mtopic, mpayload, mpacket)
|
||||
}
|
||||
} catch (err) {
|
||||
node.error("MQTT subscription handler error: " + err.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,15 +143,6 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {Object} headersObject
|
||||
* @param {string} name
|
||||
* @return {any} value
|
||||
*/
|
||||
const getHeaderValue = (headersObject, name) => {
|
||||
const asLowercase = name.toLowercase();
|
||||
return headersObject[Object.keys(headersObject).find(k => k.toLowerCase() === asLowercase)];
|
||||
}
|
||||
this.count = 0;
|
||||
this.on("input",function(msg,nodeSend,nodeDone) {
|
||||
node.count++;
|
||||
|
|
@ -256,34 +247,42 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||
opts.hooks = {
|
||||
beforeRequest: [
|
||||
options => {
|
||||
// Whilst HTTP headers are meant to be case-insensitive,
|
||||
// in the real world, there are servers that aren't so compliant.
|
||||
// GOT will lower case all headers given a chance, so we need
|
||||
// to restore the case of any headers the user has set.
|
||||
Object.keys(options.headers).forEach(h => {
|
||||
if (originalHeaderMap[h] && originalHeaderMap[h] !== h) {
|
||||
options.headers[originalHeaderMap[h]] = options.headers[h];
|
||||
delete options.headers[h];
|
||||
try {
|
||||
// Whilst HTTP headers are meant to be case-insensitive,
|
||||
// in the real world, there are servers that aren't so compliant.
|
||||
// GOT will lower case all headers given a chance, so we need
|
||||
// to restore the case of any headers the user has set.
|
||||
Object.keys(options.headers).forEach(h => {
|
||||
if (originalHeaderMap[h] && originalHeaderMap[h] !== h) {
|
||||
options.headers[originalHeaderMap[h]] = options.headers[h];
|
||||
delete options.headers[h];
|
||||
}
|
||||
})
|
||||
if (node.insecureHTTPParser) {
|
||||
// Setting the property under _unixOptions as pretty
|
||||
// much the only hack available to get got to apply
|
||||
// a core http option it doesn't think we should be
|
||||
// allowed to set
|
||||
options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
|
||||
}
|
||||
})
|
||||
if (node.insecureHTTPParser) {
|
||||
// Setting the property under _unixOptions as pretty
|
||||
// much the only hack available to get got to apply
|
||||
// a core http option it doesn't think we should be
|
||||
// allowed to set
|
||||
options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
|
||||
} catch (err) {
|
||||
node.warn("Error in beforeRequest hook: " + err.message);
|
||||
}
|
||||
}
|
||||
],
|
||||
beforeRedirect: [
|
||||
(options, response) => {
|
||||
let redirectInfo = {
|
||||
location: response.headers.location
|
||||
try {
|
||||
let redirectInfo = {
|
||||
location: response.headers.location
|
||||
}
|
||||
if (response.headers.hasOwnProperty('set-cookie')) {
|
||||
redirectInfo.cookies = extractCookies(response.headers['set-cookie']);
|
||||
}
|
||||
redirectList.push(redirectInfo)
|
||||
} catch (err) {
|
||||
node.warn("Error processing redirect: " + err.message);
|
||||
}
|
||||
if (response.headers.hasOwnProperty('set-cookie')) {
|
||||
redirectInfo.cookies = extractCookies(response.headers['set-cookie']);
|
||||
}
|
||||
redirectList.push(redirectInfo)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -422,25 +421,30 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||
let digestCreds = this.credentials;
|
||||
let sentCreds = false;
|
||||
opts.hooks.afterResponse = [(response, retry) => {
|
||||
if (response.statusCode === 401) {
|
||||
if (sentCreds) {
|
||||
return response
|
||||
try {
|
||||
if (response.statusCode === 401) {
|
||||
if (sentCreds) {
|
||||
return response
|
||||
}
|
||||
const requestUrl = new URL(response.request.requestUrl);
|
||||
const options = { headers: {} }
|
||||
const normalisedHeaders = {};
|
||||
Object.keys(response.headers).forEach(k => {
|
||||
normalisedHeaders[k.toLowerCase()] = response.headers[k]
|
||||
})
|
||||
if (normalisedHeaders['www-authenticate']) {
|
||||
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname + requestUrl.search, normalisedHeaders['www-authenticate'])
|
||||
options.headers.Authorization = authHeader;
|
||||
}
|
||||
// response.request.options.merge(options)
|
||||
sentCreds = true;
|
||||
return retry(options);
|
||||
}
|
||||
const requestUrl = new URL(response.request.requestUrl);
|
||||
const options = { headers: {} }
|
||||
const normalisedHeaders = {};
|
||||
Object.keys(response.headers).forEach(k => {
|
||||
normalisedHeaders[k.toLowerCase()] = response.headers[k]
|
||||
})
|
||||
if (normalisedHeaders['www-authenticate']) {
|
||||
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname + requestUrl.search, normalisedHeaders['www-authenticate'])
|
||||
options.headers.Authorization = authHeader;
|
||||
}
|
||||
// response.request.options.merge(options)
|
||||
sentCreds = true;
|
||||
return retry(options);
|
||||
return response
|
||||
} catch (err) {
|
||||
node.warn("Digest authentication failed: " + err.message);
|
||||
return response;
|
||||
}
|
||||
return response
|
||||
}];
|
||||
} else if (this.authType === "bearer") {
|
||||
opts.headers.Authorization = `Bearer ${this.credentials.password||""}`
|
||||
|
|
@ -688,6 +692,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||
if (!sendErrorsToCatch) {
|
||||
nodeSend(msg);
|
||||
}
|
||||
node.count--;
|
||||
nodeDone();
|
||||
});
|
||||
});
|
||||
|
|
@ -720,13 +725,29 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||
|
||||
function extractCookies(setCookie) {
|
||||
var cookies = {};
|
||||
if (!Array.isArray(setCookie)) {
|
||||
return cookies;
|
||||
}
|
||||
setCookie.forEach(function(c) {
|
||||
var parsedCookie = cookie.parse(c);
|
||||
var eq_idx = c.indexOf('=');
|
||||
var key = c.substr(0, eq_idx).trim()
|
||||
parsedCookie.value = parsedCookie[key];
|
||||
delete parsedCookie[key];
|
||||
cookies[key] = parsedCookie;
|
||||
try {
|
||||
if (typeof c !== 'string') {
|
||||
return;
|
||||
}
|
||||
var parsedCookie = cookie.parse(c);
|
||||
var eq_idx = c.indexOf('=');
|
||||
if (eq_idx === -1) {
|
||||
return;
|
||||
}
|
||||
var key = c.substr(0, eq_idx).trim()
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
parsedCookie.value = parsedCookie[key];
|
||||
delete parsedCookie[key];
|
||||
cookies[key] = parsedCookie;
|
||||
} catch (err) {
|
||||
// Skip malformed cookies
|
||||
}
|
||||
});
|
||||
return cookies;
|
||||
}
|
||||
|
|
@ -778,6 +799,9 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||
}
|
||||
|
||||
function buildDigestHeader(user, pass, method, path, authHeader) {
|
||||
if (!authHeader || typeof authHeader !== 'string') {
|
||||
throw new Error("Invalid or missing WWW-Authenticate header");
|
||||
}
|
||||
var challenge = {}
|
||||
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
||||
for (;;) {
|
||||
|
|
@ -787,6 +811,12 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||
}
|
||||
challenge[match[1]] = match[2] || match[3]
|
||||
}
|
||||
if (!challenge.nonce) {
|
||||
throw new Error("Invalid digest challenge: missing nonce");
|
||||
}
|
||||
if (!challenge.realm) {
|
||||
throw new Error("Invalid digest challenge: missing realm");
|
||||
}
|
||||
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
|
||||
var nc = qop && '00000001'
|
||||
var cnonce = qop && uuid().replace(/-/g, '')
|
||||
|
|
|
|||
|
|
@ -297,7 +297,11 @@ module.exports = function(RED) {
|
|||
}
|
||||
msg._session = {type:"websocket",id:id};
|
||||
for (var i = 0; i < this._inputNodes.length; i++) {
|
||||
this._inputNodes[i].send(msg);
|
||||
try {
|
||||
this._inputNodes[i].send(msg);
|
||||
} catch (err) {
|
||||
this.error(RED._("websocket.errors.send-error") + " " + err.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -127,32 +127,36 @@ module.exports = function(RED) {
|
|||
connectionPool[id] = client;
|
||||
|
||||
client.on('data', function (data) {
|
||||
if (node.datatype != 'buffer') {
|
||||
data = data.toString(node.datatype);
|
||||
}
|
||||
if (node.stream) {
|
||||
var msg;
|
||||
if ((node.datatype) === "utf8" && node.newline !== "") {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i]};
|
||||
if (node.trim == true) { msg.payload += node.newline; }
|
||||
try {
|
||||
if (node.datatype != 'buffer') {
|
||||
data = data.toString(node.datatype);
|
||||
}
|
||||
if (node.stream) {
|
||||
var msg;
|
||||
if ((node.datatype) === "utf8" && node.newline !== "") {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i]};
|
||||
if (node.trim == true) { msg.payload += node.newline; }
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = parts[parts.length-1];
|
||||
} else {
|
||||
msg = {topic:node.topic, payload:data};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = parts[parts.length-1];
|
||||
} else {
|
||||
msg = {topic:node.topic, payload:data};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
} else {
|
||||
if ((typeof data) === "string") {
|
||||
buffer = buffer+data;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
|
||||
if ((typeof data) === "string") {
|
||||
buffer = buffer+data;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
node.error(RED._("tcpin.errors.error",{error:err.toString()}));
|
||||
}
|
||||
});
|
||||
client.on('end', function() {
|
||||
|
|
@ -222,35 +226,39 @@ module.exports = function(RED) {
|
|||
|
||||
var buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
|
||||
socket.on('data', function (data) {
|
||||
if (node.datatype != 'buffer') {
|
||||
data = data.toString(node.datatype);
|
||||
}
|
||||
if (node.stream) {
|
||||
var msg;
|
||||
if ((typeof data) === "string" && node.newline !== "") {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort};
|
||||
if (node.trim == true) { msg.payload += node.newline; }
|
||||
try {
|
||||
if (node.datatype != 'buffer') {
|
||||
data = data.toString(node.datatype);
|
||||
}
|
||||
if (node.stream) {
|
||||
var msg;
|
||||
if ((typeof data) === "string" && node.newline !== "") {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort};
|
||||
if (node.trim == true) { msg.payload += node.newline; }
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = parts[parts.length-1];
|
||||
} else {
|
||||
msg = {topic:node.topic, payload:data, ip:socket.remoteAddress, port:socket.remotePort};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
buffer = parts[parts.length-1];
|
||||
} else {
|
||||
msg = {topic:node.topic, payload:data, ip:socket.remoteAddress, port:socket.remotePort};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((typeof data) === "string") {
|
||||
buffer = buffer+data;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
|
||||
else {
|
||||
if ((typeof data) === "string") {
|
||||
buffer = buffer+data;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
|
||||
}
|
||||
fromi = socket.remoteAddress;
|
||||
fromp = socket.remotePort;
|
||||
}
|
||||
fromi = socket.remoteAddress;
|
||||
fromp = socket.remotePort;
|
||||
} catch (err) {
|
||||
node.error(RED._("tcpin.errors.error",{error:err.toString()}));
|
||||
}
|
||||
});
|
||||
socket.on('end', function() {
|
||||
|
|
@ -678,117 +686,121 @@ module.exports = function(RED) {
|
|||
}
|
||||
var chunk = "";
|
||||
clients[connection_id].client.on('data', function(data) {
|
||||
if (node.out === "sit") { // if we are staying connected just send the buffer
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = RED.util.cloneMessage(data);
|
||||
if (node.ret === "string") {
|
||||
try {
|
||||
if (node.newline && node.newline !== "" ) {
|
||||
chunk += msg.payload.toString();
|
||||
let parts = chunk.split(node.newline);
|
||||
for (var p=0; p<parts.length-1; p+=1) {
|
||||
let m = RED.util.cloneMessage(msg);
|
||||
m.payload = parts[p];
|
||||
if (node.trim == true) { m.payload += node.newline; }
|
||||
nodeSend(m);
|
||||
}
|
||||
chunk = parts[parts.length-1];
|
||||
}
|
||||
else {
|
||||
msg.payload = msg.payload.toString();
|
||||
nodeSend(msg);
|
||||
}
|
||||
}
|
||||
catch(e) { node.error(RED._("tcpin.errors.bad-string"), msg); }
|
||||
}
|
||||
else { nodeSend(msg); }
|
||||
}
|
||||
}
|
||||
// else if (node.splitc === 0) {
|
||||
// clients[connection_id].msg.payload = data;
|
||||
// node.send(clients[connection_id].msg);
|
||||
// }
|
||||
else {
|
||||
for (var j = 0; j < data.length; j++ ) {
|
||||
if (node.out === "time") {
|
||||
if (clients[connection_id]) {
|
||||
// do the timer thing
|
||||
if (clients[connection_id].timeout) {
|
||||
i += 1;
|
||||
buf[i] = data[j];
|
||||
}
|
||||
else {
|
||||
clients[connection_id].timeout = setTimeout(function () {
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].timeout = null;
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i+1);
|
||||
buf.copy(msg.payload,0,0,i+1);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
try {
|
||||
if (node.out === "sit") { // if we are staying connected just send the buffer
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = RED.util.cloneMessage(data);
|
||||
if (node.ret === "string") {
|
||||
try {
|
||||
if (node.newline && node.newline !== "" ) {
|
||||
chunk += msg.payload.toString();
|
||||
let parts = chunk.split(node.newline);
|
||||
for (var p=0; p<parts.length-1; p+=1) {
|
||||
let m = RED.util.cloneMessage(msg);
|
||||
m.payload = parts[p];
|
||||
if (node.trim == true) { m.payload += node.newline; }
|
||||
nodeSend(m);
|
||||
}
|
||||
}, node.splitc);
|
||||
i = 0;
|
||||
buf[0] = data[j];
|
||||
chunk = parts[parts.length-1];
|
||||
}
|
||||
else {
|
||||
msg.payload = msg.payload.toString();
|
||||
nodeSend(msg);
|
||||
}
|
||||
}
|
||||
catch(e) { node.error(RED._("tcpin.errors.bad-string"), msg); }
|
||||
}
|
||||
else { nodeSend(msg); }
|
||||
}
|
||||
}
|
||||
// else if (node.splitc === 0) {
|
||||
// clients[connection_id].msg.payload = data;
|
||||
// node.send(clients[connection_id].msg);
|
||||
// }
|
||||
else {
|
||||
for (var j = 0; j < data.length; j++ ) {
|
||||
if (node.out === "time") {
|
||||
if (clients[connection_id]) {
|
||||
// do the timer thing
|
||||
if (clients[connection_id].timeout) {
|
||||
i += 1;
|
||||
buf[i] = data[j];
|
||||
}
|
||||
else {
|
||||
clients[connection_id].timeout = setTimeout(function () {
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].timeout = null;
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i+1);
|
||||
buf.copy(msg.payload,0,0,i+1);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
}
|
||||
}, node.splitc);
|
||||
i = 0;
|
||||
buf[0] = data[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// count bytes into a buffer...
|
||||
else if (node.out == "count") {
|
||||
buf[i] = data[j];
|
||||
i += 1;
|
||||
if ( i >= node.splitc) {
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
// count bytes into a buffer...
|
||||
else if (node.out == "count") {
|
||||
buf[i] = data[j];
|
||||
i += 1;
|
||||
if ( i >= node.splitc) {
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// look for a char
|
||||
else {
|
||||
buf[i] = data[j];
|
||||
i += 1;
|
||||
if (data[j] == node.splitc) {
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
// look for a char
|
||||
else {
|
||||
buf[i] = data[j];
|
||||
i += 1;
|
||||
if (data[j] == node.splitc) {
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = Buffer.alloc(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
nodeSend(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
delete clients[connection_id];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
node.error(RED._("tcpin.errors.error",{error:err.toString()}));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -98,15 +98,19 @@ module.exports = function(RED) {
|
|||
});
|
||||
|
||||
server.on('message', function (message, remote) {
|
||||
var msg;
|
||||
if (node.datatype =="base64") {
|
||||
msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
} else if (node.datatype =="utf8") {
|
||||
msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
} else {
|
||||
msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
try {
|
||||
var msg;
|
||||
if (node.datatype =="base64") {
|
||||
msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
} else if (node.datatype =="utf8") {
|
||||
msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
} else {
|
||||
msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
}
|
||||
node.send(msg);
|
||||
} catch (err) {
|
||||
node.error(RED._("udp.errors.error",{error:err.toString()}));
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
server.on('listening', function () {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<script type="text/html" data-template-name="file">
|
||||
<div class="form-row node-input-filename">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
|
||||
<input id="node-input-filename" type="text">
|
||||
<input id="node-input-filename" type="text" style="width: 100% !important;">
|
||||
<input type="hidden" id="node-input-filenameType">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
<script type="text/html" data-template-name="file in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
|
||||
<input id="node-input-filename" type="text">
|
||||
<input id="node-input-filename" type="text" style="width: 100% !important;">
|
||||
<input type="hidden" id="node-input-filenameType">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@node-red/nodes",
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.5",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
"acorn": "8.15.0",
|
||||
"acorn-walk": "8.3.4",
|
||||
"ajv": "8.17.1",
|
||||
"body-parser": "1.20.3",
|
||||
"body-parser": "1.20.4",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"content-type": "1.0.5",
|
||||
"cookie-parser": "1.4.7",
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ function importModule(module) {
|
|||
throw e;
|
||||
}
|
||||
const externalModuleDir = getInstallDir();
|
||||
const moduleDir = path.join(externalModuleDir,"node_modules",module);
|
||||
const moduleDir = path.join(externalModuleDir,"node_modules",parsedModule.module);
|
||||
// To handle both CJS and ESM we need to resolve the module to the
|
||||
// specific file that is loaded when the module is required/imported
|
||||
// As this won't be on the natural module search path, we use createRequire
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@node-red/registry",
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.5",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
|
|
@ -16,11 +16,11 @@
|
|||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "4.1.2",
|
||||
"@node-red/util": "4.1.5",
|
||||
"clone": "2.1.2",
|
||||
"fs-extra": "11.3.0",
|
||||
"semver": "7.7.1",
|
||||
"tar": "7.4.3",
|
||||
"tar": "7.5.7",
|
||||
"uglify-js": "3.19.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -699,6 +699,13 @@ class Flow {
|
|||
let toSend = msg;
|
||||
this.completeNodeMap[node.id].forEach((completeNode,index) => {
|
||||
toSend = redUtil.cloneMessage(msg);
|
||||
toSend.complete = {
|
||||
source: {
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
name: node.name
|
||||
}
|
||||
};
|
||||
completeNode.receive(toSend);
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ Node.prototype.close = function(removed) {
|
|||
// The callback takes a 'done' callback and (maybe) the removed flag
|
||||
promises.push(
|
||||
new Promise((resolve) => {
|
||||
var resolved = false;
|
||||
try {
|
||||
var args = [];
|
||||
if (callback.length === 2) {
|
||||
|
|
@ -329,13 +330,19 @@ Node.prototype.close = function(removed) {
|
|||
args.push(!!removed);
|
||||
}
|
||||
args.push(() => {
|
||||
resolve();
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
callback.apply(node, args);
|
||||
} catch(err) {
|
||||
// TODO: error thrown in node async close callback
|
||||
// We've never logged this properly.
|
||||
resolve();
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ function LocalFileSystem(config){
|
|||
}
|
||||
this.pendingWrites = {};
|
||||
this.knownCircularRefs = {};
|
||||
this.closing = false;
|
||||
|
||||
if (config.hasOwnProperty('flushInterval')) {
|
||||
this.flushInterval = Math.max(0,config.flushInterval) * 1000;
|
||||
|
|
@ -233,16 +234,19 @@ LocalFileSystem.prototype.open = function(){
|
|||
|
||||
LocalFileSystem.prototype.close = function(){
|
||||
var self = this;
|
||||
if (this.cache && this._pendingWriteTimeout) {
|
||||
clearTimeout(this._pendingWriteTimeout);
|
||||
delete this._pendingWriteTimeout;
|
||||
this.closing = true;
|
||||
if (this.cache) {
|
||||
if (this._pendingWriteTimeout) {
|
||||
clearTimeout(this._pendingWriteTimeout);
|
||||
delete this._pendingWriteTimeout;
|
||||
}
|
||||
this.flushInterval = 0;
|
||||
// Always flush pending writes on close, even if no timeout was pending
|
||||
self.writePromise = self.writePromise.then(function(){
|
||||
return self._flushPendingWrites.call(self).catch(function(err) {
|
||||
log.error(log._("context.localfilesystem.error-write",{message:err.toString()}));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
return this.writePromise;
|
||||
}
|
||||
|
|
@ -298,8 +302,9 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) {
|
|||
if (this.cache) {
|
||||
this.cache.set(scope,key,value,callback);
|
||||
this.pendingWrites[scope] = true;
|
||||
if (this._pendingWriteTimeout) {
|
||||
// there's a pending write which will handle this
|
||||
if (this._pendingWriteTimeout || this.closing) {
|
||||
// there's a pending write which will handle this,
|
||||
// or we're closing and the close() flush will handle it
|
||||
return;
|
||||
} else {
|
||||
this._pendingWriteTimeout = setTimeout(function() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@node-red/runtime",
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.5",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/registry": "4.1.2",
|
||||
"@node-red/util": "4.1.2",
|
||||
"@node-red/registry": "4.1.5",
|
||||
"@node-red/util": "4.1.5",
|
||||
"async-mutex": "0.5.0",
|
||||
"clone": "2.1.2",
|
||||
"cronosjs": "1.7.1",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@node-red/util",
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.5",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "node-red",
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.5",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "https://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
|
|
@ -31,16 +31,16 @@
|
|||
"flow"
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/editor-api": "4.1.2",
|
||||
"@node-red/runtime": "4.1.2",
|
||||
"@node-red/util": "4.1.2",
|
||||
"@node-red/nodes": "4.1.2",
|
||||
"@node-red/editor-api": "4.1.5",
|
||||
"@node-red/runtime": "4.1.5",
|
||||
"@node-red/util": "4.1.5",
|
||||
"@node-red/nodes": "4.1.5",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "3.0.2",
|
||||
"cors": "2.8.5",
|
||||
"express": "4.22.1",
|
||||
"fs-extra": "11.3.0",
|
||||
"node-red-admin": "^4.1.2",
|
||||
"node-red-admin": "^4.1.3",
|
||||
"nopt": "5.0.0",
|
||||
"semver": "7.7.1"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
**/
|
||||
|
||||
var should = require("should");
|
||||
var catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-status.js");
|
||||
var statusNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-status.js");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
|
||||
describe('status Node', function() {
|
||||
|
|
@ -27,7 +27,7 @@ describe('status Node', function() {
|
|||
it('should output a message when called', function(done) {
|
||||
var flow = [ { id:"n1", type:"status", name:"status", wires:[["n2"]], scope:[] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(catchNode, flow, function() {
|
||||
helper.load(statusNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n1.should.have.property('name', 'status');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
const should = require("should");
|
||||
const sinon = require("sinon");
|
||||
|
||||
const NR_TEST_UTILS = require("nr-test-utils");
|
||||
|
||||
// Path to the search.js module
|
||||
const searchModulePath = NR_TEST_UTILS.resolve("@node-red/editor-client/src/js/ui/search.js");
|
||||
|
||||
describe("editor-client/ui/search", function() {
|
||||
let search;
|
||||
let mockRED;
|
||||
|
||||
beforeEach(function() {
|
||||
// Set up minimal RED global mock - only what's needed for tests
|
||||
mockRED = {
|
||||
utils: {
|
||||
getNodeLabel: sinon.stub()
|
||||
}
|
||||
};
|
||||
global.RED = mockRED;
|
||||
|
||||
// Clear require cache to get fresh module instance
|
||||
delete require.cache[searchModulePath];
|
||||
search = require(searchModulePath);
|
||||
|
||||
// Reset the index for each test
|
||||
search._index = {};
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sinon.restore();
|
||||
delete global.RED;
|
||||
delete require.cache[searchModulePath];
|
||||
});
|
||||
|
||||
describe("indexNode", function() {
|
||||
it("preserves original label casing in search results", function() {
|
||||
const node = {
|
||||
id: "node1",
|
||||
type: "tab",
|
||||
_def: { category: "config" }
|
||||
};
|
||||
mockRED.utils.getNodeLabel.returns("MyFlow Name");
|
||||
|
||||
search._indexNode(node);
|
||||
|
||||
// Verify the index key is lowercase (for case-insensitive searching)
|
||||
should.exist(search._index["myflow name"]);
|
||||
|
||||
// Verify the stored label preserves original casing
|
||||
search._index["myflow name"]["node1"].label.should.equal("MyFlow Name");
|
||||
});
|
||||
|
||||
it("indexes node with mixed case label correctly", function() {
|
||||
const node = {
|
||||
id: "node2",
|
||||
type: "subflow",
|
||||
_def: { category: "subflows" }
|
||||
};
|
||||
mockRED.utils.getNodeLabel.returns("MySubFlow_Test");
|
||||
|
||||
search._indexNode(node);
|
||||
|
||||
// Index key should be lowercase
|
||||
should.exist(search._index["mysubflow_test"]);
|
||||
|
||||
// Label should preserve original casing
|
||||
search._index["mysubflow_test"]["node2"].label.should.equal("MySubFlow_Test");
|
||||
});
|
||||
|
||||
it("handles uppercase labels", function() {
|
||||
const node = {
|
||||
id: "node3",
|
||||
type: "tab",
|
||||
_def: { category: "config" }
|
||||
};
|
||||
mockRED.utils.getNodeLabel.returns("UPPERCASE FLOW");
|
||||
|
||||
search._indexNode(node);
|
||||
|
||||
should.exist(search._index["uppercase flow"]);
|
||||
search._index["uppercase flow"]["node3"].label.should.equal("UPPERCASE FLOW");
|
||||
});
|
||||
|
||||
it("handles lowercase labels", function() {
|
||||
const node = {
|
||||
id: "node4",
|
||||
type: "tab",
|
||||
_def: { category: "config" }
|
||||
};
|
||||
mockRED.utils.getNodeLabel.returns("lowercase flow");
|
||||
|
||||
search._indexNode(node);
|
||||
|
||||
should.exist(search._index["lowercase flow"]);
|
||||
search._index["lowercase flow"]["node4"].label.should.equal("lowercase flow");
|
||||
});
|
||||
|
||||
it("stores node reference correctly", function() {
|
||||
const node = {
|
||||
id: "node5",
|
||||
type: "tab",
|
||||
_def: { category: "config" }
|
||||
};
|
||||
mockRED.utils.getNodeLabel.returns("Test Flow");
|
||||
|
||||
search._indexNode(node);
|
||||
|
||||
search._index["test flow"]["node5"].node.should.equal(node);
|
||||
});
|
||||
|
||||
it("handles nodes without labels by falling back to id", function() {
|
||||
const node = {
|
||||
id: "node6",
|
||||
type: "tab",
|
||||
_def: { category: "config" }
|
||||
};
|
||||
mockRED.utils.getNodeLabel.returns(null);
|
||||
|
||||
search._indexNode(node);
|
||||
|
||||
// When there's no label from getNodeLabel,
|
||||
// the node is still indexed by its id
|
||||
should.exist(search._index["node6"]);
|
||||
search._index["node6"]["node6"].label.should.equal("node6");
|
||||
});
|
||||
});
|
||||
|
||||
describe("search", function() {
|
||||
it("finds nodes with case-insensitive search", function() {
|
||||
// Manually set up index with mixed case labels
|
||||
search._index = {
|
||||
"myflow": {
|
||||
"node1": {
|
||||
node: { id: "node1", type: "tab", _def: { category: "config" } },
|
||||
label: "MyFlow"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Search with lowercase should find the node
|
||||
const results = search.search("myflow");
|
||||
results.length.should.equal(1);
|
||||
results[0].label.should.equal("MyFlow");
|
||||
});
|
||||
|
||||
it("returns preserved casing in search results", function() {
|
||||
search._index = {
|
||||
"test subflow": {
|
||||
"node1": {
|
||||
node: { id: "node1", type: "subflow", _def: { category: "subflows" } },
|
||||
label: "Test SubFlow"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const results = search.search("test");
|
||||
results.length.should.equal(1);
|
||||
results[0].label.should.equal("Test SubFlow");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -344,6 +344,36 @@ describe("externalModules api", function() {
|
|||
should.exist(result);
|
||||
should.exist(result.existsSync);
|
||||
})
|
||||
it("imports external modules using export aliasing", async function() {
|
||||
const externalModulesDir = path.join(homeDir,"externalModules");
|
||||
await fs.ensureDir(externalModulesDir);
|
||||
externalModules.init({userDir: externalModulesDir, get:()=>{}, set:()=>{}});
|
||||
|
||||
await fs.writeFile(path.join(externalModulesDir,"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",
|
||||
"private": true,
|
||||
"dependencies": {"fake-pkg":"1.0.0"}
|
||||
}`)
|
||||
|
||||
const packageDir = path.join(externalModulesDir,"node_modules","fake-pkg");
|
||||
await fs.ensureDir(path.join(packageDir,"dist"));
|
||||
await fs.writeFile(path.join(packageDir,"package.json"),`{
|
||||
"name": "fake-pkg",
|
||||
"version": "1.0.0",
|
||||
"exports": {
|
||||
"./M.js": "./dist/M.js"
|
||||
}
|
||||
}`)
|
||||
await fs.writeFile(path.join(packageDir,"dist","M.js"),"module.exports = 42;\n");
|
||||
|
||||
await externalModules.checkFlowDependencies([]);
|
||||
|
||||
const importedValue = await externalModules.import("fake-pkg/M.js")
|
||||
const normalizedValue = importedValue && importedValue.default !== undefined ? importedValue.default : importedValue;
|
||||
normalizedValue.should.equal(42);
|
||||
})
|
||||
it("rejects unknown modules", async function() {
|
||||
externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue