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:
		
							
								
								
									
										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); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |     }); | ||||||
|     }) |  | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user