1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'dev' into dev

This commit is contained in:
Stefan Kleeschulte 2019-11-26 21:34:27 +01:00 committed by GitHub
commit 422ed371f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 841 additions and 360 deletions

View File

@ -1,3 +1,39 @@
#### 1.0.3: Maintenance Release
Runtime
- Increase timeouts in Subflow tests to minimise false positives
- Update grunt-sass and add node-sass for node12 support
- Fix timings of Delay node tests
- #2340 Update JSONata to 1.7.0
- Bump https-proxy-agent version
- #2332 Fix error handling of nodes with multiple input handlers
- Add script to generate npm publish script
- #2371 Ensure folder is present before write (e.g. flows file not in user folder)
- #2371 Handle windows UNC '\\' paths
- #2366 Handle logging of non-JSON encodable objects
Editor
- #2328 Fix language handling in subflow node
- Use default language if lng param not set in i18n req
- #2326 Fix palette editor search visualization
- #2375 Subflow status not showing i18n version of contained core nodes status
- Fix inverse of 'replace' editor event
- #2376 Fallback to base language files if present
- #2373 Support UI testing on the latest Google Chrome
- #2364 Add tooltip to expand button in markdown editor
- #2363 Support ctrl key to select tabs for Windows
- #2356 Make JSONata help initially shown in expression editor
- #2355 Prohibit line break in type menu of typedInput
Nodes
- Delay: Fix delay to not pass through .reset and .flush props consistently
- #2352 File: Using the a msg per line the last line does not get msg.topic passed
- #2339 HTTP Request: Check auth type on opening
- HTTP Request: add units info
- #2372 MQTT/WS: Improved proxy support for MQTT and WebSocket nodes
- #2370 MQTT: Add clarification that MQTT Out requires payload to send msg
#### 1.0.2: Maintenance Release #### 1.0.2: Maintenance Release
Runtime Runtime

View File

@ -16,6 +16,7 @@
var path = require("path"); var path = require("path");
var fs = require("fs-extra"); var fs = require("fs-extra");
var sass = require("node-sass");
module.exports = function(grunt) { module.exports = function(grunt) {
@ -220,6 +221,7 @@ module.exports = function(grunt) {
sass: { sass: {
build: { build: {
options: { options: {
implementation: sass,
outputStyle: 'compressed' outputStyle: 'compressed'
}, },
files: [{ files: [{

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "1.0.2", "version": "1.0.3",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -34,20 +34,20 @@
"cookie": "0.4.0", "cookie": "0.4.0",
"cookie-parser": "1.4.4", "cookie-parser": "1.4.4",
"cors": "2.8.5", "cors": "2.8.5",
"cron": "1.7.1", "cron": "1.7.2",
"denque": "1.4.1", "denque": "1.4.1",
"express": "4.17.1", "express": "4.17.1",
"express-session": "1.16.2", "express-session": "1.17.0",
"fs-extra": "8.1.0", "fs-extra": "8.1.0",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"hash-sum": "2.0.0", "hash-sum": "2.0.0",
"https-proxy-agent": "2.2.2", "https-proxy-agent": "2.2.4",
"i18next": "15.1.2", "i18next": "15.1.2",
"iconv-lite": "0.5.0", "iconv-lite": "0.5.0",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.6.5", "jsonata": "1.7.0",
"media-typer": "1.1.0", "media-typer": "1.1.0",
"memorystore": "1.6.1", "memorystore": "1.6.1",
"mime": "2.4.4", "mime": "2.4.4",
@ -66,10 +66,10 @@
"raw-body": "2.4.1", "raw-body": "2.4.1",
"request": "2.88.0", "request": "2.88.0",
"semver": "6.3.0", "semver": "6.3.0",
"uglify-js": "3.6.0", "uglify-js": "3.6.9",
"when": "3.7.8", "when": "3.7.8",
"ws": "6.2.1", "ws": "6.2.1",
"xml2js": "0.4.19" "xml2js": "0.4.22"
}, },
"optionalDependencies": { "optionalDependencies": {
"bcrypt": "3.0.6" "bcrypt": "3.0.6"
@ -93,19 +93,20 @@
"grunt-mocha-istanbul": "5.0.2", "grunt-mocha-istanbul": "5.0.2",
"grunt-nodemon": "~0.4.2", "grunt-nodemon": "~0.4.2",
"grunt-npm-command": "~0.1.2", "grunt-npm-command": "~0.1.2",
"grunt-sass": "~2.0.0", "grunt-sass": "~3.1.0",
"grunt-simple-mocha": "~0.4.1", "grunt-simple-mocha": "~0.4.1",
"http-proxy": "^1.16.2", "http-proxy": "1.18.0",
"istanbul": "0.4.5", "istanbul": "0.4.5",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"minami": "1.2.3", "minami": "1.2.3",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mosca": "^2.8.3", "mosca": "^2.8.3",
"node-red-node-test-helper": "^0.2.3",
"node-sass": "^4.13.0",
"should": "^8.4.0", "should": "^8.4.0",
"sinon": "1.17.7", "sinon": "1.17.7",
"stoppable": "^1.1.0", "stoppable": "^1.1.0",
"supertest": "3.4.2", "supertest": "3.4.2"
"node-red-node-test-helper": "^0.2.3",
"jsdoc-nr-template": "node-red/jsdoc-nr-template"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"

View File

@ -44,6 +44,7 @@ module.exports = {
user: req.user, user: req.user,
module: req.body.module, module: req.body.module,
version: req.body.version, version: req.body.version,
url: req.body.url,
req: apiUtils.getRequestLogObject(req) req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.addModule(opts).then(function(info) { runtimeAPI.nodes.addModule(opts).then(function(info) {

View File

@ -15,7 +15,7 @@
**/ **/
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
//var apiUtil = require('../util'); // var apiUtil = require('../util');
var i18n = require("@node-red/util").i18n; // TODO: separate module var i18n = require("@node-red/util").i18n; // TODO: separate module
@ -41,7 +41,7 @@ module.exports = {
var namespace = req.params[0]; var namespace = req.params[0];
var lngs = req.query.lng; var lngs = req.query.lng;
namespace = namespace.replace(/\.json$/,""); namespace = namespace.replace(/\.json$/,"");
var lang = req.query.lng; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []); var lang = req.query.lng || i18n.defaultLang; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
var prevLang = i18n.i.language; var prevLang = i18n.i.language;
// Trigger a load from disk of the language if it is not the default // Trigger a load from disk of the language if it is not the default
i18n.i.changeLanguage(lang, function(){ i18n.i.changeLanguage(lang, function(){

View File

@ -42,7 +42,7 @@ var editor;
/** /**
* Initialise the module. * Initialise the module.
* @param {Object} settings The runtime settings * @param {Object} settings The runtime settings
* @param {HTTPServer} server An instance of HTTP Server * @param {HTTPServer} _server An instance of HTTP Server
* @param {Storage} storage An instance of Node-RED Storage * @param {Storage} storage An instance of Node-RED Storage
* @param {Runtime} runtimeAPI An instance of Node-RED Runtime * @param {Runtime} runtimeAPI An instance of Node-RED Runtime
* @memberof @node-red/editor-api * @memberof @node-red/editor-api

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "1.0.2", "version": "1.0.3",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,13 +16,13 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "1.0.2", "@node-red/util": "1.0.3",
"@node-red/editor-client": "1.0.2", "@node-red/editor-client": "1.0.3",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.19.0", "body-parser": "1.19.0",
"clone": "2.1.2", "clone": "2.1.2",
"cors": "2.8.5", "cors": "2.8.5",
"express-session": "1.16.2", "express-session": "1.17.0",
"express": "4.17.1", "express": "4.17.1",
"memorystore": "1.6.1", "memorystore": "1.6.1",
"mime": "2.4.4", "mime": "2.4.4",

View File

@ -15,6 +15,17 @@
"next": "Next", "next": "Next",
"clone": "Clone project", "clone": "Clone project",
"cont": "Continue" "cont": "Continue"
},
"type": {
"string": "string",
"number": "number",
"boolean": "boolean",
"array": "array",
"buffer": "buffer",
"object": "object",
"jsonString": "JSON string",
"undefined": "undefined",
"null": "null"
} }
}, },
"workspace": { "workspace": {
@ -792,10 +803,14 @@
"copyPath": "Copy path to item", "copyPath": "Copy path to item",
"expandItems": "Expand items", "expandItems": "Expand items",
"collapseItems": "Collapse items", "collapseItems": "Collapse items",
"duplicate": "Duplicate" "duplicate": "Duplicate",
"error": {
"invalidJSON": "Invalid JSON: "
}
}, },
"markdownEditor": { "markdownEditor": {
"title": "Markdown editor", "title": "Markdown editor",
"expand": "Expand",
"format": "Formatted with markdown", "format": "Formatted with markdown",
"heading1": "Heading 1", "heading1": "Heading 1",
"heading2": "Heading 2", "heading2": "Heading 2",

View File

@ -1,7 +1,7 @@
{ {
"$string": { "$string": {
"args": "arg", "args": "arg[, prettify]",
"desc": "Casts the *arg* parameter to a string using the following casting rules:\n\n - Strings are unchanged\n - Functions are converted to an empty string\n - Numeric infinity and NaN throw an error because they cannot be represented as a JSON number\n - All other values are converted to a JSON string using the `JSON.stringify` function" "desc": "Casts the `arg` parameter to a string using the following casting rules:\n\n - Strings are unchanged\n - Functions are converted to an empty string\n - Numeric infinity and NaN throw an error because they cannot be represented as a JSON number\n - All other values are converted to a JSON string using the `JSON.stringify` function. If `prettify` is true, then \"prettified\" JSON is produced. i.e One line per field and lines will be indented based on the field depth."
}, },
"$length": { "$length": {
"args": "str", "args": "str",
@ -185,7 +185,7 @@
}, },
"$reduce": { "$reduce": {
"args":"array, function [, init]", "args":"array, function [, init]",
"desc":"Returns an aggregated value derived from applying the `function` parameter successively to each value in `array` in combination with the result of the previous application of the function.\n\nThe function must accept two arguments, and behaves like an infix operator between each value within the `array`.\n\nThe optional `init` parameter is used as the initial value in the aggregation." "desc":"Returns an aggregated value derived from applying the `function` parameter successively to each value in `array` in combination with the result of the previous application of the function.\n\nThe function must accept two arguments, and behaves like an infix operator between each value within the `array`. The signature of `function` must be of the form: `myfunc($accumulator, $value[, $index[, $array]])`\n\nThe optional `init` parameter is used as the initial value in the aggregation."
}, },
"$flowContext": { "$flowContext": {
"args": "string[, string]", "args": "string[, string]",
@ -230,6 +230,37 @@
"$parseInteger": { "$parseInteger": {
"args": "string, picture", "args": "string, picture",
"desc": "Parses the contents of the `string` parameter to an integer (as a JSON number) using the format specified by the `picture` string. The `picture` string parameter has the same format as `$formatInteger`." "desc": "Parses the contents of the `string` parameter to an integer (as a JSON number) using the format specified by the `picture` string. The `picture` string parameter has the same format as `$formatInteger`."
},
"$error": {
"args": "[str]",
"desc": "Throws an error with a message. The optional `str` will replace the default message of `$error() function evaluated`"
},
"$assert": {
"args": "arg, str",
"desc": "If `arg` is true the function returns undefined. If `arg` is false an exception is thrown with `str` as the message of the exception."
},
"$single": {
"args": "array, function",
"desc": "Returns the one and only value in the `array` parameter that satisfies the `function` predicate (i.e. the `function` returns Boolean `true` when passed the value). Throws an exception if the number of matching values is not exactly one.\n\nThe function should be supplied in the following signature: `function(value [, index [, array]])` where value is each input of the array, index is the position of that value and the whole array is passed as the third argument"
},
"$encodeUrl": {
"args": "str",
"desc": "Encodes a Uniform Resource Locator (URL) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character.\n\nExample: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "Encodes a Uniform Resource Locator (URL) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character. \n\nExample: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "Decodes a Uniform Resource Locator (URL) component previously created by encodeUrlComponent. \n\nExample: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "Decodes a Uniform Resource Locator (URL) previously created by encodeUrl. \n\nExample: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "Returns an array with duplicate values removed from `array`"
} }
} }

View File

@ -15,6 +15,17 @@
"next": "進む", "next": "進む",
"clone": "プロジェクトをクローン", "clone": "プロジェクトをクローン",
"cont": "続ける" "cont": "続ける"
},
"type": {
"string": "文字列",
"number": "数値",
"boolean": "真偽値",
"array": "配列",
"buffer": "バッファ",
"object": "オブジェクト",
"jsonString": "JSON文字列",
"undefined": "undefined",
"null": "null"
} }
}, },
"workspace": { "workspace": {
@ -791,10 +802,14 @@
"copyPath": "要素のパスをコピー", "copyPath": "要素のパスをコピー",
"expandItems": "要素を展開", "expandItems": "要素を展開",
"collapseItems": "要素を折り畳む", "collapseItems": "要素を折り畳む",
"duplicate": "複製" "duplicate": "複製",
"error": {
"invalidJSON": "不正なJSON: "
}
}, },
"markdownEditor": { "markdownEditor": {
"title": "マークダウンエディタ", "title": "マークダウンエディタ",
"expand": "拡大",
"format": "マークダウン形式で記述", "format": "マークダウン形式で記述",
"heading1": "見出しレベル1", "heading1": "見出しレベル1",
"heading2": "見出しレベル2", "heading2": "見出しレベル2",

View File

@ -1,7 +1,7 @@
{ {
"$string": { "$string": {
"args": "arg", "args": "arg[, prettify]",
"desc": "以下の型変換ルールを用いて、引数 *arg* を文字列へ型変換します。:\n\n - 文字列は変換しません。\n - 関数は空の文字列に変換します。\n - JSONの数値として表現できないため、無限大やNaNはエラーになります。\n - 他の値は `JSON.stringify` 関数を用いて、JSONの文字列へ変換します。" "desc": "以下の型変換ルールを用いて、引数 *arg* を文字列へ型変換します。:\n\n - 文字列は変換しません。\n - 関数は空の文字列に変換します。\n - JSONの数値として表現できないため、無限大やNaNはエラーになります。\n - 他の値は `JSON.stringify` 関数を用いて、JSONの文字列へ変換します。`prettify`が真の場合、JSONを整形出力します。フィールドを1行毎に出力。フィールドのネスト深さによってインデントを行います。"
}, },
"$length": { "$length": {
"args": "str", "args": "str",
@ -185,7 +185,7 @@
}, },
"$reduce": { "$reduce": {
"args": "array, function [, init]", "args": "array, function [, init]",
"desc": "配列の各要素値に関数 `function` を連続的に適用して得られる集約値を返します。 `function` の適用の際には、直前の `function` の適用結果と要素値が引数として与えられます。\n\n関数 `function` は引数を2つ取り、配列の各要素の間に配置する中置演算子のように作用しなくてはなりません。\n\n任意の引数 `init` には、集約時の初期値を設定します。" "desc": "配列の各要素値に関数 `function` を連続的に適用して得られる集約値を返します。 `function` の適用の際には、直前の `function` の適用結果と要素値が引数として与えられます。\n\n関数 `function` は引数を2つ取り、配列の各要素の間に配置する中置演算子のように作用しなくてはなりません。関数`function`のシグネチャは`myfunc($accumulator, $value[, $index[, $array]])`という形式でなければなりません。\n\n任意の引数 `init` には、集約時の初期値を設定します。"
}, },
"$flowContext": { "$flowContext": {
"args": "string", "args": "string",
@ -230,5 +230,37 @@
"$parseInteger": { "$parseInteger": {
"args": "string, picture", "args": "string, picture",
"desc": "`picture`文字列の指定に従って、`string`パラメータを整数(JSON数値)に変換します。`picture`文字列は`$formatInteger`と同じ形式です。" "desc": "`picture`文字列の指定に従って、`string`パラメータを整数(JSON数値)に変換します。`picture`文字列は`$formatInteger`と同じ形式です。"
},
"$error": {
"args": "[str]",
"desc": "メッセージを指定して例外を送出します。メッセージ`str`を省略した場合は`$error() function evaluated`をメッセージとします。"
},
"$assert": {
"args": "arg, str",
"desc": "`arg`が真の場合、undefinedを返します。偽の場合、`str`をメッセージとする例外を送出します。"
},
"$single": {
"args": "array, function",
"desc": "`array`の要素のうち、条件判定関数`function`を満たす(`function`に与えた場合に真偽値`true`を返す)要素が1つのみである場合、それを返します。マッチする要素が1つのみでない場合、例外を送出します。\n\n指定する関数は`function(value [, index [, array]])`というシグネチャでなければなりません。ここで、`value`は`array`の要素値、`index`は要素の添字、第三引数には配列全体を渡します。"
},
"$encodeUrl": {
"args": "str",
"desc": "Uniform Resource Locator (URL)を構成する文字を1、、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "Uniform Resource Locator (URL)要素を構成する文字を1、、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "encodeUrlComponentで置換したUniform Resource Locator (URL)をデコードします。\n\n例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "encodeUrlで置換したUniform Resource Locator (URL)要素をデコードします。 \n\n例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "配列`array`から重複要素を削除した配列を返します。"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-client", "name": "@node-red/editor-client",
"version": "1.0.2", "version": "1.0.3",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -39,7 +39,7 @@ RED.history = (function() {
inverseEv = { inverseEv = {
t: 'replace', t: 'replace',
config: RED.nodes.createCompleteNodeSet(), config: RED.nodes.createCompleteNodeSet(),
changed: [], changed: {},
rev: RED.nodes.version() rev: RED.nodes.version()
}; };
RED.nodes.clear(); RED.nodes.clear();

View File

@ -845,7 +845,7 @@ RED.nodes = (function() {
var m = /^subflow:(.+)$/.exec(newNodes[i].type); var m = /^subflow:(.+)$/.exec(newNodes[i].type);
if (m) { if (m) {
var subflowId = m[1]; var subflowId = m[1];
var parent = getSubflow(newNodes[i].z || activeWorkspace); var parent = getSubflow(activeWorkspace);
if (parent) { if (parent) {
var err; var err;
if (subflowId === parent.id) { if (subflowId === parent.id) {

View File

@ -218,7 +218,7 @@ RED.tabs = (function() {
var thisTab = $(this).parent(); var thisTab = $(this).parent();
var fireSelectionChanged = false; var fireSelectionChanged = false;
if (options.onselect) { if (options.onselect) {
if (evt.metaKey) { if (evt.metaKey || evt.ctrlKey) {
if (thisTab.hasClass("selected")) { if (thisTab.hasClass("selected")) {
thisTab.removeClass("selected"); thisTab.removeClass("selected");
if (thisTab[0] !== currentTab[0]) { if (thisTab[0] !== currentTab[0]) {

View File

@ -2484,7 +2484,7 @@ RED.editor = (function() {
editor.toolbar = customEditTypes['_markdown'].buildToolbar(toolbarRow,editor); editor.toolbar = customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
if (options.expandable !== false) { if (options.expandable !== false) {
var expandButton = $('<button type="button" class="red-ui-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(editor.toolbar); var expandButton = $('<button type="button" class="red-ui-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(editor.toolbar);
RED.popover.tooltip(expandButton, RED._("markdownEditor.expand"));
expandButton.on("click", function(e) { expandButton.on("click", function(e) {
e.preventDefault(); e.preventDefault();
var value = editor.getValue(); var value = editor.getValue();
@ -2558,7 +2558,7 @@ RED.editor = (function() {
/** /**
* Register a type editor. * Register a type editor.
* @param {string} type - the type name * @param {string} type - the type name
* @param {object} options - the editor definition * @param {object} definition - the editor definition
* @function * @function
* @memberof RED.editor * @memberof RED.editor
*/ */

View File

@ -207,6 +207,7 @@
} }
expressionEditor.getSession().setValue(v||"",-1); expressionEditor.getSession().setValue(v||"",-1);
}); });
funcSelect.change();
var tabs = RED.tabs.create({ var tabs = RED.tabs.create({
element: $("#red-ui-editor-type-expression-tabs"), element: $("#red-ui-editor-type-expression-tabs"),

View File

@ -302,8 +302,8 @@
types:[ types:[
'str','num','bool', 'str','num','bool',
{value:"null",label:"null",hasValue:false}, {value:"null",label:"null",hasValue:false},
{value:"array",label:"array",hasValue:false}, {value:"array",label:RED._("common.type.array"),hasValue:false},
{value:"object",label:"object",hasValue:false} {value:"object",label:RED._("common.type.object"),hasValue:false}
], ],
default: valType default: valType
}); });
@ -577,7 +577,7 @@
} catch(err) { } catch(err) {
rootNode = null; rootNode = null;
list.treeList('data',[{ list.treeList('data',[{
label: "Invalid JSON: "+err.toString() label: RED._("jsonEditor.error.invalidJSON")+err.toString()
}]); }]);
} }
} }

View File

@ -75,13 +75,16 @@ RED.palette.editor = (function() {
}); });
}) })
} }
function installNodeModule(id,version,callback) { function installNodeModule(id,version,url,callback) {
var requestBody = { var requestBody = {
module: id module: id
}; };
if (version) { if (version) {
requestBody.version = version; requestBody.version = version;
} }
if (url) {
requestBody.url = url;
}
$.ajax({ $.ajax({
url:"nodes", url:"nodes",
type: "POST", type: "POST",
@ -622,7 +625,7 @@ RED.palette.editor = (function() {
if ($(this).hasClass('disabled')) { if ($(this).hasClass('disabled')) {
return; return;
} }
update(entry,loadedIndex[entry.name].version,container,function(err){}); update(entry,loadedIndex[entry.name].version,loadedIndex[entry.name].pkg_url,container,function(err){});
}) })
@ -788,7 +791,7 @@ RED.palette.editor = (function() {
initInstallTab(); initInstallTab();
}) })
packageList = $('<ol>',{style:"position: absolute;top: 78px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({ packageList = $('<ol>',{style:"position: absolute;top: 79px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({
addButton: false, addButton: false,
scrollOnAdd: false, scrollOnAdd: false,
addItem: function(container,i,object) { addItem: function(container,i,object) {
@ -872,7 +875,7 @@ RED.palette.editor = (function() {
$('<div id="red-ui-palette-module-install-shade" class="red-ui-palette-module-shade hide"><div class="red-ui-palette-module-shade-status"></div><img src="red/images/spin.svg" class="red-ui-palette-spinner"/></div>').appendTo(installTab); $('<div id="red-ui-palette-module-install-shade" class="red-ui-palette-module-shade hide"><div class="red-ui-palette-module-shade-status"></div><img src="red/images/spin.svg" class="red-ui-palette-spinner"/></div>').appendTo(installTab);
} }
function update(entry,version,container,done) { function update(entry,version,url,container,done) {
if (RED.settings.theme('palette.editable') === false) { if (RED.settings.theme('palette.editable') === false) {
done(new Error('Palette not editable')); done(new Error('Palette not editable'));
return; return;
@ -898,7 +901,7 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log"); RED.actions.invoke("core:show-event-log");
}); });
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version); RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version);
installNodeModule(entry.name,version,function(xhr) { installNodeModule(entry.name,version,url,function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { if (xhr.responseJSON) {
@ -1023,7 +1026,7 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log"); RED.actions.invoke("core:show-event-log");
}); });
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version); RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
installNodeModule(entry.id,entry.version,function(xhr) { installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { if (xhr.responseJSON) {

View File

@ -19,12 +19,18 @@ RED.subflow = (function() {
var currentLocale = "en-US"; var currentLocale = "en-US";
var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow">'+ var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow">'+
'<div class="form-row"><label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label><input type="text" id="node-input-name"></div>'+ '<div class="form-row">'+
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
'<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
'</div>'+
'<div id="subflow-input-ui"></div>'+ '<div id="subflow-input-ui"></div>'+
'</script>'; '</script>';
var _subflowTemplateEditTemplate = '<script type="text/x-red" data-template-name="subflow-template">'+ var _subflowTemplateEditTemplate = '<script type="text/x-red" data-template-name="subflow-template">'+
'<div class="form-row"><label for="subflow-input-name" data-i18n="[append]common.label.name"><i class="fa fa-tag"></i> </label><input type="text" id="subflow-input-name"></div>'+ '<div class="form-row">'+
'<label for="subflow-input-name" data-i18n="[append]common.label.name"><i class="fa fa-tag"></i> </label>'+
'<input type="text" id="subflow-input-name" data-i18n="[placeholder]common.label.name">'+
'</div>'+
'<div class="form-row">'+ '<div class="form-row">'+
'<ul style="margin-bottom: 20px;" id="subflow-env-tabs"></ul>'+ '<ul style="margin-bottom: 20px;" id="subflow-env-tabs"></ul>'+
'</div>'+ '</div>'+
@ -802,8 +808,8 @@ RED.subflow = (function() {
} }
$("<option/>", opt).text(item.text).appendTo(locales); $("<option/>", opt).text(item.text).appendTo(locales);
}); });
currentLocale = RED.i18n.lang(); var locale = RED.i18n.lang();
locales.val(currentLocale); locales.val(locale);
locales.on("change", function() { locales.on("change", function() {
currentLocale = $(this).val(); currentLocale = $(this).val();
@ -1048,7 +1054,7 @@ RED.subflow = (function() {
} }
langs.forEach(function(l) { langs.forEach(function(l) {
var row = $('<div>').appendTo(content); var row = $('<div>').appendTo(content);
$('<span>').css({display:"inline-block",width:"50px"}).text(l+(l===currentLocale?"*":"")).appendTo(row); $('<span>').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row);
$('<span>').text(ui.label[l]||"").appendTo(row); $('<span>').text(ui.label[l]||"").appendTo(row);
}); });
return content; return content;
@ -1371,7 +1377,8 @@ RED.subflow = (function() {
} }
var labels = ui.label || {}; var labels = ui.label || {};
var labelText = lookupLabel(labels, labels["en-US"]||tenv.name, currentLocale); var locale = RED.i18n.lang();
var labelText = lookupLabel(labels, labels["en-US"]||tenv.name, locale);
var label = $('<label>').appendTo(row); var label = $('<label>').appendTo(row);
var labelContainer = $('<span></span>').appendTo(label); var labelContainer = $('<span></span>').appendTo(label);
if (ui.icon) { if (ui.icon) {
@ -1423,7 +1430,7 @@ RED.subflow = (function() {
input = $('<select>').css('width','70%').appendTo(row); input = $('<select>').css('width','70%').appendTo(row);
if (ui.opts.opts) { if (ui.opts.opts) {
ui.opts.opts.forEach(function(o) { ui.opts.opts.forEach(function(o) {
$('<option>').val(o.v).text(lookupLabel(o.l, o.l['en-US']||o.v, currentLocale)).appendTo(input); $('<option>').val(o.v).text(lookupLabel(o.l, o.l['en-US']||o.v, locale)).appendTo(input);
}) })
} }
input.val(val.value); input.val(val.value);

View File

@ -164,7 +164,7 @@ RED.workspaces = (function() {
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody); var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
$('<div class="form-row">'+ $('<div class="form-row">'+
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+ '<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
'<input type="text" id="node-input-name">'+ '<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
'</div>').appendTo(dialogForm); '</div>').appendTo(dialogForm);

View File

@ -70,6 +70,7 @@
border: 1px solid $primary-border-color; border: 1px solid $primary-border-color;
box-sizing: border-box; box-sizing: border-box;
background: $secondary-background; background: $secondary-background;
white-space: nowrap;
z-index: 2000; z-index: 2000;
a { a {
padding: 6px 18px 6px 6px; padding: 6px 18px 6px 6px;

View File

@ -109,6 +109,7 @@
{ {
'$abs':{ args:[ 'number' ]}, '$abs':{ args:[ 'number' ]},
'$append':{ args:[ 'array1', 'array2' ]}, '$append':{ args:[ 'array1', 'array2' ]},
'$assert':{ args: [ 'arg', 'str' ]},
'$average':{ args:[ 'array' ]}, '$average':{ args:[ 'array' ]},
'$base64decode':{ args:[ ]}, '$base64decode':{ args:[ ]},
'$base64encode':{ args:[ ]}, '$base64encode':{ args:[ ]},
@ -116,8 +117,14 @@
'$ceil':{ args:[ 'number' ]}, '$ceil':{ args:[ 'number' ]},
'$contains':{ args:[ 'str', 'pattern' ]}, '$contains':{ args:[ 'str', 'pattern' ]},
'$count':{ args:[ 'array' ]}, '$count':{ args:[ 'array' ]},
'$decodeUrl':{ args:[ 'str' ]},
'$decodeUrlComponent':{ args:[ 'str' ]},
'$distinct':{ args:[ 'array' ]},
'$each':{ args:[ 'object', 'function' ]}, '$each':{ args:[ 'object', 'function' ]},
'$encodeUrl':{ args: ['str'] },
'$encodeUrlComponent':{ args:[ 'str' ]},
'$env': { args:[ 'arg' ]}, '$env': { args:[ 'arg' ]},
'$error':{ args:[ 'str' ]},
'$eval': { args: ['expr', 'context']}, '$eval': { args: ['expr', 'context']},
'$exists':{ args:[ 'arg' ]}, '$exists':{ args:[ 'arg' ]},
'$filter':{ args:[ 'array', 'function' ]}, '$filter':{ args:[ 'array', 'function' ]},
@ -151,12 +158,13 @@
'$reverse':{ args:[ 'array' ]}, '$reverse':{ args:[ 'array' ]},
'$round':{ args:[ 'number', 'precision' ]}, '$round':{ args:[ 'number', 'precision' ]},
'$shuffle':{ args:[ 'array' ]}, '$shuffle':{ args:[ 'array' ]},
'$sift':{ args:[ 'object', 'function' ]}, '$sift':{ args: ['object', 'function'] },
'$single':{ args: ['array', 'function'] },
'$sort':{ args:[ 'array', 'function' ]}, '$sort':{ args:[ 'array', 'function' ]},
'$split':{ args:[ 'str', 'separator', 'limit' ]}, '$split':{ args:[ 'str', 'separator', 'limit' ]},
'$spread':{ args:[ 'object' ]}, '$spread':{ args:[ 'object' ]},
'$sqrt':{ args:[ 'number' ]}, '$sqrt':{ args:[ 'number' ]},
'$string':{ args:[ 'arg' ]}, '$string':{ args:[ 'arg', 'prettify' ]},
'$substring':{ args:[ 'str', 'start', 'length' ]}, '$substring':{ args:[ 'str', 'start', 'length' ]},
'$substringAfter':{ args:[ 'str', 'chars' ]}, '$substringAfter':{ args:[ 'str', 'chars' ]},
'$substringBefore':{ args:[ 'str', 'chars' ]}, '$substringBefore':{ args:[ 'str', 'chars' ]},

View File

@ -30,16 +30,16 @@
<!-- (with the 'node-input-' prefix). --> <!-- (with the 'node-input-' prefix). -->
<!-- The available icon classes are defined Font Awesome Icons (FA Icons) --> <!-- The available icon classes are defined Font Awesome Icons (FA Icons) -->
<div class="form-row"> <div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label> <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" placeholder="Topic"> <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div> </div>
<br/> <br/>
<!-- By convention, most nodes have a 'name' property. The following div --> <!-- By convention, most nodes have a 'name' property. The following div -->
<!-- provides the necessary field. Should always be the last option --> <!-- provides the necessary field. Should always be the last option -->
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <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" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>

View File

@ -2,7 +2,7 @@
<script type="text/x-red" data-template-name="comment"> <script type="text/x-red" data-template-name="comment">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <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"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-row node-text-editor-row"> <div class="form-row node-text-editor-row">
<input type="hidden" id="node-input-info" autofocus="autofocus"> <input type="hidden" id="node-input-info" autofocus="autofocus">

View File

@ -127,7 +127,7 @@
}, },
oneditprepare: function() { oneditprepare: function() {
var node = this; var node = this;
var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false}; var previousValueType = {value:"prev",label:this._("switch.previous"),hasValue:false};
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global','jsonata','env']}); $("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global','jsonata','env']});
var outputCount = $("#node-input-outputs").val("{}"); var outputCount = $("#node-input-outputs").val("{}");
@ -237,15 +237,15 @@
function createTypeValueField(){ function createTypeValueField(){
return $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'string',types:[ return $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'string',types:[
{value:"string",label:"string",hasValue:false}, {value:"string",label:RED._("common.type.string"),hasValue:false,icon:"red/images/typedInput/az.png"},
{value:"number",label:"number",hasValue:false}, {value:"number",label:RED._("common.type.number"),hasValue:false,icon:"red/images/typedInput/09.png"},
{value:"boolean",label:"boolean",hasValue:false}, {value:"boolean",label:RED._("common.type.boolean"),hasValue:false,icon:"red/images/typedInput/bool.png"},
{value:"array",label:"array",hasValue:false}, {value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.png"},
{value:"buffer",label:"buffer",hasValue:false}, {value:"buffer",label:RED._("common.type.buffer"),hasValue:false,icon:"red/images/typedInput/bin.png"},
{value:"object",label:"object",hasValue:false}, {value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.png"},
{value:"json",label:"JSON string",hasValue:false}, {value:"json",label:RED._("common.type.jsonString"),hasValue:false,icon:"red/images/typedInput/json.png"},
{value:"undefined",label:"undefined",hasValue:false}, {value:"undefined",label:RED._("common.type.undefined"),hasValue:false},
{value:"null",label:"null",hasValue:false} {value:"null",label:RED._("common.type.null"),hasValue:false}
]}); ]});
} }

View File

@ -153,15 +153,24 @@ module.exports = function(RED) {
} }
else if (node.pauseType === "rate") { else if (node.pauseType === "rate") {
node.on("input", function(msg) { node.on("input", function(msg) {
if (msg.hasOwnProperty("reset")) {
if (node.intervalID !== -1 ) {
clearInterval(node.intervalID);
node.intervalID = -1;
}
node.buffer = [];
node.status({text:"reset"});
return;
}
if (!node.drop) { if (!node.drop) {
var m = RED.util.cloneMessage(msg);
delete m.flush;
if (node.intervalID !== -1) { if (node.intervalID !== -1) {
if (!msg.hasOwnProperty("flush")) { node.buffer.push(m);
node.buffer.push(msg); node.reportDepth();
node.reportDepth();
}
} }
else { else {
node.send(msg); node.send(m);
node.reportDepth(); node.reportDepth();
node.intervalID = setInterval(function() { node.intervalID = setInterval(function() {
if (node.buffer.length === 0) { if (node.buffer.length === 0) {
@ -174,6 +183,12 @@ module.exports = function(RED) {
node.reportDepth(); node.reportDepth();
}, node.rate); }, node.rate);
} }
if (msg.hasOwnProperty("flush")) {
while (node.buffer.length > 0) {
node.send(node.buffer.shift());
}
node.status({});
}
} }
else { else {
var timeSinceLast; var timeSinceLast;
@ -189,18 +204,6 @@ module.exports = function(RED) {
node.send(msg); node.send(msg);
} }
} }
if (msg.hasOwnProperty("reset")) {
clearInterval(node.intervalID);
node.intervalID = -1;
node.buffer = [];
node.status({text:"reset"});
}
if (msg.hasOwnProperty("flush")) {
while (node.buffer.length > 0) {
node.send(node.buffer.shift());
}
node.status({});
}
}); });
node.on("close", function() { node.on("close", function() {
clearInterval(node.intervalID); clearInterval(node.intervalID);

View File

@ -111,9 +111,13 @@ module.exports = function(RED) {
if (typeof this.cleansession === 'undefined') { if (typeof this.cleansession === 'undefined') {
this.cleansession = true; this.cleansession = true;
} }
var prox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; } var prox, noprox;
if (process.env.HTTP_PROXY != null) { prox = process.env.HTTP_PROXY; } 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(","); }
// Create the URL to pass in to the MQTT.js library // Create the URL to pass in to the MQTT.js library
if (this.brokerurl === "") { if (this.brokerurl === "") {
@ -121,9 +125,15 @@ module.exports = function(RED) {
if (this.broker.indexOf("://") > -1) { if (this.broker.indexOf("://") > -1) {
this.brokerurl = this.broker; this.brokerurl = this.broker;
// Only for ws or wss, check if proxy env var for additional configuration // Only for ws or wss, check if proxy env var for additional configuration
if (this.brokerurl.indexOf("wss://") > -1 || this.brokerurl.indexOf("ws://") > -1 ) if (this.brokerurl.indexOf("wss://") > -1 || this.brokerurl.indexOf("ws://") > -1 ) {
// check if proxy is set in env // check if proxy is set in env
if (prox) { var noproxy;
if (noprox) {
for (var i = 0; i < noprox.length; i += 1) {
if (this.brokerurl.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
}
}
if (prox && !noproxy) {
var parsedUrl = url.parse(this.brokerurl); var parsedUrl = url.parse(this.brokerurl);
var proxyOpts = url.parse(prox); var proxyOpts = url.parse(prox);
// true for wss // true for wss
@ -134,9 +144,11 @@ module.exports = function(RED) {
agent: agent agent: agent
} }
} }
if (this.brokerurl.indexOf("mqtts://") > -1 && (!this.usetls || !n.tls)) }
if (this.brokerurl.indexOf("mqtts://") > -1 && (!this.usetls || !n.tls)) {
// Default to validating the server cert // Default to validating the server cert
this.verifyservercert = true; this.verifyservercert = true;
}
} else { } else {
// construct the std mqtt:// url // construct the std mqtt:// url
if (this.usetls) { if (this.usetls) {

View File

@ -170,6 +170,7 @@
if (this.authType) { if (this.authType) {
$('#node-input-useAuth').prop('checked', true); $('#node-input-useAuth').prop('checked', true);
$("#node-input-authType-select").val(this.authType); $("#node-input-authType-select").val(this.authType);
$("#node-input-authType-select").change();
} else { } else {
$('#node-input-useAuth').prop('checked', false); $('#node-input-useAuth').prop('checked', false);
} }

View File

@ -39,10 +39,10 @@ module.exports = function(RED) {
else { this.reqTimeout = 120000; } else { this.reqTimeout = 120000; }
var prox, noprox; var prox, noprox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; } if (process.env.http_proxy) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY != null) { prox = process.env.HTTP_PROXY; } if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy != null) { noprox = process.env.no_proxy.split(","); } if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY != null) { noprox = process.env.NO_PROXY.split(","); } if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
var proxyConfig = null; var proxyConfig = null;
if (n.proxy) { if (n.proxy) {
@ -88,8 +88,13 @@ module.exports = function(RED) {
if (msg.method && n.method && (n.method === "use")) { if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter method = msg.method.toUpperCase(); // use the msg parameter
} }
var isHttps = (/^https/i.test(url));
var opts = {}; var opts = {};
opts.url = url; opts.url = url;
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
opts.defaultPort = isHttps?443:80;
opts.timeout = node.reqTimeout; opts.timeout = node.reqTimeout;
opts.method = method; opts.method = method;
opts.headers = {}; opts.headers = {};
@ -284,9 +289,10 @@ module.exports = function(RED) {
opts.headers[clSet] = opts.headers['content-length']; opts.headers[clSet] = opts.headers['content-length'];
delete opts.headers['content-length']; delete opts.headers['content-length'];
} }
var noproxy; var noproxy;
if (noprox) { if (noprox) {
for (var i in noprox) { for (var i = 0; i < noprox.length; i += 1) {
if (url.indexOf(noprox[i]) !== -1) { noproxy=true; } if (url.indexOf(noprox[i]) !== -1) { noproxy=true; }
} }
} }

View File

@ -19,6 +19,8 @@ module.exports = function(RED) {
var ws = require("ws"); var ws = require("ws");
var inspect = require("util").inspect; var inspect = require("util").inspect;
var url = require("url"); var url = require("url");
var HttpsProxyAgent = require('https-proxy-agent');
var serverUpgradeAdded = false; var serverUpgradeAdded = false;
function handleServerUpgrade(request, socket, head) { function handleServerUpgrade(request, socket, head) {
@ -55,7 +57,28 @@ module.exports = function(RED) {
function startconn() { // Connect to remote endpoint function startconn() { // Connect to remote endpoint
node.tout = null; 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 = {}; var options = {};
if (agent) {
options.agent = agent;
}
if (node.tls) { if (node.tls) {
var tlsNode = RED.nodes.getNode(node.tls); var tlsNode = RED.nodes.getNode(node.tls);
if (tlsNode) { if (tlsNode) {

View File

@ -371,7 +371,6 @@ module.exports = function(RED) {
var server = net.createServer(function (socket) { var server = net.createServer(function (socket) {
socket.setKeepAlive(true,120000); socket.setKeepAlive(true,120000);
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); } if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var remoteDetails = socket.remoteAddress+":"+socket.remotePort;
node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort})); node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.push(socket); connectedSockets.push(socket);
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})}); node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});

View File

@ -230,17 +230,17 @@ module.exports = function(RED) {
node.send(msg); // finally send the array node.send(msg); // finally send the array
} }
} }
else { else {
var len = a.length; var len = a.length;
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg); var newMessage = RED.util.cloneMessage(msg);
newMessage.payload = a[i]; newMessage.payload = a[i];
if (!has_parts) { if (!has_parts) {
newMessage.parts = { newMessage.parts = {
id: msg._msgid, id: msg._msgid,
index: i, index: i,
count: len count: len
}; };
} }
else { else {
newMessage.parts.index -= node.skip; newMessage.parts.index -= node.skip;
@ -251,8 +251,8 @@ module.exports = function(RED) {
} }
} }
node.send(newMessage); node.send(newMessage);
} }
} }
node.linecount = 0; node.linecount = 0;
} }
catch(e) { node.error(e,msg); } catch(e) { node.error(e,msg); }

View File

@ -285,7 +285,11 @@
$("#node-input-property").typedInput('types',['msg']); $("#node-input-property").typedInput('types',['msg']);
$("#node-input-joiner").typedInput("show"); $("#node-input-joiner").typedInput("show");
} else { } else {
$("#node-input-property").typedInput('types',['msg', {value:"full",label:"complete message",hasValue:false}]); $("#node-input-property").typedInput('types', ['msg', {
value: "full",
label: RED._("node-red:join.completeMessage"),
hasValue: false
}]);
} }
}); });
@ -297,7 +301,11 @@
$("#node-input-property").typedInput({ $("#node-input-property").typedInput({
typeField: $("#node-input-propertyType"), typeField: $("#node-input-propertyType"),
types:['msg', {value:"full", label:"complete message", hasValue:false}] types: ['msg', {
value: "full",
label: RED._("node-red:join.completeMessage"),
hasValue: false
}]
}); });
$("#node-input-key").typedInput({ $("#node-input-key").typedInput({

View File

@ -341,6 +341,7 @@ module.exports = function(RED) {
} }
else if (node.format === "lines") { else if (node.format === "lines") {
var m = { payload: spare, var m = { payload: spare,
topic:msg.topic,
parts: { parts: {
index: count, index: count,
count: count+1, count: count+1,

View File

@ -604,33 +604,34 @@
"label": { "label": {
"property": "Property", "property": "Property",
"rule": "rule", "rule": "rule",
"repair" : "recreate message sequences" "repair": "recreate message sequences"
}, },
"previous": "previous value",
"and": "and", "and": "and",
"checkall": "checking all rules", "checkall": "checking all rules",
"stopfirst": "stopping after first match", "stopfirst": "stopping after first match",
"ignorecase": "ignore case", "ignorecase": "ignore case",
"rules": { "rules": {
"btwn":"is between", "btwn": "is between",
"cont":"contains", "cont": "contains",
"regex":"matches regex", "regex": "matches regex",
"true":"is true", "true": "is true",
"false":"is false", "false": "is false",
"null":"is null", "null": "is null",
"nnull":"is not null", "nnull": "is not null",
"istype":"is of type", "istype": "is of type",
"empty":"is empty", "empty": "is empty",
"nempty":"is not empty", "nempty": "is not empty",
"head":"head", "head": "head",
"tail":"tail", "tail": "tail",
"index":"index between", "index": "index between",
"exp":"JSONata exp", "exp": "JSONata exp",
"else":"otherwise", "else": "otherwise",
"hask":"has key" "hask": "has key"
}, },
"errors": { "errors": {
"invalid-expr": "Invalid JSONata expression: __error__", "invalid-expr": "Invalid JSONata expression: __error__",
"too-many" : "too many pending messages in switch node" "too-many": "too many pending messages in switch node"
} }
}, },
"change": { "change": {
@ -848,41 +849,42 @@
"stream":"Handle as a stream of messages", "stream":"Handle as a stream of messages",
"addname":" Copy key to " "addname":" Copy key to "
}, },
"join":{ "join": {
"join": "join", "join": "join",
"mode":{ "mode": {
"mode":"Mode", "mode": "Mode",
"auto":"automatic", "auto": "automatic",
"merge":"merge sequences", "merge": "merge sequences",
"reduce":"reduce sequence", "reduce": "reduce sequence",
"custom":"manual" "custom": "manual"
}, },
"combine":"Combine each", "combine": "Combine each",
"create":"to create", "completeMessage": "complete message",
"type":{ "create": "to create",
"string":"a String", "type": {
"array":"an Array", "string": "a String",
"buffer":"a Buffer", "array": "an Array",
"object":"a key/value Object", "buffer": "a Buffer",
"merged":"a merged Object" "object": "a key/value Object",
"merged": "a merged Object"
}, },
"using":"using the value of", "using": "using the value of",
"key":"as the key", "key": "as the key",
"joinedUsing":"joined using", "joinedUsing": "joined using",
"send":"Send the message:", "send": "Send the message:",
"afterCount":"After a number of message parts", "afterCount": "After a number of message parts",
"count":"count", "count": "count",
"subsequent":"and every subsequent message.", "subsequent": "and every subsequent message.",
"afterTimeout":"After a timeout following the first message", "afterTimeout": "After a timeout following the first message",
"seconds":"seconds", "seconds": "seconds",
"complete":"After a message with the <code>msg.complete</code> property set", "complete": "After a message with the <code>msg.complete</code> property set",
"tip":"This mode assumes this node is either paired with a <i>split</i> node or the received messages will have a properly configured <code>msg.parts</code> property.", "tip": "This mode assumes this node is either paired with a <i>split</i> node or the received messages will have a properly configured <code>msg.parts</code> property.",
"too-many" : "too many pending messages in join node", "too-many": "too many pending messages in join node",
"merge": { "merge": {
"topics-label":"Merged Topics", "topics-label": "Merged Topics",
"topics":"topics", "topics": "topics",
"topic" : "topic", "topic": "topic",
"on-change":"Send merged message on arrival of a new topic" "on-change": "Send merged message on arrival of a new topic"
}, },
"reduce": { "reduce": {
"exp": "Reduce exp", "exp": "Reduce exp",

View File

@ -36,7 +36,7 @@
<h3>Inputs</h3> <h3>Inputs</h3>
<dl class="message-properties"> <dl class="message-properties">
<dt>payload <span class="property-type">string | buffer</span></dt> <dt>payload <span class="property-type">string | buffer</span></dt>
<dd> most users prefer simple text payloads, but binary buffers can also be published.</dd> <dd> the payload to publish. If this property is not set, no message will be sent. To send a blank message, set this property to an empty String.</dd>
<dt class="optional">topic <span class="property-type">string</span></dt> <dt class="optional">topic <span class="property-type">string</span></dt>
<dd> the MQTT topic to publish to.</dd> <dd> the MQTT topic to publish to.</dd>

View File

@ -36,7 +36,7 @@
<dt class="optional">followRedirects</dt> <dt class="optional">followRedirects</dt>
<dd>If set to <code>false</code> prevent following Redirect (HTTP 301).<code>true</code> by default</dd> <dd>If set to <code>false</code> prevent following Redirect (HTTP 301).<code>true</code> by default</dd>
<dt class="optional">requestTimeout</dt> <dt class="optional">requestTimeout</dt>
<dd>If set to a positive number, will override the globally set <code>httpRequestTimeout</code> parameter.</dd> <dd>If set to a positive number of milliseconds, will override the globally set <code>httpRequestTimeout</code> parameter.</dd>
</dl> </dl>
<h3>Outputs</h3> <h3>Outputs</h3>
<dl class="message-properties"> <dl class="message-properties">

View File

@ -604,6 +604,7 @@
"rule": "条件", "rule": "条件",
"repair": "メッセージ列の補正" "repair": "メッセージ列の補正"
}, },
"previous": "前回の値",
"and": "", "and": "",
"checkall": "全ての条件を適用", "checkall": "全ての条件を適用",
"stopfirst": "最初に合致した条件で終了", "stopfirst": "最初に合致した条件で終了",
@ -856,6 +857,7 @@
"custom": "手動" "custom": "手動"
}, },
"combine": "結合", "combine": "結合",
"completeMessage": "メッセージ全体",
"create": "出力", "create": "出力",
"type": { "type": {
"string": "文字列", "string": "文字列",

View File

@ -33,6 +33,8 @@
<dd><code>false</code>をセットすると、自己署名証明書を使用するhttpsサイトへのリクエストを許可します。</dd> <dd><code>false</code>をセットすると、自己署名証明書を使用するhttpsサイトへのリクエストを許可します。</dd>
<dt class="optional">followRedirects</dt> <dt class="optional">followRedirects</dt>
<dd><code>false</code>をセットすると、リダイレクトを行いません。デフォルトは<code>true</code>です。</dd> <dd><code>false</code>をセットすると、リダイレクトを行いません。デフォルトは<code>true</code>です。</dd>
<dt class="optional">requestTimeout</dt>
<dd>正のミリ秒数をセットすると、 グローバルに設定された<code>httpRequestTimeout</code>パラメータを上書きします。</dd>
</dl> </dl>
<h3>出力</h3> <h3>出力</h3>
<dl class="message-properties"> <dl class="message-properties">

View File

@ -98,7 +98,7 @@
<p><i>合計値</i>」には出力メッセージを送信する前に受信すべきメッセージ数を指定します。オブジェクト出力の場合、この合計値に達すると後続メッセージの到着毎にメッセージを出力するように設定することもできます。</p> <p><i>合計値</i>」には出力メッセージを送信する前に受信すべきメッセージ数を指定します。オブジェクト出力の場合、この合計値に達すると後続メッセージの到着毎にメッセージを出力するように設定することもできます。</p>
<p><i></i>」には新規メッセージを送信するまでの経過時間を設定します。</p> <p><i></i>」には新規メッセージを送信するまでの経過時間を設定します。</p>
<p><code>msg.complete</code>プロパティを設定したメッセージを受信すると、出力メッセージを送信します。この時、メッセージ列の数をリセットします。</p> <p><code>msg.complete</code>プロパティを設定したメッセージを受信すると、出力メッセージを送信します。この時、メッセージ列の数をリセットします。</p>
<p><code>msg.reset</code>プロパティを設定したメッセージを受すると、部分的に受信済みのメッセージを破棄します。これらのメッセージは送信されません。この時、メッセージ列の数をリセットします。</p> <p><code>msg.reset</code>プロパティを設定したメッセージを受すると、部分的に受信済みのメッセージを破棄します。これらのメッセージは送信されません。この時、メッセージ列の数をリセットします。</p>
<h4>列の集約モード</h4> <h4>列の集約モード</h4>
<p>列の集約モードを選択すると、メッセージ列を構成する各々のメッセージに対して式を適用し、集約した値を用いて一つのメッセージを構成します。</p> <p>列の集約モードを選択すると、メッセージ列を構成する各々のメッセージに対して式を適用し、集約した値を用いて一つのメッセージを構成します。</p>

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/nodes", "name": "@node-red/nodes",
"version": "1.0.2", "version": "1.0.3",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
@ -22,12 +22,12 @@
"cookie-parser": "1.4.4", "cookie-parser": "1.4.4",
"cookie": "0.4.0", "cookie": "0.4.0",
"cors": "2.8.5", "cors": "2.8.5",
"cron": "1.7.1", "cron": "1.7.2",
"denque": "1.4.1", "denque": "1.4.1",
"fs-extra": "8.1.0", "fs-extra": "8.1.0",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"hash-sum": "2.0.0", "hash-sum": "2.0.0",
"https-proxy-agent": "2.2.2", "https-proxy-agent": "2.2.4",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"media-typer": "1.1.0", "media-typer": "1.1.0",
@ -38,7 +38,7 @@
"raw-body": "2.4.1", "raw-body": "2.4.1",
"request": "2.88.0", "request": "2.88.0",
"ws": "6.2.1", "ws": "6.2.1",
"xml2js": "0.4.19", "xml2js": "0.4.22",
"iconv-lite": "0.5.0" "iconv-lite": "0.5.0"
} }
} }

View File

@ -32,6 +32,7 @@ var paletteEditorEnabled = false;
var settings; var settings;
var moduleRe = /^(@[^/]+?[/])?[^/]+?$/; var moduleRe = /^(@[^/]+?[/])?[^/]+?$/;
var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
var pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
function init(runtime) { function init(runtime) {
events = runtime.events; events = runtime.events;
@ -76,14 +77,17 @@ function checkExistingModule(module,version) {
} }
return false; return false;
} }
function installModule(module,version) { function installModule(module,version,url) {
activePromise = activePromise.then(() => { activePromise = activePromise.then(() => {
//TODO: ensure module is 'safe' //TODO: ensure module is 'safe'
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
var installName = module; var installName = module;
var isUpgrade = false; var isUpgrade = false;
try { try {
if (moduleRe.test(module)) { if (url && pkgurlRe.test(url)) {
// Git remote url or Tarball url - check the valid package url
installName = url;
} else if (moduleRe.test(module)) {
// Simple module name - assume it can be npm installed // Simple module name - assume it can be npm installed
if (version) { if (version) {
installName += "@"+version; installName += "@"+version;

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/registry", "name": "@node-red/registry",
"version": "1.0.2", "version": "1.0.3",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,9 +16,9 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "1.0.2", "@node-red/util": "1.0.3",
"semver": "6.3.0", "semver": "6.3.0",
"uglify-js": "3.6.0", "uglify-js": "3.6.9",
"when": "3.7.8" "when": "3.7.8"
} }
} }

View File

@ -159,6 +159,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to install * @param {String} opts.module - the id of the module to install
* @param {String} opts.version - (optional) the version of the module to install * @param {String} opts.version - (optional) the version of the module to install
* @param {String} opts.url - (optional) url to install
* @param {Object} opts.req - the request to log (optional) * @param {Object} opts.req - the request to log (optional)
* @return {Promise<ModuleInfo>} - the node module info * @return {Promise<ModuleInfo>} - the node module info
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
@ -183,20 +184,20 @@ var api = module.exports = {
return reject(err); return reject(err);
} }
} }
runtime.nodes.installModule(opts.module,opts.version).then(function(info) { runtime.nodes.installModule(opts.module,opts.version,opts.url).then(function(info) {
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version}, opts.req); runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url}, opts.req);
return resolve(info); return resolve(info);
}).catch(function(err) { }).catch(function(err) {
if (err.code === 404) { if (err.code === 404) {
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:"not_found"}, opts.req); runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:"not_found"}, opts.req);
// TODO: code/status // TODO: code/status
err.status = 404; err.status = 404;
} else if (err.code) { } else if (err.code) {
err.status = 400; err.status = 400;
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code}, opts.req); runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req);
} else { } else {
err.status = 400; err.status = 400;
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code||"unexpected_error",message:err.toString()}, opts.req); runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
} }
return reject(err); return reject(err);
}) })

View File

@ -224,7 +224,7 @@ Node.prototype._emitInput = function(arg) {
} }
); );
} catch(err) { } catch(err) {
node.error(err,msg); node.error(err,arg);
} }
} }
} }

View File

@ -200,6 +200,9 @@ class Subflow extends Flow {
self.node.status({text:text}); self.node.status({text:text});
} else if (msg.status !== undefined) { } else if (msg.status !== undefined) {
// if msg.status exists // if msg.status exists
if (msg.status.hasOwnProperty("text") && msg.status.text.indexOf("common.") === 0) {
msg.status.text = "node-red:"+msg.status.text;
}
self.node.status(msg.status) self.node.status(msg.status)
} }
}) })
@ -310,7 +313,6 @@ class Subflow extends Flow {
} }
} }
} }
super.start(diff); super.start(diff);
} }
@ -435,7 +437,6 @@ class Subflow extends Flow {
} }
return handled; return handled;
} }
} }

View File

@ -150,10 +150,10 @@ function reportNodeStateChange(info,enabled) {
} }
} }
function installModule(module,version) { function installModule(module,version,url) {
var existingModule = registry.getModuleInfo(module); var existingModule = registry.getModuleInfo(module);
var isUpgrade = !!existingModule; var isUpgrade = !!existingModule;
return registry.installModule(module,version).then(function(info) { return registry.installModule(module,version,url).then(function(info) {
if (isUpgrade) { if (isUpgrade) {
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}}); events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}});
} else { } else {

View File

@ -56,8 +56,9 @@ function init(_settings, _runtime) {
if (settings.flowFile) { if (settings.flowFile) {
flowsFile = settings.flowFile; flowsFile = settings.flowFile;
// handle Unix and Windows "C:\" // handle Unix and Windows "C:\" and Windows "\\" for UNC.
if ((flowsFile[0] == "/") || (flowsFile[1] == ":")) { if (fspath.isAbsolute(flowsFile)) {
//if (((flowsFile[0] == "\\") && (flowsFile[1] == "\\")) || (flowsFile[0] == "/") || (flowsFile[1] == ":")) {
// Absolute path // Absolute path
flowsFullPath = flowsFile; flowsFullPath = flowsFile;
} else if (flowsFile.substring(0,2) === "./") { } else if (flowsFile.substring(0,2) === "./") {

View File

@ -15,6 +15,7 @@
**/ **/
var fs = require('fs-extra'); var fs = require('fs-extra');
var fspath = require('path');
var when = require('when'); var when = require('when');
var nodeFn = require('when/node/function'); var nodeFn = require('when/node/function');
@ -79,25 +80,31 @@ module.exports = {
* the write hits disk. * the write hits disk.
*/ */
writeFile: function(path,content,backupPath) { writeFile: function(path,content,backupPath) {
if (backupPath) { if (backupPath) {
if (fs.existsSync(path)) { if (fs.existsSync(path)) {
fs.renameSync(path,backupPath); fs.renameSync(path,backupPath);
} }
} }
return when.promise(function(resolve,reject) { return when.promise(function(resolve,reject) {
var stream = fs.createWriteStream(path); fs.ensureDir(fspath.dirname(path), (err)=>{
stream.on('open',function(fd) { if (err) {
stream.write(content,'utf8',function() { reject(err);
fs.fsync(fd,function(err) { return;
if (err) { }
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()})); var stream = fs.createWriteStream(path);
} stream.on('open',function(fd) {
stream.end(resolve); stream.write(content,'utf8',function() {
fs.fsync(fd,function(err) {
if (err) {
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()}));
}
stream.end(resolve);
});
}); });
}); });
}); stream.on('error',function(err) {
stream.on('error',function(err) { reject(err);
reject(err); });
}); });
}); });
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/runtime", "name": "@node-red/runtime",
"version": "1.0.2", "version": "1.0.3",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,8 +16,8 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/registry": "1.0.2", "@node-red/registry": "1.0.3",
"@node-red/util": "1.0.2", "@node-red/util": "1.0.3",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.17.1", "express": "4.17.1",
"fs-extra": "8.1.0", "fs-extra": "8.1.0",

View File

@ -81,35 +81,55 @@ function mergeCatalog(fallback,catalog) {
} }
} }
var MessageFileLoader = {
type: "backend", function readFile(lng, ns) {
init: function(services, backendOptions, i18nextOptions) {}, return new Promise((resolve, reject) => {
read: function(lng, ns, callback) { if (resourceCache[ns] && resourceCache[ns][lng]) {
if (resourceMap[ns]) { resolve(resourceCache[ns][lng]);
var file = path.join(resourceMap[ns].basedir,lng,resourceMap[ns].file); } else if (resourceMap[ns]) {
//console.log(file); var file = path.join(resourceMap[ns].basedir, lng, resourceMap[ns].file);
fs.readFile(file,"utf8",function(err,content) { fs.readFile(file, "utf8", function (err, content) {
if (err) { if (err) {
callback(err); reject(err);
} else { } else {
try { try {
resourceCache[ns] = resourceCache[ns]||{}; resourceCache[ns] = resourceCache[ns] || {};
resourceCache[ns][lng] = JSON.parse(content.replace(/^\uFEFF/, '')); resourceCache[ns][lng] = JSON.parse(content.replace(/^\uFEFF/, ''));
//console.log(resourceCache[ns][lng]); var baseLng = lng.split('-')[0];
if (lng !== defaultLang) { if (baseLng !== lng && resourceCache[ns][baseLng]) {
mergeCatalog(resourceCache[ns][defaultLang],resourceCache[ns][lng]); mergeCatalog(resourceCache[ns][baseLng], resourceCache[ns][lng]);
} }
callback(null, resourceCache[ns][lng]); if (lng !== defaultLang) {
} catch(e) { mergeCatalog(resourceCache[ns][defaultLang], resourceCache[ns][lng]);
callback(e); }
resolve(resourceCache[ns][lng]);
} catch (e) {
reject(e);
} }
} }
}); });
} else { } else {
callback(new Error("Unrecognised namespace")); reject(new Error("Unrecognised namespace"));
} }
} });
}
var MessageFileLoader = {
type: "backend",
init: function (services, backendOptions, i18nextOptions) { },
read: function (lng, ns, callback) {
readFile(lng, ns)
.then(data => callback(null, data))
.catch(err => {
if (/-/.test(lng)) {
// if reading language file fails -> try reading base language (e. g. 'fr' instead of 'fr-FR' or 'de' for 'de-DE')
var baseLng = lng.split('-')[0];
readFile(baseLng, ns).then(baseData => callback(null, baseData)).catch(err => callback(err));
} else {
callback(err);
}
});
}
} }
function getCurrentLocale() { function getCurrentLocale() {

View File

@ -84,9 +84,14 @@ var consoleLogger = function(msg) {
util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack); util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack);
} else { } else {
var message = msg.msg; var message = msg.msg;
if (typeof message === 'object' && message !== null && message.toString() === '[object Object]' && message.message) { try {
message = message.message; if (typeof message === 'object' && message !== null && message.toString() === '[object Object]' && message.message) {
message = message.message;
}
} catch(e){
message = 'Exception trying to log: '+util.inspect(message);
} }
util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message); util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message);
} }
} }

View File

@ -652,130 +652,148 @@ function normaliseNodeTypeName(name) {
* @memberof @node-red/util_util * @memberof @node-red/util_util
*/ */
function encodeObject(msg,opts) { function encodeObject(msg,opts) {
var debuglength = 1000; try {
if (opts && opts.hasOwnProperty('maxLength')) { var debuglength = 1000;
debuglength = opts.maxLength; if (opts && opts.hasOwnProperty('maxLength')) {
} debuglength = opts.maxLength;
var msgType = typeof msg.msg;
if (msg.msg instanceof Error) {
msg.format = "error";
var errorMsg = {};
if (msg.msg.name) {
errorMsg.name = msg.msg.name;
} }
if (msg.msg.hasOwnProperty('message')) { var msgType = typeof msg.msg;
errorMsg.message = msg.msg.message; if (msg.msg instanceof Error) {
} else { msg.format = "error";
errorMsg.message = msg.msg.toString(); var errorMsg = {};
} if (msg.msg.name) {
msg.msg = JSON.stringify(errorMsg); errorMsg.name = msg.msg.name;
} else if (msg.msg instanceof Buffer) { }
msg.format = "buffer["+msg.msg.length+"]"; if (msg.msg.hasOwnProperty('message')) {
msg.msg = msg.msg.toString('hex'); errorMsg.message = msg.msg.message;
if (msg.msg.length > debuglength) { } else {
msg.msg = msg.msg.substring(0,debuglength); errorMsg.message = msg.msg.toString();
} }
} else if (msg.msg && msgType === 'object') { msg.msg = JSON.stringify(errorMsg);
try { } else if (msg.msg instanceof Buffer) {
msg.format = msg.msg.constructor.name || "Object"; msg.format = "buffer["+msg.msg.length+"]";
// Handle special case of msg.req/res objects from HTTP In node msg.msg = msg.msg.toString('hex');
if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") { if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength);
}
} else if (msg.msg && msgType === 'object') {
try {
msg.format = msg.msg.constructor.name || "Object";
// Handle special case of msg.req/res objects from HTTP In node
if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") {
msg.format = "Object";
}
} catch(err) {
msg.format = "Object"; msg.format = "Object";
} }
} catch(err) { if (/error/i.test(msg.format)) {
msg.format = "Object"; msg.msg = JSON.stringify({
} name: msg.msg.name,
if (/error/i.test(msg.format)) { message: msg.msg.message
msg.msg = JSON.stringify({ });
name: msg.msg.name, } else {
message: msg.msg.message var isArray = util.isArray(msg.msg);
}); if (isArray) {
} else { msg.format = "array["+msg.msg.length+"]";
var isArray = util.isArray(msg.msg); if (msg.msg.length > debuglength) {
if (isArray) { // msg.msg = msg.msg.slice(0,debuglength);
msg.format = "array["+msg.msg.length+"]"; msg.msg = {
if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.slice(0,debuglength);
msg.msg = {
__enc__: true,
type: "array",
data: msg.msg.slice(0,debuglength),
length: msg.msg.length
}
}
}
if (isArray || (msg.format === "Object")) {
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
if (key === '_req' || key === '_res') {
value = {
__enc__: true,
type: "internal"
}
} else if (value instanceof Error) {
value = value.toString()
} else if (util.isArray(value) && value.length > debuglength) {
value = {
__enc__: true, __enc__: true,
type: "array", type: "array",
data: value.slice(0,debuglength), data: msg.msg.slice(0,debuglength),
length: value.length length: msg.msg.length
}
} else if (typeof value === 'string') {
if (value.length > debuglength) {
value = value.substring(0,debuglength)+"...";
}
} else if (typeof value === 'function') {
value = {
__enc__: true,
type: "function"
}
} else if (typeof value === 'number') {
if (isNaN(value) || value === Infinity || value === -Infinity) {
value = {
__enc__: true,
type: "number",
data: value.toString()
}
}
} else if (value && value.constructor) {
if (value.type === "Buffer") {
value.__enc__ = true;
value.length = value.data.length;
if (value.length > debuglength) {
value.data = value.data.slice(0,debuglength);
}
} else if (value.constructor.name === "ServerResponse") {
value = "[internal]"
} else if (value.constructor.name === "Socket") {
value = "[internal]"
} }
} }
return value; }
}," "); if (isArray || (msg.format === "Object")) {
} else { msg.msg = safeJSONStringify(msg.msg, function(key, value) {
try { msg.msg = msg.msg.toString(); } if (key === '_req' || key === '_res') {
catch(e) { msg.msg = "[Type not printable]"; } value = {
__enc__: true,
type: "internal"
}
} else if (value instanceof Error) {
value = value.toString()
} else if (util.isArray(value) && value.length > debuglength) {
value = {
__enc__: true,
type: "array",
data: value.slice(0,debuglength),
length: value.length
}
} else if (typeof value === 'string') {
if (value.length > debuglength) {
value = value.substring(0,debuglength)+"...";
}
} else if (typeof value === 'function') {
value = {
__enc__: true,
type: "function"
}
} else if (typeof value === 'number') {
if (isNaN(value) || value === Infinity || value === -Infinity) {
value = {
__enc__: true,
type: "number",
data: value.toString()
}
}
} else if (value && value.constructor) {
if (value.type === "Buffer") {
value.__enc__ = true;
value.length = value.data.length;
if (value.length > debuglength) {
value.data = value.data.slice(0,debuglength);
}
} else if (value.constructor.name === "ServerResponse") {
value = "[internal]"
} else if (value.constructor.name === "Socket") {
value = "[internal]"
}
}
return value;
}," ");
} else {
try { msg.msg = msg.msg.toString(); }
catch(e) { msg.msg = "[Type not printable]" + util.inspect(msg.msg); }
}
}
} else if (msgType === "function") {
msg.format = "function";
msg.msg = "[function]"
} else if (msgType === "boolean") {
msg.format = "boolean";
msg.msg = msg.msg.toString();
} else if (msgType === "number") {
msg.format = "number";
msg.msg = msg.msg.toString();
} else if (msg.msg === null || msgType === "undefined") {
msg.format = (msg.msg === null)?"null":"undefined";
msg.msg = "(undefined)";
} else {
msg.format = "string["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength)+"...";
} }
} }
} else if (msgType === "function") { return msg;
msg.format = "function"; } catch(e) {
msg.msg = "[function]" msg.format = "error";
} else if (msgType === "boolean") { var errorMsg = {};
msg.format = "boolean"; if (e.name) {
msg.msg = msg.msg.toString(); errorMsg.name = e.name;
} else if (msgType === "number") {
msg.format = "number";
msg.msg = msg.msg.toString();
} else if (msg.msg === null || msgType === "undefined") {
msg.format = (msg.msg === null)?"null":"undefined";
msg.msg = "(undefined)";
} else {
msg.format = "string["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength)+"...";
} }
if (e.hasOwnProperty('message')) {
errorMsg.message = 'encodeObject Error: ['+e.message + '] Value: '+util.inspect(msg.msg);
} else {
errorMsg.message = 'encodeObject Error: ['+e.toString() + '] Value: '+util.inspect(msg.msg);
}
if (errorMsg.message.length > debuglength) {
errorMsg.message = errorMsg.message.substring(0,debuglength);
}
msg.msg = JSON.stringify(errorMsg);
return msg;
} }
return msg;
} }
module.exports = { module.exports = {

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/util", "name": "@node-red/util",
"version": "1.0.2", "version": "1.0.3",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
@ -18,7 +18,7 @@
"clone": "2.1.2", "clone": "2.1.2",
"i18next": "15.1.2", "i18next": "15.1.2",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.6.5", "jsonata": "1.7.0",
"when": "3.7.8" "when": "3.7.8"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "1.0.2", "version": "1.0.3",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -31,10 +31,10 @@
"flow" "flow"
], ],
"dependencies": { "dependencies": {
"@node-red/editor-api": "1.0.2", "@node-red/editor-api": "1.0.3",
"@node-red/runtime": "1.0.2", "@node-red/runtime": "1.0.3",
"@node-red/util": "1.0.2", "@node-red/util": "1.0.3",
"@node-red/nodes": "1.0.2", "@node-red/nodes": "1.0.3",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.17.1", "express": "4.17.1",

View File

@ -4,4 +4,4 @@ npm install --no-save \
wdio-mocha-framework@^0.6.4 \ wdio-mocha-framework@^0.6.4 \
wdio-spec-reporter@^0.1.5 \ wdio-spec-reporter@^0.1.5 \
webdriverio@^4.14.1 \ webdriverio@^4.14.1 \
chromedriver@2 chromedriver@^78.0.1

View File

@ -61,7 +61,7 @@ exports.config = {
maxInstances: 2, maxInstances: 2,
// //
browserName: 'chrome', browserName: 'chrome',
chromeOptions: { 'goog:chromeOptions': {
args: process.env.NODE_RED_NON_HEADLESS args: process.env.NODE_RED_NON_HEADLESS
// Runs tests with opening a browser. // Runs tests with opening a browser.
? ['--disable-gpu'] ? ['--disable-gpu']
@ -134,7 +134,7 @@ exports.config = {
// Services take over a specific job you don't want to take care of. They enhance // Services take over a specific job you don't want to take care of. They enhance
// your test setup with almost no effort. Unlike plugins, they don't add new // your test setup with almost no effort. Unlike plugins, they don't add new
// commands. Instead, they hook themselves up into the test process. // commands. Instead, they hook themselves up into the test process.
port: '9515', port: 9515,
path: '/', path: '/',
services: ['chromedriver'], services: ['chromedriver'],
// //

View File

@ -251,7 +251,8 @@ describe('delay Node', function() {
var helperNode1 = helper.getNode("helperNode1"); var helperNode1 = helper.getNode("helperNode1");
var receivedMessagesStack = []; var receivedMessagesStack = [];
var rate = 1000/aLimit; // Add a small grace to the calculated delay
var rate = 1000/aLimit + 10;
var receiveTimestamp; var receiveTimestamp;

View File

@ -227,7 +227,7 @@ describe("api/admin/nodes", function() {
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo',version:"1.2.3"}) .send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
@ -238,6 +238,7 @@ describe("api/admin/nodes", function() {
res.body.nodes[0].should.have.property("id","123"); res.body.nodes[0].should.have.property("id","123");
opts.should.have.property("module","foo"); opts.should.have.property("module","foo");
opts.should.have.property("version","1.2.3"); opts.should.have.property("version","1.2.3");
opts.should.have.property("url","https://example/foo-1.2.3.tgz");
done(); done();
}); });
}); });
@ -256,7 +257,7 @@ describe("api/admin/nodes", function() {
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo',version:"1.2.3"}) .send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"})
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {

View File

@ -121,6 +121,17 @@ describe('nodes/registry/installer', function() {
done(); done();
}); });
}); });
it("rejects when update requested to existing version and url", function(done) {
sinon.stub(typeRegistry,"getModuleInfo", function() {
return {
version: "0.1.1"
}
});
installer.installModule("this_wont_exist","0.1.1","https://example/foo-0.1.1.tgz").catch(function(err) {
err.code.should.be.eql('module_already_loaded');
done();
});
});
it("rejects with generic error", function(done) { it("rejects with generic error", function(done) {
var res = { var res = {
code: 1, code: 1,
@ -201,6 +212,29 @@ describe('nodes/registry/installer', function() {
done(err); done(err);
}); });
}); });
it("succeeds when url is valid node-red module", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
initInstaller(p)
var addModule = sinon.stub(registry,"addModule",function(md) {
return when.resolve(nodeInfo);
});
installer.installModule("this_wont_exist",null,"https://example/foo-0.1.1.tgz").then(function(info) {
info.should.eql(nodeInfo);
done();
}).catch(function(err) {
done(err);
});
});
}); });
describe("uninstalls module", function() { describe("uninstalls module", function() {

View File

@ -298,7 +298,7 @@ describe('Subflow', function() {
// // stoppedNodes.should.have.a.property(sfConfigId); // // stoppedNodes.should.have.a.property(sfConfigId);
done(); done();
}); });
},50); },150);
}); });
it("instantiates a subflow inside a subflow and stops it",function(done) { it("instantiates a subflow inside a subflow and stops it",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -331,7 +331,7 @@ describe('Subflow', function() {
Object.keys(currentNodes).should.have.length(0); Object.keys(currentNodes).should.have.length(0);
done(); done();
}); });
},50); },150);
}); });
it("rewires a subflow node on update/start",function(done){ it("rewires a subflow node on update/start",function(done){
@ -391,8 +391,8 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50); },150);
},50); },150);
}); });
}); });
describe('#stop', function() { describe('#stop', function() {
@ -452,7 +452,7 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50); },150);
}); });
it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) { it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -491,7 +491,7 @@ describe('Subflow', function() {
done(); done();
}); });
},50); },150);
}); });
}); });
@ -536,7 +536,7 @@ describe('Subflow', function() {
done(); done();
}); });
},50); },150);
}); });
it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) { it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -578,7 +578,7 @@ describe('Subflow', function() {
done(); done();
}); });
},50); },150);
}); });
it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) { it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -620,7 +620,7 @@ describe('Subflow', function() {
done(); done();
}); });
},50); },150);
}); });
it("does not emit a regular status event if it contains a subflow-status node", function(done) { it("does not emit a regular status event if it contains a subflow-status node", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -690,7 +690,7 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50); },150);
}); });
it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) { it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -727,7 +727,7 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50); },150);
}); });
}); });
@ -777,7 +777,7 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50); },150);
}); });
it("can access subflow env var", function(done) { it("can access subflow env var", function(done) {
@ -817,7 +817,7 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50); },150);
}); });
it("can access nested subflow env var", function(done) { it("can access nested subflow env var", function(done) {
@ -875,9 +875,9 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50); },150);
},50); },150);
},50); },150);
}); });
}); });

View File

@ -19,6 +19,7 @@ var fs = require('fs-extra');
var path = require('path'); var path = require('path');
var sinon = require('sinon'); var sinon = require('sinon');
var NR_TEST_UTILS = require("nr-test-utils"); var NR_TEST_UTILS = require("nr-test-utils");
var process = require("process");
var localfilesystem = NR_TEST_UTILS.require("@node-red/runtime/lib/storage/localfilesystem"); var localfilesystem = NR_TEST_UTILS.require("@node-red/runtime/lib/storage/localfilesystem");
var log = NR_TEST_UTILS.require("@node-red/util").log; var log = NR_TEST_UTILS.require("@node-red/util").log;
@ -474,4 +475,44 @@ describe('storage/localfilesystem', function() {
done(err); done(err);
}); });
}); });
it('should handle flow file in random unc path and non-existent subfolder',function(done) {
// only test on win32
if (process.platform !== 'win32') {
console.log('skipped test as not win32');
done();
return;
}
// get a real windows path
var flowFile = path.win32.resolve(userDir+'/some/random/path');
var rootdir = path.win32.resolve(userDir+'/some');
// make it into a local UNC path
flowFile = flowFile.replace('C:\\', '\\\\localhost\\c$\\');
localfilesystem.init({userDir:userDir, flowFile:flowFile}, mockRuntime).then(function() {
fs.existsSync(flowFile).should.be.false();
localfilesystem.saveFlows(testFlow).then(function() {
fs.existsSync(flowFile).should.be.true();
localfilesystem.getFlows().then(function(flows) {
flows.should.eql(testFlow);
// cleanup
fs.removeSync(rootdir);
done();
}).catch(function(err) {
// cleanup
fs.removeSync(rootdir);
done(err);
});
}).catch(function(err) {
// cleanup
fs.removeSync(rootdir);
done(err);
});
}).catch(function(err) {
// cleanup
fs.removeSync(rootdir);
done(err);
});
});
}); });

View File

@ -224,5 +224,29 @@ describe("@node-red/util/log", function() {
}); });
it('it can log without exception', function() {
var msg = {
msg: {
mystrangeobj:"hello",
},
};
msg.msg.toString = function(){
throw new Error('Exception in toString - should have been caught');
}
msg.msg.constructor = { name: "strangeobj" };
var ret = log.info(msg.msg);
});
it('it can log an object but use .message', function() {
var msg = {
msg: {
message: "my special message",
mystrangeobj:"hello",
},
};
var ret = log.info(msg.msg);
sinon.assert.calledWithMatch(util.log,"my special message");
});
}); });

View File

@ -772,6 +772,117 @@ describe("@node-red/util/util", function() {
var resultJson = JSON.parse(result.msg); var resultJson = JSON.parse(result.msg);
resultJson.socket.should.eql('[internal]'); resultJson.socket.should.eql('[internal]');
}); });
it('object which fails to serialise', function(done) {
var msg = {
msg: {
obj:{
cantserialise:{
message:'this will not be displayed',
toJSON: function(val) {
throw 'this exception should have been caught';
return 'should not display because we threw first';
},
},
canserialise:{
message:'this should be displayed',
}
},
}
};
var result = util.encodeObject(msg);
result.format.should.eql("error");
var success = (result.msg.indexOf('cantserialise') > 0);
success &= (result.msg.indexOf('this exception should have been caught') > 0);
success &= (result.msg.indexOf('canserialise') > 0);
success.should.eql(1);
done();
});
it('object which fails to serialise - different error type', function(done) {
var msg = {
msg: {
obj:{
cantserialise:{
message:'this will not be displayed',
toJSON: function(val) {
throw new Error('this exception should have been caught');
return 'should not display because we threw first';
},
},
canserialise:{
message:'this should be displayed',
}
},
}
};
var result = util.encodeObject(msg);
result.format.should.eql("error");
var success = (result.msg.indexOf('cantserialise') > 0);
success &= (result.msg.indexOf('this exception should have been caught') > 0);
success &= (result.msg.indexOf('canserialise') > 0);
success.should.eql(1);
done();
});
it('very large object which fails to serialise should be truncated', function(done) {
var msg = {
msg: {
obj:{
big:"",
cantserialise:{
message:'this will not be displayed',
toJSON: function(val) {
throw new Error('this exception should have been caught');
return 'should not display because we threw first';
},
},
canserialise:{
message:'this should be displayed',
}
},
}
};
for (var i = 0; i < 1000; i++) {
msg.msg.obj.big += 'some more string ';
}
var result = util.encodeObject(msg);
result.format.should.eql("error");
var resultJson = JSON.parse(result.msg);
var success = (resultJson.message.length <= 1000);
success.should.eql(true);
done();
});
it('test bad toString', function(done) {
var msg = {
msg: {
mystrangeobj:"hello",
},
};
msg.msg.toString = function(){
throw new Error('Exception in toString - should have been caught');
}
msg.msg.constructor = { name: "strangeobj" };
var result = util.encodeObject(msg);
var success = (result.msg.indexOf('[Type not printable]') >= 0);
success.should.eql(true);
done();
});
it('test bad object constructor', function(done) {
var msg = {
msg: {
mystrangeobj:"hello",
constructor: {
get name(){
throw new Error('Exception in constructor name');
}
}
},
};
var result = util.encodeObject(msg);
done();
});
}); });
}); });
}); });