mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Resolve merge conflicts on update from upstream
This commit is contained in:
commit
965d877b63
3196
CHANGELOG.md
Normal file
3196
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@ -6,7 +6,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/andrewsgreene/not-node-red.git"
|
||||
"url": "https://github.com/defenseunicorns/Sparkles-Guide.git"
|
||||
},
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
@ -19,13 +19,13 @@
|
||||
"docs": "grunt docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": "8.6.0",
|
||||
"acorn": "8.7.0",
|
||||
"acorn-walk": "8.2.0",
|
||||
"ajv": "8.8.2",
|
||||
"async-mutex": "0.3.2",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.19.0",
|
||||
"body-parser": "1.19.1",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"clone": "2.1.2",
|
||||
"content-type": "1.0.4",
|
||||
@ -34,7 +34,7 @@
|
||||
"cors": "2.8.5",
|
||||
"cronosjs": "1.7.1",
|
||||
"denque": "2.0.1",
|
||||
"express": "4.17.1",
|
||||
"express": "4.17.2",
|
||||
"express-session": "1.17.2",
|
||||
"form-data": "4.0.0",
|
||||
"fs-extra": "10.0.0",
|
||||
@ -43,7 +43,7 @@
|
||||
"hash-sum": "2.0.0",
|
||||
"hpagent": "0.1.2",
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"i18next": "21.5.4",
|
||||
"i18next": "21.6.6",
|
||||
"iconv-lite": "0.6.3",
|
||||
"is-utf8": "0.2.1",
|
||||
"js-yaml": "3.14.1",
|
||||
@ -54,21 +54,21 @@
|
||||
"memorystore": "1.6.6",
|
||||
"mime": "2.5.2",
|
||||
"moment-timezone": "0.5.34",
|
||||
"mqtt": "4.2.8",
|
||||
"mqtt": "4.3.4",
|
||||
"multer": "1.4.3",
|
||||
"mustache": "4.2.0",
|
||||
"node-red-admin": "^2.2.1",
|
||||
"nopt": "5.0.0",
|
||||
"oauth2orize": "1.11.1",
|
||||
"on-headers": "1.0.2",
|
||||
"passport": "0.5.0",
|
||||
"passport": "0.5.2",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"raw-body": "2.4.2",
|
||||
"semver": "7.3.5",
|
||||
"tar": "6.1.11",
|
||||
"tough-cookie": "4.0.0",
|
||||
"uglify-js": "3.14.4",
|
||||
"uglify-js": "3.14.5",
|
||||
"uuid": "8.3.2",
|
||||
"ws": "7.5.1",
|
||||
"xml2js": "0.4.23"
|
||||
@ -78,7 +78,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^9.1.1",
|
||||
"dompurify": "2.3.3",
|
||||
"dompurify": "2.3.4",
|
||||
"grunt": "1.4.1",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-cli": "~1.4.3",
|
||||
@ -101,17 +101,17 @@
|
||||
"i18next-http-backend": "1.3.1",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
||||
"marked": "3.0.7",
|
||||
"marked": "4.0.10",
|
||||
"minami": "1.2.3",
|
||||
"mocha": "9.1.3",
|
||||
"node-red-node-test-helper": "^0.2.7",
|
||||
"nodemon": "2.0.15",
|
||||
"proxy": "^1.0.2",
|
||||
"sass": "1.44.0",
|
||||
"sass": "1.48.0",
|
||||
"should": "13.2.3",
|
||||
"sinon": "11.1.2",
|
||||
"stoppable": "^1.1.0",
|
||||
"supertest": "6.1.6"
|
||||
"supertest": "6.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
@ -146,7 +146,7 @@ function authenticateUserToken(req) {
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
@ -163,6 +163,9 @@ TokensStrategy.prototype.authenticate = function(req) {
|
||||
authenticateUserToken(req).then(user => {
|
||||
this.success(user,{scope:user.permissions});
|
||||
}).catch(err => {
|
||||
if (err) {
|
||||
log.trace("token authentication failure: "+err.stack?err.stack:err)
|
||||
}
|
||||
this.fail(401);
|
||||
});
|
||||
}
|
||||
|
@ -90,6 +90,8 @@ function init(settings,_server,storage,runtimeAPI) {
|
||||
auth.getToken,
|
||||
auth.errorHandler
|
||||
);
|
||||
} else if (settings.adminAuth.tokens) {
|
||||
adminApp.use(passport.initialize());
|
||||
}
|
||||
adminApp.post("/auth/revoke",auth.needsPermission(""),auth.revoke,apiUtil.errorHandler);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-api",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.6",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@ -16,14 +16,14 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "2.1.4",
|
||||
"@node-red/editor-client": "2.1.4",
|
||||
"@node-red/util": "2.1.6",
|
||||
"@node-red/editor-client": "2.1.6",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.19.0",
|
||||
"body-parser": "1.19.1",
|
||||
"clone": "2.1.2",
|
||||
"cors": "2.8.5",
|
||||
"express-session": "1.17.2",
|
||||
"express": "4.17.1",
|
||||
"express": "4.17.2",
|
||||
"memorystore": "1.6.6",
|
||||
"mime": "2.5.2",
|
||||
"multer": "1.4.3",
|
||||
@ -31,7 +31,7 @@
|
||||
"oauth2orize": "1.11.1",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"passport": "0.5.0",
|
||||
"passport": "0.5.2",
|
||||
"ws": "7.5.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
@ -59,6 +59,8 @@
|
||||
"hideOtherFlows": "他のフローを非表示",
|
||||
"showAllFlows": "全てのフローを表示",
|
||||
"hideAllFlows": "全てのフローを非表示",
|
||||
"hiddenFlows": "__count__ 個の非表示のフロー一覧",
|
||||
"hiddenFlows_plural": "__count__ 個の非表示のフロー一覧",
|
||||
"showLastHiddenFlow": "最後に非表示にしたフローを表示",
|
||||
"listFlows": "フロー一覧",
|
||||
"listSubflows": "サブフロー一覧",
|
||||
@ -669,7 +671,8 @@
|
||||
"unusedConfigNodes": "未使用の設定ノード",
|
||||
"invalidNodes": "不正なノード",
|
||||
"uknownNodes": "未知のノード",
|
||||
"unusedSubflows": "未使用のサブフロー"
|
||||
"unusedSubflows": "未使用のサブフロー",
|
||||
"hiddenFlows": "非表示のフロー"
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-client",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.6",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -71,6 +71,7 @@ RED.clipboard = (function() {
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
RED.view.focus();
|
||||
}
|
||||
},
|
||||
{ // red-ui-clipboard-dialog-download
|
||||
@ -81,6 +82,7 @@ RED.clipboard = (function() {
|
||||
var data = $("#red-ui-clipboard-dialog-export-text").val();
|
||||
downloadData("flows.json", data);
|
||||
$( this ).dialog( "close" );
|
||||
RED.view.focus();
|
||||
}
|
||||
},
|
||||
{ // red-ui-clipboard-dialog-export
|
||||
@ -95,6 +97,7 @@ RED.clipboard = (function() {
|
||||
$( this ).dialog( "close" );
|
||||
copyText(flowData);
|
||||
RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"});
|
||||
RED.view.focus();
|
||||
} else {
|
||||
var flowToExport = $("#red-ui-clipboard-dialog-export-text").val();
|
||||
var selectedPath = activeLibraries[activeTab].getSelected();
|
||||
@ -110,6 +113,7 @@ RED.clipboard = (function() {
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
$(dialog).dialog( "close" );
|
||||
RED.view.focus();
|
||||
RED.notify(RED._("library.exportedToLibrary"),"success");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
if (xhr.status === 401) {
|
||||
@ -171,6 +175,7 @@ RED.clipboard = (function() {
|
||||
}
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
RED.view.focus();
|
||||
}
|
||||
},
|
||||
{ // red-ui-clipboard-dialog-import-conflict
|
||||
@ -203,6 +208,7 @@ RED.clipboard = (function() {
|
||||
// console.table(pendingImportConfig.importNodes.map(function(n) { return {id:n.id,type:n.type,result:importMap[n.id]}}))
|
||||
RED.view.importNodes(newNodes, pendingImportConfig.importOptions);
|
||||
$( this ).dialog( "close" );
|
||||
RED.view.focus();
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -940,7 +946,8 @@ RED.clipboard = (function() {
|
||||
if (truncated) {
|
||||
msg += "_truncated";
|
||||
}
|
||||
$("#red-ui-clipboard-hidden").val(value).focus().select();
|
||||
var clipboardHidden = $('<textarea type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo(document.body);
|
||||
clipboardHidden.val(value).focus().select();
|
||||
var result = document.execCommand("copy");
|
||||
if (result && element) {
|
||||
var popover = RED.popover.create({
|
||||
@ -954,14 +961,13 @@ RED.clipboard = (function() {
|
||||
},1000);
|
||||
popover.open();
|
||||
}
|
||||
$("#red-ui-clipboard-hidden").val("");
|
||||
clipboardHidden.remove();
|
||||
if (currentFocus) {
|
||||
$(currentFocus).focus();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function importNodes(nodesStr,addFlow) {
|
||||
var newNodes = nodesStr;
|
||||
if (typeof nodesStr === 'string') {
|
||||
@ -1236,8 +1242,6 @@ RED.clipboard = (function() {
|
||||
init: function() {
|
||||
setupDialogs();
|
||||
|
||||
$('<textarea type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo("#red-ui-editor");
|
||||
|
||||
RED.actions.add("core:show-export-dialog",showExportNodes);
|
||||
RED.actions.add("core:show-import-dialog",showImportNodes);
|
||||
|
||||
|
@ -578,7 +578,7 @@ RED.tabs = (function() {
|
||||
|
||||
function findPreviousVisibleTab(li) {
|
||||
if (!li) {
|
||||
li = ul.find("li.active").parent();
|
||||
li = ul.find("li.active");
|
||||
}
|
||||
var previous = li.prev();
|
||||
while(previous.length > 0 && previous.hasClass("hide-tab")) {
|
||||
@ -588,9 +588,9 @@ RED.tabs = (function() {
|
||||
}
|
||||
function findNextVisibleTab(li) {
|
||||
if (!li) {
|
||||
li = ul.find("li.active").parent();
|
||||
li = ul.find("li.active");
|
||||
}
|
||||
var next = ul.find("li.active").next();
|
||||
var next = li.next();
|
||||
while(next.length > 0 && next.hasClass("hide-tab")) {
|
||||
next = next.next();
|
||||
}
|
||||
|
@ -247,7 +247,7 @@
|
||||
var currentExpression = expressionEditor.getValue();
|
||||
var expr;
|
||||
var usesContext = false;
|
||||
var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
|
||||
var legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
|
||||
$(".red-ui-editor-type-expression-legacy").toggle(legacyMode);
|
||||
try {
|
||||
expr = jsonata(currentExpression);
|
||||
|
@ -81,7 +81,8 @@
|
||||
clearTimeout: true,
|
||||
setInterval: true,
|
||||
clearInterval: true
|
||||
}
|
||||
},
|
||||
extraLibs: options.extraLibs
|
||||
});
|
||||
if (options.cursor) {
|
||||
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
|
||||
|
@ -55,7 +55,9 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!isSameObj(old_env, new_env)) {
|
||||
if (!old_env && new_env.length === 0) {
|
||||
delete node.env;
|
||||
} else if (!isSameObj(old_env, new_env)) {
|
||||
editState.changes.env = node.env;
|
||||
if (new_env.length === 0) {
|
||||
delete node.env;
|
||||
|
@ -109,7 +109,7 @@ RED.utils = (function() {
|
||||
window._marked.use({extensions: [descriptionList, description] } );
|
||||
|
||||
function renderMarkdown(txt) {
|
||||
var rendered = _marked(txt);
|
||||
var rendered = _marked.parse(txt);
|
||||
var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true})
|
||||
return cleaned;
|
||||
}
|
||||
|
@ -596,7 +596,7 @@ RED.view = (function() {
|
||||
},
|
||||
tooltip: function(d) {
|
||||
if (d.validationErrors && d.validationErrors.length > 0) {
|
||||
return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ")
|
||||
return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ")
|
||||
}
|
||||
},
|
||||
show: function(n) { return !n.valid }
|
||||
|
@ -66,7 +66,7 @@ RED.workspaces = (function() {
|
||||
var tabId = RED.nodes.id();
|
||||
do {
|
||||
workspaceIndex += 1;
|
||||
} while ($("#red-ui-workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
|
||||
} while ($("#red-ui-workspace-tabs li[flowname='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
|
||||
|
||||
ws = {
|
||||
type: "tab",
|
||||
@ -79,12 +79,15 @@ RED.workspaces = (function() {
|
||||
};
|
||||
RED.nodes.addWorkspace(ws,targetIndex);
|
||||
workspace_tabs.addTab(ws,targetIndex);
|
||||
|
||||
workspace_tabs.activateTab(tabId);
|
||||
if (!skipHistoryEntry) {
|
||||
RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()});
|
||||
RED.nodes.dirty(true);
|
||||
}
|
||||
}
|
||||
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
|
||||
|
||||
RED.view.focus();
|
||||
return ws;
|
||||
}
|
||||
@ -208,10 +211,20 @@ RED.workspaces = (function() {
|
||||
},
|
||||
onhide: function(tab) {
|
||||
hideStack.push(tab.id);
|
||||
|
||||
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
|
||||
hiddenTabs[tab.id] = true;
|
||||
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
|
||||
|
||||
RED.events.emit("workspace:hide",{workspace: tab.id})
|
||||
},
|
||||
onshow: function(tab) {
|
||||
removeFromHideStack(tab.id);
|
||||
|
||||
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
|
||||
delete hiddenTabs[tab.id];
|
||||
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
|
||||
|
||||
RED.events.emit("workspace:show",{workspace: tab.id})
|
||||
},
|
||||
minimumActiveTabWidth: 150,
|
||||
@ -542,9 +555,6 @@ RED.workspaces = (function() {
|
||||
}
|
||||
if (workspace_tabs.contains(id)) {
|
||||
workspace_tabs.hideTab(id);
|
||||
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
|
||||
hiddenTabs[id] = true;
|
||||
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
|
||||
}
|
||||
},
|
||||
isHidden: function(id) {
|
||||
@ -572,14 +582,11 @@ RED.workspaces = (function() {
|
||||
}
|
||||
workspace_tabs.activateTab(id);
|
||||
}
|
||||
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
|
||||
delete hiddenTabs[id];
|
||||
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
|
||||
},
|
||||
refresh: function() {
|
||||
RED.nodes.eachWorkspace(function(ws) {
|
||||
workspace_tabs.renameTab(ws.id,ws.label);
|
||||
|
||||
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
|
||||
})
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
if (workspace_tabs.contains(sf.id)) {
|
||||
|
@ -360,6 +360,7 @@ button.red-ui-button-small
|
||||
position: absolute;
|
||||
top: -3000px;
|
||||
}
|
||||
|
||||
.form-row .red-ui-editor-node-label-form-row {
|
||||
margin: 5px 0 0 50px;
|
||||
label {
|
||||
|
@ -73,8 +73,8 @@ export default {
|
||||
},
|
||||
element: "#red-ui-workspace-tabs > li.active",
|
||||
description: {
|
||||
"en-US": '<p>Tabs can now be hidden by clicking their <i class="fa fa-times"></i> icon.</p><p>The Info Sidebar will still list all of your tabs, and tell you which ones are currently hidden.',
|
||||
"ja": '<p><i class="fa fa-times"></i> アイコンをクリックすることで、タブを非表示にできます。</p><p>情報サイドバーには、全てのタブが一覧表示されており、現在非表示になっているタブを確認できます。'
|
||||
"en-US": '<p>Tabs can now be hidden by clicking their <i class="fa fa-eye-slash"></i> icon.</p><p>The Info Sidebar will still list all of your tabs, and tell you which ones are currently hidden.',
|
||||
"ja": '<p><i class="fa fa-eye-slash"></i> アイコンをクリックすることで、タブを非表示にできます。</p><p>情報サイドバーには、全てのタブが一覧表示されており、現在非表示になっているタブを確認できます。'
|
||||
},
|
||||
interactive: false,
|
||||
prepare() {
|
||||
|
711
packages/node_modules/@node-red/nodes/core/common/20-inject.html
vendored
Normal file
711
packages/node_modules/@node-red/nodes/core/common/20-inject.html
vendored
Normal file
@ -0,0 +1,711 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="inject">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
|
||||
<div class="form-row node-input-property-container-row">
|
||||
<ol id="node-input-property-container"></ol>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-once">
|
||||
<label for="node-input-once"> </label>
|
||||
<input type="checkbox" id="node-input-once" style="display:inline-block; width:15px; vertical-align:baseline;">
|
||||
<span data-i18n="inject.onstart"></span>
|
||||
<input type="text" id="node-input-onceDelay" placeholder="0.1" style="width:45px; height:28px;">
|
||||
<span data-i18n="inject.onceDelay"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for=""><i class="fa fa-repeat"></i> <span data-i18n="inject.label.repeat"></span></label>
|
||||
<select id="inject-time-type-select">
|
||||
<option value="none" data-i18n="inject.none"></option>
|
||||
<option value="interval" data-i18n="inject.interval"></option>
|
||||
<option value="interval-time" data-i18n="inject.interval-time"></option>
|
||||
<option value="time" data-i18n="inject.time"></option>
|
||||
</select>
|
||||
<input type="hidden" id="node-input-repeat">
|
||||
<input type="hidden" id="node-input-crontab">
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
|
||||
<span data-i18n="inject.every"></span>
|
||||
<input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
|
||||
<select style="width:100px" id="inject-time-interval-units">
|
||||
<option value="s" data-i18n="inject.seconds"></option>
|
||||
<option value="m" data-i18n="inject.minutes"></option>
|
||||
<option value="h" data-i18n="inject.hours"></option>
|
||||
</select><br/>
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
|
||||
<span data-i18n="inject.every"></span> <select style="width:90px; margin-left:20px;" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="10">10</option>
|
||||
<option value="12">12</option>
|
||||
<option value="15">15</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="0">60</option>
|
||||
</select> <span data-i18n="inject.minutes"></span><br/>
|
||||
<span data-i18n="inject.between"></span> <select id="inject-time-interval-time-start" class="inject-time-times"></select>
|
||||
<span data-i18n="inject.and"></span> <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
|
||||
<div id="inject-time-interval-time-days" class="inject-time-days" style="margin-top:5px">
|
||||
<div style="display:inline-block; vertical-align:top; margin-right:5px;" data-i18n="inject.on">on</div>
|
||||
<div style="display:inline-block;">
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
|
||||
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
|
||||
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
|
||||
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
|
||||
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
|
||||
<span data-i18n="inject.at"></span> <input type="text" id="inject-time-time" value="12:00"></input><br/>
|
||||
<div id="inject-time-time-days" class="inject-time-days">
|
||||
<div style="display:inline-block; vertical-align:top; margin-right:5px;" data-i18n="inject.on"></div>
|
||||
<div style="display:inline-block;">
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
|
||||
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
|
||||
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
|
||||
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
|
||||
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.inject-time-row {
|
||||
padding-left: 110px;
|
||||
}
|
||||
.inject-time-row select {
|
||||
margin: 3px 0;
|
||||
}
|
||||
.inject-time-days label {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
vertical-align: baseline;
|
||||
width: 100px;
|
||||
}
|
||||
.inject-time-days input {
|
||||
width: auto !important;
|
||||
vertical-align: baseline !important;
|
||||
}
|
||||
.inject-time-times {
|
||||
width: 90px !important;
|
||||
}
|
||||
#inject-time-time {
|
||||
width: 75px;
|
||||
margin-left: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.inject-time-count {
|
||||
padding-left: 3px !important;
|
||||
width: 80px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
|
||||
function resizeDialog(size) {
|
||||
size = size || { height: $(".red-ui-tray-content form").height() }
|
||||
var rows = $("#dialog-form>div:not(.node-input-property-container-row):visible");
|
||||
var height = size.height;
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-property-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
height += 16;
|
||||
$("#node-input-property-container").editableList('height',height);
|
||||
}
|
||||
/** Retrieve editableList items (refactored for re-use in the form inject button)*/
|
||||
function getProps(el, legacy) {
|
||||
var result = {
|
||||
props: []
|
||||
}
|
||||
el.each(function(i) {
|
||||
var prop = $(this);
|
||||
var p = {
|
||||
p:prop.find(".node-input-prop-property-name").typedInput('value')
|
||||
};
|
||||
if (p.p) {
|
||||
p.v = prop.find(".node-input-prop-property-value").typedInput('value');
|
||||
p.vt = prop.find(".node-input-prop-property-value").typedInput('type');
|
||||
if(legacy) {
|
||||
if (p.p === "payload") { // save payload to old "legacy" property
|
||||
result.payloadType = p.vt;
|
||||
result.payload = p.v;
|
||||
delete p.v;
|
||||
delete p.vt;
|
||||
} else if (p.p === "topic" && p.vt === "str") {
|
||||
result.topic = p.v;
|
||||
delete p.v;
|
||||
}
|
||||
}
|
||||
result.props.push(p);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/** Perform inject, optionally sending a custom msg (refactored for re-use in the form inject button)*/
|
||||
function doInject(node, customMsg) {
|
||||
var label = node._def.label.call(node,customMsg?customMsg.__user_inject_props__:undefined);
|
||||
if (label.length > 30) {
|
||||
label = label.substring(0, 50) + "...";
|
||||
}
|
||||
label = label.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
$.ajax({
|
||||
url: "inject/" + node.id,
|
||||
type: "POST",
|
||||
data: JSON.stringify(customMsg||{}),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: function (resp) {
|
||||
RED.notify(node._("inject.success", { label: label }), { type: "success", id: "inject", timeout: 2000 });
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
if (jqXHR.status == 404) {
|
||||
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.not-deployed") }), "error");
|
||||
} else if (jqXHR.status == 500) {
|
||||
RED.notify(node._("common.notification.error", { message: node._("inject.errors.failed") }), "error");
|
||||
} else if (jqXHR.status == 0) {
|
||||
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.no-response") }), "error");
|
||||
} else {
|
||||
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.unexpected", { status: jqXHR.status, message: textStatus }) }), "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType('inject',{
|
||||
category: 'common',
|
||||
color:"#a6bbcf",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v) {
|
||||
if (!v || v.length === 0) { return true }
|
||||
for (var i=0;i<v.length;i++) {
|
||||
if (/msg|flow|global/.test(v[i].vt)) {
|
||||
if (!RED.utils.validatePropertyExpression(v[i].v)) {
|
||||
return false;
|
||||
}
|
||||
} else if (v[i].vt === "jsonata") {
|
||||
try{jsonata(v[i].v);}catch(e){return false;}
|
||||
} else if (v[i].vt === "json") {
|
||||
try{JSON.parse(v[i].v);}catch(e){return false;}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
repeat: {value:"", validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
|
||||
crontab: {value:""},
|
||||
once: {value:false},
|
||||
onceDelay: {value:0.1},
|
||||
topic: {value:""},
|
||||
payload: {value:"", validate: RED.validators.typedInput("payloadType")},
|
||||
payloadType: {value:"date"},
|
||||
},
|
||||
icon: "inject.svg",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
outputLabels: function(index) {
|
||||
var lab = '';
|
||||
|
||||
// if only payload and topic - display payload type
|
||||
// if only one property - show it's type
|
||||
// if more than one property (other than payload and topic) - show "x properties" where x is the number of properties.
|
||||
// this.props will not be an array for legacy inject nodes until they are re-deployed
|
||||
//
|
||||
var props = this.props;
|
||||
if (!Array.isArray(props)) {
|
||||
props = [
|
||||
{ p:"payload", v: this.payload, vt: this.payloadType },
|
||||
{ p:"topic", v: this.topic, vt: "str" }
|
||||
]
|
||||
}
|
||||
if (props) {
|
||||
for (var i=0,l=props.length; i<l; i++) {
|
||||
if (i > 0) lab += "\n";
|
||||
if (i === 5) {
|
||||
lab += "... +"+(props.length-5);
|
||||
break;
|
||||
}
|
||||
lab += props[i].p+": ";
|
||||
|
||||
var propType = props[i].p === "payload"? this.payloadType : props[i].vt;
|
||||
if (propType === "json") {
|
||||
try {
|
||||
var parsedProp = JSON.parse(props[i].p === "payload"? this.payload : props[i].v);
|
||||
propType = typeof parsedProp;
|
||||
if (propType === "object" && Array.isArray(parsedProp)) {
|
||||
propType = "Array";
|
||||
}
|
||||
} catch(e) {
|
||||
propType = "invalid";
|
||||
}
|
||||
}
|
||||
lab += this._("inject.label."+propType);
|
||||
}
|
||||
}
|
||||
return lab;
|
||||
},
|
||||
label: function(customProps) {
|
||||
var suffix = "";
|
||||
// if fire once then add small indication
|
||||
if (this.once) {
|
||||
suffix = " ¹";
|
||||
}
|
||||
// but replace with repeat one if set to repeat
|
||||
if ((this.repeat && this.repeat != 0) || this.crontab) {
|
||||
suffix = " ↻";
|
||||
}
|
||||
if (this.name) {
|
||||
return this.name+suffix;
|
||||
}
|
||||
var payload = "";
|
||||
var payloadType = "str";
|
||||
var topic = "";
|
||||
if (customProps) {
|
||||
for (var i=0;i<customProps.length;i++) {
|
||||
if (customProps[i].p === "payload") {
|
||||
payload = customProps[i].v;
|
||||
payloadType = customProps[i].vt;
|
||||
} else if (customProps[i].p === "topic") {
|
||||
topic = customProps[i].v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
payload = this.payload || "";
|
||||
payloadType = this.payloadType || "str";
|
||||
topic = this.topic || "";
|
||||
}
|
||||
if (payloadType === "string" ||
|
||||
payloadType === "str" ||
|
||||
payloadType === "num" ||
|
||||
payloadType === "bool" ||
|
||||
payloadType === "json") {
|
||||
if ((topic !== "") && ((topic.length + payload.length) <= 32)) {
|
||||
return topic + ":" + payload+suffix;
|
||||
} else if (payload.length > 0 && payload.length < 24) {
|
||||
return payload+suffix;
|
||||
} else {
|
||||
return this._("inject.inject")+suffix;
|
||||
}
|
||||
} else if (payloadType === 'date' || payloadType === 'bin' || payloadType === 'env') {
|
||||
if ((topic !== "") && (topic.length <= 16)) {
|
||||
return topic + ":" + this._('inject.label.'+payloadType)+suffix;
|
||||
} else {
|
||||
return this._('inject.label.'+payloadType)+suffix;
|
||||
}
|
||||
} else if (payloadType === 'flow' || payloadType === 'global') {
|
||||
var key = RED.utils.parseContextKey(payload);
|
||||
return payloadType+"."+key.key+suffix;
|
||||
} else {
|
||||
return this._("inject.inject")+suffix;
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var payloadType = node.payloadType;
|
||||
|
||||
if (node.payloadType == null) {
|
||||
if (node.payload == "") {
|
||||
payloadType = "date";
|
||||
} else {
|
||||
payloadType = "str";
|
||||
}
|
||||
} else if (node.payloadType === 'string' || node.payloadType === 'none') {
|
||||
payloadType = "str";
|
||||
}
|
||||
|
||||
$("#inject-time-type-select").on("change", function() {
|
||||
$("#node-input-crontab").val('');
|
||||
var id = $("#inject-time-type-select").val();
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-row-"+id).show();
|
||||
if ((id == "none") || (id == "interval") || (id == "interval-time")) {
|
||||
$("#node-once").show();
|
||||
}
|
||||
else {
|
||||
$("#node-once").hide();
|
||||
$("#node-input-once").prop('checked', false);
|
||||
}
|
||||
|
||||
// Scroll down
|
||||
var scrollDiv = $("#dialog-form").parent();
|
||||
scrollDiv.scrollTop(scrollDiv.prop('scrollHeight'));
|
||||
resizeDialog();
|
||||
});
|
||||
|
||||
$("#node-input-once").on("change", function() {
|
||||
$("#node-input-onceDelay").attr('disabled', !$("#node-input-once").prop('checked'));
|
||||
})
|
||||
|
||||
$(".inject-time-times").each(function() {
|
||||
for (var i=0; i<24; i++) {
|
||||
var l = (i<10?"0":"")+i+":00";
|
||||
$(this).append($("<option></option>").val(i).text(l));
|
||||
}
|
||||
});
|
||||
$("<option></option>").val(24).text("00:00").appendTo("#inject-time-interval-time-end");
|
||||
$("#inject-time-interval-time-start").on("change", function() {
|
||||
var start = Number($("#inject-time-interval-time-start").val());
|
||||
var end = Number($("#inject-time-interval-time-end").val());
|
||||
$("#inject-time-interval-time-end option").remove();
|
||||
for (var i=start+1; i<25; i++) {
|
||||
var l = (i<10?"0":"")+i+":00";
|
||||
if (i==24) {
|
||||
l = "00:00";
|
||||
}
|
||||
var opt = $("<option></option>").val(i).text(l).appendTo("#inject-time-interval-time-end");
|
||||
if (i === end) {
|
||||
opt.attr("selected","selected");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(".inject-time-count").spinner({
|
||||
//max:60,
|
||||
min:1
|
||||
});
|
||||
|
||||
var repeattype = "none";
|
||||
if (node.repeat != "" && node.repeat != 0) {
|
||||
repeattype = "interval";
|
||||
var r = "s";
|
||||
var c = node.repeat;
|
||||
if (node.repeat % 60 === 0) { r = "m"; c = c/60; }
|
||||
if (node.repeat % 1440 === 0) { r = "h"; c = c/60; }
|
||||
$("#inject-time-interval-count").val(c);
|
||||
$("#inject-time-interval-units").val(r);
|
||||
$("#inject-time-interval-days").prop("disabled","disabled");
|
||||
} else if (node.crontab) {
|
||||
var cronparts = node.crontab.split(" ");
|
||||
var days = cronparts[4];
|
||||
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
|
||||
repeattype = "time";
|
||||
// Fixed time
|
||||
var time = cronparts[1]+":"+cronparts[0];
|
||||
$("#inject-time-time").val(time);
|
||||
$("#inject-time-type-select").val("s");
|
||||
if (days == "*") {
|
||||
$("#inject-time-time-days input[type=checkbox]").prop("checked",true);
|
||||
} else {
|
||||
$("#inject-time-time-days input[type=checkbox]").removeAttr("checked");
|
||||
days.split(",").forEach(function(v) {
|
||||
$("#inject-time-time-days [value=" + v + "]").prop("checked", true);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
repeattype = "interval-time";
|
||||
// interval - time period
|
||||
var minutes = cronparts[0].slice(2);
|
||||
if (minutes === "") { minutes = "0"; }
|
||||
$("#inject-time-interval-time-units").val(minutes);
|
||||
if (days == "*") {
|
||||
$("#inject-time-interval-time-days input[type=checkbox]").prop("checked",true);
|
||||
} else {
|
||||
$("#inject-time-interval-time-days input[type=checkbox]").removeAttr("checked");
|
||||
days.split(",").forEach(function(v) {
|
||||
$("#inject-time-interval-time-days [value=" + v + "]").prop("checked", true);
|
||||
});
|
||||
}
|
||||
var time = cronparts[1];
|
||||
var timeparts = time.split(",");
|
||||
var start;
|
||||
var end;
|
||||
if (timeparts.length == 1) {
|
||||
// 0 or 0-10
|
||||
var hours = timeparts[0].split("-");
|
||||
if (hours.length == 1) {
|
||||
if (hours[0] === "") {
|
||||
start = "0";
|
||||
end = "0";
|
||||
}
|
||||
else {
|
||||
start = hours[0];
|
||||
end = Number(hours[0])+1;
|
||||
}
|
||||
} else {
|
||||
start = hours[0];
|
||||
end = Number(hours[1])+1;
|
||||
}
|
||||
} else {
|
||||
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
|
||||
var startparts = timeparts[0].split("-");
|
||||
start = startparts[0];
|
||||
|
||||
var endparts = timeparts[1].split("-");
|
||||
if (endparts.length == 1) {
|
||||
end = Number(endparts[0])+1;
|
||||
} else {
|
||||
end = Number(endparts[1])+1;
|
||||
}
|
||||
}
|
||||
$("#inject-time-interval-time-end").val(end);
|
||||
$("#inject-time-interval-time-start").val(start);
|
||||
|
||||
}
|
||||
} else {
|
||||
$("#inject-time-type-select").val("none");
|
||||
}
|
||||
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-type-select").val(repeattype);
|
||||
$("#inject-time-row-"+repeattype).show();
|
||||
|
||||
/* */
|
||||
|
||||
var eList = $('#node-input-property-container').css('min-height','120px').css('min-width','450px');
|
||||
|
||||
eList.editableList({
|
||||
buttons: [
|
||||
{
|
||||
id: "node-inject-test-inject-button",
|
||||
label: node._("inject.injectNow"),
|
||||
click: function(e) {
|
||||
var items = eList.editableList('items');
|
||||
var props = getProps(items);
|
||||
var m = {__user_inject_props__: props.props};
|
||||
doInject(node, m);
|
||||
}
|
||||
}
|
||||
],
|
||||
addItem: function(container,i,opt) {
|
||||
var prop = opt;
|
||||
if (!prop.hasOwnProperty('p')) {
|
||||
prop = {p:"",v:"",vt:"str"};
|
||||
}
|
||||
container.css({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap'
|
||||
});
|
||||
var row = $('<div/>').appendTo(container);
|
||||
|
||||
var propertyName = $('<input/>',{class:"node-input-prop-property-name",type:"text"})
|
||||
.css("width","30%")
|
||||
.appendTo(row)
|
||||
.typedInput({types:['msg']});
|
||||
|
||||
$('<div/>',{style: 'display:inline-block; padding:0px 6px;'})
|
||||
.text('=')
|
||||
.appendTo(row);
|
||||
|
||||
var propertyValue = $('<input/>',{class:"node-input-prop-property-value",type:"text"})
|
||||
.css("width","calc(70% - 30px)")
|
||||
.appendTo(row)
|
||||
.typedInput({default:prop.vt || 'str',types:['flow','global','str','num','bool','json','bin','date','jsonata','env','msg']});
|
||||
|
||||
propertyName.typedInput('value',prop.p);
|
||||
propertyValue.typedInput('value',prop.v);
|
||||
},
|
||||
removable: true,
|
||||
sortable: true
|
||||
});
|
||||
$('#node-inject-test-inject-button').css("float", "right").css("margin-right", "unset");
|
||||
|
||||
if (RED.nodes.subflow(node.z)) {
|
||||
$('#node-inject-test-inject-button').attr("disabled",true);
|
||||
}
|
||||
|
||||
if (!node.props) {
|
||||
var payload = {
|
||||
p:'payload',
|
||||
v: node.payload ? node.payload : '',
|
||||
vt:payloadType ? payloadType : 'date'
|
||||
};
|
||||
var topic = {
|
||||
p:'topic',
|
||||
v: node.topic ? node.topic : '',
|
||||
vt:'str'
|
||||
}
|
||||
node.props = [payload,topic];
|
||||
}
|
||||
|
||||
for (var i=0; i<node.props.length; i++) {
|
||||
var prop = node.props[i];
|
||||
var newProp = { p: prop.p, v: prop.v, vt: prop.vt };
|
||||
if (newProp.v === undefined) {
|
||||
if (prop.p === 'payload') {
|
||||
newProp.v = node.payload ? node.payload : '';
|
||||
newProp.vt = payloadType ? payloadType : 'date';
|
||||
} else if (prop.p === 'topic' && prop.vt === "str") {
|
||||
newProp.v = node.topic ? node.topic : '';
|
||||
}
|
||||
}
|
||||
if (newProp.vt === "string") {
|
||||
// Fix bug in pre 2.1 where an old Inject node might have
|
||||
// a migrated rule with type 'string' not 'str'
|
||||
newProp.vt = "str";
|
||||
}
|
||||
eList.editableList('addItem',newProp);
|
||||
}
|
||||
|
||||
$("#inject-time-type-select").trigger("change");
|
||||
$("#inject-time-interval-time-start").trigger("change");
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
var repeat = "";
|
||||
var crontab = "";
|
||||
var type = $("#inject-time-type-select").val();
|
||||
if (type == "none") {
|
||||
// nothing
|
||||
} else if (type == "interval") {
|
||||
var count = $("#inject-time-interval-count").val();
|
||||
var units = $("#inject-time-interval-units").val();
|
||||
if (units == "s") {
|
||||
repeat = count;
|
||||
} else {
|
||||
if (units == "m") {
|
||||
//crontab = "*/"+count+" * * * "+days;
|
||||
repeat = count * 60;
|
||||
} else if (units == "h") {
|
||||
//crontab = "0 */"+count+" * * "+days;
|
||||
repeat = count * 60 * 60;
|
||||
}
|
||||
}
|
||||
} else if (type == "interval-time") {
|
||||
repeat = "";
|
||||
var count = $("#inject-time-interval-time-units").val();
|
||||
var startTime = Number($("#inject-time-interval-time-start").val());
|
||||
var endTime = Number($("#inject-time-interval-time-end").val());
|
||||
var days = $('#inject-time-interval-time-days input[type=checkbox]:checked').map(function(_, el) {
|
||||
return $(el).val()
|
||||
}).get();
|
||||
if (days.length == 0) {
|
||||
crontab = "";
|
||||
} else {
|
||||
if (days.length == 7) {
|
||||
days="*";
|
||||
} else {
|
||||
days = days.join(",");
|
||||
}
|
||||
var timerange = "";
|
||||
if (endTime == 0) {
|
||||
timerange = startTime+"-23";
|
||||
} else if (startTime+1 < endTime) {
|
||||
timerange = startTime+"-"+(endTime-1);
|
||||
} else if (startTime+1 == endTime) {
|
||||
timerange = startTime;
|
||||
} else {
|
||||
var startpart = "";
|
||||
var endpart = "";
|
||||
if (startTime == 23) {
|
||||
startpart = "23";
|
||||
} else {
|
||||
startpart = startTime+"-23";
|
||||
}
|
||||
if (endTime == 1) {
|
||||
endpart = "0";
|
||||
} else {
|
||||
endpart = "0-"+(endTime-1);
|
||||
}
|
||||
timerange = startpart+","+endpart;
|
||||
}
|
||||
if (count === "0") {
|
||||
crontab = count+" "+timerange+" * * "+days;
|
||||
} else {
|
||||
crontab = "*/"+count+" "+timerange+" * * "+days;
|
||||
}
|
||||
}
|
||||
} else if (type == "time") {
|
||||
var time = $("#inject-time-time").val();
|
||||
var days = $('#inject-time-time-days input[type=checkbox]:checked').map(function(_, el) {
|
||||
return $(el).val()
|
||||
}).get();
|
||||
if (days.length == 0) {
|
||||
crontab = "";
|
||||
} else {
|
||||
if (days.length == 7) {
|
||||
days="*";
|
||||
} else {
|
||||
days = days.join(",");
|
||||
}
|
||||
var parts = time.split(":");
|
||||
if (parts.length === 2) {
|
||||
repeat = "";
|
||||
parts[1] = ("00" + (parseInt(parts[1]) % 60)).substr(-2);
|
||||
parts[0] = ("00" + (parseInt(parts[0]) % 24)).substr(-2);
|
||||
crontab = parts[1]+" "+parts[0]+" * * "+days;
|
||||
}
|
||||
else { crontab = ""; }
|
||||
}
|
||||
}
|
||||
|
||||
$("#node-input-repeat").val(repeat);
|
||||
$("#node-input-crontab").val(crontab);
|
||||
|
||||
/* Gather the properties */
|
||||
var items = $("#node-input-property-container").editableList('items');
|
||||
delete this.payloadType;
|
||||
delete this.payload;
|
||||
this.topic = "";
|
||||
var result = getProps(items, true);
|
||||
this.props = result.props;
|
||||
if(result.hasOwnProperty('payloadType')) { this.payloadType = result.payloadType; };
|
||||
if(result.hasOwnProperty('payload')) { this.payload = result.payload; };
|
||||
if(result.hasOwnProperty('topic')) { this.topic = result.topic; };
|
||||
},
|
||||
button: {
|
||||
enabled: function() {
|
||||
return !this.changed
|
||||
},
|
||||
onclick: function () {
|
||||
if (this.changed) {
|
||||
return RED.notify(RED._("notification.warning", { message: RED._("notification.warnings.undeployedChanges") }), "warning");
|
||||
}
|
||||
doInject(this);
|
||||
}
|
||||
},
|
||||
oneditresize: resizeDialog
|
||||
});
|
||||
})();
|
||||
</script>
|
611
packages/node_modules/@node-red/nodes/core/function/10-function.html
vendored
Normal file
611
packages/node_modules/@node-red/nodes/core/function/10-function.html
vendored
Normal file
@ -0,0 +1,611 @@
|
||||
<script type="text/html" data-template-name="function">
|
||||
<style>
|
||||
.func-tabs-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#node-input-libs-container-row .red-ui-editableList-container {
|
||||
padding: 0px;
|
||||
}
|
||||
#node-input-libs-container-row .red-ui-editableList-container li {
|
||||
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 .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 > span.input-error > i {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
</style>
|
||||
<input type="hidden" id="node-input-func">
|
||||
<input type="hidden" id="node-input-noerr">
|
||||
<input type="hidden" id="node-input-finalize">
|
||||
<input type="hidden" id="node-input-initialize">
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-row func-tabs-row">
|
||||
<ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
|
||||
</div>
|
||||
<div id="func-tabs-content" style="min-height: calc(100% - 95px);">
|
||||
|
||||
<div id="func-tab-config" style="display:none">
|
||||
<div class="form-row">
|
||||
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
||||
<input id="node-input-outputs" style="width: 60px;" value="1">
|
||||
</div>
|
||||
|
||||
<div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
|
||||
<label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-input-libs-row hide" id="node-input-libs-container-row">
|
||||
<ol id="node-input-libs-container"></ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="func-tab-init" style="display:none">
|
||||
<div class="form-row node-text-editor-row" style="position:relative">
|
||||
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-init-editor" ></div>
|
||||
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="func-tab-body" style="display:none">
|
||||
<div class="form-row node-text-editor-row" style="position:relative">
|
||||
<div style="height: 220px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
||||
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="func-tab-finalize" style="display:none">
|
||||
<div class="form-row node-text-editor-row" style="position:relative">
|
||||
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
|
||||
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
(function() {
|
||||
|
||||
var invalidModuleVNames = [
|
||||
'console',
|
||||
'util',
|
||||
'Buffer',
|
||||
'Date',
|
||||
'RED',
|
||||
'node',
|
||||
'__node__',
|
||||
'context',
|
||||
'flow',
|
||||
'global',
|
||||
'env',
|
||||
'setTimeout',
|
||||
'clearTimeout',
|
||||
'setInterval',
|
||||
'clearInterval',
|
||||
'promisify'
|
||||
]
|
||||
|
||||
var knownFunctionNodes = {};
|
||||
RED.events.on("nodes:add", function(n) {
|
||||
if (n.type === "function") {
|
||||
knownFunctionNodes[n.id] = n;
|
||||
}
|
||||
})
|
||||
RED.events.on("nodes:remove", function(n) {
|
||||
if (n.type === "function") {
|
||||
delete knownFunctionNodes[n.id];
|
||||
}
|
||||
})
|
||||
|
||||
var missingModules = [];
|
||||
var missingModuleReasons = {};
|
||||
RED.events.on("runtime-state", function(event) {
|
||||
if (event.error === "missing-modules") {
|
||||
missingModules = event.modules.map(function(m) { missingModuleReasons[m.module] = m.error; return m.module });
|
||||
for (var id in knownFunctionNodes) {
|
||||
if (knownFunctionNodes.hasOwnProperty(id) && knownFunctionNodes[id].libs && knownFunctionNodes[id].libs.length > 0) {
|
||||
RED.editor.validateNode(knownFunctionNodes[id])
|
||||
}
|
||||
}
|
||||
} else if (!event.text) {
|
||||
missingModuleReasons = {};
|
||||
missingModules = [];
|
||||
for (var id in knownFunctionNodes) {
|
||||
if (knownFunctionNodes.hasOwnProperty(id) && knownFunctionNodes[id].libs && knownFunctionNodes[id].libs.length > 0) {
|
||||
RED.editor.validateNode(knownFunctionNodes[id])
|
||||
}
|
||||
}
|
||||
}
|
||||
RED.view.redraw();
|
||||
});
|
||||
|
||||
var installAllowList = ['*'];
|
||||
var installDenyList = [];
|
||||
|
||||
var modulesEnabled = true;
|
||||
if (RED.settings.get('externalModules.modules.allowInstall', true) === false) {
|
||||
modulesEnabled = false;
|
||||
}
|
||||
var settingsAllowList = RED.settings.get("externalModules.modules.allowList")
|
||||
var settingsDenyList = RED.settings.get("externalModules.modules.denyList")
|
||||
if (settingsAllowList || settingsDenyList) {
|
||||
installAllowList = settingsAllowList;
|
||||
installDenyList = settingsDenyList
|
||||
}
|
||||
installAllowList = RED.utils.parseModuleList(installAllowList);
|
||||
installDenyList = RED.utils.parseModuleList(installDenyList);
|
||||
|
||||
|
||||
// object that maps from library name to its descriptor
|
||||
var allLibs = [];
|
||||
|
||||
function moduleName(module) {
|
||||
var match = /^([^@]+)@(.+)/.exec(module);
|
||||
if (match) {
|
||||
return [match[1], match[2]];
|
||||
}
|
||||
return [module, undefined];
|
||||
}
|
||||
|
||||
function getAllUsedModules() {
|
||||
var moduleSet = new Set();
|
||||
for (var id in knownFunctionNodes) {
|
||||
if (knownFunctionNodes.hasOwnProperty(id)) {
|
||||
if (knownFunctionNodes[id].libs) {
|
||||
for (var i=0, l=knownFunctionNodes[id].libs.length; i<l; i++) {
|
||||
if (RED.utils.checkModuleAllowed(knownFunctionNodes[id].libs[i].module,null,installAllowList,installDenyList)) {
|
||||
moduleSet.add(knownFunctionNodes[id].libs[i].module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var modules = Array.from(moduleSet);
|
||||
modules.sort();
|
||||
return modules;
|
||||
}
|
||||
|
||||
function prepareLibraryConfig(node) {
|
||||
$(".node-input-libs-row").show();
|
||||
var usedModules = getAllUsedModules();
|
||||
var typedModules = usedModules.map(function(l) {
|
||||
return {icon:"fa fa-cube", value:l,label:l,hasValue:false}
|
||||
})
|
||||
typedModules.push({
|
||||
value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg"
|
||||
})
|
||||
|
||||
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 fmoduleSpan = $("<span>").appendTo(row0);
|
||||
var fmodule = $("<input/>", {
|
||||
class: "node-input-libs-val",
|
||||
placeholder: RED._("node-red:function.require.module"),
|
||||
type: "text"
|
||||
}).css({
|
||||
}).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);
|
||||
}
|
||||
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_") {
|
||||
val = fmodule.val();
|
||||
}
|
||||
var errors = [];
|
||||
|
||||
if (!RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList)) {
|
||||
return RED._("node-red:function.error.moduleNotAllowed",{module:val});
|
||||
} else {
|
||||
return RED._("node-red:function.error.moduleLoadError",{module:val,error:missingModuleReasons[val]});
|
||||
}
|
||||
})
|
||||
|
||||
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) {
|
||||
fvar.addClass("input-error");
|
||||
vnameWarning.addClass("input-error");
|
||||
} else {
|
||||
fvar.removeClass("input-error");
|
||||
vnameWarning.removeClass("input-error");
|
||||
}
|
||||
});
|
||||
|
||||
fmodule.on("change keyup paste", function (e) {
|
||||
var val = $(this).typedInput("type");
|
||||
if (val === "_custom_") {
|
||||
val = $(this).val();
|
||||
}
|
||||
var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/].?/g, function(v) { return v[1]?v[1].toUpperCase():"" });
|
||||
fvar.val(varName);
|
||||
fvar.trigger("change");
|
||||
|
||||
if (RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList) && (missingModules.indexOf(val) === -1)) {
|
||||
fmodule.removeClass("input-error");
|
||||
moduleWarning.removeClass("input-error");
|
||||
} else {
|
||||
fmodule.addClass("input-error");
|
||||
moduleWarning.addClass("input-error");
|
||||
}
|
||||
});
|
||||
if (RED.utils.checkModuleAllowed(opt.module,null,installAllowList,installDenyList) && (missingModules.indexOf(opt.module) === -1)) {
|
||||
fmodule.removeClass("input-error");
|
||||
moduleWarning.removeClass("input-error");
|
||||
} else {
|
||||
fmodule.addClass("input-error");
|
||||
moduleWarning.addClass("input-error");
|
||||
}
|
||||
if (opt.var) {
|
||||
fvar.trigger("change");
|
||||
}
|
||||
},
|
||||
removable: true
|
||||
});
|
||||
|
||||
var libs = node.libs || [];
|
||||
for (var i=0,l=libs.length;i<l; i++) {
|
||||
libList.editableList('addItem',libs[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getLibsList() {
|
||||
var _libs = [];
|
||||
if (RED.settings.functionExternalModules !== false) {
|
||||
var libs = $("#node-input-libs-container").editableList("items");
|
||||
libs.each(function(i) {
|
||||
var item = $(this);
|
||||
var v = item.find(".node-input-libs-var").val();
|
||||
var n = item.find(".node-input-libs-val").typedInput("type");
|
||||
if (n === "_custom_") {
|
||||
n = item.find(".node-input-libs-val").val();
|
||||
}
|
||||
if ((!v || (v === "")) ||
|
||||
(!n || (n === ""))) {
|
||||
return;
|
||||
}
|
||||
_libs.push({
|
||||
var: v,
|
||||
module: n
|
||||
});
|
||||
});
|
||||
}
|
||||
return _libs;
|
||||
}
|
||||
|
||||
RED.nodes.registerType('function',{
|
||||
color:"#fdd0a2",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
func: {value:"\nreturn msg;"},
|
||||
outputs: {value:1},
|
||||
noerr: {value:0,required:true,validate:function(v) { return !v; }},
|
||||
initialize: {value:""},
|
||||
finalize: {value:""},
|
||||
libs: {value: [], validate: function(v) {
|
||||
if (!v) { return true; }
|
||||
for (var i=0,l=v.length;i<l;i++) {
|
||||
var m = v[i];
|
||||
if (!RED.utils.checkModuleAllowed(m.module,null,installAllowList,installDenyList)) {
|
||||
return false
|
||||
}
|
||||
if (m.var === "" || / /.test(m.var)) {
|
||||
return false;
|
||||
}
|
||||
if (missingModules.indexOf(m.module) > -1) {
|
||||
return false;
|
||||
}
|
||||
if (invalidModuleVNames.indexOf(m.var) !== -1){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "function.svg",
|
||||
label: function() {
|
||||
return this.name||this._("function.function");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
|
||||
var tabs = RED.tabs.create({
|
||||
id: "func-tabs",
|
||||
onchange: function(tab) {
|
||||
$("#func-tabs-content").children().hide();
|
||||
$("#" + tab.id).show();
|
||||
let editor = $("#" + tab.id).find('.monaco-editor').first();
|
||||
if(editor.length) {
|
||||
if(that.editor.nodered && that.editor.type == "monaco") {
|
||||
that.editor.nodered.refreshModuleLibs(getLibsList());
|
||||
}
|
||||
RED.tray.resize();
|
||||
}
|
||||
}
|
||||
});
|
||||
tabs.addTab({
|
||||
id: "func-tab-config",
|
||||
iconClass: "fa fa-cog",
|
||||
label: that._("function.label.setup")
|
||||
});
|
||||
|
||||
tabs.addTab({
|
||||
id: "func-tab-init",
|
||||
label: that._("function.label.initialize")
|
||||
});
|
||||
tabs.addTab({
|
||||
id: "func-tab-body",
|
||||
label: that._("function.label.function")
|
||||
});
|
||||
tabs.addTab({
|
||||
id: "func-tab-finalize",
|
||||
label: that._("function.label.finalize")
|
||||
});
|
||||
|
||||
tabs.activateTab("func-tab-body");
|
||||
|
||||
$( "#node-input-outputs" ).spinner({
|
||||
min:0,
|
||||
change: function(event, ui) {
|
||||
var value = this.value;
|
||||
if (!value.match(/^\d+$/)) { value = 1; }
|
||||
else if (value < this.min) { value = this.min; }
|
||||
if (value !== this.value) { $(this).spinner("value", value); }
|
||||
}
|
||||
});
|
||||
|
||||
var buildEditor = function(id, value, defaultValue, extraLibs) {
|
||||
var editor = RED.editor.createEditor({
|
||||
id: id,
|
||||
mode: 'ace/mode/nrjavascript',
|
||||
value: value || defaultValue || "",
|
||||
globals: {
|
||||
msg:true,
|
||||
context:true,
|
||||
RED: true,
|
||||
util: true,
|
||||
flow: true,
|
||||
global: true,
|
||||
console: true,
|
||||
Buffer: true,
|
||||
setTimeout: true,
|
||||
clearTimeout: true,
|
||||
setInterval: true,
|
||||
clearInterval: true
|
||||
},
|
||||
extraLibs: extraLibs
|
||||
});
|
||||
if (defaultValue && value === "") {
|
||||
editor.moveCursorTo(defaultValue.split("\n").length - 1, 0);
|
||||
}
|
||||
return editor;
|
||||
}
|
||||
this.initEditor = buildEditor('node-input-init-editor',$("#node-input-initialize").val(),RED._("node-red:function.text.initialize"))
|
||||
this.editor = buildEditor('node-input-func-editor',$("#node-input-func").val(), undefined, that.libs || [])
|
||||
this.finalizeEditor = buildEditor('node-input-finalize-editor',$("#node-input-finalize").val(),RED._("node-red:function.text.finalize"))
|
||||
|
||||
RED.library.create({
|
||||
url:"functions", // where to get the data from
|
||||
type:"function", // the type of object the library is for
|
||||
editor:this.editor, // the field name the main text body goes to
|
||||
mode:"ace/mode/nrjavascript",
|
||||
fields:[
|
||||
'name', 'outputs',
|
||||
{
|
||||
name: 'initialize',
|
||||
get: function() {
|
||||
return that.initEditor.getValue();
|
||||
},
|
||||
set: function(v) {
|
||||
that.initEditor.setValue(v||RED._("node-red:function.text.initialize"), -1);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finalize',
|
||||
get: function() {
|
||||
return that.finalizeEditor.getValue();
|
||||
},
|
||||
set: function(v) {
|
||||
that.finalizeEditor.setValue(v||RED._("node-red:function.text.finalize"), -1);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'info',
|
||||
get: function() {
|
||||
return that.infoEditor.getValue();
|
||||
},
|
||||
set: function(v) {
|
||||
that.infoEditor.setValue(v||"", -1);
|
||||
}
|
||||
}
|
||||
],
|
||||
ext:"js"
|
||||
});
|
||||
this.editor.focus();
|
||||
|
||||
|
||||
var expandButtonClickHandler = function(editor) {
|
||||
return function(e) {
|
||||
e.preventDefault();
|
||||
var value = editor.getValue();
|
||||
var extraLibs = that.libs || [];
|
||||
RED.editor.editJavaScript({
|
||||
value: value,
|
||||
width: "Infinity",
|
||||
cursor: editor.getCursorPosition(),
|
||||
mode: "ace/mode/nrjavascript",
|
||||
complete: function(v,cursor) {
|
||||
editor.setValue(v, -1);
|
||||
editor.gotoLine(cursor.row+1,cursor.column,false);
|
||||
setTimeout(function() {
|
||||
editor.focus();
|
||||
},300);
|
||||
},
|
||||
extraLibs: extraLibs
|
||||
})
|
||||
}
|
||||
}
|
||||
$("#node-init-expand-js").on("click", expandButtonClickHandler(this.initEditor));
|
||||
$("#node-function-expand-js").on("click", expandButtonClickHandler(this.editor));
|
||||
$("#node-finalize-expand-js").on("click", expandButtonClickHandler(this.finalizeEditor));
|
||||
|
||||
RED.popover.tooltip($("#node-init-expand-js"), RED._("node-red:common.label.expand"));
|
||||
RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand"));
|
||||
RED.popover.tooltip($("#node-finalize-expand-js"), RED._("node-red:common.label.expand"));
|
||||
|
||||
if (RED.settings.functionExternalModules !== false) {
|
||||
prepareLibraryConfig(that);
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
var node = this;
|
||||
var noerr = 0;
|
||||
$("#node-input-noerr").val(0);
|
||||
|
||||
var disposeEditor = function(editorName,targetName,defaultValue) {
|
||||
var editor = node[editorName];
|
||||
var annot = editor.getSession().getAnnotations();
|
||||
for (var k=0; k < annot.length; k++) {
|
||||
if (annot[k].type === "error") {
|
||||
noerr += annot.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var val = editor.getValue();
|
||||
if (defaultValue) {
|
||||
if (val.trim() == defaultValue.trim()) {
|
||||
val = "";
|
||||
}
|
||||
}
|
||||
editor.destroy();
|
||||
delete node[editorName];
|
||||
$("#"+targetName).val(val);
|
||||
}
|
||||
disposeEditor("editor","node-input-func");
|
||||
disposeEditor("initEditor","node-input-initialize", RED._("node-red:function.text.initialize"));
|
||||
disposeEditor("finalizeEditor","node-input-finalize", RED._("node-red:function.text.finalize"));
|
||||
|
||||
$("#node-input-noerr").val(noerr);
|
||||
this.noerr = noerr;
|
||||
node.libs = getLibsList();
|
||||
},
|
||||
oneditcancel: function() {
|
||||
var node = this;
|
||||
|
||||
node.editor.destroy();
|
||||
delete node.editor;
|
||||
|
||||
node.initEditor.destroy();
|
||||
delete node.initEditor;
|
||||
|
||||
node.finalizeEditor.destroy();
|
||||
delete node.finalizeEditor;
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#dialog-form .node-text-editor").css("height",height+"px");
|
||||
|
||||
var height = size.height;
|
||||
$("#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 - 192)+"px");
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
506
packages/node_modules/@node-red/nodes/core/function/10-function.js
vendored
Normal file
506
packages/node_modules/@node-red/nodes/core/function/10-function.js
vendored
Normal file
@ -0,0 +1,506 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
var util = require("util");
|
||||
var vm = require("vm");
|
||||
var acorn = require("acorn");
|
||||
var acornWalk = require("acorn-walk");
|
||||
|
||||
function sendResults(node,send,_msgid,msgs,cloneFirstMessage) {
|
||||
if (msgs == null) {
|
||||
return;
|
||||
} else if (!util.isArray(msgs)) {
|
||||
msgs = [msgs];
|
||||
}
|
||||
var msgCount = 0;
|
||||
for (var m=0; m<msgs.length; m++) {
|
||||
if (msgs[m]) {
|
||||
if (!util.isArray(msgs[m])) {
|
||||
msgs[m] = [msgs[m]];
|
||||
}
|
||||
for (var n=0; n < msgs[m].length; n++) {
|
||||
var msg = msgs[m][n];
|
||||
if (msg !== null && msg !== undefined) {
|
||||
if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) {
|
||||
if (msgCount === 0 && cloneFirstMessage !== false) {
|
||||
msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
|
||||
msg = msgs[m][n];
|
||||
}
|
||||
msg._msgid = _msgid;
|
||||
msgCount++;
|
||||
} else {
|
||||
var type = typeof msg;
|
||||
if (type === 'object') {
|
||||
type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
|
||||
}
|
||||
node.error(RED._("function.error.non-message-returned",{ type: type }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msgCount>0) {
|
||||
send(msgs);
|
||||
}
|
||||
}
|
||||
|
||||
function createVMOpt(node, kind) {
|
||||
var opt = {
|
||||
filename: 'Function node'+kind+':'+node.id+(node.name?' ['+node.name+']':''), // filename for stack traces
|
||||
displayErrors: true
|
||||
// Using the following options causes node 4/6 to not include the line number
|
||||
// in the stack output. So don't use them.
|
||||
// lineOffset: -11, // line number offset to be used for stack traces
|
||||
// columnOffset: 0, // column number offset to be used for stack traces
|
||||
};
|
||||
return opt;
|
||||
}
|
||||
|
||||
function updateErrorInfo(err) {
|
||||
if (err.stack) {
|
||||
var stack = err.stack.toString();
|
||||
var m = /^([^:]+):([^:]+):(\d+).*/.exec(stack);
|
||||
if (m) {
|
||||
var line = parseInt(m[3]) -1;
|
||||
var kind = "body:";
|
||||
if (/setup/.exec(m[1])) {
|
||||
kind = "setup:";
|
||||
}
|
||||
if (/cleanup/.exec(m[1])) {
|
||||
kind = "cleanup:";
|
||||
}
|
||||
err.message += " ("+kind+"line "+line+")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function FunctionNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
node.name = n.name;
|
||||
node.func = n.func;
|
||||
node.outputs = n.outputs;
|
||||
node.ini = n.initialize ? n.initialize.trim() : "";
|
||||
node.fin = n.finalize ? n.finalize.trim() : "";
|
||||
node.libs = n.libs || [];
|
||||
|
||||
if (RED.settings.functionExternalModules === false && node.libs.length > 0) {
|
||||
throw new Error(RED._("function.error.externalModuleNotAllowed"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
var functionText = "var results = null;"+
|
||||
"results = (async function(msg,__send__,__done__){ "+
|
||||
"var __msgid__ = msg._msgid;"+
|
||||
"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,"+
|
||||
"on:__node__.on,"+
|
||||
"status:__node__.status,"+
|
||||
"send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+
|
||||
"done:__done__"+
|
||||
"};\n"+
|
||||
node.func+"\n"+
|
||||
"})(msg,__send__,__done__);";
|
||||
|
||||
var handleNodeDoneCall = true;
|
||||
|
||||
// Check to see if the Function appears to call `node.done()`. If so,
|
||||
// we will assume it is well written and does actually call node.done().
|
||||
// Otherwise, we will call node.done() after the function returns regardless.
|
||||
if (/node\.done\s*\(\s*\)/.test(functionText)) {
|
||||
// We have spotted the code contains `node.done`. It could be in a comment
|
||||
// so need to do the extra work to parse the AST and examine it properly.
|
||||
acornWalk.simple(acorn.parse(functionText,{ecmaVersion: "latest"} ), {
|
||||
CallExpression(astNode) {
|
||||
if (astNode.callee && astNode.callee.object) {
|
||||
if (astNode.callee.object.name === "node" && astNode.callee.property.name === "done") {
|
||||
handleNodeDoneCall = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var finScript = null;
|
||||
var finOpt = null;
|
||||
node.topic = n.topic;
|
||||
node.outstandingTimers = [];
|
||||
node.outstandingIntervals = [];
|
||||
node.clearStatus = false;
|
||||
|
||||
var sandbox = {
|
||||
console:console,
|
||||
util:util,
|
||||
Buffer:Buffer,
|
||||
Date: Date,
|
||||
RED: {
|
||||
util: RED.util
|
||||
},
|
||||
__node__: {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
outputCount: node.outputs,
|
||||
log: function() {
|
||||
node.log.apply(node, arguments);
|
||||
},
|
||||
error: function() {
|
||||
node.error.apply(node, arguments);
|
||||
},
|
||||
warn: function() {
|
||||
node.warn.apply(node, arguments);
|
||||
},
|
||||
debug: function() {
|
||||
node.debug.apply(node, arguments);
|
||||
},
|
||||
trace: function() {
|
||||
node.trace.apply(node, arguments);
|
||||
},
|
||||
send: function(send, id, msgs, cloneMsg) {
|
||||
sendResults(node, send, id, msgs, cloneMsg);
|
||||
},
|
||||
on: function() {
|
||||
if (arguments[0] === "input") {
|
||||
throw new Error(RED._("function.error.inputListener"));
|
||||
}
|
||||
node.on.apply(node, arguments);
|
||||
},
|
||||
status: function() {
|
||||
node.clearStatus = true;
|
||||
node.status.apply(node, arguments);
|
||||
}
|
||||
},
|
||||
context: {
|
||||
set: function() {
|
||||
node.context().set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().get.apply(node,arguments);
|
||||
},
|
||||
keys: function() {
|
||||
return node.context().keys.apply(node,arguments);
|
||||
},
|
||||
get global() {
|
||||
return node.context().global;
|
||||
},
|
||||
get flow() {
|
||||
return node.context().flow;
|
||||
}
|
||||
},
|
||||
flow: {
|
||||
set: function() {
|
||||
node.context().flow.set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().flow.get.apply(node,arguments);
|
||||
},
|
||||
keys: function() {
|
||||
return node.context().flow.keys.apply(node,arguments);
|
||||
}
|
||||
},
|
||||
global: {
|
||||
set: function() {
|
||||
node.context().global.set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().global.get.apply(node,arguments);
|
||||
},
|
||||
keys: function() {
|
||||
return node.context().global.keys.apply(node,arguments);
|
||||
}
|
||||
},
|
||||
env: {
|
||||
get: function(envVar) {
|
||||
return RED.util.getSetting(node, envVar);
|
||||
}
|
||||
},
|
||||
setTimeout: function () {
|
||||
var func = arguments[0];
|
||||
var timerId;
|
||||
arguments[0] = function() {
|
||||
sandbox.clearTimeout(timerId);
|
||||
try {
|
||||
func.apply(node,arguments);
|
||||
} catch(err) {
|
||||
node.error(err,{});
|
||||
}
|
||||
};
|
||||
timerId = setTimeout.apply(node,arguments);
|
||||
node.outstandingTimers.push(timerId);
|
||||
return timerId;
|
||||
},
|
||||
clearTimeout: function(id) {
|
||||
clearTimeout(id);
|
||||
var index = node.outstandingTimers.indexOf(id);
|
||||
if (index > -1) {
|
||||
node.outstandingTimers.splice(index,1);
|
||||
}
|
||||
},
|
||||
setInterval: function() {
|
||||
var func = arguments[0];
|
||||
var timerId;
|
||||
arguments[0] = function() {
|
||||
try {
|
||||
func.apply(node,arguments);
|
||||
} catch(err) {
|
||||
node.error(err,{});
|
||||
}
|
||||
};
|
||||
timerId = setInterval.apply(node,arguments);
|
||||
node.outstandingIntervals.push(timerId);
|
||||
return timerId;
|
||||
},
|
||||
clearInterval: function(id) {
|
||||
clearInterval(id);
|
||||
var index = node.outstandingIntervals.indexOf(id);
|
||||
if (index > -1) {
|
||||
node.outstandingIntervals.splice(index,1);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (util.hasOwnProperty('promisify')) {
|
||||
sandbox.setTimeout[util.promisify.custom] = function(after, value) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
sandbox.setTimeout(function(){ resolve(value); }, after);
|
||||
});
|
||||
};
|
||||
sandbox.promisify = util.promisify;
|
||||
}
|
||||
const moduleLoadPromises = [];
|
||||
|
||||
if (node.hasOwnProperty("libs")) {
|
||||
let moduleErrors = false;
|
||||
var modules = node.libs;
|
||||
modules.forEach(module => {
|
||||
var vname = module.hasOwnProperty("var") ? module.var : null;
|
||||
if (vname && (vname !== "")) {
|
||||
if (sandbox.hasOwnProperty(vname) || vname === 'node') {
|
||||
node.error(RED._("function.error.moduleNameError",{name:vname}))
|
||||
moduleErrors = true;
|
||||
return;
|
||||
}
|
||||
sandbox[vname] = null;
|
||||
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"));
|
||||
}
|
||||
}
|
||||
const RESOLVING = 0;
|
||||
const RESOLVED = 1;
|
||||
const ERROR = 2;
|
||||
var state = RESOLVING;
|
||||
var messages = [];
|
||||
var processMessage = (() => {});
|
||||
|
||||
node.on("input", function(msg,send,done) {
|
||||
if(state === RESOLVING) {
|
||||
messages.push({msg:msg, send:send, done:done});
|
||||
}
|
||||
else if(state === RESOLVED) {
|
||||
processMessage(msg, send, done);
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
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 => {
|
||||
node.error(RED._("function.error.externalModuleLoadError"));
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("function",FunctionNode, {
|
||||
dynamicModuleList: "libs",
|
||||
settings: {
|
||||
functionExternalModules: { value: true, exportable: true }
|
||||
}
|
||||
});
|
||||
RED.library.register("functions");
|
||||
};
|
284
packages/node_modules/@node-red/nodes/core/function/89-delay.html
vendored
Normal file
284
packages/node_modules/@node-red/nodes/core/function/89-delay.html
vendored
Normal file
@ -0,0 +1,284 @@
|
||||
<!--
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/html" data-template-name="delay">
|
||||
<div class="form-row">
|
||||
<label for="node-input-delay-action"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
|
||||
<select id="node-input-delay-action" style="width:270px !important">
|
||||
<option value="delay" data-i18n="delay.delaymsg"></option>
|
||||
<option value="rate" data-i18n="delay.limitrate"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="delay-details">
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<select id="node-input-delay-type" style="width:270px !important">
|
||||
<option value="delay" data-i18n="delay.delayfixed"></option>
|
||||
<option value="random" data-i18n="delay.randomdelay"></option>
|
||||
<option value="delayv" data-i18n="delay.delayvarmsg"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="delay-details-for">
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="delay.for"></span></label>
|
||||
<input type="text" id="node-input-timeout" style="text-align:end; width:50px !important">
|
||||
<select id="node-input-timeoutUnits" style="width:200px !important">
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
<option value="minutes" data-i18n="delay.mins"></option>
|
||||
<option value="hours" data-i18n="delay.hours"></option>
|
||||
<option value="days" data-i18n="delay.days"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="random-details" class="form-row">
|
||||
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
|
||||
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:50px !important">
|
||||
<span data-i18n="delay.and"></span>
|
||||
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:50px !important">
|
||||
<select id="node-input-randomUnits" style="width:140px !important">
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
<option value="minutes" data-i18n="delay.mins"></option>
|
||||
<option value="hours" data-i18n="delay.hours"></option>
|
||||
<option value="days" data-i18n="delay.days"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rate-details">
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<select id="node-input-rate-type" style="width:270px !important">
|
||||
<option value="all" data-i18n="delay.limitall"></option>
|
||||
<option value="topic" data-i18n="delay.limittopic"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-rate"><i class="fa fa-clock-o"></i> <span data-i18n="delay.rate"></span></label>
|
||||
<input type="text" id="node-input-rate" placeholder="1" style="text-align:end; width:40px !important">
|
||||
<label for="node-input-rateUnits"><span data-i18n="delay.msgper"></span></label>
|
||||
<input type="text" id="node-input-nbRateUnits" placeholder="1" style="text-align:end; width:40px !important">
|
||||
<select id="node-input-rateUnits" style="width:90px !important">
|
||||
<option value="second" data-i18n="delay.label.units.second.singular"></option>
|
||||
<option value="minute" data-i18n="delay.label.units.minute.singular"></option>
|
||||
<option value="hour" data-i18n="delay.label.units.hour.singular"></option>
|
||||
<option value="day" data-i18n="delay.label.units.day.singular"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="rate-override" style="display: flex; align-items: center">
|
||||
<label></label><input style="width:30px; margin:0" type="checkbox" id="node-input-allowrate"><label style="margin:0;width: auto;" for="node-input-allowrate" data-i18n="delay.allowrate"></label>
|
||||
</div>
|
||||
<div class="form-row" id="rate-details-drop">
|
||||
<input type="hidden" id="node-input-outputs" value="1">
|
||||
<label></label>
|
||||
<select id="node-input-drop-select" style="width: 70%">
|
||||
<option id="node-input-drop-select-queue" value="queue" data-i18n="delay.queuemsg"></option>
|
||||
<option value="drop" data-i18n="delay.dropmsg"></option>
|
||||
<option value="emit" data-i18n="delay.sendmsg"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="rate-details-per-topic">
|
||||
<label></label>
|
||||
<select id="node-input-rate-topic-type" style="width:270px !important">
|
||||
<option value="queue" data-i18n="delay.fairqueue"></option>
|
||||
<option value="timed" data-i18n="delay.timedqueue"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('delay',{
|
||||
category: 'function',
|
||||
color:"#E6E0F8",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
pauseType: {value:"delay", required:true},
|
||||
timeout: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
timeoutUnits: {value:"seconds"},
|
||||
rate: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
nbRateUnits: {value:"1", required:false,
|
||||
validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
rateUnits: {value: "second"},
|
||||
randomFirst: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
randomLast: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
|
||||
randomUnits: {value: "seconds"},
|
||||
drop: {value:false},
|
||||
allowrate: {value:false},
|
||||
outputs: { value: 1},
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "timer.svg",
|
||||
label: function() {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.pauseType == "delayv") {
|
||||
return this._("delay.label.variable");
|
||||
} else if (this.pauseType == "delay") {
|
||||
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
|
||||
if (this.timeoutUnits == "milliseconds") { units = "ms"; }
|
||||
return this._("delay.label.delay")+" "+this.timeout+units;
|
||||
} else if (this.pauseType == "random") {
|
||||
return this._("delay.label.random");
|
||||
} else {
|
||||
var rate = this.rate+" msg/"+(this.rateUnits ? (this.nbRateUnits > 1 ? this.nbRateUnits : '') + this.rateUnits.charAt(0) : "s");
|
||||
if (this.pauseType == "rate") {
|
||||
return this._("delay.label.limit")+" "+rate;
|
||||
} else if (this.pauseType == "timed") {
|
||||
return this._("delay.label.limitTopic")+" "+rate;
|
||||
} else {
|
||||
return this._("delay.label.limitTopic")+" "+rate;
|
||||
}
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
$( "#node-input-timeout" ).spinner({min:1});
|
||||
$( "#node-input-rate" ).spinner({min:1});
|
||||
$( "#node-input-nbRateUnits" ).spinner({min:1});
|
||||
|
||||
$( "#node-input-randomFirst" ).spinner({min:0});
|
||||
$( "#node-input-randomLast" ).spinner({min:1});
|
||||
|
||||
$('.ui-spinner-button').on("click", function() {
|
||||
$(this).siblings('input').trigger("change");
|
||||
});
|
||||
|
||||
$( "#node-input-nbRateUnits" ).on('change keyup', function() {
|
||||
var $this = $(this);
|
||||
var val = parseInt($this.val());
|
||||
var type = "singular";
|
||||
if (val > 1) {
|
||||
type = "plural";
|
||||
}
|
||||
if ($this.attr("data-type") == type) {
|
||||
return;
|
||||
}
|
||||
$this.attr("data-type", type);
|
||||
$("#node-input-rateUnits option").each(function () {
|
||||
var $option = $(this);
|
||||
var key = "delay.label.units." + $option.val() + "." + type;
|
||||
$option.attr('data-i18n', 'node-red:' + key);
|
||||
$option.html(node._(key));
|
||||
});
|
||||
});
|
||||
|
||||
if (this.pauseType == "delay") {
|
||||
$("#node-input-delay-action").val('delay');
|
||||
$("#node-input-delay-type").val('delay');
|
||||
} else if (this.pauseType == "delayv") {
|
||||
$("#node-input-delay-action").val('delay');
|
||||
$("#node-input-delay-type").val('delayv');
|
||||
} else if (this.pauseType == "random") {
|
||||
$("#node-input-delay-action").val('delay');
|
||||
$("#node-input-delay-type").val('random');
|
||||
} else if (this.pauseType == "rate") {
|
||||
$("#node-input-delay-action").val('rate');
|
||||
$("#node-input-rate-type").val('all');
|
||||
} else if (this.pauseType == "queue") {
|
||||
$("#node-input-delay-action").val('rate');
|
||||
$("#node-input-rate-type").val('topic');
|
||||
$("#node-input-rate-topic-type").val('queue');
|
||||
} else if (this.pauseType == "timed") {
|
||||
$("#node-input-delay-action").val('rate');
|
||||
$("#node-input-rate-type").val('topic');
|
||||
$("#node-input-rate-topic-type").val('timed');
|
||||
}
|
||||
|
||||
if (!this.timeoutUnits) {
|
||||
$("#node-input-timeoutUnits option").filter(function() {
|
||||
return $(this).val() == 'seconds';
|
||||
}).attr('selected', true);
|
||||
}
|
||||
|
||||
if (!this.randomUnits) {
|
||||
$("#node-input-randomUnits option").filter(function() {
|
||||
return $(this).val() == 'seconds';
|
||||
}).attr('selected', true);
|
||||
}
|
||||
|
||||
$("#node-input-delay-action").on("change",function() {
|
||||
if (this.value === "delay") {
|
||||
$("#delay-details").show();
|
||||
$("#rate-details").hide();
|
||||
} else if (this.value === "rate") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
}
|
||||
}).trigger("change");
|
||||
|
||||
$("#node-input-delay-type").on("change", function() {
|
||||
if (this.value === "delay") {
|
||||
$("#delay-details-for").show();
|
||||
$("#random-details").hide();
|
||||
} else if (this.value === "delayv") {
|
||||
$("#delay-details-for").show();
|
||||
$("#random-details").hide();
|
||||
} else if (this.value === "random") {
|
||||
$("#delay-details-for").hide();
|
||||
$("#random-details").show();
|
||||
}
|
||||
}).trigger("change");
|
||||
|
||||
if (this.outputs === 2) {
|
||||
$("#node-input-drop-select").val("emit");
|
||||
} else if (this.drop) {
|
||||
$("#node-input-drop-select").val("drop");
|
||||
} else {
|
||||
$("#node-input-drop-select").val("queue");
|
||||
}
|
||||
|
||||
$("#node-input-rate-type").on("change", function() {
|
||||
if (this.value === "all") {
|
||||
$("#rate-details-per-topic").hide();
|
||||
$("#node-input-drop-select-queue").attr('disabled', false);
|
||||
} else if (this.value === "topic") {
|
||||
if ($("#node-input-drop-select").val() === "queue") {
|
||||
$("#node-input-drop-select").val("drop");
|
||||
}
|
||||
$("#node-input-drop-select-queue").attr('disabled', true);
|
||||
$("#rate-details-per-topic").show();
|
||||
}
|
||||
}).trigger("change");
|
||||
},
|
||||
oneditsave: function() {
|
||||
var action = $("#node-input-delay-action").val();
|
||||
if (action === "delay") {
|
||||
this.pauseType = $("#node-input-delay-type").val();
|
||||
$("#node-input-outputs").val(1);
|
||||
} else if (action === "rate") {
|
||||
action = $("#node-input-rate-type").val();
|
||||
if (action === "all") {
|
||||
this.pauseType = "rate";
|
||||
} else {
|
||||
this.pauseType = $("#node-input-rate-topic-type").val();
|
||||
}
|
||||
var dropType = $("#node-input-drop-select").val();
|
||||
this.drop = dropType !== "queue";
|
||||
$("#node-input-outputs").val(dropType === "emit"?2:1);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
97
packages/node_modules/@node-red/nodes/core/function/rbe.js
vendored
Normal file
97
packages/node_modules/@node-red/nodes/core/function/rbe.js
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function RbeNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.func = n.func || "rbe";
|
||||
this.gap = n.gap || "0";
|
||||
this.start = n.start || '';
|
||||
this.inout = n.inout || "out";
|
||||
this.pc = false;
|
||||
if (this.gap.substr(-1) === "%") {
|
||||
this.pc = true;
|
||||
this.gap = parseFloat(this.gap);
|
||||
}
|
||||
this.g = this.gap;
|
||||
this.property = n.property || "payload";
|
||||
this.topi = n.topi || "topic";
|
||||
this.septopics = true;
|
||||
if (n.septopics !== undefined && n.septopics === false) {
|
||||
this.septopics = false;
|
||||
}
|
||||
|
||||
var node = this;
|
||||
|
||||
node.previous = {};
|
||||
this.on("input",function(msg) {
|
||||
var topic;
|
||||
try {
|
||||
topic = RED.util.getMessageProperty(msg,node.topi);
|
||||
}
|
||||
catch(e) { }
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (node.septopics && topic && (typeof topic === "string") && (topic !== "")) {
|
||||
delete node.previous[msg.topic];
|
||||
}
|
||||
else { node.previous = {}; }
|
||||
}
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
var t = "_no_topic";
|
||||
if (node.septopics) { t = topic || t; }
|
||||
if ((this.func === "rbe") || (this.func === "rbei")) {
|
||||
var doSend = (this.func !== "rbei") || (node.previous.hasOwnProperty(t)) || false;
|
||||
if (typeof(value) === "object") {
|
||||
if (typeof(node.previous[t]) !== "object") { node.previous[t] = {}; }
|
||||
if (!RED.util.compareObjects(value, node.previous[t])) {
|
||||
node.previous[t] = RED.util.cloneMessage(value);
|
||||
if (doSend) { node.send(msg); }
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (value !== node.previous[t]) {
|
||||
node.previous[t] = RED.util.cloneMessage(value);
|
||||
if (doSend) { node.send(msg); }
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var n = parseFloat(value);
|
||||
if (!isNaN(n)) {
|
||||
if ((typeof node.previous[t] === 'undefined') && (this.func === "narrowband" || this.func === "narrowbandEq")) {
|
||||
if (node.start === '') { node.previous[t] = n; }
|
||||
else { node.previous[t] = node.start; }
|
||||
}
|
||||
if (node.pc) { node.gap = Math.abs(node.previous[t] * node.g / 100) || 0; }
|
||||
else { node.gap = Number(node.gap); }
|
||||
if ((node.previous[t] === undefined) && (node.func === "narrowbandEq")) { node.previous[t] = n; }
|
||||
if (node.previous[t] === undefined) { node.previous[t] = n - node.gap - 1; }
|
||||
if (Math.abs(n - node.previous[t]) === node.gap) {
|
||||
if ((this.func === "deadbandEq")||(this.func === "narrowband")) {
|
||||
if (node.inout === "out") { node.previous[t] = n; }
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
else if (Math.abs(n - node.previous[t]) > node.gap) {
|
||||
if (this.func === "deadband" || this.func === "deadbandEq") {
|
||||
if (node.inout === "out") { node.previous[t] = n; }
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
else if (Math.abs(n - node.previous[t]) < node.gap) {
|
||||
if ((this.func === "narrowband")||(this.func === "narrowbandEq")) {
|
||||
if (node.inout === "out") { node.previous[t] = n; }
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
if (node.inout === "in") { node.previous[t] = n; }
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("rbe.warn.nonumber"));
|
||||
}
|
||||
}
|
||||
} // ignore msg with no payload property.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rbe",RbeNode);
|
||||
}
|
675
packages/node_modules/@node-red/nodes/core/network/21-httprequest.js
vendored
Normal file
675
packages/node_modules/@node-red/nodes/core/network/21-httprequest.js
vendored
Normal file
@ -0,0 +1,675 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
const got = require("got");
|
||||
const {CookieJar} = require("tough-cookie");
|
||||
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
|
||||
const FormData = require('form-data');
|
||||
const { v4: uuid } = require('uuid');
|
||||
const crypto = require('crypto');
|
||||
const URL = require("url").URL
|
||||
var mustache = require("mustache");
|
||||
var querystring = require("querystring");
|
||||
var cookie = require("cookie");
|
||||
var hashSum = require("hash-sum");
|
||||
|
||||
|
||||
// Cache a reference to the existing https.request function
|
||||
// so we can compare later to see if an old agent-base instance
|
||||
// has been required.
|
||||
// This is generally okay as the core nodes are required before
|
||||
// any contrib nodes. Where it will fail is if the agent-base module
|
||||
// is required via the settings file or outside of Node-RED before it
|
||||
// is started.
|
||||
// If there are other modules that patch the function, they will get undone
|
||||
// as well. Not much we can do about that right now. Patching core
|
||||
// functions is bad.
|
||||
const HTTPS_MODULE = require("https");
|
||||
const HTTPS_REQUEST = HTTPS_MODULE.request;
|
||||
|
||||
function checkNodeAgentPatch() {
|
||||
if (HTTPS_MODULE.request !== HTTPS_REQUEST && HTTPS_MODULE.request.length === 2) {
|
||||
RED.log.warn(`
|
||||
|
||||
---------------------------------------------------------------------
|
||||
Patched https.request function detected. This will break the
|
||||
HTTP Request node. The original code has now been restored.
|
||||
|
||||
This is likely caused by a contrib node including an old version of
|
||||
the 'agent-base@<5.0.0' module.
|
||||
|
||||
You can identify what node is at fault by running:
|
||||
npm list agent-base
|
||||
in your Node-RED user directory (${RED.settings.userDir}).
|
||||
---------------------------------------------------------------------
|
||||
`);
|
||||
HTTPS_MODULE.request = HTTPS_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
function HTTPRequest(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
checkNodeAgentPatch();
|
||||
var node = this;
|
||||
var nodeUrl = n.url;
|
||||
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
||||
var nodeMethod = n.method || "GET";
|
||||
var paytoqs = false;
|
||||
var paytobody = false;
|
||||
var redirectList = [];
|
||||
var sendErrorsToCatch = n.senderr;
|
||||
|
||||
var nodeHTTPPersistent = n["persist"];
|
||||
if (n.tls) {
|
||||
var tlsNode = RED.nodes.getNode(n.tls);
|
||||
}
|
||||
this.ret = n.ret || "txt";
|
||||
this.authType = n.authType || "basic";
|
||||
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
|
||||
else { this.reqTimeout = 120000; }
|
||||
|
||||
if (n.paytoqs === true || n.paytoqs === "query") { paytoqs = true; }
|
||||
else if (n.paytoqs === "body") { paytobody = true; }
|
||||
|
||||
|
||||
var prox, noprox;
|
||||
if (process.env.http_proxy) { prox = process.env.http_proxy; }
|
||||
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
|
||||
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
|
||||
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
|
||||
|
||||
var proxyConfig = null;
|
||||
if (n.proxy) {
|
||||
proxyConfig = RED.nodes.getNode(n.proxy);
|
||||
prox = proxyConfig.url;
|
||||
noprox = proxyConfig.noproxy;
|
||||
}
|
||||
|
||||
let timingLog = false;
|
||||
if (RED.settings.hasOwnProperty("httpRequestTimingLog")) {
|
||||
timingLog = RED.settings.httpRequestTimingLog;
|
||||
}
|
||||
|
||||
this.on("input",function(msg,nodeSend,nodeDone) {
|
||||
checkNodeAgentPatch();
|
||||
//reset redirectList on each request
|
||||
redirectList = [];
|
||||
var preRequestTimestamp = process.hrtime();
|
||||
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
|
||||
var url = nodeUrl || msg.url;
|
||||
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
|
||||
node.warn(RED._("common.errors.nooverride"));
|
||||
}
|
||||
|
||||
if (isTemplatedUrl) {
|
||||
url = mustache.render(nodeUrl,msg);
|
||||
}
|
||||
if (!url) {
|
||||
node.error(RED._("httpin.errors.no-url"),msg);
|
||||
nodeDone();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// url must start http:// or https:// so assume http:// if not set
|
||||
if (url.indexOf("://") !== -1 && url.indexOf("http") !== 0) {
|
||||
node.warn(RED._("httpin.errors.invalid-transport"));
|
||||
node.status({fill:"red",shape:"ring",text:"httpin.errors.invalid-transport"});
|
||||
nodeDone();
|
||||
return;
|
||||
}
|
||||
if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) {
|
||||
if (tlsNode) {
|
||||
url = "https://"+url;
|
||||
} else {
|
||||
url = "http://"+url;
|
||||
}
|
||||
}
|
||||
|
||||
// The Request module used in Node-RED 1.x was tolerant of query strings that
|
||||
// were partially encoded. For example - "?a=hello%20there&b=20%"
|
||||
// The GOT module doesn't like that.
|
||||
// The following is an attempt to normalise the url to ensure it is properly
|
||||
// encoded. We cannot just encode it directly as we don't want any valid
|
||||
// encoded entity to end up doubly encoded.
|
||||
if (url.indexOf("?") > -1) {
|
||||
// Only do this if there is a query string to deal with
|
||||
const [hostPath, ...queryString] = url.split("?")
|
||||
const query = queryString.join("?");
|
||||
if (query) {
|
||||
// Look for any instance of % not followed by two hex chars.
|
||||
// Replace any we find with %25.
|
||||
const escapedQueryString = query.replace(/(%.?.?)/g, function(v) {
|
||||
if (/^%[a-f0-9]{2}/i.test(v)) {
|
||||
return v;
|
||||
}
|
||||
return v.replace(/%/,"%25")
|
||||
})
|
||||
url = hostPath+"?"+escapedQueryString;
|
||||
}
|
||||
}
|
||||
|
||||
var method = nodeMethod.toUpperCase() || "GET";
|
||||
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
|
||||
node.warn(RED._("common.errors.nooverride"));
|
||||
}
|
||||
if (msg.method && n.method && (n.method === "use")) {
|
||||
method = msg.method.toUpperCase(); // use the msg parameter
|
||||
}
|
||||
|
||||
// var isHttps = (/^https/i.test(url));
|
||||
|
||||
var opts = {};
|
||||
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
|
||||
// Had to remove this to get http->https redirect to work
|
||||
// opts.defaultPort = isHttps?443:80;
|
||||
opts.timeout = node.reqTimeout;
|
||||
opts.throwHttpErrors = false;
|
||||
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
|
||||
opts.decompress = false;
|
||||
opts.method = method;
|
||||
opts.headers = {};
|
||||
opts.retry = 0;
|
||||
opts.responseType = 'buffer';
|
||||
opts.maxRedirects = 21;
|
||||
opts.cookieJar = new CookieJar();
|
||||
opts.ignoreInvalidCookies = true;
|
||||
opts.forever = nodeHTTPPersistent;
|
||||
if (msg.requestTimeout !== undefined) {
|
||||
if (isNaN(msg.requestTimeout)) {
|
||||
node.warn(RED._("httpin.errors.timeout-isnan"));
|
||||
} else if (msg.requestTimeout < 1) {
|
||||
node.warn(RED._("httpin.errors.timeout-isnegative"));
|
||||
} else {
|
||||
opts.timeout = msg.requestTimeout;
|
||||
}
|
||||
}
|
||||
const originalHeaderMap = {};
|
||||
|
||||
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];
|
||||
}
|
||||
})
|
||||
}
|
||||
],
|
||||
beforeRedirect: [
|
||||
(options, response) => {
|
||||
let redirectInfo = {
|
||||
location: response.headers.location
|
||||
}
|
||||
if (response.headers.hasOwnProperty('set-cookie')) {
|
||||
redirectInfo.cookies = extractCookies(response.headers['set-cookie']);
|
||||
}
|
||||
redirectList.push(redirectInfo)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var ctSet = "Content-Type"; // set default camel case
|
||||
var clSet = "Content-Length";
|
||||
if (msg.headers) {
|
||||
if (msg.headers.hasOwnProperty('x-node-red-request-node')) {
|
||||
var headerHash = msg.headers['x-node-red-request-node'];
|
||||
delete msg.headers['x-node-red-request-node'];
|
||||
var hash = hashSum(msg.headers);
|
||||
if (hash === headerHash) {
|
||||
delete msg.headers;
|
||||
}
|
||||
}
|
||||
if (msg.headers) {
|
||||
for (var v in msg.headers) {
|
||||
if (msg.headers.hasOwnProperty(v)) {
|
||||
var name = v.toLowerCase();
|
||||
if (name !== "content-type" && name !== "content-length") {
|
||||
// only normalise the known headers used later in this
|
||||
// function. Otherwise leave them alone.
|
||||
name = v;
|
||||
}
|
||||
else if (name === 'content-type') { ctSet = v; }
|
||||
else { clSet = v; }
|
||||
opts.headers[name] = msg.headers[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.hasOwnProperty('followRedirects')) {
|
||||
opts.followRedirect = !!msg.followRedirects;
|
||||
}
|
||||
|
||||
if (opts.headers.hasOwnProperty('cookie')) {
|
||||
var cookies = cookie.parse(opts.headers.cookie, {decode:String});
|
||||
for (var name in cookies) {
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, cookies[name], {encode:String}), url, {ignoreError: true});
|
||||
}
|
||||
delete opts.headers.cookie;
|
||||
}
|
||||
if (msg.cookies) {
|
||||
for (var name in msg.cookies) {
|
||||
if (msg.cookies.hasOwnProperty(name)) {
|
||||
if (msg.cookies[name] === null || msg.cookies[name].value === null) {
|
||||
// This case clears a cookie for HTTP In/Response nodes.
|
||||
// Ignore for this node.
|
||||
} else if (typeof msg.cookies[name] === 'object') {
|
||||
if(msg.cookies[name].encode === false){
|
||||
// If the encode option is false, the value is not encoded.
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name].value, {encode: String}), url, {ignoreError: true});
|
||||
} else {
|
||||
// The value is encoded by encodeURIComponent().
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name].value), url, {ignoreError: true});
|
||||
}
|
||||
} else {
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name]), url, {ignoreError: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (fixed in got v12)
|
||||
// var cred = ""
|
||||
if (this.credentials.user || this.credentials.password) {
|
||||
// cred = `${this.credentials.user}:${this.credentials.password}`;
|
||||
if (this.credentials.user === undefined) { this.credentials.user = ""}
|
||||
if (this.credentials.password === undefined) { this.credentials.password = ""}
|
||||
opts.headers.Authorization = "Basic " + Buffer.from(`${this.credentials.user}:${this.credentials.password}`).toString("base64");
|
||||
}
|
||||
// build own basic auth header
|
||||
// opts.headers.Authorization = "Basic " + Buffer.from(cred).toString("base64");
|
||||
} else if (this.authType === "digest") {
|
||||
let digestCreds = this.credentials;
|
||||
let sentCreds = false;
|
||||
opts.hooks.afterResponse = [(response, retry) => {
|
||||
if (response.statusCode === 401) {
|
||||
if (sentCreds) {
|
||||
return response
|
||||
}
|
||||
const requestUrl = new URL(response.request.requestUrl);
|
||||
const options = response.request.options;
|
||||
const normalisedHeaders = {};
|
||||
Object.keys(response.headers).forEach(k => {
|
||||
normalisedHeaders[k.toLowerCase()] = response.headers[k]
|
||||
})
|
||||
if (normalisedHeaders['www-authenticate']) {
|
||||
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
||||
options.headers.Authorization = authHeader;
|
||||
}
|
||||
sentCreds = true;
|
||||
return retry(options);
|
||||
}
|
||||
return response
|
||||
}];
|
||||
} else if (this.authType === "bearer") {
|
||||
opts.headers.Authorization = `Bearer ${this.credentials.password||""}`
|
||||
}
|
||||
}
|
||||
var payload = null;
|
||||
|
||||
|
||||
if (method !== 'GET' && method !== 'HEAD' && typeof msg.payload !== "undefined") {
|
||||
if (opts.headers['content-type'] == 'multipart/form-data' && typeof msg.payload === "object") {
|
||||
let formData = new FormData();
|
||||
for (var opt in msg.payload) {
|
||||
if (msg.payload.hasOwnProperty(opt)) {
|
||||
var val = msg.payload[opt];
|
||||
if (val !== undefined && val !== null) {
|
||||
if (typeof val === 'string' || Buffer.isBuffer(val)) {
|
||||
formData.append(opt, val);
|
||||
} else if (typeof val === 'object' && val.hasOwnProperty('value')) {
|
||||
formData.append(opt,val.value,val.options || {});
|
||||
} else {
|
||||
formData.append(opt,JSON.stringify(val));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// GOT will only set the content-type header with the correct boundary
|
||||
// if the header isn't set. So we delete it here, for GOT to reset it.
|
||||
delete opts.headers['content-type'];
|
||||
opts.body = formData;
|
||||
} else {
|
||||
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
|
||||
payload = msg.payload;
|
||||
} else if (typeof msg.payload == "number") {
|
||||
payload = msg.payload+"";
|
||||
} else {
|
||||
if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
|
||||
payload = querystring.stringify(msg.payload);
|
||||
} else {
|
||||
payload = JSON.stringify(msg.payload);
|
||||
if (opts.headers['content-type'] == null) {
|
||||
opts.headers[ctSet] = "application/json";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (opts.headers['content-length'] == null) {
|
||||
if (Buffer.isBuffer(payload)) {
|
||||
opts.headers[clSet] = payload.length;
|
||||
} else {
|
||||
opts.headers[clSet] = Buffer.byteLength(payload);
|
||||
}
|
||||
}
|
||||
opts.body = payload;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (method == 'GET' && typeof msg.payload !== "undefined" && paytoqs) {
|
||||
if (typeof msg.payload === "object") {
|
||||
try {
|
||||
if (url.indexOf("?") !== -1) {
|
||||
url += (url.endsWith("?")?"":"&") + querystring.stringify(msg.payload);
|
||||
} else {
|
||||
url += "?" + querystring.stringify(msg.payload);
|
||||
}
|
||||
} catch(err) {
|
||||
|
||||
node.error(RED._("httpin.errors.invalid-payload"),msg);
|
||||
nodeDone();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
node.error(RED._("httpin.errors.invalid-payload"),msg);
|
||||
nodeDone();
|
||||
return;
|
||||
}
|
||||
} else if ( method == "GET" && typeof msg.payload !== "undefined" && paytobody) {
|
||||
opts.allowGetBody = true;
|
||||
if (typeof msg.payload === "object") {
|
||||
opts.body = JSON.stringify(msg.payload);
|
||||
} else if (typeof msg.payload == "number") {
|
||||
opts.body = msg.payload+"";
|
||||
} else if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
|
||||
opts.body = msg.payload;
|
||||
}
|
||||
}
|
||||
|
||||
// revert to user supplied Capitalisation if needed.
|
||||
if (opts.headers.hasOwnProperty('content-type') && (ctSet !== 'content-type')) {
|
||||
opts.headers[ctSet] = opts.headers['content-type'];
|
||||
delete opts.headers['content-type'];
|
||||
}
|
||||
if (opts.headers.hasOwnProperty('content-length') && (clSet !== 'content-length')) {
|
||||
opts.headers[clSet] = opts.headers['content-length'];
|
||||
delete opts.headers['content-length'];
|
||||
}
|
||||
|
||||
var noproxy;
|
||||
if (noprox) {
|
||||
for (var i = 0; i < noprox.length; i += 1) {
|
||||
if (url.indexOf(noprox[i]) !== -1) { noproxy=true; }
|
||||
}
|
||||
}
|
||||
if (prox && !noproxy) {
|
||||
var match = prox.match(/^(https?:\/\/)?(.+)?:([0-9]+)?/i);
|
||||
if (match) {
|
||||
let proxyAgent;
|
||||
let proxyURL = new URL(prox);
|
||||
//set username/password to null to stop empty creds header
|
||||
let proxyOptions = {
|
||||
proxy: {
|
||||
protocol: proxyURL.protocol,
|
||||
hostname: proxyURL.hostname,
|
||||
port: proxyURL.port,
|
||||
username: null,
|
||||
password: null
|
||||
},
|
||||
maxFreeSockets: 256,
|
||||
maxSockets: 256,
|
||||
keepAlive: true
|
||||
}
|
||||
if (proxyConfig && proxyConfig.credentials) {
|
||||
let proxyUsername = proxyConfig.credentials.username || '';
|
||||
let proxyPassword = proxyConfig.credentials.password || '';
|
||||
if (proxyUsername || proxyPassword) {
|
||||
proxyOptions.proxy.username = proxyUsername;
|
||||
proxyOptions.proxy.password = proxyPassword;
|
||||
}
|
||||
} else if (proxyURL.username || proxyURL.password){
|
||||
proxyOptions.proxy.username = proxyURL.username;
|
||||
proxyOptions.proxy.password = proxyURL.password;
|
||||
}
|
||||
//need both incase of http -> https redirect
|
||||
opts.agent = {
|
||||
http: new HttpProxyAgent(proxyOptions),
|
||||
https: new HttpsProxyAgent(proxyOptions)
|
||||
};
|
||||
|
||||
} else {
|
||||
node.warn("Bad proxy url: "+ prox);
|
||||
}
|
||||
}
|
||||
if (tlsNode) {
|
||||
opts.https = {};
|
||||
tlsNode.addTLSOptions(opts.https);
|
||||
if (opts.https.ca) {
|
||||
opts.https.certificateAuthority = opts.https.ca;
|
||||
delete opts.https.ca;
|
||||
}
|
||||
if (opts.https.cert) {
|
||||
opts.https.certificate = opts.https.cert;
|
||||
delete opts.https.cert;
|
||||
}
|
||||
} else {
|
||||
if (msg.hasOwnProperty('rejectUnauthorized')) {
|
||||
opts.https = { rejectUnauthorized: msg.rejectUnauthorized };
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have established all of our own headers, take a snapshot
|
||||
// of their case so we can restore it prior to the request being sent.
|
||||
if (opts.headers) {
|
||||
Object.keys(opts.headers).forEach(h => {
|
||||
originalHeaderMap[h.toLowerCase()] = h
|
||||
})
|
||||
}
|
||||
got(url,opts).then(res => {
|
||||
msg.statusCode = res.statusCode;
|
||||
msg.headers = res.headers;
|
||||
msg.responseUrl = res.url;
|
||||
msg.payload = res.body;
|
||||
msg.redirectList = redirectList;
|
||||
msg.retry = 0;
|
||||
|
||||
if (msg.headers.hasOwnProperty('set-cookie')) {
|
||||
msg.responseCookies = extractCookies(msg.headers['set-cookie']);
|
||||
}
|
||||
msg.headers['x-node-red-request-node'] = hashSum(msg.headers);
|
||||
// msg.url = url; // revert when warning above finally removed
|
||||
if (node.metric()) {
|
||||
// Calculate request time
|
||||
var diff = process.hrtime(preRequestTimestamp);
|
||||
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
|
||||
var metricRequestDurationMillis = ms.toFixed(3);
|
||||
node.metric("duration.millis", msg, metricRequestDurationMillis);
|
||||
if (res.client && res.client.bytesRead) {
|
||||
node.metric("size.bytes", msg, res.client.bytesRead);
|
||||
}
|
||||
if (timingLog) {
|
||||
emitTimingMetricLog(res.timings, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the payload to the required return type
|
||||
if (node.ret !== "bin") {
|
||||
msg.payload = msg.payload.toString('utf8'); // txt
|
||||
|
||||
if (node.ret === "obj") {
|
||||
try { msg.payload = JSON.parse(msg.payload); } // obj
|
||||
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
|
||||
}
|
||||
}
|
||||
node.status({});
|
||||
nodeSend(msg);
|
||||
nodeDone();
|
||||
}).catch(err => {
|
||||
// Pre 2.1, any errors would be sent to both Catch node and sent on as normal.
|
||||
// This is not ideal but is the legacy behaviour of the node.
|
||||
// 2.1 adds the 'senderr' option, if set to true, will *only* send errors
|
||||
// to Catch nodes. If false, it still does both behaviours.
|
||||
// TODO: 3.0 - make it one or the other.
|
||||
|
||||
if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
|
||||
node.error(RED._("common.notification.errors.no-response"), msg);
|
||||
node.status({fill:"red", shape:"ring", text:"common.notification.errors.no-response"});
|
||||
} else {
|
||||
node.error(err,msg);
|
||||
node.status({fill:"red", shape:"ring", text:err.code});
|
||||
}
|
||||
msg.payload = err.toString() + " : " + url;
|
||||
msg.statusCode = err.code || (err.response?err.response.statusCode:undefined);
|
||||
if (node.metric() && timingLog) {
|
||||
emitTimingMetricLog(err.timings, msg);
|
||||
}
|
||||
if (!sendErrorsToCatch) {
|
||||
nodeSend(msg);
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
});
|
||||
|
||||
this.on("close",function() {
|
||||
node.status({});
|
||||
});
|
||||
|
||||
function emitTimingMetricLog(timings, msg) {
|
||||
const props = [
|
||||
"start",
|
||||
"socket",
|
||||
"lookup",
|
||||
"connect",
|
||||
"secureConnect",
|
||||
"upload",
|
||||
"response",
|
||||
"end",
|
||||
"error",
|
||||
"abort"
|
||||
];
|
||||
if (timings) {
|
||||
props.forEach(p => {
|
||||
if (timings[p]) {
|
||||
node.metric(`timings.${p}`, msg, timings[p]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function extractCookies(setCookie) {
|
||||
var 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;
|
||||
});
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType("http request",HTTPRequest,{
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
}
|
||||
});
|
||||
|
||||
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
|
||||
|
||||
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
||||
/**
|
||||
* RFC 2617: handle both MD5 and MD5-sess algorithms.
|
||||
*
|
||||
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
||||
* HA1=MD5(username:realm:password)
|
||||
* If the algorithm directive's value is "MD5-sess", then HA1 is
|
||||
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
||||
*/
|
||||
var ha1 = md5(user + ':' + realm + ':' + pass)
|
||||
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
|
||||
return md5(ha1 + ':' + nonce + ':' + cnonce)
|
||||
} else {
|
||||
return ha1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function buildDigestHeader(user, pass, method, path, authHeader) {
|
||||
var challenge = {}
|
||||
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
||||
for (;;) {
|
||||
var match = re.exec(authHeader)
|
||||
if (!match) {
|
||||
break
|
||||
}
|
||||
challenge[match[1]] = match[2] || match[3]
|
||||
}
|
||||
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
|
||||
var nc = qop && '00000001'
|
||||
var cnonce = qop && uuid().replace(/-/g, '')
|
||||
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
|
||||
var ha2 = md5(method + ':' + path)
|
||||
var digestResponse = qop
|
||||
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
||||
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
|
||||
var authValues = {
|
||||
username: user,
|
||||
realm: challenge.realm,
|
||||
nonce: challenge.nonce,
|
||||
uri: path,
|
||||
qop: qop,
|
||||
response: digestResponse,
|
||||
nc: nc,
|
||||
cnonce: cnonce,
|
||||
algorithm: challenge.algorithm,
|
||||
opaque: challenge.opaque
|
||||
}
|
||||
|
||||
authHeader = []
|
||||
for (var k in authValues) {
|
||||
if (authValues[k]) {
|
||||
if (k === 'qop' || k === 'nc' || k === 'algorithm') {
|
||||
authHeader.push(k + '=' + authValues[k])
|
||||
} else {
|
||||
authHeader.push(k + '="' + authValues[k] + '"')
|
||||
}
|
||||
}
|
||||
}
|
||||
authHeader = 'Digest ' + authHeader.join(', ')
|
||||
return authHeader
|
||||
}
|
||||
}
|
417
packages/node_modules/@node-red/nodes/core/network/22-websocket.js
vendored
Normal file
417
packages/node_modules/@node-red/nodes/core/network/22-websocket.js
vendored
Normal file
@ -0,0 +1,417 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var ws = require("ws");
|
||||
var inspect = require("util").inspect;
|
||||
var url = require("url");
|
||||
var HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
|
||||
var serverUpgradeAdded = false;
|
||||
function handleServerUpgrade(request, socket, head) {
|
||||
const pathname = url.parse(request.url).pathname;
|
||||
if (listenerNodes.hasOwnProperty(pathname)) {
|
||||
listenerNodes[pathname].server.handleUpgrade(request, socket, head, function done(ws) {
|
||||
listenerNodes[pathname].server.emit('connection', ws, request);
|
||||
});
|
||||
} else {
|
||||
// Don't destroy the socket as other listeners may want to handle the
|
||||
// event.
|
||||
}
|
||||
}
|
||||
var listenerNodes = {};
|
||||
var activeListenerNodes = 0;
|
||||
|
||||
|
||||
// A node red node that sets up a local websocket server
|
||||
function WebSocketListenerNode(n) {
|
||||
// Create a RED node
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
|
||||
// Store local copies of the node configuration (as defined in the .html)
|
||||
node.path = n.path;
|
||||
node.wholemsg = (n.wholemsg === "true");
|
||||
|
||||
node._inputNodes = []; // collection of nodes that want to receive events
|
||||
node._clients = {};
|
||||
// match absolute url
|
||||
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
|
||||
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;
|
||||
if (process.env.http_proxy) { prox = process.env.http_proxy; }
|
||||
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
|
||||
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
|
||||
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
|
||||
|
||||
var noproxy = false;
|
||||
if (noprox) {
|
||||
for (var i in noprox) {
|
||||
if (node.path.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
|
||||
}
|
||||
}
|
||||
|
||||
var agent = undefined;
|
||||
if (prox && !noproxy) {
|
||||
agent = new HttpsProxyAgent(prox);
|
||||
}
|
||||
|
||||
var options = {};
|
||||
if (agent) {
|
||||
options.agent = agent;
|
||||
}
|
||||
if (node.tls) {
|
||||
var tlsNode = RED.nodes.getNode(node.tls);
|
||||
if (tlsNode) {
|
||||
tlsNode.addTLSOptions(options);
|
||||
}
|
||||
}
|
||||
var socket = new ws(node.path,options);
|
||||
socket.setMaxListeners(0);
|
||||
node.server = socket; // keep for closing
|
||||
handleConnection(socket);
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
socket.on('open',function() {
|
||||
if (!node.isServer) {
|
||||
if (node.heartbeat) {
|
||||
clearInterval(node.heartbeatInterval);
|
||||
node.heartbeatInterval = setInterval(function() {
|
||||
if (socket.nrPendingHeartbeat) {
|
||||
// No pong received
|
||||
socket.terminate();
|
||||
socket.nrErrorHandler(new Error("timeout"));
|
||||
return;
|
||||
}
|
||||
socket.nrPendingHeartbeat = true;
|
||||
try {
|
||||
socket.ping();
|
||||
} catch(err) {}
|
||||
},node.heartbeat);
|
||||
}
|
||||
node.emit('opened',{count:'',id:id});
|
||||
}
|
||||
});
|
||||
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});
|
||||
} else {
|
||||
node.emit('closed',{count:'',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('message',function(data,flags) {
|
||||
node.handleEvent(id,socket,'message',data,flags);
|
||||
});
|
||||
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) {
|
||||
activeListenerNodes++;
|
||||
if (!serverUpgradeAdded) {
|
||||
RED.server.on('upgrade', handleServerUpgrade);
|
||||
serverUpgradeAdded = true
|
||||
}
|
||||
|
||||
var path = RED.settings.httpNodeRoot || "/";
|
||||
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
|
||||
node.fullPath = path;
|
||||
|
||||
if (listenerNodes.hasOwnProperty(path)) {
|
||||
node.error(RED._("websocket.errors.duplicate-path",{path: node.path}));
|
||||
return;
|
||||
}
|
||||
listenerNodes[node.fullPath] = node;
|
||||
var serverOptions = {
|
||||
noServer: true
|
||||
}
|
||||
if (RED.settings.webSocketNodeVerifyClient) {
|
||||
serverOptions.verifyClient = RED.settings.webSocketNodeVerifyClient;
|
||||
}
|
||||
// Create a WebSocket Server
|
||||
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;
|
||||
startconn(); // start outbound connection
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
if (node.heartbeatInterval) {
|
||||
clearInterval(node.heartbeatInterval);
|
||||
}
|
||||
if (node.isServer) {
|
||||
delete listenerNodes[node.fullPath];
|
||||
node.server.close();
|
||||
node._inputNodes = [];
|
||||
activeListenerNodes--;
|
||||
// if (activeListenerNodes === 0 && serverUpgradeAdded) {
|
||||
// RED.server.removeListener('upgrade', handleServerUpgrade);
|
||||
// serverUpgradeAdded = false;
|
||||
// }
|
||||
}
|
||||
else {
|
||||
node.closing = true;
|
||||
node.server.close();
|
||||
if (node.tout) {
|
||||
clearTimeout(node.tout);
|
||||
node.tout = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
|
||||
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
|
||||
|
||||
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
|
||||
this._inputNodes.push(handler);
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.removeInputNode = function(/*Node*/handler) {
|
||||
this._inputNodes.forEach(function(node, i, inputNodes) {
|
||||
if (node === handler) {
|
||||
inputNodes.splice(i, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags) {
|
||||
var msg;
|
||||
if (this.wholemsg) {
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
if (typeof msg !== "object" && !Array.isArray(msg) && (msg !== null)) {
|
||||
msg = { payload:msg };
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
msg = { payload:data };
|
||||
}
|
||||
} else {
|
||||
msg = {
|
||||
payload:data
|
||||
};
|
||||
}
|
||||
msg._session = {type:"websocket",id:id};
|
||||
for (var i = 0; i < this._inputNodes.length; i++) {
|
||||
this._inputNodes[i].send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.broadcast = function(data) {
|
||||
if (this.isServer) {
|
||||
for (let client in this._clients) {
|
||||
if (this._clients.hasOwnProperty(client)) {
|
||||
try {
|
||||
this._clients[client].send(data);
|
||||
} catch(err) {
|
||||
this.warn(RED._("websocket.errors.send-error")+" "+client+" "+err.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
this.server.send(data);
|
||||
} catch(err) {
|
||||
this.warn(RED._("websocket.errors.send-error")+" "+err.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.reply = function(id,data) {
|
||||
var session = this._clients[id];
|
||||
if (session) {
|
||||
try {
|
||||
session.send(data);
|
||||
}
|
||||
catch(e) { // swallow any errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function WebSocketInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
var node = this;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (this.serverConfig) {
|
||||
this.serverConfig.registerInputNode(this);
|
||||
// TODO: nls
|
||||
this.serverConfig.on('opened', function(event) {
|
||||
node.status({
|
||||
fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
|
||||
event:"connect",
|
||||
_session: {type:"websocket",id:event.id}
|
||||
});
|
||||
});
|
||||
this.serverConfig.on('erro', function(event) {
|
||||
node.status({
|
||||
fill:"red",shape:"ring",text:"common.status.error",
|
||||
event:"error",
|
||||
_session: {type:"websocket",id:event.id}
|
||||
});
|
||||
});
|
||||
this.serverConfig.on('closed', function(event) {
|
||||
var status;
|
||||
if (event.count > 0) {
|
||||
status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
|
||||
} else {
|
||||
status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
|
||||
}
|
||||
status.event = "disconnect";
|
||||
status._session = {type:"websocket",id:event.id}
|
||||
node.status(status);
|
||||
});
|
||||
} else {
|
||||
this.error(RED._("websocket.errors.missing-conf"));
|
||||
}
|
||||
this.on('close', function() {
|
||||
if (node.serverConfig) {
|
||||
node.serverConfig.removeInputNode(node);
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket in",WebSocketInNode);
|
||||
|
||||
function WebSocketOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (!this.serverConfig) {
|
||||
return this.error(RED._("websocket.errors.missing-conf"));
|
||||
}
|
||||
else {
|
||||
// TODO: nls
|
||||
this.serverConfig.on('opened', function(event) {
|
||||
node.status({
|
||||
fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
|
||||
event:"connect",
|
||||
_session: {type:"websocket",id:event.id}
|
||||
});
|
||||
});
|
||||
this.serverConfig.on('erro', function(event) {
|
||||
node.status({
|
||||
fill:"red",shape:"ring",text:"common.status.error",
|
||||
event:"error",
|
||||
_session: {type:"websocket",id:event.id}
|
||||
})
|
||||
});
|
||||
this.serverConfig.on('closed', function(event) {
|
||||
var status;
|
||||
if (event.count > 0) {
|
||||
status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
|
||||
} else {
|
||||
status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
|
||||
}
|
||||
status.event = "disconnect";
|
||||
status._session = {type:"websocket",id:event.id}
|
||||
node.status(status);
|
||||
});
|
||||
}
|
||||
this.on("input", function(msg, nodeSend, nodeDone) {
|
||||
var payload;
|
||||
if (this.serverConfig.wholemsg) {
|
||||
var sess;
|
||||
if (msg._session) { sess = JSON.stringify(msg._session); }
|
||||
delete msg._session;
|
||||
payload = JSON.stringify(msg);
|
||||
if (sess) { msg._session = JSON.parse(sess); }
|
||||
}
|
||||
else if (msg.hasOwnProperty("payload")) {
|
||||
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
|
||||
payload = RED.util.ensureString(msg.payload);
|
||||
}
|
||||
else {
|
||||
payload = msg.payload;
|
||||
}
|
||||
}
|
||||
if (payload) {
|
||||
if (msg._session && msg._session.type == "websocket") {
|
||||
node.serverConfig.reply(msg._session.id,payload);
|
||||
} else {
|
||||
node.serverConfig.broadcast(payload,function(error) {
|
||||
if (!!error) {
|
||||
node.warn(RED._("websocket.errors.send-error")+inspect(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
nodeDone();
|
||||
});
|
||||
this.on('close', function() {
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket out",WebSocketOutNode);
|
||||
}
|
156
packages/node_modules/@node-red/nodes/examples/common/link/03 - Link call.json
vendored
Normal file
156
packages/node_modules/@node-red/nodes/examples/common/link/03 - Link call.json
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
[
|
||||
{
|
||||
"id": "62ea32aa.d73aac",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Example: Link Call Node",
|
||||
"info": "Link call node can call link in node then get result from link out node.",
|
||||
"x": 230,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "c588bc36.87fec",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓ call link in node",
|
||||
"info": "",
|
||||
"x": 440,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "cd31efb4d2c6967e",
|
||||
"type": "link call",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"links": [
|
||||
"dbc46892c8d14c37"
|
||||
],
|
||||
"timeout": "30",
|
||||
"x": 420,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"c3db64d1d2260340"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dbc46892c8d14c37",
|
||||
"type": "link in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"links": [],
|
||||
"x": 315,
|
||||
"y": 340,
|
||||
"wires": [
|
||||
[
|
||||
"e10575d73f2e5352"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6b61792143b3b0a3",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 240,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"cd31efb4d2c6967e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e10575d73f2e5352",
|
||||
"type": "change",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"rules": [
|
||||
{
|
||||
"t": "set",
|
||||
"p": "payload",
|
||||
"pt": "msg",
|
||||
"to": "Hello, World!",
|
||||
"tot": "str"
|
||||
}
|
||||
],
|
||||
"action": "",
|
||||
"property": "",
|
||||
"from": "",
|
||||
"to": "",
|
||||
"reg": false,
|
||||
"x": 450,
|
||||
"y": 340,
|
||||
"wires": [
|
||||
[
|
||||
"cf8438e7137bc0f0"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cf8438e7137bc0f0",
|
||||
"type": "link out",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"mode": "return",
|
||||
"links": [],
|
||||
"x": 595,
|
||||
"y": 340,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "c3db64d1d2260340",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 600,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "6d077dfa0987febb",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑called from link call node",
|
||||
"info": "",
|
||||
"x": 410,
|
||||
"y": 380,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "53b9a0adfd8c4217",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑return to link call node",
|
||||
"info": "",
|
||||
"x": 680,
|
||||
"y": 380,
|
||||
"wires": []
|
||||
}
|
||||
]
|
113
packages/node_modules/@node-red/nodes/examples/storage/read file/01 - Read string from a file.json
vendored
Normal file
113
packages/node_modules/@node-red/nodes/examples/storage/read file/01 - Read string from a file.json
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
[
|
||||
{
|
||||
"id": "84222b92.d65d18",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "Hello, World!",
|
||||
"payloadType": "str",
|
||||
"x": 190,
|
||||
"y": 180,
|
||||
"wires": [
|
||||
[
|
||||
"b4b9f603.739598"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7b014430.dfd94c",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Write string to a file, then read from the file",
|
||||
"info": "Read file node can read string from a file.",
|
||||
"x": 220,
|
||||
"y": 100,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b4b9f603.739598",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 380,
|
||||
"y": 180,
|
||||
"wires": [
|
||||
[
|
||||
"6dc01cac.5c4bf4"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2587adb9.7e60f2",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 770,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "6dc01cac.5c4bf4",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 580,
|
||||
"y": 180,
|
||||
"wires": [
|
||||
[
|
||||
"2587adb9.7e60f2"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f4b4309a.3b78a",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read result from file",
|
||||
"info": "",
|
||||
"x": 590,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "672d3693.3cabd8",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 400,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -0,0 +1,114 @@
|
||||
[
|
||||
{
|
||||
"id": "8997398f.c5d628",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "😀",
|
||||
"payloadType": "str",
|
||||
"x": 170,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"56e32d23.050f44"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4e598e65.1799d",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Read data in specified encoding",
|
||||
"info": "Read file node can specify encoding of data read from a file.",
|
||||
"x": 190,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "56e32d23.050f44",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 340,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"38fa0579.f2cd8a"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d28c8994.99c0a8",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 730,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "38fa0579.f2cd8a",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "base64",
|
||||
"allProps": false,
|
||||
"x": 540,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"d28c8994.99c0a8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fa22ca20.ae4528",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read data from file as base64 string",
|
||||
"info": "",
|
||||
"x": 600,
|
||||
"y": 300,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "148e25ad.98891a",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 360,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -0,0 +1,132 @@
|
||||
[
|
||||
{
|
||||
"id": "6a0b1d03.d4cee4",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 160,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"d4b00cb7.a5a23"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f17ea1d1.8ecc3",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Read data breaking lines into individual messages",
|
||||
"info": "Read file node can break read text into messages with individual lines",
|
||||
"x": 230,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "99ae7806.1d6428",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 480,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"70d7892f.d27db8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7ed8282c.92b338",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 750,
|
||||
"y": 280,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "70d7892f.d27db8",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "lines",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 560,
|
||||
"y": 280,
|
||||
"wires": [
|
||||
[
|
||||
"7ed8282c.92b338"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "c1b7e05.1d94b2",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read data from file breaking lines into messages",
|
||||
"info": "",
|
||||
"x": 660,
|
||||
"y": 320,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "a5f647b2.cf27a8",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 500,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "d4b00cb7.a5a23",
|
||||
"type": "template",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "data",
|
||||
"field": "payload",
|
||||
"fieldType": "msg",
|
||||
"format": "handlebars",
|
||||
"syntax": "plain",
|
||||
"template": "one\ntwo\nthree!",
|
||||
"output": "str",
|
||||
"x": 310,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"99ae7806.1d6428"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"id": "bdd57acc.2edc48",
|
||||
"type": "inject",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
@ -20,8 +20,8 @@
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 220,
|
||||
"y": 1040,
|
||||
"x": 180,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"7a069b01.0c2324"
|
||||
@ -31,25 +31,25 @@
|
||||
{
|
||||
"id": "1fd12220.33953e",
|
||||
"type": "comment",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Creating a message stream from lines of data",
|
||||
"info": "File-in node can break read text into messages with individual lines. The messages creates a stream of messages.",
|
||||
"x": 270,
|
||||
"y": 960,
|
||||
"info": "Read file node can break read text into messages with individual lines. The messages creates a stream of messages.",
|
||||
"x": 230,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "ab6eb213.2a08d",
|
||||
"type": "file",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 540,
|
||||
"y": 1040,
|
||||
"x": 500,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"b7ed49b0.649fb8"
|
||||
@ -59,7 +59,7 @@
|
||||
{
|
||||
"id": "c48d8ae0.9ff3a8",
|
||||
"type": "debug",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
@ -68,22 +68,22 @@
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 810,
|
||||
"y": 1140,
|
||||
"x": 770,
|
||||
"y": 320,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b7ed49b0.649fb8",
|
||||
"type": "file in",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "lines",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 280,
|
||||
"y": 1140,
|
||||
"x": 240,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"83073ebe.fcce4"
|
||||
@ -93,27 +93,27 @@
|
||||
{
|
||||
"id": "3c33e69f.6a04ba",
|
||||
"type": "comment",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read data from file breaking lines into messages",
|
||||
"info": "",
|
||||
"x": 380,
|
||||
"y": 1180,
|
||||
"x": 340,
|
||||
"y": 360,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "3598bf7d.5712a",
|
||||
"type": "comment",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 560,
|
||||
"y": 1000,
|
||||
"x": 520,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "7a069b01.0c2324",
|
||||
"type": "template",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "data",
|
||||
"field": "payload",
|
||||
"fieldType": "msg",
|
||||
@ -121,8 +121,8 @@
|
||||
"syntax": "plain",
|
||||
"template": "Apple\nBanana\nGrape\nOrange",
|
||||
"output": "str",
|
||||
"x": 370,
|
||||
"y": 1040,
|
||||
"x": 330,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"ab6eb213.2a08d"
|
||||
@ -132,7 +132,7 @@
|
||||
{
|
||||
"id": "8d4ed1d0.821fe",
|
||||
"type": "join",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"mode": "auto",
|
||||
"build": "string",
|
||||
@ -145,8 +145,8 @@
|
||||
"timeout": "",
|
||||
"count": "",
|
||||
"reduceRight": false,
|
||||
"x": 630,
|
||||
"y": 1140,
|
||||
"x": 590,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"c48d8ae0.9ff3a8"
|
||||
@ -156,7 +156,7 @@
|
||||
{
|
||||
"id": "83073ebe.fcce4",
|
||||
"type": "switch",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "< D",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
@ -170,8 +170,8 @@
|
||||
"checkall": "true",
|
||||
"repair": true,
|
||||
"outputs": 1,
|
||||
"x": 470,
|
||||
"y": 1140,
|
||||
"x": 430,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"8d4ed1d0.821fe"
|
||||
@ -181,21 +181,21 @@
|
||||
{
|
||||
"id": "2088e195.f7aebe",
|
||||
"type": "comment",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓filter data before \"D\"",
|
||||
"info": "",
|
||||
"x": 520,
|
||||
"y": 1100,
|
||||
"x": 480,
|
||||
"y": 280,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b848cdc7.61e06",
|
||||
"type": "comment",
|
||||
"z": "194a3e4f.a92772",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑join to single string",
|
||||
"info": "",
|
||||
"x": 670,
|
||||
"y": 1180,
|
||||
"x": 630,
|
||||
"y": 360,
|
||||
"wires": []
|
||||
}
|
||||
]
|
113
packages/node_modules/@node-red/nodes/examples/storage/write file/01 - Write string to a file.json
vendored
Normal file
113
packages/node_modules/@node-red/nodes/examples/storage/write file/01 - Write string to a file.json
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
[
|
||||
{
|
||||
"id": "84222b92.d65d18",
|
||||
"type": "inject",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "Hello, World!",
|
||||
"payloadType": "str",
|
||||
"x": 150,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"b4b9f603.739598"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7b014430.dfd94c",
|
||||
"type": "comment",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "Write string to a file, then read from the file",
|
||||
"info": "Write file node can write string from a file.",
|
||||
"x": 180,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b4b9f603.739598",
|
||||
"type": "file",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 340,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"6dc01cac.5c4bf4"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2587adb9.7e60f2",
|
||||
"type": "debug",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 730,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "6dc01cac.5c4bf4",
|
||||
"type": "file in",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 540,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"2587adb9.7e60f2"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f4b4309a.3b78a",
|
||||
"type": "comment",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "↑read result from file",
|
||||
"info": "",
|
||||
"x": 550,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "672d3693.3cabd8",
|
||||
"type": "comment",
|
||||
"z": "5132b95f037524f9",
|
||||
"name": "↓write to /tmp/hello.txt",
|
||||
"info": "",
|
||||
"x": 360,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -0,0 +1,118 @@
|
||||
[
|
||||
{
|
||||
"id": "704479e1.399388",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "filename",
|
||||
"v": "/tmp/hello.txt",
|
||||
"vt": "str"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "Hello, World!",
|
||||
"payloadType": "str",
|
||||
"x": 190,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"402f3b7e.988014"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8e876a75.e9beb8",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Write string to a file specied by filename property, the read from the file",
|
||||
"info": "Write file node can target file using `filename` property.",
|
||||
"x": 310,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "402f3b7e.988014",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 350,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"26e077d6.bbcd98"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "97b6b6b2.a54b38",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 730,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "26e077d6.bbcd98",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 540,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[
|
||||
"97b6b6b2.a54b38"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "85062297.da79",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read result from file",
|
||||
"info": "",
|
||||
"x": 550,
|
||||
"y": 300,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "7316c4fc.b1dcdc",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write to file specified by filename property",
|
||||
"info": "",
|
||||
"x": 460,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
}
|
||||
]
|
85
packages/node_modules/@node-red/nodes/examples/storage/write file/03 - Delete a file.json
vendored
Normal file
85
packages/node_modules/@node-red/nodes/examples/storage/write file/03 - Delete a file.json
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
[
|
||||
{
|
||||
"id": "4ac00fb0.d5f52",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 180,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"542cc2f4.92857c"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "671f8295.0e6f6c",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Delete a file",
|
||||
"info": "Write file node can delete a file.",
|
||||
"x": 130,
|
||||
"y": 160,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "542cc2f4.92857c",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "delete",
|
||||
"encoding": "none",
|
||||
"x": 380,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"a24da523.5babe8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a24da523.5babe8",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 590,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "51157051.2f62",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓delete a file",
|
||||
"info": "",
|
||||
"x": 350,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -0,0 +1,113 @@
|
||||
[
|
||||
{
|
||||
"id": "e4ef1f5e.7cd82",
|
||||
"type": "inject",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Base64 encoded string",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "8J+YgA==",
|
||||
"payloadType": "str",
|
||||
"x": 200,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"72b37cc8.177054"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f5997af4.5a9298",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "Specify encoding of written data",
|
||||
"info": "Write file node can specify encoding of data.",
|
||||
"x": 170,
|
||||
"y": 140,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "72b37cc8.177054",
|
||||
"type": "file",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "base64",
|
||||
"x": 420,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"2da33ec.f45cac2"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2e814354.278c8c",
|
||||
"type": "debug",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 810,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "2da33ec.f45cac2",
|
||||
"type": "file in",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "",
|
||||
"filename": "/tmp/hello.txt",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "none",
|
||||
"x": 620,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"2e814354.278c8c"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ec754c99.84bfd",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↓write string with base64 encoding",
|
||||
"info": "",
|
||||
"x": 480,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "3e6704ff.4ce25c",
|
||||
"type": "comment",
|
||||
"z": "6312c0588348b2d4",
|
||||
"name": "↑read result from file",
|
||||
"info": "",
|
||||
"x": 630,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
}
|
||||
]
|
@ -21,7 +21,7 @@
|
||||
<dt>msg <span class="property-type">object</span></dt>
|
||||
<dd>A msg object containing information to populate the template.</dd>
|
||||
<dt class="optional">template <span class="property-type">string</span></dt>
|
||||
<dd>A template to be populated from msg.payload. If not configured in the edit panel,
|
||||
<dd>A template to be populated from <code>msg.payload</code>. If not configured in the edit panel,
|
||||
this can be set as a property of msg.</dd>
|
||||
</dl>
|
||||
<h3>Outputs</h3>
|
||||
|
@ -60,5 +60,5 @@
|
||||
for the next topic.
|
||||
</p>
|
||||
<p><b>Note</b>: In rate limit mode the maximum queue depth can be set by a property in your
|
||||
<i>settings.js</i> file. For example <code>nodeMessageBufferMaxLength: 1000,</code>
|
||||
<i>settings.js</i> file. For example <code>nodeMessageBufferMaxLength: 1000,</code></p>
|
||||
</script>
|
||||
|
@ -25,7 +25,7 @@
|
||||
<h3>Details</h3>
|
||||
<p>In RBE mode this node will block until the <code>msg.payload</code>,
|
||||
(or selected property) value is different to the previous one.
|
||||
If required it can ignore the intial value, so as not to send anything at start.</p>
|
||||
If required it can ignore the initial value, so as not to send anything at start.</p>
|
||||
<p>The <a href="https://en.wikipedia.org/wiki/Deadband" target="_blank">Deadband</a> modes will block the incoming value
|
||||
<i>unless</i> its change is greater or greater-equal than ± the band gap away from a previous value.</p>
|
||||
<p>The Narrowband modes will block the incoming value,
|
||||
@ -37,5 +37,5 @@
|
||||
ignoring any values out of range, or the previous input value, which resets the set point, thus allowing
|
||||
gradual drift (deadband), or a step change (narrowband).</p>
|
||||
<p><b>Note:</b> This works on a per <code>msg.topic</code> basis, though this can be changed to another property if desired.
|
||||
This means that a single rbe node can handle multiple different topics at the same time.</p>
|
||||
This means that a single filter node can handle multiple different topics at the same time.</p>
|
||||
</script>
|
||||
|
@ -52,7 +52,7 @@
|
||||
<dt class="optional">topic <span class="property-type">string|object|array</span></dt>
|
||||
<dd>For the <code>"subscribe"</code> and <code>"unsubscribe"</code> actions, this property
|
||||
provides the topic. It can be set as either:<ul>
|
||||
<li>a String continaing the topic filter</li>
|
||||
<li>a String containing the topic filter</li>
|
||||
<li>an Object containing <code>topic</code> and <code>qos</code> properties</li>
|
||||
<li>an array of either strings or objects to handle multiple topics in one</li>
|
||||
</ul>
|
||||
|
@ -52,7 +52,7 @@
|
||||
<dd>In case any redirects occurred while processing the request, this property is the final redirected url.
|
||||
Otherwise, the url of the original request.</dd>
|
||||
<dt>responseCookies <span class="property-type">object</span></dt>
|
||||
<dd>If the response includes cookies, this propery is an object of name/value pairs for each cookie.</dd>
|
||||
<dd>If the response includes cookies, this property is an object of name/value pairs for each cookie.</dd>
|
||||
<dt>redirectList <span class="property-type">array</span></dt>
|
||||
<dd>If the request was redirected one or more times, the accumulated information will be added to this property. `location` is the next redirect destination. `cookies` is the cookies returned from the redirect source.</dd>
|
||||
</dl>
|
||||
|
@ -60,7 +60,7 @@
|
||||
</p>
|
||||
<p>When operating in this mode, the node will not set the <code>msg.parts.count</code>
|
||||
property as it does not know how many messages to expect in the stream. This
|
||||
means it cannot be used with the <b>join</b> node in its automatic mode</p>
|
||||
means it cannot be used with the <b>join</b> node in its automatic mode.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="join">
|
||||
|
@ -46,4 +46,6 @@
|
||||
<code>{{global[store].名前}}</code>を用います。
|
||||
</p>
|
||||
<p><b>注: </b>デフォルトでは、<i>mustache</i>形式は置換対象のHTML要素をエスケープします。これを抑止するには<code>{{{三重}}}</code>括弧形式を使います。</p>
|
||||
<p>もし、コンテンツの中で<code>{{ }}</code>を出力する必要がある場合は、テンプレートで使われる記号文字を変えることもできます。例えば、<code>[[ ]]</code>を代わりに用いるには、テンプレートの先頭に以下の行を追加します。</p>
|
||||
<pre>{{=[[ ]]=}}</pre>
|
||||
</script>
|
||||
|
@ -25,11 +25,14 @@
|
||||
<dt class="optional">reset</dt>
|
||||
<dd>受信メッセージでこのプロパティを任意の値に設定すると、ノードが保持する全ての未送信メッセージをクリアします。</dd>
|
||||
<dt class="optional">flush</dt>
|
||||
<dd>受信メッセージでこのプロパティを任意の値に設定すると、ノードが保持する全ての未送信メッセージを直ちに送信します。</dd>
|
||||
<dd>本プロパティに数値が設定されたメッセージを受信すると、直ちに指定された数のメッセージを送信します。もし他の型(例えば真偽型)が設定されている場合は、ノードが保持している全ての未送信メッセージを直ちに送信します。</dd>
|
||||
<dt class="optional">toFront</dt>
|
||||
<dd>流量制御モードにおいて、本プロパティに真偽型<code>true</code>が設定されたメッセージを受け取ると、キューの先頭に追加され、その後に送信されます。<code>msg.flush=1</code>と組み合わせて用いると、すぐに再送信できます。</dd>
|
||||
</dl>
|
||||
<h3>詳細</h3>
|
||||
<p>メッセージを遅延させるように設定する場合、遅延時間は固定値、範囲内の乱数値、メッセージ毎の動的な指定値のいずれかを指定できます。</p>
|
||||
<p>メッセージを遅延させるように設定する場合、遅延時間は固定値、範囲内の乱数値、メッセージ毎の動的な指定値のいずれかを指定できます。各メッセージは、到着時刻に基づいて、他のメッセージとは独立して遅延されます。</p>
|
||||
<p>流量制御する場合、メッセージは指定した時間間隔内に分散して送信します。キューに残っているメッセージ数はノードのステータスに表示されます。受け取った中間メッセージを破棄することも可能です。</p>
|
||||
<p>流量値を上書きできるように設定されている場合、新しい流量値はすぐに適用されます。この流量値は、再度変更されるまで、本ノードがリセットされるまで、またはフローが再実行されるまで有効です。</p>
|
||||
<p>流量制御は全てのメッセージに適用することも、<code>msg.topic</code>値でグループ化して適用することも可能です。グループ化すると、中間メッセージは自動的に破棄されます。時間間隔毎に全てのトピックの最新メッセージを送信するか、次のトピックの最新メッセージを送信するかを指定できます。</p>
|
||||
<p><b>注</b>: 流量制御モードでは、キューの大きさの最大値を<i>settings.js</i>ファイルのプロパティに設定できます。例えば、次の様な設定です。<code>nodeMessageBufferMaxLength: 1000,</code></p>
|
||||
</script>
|
||||
|
@ -27,5 +27,5 @@
|
||||
<p>不感帯モードでは%による指定もサポートしています。入力と前の値の差分がX%より大きな場合に出力を行います。</p>
|
||||
<p>狭帯域(narrowband)モードでは、前の値に対する差分が一定値より大きな場合に入力ペイロードをブロックします。このモードは、故障したセンサから発生する外れ値を無視する時などに有用です。</p>
|
||||
<p>不感帯モードと狭帯域モードでは、以前の有効出力値、もしくは、以前の入力値との比較ができます。有効出力値を用いると範囲外の値を無視することが、入力値を用いると設定点がリセットされるため漸次的変化(不感帯モード)もしくは段階的変化(狭帯域モード)が可能です。</p>
|
||||
<p><b>注:</b> このノードは<code>msg.topic</code>毎に動作します。そのため、ひとつのrbeノードで複数の異なるトピックを同時に扱うことができます。</p>
|
||||
<p><b>注:</b> このノードは<code>msg.topic</code>毎に動作します。そのため、ひとつのfilterノードで複数の異なるトピックを同時に扱うことができます。</p>
|
||||
</script>
|
||||
|
@ -26,11 +26,46 @@
|
||||
<dd>0: 最大1度到着, 1: 一度以上到着, 2: 1度のみ到着</dd>
|
||||
<dt>retain <span class="property-type">真偽値</span></dt>
|
||||
<dd>真の場合、メッセージを保持。メッセージが古い値の場合があります。</dd>
|
||||
<dt class="optional">responseTopic <span class="property-type">文字列</span></dt>
|
||||
<dd><b>MQTTv5</b>: メッセージのMQTT応答トピック</dd>
|
||||
<dt class="optional">correlationData <span class="property-type">バッファ</span></dt>
|
||||
<dd><b>MQTTv5</b>: メッセージの相関データ</dd>
|
||||
<dt class="optional">contentType <span class="property-type">文字列</span></dt>
|
||||
<dd><b>MQTTv5</b>: ペイロードのコンテントタイプ</dd>
|
||||
<dt class="optional">userProperties <span class="property-type">オブジェクト</span></dt>
|
||||
<dd><b>MQTTv5</b>: メッセージのユーザプロパティ</dd>
|
||||
<dt class="optional">messageExpiryInterval <span class="property-type">数値</span></dt>
|
||||
<dd><b>MQTTv5</b>: 秒単位のメッセージの有効期限</dd>
|
||||
</dl>
|
||||
<h3>詳細</h3>
|
||||
<p>購読トピックにはMQTTのワイルドカード(+: 1レベル, #: 複数レベル)を含めることができます。</p>
|
||||
<p>このノードの利用のためには、MQTTブローカへの接続設定が必要です。この設定は鉛筆アイコンをクリックすることで行えます。</p>
|
||||
<p>MQTT(inおよびout)ノードはブローカへの接続設定を必要に応じて共有できます。</p>
|
||||
<h4>動的購読</h4>
|
||||
本ノードは、MQTTの接続と購読を動的に制御するよう設定できます。有効にすると、本ノードの入力にメッセージを渡すことで制御できます。
|
||||
<h3>入力</h3>
|
||||
<p>これらは、動的購読が設定されている場合のみ適用されます。</p>
|
||||
<dl class="message-properties">
|
||||
<dt>action <span class="property-type">文字列</span></dt>
|
||||
<dd>本ノードが行う動作の名前。利用可能な動作は<code>"connect"</code>、<code>"disconnect"</code>、<code>"subscribe"</code>、<code>"unsubscribe"</code>です。</dd>
|
||||
<dt class="optional">topic <span class="property-type">文字列|オブジェクト|配列</span></dt>
|
||||
<dd><code>"subscribe"</code>と<code>"unsubscribe"</code>の動作に対して、本プロパティはトピックを提供します。次のいずれかを設定できます:<ul>
|
||||
<li>トピックフィルターを含む文字列</li>
|
||||
<li><code>topic</code>と<code>qos</code>プロパティを持つオブジェクト</li>
|
||||
<li>複数のトピックを扱う文字列やオブジェクトの配列</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="optional">broker <span class="property-type">broker</span> </dt>
|
||||
<dd><code>"connect"</code>の動作に対して、本プロパティは次の様な個々のブローカ設定を上書きします: <ul>
|
||||
<li><code>broker</code></li>
|
||||
<li><code>port</code></li>
|
||||
<li><code>url</code> - 完全な接続URLを提供するために、brokerとportを上書き</li>
|
||||
<li><code>username</code></li>
|
||||
<li><code>password</code></li>
|
||||
</ul>
|
||||
<p>本プロパティが設定され既にブローカが接続されている場合、<code>force</code>プロパティを設定しない限り、エラーがログに記録されます。設定された場合はブローカから切断され、新しい設定を適用して再接続します。</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="mqtt out">
|
||||
@ -39,15 +74,24 @@
|
||||
<dl class="message-properties">
|
||||
<dt>payload <span class="property-type">文字列 | バッファ</span></dt>
|
||||
<dd>発行するペイロード。プロパティが設定されていない場合には、メッセージは送信されません。空のメッセージを送信するには、プロパティに空文字列を設定します。</dd>
|
||||
|
||||
<dt class="optional">topic <span class="property-type">文字列</span></dt>
|
||||
<dd>発行対象のMQTTトピック</dd>
|
||||
|
||||
<dt class="optional">qos <span class="property-type">数値</span></dt>
|
||||
<dd>0: 最大一度到着, 1: 一度以上到着, 2: 一度のみ到着。デフォルトは0です。</dd>
|
||||
|
||||
<dt class="optional">retain <span class="property-type">真偽値</span></dt>
|
||||
<dd>真の場合、メッセージをブローカに保持します。デフォルトは偽です。</dd>
|
||||
<dt class="optional">responseTopic <span class="property-type">文字列</span></dt>
|
||||
<dd><b>MQTTv5</b>: メッセージのMQTT応答トピック</dd>
|
||||
<dt class="optional">correlationData <span class="property-type">バッファ</span></dt>
|
||||
<dd><b>MQTTv5</b>: メッセージの相関データ</dd>
|
||||
<dt class="optional">contentType <span class="property-type">文字列</span></dt>
|
||||
<dd><b>MQTTv5</b>: ペイロードのコンテントタイプ</dd>
|
||||
<dt class="optional">userProperties <span class="property-type">オブジェクト</span></dt>
|
||||
<dd><b>MQTTv5</b>: メッセージのユーザプロパティ</dd>
|
||||
<dt class="optional">messageExpiryInterval <span class="property-type">数値</span></dt>
|
||||
<dd><b>MQTTv5</b>: 秒単位のメッセージの有効期限</dd>
|
||||
<dt class="optional">topicAlias <span class="property-type">数値</span></dt>
|
||||
<dd><b>MQTTv5</b>: 使用するMQTTトピックエイリアス</dd>
|
||||
</dl>
|
||||
<h3>詳細</h3>
|
||||
<p><code>msg.payload</code>を発行するメッセージのペイロードとして用います。ペイロードがオブジェクトの場合、送信の際にJSON文字列に変換します。ペイロードがバイナリバッファの場合、そのまま送信します。</p>
|
||||
@ -55,6 +99,24 @@
|
||||
<p>同様に、QoSとretainもノードの設定、もしくは、ノードの設定が空の場合には、それぞれ<code>msg.qos</code>および<code>msg.retain</code>で指定できます。以前ブローカに保存したトピックをクリアするには、retainフラグを設定して当該トピックに空のメッセージを発行します。</p>
|
||||
<p>このノードの利用のためには、MQTTブローカへの接続設定が必要です。この設定は鉛筆アイコンをクリックすることで行えます。</p>
|
||||
<p>MQTT(inおよびout)ノードはブローカへの接続設定を必要に応じて共有できます。</p>
|
||||
|
||||
<h4>動的制御</h4>
|
||||
本ノードによって接続を動的に制御できます。本ノードが以下の制御メッセージのいずれかを受け取った際は、ペイロードと同じ様にパブリッシュされることはありません。
|
||||
<h3>入力</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>action <span class="property-type">文字列</span></dt>
|
||||
<dd>本ノードが行う動作の名前。利用可能な動作は<code>"connect"</code>、<code>"disconnect"</code>、<code>"subscribe"</code>、<code>"unsubscribe"</code>です。</dd>
|
||||
<dt class="optional">broker <span class="property-type">broker</span> </dt>
|
||||
<dd><code>"connect"</code>の動作に対して、本プロパティは次の様な個々のブローカ設定を上書きします: <ul>
|
||||
<li><code>broker</code></li>
|
||||
<li><code>port</code></li>
|
||||
<li><code>url</code> - 完全な接続URLを提供するために、brokerとportを上書き</li>
|
||||
<li><code>username</code></li>
|
||||
<li><code>password</code></li>
|
||||
</ul>
|
||||
<p>本プロパティが設定され既にブローカが接続されている場合、<code>force</code>プロパティを設定しない限り、エラーがログに記録されます。設定された場合はブローカから切断され、新しい設定を適用して再接続します。</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="mqtt-broker">
|
||||
@ -70,5 +132,4 @@
|
||||
<h4>WebSocket</h4>
|
||||
<p>WebSocketによる接続を行うように設定できます。WebSocketを利用するには、サーバフィールドに接続先のURIを完全な形式で記述します。以下に例を示します。</p>
|
||||
<pre>ws://example.com:4000/mqtt</pre>
|
||||
|
||||
</script>
|
||||
|
@ -36,7 +36,7 @@
|
||||
<h3>詳細</h3>
|
||||
<p>「列名」にカラム名のリストを指定することができます。CSVからオブジェクトに変換を行う際、カラム名をプロパティ名として使用します。「列名」の代わりに、CSVデータの1行目にカラム名を含めることもできます。</p>
|
||||
<p>CSVへの変換を行う際には、オブジェクトから取り出すべきプロパティとその順序を「列名」を参照して決めます。</p>
|
||||
<p>列名がない場合、本ノードは<code>msg.columns</code>プロパティの単純なコンマ区切りリストを使用して、何を抽出するかを決定します。もしそれが存在しない場合、すべてのオブジェクトプロパティを見つけた順序で出力します。</p>
|
||||
<p>列名がない場合、本ノードは<code>msg.columns</code>プロパティの単純なコンマ区切りリストを使用して、何をどの順序で抽出するかを決定します。もし存在しない場合、すべてのオブジェクトプロパティを見つけた順序で出力します。</p>
|
||||
<p>入力が配列の場合には、「列名」はカラム名を表す行の出力指定がされた場合だけ用います。</p>
|
||||
<p>「数値を変換する」オプションがチェックされている場合、文字列型の数値が数値として返されます。つまり「1,"1.5",2」の真ん中の値が数値になります。</p>
|
||||
<p>「空の文字を含む」オプションがチェックされている場合、空の文字列が結果に返されます。つまり「"1","",3」の真ん中の値が空の文字列になります。</p>
|
||||
|
@ -52,7 +52,6 @@
|
||||
<p>このモードで処理する際には、メッセージ数を予め知ることができないため、<code>msg.parts.count</code>プロパティは設定されません。従って、<b>join</b>ノードの「自動モード」と組み合わせることはできません。</p>
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/html" data-help-name="join">
|
||||
<p>メッセージ列を結合して一つのメッセージにします。</p>
|
||||
<p>メッセージの結合には次の3つのモードが利用できます。</p>
|
||||
@ -80,6 +79,10 @@
|
||||
</dd>
|
||||
<dt class="optional">complete</dt>
|
||||
<dd>設定されている場合、本ノードはペイロードを追加し、保持しているメッセージを送信します。ペイロードを追加したくない場合は、msgから削除してください。</dd>
|
||||
<dt class="optional">reset</dt>
|
||||
<dd>設定されている場合、本ノードは部分的に完成したメッセージを送信せず、削除します。</dd>
|
||||
<dt class="optional">restartTimeout</dt>
|
||||
<dd>設定されている場合、本ノードにタイムアウトが設定され、そのタイムアウトを用いて処理が再開されます。</dd>
|
||||
</dl>
|
||||
<h3>詳細</h3>
|
||||
|
||||
@ -96,7 +99,7 @@
|
||||
</ul>
|
||||
<p>出力メッセージのその他のプロパティはメッセージを送信する直前のメッセージをコピーします。</p>
|
||||
<p>「<i>合計値</i>」には出力メッセージを送信する前に受信すべきメッセージ数を指定します。オブジェクト出力の場合、この合計値に達すると後続メッセージの到着毎にメッセージを出力するように設定することもできます。</p>
|
||||
<p>「<i>秒</i>」には新規メッセージを送信するまでの経過時間を設定します。</p>
|
||||
<p>「<i>秒</i>」には新規メッセージを送信するまでの経過時間を設定します。<code>msg.restartTimeout</code>プロパティを設定したメッセージを渡すことで、指定した時間で再開できます。</p>
|
||||
<p><code>msg.complete</code>プロパティを設定したメッセージを受信すると、出力メッセージを送信します。この時、メッセージ列の数をリセットします。</p>
|
||||
<p><code>msg.reset</code>プロパティを設定したメッセージを受信すると、部分的に受信済みのメッセージを破棄します。これらのメッセージは送信されません。この時、メッセージ列の数をリセットします。</p>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/nodes",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.6",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -15,10 +15,10 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"acorn": "8.6.0",
|
||||
"acorn": "8.7.0",
|
||||
"acorn-walk": "8.2.0",
|
||||
"ajv": "8.8.2",
|
||||
"body-parser": "1.19.0",
|
||||
"body-parser": "1.19.1",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"content-type": "1.0.4",
|
||||
"cookie-parser": "1.4.6",
|
||||
@ -36,7 +36,7 @@
|
||||
"is-utf8": "0.2.1",
|
||||
"js-yaml": "3.14.1",
|
||||
"media-typer": "1.1.0",
|
||||
"mqtt": "4.2.8",
|
||||
"mqtt": "4.3.4",
|
||||
"multer": "1.4.3",
|
||||
"mustache": "4.2.0",
|
||||
"on-headers": "1.0.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/registry",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.6",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@ -16,11 +16,11 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "2.1.4",
|
||||
"@node-red/util": "2.1.6",
|
||||
"clone": "2.1.2",
|
||||
"fs-extra": "10.0.0",
|
||||
"semver": "7.3.5",
|
||||
"tar": "6.1.11",
|
||||
"uglify-js": "3.14.4"
|
||||
"uglify-js": "3.14.5"
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ function createNode(flow,config) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
Object.defineProperty(conf,'_module', {value: typeRegistry.getNodeInfo(type), enumerable: false, writable: true })
|
||||
Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true })
|
||||
newNode = new nodeTypeConstructor(conf);
|
||||
} catch (err) {
|
||||
|
@ -59,6 +59,9 @@ function Node(n) {
|
||||
// which we can tolerate as they are the same object.
|
||||
Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true })
|
||||
}
|
||||
if (n._module) {
|
||||
Object.defineProperty(this,'_module', {value: n._module, enumerable: false, writable: true })
|
||||
}
|
||||
this.updateWires(n.wires);
|
||||
}
|
||||
|
||||
@ -496,7 +499,12 @@ function log_helper(self, level, msg) {
|
||||
if (self.name) {
|
||||
o.name = self.name;
|
||||
}
|
||||
self._flow.log(o);
|
||||
// See https://github.com/node-red/node-red/issues/3327
|
||||
try {
|
||||
self._flow.log(o);
|
||||
} catch(err) {
|
||||
logUnexpectedError(self, err)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Log an INFO level message
|
||||
@ -576,4 +584,59 @@ Node.prototype.status = function(status) {
|
||||
this._flow.handleStatus(this,status);
|
||||
};
|
||||
|
||||
|
||||
function inspectObject(flow) {
|
||||
try {
|
||||
let properties = new Set()
|
||||
let currentObj = flow
|
||||
do {
|
||||
if (!Object.getPrototypeOf(currentObj)) { break }
|
||||
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
|
||||
} while ((currentObj = Object.getPrototypeOf(currentObj)))
|
||||
let propList = [...properties.keys()].map(item => `${item}[${(typeof flow[item])[0]}]`)
|
||||
propList.sort();
|
||||
let result = [];
|
||||
let line = "";
|
||||
while (propList.length > 0) {
|
||||
let prop = propList.shift()
|
||||
if (line.length+prop.length > 80) {
|
||||
result.push(line)
|
||||
line = "";
|
||||
} else {
|
||||
line += " "+prop
|
||||
}
|
||||
}
|
||||
if (line.length > 0) {
|
||||
result.push(line);
|
||||
}
|
||||
return result.join("\n ")
|
||||
|
||||
} catch(err) {
|
||||
return "Failed to capture object properties: "+err.toString()
|
||||
}
|
||||
}
|
||||
|
||||
function logUnexpectedError(node, error) {
|
||||
let moduleInfo = node._module?`${node._module.module}@${node._module.version}`:"undefined"
|
||||
Log.error(`
|
||||
********************************************************************
|
||||
Unexpected Node Error
|
||||
${error.stack}
|
||||
Node:
|
||||
Type: ${node.type}
|
||||
Module: ${moduleInfo}
|
||||
ID: ${node._alias||node.id}
|
||||
Properties:
|
||||
${inspectObject(node)}
|
||||
Flow: ${node._flow?node._flow.path:'undefined'}
|
||||
Type: ${node._flow?node._flow.TYPE:'undefined'}
|
||||
Properties:
|
||||
${node._flow?inspectObject(node._flow):'undefined'}
|
||||
|
||||
Please report this issue, including the information logged above:
|
||||
https://github.com/node-red/node-red/issues/
|
||||
********************************************************************
|
||||
`)
|
||||
}
|
||||
|
||||
module.exports = Node;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/runtime",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.6",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@ -16,11 +16,11 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/registry": "2.1.4",
|
||||
"@node-red/util": "2.1.4",
|
||||
"@node-red/registry": "2.1.6",
|
||||
"@node-red/util": "2.1.6",
|
||||
"async-mutex": "0.3.2",
|
||||
"clone": "2.1.2",
|
||||
"express": "4.17.1",
|
||||
"express": "4.17.2",
|
||||
"fs-extra": "10.0.0",
|
||||
"json-stringify-safe": "5.0.1"
|
||||
}
|
||||
|
@ -32,8 +32,14 @@ function wrapEventFunction(obj,func) {
|
||||
return function(eventName, listener) {
|
||||
if (deprecatedEvents.hasOwnProperty(eventName)) {
|
||||
const log = require("@node-red/util").log;
|
||||
const stack = (new Error().stack).split("\n")[2].split("(")[1].slice(0,-1);
|
||||
log.warn(`[RED.events] Deprecated use of "${eventName}" event from "${stack}". Use "${deprecatedEvents[eventName]}" instead.`)
|
||||
|
||||
const stack = (new Error().stack).split("\n");
|
||||
let location = "(unknown)"
|
||||
// See https://github.com/node-red/node-red/issues/3292
|
||||
if (stack.length > 2) {
|
||||
location = stack[2].split("(")[1].slice(0,-1);
|
||||
}
|
||||
log.warn(`[RED.events] Deprecated use of "${eventName}" event from "${location}". Use "${deprecatedEvents[eventName]}" instead.`)
|
||||
}
|
||||
return events["_"+func].call(events,eventName,listener)
|
||||
}
|
||||
|
@ -686,7 +686,7 @@ function prepareJSONataExpression(value,node) {
|
||||
return moment(arg1, arg2, arg3, arg4);
|
||||
});
|
||||
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
|
||||
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);
|
||||
expr._legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(value);
|
||||
expr._node = node;
|
||||
return expr;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/util",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.6",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -16,7 +16,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"fs-extra": "10.0.0",
|
||||
"i18next": "21.5.4",
|
||||
"i18next": "21.6.6",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"jsonata": "1.8.5",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
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.1.4",
|
||||
"version": "2.1.6",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@ -31,13 +31,13 @@
|
||||
"flow"
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/editor-api": "2.1.4",
|
||||
"@node-red/runtime": "2.1.4",
|
||||
"@node-red/util": "2.1.4",
|
||||
"@node-red/nodes": "2.1.4",
|
||||
"@node-red/editor-api": "2.1.6",
|
||||
"@node-red/runtime": "2.1.6",
|
||||
"@node-red/util": "2.1.6",
|
||||
"@node-red/nodes": "2.1.6",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"express": "4.17.1",
|
||||
"express": "4.17.2",
|
||||
"fs-extra": "10.0.0",
|
||||
"node-red-admin": "^2.2.1",
|
||||
"nopt": "5.0.0",
|
||||
|
539
test/nodes/core/function/rbe_spec.js
Normal file
539
test/nodes/core/function/rbe_spec.js
Normal file
@ -0,0 +1,539 @@
|
||||
|
||||
var should = require("should");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
|
||||
var testNode = require("nr-test-utils").require("@node-red/nodes/core/function/rbe.js");
|
||||
|
||||
describe('rbe node', function() {
|
||||
"use strict";
|
||||
|
||||
beforeEach(function(done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
helper.unload().then(function() {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be loaded with correct defaults", function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", "name":"rbe1", "wires":[[]]}];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
n1.should.have.property("name", "rbe1");
|
||||
n1.should.have.property("func", "rbe");
|
||||
n1.should.have.property("gap", "0");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if payload changes - with multiple topics (rbe)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"rbe", gap:"0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", 2);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 2) {
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.have.a.property("b",1);
|
||||
msg.payload.should.have.a.property("c",2);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 3) {
|
||||
msg.should.have.a.property("payload",true);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 4) {
|
||||
msg.should.have.a.property("payload",false);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 5) {
|
||||
msg.should.have.a.property("payload",true);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 6) {
|
||||
msg.should.have.a.property("topic","a");
|
||||
msg.should.have.a.property("payload",1);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 7) {
|
||||
msg.should.have.a.property("topic","b");
|
||||
msg.should.have.a.property("payload",1);
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
c += 1;
|
||||
msg.should.have.a.property("topic","c");
|
||||
msg.should.have.a.property("payload",1);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {payload:2});
|
||||
n1.emit("input", {payload:2});
|
||||
n1.emit("input", {payload:{b:1,c:2}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
n1.emit("input", {payload:true});
|
||||
n1.emit("input", {payload:false});
|
||||
n1.emit("input", {payload:false});
|
||||
n1.emit("input", {payload:true});
|
||||
|
||||
n1.emit("input", {topic:"a",payload:1});
|
||||
n1.emit("input", {topic:"b",payload:1});
|
||||
n1.emit("input", {topic:"b",payload:1});
|
||||
n1.emit("input", {topic:"a",payload:1});
|
||||
n1.emit("input", {topic:"c",payload:1});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore multiple topics if told to (rbe)', function(done) {
|
||||
var flow = [{id:"n1", type:"rbe", func:"rbe", gap:"0", septopics:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", 2);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 2) {
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.have.a.property("b",1);
|
||||
msg.payload.should.have.a.property("c",2);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 3) {
|
||||
msg.should.have.a.property("payload",true);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 4) {
|
||||
msg.should.have.a.property("payload",false);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 5) {
|
||||
msg.should.have.a.property("payload",true);
|
||||
c+=1;
|
||||
}
|
||||
else if (c == 6) {
|
||||
msg.should.have.a.property("topic","a");
|
||||
msg.should.have.a.property("payload",1);
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("topic","a");
|
||||
msg.should.have.a.property("payload",2);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {topic:"a",payload:"a"});
|
||||
n1.emit("input", {topic:"b",payload:"a"});
|
||||
n1.emit("input", {topic:"c",payload:"a"});
|
||||
n1.emit("input", {topic:"a",payload:2});
|
||||
n1.emit("input", {topic:"b",payload:2});
|
||||
n1.emit("input", {payload:{b:1,c:2}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
n1.emit("input", {topic:"a",payload:true});
|
||||
n1.emit("input", {topic:"b",payload:false});
|
||||
n1.emit("input", {topic:"c",payload:false});
|
||||
n1.emit("input", {topic:"d",payload:true});
|
||||
|
||||
n1.emit("input", {topic:"a",payload:1});
|
||||
n1.emit("input", {topic:"b",payload:1});
|
||||
n1.emit("input", {topic:"c",payload:1});
|
||||
n1.emit("input", {topic:"d",payload:1});
|
||||
n1.emit("input", {topic:"a",payload:2});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if another chosen property changes - foo (rbe)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"rbe", gap:"0", property:"foo", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("foo", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("foo", "b");
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("foo");
|
||||
msg.foo.should.have.a.property("b",1);
|
||||
msg.foo.should.have.a.property("c",2);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {foo:"a"});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {foo:"a"});
|
||||
n1.emit("input", {payload:"a"});
|
||||
n1.emit("input", {foo:"a"});
|
||||
n1.emit("input", {foo:"b"});
|
||||
n1.emit("input", {foo:{b:1,c:2}});
|
||||
n1.emit("input", {foo:{c:2,b:1}});
|
||||
n1.emit("input", {payload:{c:2,b:1}});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if payload changes - ignoring first value (rbei)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"rbei", gap:"0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
msg.should.have.a.property("topic", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
msg.should.have.a.property("topic", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", "c");
|
||||
msg.should.have.a.property("topic", "a");
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("payload", "c");
|
||||
msg.should.have.a.property("topic", "b");
|
||||
done();
|
||||
}
|
||||
|
||||
});
|
||||
n1.emit("input", {payload:"a", topic:"a"});
|
||||
n1.emit("input", {payload:"a", topic:"b"});
|
||||
n1.emit("input", {payload:"a", topic:"a"});
|
||||
n1.emit("input", {payload:"b", topic:"a"});
|
||||
n1.emit("input", {payload:"b", topic:"b"});
|
||||
n1.emit("input", {payload:"c", topic:"a"});
|
||||
n1.emit("input", {payload:"c", topic:"b"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send output if queue is reset (rbe)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"rbe", gap:"0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 3) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 4) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 5) {
|
||||
msg.should.have.a.property("payload", "b");
|
||||
c+=1;
|
||||
}
|
||||
else if (c === 6) {
|
||||
msg.should.have.a.property("payload", "a");
|
||||
c+=1;
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("payload", "c");
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {reset:true}); // reset all
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"b", reset:""}); // reset b
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {reset:""}); // reset all
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"c"}); // don't reset a non topic
|
||||
n1.emit("input", {topic:"b", payload:"b"});
|
||||
n1.emit("input", {topic:"a", payload:"a"});
|
||||
n1.emit("input", {topic:"c", payload:"c"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if x away from original value (deadbandEq)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"deadbandEq", gap:"10", inout:"out", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c = c + 1;
|
||||
if (c === 1) {
|
||||
msg.should.have.a.property("payload", 0);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", 10);
|
||||
}
|
||||
else if (c == 3) {
|
||||
msg.should.have.a.property("payload", 20);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:2});
|
||||
n1.emit("input", {payload:4});
|
||||
n1.emit("input", {payload:6});
|
||||
n1.emit("input", {payload:8});
|
||||
n1.emit("input", {payload:10});
|
||||
n1.emit("input", {payload:15});
|
||||
n1.emit("input", {payload:20});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if more than x away from original value (deadband)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"deadband", gap:"10", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c = c + 1;
|
||||
//console.log(c,msg);
|
||||
if (c === 1) {
|
||||
msg.should.have.a.property("payload", 0);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", 20);
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("payload", "5 deg");
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:2});
|
||||
n1.emit("input", {payload:4});
|
||||
n1.emit("input", {payload:"6 deg"});
|
||||
n1.emit("input", {payload:8});
|
||||
n1.emit("input", {payload:20});
|
||||
n1.emit("input", {payload:15});
|
||||
n1.emit("input", {payload:"5 deg"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only send output if more than x% away from original value (deadband)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"deadband", gap:"10%", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c = c + 1;
|
||||
if (c === 1) {
|
||||
msg.should.have.a.property("payload", 100);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", 111);
|
||||
}
|
||||
else if (c === 3) {
|
||||
msg.should.have.a.property("payload", 135);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:100});
|
||||
n1.emit("input", {payload:95});
|
||||
n1.emit("input", {payload:105});
|
||||
n1.emit("input", {payload:111});
|
||||
n1.emit("input", {payload:120});
|
||||
n1.emit("input", {payload:135});
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn if no number found in deadband mode', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"deadband", gap:"10", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c += 1;
|
||||
});
|
||||
setTimeout( function() {
|
||||
c.should.equal(0);
|
||||
helper.log().called.should.be.true;
|
||||
var logEvents = helper.log().args.filter(function (evt) {
|
||||
return evt[0].type == "rbe";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
var msg = logEvents[0][0];
|
||||
msg.should.have.property('level', helper.log().WARN);
|
||||
msg.should.have.property('id', 'n1');
|
||||
msg.should.have.property('type', 'rbe');
|
||||
msg.should.have.property('msg', 'rbe.warn.nonumber');
|
||||
done();
|
||||
},50);
|
||||
n1.emit("input", {payload:"banana"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send output if x away or greater from original value (narrowbandEq)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"narrowbandEq", gap:"10", inout:"out", start:"1", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
c = c + 1;
|
||||
//console.log(c,msg);
|
||||
if (c === 1) {
|
||||
msg.should.have.a.property("payload", 0);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.a.property("payload", 5);
|
||||
}
|
||||
else if (c === 3) {
|
||||
msg.should.have.a.property("payload", 10);
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.emit("input", {payload:100});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:10});
|
||||
n1.emit("input", {payload:5});
|
||||
n1.emit("input", {payload:15});
|
||||
n1.emit("input", {payload:10});
|
||||
n1.emit("input", {payload:20});
|
||||
n1.emit("input", {payload:25});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send output if more than x away from original value (narrowband)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"narrowband", gap:"10", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", 0);
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload","6 deg");
|
||||
}
|
||||
else {
|
||||
msg.should.have.a.property("payload", "5 deg");
|
||||
done();
|
||||
}
|
||||
c += 1;
|
||||
});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:20});
|
||||
n1.emit("input", {payload:40});
|
||||
n1.emit("input", {payload:"6 deg"});
|
||||
n1.emit("input", {payload:18});
|
||||
n1.emit("input", {payload:20});
|
||||
n1.emit("input", {payload:50});
|
||||
n1.emit("input", {payload:"5 deg"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send output if gap is 0 and input doesnt change (narrowband)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"narrowband", gap:"0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", 1);
|
||||
}
|
||||
else if (c === 4) {
|
||||
msg.should.have.a.property("payload",1);
|
||||
done();
|
||||
}
|
||||
c += 1;
|
||||
});
|
||||
n1.emit("input", {payload:1});
|
||||
n1.emit("input", {payload:1});
|
||||
n1.emit("input", {payload:1});
|
||||
n1.emit("input", {payload:1});
|
||||
n1.emit("input", {payload:0});
|
||||
n1.emit("input", {payload:1});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send output if more than x away from original value (narrowband in step mode)', function(done) {
|
||||
var flow = [{"id":"n1", "type":"rbe", func:"narrowband", gap:"10", inout:"in", start:"500", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
var c = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if (c === 0) {
|
||||
msg.should.have.a.property("payload", 55);
|
||||
}
|
||||
else if (c === 1) {
|
||||
msg.should.have.a.property("payload", 205);
|
||||
done();
|
||||
}
|
||||
c += 1;
|
||||
});
|
||||
n1.emit("input", {payload:50});
|
||||
n1.emit("input", {payload:55});
|
||||
n1.emit("input", {payload:200});
|
||||
n1.emit("input", {payload:205});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user