diff --git a/packages/node_modules/@node-red/nodes/core/network/22-websocket.html b/packages/node_modules/@node-red/nodes/core/network/22-websocket.html index eae52b5fb..2976342cb 100644 --- a/packages/node_modules/@node-red/nodes/core/network/22-websocket.html +++ b/packages/node_modules/@node-red/nodes/core/network/22-websocket.html @@ -40,6 +40,92 @@ (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" }, + { value: "global", label: "global.", hasValue: true }, + { value: "env", label: "env", hasValue: true }, + ]; + 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() { $("#websocket-client-row").hide(); $("#node-input-mode").on("change", function() { @@ -192,7 +278,8 @@ value: "", label:RED._("node-red:websocket.sendheartbeat"), validate: RED.validators.number(/*blank allowed*/true) }, - subprotocol: {value:"",required: false} + subprotocol: {value:"",required: false}, + headers: { value: [] } }, inputs:0, outputs:0, @@ -200,6 +287,9 @@ return this.path; }, oneditprepare: function() { + + const node = this; + $("#node-config-input-path").on("change keyup paste",function() { $(".node-config-row-tls").toggle(/^wss:/i.test($(this).val())) }); @@ -214,14 +304,114 @@ if (!heartbeatActive) { $("#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 = $('
').css({ + overflow: 'hidden', + whiteSpace: 'nowrap', + display: 'flex' + }).appendTo(container); + const propertNameCell = $('
').css({ 'flex-grow': 1 }).appendTo(row); + const propertyName = $('', { class: "node-input-header-name", type: "text", style: "width: 100%" }) + .appendTo(propertNameCell) + .typedInput({ types: headerTypes }); + + const propertyValueCell = $('
').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row); + const propertyValue = $('', { 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 == "flow" || valueType == "global" || 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() { + + const node = this; + if (!/^wss:/i.test($("#node-config-input-path").val())) { $("#node-config-input-tls").val("_ADD_"); } if (!$("#node-config-input-hb-cb").prop("checked")) { $("#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 +489,15 @@
+
+ +
+
+
    +

    - +

    +
    diff --git a/packages/node_modules/@node-red/nodes/core/network/22-websocket.js b/packages/node_modules/@node-red/nodes/core/network/22-websocket.js index d2a862a46..acceabda0 100644 --- a/packages/node_modules/@node-red/nodes/core/network/22-websocket.js +++ b/packages/node_modules/@node-red/nodes/core/network/22-websocket.js @@ -58,6 +58,7 @@ module.exports = function(RED) { node.isServer = !/^ws{1,2}:\/\//i.test(node.path); node.closing = false; node.tls = n.tls; + node.upgradeHeaders = n.headers if (n.hb) { var heartbeat = parseInt(n.hb); @@ -96,6 +97,46 @@ module.exports = function(RED) { 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;ipayload 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 __path__.", "url1": "URL sollte ws:// oder wss:// Schema verwenden und auf einen vorhandenen WebSocket-Listener verweisen.", - "url2": "Standardmäßig enthält payload 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 payload 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": { "connected": "Verbunden __count__", diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index a7b583878..3df30b9c9 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -586,7 +586,8 @@ "path1": "By default, payload 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 __path__.", "url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.", - "url2": "By default, payload 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, payload 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": { "connected": "connected __count__", diff --git a/packages/node_modules/@node-red/nodes/locales/fr/messages.json b/packages/node_modules/@node-red/nodes/locales/fr/messages.json index 7890ce9a2..009c52331 100644 --- a/packages/node_modules/@node-red/nodes/locales/fr/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/fr/messages.json @@ -583,7 +583,8 @@ "path1": "Par défaut, payload 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 à __path__.", "url1": "L'URL doit utiliser le schéma ws:// ou wss:// et pointer vers un écouteur websocket existant.", - "url2": "Par défaut, payload 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, payload 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": { "connected": "__count__ connecté", diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index c1a3c9262..48b95c93f 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -586,7 +586,8 @@ "path1": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。", "path2": "このパスは __path__ の相対パスになります。", "url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。", - "url2": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。" + "url2": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。", + "headers": "ヘッダーは、HTTP から WS/WSS プロトコルへのプロトコル アップグレード メカニズム中にのみ送信されます。" }, "status": { "connected": "接続数 __count__", diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json index c0f57591f..c82e0f51b 100644 --- a/packages/node_modules/@node-red/nodes/locales/ko/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ko/messages.json @@ -451,7 +451,8 @@ "path1": "표준으로는 payload 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.", "path2": "This path will be relative to __path__.", "url1": "URL에는 ws:// 또는 wss:// 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.", - "url2": "표준으로는 payload 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다." + "url2": "표준으로는 payload 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.", + "headers": "헤더는 HTTP에서 WS/WSS 프로토콜로 프로토콜 업그레이드 메커니즘 중에만 제출됩니다." }, "status": { "connected": "접속 수 __count__", diff --git a/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json b/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json index 00058ddf3..274053bc6 100644 --- a/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json @@ -573,7 +573,8 @@ "path1": "Por padrão, a carga útil 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 __path__.", "url1": "A URL deve usar o esquema ws:// ou wss:// e apontar para um ouvinte de websocket existente.", - "url2": "Por padrão, carga útil 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, carga útil 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": { "connected": "conectado __count__", diff --git a/packages/node_modules/@node-red/nodes/locales/ru/messages.json b/packages/node_modules/@node-red/nodes/locales/ru/messages.json index 58ccf90fe..d2bb5777e 100644 --- a/packages/node_modules/@node-red/nodes/locales/ru/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ru/messages.json @@ -475,7 +475,8 @@ "path1": "По умолчанию payload будет содержать данные, которые будут отправлены или получены из websocket. Слушатель может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.", "path2": "Путь будет относительно __path__.", "url1": "URL должен использовать схему ws:// или wss:// и указывать на существующего слушателя websocket.", - "url2": "По умолчанию payload будет содержать данные, которые будут отправлены или получены из websocket. Клиент может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON." + "url2": "По умолчанию payload будет содержать данные, которые будут отправлены или получены из websocket. Клиент может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.", + "headers": "Заголовки передаются только во время механизма обновления протокола с HTTP на протокол WS/WSS." }, "status": { "connected": "подключен __count__", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 3d0cf8007..7a0ea4ae4 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -576,7 +576,8 @@ "path1": "默认情况下,payload将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.", "path2": "这条路径将相对于 __path__.", "url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.", - "url2": "默认情况下,payload 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象." + "url2": "默认情况下,payload 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象.", + "headers": "标头仅在协议升级机制期间提交,从 HTTP 到 WS/WSS 协议." }, "status": { "connected": "已连接数量 __count__", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index d63c47865..f43531fb1 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -471,7 +471,8 @@ "path1": "預設情況下,payload將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.", "path2": "這條路徑將相對於 __path__.", "url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.", - "url2": "預設情況下,payload 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件." + "url2": "預設情況下,payload 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件.", + "headers": "標頭僅在協定升級機制期間提交,從 HTTP 到 WS/WSS 協定." }, "status": { "connected": "連接數 __count__",