mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'dev' into dev
This commit is contained in:
commit
bfabee072d
23
CHANGELOG.md
23
CHANGELOG.md
@ -1,3 +1,26 @@
|
|||||||
|
#### 3.1.7: Maintenance Release
|
||||||
|
|
||||||
|
- Add Japanese translation for v3.1.6 (#4603) @kazuhitoyokoi
|
||||||
|
- Update jsonata version (#4593) @hardillb
|
||||||
|
|
||||||
|
#### 3.1.6: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Do not flag env var in num typedInput as error (#4582) @knolleary
|
||||||
|
- Fix example flow name in import dialog (#4578) @kazuhitoyokoi
|
||||||
|
- Fix missing node icons in workspace (#4570) @knolleary
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Handle undefined env vars (#4581) @knolleary
|
||||||
|
- fix: Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 … (#4568) @JaysonHurst
|
||||||
|
- chore: remove never use import code (#4580) @giscafer
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- fix: template node zh-CN translation (#4575) @giscafer
|
||||||
|
|
||||||
#### 3.1.5: Maintenance Release
|
#### 3.1.5: Maintenance Release
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
var apiUtils = require("../util");
|
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
var settings;
|
var settings;
|
||||||
var theme = require("../editor/theme");
|
var theme = require("../editor/theme");
|
||||||
|
@ -18,7 +18,6 @@ var BearerStrategy = require('passport-http-bearer').Strategy;
|
|||||||
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
|
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
|
||||||
|
|
||||||
var passport = require("passport");
|
var passport = require("passport");
|
||||||
var crypto = require("crypto");
|
|
||||||
var util = require("util");
|
var util = require("util");
|
||||||
|
|
||||||
var Tokens = require("./tokens");
|
var Tokens = require("./tokens");
|
||||||
|
@ -14,11 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
|
||||||
var comms = require("./comms");
|
var comms = require("./comms");
|
||||||
var library = require("./library");
|
|
||||||
var info = require("./settings");
|
var info = require("./settings");
|
||||||
|
|
||||||
var auth = require("../auth");
|
var auth = require("../auth");
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var apiUtils = require("../util");
|
var apiUtils = require("../util");
|
||||||
var fs = require('fs');
|
|
||||||
var fspath = require('path');
|
|
||||||
|
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
|
|
||||||
|
@ -13,9 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
// var apiUtil = require('../util');
|
|
||||||
|
|
||||||
var i18n = require("@node-red/util").i18n; // TODO: separate module
|
var i18n = require("@node-red/util").i18n; // TODO: separate module
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var apiUtils = require("../util");
|
var apiUtils = require("../util");
|
||||||
var express = require("express");
|
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
var settings;
|
var settings;
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
var util = require("util");
|
var util = require("util");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
@ -99,7 +99,7 @@ module.exports = {
|
|||||||
// settings.instanceId is set asynchronously to the editor-api
|
// settings.instanceId is set asynchronously to the editor-api
|
||||||
// being initiaised. So we defer calculating the cacheBuster hash
|
// being initiaised. So we defer calculating the cacheBuster hash
|
||||||
// until the first load of the editor
|
// until the first load of the editor
|
||||||
cacheBuster = crypto.createHash('md5').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12)
|
cacheBuster = crypto.createHash('sha1').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12)
|
||||||
}
|
}
|
||||||
|
|
||||||
let sessionMessages;
|
let sessionMessages;
|
||||||
|
@ -24,11 +24,8 @@
|
|||||||
* @namespace @node-red/editor-api
|
* @namespace @node-red/editor-api
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
var bodyParser = require("body-parser");
|
var bodyParser = require("body-parser");
|
||||||
var util = require('util');
|
|
||||||
var passport = require('passport');
|
var passport = require('passport');
|
||||||
var cors = require('cors');
|
|
||||||
|
|
||||||
var auth = require("./auth");
|
var auth = require("./auth");
|
||||||
var apiUtil = require("./util");
|
var apiUtil = require("./util");
|
||||||
|
@ -303,7 +303,8 @@
|
|||||||
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
|
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
|
||||||
},
|
},
|
||||||
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
|
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
|
||||||
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。"
|
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。",
|
||||||
|
"alreadyExists": "本ノードは既に存在"
|
||||||
},
|
},
|
||||||
"copyMessagePath": "パスをコピーしました",
|
"copyMessagePath": "パスをコピーしました",
|
||||||
"copyMessageValue": "値をコピーしました",
|
"copyMessageValue": "値をコピーしました",
|
||||||
|
@ -919,7 +919,10 @@ RED.utils = (function() {
|
|||||||
* @returns true if valid, String if invalid
|
* @returns true if valid, String if invalid
|
||||||
*/
|
*/
|
||||||
function validateTypedProperty(propertyValue, propertyType, opt) {
|
function validateTypedProperty(propertyValue, propertyType, opt) {
|
||||||
|
if (propertyValue && /^\${[^}]+}$/.test(propertyValue)) {
|
||||||
|
// Allow ${ENV_VAR} value
|
||||||
|
return true
|
||||||
|
}
|
||||||
let error
|
let error
|
||||||
if (propertyType === 'json') {
|
if (propertyType === 'json') {
|
||||||
try {
|
try {
|
||||||
|
@ -4156,7 +4156,7 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
var width = img.width * scaleFactor;
|
var width = img.width * scaleFactor;
|
||||||
if (width > 20) {
|
if (width > 20) {
|
||||||
scalefactor *= 20/width;
|
scaleFactor *= 20/width;
|
||||||
width = 20;
|
width = 20;
|
||||||
}
|
}
|
||||||
var height = img.height * scaleFactor;
|
var height = img.height * scaleFactor;
|
||||||
|
@ -16,8 +16,20 @@
|
|||||||
RED.validators = {
|
RED.validators = {
|
||||||
number: function(blankAllowed,mopt){
|
number: function(blankAllowed,mopt){
|
||||||
return function(v, opt) {
|
return function(v, opt) {
|
||||||
if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) {
|
if (blankAllowed && (v === '' || v === undefined)) {
|
||||||
return true;
|
return true
|
||||||
|
}
|
||||||
|
if (v !== '') {
|
||||||
|
if (/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(v)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (/^\${[^}]+}$/.test(v)) {
|
||||||
|
// Allow ${ENV_VAR} value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isNaN(v)) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
if (opt && opt.label) {
|
if (opt && opt.label) {
|
||||||
return RED._("validator.errors.invalid-num-prop", {
|
return RED._("validator.errors.invalid-num-prop", {
|
||||||
|
@ -38,7 +38,7 @@ body {
|
|||||||
}
|
}
|
||||||
#red-ui-main-container {
|
#red-ui-main-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top:40px; left:0; bottom: 0; right:0;
|
top: var(--red-ui-header-height); left:0; bottom: 0; right:0;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +259,8 @@ $deploy-button-background-disabled-hover: #555;
|
|||||||
|
|
||||||
$header-background: #000;
|
$header-background: #000;
|
||||||
$header-button-background-active: #121212;
|
$header-button-background-active: #121212;
|
||||||
$header-menu-color: #C7C7C7;
|
$header-accent: #d41313;
|
||||||
|
$header-menu-color: #eee;
|
||||||
$header-menu-color-disabled: #666;
|
$header-menu-color-disabled: #666;
|
||||||
$header-menu-heading-color: #fff;
|
$header-menu-heading-color: #fff;
|
||||||
$header-menu-sublabel-color: #aeaeae;
|
$header-menu-sublabel-color: #aeaeae;
|
||||||
|
@ -23,16 +23,20 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: var(--red-ui-header-height);
|
||||||
background: var(--red-ui-header-background);
|
background: var(--red-ui-header-background);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0px 0px 0px 20px;
|
padding: 0px 0px 0px 20px;
|
||||||
color: var(--red-ui-header-menu-color);
|
color: var(--red-ui-header-menu-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid var(--red-ui-header-accent);
|
||||||
|
padding-top: 2px;
|
||||||
|
|
||||||
span.red-ui-header-logo {
|
span.red-ui-header-logo {
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -42,7 +46,7 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
margin-left: 5px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
|
17
packages/node_modules/@node-red/editor-client/src/sass/sizes.scss
vendored
Normal file
17
packages/node_modules/@node-red/editor-client/src/sass/sizes.scss
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
|
||||||
|
$header-height: 48px;
|
@ -15,4 +15,5 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
@import "colors";
|
@import "colors";
|
||||||
|
@import "sizes";
|
||||||
@import "variables";
|
@import "variables";
|
@ -15,6 +15,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
@import "colors";
|
@import "colors";
|
||||||
|
@import "sizes";
|
||||||
@import "variables";
|
@import "variables";
|
||||||
@import "mixins";
|
@import "mixins";
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
--red-ui-shadow: #{$shadow};
|
--red-ui-shadow: #{$shadow};
|
||||||
|
|
||||||
|
// Header Height
|
||||||
|
--red-ui-header-height: #{$header-height};
|
||||||
|
|
||||||
// Main body text
|
// Main body text
|
||||||
--red-ui-primary-text-color: #{$primary-text-color};
|
--red-ui-primary-text-color: #{$primary-text-color};
|
||||||
// UI control label text
|
// UI control label text
|
||||||
@ -240,6 +243,7 @@
|
|||||||
|
|
||||||
|
|
||||||
--red-ui-header-background: #{$header-background};
|
--red-ui-header-background: #{$header-background};
|
||||||
|
--red-ui-header-accent: #{$header-accent};
|
||||||
--red-ui-header-button-background-active: #{$header-button-background-active};
|
--red-ui-header-button-background-active: #{$header-button-background-active};
|
||||||
--red-ui-header-menu-color: #{$header-menu-color};
|
--red-ui-header-menu-color: #{$header-menu-color};
|
||||||
--red-ui-header-menu-color-disabled: #{$header-menu-color-disabled};
|
--red-ui-header-menu-color-disabled: #{$header-menu-color-disabled};
|
||||||
|
@ -227,34 +227,42 @@
|
|||||||
name: {value:""},
|
name: {value:""},
|
||||||
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) {
|
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) {
|
||||||
if (!v || v.length === 0) { return true }
|
if (!v || v.length === 0) { return true }
|
||||||
|
const errors = []
|
||||||
for (var i=0;i<v.length;i++) {
|
for (var i=0;i<v.length;i++) {
|
||||||
|
if (/^\${[^}]+}$/.test(v[i].v)) {
|
||||||
|
// Allow ${ENV_VAR} value
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (/msg|flow|global/.test(v[i].vt)) {
|
if (/msg|flow|global/.test(v[i].vt)) {
|
||||||
if (!RED.utils.validatePropertyExpression(v[i].v)) {
|
if (!RED.utils.validatePropertyExpression(v[i].v)) {
|
||||||
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v });
|
errors.push(RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }))
|
||||||
}
|
}
|
||||||
} else if (v[i].vt === "jsonata") {
|
} else if (v[i].vt === "jsonata") {
|
||||||
try{ jsonata(v[i].v); }
|
try{ jsonata(v[i].v); }
|
||||||
catch(e){
|
catch(e){
|
||||||
return RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message });
|
errors.push(RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message }))
|
||||||
}
|
}
|
||||||
} else if (v[i].vt === "json") {
|
} else if (v[i].vt === "json") {
|
||||||
try{ JSON.parse(v[i].v); }
|
try{ JSON.parse(v[i].v); }
|
||||||
catch(e){
|
catch(e){
|
||||||
return RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message });
|
errors.push(RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message }))
|
||||||
}
|
}
|
||||||
} else if (v[i].vt === "num"){
|
} else if (v[i].vt === "num"){
|
||||||
if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) {
|
if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) {
|
||||||
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v });
|
errors.push(RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return errors
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
repeat: {
|
repeat: {
|
||||||
value:"", validate: function(v, opt) {
|
value:"", validate: function(v, opt) {
|
||||||
if ((v === "") ||
|
if ((v === "") ||
|
||||||
(RED.validators.number(v) &&
|
(RED.validators.number()(v) &&
|
||||||
(v >= 0) && (v <= 2147483))) {
|
(v >= 0) && (v <= 2147483))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -263,7 +271,7 @@
|
|||||||
},
|
},
|
||||||
crontab: {value:""},
|
crontab: {value:""},
|
||||||
once: {value:false},
|
once: {value:false},
|
||||||
onceDelay: {value:0.1},
|
onceDelay: {value:0.1, validate: RED.validators.number(true)},
|
||||||
topic: {value:""},
|
topic: {value:""},
|
||||||
payload: {value:"", validate: RED.validators.typedInput("payloadType", false) },
|
payload: {value:"", validate: RED.validators.typedInput("payloadType", false) },
|
||||||
payloadType: {value:"date"},
|
payloadType: {value:"date"},
|
||||||
|
@ -40,6 +40,99 @@
|
|||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
const headerTypes = [
|
||||||
|
/*
|
||||||
|
{ value: "Accept", label: "Accept", hasValue: false },
|
||||||
|
{ value: "Accept-Encoding", label: "Accept-Encoding", hasValue: false },
|
||||||
|
{ value: "Accept-Language", label: "Accept-Language", hasValue: false },
|
||||||
|
*/
|
||||||
|
{ value: "Authorization", label: "Authorization", hasValue: false },
|
||||||
|
/*
|
||||||
|
{ value: "Content-Type", label: "Content-Type", hasValue: false },
|
||||||
|
{ value: "Cache-Control", label: "Cache-Control", hasValue: false },
|
||||||
|
*/
|
||||||
|
{ value: "User-Agent", label: "User-Agent", hasValue: false },
|
||||||
|
/*
|
||||||
|
{ value: "Location", label: "Location", hasValue: false },
|
||||||
|
*/
|
||||||
|
{ value: "other", label: RED._("node-red:httpin.label.other"),
|
||||||
|
hasValue: true, icon: "red/images/typedInput/az.svg" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const headerOptions = {};
|
||||||
|
const defaultOptions = [
|
||||||
|
{ value: "other", label: RED._("node-red:httpin.label.other"),
|
||||||
|
hasValue: true, icon: "red/images/typedInput/az.svg" },
|
||||||
|
"env",
|
||||||
|
];
|
||||||
|
/*
|
||||||
|
headerOptions["accept"] = [
|
||||||
|
{ value: "text/plain", label: "text/plain", hasValue: false },
|
||||||
|
{ value: "text/html", label: "text/html", hasValue: false },
|
||||||
|
{ value: "application/json", label: "application/json", hasValue: false },
|
||||||
|
{ value: "application/xml", label: "application/xml", hasValue: false },
|
||||||
|
...defaultOptions,
|
||||||
|
];
|
||||||
|
|
||||||
|
headerOptions["accept-encoding"] = [
|
||||||
|
{ value: "gzip", label: "gzip", hasValue: false },
|
||||||
|
{ value: "deflate", label: "deflate", hasValue: false },
|
||||||
|
{ value: "compress", label: "compress", hasValue: false },
|
||||||
|
{ value: "br", label: "br", hasValue: false },
|
||||||
|
{ value: "gzip, deflate", label: "gzip, deflate", hasValue: false },
|
||||||
|
{ value: "gzip, deflate, br", label: "gzip, deflate, br", hasValue: false },
|
||||||
|
...defaultOptions,
|
||||||
|
];
|
||||||
|
headerOptions["accept-language"] = [
|
||||||
|
{ value: "*", label: "*", hasValue: false },
|
||||||
|
{ value: "en-GB, en-US, en;q=0.9", label: "en-GB, en-US, en;q=0.9", hasValue: false },
|
||||||
|
{ value: "de-AT, de-DE;q=0.9, en;q=0.5", label: "de-AT, de-DE;q=0.9, en;q=0.5", hasValue: false },
|
||||||
|
{ value: "es-mx,es,en;q=0.5", label: "es-mx,es,en;q=0.5", hasValue: false },
|
||||||
|
{ value: "fr-CH, fr;q=0.9, en;q=0.8", label: "fr-CH, fr;q=0.9, en;q=0.8", hasValue: false },
|
||||||
|
{ value: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", label: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", hasValue: false },
|
||||||
|
{ value: "ja-JP, jp", label: "ja-JP, jp", hasValue: false },
|
||||||
|
...defaultOptions,
|
||||||
|
];
|
||||||
|
headerOptions["content-type"] = [
|
||||||
|
{ value: "text/css", label: "text/css", hasValue: false },
|
||||||
|
{ value: "text/plain", label: "text/plain", hasValue: false },
|
||||||
|
{ value: "text/html", label: "text/html", hasValue: false },
|
||||||
|
{ value: "application/json", label: "application/json", hasValue: false },
|
||||||
|
{ value: "application/octet-stream", label: "application/octet-stream", hasValue: false },
|
||||||
|
{ value: "application/pdf", label: "application/pdf", hasValue: false },
|
||||||
|
{ value: "application/xml", label: "application/xml", hasValue: false },
|
||||||
|
{ value: "application/zip", label: "application/zip", hasValue: false },
|
||||||
|
{ value: "multipart/form-data", label: "multipart/form-data", hasValue: false },
|
||||||
|
{ value: "audio/aac", label: "audio/aac", hasValue: false },
|
||||||
|
{ value: "audio/ac3", label: "audio/ac3", hasValue: false },
|
||||||
|
{ value: "audio/basic", label: "audio/basic", hasValue: false },
|
||||||
|
{ value: "audio/mp4", label: "audio/mp4", hasValue: false },
|
||||||
|
{ value: "audio/ogg", label: "audio/ogg", hasValue: false },
|
||||||
|
{ value: "image/bmp", label: "image/bmp", hasValue: false },
|
||||||
|
{ value: "image/gif", label: "image/gif", hasValue: false },
|
||||||
|
{ value: "image/jpeg", label: "image/jpeg", hasValue: false },
|
||||||
|
{ value: "image/png", label: "image/png", hasValue: false },
|
||||||
|
{ value: "image/tiff", label: "image/tiff", hasValue: false },
|
||||||
|
...defaultOptions,
|
||||||
|
];
|
||||||
|
headerOptions["cache-control"] = [
|
||||||
|
{ value: "max-age=0", label: "max-age=0", hasValue: false },
|
||||||
|
{ value: "max-age=86400", label: "max-age=86400", hasValue: false },
|
||||||
|
{ value: "no-cache", label: "no-cache", hasValue: false },
|
||||||
|
...defaultOptions,
|
||||||
|
];
|
||||||
|
*/
|
||||||
|
headerOptions["user-agent"] = [
|
||||||
|
{ value: "Mozilla/5.0", label: "Mozilla/5.0", hasValue: false },
|
||||||
|
...defaultOptions,
|
||||||
|
];
|
||||||
|
|
||||||
|
function getHeaderOptions(headerName) {
|
||||||
|
const lc = (headerName || "").toLowerCase();
|
||||||
|
let opts = headerOptions[lc];
|
||||||
|
return opts || defaultOptions;
|
||||||
|
}
|
||||||
|
|
||||||
function ws_oneditprepare() {
|
function ws_oneditprepare() {
|
||||||
$("#websocket-client-row").hide();
|
$("#websocket-client-row").hide();
|
||||||
$("#node-input-mode").on("change", function() {
|
$("#node-input-mode").on("change", function() {
|
||||||
@ -192,7 +285,8 @@
|
|||||||
value: "",
|
value: "",
|
||||||
label:RED._("node-red:websocket.sendheartbeat"),
|
label:RED._("node-red:websocket.sendheartbeat"),
|
||||||
validate: RED.validators.number(/*blank allowed*/true) },
|
validate: RED.validators.number(/*blank allowed*/true) },
|
||||||
subprotocol: {value:"",required: false}
|
subprotocol: {value:"",required: false},
|
||||||
|
headers: { value: [] }
|
||||||
},
|
},
|
||||||
inputs:0,
|
inputs:0,
|
||||||
outputs:0,
|
outputs:0,
|
||||||
@ -200,6 +294,9 @@
|
|||||||
return this.path;
|
return this.path;
|
||||||
},
|
},
|
||||||
oneditprepare: function() {
|
oneditprepare: function() {
|
||||||
|
|
||||||
|
const node = this;
|
||||||
|
|
||||||
$("#node-config-input-path").on("change keyup paste",function() {
|
$("#node-config-input-path").on("change keyup paste",function() {
|
||||||
$(".node-config-row-tls").toggle(/^wss:/i.test($(this).val()))
|
$(".node-config-row-tls").toggle(/^wss:/i.test($(this).val()))
|
||||||
});
|
});
|
||||||
@ -214,14 +311,114 @@
|
|||||||
if (!heartbeatActive) {
|
if (!heartbeatActive) {
|
||||||
$("#node-config-input-hb").val("");
|
$("#node-config-input-hb").val("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasMatch = function (arr, value) {
|
||||||
|
return arr.some(function (ht) {
|
||||||
|
return ht.value === value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerList = $("#node-input-headers-container").css('min-height', '150px').css('min-width', '450px').editableList({
|
||||||
|
addItem: function (container, i, header) {
|
||||||
|
const row = $('<div/>').css({
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
display: 'flex'
|
||||||
|
}).appendTo(container);
|
||||||
|
const propertNameCell = $('<div/>').css({ 'flex-grow': 1 }).appendTo(row);
|
||||||
|
const propertyName = $('<input/>', { class: "node-input-header-name", type: "text", style: "width: 100%" })
|
||||||
|
.appendTo(propertNameCell)
|
||||||
|
.typedInput({ types: headerTypes });
|
||||||
|
|
||||||
|
const propertyValueCell = $('<div/>').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row);
|
||||||
|
const propertyValue = $('<input/>', { class: "node-input-header-value", type: "text", style: "width: 100%" })
|
||||||
|
.appendTo(propertyValueCell)
|
||||||
|
.typedInput({
|
||||||
|
types: getHeaderOptions(header.keyType)
|
||||||
|
});
|
||||||
|
|
||||||
|
const setup = function(_header) {
|
||||||
|
const headerTypeIsAPreset = function(h) {return hasMatch(headerTypes, h) };
|
||||||
|
const headerValueIsAPreset = function(h, v) {return hasMatch(getHeaderOptions(h), v) };
|
||||||
|
|
||||||
|
const {keyType, keyValue, valueType, valueValue} = header;
|
||||||
|
|
||||||
|
if(keyType == "other") {
|
||||||
|
propertyName.typedInput('type', keyType);
|
||||||
|
propertyName.typedInput('value', keyValue);
|
||||||
|
} else if (headerTypeIsAPreset(keyType)) {
|
||||||
|
propertyName.typedInput('type', keyType);
|
||||||
|
} else {
|
||||||
|
propertyName.typedInput('type', "other");
|
||||||
|
propertyName.typedInput('value', keyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(valueType == "other" || valueType == "env" ) {
|
||||||
|
propertyValue.typedInput('type', valueType);
|
||||||
|
propertyValue.typedInput('value', valueValue);
|
||||||
|
} else if (headerValueIsAPreset(propertyName.typedInput('type'), valueType)) {
|
||||||
|
propertyValue.typedInput('type', valueType);
|
||||||
|
} else {
|
||||||
|
propertyValue.typedInput('type', "other");
|
||||||
|
propertyValue.typedInput('value', valueValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setup(header);
|
||||||
|
|
||||||
|
propertyName.on('change', function (event) {
|
||||||
|
propertyValue.typedInput('types', getHeaderOptions(propertyName.typedInput('type')));
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
sortable: true,
|
||||||
|
removable: true
|
||||||
|
});
|
||||||
|
if (node.headers) {
|
||||||
|
for (let index = 0; index < node.headers.length; index++) {
|
||||||
|
const element = node.headers[index];
|
||||||
|
headerList.editableList('addItem', node.headers[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
oneditsave: function() {
|
oneditsave: function() {
|
||||||
|
|
||||||
|
const node = this;
|
||||||
|
|
||||||
if (!/^wss:/i.test($("#node-config-input-path").val())) {
|
if (!/^wss:/i.test($("#node-config-input-path").val())) {
|
||||||
$("#node-config-input-tls").val("_ADD_");
|
$("#node-config-input-tls").val("_ADD_");
|
||||||
}
|
}
|
||||||
if (!$("#node-config-input-hb-cb").prop("checked")) {
|
if (!$("#node-config-input-hb-cb").prop("checked")) {
|
||||||
$("#node-config-input-hb").val("0");
|
$("#node-config-input-hb").val("0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const headers = $("#node-input-headers-container").editableList('items');
|
||||||
|
|
||||||
|
node.headers = [];
|
||||||
|
headers.each(function(i) {
|
||||||
|
const header = $(this);
|
||||||
|
const keyType = header.find(".node-input-header-name").typedInput('type');
|
||||||
|
const keyValue = header.find(".node-input-header-name").typedInput('value');
|
||||||
|
const valueType = header.find(".node-input-header-value").typedInput('type');
|
||||||
|
const valueValue = header.find(".node-input-header-value").typedInput('value');
|
||||||
|
node.headers.push({
|
||||||
|
keyType, keyValue, valueType, valueValue
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditresize: function(size) {
|
||||||
|
const dlg = $("#dialog-form");
|
||||||
|
const expandRow = dlg.find('.node-input-headers-container-row');
|
||||||
|
let height = dlg.height() - 5;
|
||||||
|
if(expandRow && expandRow.length){
|
||||||
|
const siblingRows = dlg.find('> .form-row:not(.node-input-headers-container-row)');
|
||||||
|
for (let i = 0; i < siblingRows.size(); i++) {
|
||||||
|
const cr = $(siblingRows[i]);
|
||||||
|
if(cr.is(":visible"))
|
||||||
|
height -= cr.outerHeight(true);
|
||||||
|
}
|
||||||
|
$("#node-input-headers-container").editableList('height',height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -299,8 +496,15 @@
|
|||||||
<span data-i18n="inject.seconds"></span>
|
<span data-i18n="inject.seconds"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row" style="margin-bottom:0;">
|
||||||
|
<label><i class="fa fa-list"></i> <span data-i18n="httpin.label.headers"></span></label>
|
||||||
|
</div>
|
||||||
|
<div class="form-row node-input-headers-container-row">
|
||||||
|
<ol id="node-input-headers-container"></ol>
|
||||||
|
</div>
|
||||||
<div class="form-tips">
|
<div class="form-tips">
|
||||||
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
|
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
|
||||||
<span data-i18n="[html]websocket.tip.url2"></span>
|
<p><span data-i18n="[html]websocket.tip.url2"></span></p>
|
||||||
|
<span data-i18n="[html]websocket.tip.headers"></span>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
@ -58,6 +58,7 @@ module.exports = function(RED) {
|
|||||||
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
|
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
|
||||||
node.closing = false;
|
node.closing = false;
|
||||||
node.tls = n.tls;
|
node.tls = n.tls;
|
||||||
|
node.upgradeHeaders = n.headers
|
||||||
|
|
||||||
if (n.hb) {
|
if (n.hb) {
|
||||||
var heartbeat = parseInt(n.hb);
|
var heartbeat = parseInt(n.hb);
|
||||||
@ -96,6 +97,42 @@ module.exports = function(RED) {
|
|||||||
tlsNode.addTLSOptions(options);
|
tlsNode.addTLSOptions(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to check if undefined, to guard against previous installs, that will not have had this property set (applies to 3.1.x setups)
|
||||||
|
// Else this will be breaking potentially
|
||||||
|
if(node.upgradeHeaders !== undefined && node.upgradeHeaders.length > 0){
|
||||||
|
options.headers = {};
|
||||||
|
for(let i = 0;i<node.upgradeHeaders.length;i++){
|
||||||
|
const header = node.upgradeHeaders[i];
|
||||||
|
const keyType = header.keyType;
|
||||||
|
const keyValue = header.keyValue;
|
||||||
|
const valueType = header.valueType;
|
||||||
|
const valueValue = header.valueValue;
|
||||||
|
|
||||||
|
const headerName = keyType === 'other' ? keyValue : keyType;
|
||||||
|
let headerValue;
|
||||||
|
|
||||||
|
switch(valueType){
|
||||||
|
case 'other':
|
||||||
|
headerValue = valueValue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'env':
|
||||||
|
headerValue = RED.util.evaluateNodeProperty(valueValue,valueType,node);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
headerValue = valueType;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(headerName && headerValue){
|
||||||
|
options.headers[headerName] = headerValue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var socket = new ws(node.path,node.subprotocol,options);
|
var socket = new ws(node.path,node.subprotocol,options);
|
||||||
socket.setMaxListeners(0);
|
socket.setMaxListeners(0);
|
||||||
node.server = socket; // keep for closing
|
node.server = socket; // keep for closing
|
||||||
|
@ -516,7 +516,8 @@
|
|||||||
"path1": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Empfänger (Listener) kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt.",
|
"path1": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Empfänger (Listener) kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt.",
|
||||||
"path2": "Dieser Pfad ist relativ zu <code>__path__</code>.",
|
"path2": "Dieser Pfad ist relativ zu <code>__path__</code>.",
|
||||||
"url1": "URL sollte ws:// oder wss:// Schema verwenden und auf einen vorhandenen WebSocket-Listener verweisen.",
|
"url1": "URL sollte ws:// oder wss:// Schema verwenden und auf einen vorhandenen WebSocket-Listener verweisen.",
|
||||||
"url2": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt."
|
"url2": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt.",
|
||||||
|
"headers": "Header werden nur während des Protokollaktualisierungsmechanismus übermittelt, von HTTP auf das WS/WSS-Protokoll."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "Verbunden __count__",
|
"connected": "Verbunden __count__",
|
||||||
|
@ -586,7 +586,8 @@
|
|||||||
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
|
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
|
||||||
"path2": "This path will be relative to <code>__path__</code>.",
|
"path2": "This path will be relative to <code>__path__</code>.",
|
||||||
"url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.",
|
"url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.",
|
||||||
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
|
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string.",
|
||||||
|
"headers": "Headers are only submitted during the Protocol upgrade mechanism, from HTTP to the WS/WSS Protocol."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "connected __count__",
|
"connected": "connected __count__",
|
||||||
|
@ -583,7 +583,8 @@
|
|||||||
"path1": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. L'écouteur peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON.",
|
"path1": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. L'écouteur peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON.",
|
||||||
"path2": "Ce chemin sera relatif à <code>__path__</code>.",
|
"path2": "Ce chemin sera relatif à <code>__path__</code>.",
|
||||||
"url1": "L'URL doit utiliser le schéma ws:// ou wss:// et pointer vers un écouteur websocket existant.",
|
"url1": "L'URL doit utiliser le schéma ws:// ou wss:// et pointer vers un écouteur websocket existant.",
|
||||||
"url2": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. Le client peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON."
|
"url2": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. Le client peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON.",
|
||||||
|
"headers": "Les en-têtes ne sont soumis que lors du mécanisme de mise à niveau du protocole, de HTTP vers le protocole WS/WSS."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "__count__ connecté",
|
"connected": "__count__ connecté",
|
||||||
|
@ -586,7 +586,8 @@
|
|||||||
"path1": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
|
"path1": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
|
||||||
"path2": "このパスは <code>__path__</code> の相対パスになります。",
|
"path2": "このパスは <code>__path__</code> の相対パスになります。",
|
||||||
"url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。",
|
"url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。",
|
||||||
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。"
|
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
|
||||||
|
"headers": "ヘッダーは、HTTP から WS/WSS プロトコルへのプロトコル アップグレード メカニズム中にのみ送信されます。"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "接続数 __count__",
|
"connected": "接続数 __count__",
|
||||||
|
@ -451,7 +451,8 @@
|
|||||||
"path1": "표준으로는 <code>payload</code> 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
|
"path1": "표준으로는 <code>payload</code> 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
|
||||||
"path2": "This path will be relative to <code>__path__</code>.",
|
"path2": "This path will be relative to <code>__path__</code>.",
|
||||||
"url1": "URL에는 ws:// 또는 wss:// 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.",
|
"url1": "URL에는 ws:// 또는 wss:// 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.",
|
||||||
"url2": "표준으로는 <code>payload</code> 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다."
|
"url2": "표준으로는 <code>payload</code> 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
|
||||||
|
"headers": "헤더는 HTTP에서 WS/WSS 프로토콜로 프로토콜 업그레이드 메커니즘 중에만 제출됩니다."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "접속 수 __count__",
|
"connected": "접속 수 __count__",
|
||||||
|
@ -573,7 +573,8 @@
|
|||||||
"path1": "Por padrão, a <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O ouvinte pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON.",
|
"path1": "Por padrão, a <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O ouvinte pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON.",
|
||||||
"path2": "Este caminho será relativo a <code>__path__</code>.",
|
"path2": "Este caminho será relativo a <code>__path__</code>.",
|
||||||
"url1": "A URL deve usar o esquema ws:// ou wss:// e apontar para um ouvinte de websocket existente.",
|
"url1": "A URL deve usar o esquema ws:// ou wss:// e apontar para um ouvinte de websocket existente.",
|
||||||
"url2": "Por padrão, <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O cliente pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON."
|
"url2": "Por padrão, <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O cliente pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON.",
|
||||||
|
"headers": "Os cabeçalhos são enviados apenas durante o mecanismo de atualização do protocolo, do HTTP para o protocolo WS/WSS."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "conectado __count__",
|
"connected": "conectado __count__",
|
||||||
|
@ -475,7 +475,8 @@
|
|||||||
"path1": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Слушатель может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.",
|
"path1": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Слушатель может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.",
|
||||||
"path2": "Путь будет относительно <code>__path__</code>.",
|
"path2": "Путь будет относительно <code>__path__</code>.",
|
||||||
"url1": "URL должен использовать схему ws:// или wss:// и указывать на существующего слушателя websocket.",
|
"url1": "URL должен использовать схему ws:// или wss:// и указывать на существующего слушателя websocket.",
|
||||||
"url2": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Клиент может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON."
|
"url2": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Клиент может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.",
|
||||||
|
"headers": "Заголовки передаются только во время механизма обновления протокола с HTTP на протокол WS/WSS."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "подключен __count__",
|
"connected": "подключен __count__",
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<dt class="optional">template <span class="property-type">string</span></dt>
|
<dt class="optional">template <span class="property-type">string</span></dt>
|
||||||
<dd>由<code>msg.payload</code>填充的模板。如果未在编辑面板中配置,则可以将设为msg的属性。</dd>
|
<dd>由<code>msg.payload</code>填充的模板。如果未在编辑面板中配置,则可以将设为msg的属性。</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<h3>Outputs</h3>
|
<h3>输出</h3>
|
||||||
<dl class="message-properties">
|
<dl class="message-properties">
|
||||||
<dt>msg <span class="property-type">object</span></dt>
|
<dt>msg <span class="property-type">object</span></dt>
|
||||||
<dd>由来自传入msg的属性来填充已配置的模板后输出的带有属性的msg。</dd>
|
<dd>由来自传入msg的属性来填充已配置的模板后输出的带有属性的msg。</dd>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<p>默认情况下使用<i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>格式。如有需要也可以切换其他格式。</p>
|
<p>默认情况下使用<i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>格式。如有需要也可以切换其他格式。</p>
|
||||||
<p>例如:
|
<p>例如:
|
||||||
<pre>Hello {{payload.name}}. Today is {{date}}</pre>
|
<pre>Hello {{payload.name}}. Today is {{date}}</pre>
|
||||||
<p>receives a message containing:
|
<p>接收一条消息,其中包含:
|
||||||
<pre>{
|
<pre>{
|
||||||
date: "Monday",
|
date: "Monday",
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -576,7 +576,8 @@
|
|||||||
"path1": "默认情况下,<code>payload</code>将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
|
"path1": "默认情况下,<code>payload</code>将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
|
||||||
"path2": "这条路径将相对于 <code>__path__</code>.",
|
"path2": "这条路径将相对于 <code>__path__</code>.",
|
||||||
"url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.",
|
"url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.",
|
||||||
"url2": "默认情况下,<code>payload</code> 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象."
|
"url2": "默认情况下,<code>payload</code> 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象.",
|
||||||
|
"headers": "标头仅在协议升级机制期间提交,从 HTTP 到 WS/WSS 协议."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "已连接数量 __count__",
|
"connected": "已连接数量 __count__",
|
||||||
|
@ -471,7 +471,8 @@
|
|||||||
"path1": "預設情況下,<code>payload</code>將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.",
|
"path1": "預設情況下,<code>payload</code>將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.",
|
||||||
"path2": "這條路徑將相對於 <code>__path__</code>.",
|
"path2": "這條路徑將相對於 <code>__path__</code>.",
|
||||||
"url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.",
|
"url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.",
|
||||||
"url2": "預設情況下,<code>payload</code> 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件."
|
"url2": "預設情況下,<code>payload</code> 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件.",
|
||||||
|
"headers": "標頭僅在協定升級機制期間提交,從 HTTP 到 WS/WSS 協定."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "連接數 __count__",
|
"connected": "連接數 __count__",
|
||||||
|
@ -36,7 +36,7 @@ async function getFlowsFromPath(path) {
|
|||||||
promises.push(getFlowsFromPath(fullPath));
|
promises.push(getFlowsFromPath(fullPath));
|
||||||
} else if (/\.json$/.test(file)){
|
} else if (/\.json$/.test(file)){
|
||||||
validFiles.push(file);
|
validFiles.push(file);
|
||||||
promises.push(Promise.resolve(file.split(".")[0]))
|
promises.push(Promise.resolve(file.replace(/\.json$/, '')))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -485,7 +485,7 @@ class Flow {
|
|||||||
}
|
}
|
||||||
if (!key.startsWith("$parent.")) {
|
if (!key.startsWith("$parent.")) {
|
||||||
if (this._env.hasOwnProperty(key)) {
|
if (this._env.hasOwnProperty(key)) {
|
||||||
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
|
return (this._env[key] && Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
key = key.substring(8);
|
key = key.substring(8);
|
||||||
|
@ -41,7 +41,7 @@ class Group {
|
|||||||
}
|
}
|
||||||
if (!key.startsWith("$parent.")) {
|
if (!key.startsWith("$parent.")) {
|
||||||
if (this._env.hasOwnProperty(key)) {
|
if (this._env.hasOwnProperty(key)) {
|
||||||
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
|
return (this._env[key] && Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
key = key.substring(8);
|
key = key.substring(8);
|
||||||
|
@ -376,7 +376,7 @@ class Subflow extends Flow {
|
|||||||
}
|
}
|
||||||
if (!key.startsWith("$parent.")) {
|
if (!key.startsWith("$parent.")) {
|
||||||
if (this._env.hasOwnProperty(key)) {
|
if (this._env.hasOwnProperty(key)) {
|
||||||
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
|
return (this._env[key] && Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
key = key.substring(8);
|
key = key.substring(8);
|
||||||
|
@ -77,7 +77,7 @@ var storageModuleInterface = {
|
|||||||
flows: flows,
|
flows: flows,
|
||||||
credentials: creds
|
credentials: creds
|
||||||
};
|
};
|
||||||
result.rev = crypto.createHash('md5').update(JSON.stringify(result.flows)).digest("hex");
|
result.rev = crypto.createHash('sha256').update(JSON.stringify(result.flows)).digest("hex");
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -95,7 +95,7 @@ var storageModuleInterface = {
|
|||||||
|
|
||||||
return credentialSavePromise.then(function() {
|
return credentialSavePromise.then(function() {
|
||||||
return storageModule.saveFlows(flows, user).then(function() {
|
return storageModule.saveFlows(flows, user).then(function() {
|
||||||
return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex");
|
return crypto.createHash('sha256').update(JSON.stringify(config.flows)).digest("hex");
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -33,16 +33,15 @@ describe("library api", function() {
|
|||||||
should.not.exist(library.getExampleFlowPath('foo','bar'));
|
should.not.exist(library.getExampleFlowPath('foo','bar'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a valid example path', function(done) {
|
it('returns valid example paths', function(done) {
|
||||||
library.init();
|
library.init();
|
||||||
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
|
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
|
||||||
try {
|
try {
|
||||||
var flows = library.getExampleFlows();
|
var flows = library.getExampleFlows();
|
||||||
flows.should.deepEqual({"test-module":{"f":["one"]}});
|
flows.should.deepEqual({"test-module":{"f":["1.2.3","one"]}});
|
||||||
|
|
||||||
var examplePath = library.getExampleFlowPath('test-module','one');
|
var examplePath = library.getExampleFlowPath('test-module','one');
|
||||||
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'))
|
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'));
|
||||||
|
|
||||||
|
|
||||||
library.removeExamplesDir('test-module');
|
library.removeExamplesDir('test-module');
|
||||||
|
|
||||||
@ -57,6 +56,5 @@ describe("library api", function() {
|
|||||||
done(err);
|
done(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user