diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70d36deb1..cf871716a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18, 20, 22] + node-version: [18, 20, 22.4.x] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 062fbc944..e4ebd3087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +#### 4.0.2: Maintenance Release + +Editor + + - Use a more subtle border on the header (#4818) @bonanitech + - Improve the editor's French translations (#4824) @GogoVega + - Clean up orphaned editors (#4821) @Steve-Mcl + - Fix node validation if the property is not required (#4812) @GogoVega + - Ensure mermaid.min.js is cached properly between loads of the editor (#4817) @knolleary + +Runtime + + - Allow auth cookie name to be customised (#4815) @knolleary + - Guard against undefined sessions in multiplayer (#4816) @knolleary + +#### 4.0.1: Maintenance Release + +Editor + + - Ensure subflow instance credential property values are extracted (#4802) @knolleary + - Use `_ADD_` value for both `add new...` and `none` options (#4800) @GogoVega + - Fix the config node select value assignment (#4788) @GogoVega + - Add tooltip for number of subflow instance on info tab (#4786) @kazuhitoyokoi + - Add Japanese translations for v4.0.0 (#4785) @kazuhitoyokoi + +Runtime + + - Ensure group nodes are properly exported in /flow api (#4803) @knolleary + + Nodes + + - Joins: make using msg.parts optional in join node (#4796) @dceejay + - HTTP Request: UI proxy should setup agents for both http_proxy and https_proxy (#4794) @Steve-Mcl + - HTTP Request: Remove default user agent (#4791) @Steve-Mcl + #### 4.0.0: Milestone Release This marks the next major release of Node-RED. The following changes represent diff --git a/package.json b/package.json index 5783ef911..6015c0c9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "4.0.0", + "version": "4.1.0-beta.0", "description": "Low-code programming for event-driven applications", "homepage": "https://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index c5e1d93c7..30ff06756 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -182,6 +182,10 @@ function genericStrategy(adminApp,strategy) { maxAge: null, ...settings.httpAdminCookieOptions } + if (sessionOptions.cookie.name){ + sessionOptions.name = sessionOptions.cookie.name + delete sessionOptions.cookie.name + } } adminApp.use(session(sessionOptions)); //TODO: all passport references ought to be in ./auth @@ -217,10 +221,10 @@ function genericStrategy(adminApp,strategy) { adminApp.get('/auth/strategy', passport.authenticate(strategy.name, { session:false, - failureMessage: true, - failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed' + failWithError: true, + failureMessage: true }), - completeGenerateStrategyAuth, + completeGenericStrategyAuth, handleStrategyError ); @@ -232,14 +236,14 @@ function genericStrategy(adminApp,strategy) { passport.authenticate(strategy.name, { session:false, failureMessage: true, - failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed' + failWithError: true }), - completeGenerateStrategyAuth, + completeGenericStrategyAuth, handleStrategyError ); } -function completeGenerateStrategyAuth(req,res) { +function completeGenericStrategyAuth(req,res) { var tokens = req.user.tokens; delete req.user.tokens; // Successful authentication, redirect home. @@ -249,6 +253,8 @@ function handleStrategyError(err, req, res, next) { if (res.headersSent) { return next(err) } + // Remove the header that passport auto-adds as we don't need it + res.removeHeader('WWW-Authenticate') log.audit({event: "auth.login.fail.oauth",error:err.toString()}); res.redirect(settings.httpAdminRoot + '?session_message='+err.toString()); } diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 5700ed65e..2c9d571bf 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "4.0.0", + "version": "4.1.0-beta.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "4.0.0", - "@node-red/editor-client": "4.0.0", + "@node-red/util": "4.1.0-beta.0", + "@node-red/editor-client": "4.1.0-beta.0", "bcryptjs": "2.4.3", "body-parser": "1.20.2", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/locales/fr/editor.json b/packages/node_modules/@node-red/editor-client/locales/fr/editor.json index bacd5b70f..950266007 100644 --- a/packages/node_modules/@node-red/editor-client/locales/fr/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/fr/editor.json @@ -27,7 +27,8 @@ "lock": "Verrouiller", "unlock": "Déverrouiller", "locked": "Verrouillé", - "unlocked": "Déverrouillé" + "unlocked": "Déverrouillé", + "format": "Format" }, "type": { "string": "chaîne de caractères", @@ -54,10 +55,10 @@ "workspace": { "defaultName": "Flux __number__", "editFlow": "Modifier le flux : __name__", - "confirmDelete": "Confirmation de la suppression", - "delete": "Etes-vous sûr de vouloir supprimer '__label__'?", - "dropFlowHere": "Déposer le flux ici", - "dropImageHere": "Déposer l'image ici", + "confirmDelete": "Confirmer la suppression", + "delete": "Êtes-vous sûr de vouloir supprimer '__label__' ?", + "dropFlowHere": "Lâchez le flux ici", + "dropImageHere": "Lâchez l'image ici", "addFlow": "Ajouter un flux", "addFlowToRight": "Ajouter un flux à droite", "closeFlow": "Fermer le flux", @@ -74,7 +75,7 @@ "enabled": "Activé", "disabled": "Désactivé", "info": "Description", - "selectNodes": "Cliquer sur les noeuds pour sélectionner", + "selectNodes": "Cliquer pour sélectionner", "enableFlow": "Activer le flux", "disableFlow": "Désactiver le flux", "lockFlow": "Verrouiller le flux", @@ -98,7 +99,7 @@ "rtl": "De droite à gauche", "auto": "Contextuel", "language": "Langue", - "browserDefault": "Navigateur par défaut" + "browserDefault": "Par défaut du Navigateur" }, "sidebar": { "show": "Afficher la barre latérale" @@ -134,7 +135,7 @@ "disableSelectedNodes": "Désactiver les noeuds sélectionnés", "showSelectedNodeLabels": "Afficher les étiquettes des noeuds sélectionnés", "hideSelectedNodeLabels": "Masquer les étiquettes des noeuds sélectionnés", - "showWelcomeTours": "Afficher les visites guidées pour les nouvelles versions", + "showWelcomeTours": "Afficher les visites guidées des nouvelles versions", "help": "Site web de Node-RED", "projects": "Projets", "projects-new": "Nouveau projet", @@ -143,7 +144,7 @@ "showNodeLabelDefault": "Afficher l'étiquette des noeuds nouvellement ajoutés", "codeEditor": "Éditeur de code", "groups": "Groupes", - "groupSelection": "Grouper cette sélection", + "groupSelection": "Grouper la sélection", "ungroupSelection": "Dégrouper la sélection", "groupMergeSelection": "Fusionner la sélection", "groupRemoveSelection": "Supprimer du groupe", @@ -155,7 +156,7 @@ "alignMiddle": "Aligner au milieu", "alignBottom": "Aligner en bas", "distributeHorizontally": "Répartir horizontalement", - "distributeVertically": "Distribuer verticalement", + "distributeVertically": "Répartir verticalement", "moveToBack": "Déplacer vers l'arrière", "moveToFront": "Déplacer vers l'avant", "moveBackwards": "Reculer", @@ -163,21 +164,21 @@ } }, "actions": { - "toggle-navigator": "Basculer de navigateur", - "zoom-out": "Dézoomer", - "zoom-reset": "Réinitialiser le zoom", + "toggle-navigator": "Basculer l'affichage du navigateur", + "zoom-out": "Réduire", + "zoom-reset": "Réinitialiser", "zoom-in": "Agrandir", "search-flows": "Rechercher le flux", "search-prev": "Précédent", "search-next": "Suivant", - "search-counter": "\"__term__\" __result__ de __count__" + "search-counter": "\"__term__\" __result__ sur __count__" }, "user": { "loggedInAs": "Connecté en tant que __name__", "username": "Nom d'utilisateur", "password": "Mot de passe", - "login": "Connexion", - "loginFailed": "Échec de la connexion", + "login": "Se connecter", + "loginFailed": "Échec de connexion", "notAuthorized": "Pas autorisé", "errors": { "settings": "Vous devez être connecté pour accéder aux paramètres", @@ -193,16 +194,16 @@ "warning": "Attention : __message__", "warnings": { "undeployedChanges": "Le noeud a des modifications non déployées", - "nodeActionDisabled": "Actions de noeud désactivées", - "nodeActionDisabledSubflow": "Actions de noeud désactivées dans le sous-flux", + "nodeActionDisabled": "Les actions du noeud sont désactivées", + "nodeActionDisabledSubflow": "Les actions de noeud sont désactivées à l'intérieur du sous-flux", "missing-types": "
Flux arrêtés en raison de types de noeuds manquants.
", "missing-modules": "Flux arrêtés en raison de modules manquants.
", - "safe-mode": "Flux arrêtés en mode sans échec.
Vous pouvez modifier vos flux et déployer les changements pour redémarrer.
", + "safe-mode": "Flux arrêtés en mode sans échec.
Vous pouvez modifier vos flux et déployer ensuite les changements afin de démarrer vos flux.
", "restartRequired": "Node-RED doit être redémarré pour mettre à jour les modules", - "credentials_load_failed": "Les flux se sont arrêtés car les informations d'identification n'ont pas pu être déchiffrées.
Le fichier d'informations d'identification du flux est chiffré, mais la clé de chiffrement du projet est manquante ou invalide.
", - "credentials_load_failed_reset": "Les informations d'identification n'ont pas pu être déchiffrées
Le fichier d'informations d'identification du flux est chiffré, mais la clé de chiffrement du projet est manquante ou invalide.
Le fichier d'informations d'identification du flux sera réinitialisé lors du prochain déploiement. Toutes les informations d'identification de flux existantes seront perdues.
", + "credentials_load_failed": "Les flux se sont arrêtés car les informations d'identification n'ont pas pu être déchiffrées.
Le fichier d'informations d'identification du flux est chiffré mais la clé de chiffrement du projet est manquante ou invalide.
", + "credentials_load_failed_reset": "Les informations d'identification n'ont pas pu être déchiffrées
Le fichier d'informations d'identification du flux est chiffré mais la clé de chiffrement du projet est manquante ou invalide.
Le fichier d'informations d'identification du flux sera réinitialisé lors du prochain déploiement. Toutes les informations d'identification des flux existants seront perdues.
", "missing_flow_file": "Fichier contenant les flux introuvable.
Le projet n'est pas configuré avec un fichier de flux.
", - "missing_package_file": "Fichier de paquetage du projet introuvable.
Il manque au projet un fichier package.json.
", + "missing_package_file": "Fichier de paquetage du projet introuvable.
Il manque au projet le fichier package.json
.
Le projet est vide.
Voulez-vous créer un ensemble de fichiers de projet par défaut ?
Sinon, vous devrez ajouter manuellement des fichiers au projet (en dehors de l'éditeur).
Le projet '__project__' est introuvable.
", "git_merge_conflict": "La fusion automatique des modifications a échoué.
Corriger les conflits non fusionnés, puis valider le résultat.
" @@ -219,7 +220,7 @@ }, "project": { "change-branch": "Changer pour une branche locale '__project__'", - "merge-abort": "Git fusion abandonnée", + "merge-abort": "Fusion Git abandonnée", "loaded": "Projet '__project__' chargé", "updated": "Projet '__project__' mis à jour", "pull": "Projet '__project__' rechargé", @@ -352,7 +353,7 @@ "backgroundUpdate": "Les flux sur le serveur ont été mis à jour.", "conflictChecking": "Vérifier si les modifications peuvent être fusionnées automatiquement", "conflictAutoMerge": "Les modifications n'incluent aucun conflit et peuvent être fusionnées automatiquement.", - "conflictManualMerge": "Les changements incluent des conflits qui doivent être résolus avant de pouvoir être déployés.", + "conflictManualMerge": "Les modifications incluent des conflits qui doivent être résolus avant de pouvoir être déployées.", "plusNMore": "+ __count__ en plus" } }, @@ -372,16 +373,17 @@ "deleted": "supprimé", "flowDeleted": "flux supprimé", "flowAdded": "flux ajouté", + "moved": "déplacé", "movedTo": "déplacé vers __id__", "movedFrom": "déplacé depuis __id__" }, "nodeCount": "__count__ noeud", "nodeCount_plural": "__count__ noeuds", "local": "Changements locaux", - "remote": "Modifications à distance", + "remote": "Changements distants", "reviewChanges": "Examiner les modifications", "noBinaryFileShowed": "Impossible d'afficher le contenu du fichier binaire", - "viewCommitDiff": "Afficher les modifications de validation", + "viewCommitDiff": "Afficher les modifications de la validation", "compareChanges": "Comparer les modifications", "saveConflict": "Enregistrer la résolution des conflits", "conflictHeader": "__resolved__ sur __unresolved__ conflit(s) résolu(s)", @@ -395,9 +397,9 @@ "edit": "Modifier le modèle du sous-flux", "subflowInstances": "Il existe __count__ instance de ce modèle de sous-flux", "subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux", - "editSubflowProperties": "modifier les propriétés", - "input": "Entrées:", - "output": "Sorties:", + "editSubflowProperties": "Modifier les propriétés", + "input": "Entrées :", + "output": "Sorties :", "status": "Statut du noeud", "deleteSubflow": "Supprimer le sous-flux", "confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?", @@ -411,7 +413,7 @@ "version": "Version", "versionPlaceholder": "x.y.z", "keys": "Mots clés", - "keysPlaceholder": "Mots clés séparés par des virgules", + "keysPlaceholder": "Mots clés séparés par une virgule", "author": "Auteur", "authorPlaceholder": "Votre nomÉchec du chargement du catalogue de noeuds.
Vérifier la console du navigateur pour plus d'informations
", @@ -651,7 +653,7 @@ }, "confirm": { "install": { - "body": "Installation de '__module__'
Avant l'installation, veuiller lire la documentation du noeud. Certains noeuds ont des dépendances qui ne peuvent pas être résolues automatiquement et peuvent nécessiter un redémarrage de Node-RED.
", + "body": "Installation de '__module__'
Avant l'installation, veuillez lire la documentation du noeud. Certains noeuds ont des dépendances qui ne peuvent pas être résolues automatiquement et peuvent nécessiter un redémarrage de Node-RED.
", "title": "Installer les noeuds" }, "remove": { @@ -666,7 +668,7 @@ "title": "Mettre à jour les noeuds" }, "cannotUpdate": { - "body": "Une mise à jour pour ce noeud est disponible, mais il n'est pas installé dans un emplacement que le gestionnaire de palette peut mettre à jour.Impossible d'extraire les modifications à distance ; vos modifications locales non mises en place seraient écrasées.
Valider vos modifications et réessayer.
", - "showUnstagedChanges": "Afficher les modifications non mise en place", + "unablePull": "Impossible d'extraire les modifications à distance; vos modifications locales non mises en place seraient écrasées.
Valider vos modifications et réessayer.
", + "showUnstagedChanges": "Afficher les modifications non indexées", "connectionFailed": "Impossible de se connecter au référentiel distant: ", "pullUnrelatedHistory": "Le réferentiel distant a un historique de validations sans rapport.
Êtes-vous sûr de vouloir extraire les modifications dans votre référentiel local ?
", - "pullChanges": "Tirer les changements", + "pullChanges": "Tirer les changements distants", "history": "Historique", "projectHistory": "Historique du projet", "daysAgo": "il y a __count__ jour", @@ -974,7 +976,7 @@ "result": "Résultat", "format": "Format", "compatMode": "Mode de compatibilité activé", - "compatModeDesc": " L'expression actuelle semble toujours faire référence à msg
et sera donc évaluée en mode de compatibilité. Veuiller mettre à jour l'expression pour ne pas utiliser msg
car ce mode sera supprimé à l'avenir.
Lorsque la prise en charge de JSONata a été ajoutée pour la première fois à Node-RED, il fallait que l'expression référencie l'objet msg
. Par exemple, msg.payload
serait utilisé pour accéder à la charge utile.
Cela n'est plus nécessaire car l'expression sera évaluée directement par rapport au message. Pour accéder à la charge utile, l'expression doit être simplement charge utile
.
L'expression actuelle semble toujours faire référence à msg
et sera donc évaluée en mode de compatibilité. Veuillez mettre à jour l'expression pour ne pas utiliser msg
car ce mode sera supprimé à l'avenir.
Lorsque la prise en charge de JSONata a été ajoutée pour la première fois à Node-RED, il fallait que l'expression référencie l'objet msg
. Par exemple, msg.payload
serait utilisé pour accéder à la charge utile.
Cela n'est plus nécessaire car l'expression sera évaluée directement par rapport au message. Pour accéder à la charge utile, l'expression doit être simplement charge utile
.
msg.rate
+ in Millisekunden enthält. Dies trifft nur zu, wenn in der Node konfiguriert ist, das empfangene
+ Nachrichten den konfigurierten Wert überschreiben können.true
gesetzt ist, Anschließend wird die Nachricht an den Anfang der Warteschlange verschoben
+ und als nächstes freigegeben. Dies kann in Kombination mit msg.flush=1
verwendet werden, um sofort erneut zu senden.
+ Wenn Verzögerung als Nachrichtenaktion eingestellt ist, kann die Verzögerungszeit ein fixer Wert,
diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json
index 051d65f47..a51e504cf 100644
--- a/packages/node_modules/@node-red/nodes/locales/de/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json
@@ -912,6 +912,7 @@
"objectSend": "Sende eine Nachricht für jedes Schlüssel/Wert-Paar",
"strBuff": "string / buffer",
"array": "array",
+ "splitThe": "Split",
"splitUsing": "Aufteilung",
"splitLength": "feste Längen von",
"stream": "Als Nachrichtenstrom behandeln (Streaming-Modus)",
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 560d192c1..bc89992e2 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
@@ -1011,12 +1011,13 @@
"tip": "Tip: The filename should be an absolute path, otherwise it will be relative to the working directory of the Node-RED process."
},
"split": {
- "split": "Split",
+ "split": "split",
"intro": "Split msg.payload
based on type:",
"object": "Object",
"objectSend": "Send a message for each key/value pair",
"strBuff": "String / Buffer",
"array": "Array",
+ "splitThe": "Split the",
"splitUsing": "Split using",
"splitLength": "Fixed length of",
"stream": "Handle as a stream of messages",
@@ -1046,6 +1047,7 @@
"joinedUsing": "joined using",
"send": "Send the message:",
"afterCount": "After a number of message parts",
+ "useparts": "Use existing msg.parts property",
"count": "count",
"subsequent": "and every subsequent message.",
"afterTimeout": "After a timeout following the first message",
@@ -1112,6 +1114,7 @@
"too-many": "too many pending messages in batch node",
"unexpected": "unexpected mode",
"no-parts": "no parts property in message",
+ "honourParts": "Allow msg.parts to also complete batch operation.",
"error": {
"invalid-count": "Invalid count",
"invalid-overlap": "Invalid overlap",
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 fe81a80ad..8d38ac077 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
@@ -1017,6 +1017,7 @@
"objectSend": "各key/valueペアのメッセージを送信",
"strBuff": "文字列 / バッファ",
"array": "配列",
+ "splitThe": "に基づく",
"splitUsing": "分割",
"splitLength": "固定長",
"stream": "メッセージのストリームとして処理",
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 274053bc6..51e1fd897 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
@@ -44,7 +44,7 @@
"global": "contexto global",
"str": "Cadeia de caracteres",
"num": "número",
- "bool": "booliano",
+ "bool": "booliano",
"json": "objeto",
"bin": "Armazenamento temporário",
"date": "Carimbo de data/hora",
@@ -352,8 +352,8 @@
}
},
"trigger": {
- "send": "Enviar",
- "then": "então",
+ "send": "Enviar",
+ "then": "então",
"then-send": "então enviem",
"output": {
"string": "a cadeia de caracteres",
@@ -446,7 +446,7 @@
"staticTopic": "Assinar um tópico único",
"dynamicTopic": "Assinatura dinâmica",
"auto-connect": "Conectar automaticamente",
- "auto-mode-depreciated": "Esta opção está deprecada. Favor utilizar o novo modo de auto-detecção."
+ "auto-mode-depreciated": "Esta opção está deprecada. Favor utilizar o novo modo de auto-detecção."
},
"sections-label": {
"birth-message": "Mensagem enviada na conexão (mensagem de nascimento)",
@@ -466,8 +466,8 @@
"close-topic": "Deixe em branco para desativar a mensagem de fechamento"
},
"state": {
- "connected": "Conectado ao negociante: _ broker _",
- "disconnected": "Desconectado do negociante: _ broker _",
+ "connected": "Conectado ao negociante: _ broker _",
+ "disconnected": "Desconectado do negociante: _ broker _",
"connect-failed": "Falha na conexão com o negociante: __broker__",
"broker-disconnected": "Cliente de negociante __broker__ desconectado: __reasonCode__ __reasonString__"
},
@@ -898,7 +898,7 @@
"o2j": "Objeto para opções JSON",
"pretty": "Formatar cadeia de caracteres JSON",
"action": "Ação",
- "property": "Propriedade",
+ "property": "Propriedade",
"actions": {
"toggle": "Converter entre cadeia de caracteres JSON e Objeto",
"str": "Sempre converter em cadeia de caracteres JSON",
@@ -929,7 +929,7 @@
"write": "escrever arquivo",
"read": "ler arquivo",
"filename": "Nome do arquivo",
- "path": "caminho",
+ "path": "caminho",
"action": "Ação",
"addnewline": "Adicionar nova linha (\\n) a cada carga útil?",
"createdir": "Criar diretório se não existir?",
@@ -994,6 +994,7 @@
"objectSend": "Envia uma mensagem para cada par chave/valor",
"strBuff": "Cadeia de caracteres / Armazenamento Temporário",
"array": "Matriz",
+ "splitThe": "Dividir",
"splitUsing": "Dividir usando",
"splitLength": "Comprimento fixo de",
"stream": "Tratar como uma transmissão de mensagens",
@@ -1066,9 +1067,9 @@
"batch" : {
"batch": "lote",
"mode": {
- "label": "Modo",
- "num-msgs": "Agrupar por número de mensagens",
- "interval": "Agrupar por intervalo de tempo",
+ "label": "Modo",
+ "num-msgs": "Agrupar por número de mensagens",
+ "interval": "Agrupar por intervalo de tempo",
"concat": "Concatenar sequências"
},
"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 d2bb5777e..2694ac6a5 100644
--- a/packages/node_modules/@node-red/nodes/locales/ru/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ru/messages.json
@@ -874,6 +874,7 @@
"objectSend":"Отправлять сообщение для каждой пары ключ/значение",
"strBuff":"Строка / Буфер",
"array":"Массив",
+ "splitThe": "Pазделить",
"splitUsing":"С помощью",
"splitLength":"Фикс. длина",
"stream":"Обрабатывать как поток сообщений",
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 7a0ea4ae4..7d5616a8f 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
@@ -997,6 +997,7 @@
"objectSend": "每个键值对作为单个消息发送",
"strBuff": "字符串 / Buffer",
"array": "数组",
+ "splitThe": "Split",
"splitUsing": "拆分使用",
"splitLength": "固定长度",
"stream": "作为消息流处理",
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 f43531fb1..7d16c5817 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
@@ -866,6 +866,7 @@
"objectSend": "每個鍵值對作為單個消息發送",
"strBuff": "字串 / Buffer",
"array": "陣列",
+ "splitThe": "Split",
"splitUsing": "拆分使用",
"splitLength": "固定長度",
"stream": "作為消息流處理",
diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json
index 9b9279691..ff26f1e6f 100644
--- a/packages/node_modules/@node-red/nodes/package.json
+++ b/packages/node_modules/@node-red/nodes/package.json
@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
- "version": "4.0.0",
+ "version": "4.1.0-beta.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json
index 11c28ce77..7f7a0a694 100644
--- a/packages/node_modules/@node-red/registry/package.json
+++ b/packages/node_modules/@node-red/registry/package.json
@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
- "version": "4.0.0",
+ "version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,7 +16,7 @@
}
],
"dependencies": {
- "@node-red/util": "4.0.0",
+ "@node-red/util": "4.1.0-beta.0",
"clone": "2.1.2",
"fs-extra": "11.2.0",
"semver": "7.5.4",
diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js
index 231f39515..f21bd56f9 100644
--- a/packages/node_modules/@node-red/runtime/lib/flows/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js
@@ -645,16 +645,27 @@ function getFlow(id) {
if (id !== 'global') {
result.nodes = [];
}
+
+ if (flow.groups) {
+ var nodeIds = Object.keys(flow.groups);
+ if (nodeIds.length > 0) {
+ nodeIds.forEach(function(nodeId) {
+ var node = jsonClone(flow.groups[nodeId]);
+ delete node.credentials;
+ result.nodes.push(node)
+ })
+ }
+ }
if (flow.nodes) {
var nodeIds = Object.keys(flow.nodes);
if (nodeIds.length > 0) {
- result.nodes = nodeIds.map(function(nodeId) {
+ nodeIds.forEach(function(nodeId) {
var node = jsonClone(flow.nodes[nodeId]);
if (node.type === 'link out') {
delete node.wires;
}
delete node.credentials;
- return node;
+ result.nodes.push(node)
})
}
}
@@ -680,6 +691,17 @@ function getFlow(id) {
delete node.credentials
return node
});
+ if (subflow.groups) {
+ var nodeIds = Object.keys(subflow.groups);
+ if (nodeIds.length > 0) {
+ nodeIds.forEach(function(nodeId) {
+ var node = jsonClone(subflow.groups[nodeId]);
+ delete node.credentials;
+ subflow.nodes.push(node)
+ })
+ }
+ delete subflow.groups
+ }
if (subflow.configs) {
var configIds = Object.keys(subflow.configs);
subflow.configs = configIds.map(function(id) {
diff --git a/packages/node_modules/@node-red/runtime/lib/multiplayer/index.js b/packages/node_modules/@node-red/runtime/lib/multiplayer/index.js
index adfa63c28..08cb0d5a1 100644
--- a/packages/node_modules/@node-red/runtime/lib/multiplayer/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/multiplayer/index.js
@@ -23,14 +23,16 @@ module.exports = {
if (existingSessionId) {
connections.delete(opts.session)
const session = sessions.get(existingSessionId)
- session.active = false
- session.idleTimeout = setTimeout(() => {
- sessions.delete(existingSessionId)
- }, 30000)
- runtime.events.emit('comms', {
- topic: "multiplayer/connection-removed",
- data: { session: existingSessionId }
- })
+ if (session) {
+ session.active = false
+ session.idleTimeout = setTimeout(() => {
+ sessions.delete(existingSessionId)
+ }, 30000)
+ runtime.events.emit('comms', {
+ topic: "multiplayer/connection-removed",
+ data: { session: existingSessionId }
+ })
+ }
}
})
runtime.events.on('comms:message:multiplayer/connect', (opts) => {
@@ -91,29 +93,31 @@ module.exports = {
const sessionId = connections.get(opts.session)
const session = sessions.get(sessionId)
- if (opts.user) {
- if (session.user.anonymous !== opts.user.anonymous) {
- session.user = opts.user
- runtime.events.emit('comms', {
- topic: 'multiplayer/connection-added',
- excludeSession: opts.session,
- data: session
- })
+ if (session) {
+ if (opts.user) {
+ if (session.user.anonymous !== opts.user.anonymous) {
+ session.user = opts.user
+ runtime.events.emit('comms', {
+ topic: 'multiplayer/connection-added',
+ excludeSession: opts.session,
+ data: session
+ })
+ }
}
- }
- session.location = opts.data
+ session.location = opts.data
- const payload = {
- session: sessionId,
- workspace: opts.data.workspace,
- node: opts.data.node
+ const payload = {
+ session: sessionId,
+ workspace: opts.data.workspace,
+ node: opts.data.node
+ }
+ runtime.events.emit('comms', {
+ topic: 'multiplayer/location',
+ data: payload,
+ excludeSession: opts.session
+ })
}
- runtime.events.emit('comms', {
- topic: 'multiplayer/location',
- data: payload,
- excludeSession: opts.session
- })
})
}
}
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json
index 9881819d7..76cfac8a0 100644
--- a/packages/node_modules/@node-red/runtime/package.json
+++ b/packages/node_modules/@node-red/runtime/package.json
@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
- "version": "4.0.0",
+ "version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
- "@node-red/registry": "4.0.0",
- "@node-red/util": "4.0.0",
+ "@node-red/registry": "4.1.0-beta.0",
+ "@node-red/util": "4.1.0-beta.0",
"async-mutex": "0.5.0",
"clone": "2.1.2",
"express": "4.19.2",
diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json
index 22ac1fda5..d952e5989 100644
--- a/packages/node_modules/@node-red/util/package.json
+++ b/packages/node_modules/@node-red/util/package.json
@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
- "version": "4.0.0",
+ "version": "4.1.0-beta.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json
index f653eb15f..3c33133ec 100644
--- a/packages/node_modules/node-red/package.json
+++ b/packages/node_modules/node-red/package.json
@@ -1,6 +1,6 @@
{
"name": "node-red",
- "version": "4.0.0",
+ "version": "4.1.0-beta.0",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"flow"
],
"dependencies": {
- "@node-red/editor-api": "4.0.0",
- "@node-red/runtime": "4.0.0",
- "@node-red/util": "4.0.0",
- "@node-red/nodes": "4.0.0",
+ "@node-red/editor-api": "4.1.0-beta.0",
+ "@node-red/runtime": "4.1.0-beta.0",
+ "@node-red/util": "4.1.0-beta.0",
+ "@node-red/nodes": "4.1.0-beta.0",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"cors": "2.8.5",
diff --git a/test/nodes/core/network/21-httprequest_spec.js b/test/nodes/core/network/21-httprequest_spec.js
index 7d36903a5..0df73b970 100644
--- a/test/nodes/core/network/21-httprequest_spec.js
+++ b/test/nodes/core/network/21-httprequest_spec.js
@@ -17,6 +17,8 @@
var http = require("http");
var https = require("https");
var should = require("should");
+var sinon = require("sinon");
+var httpProxyHelper = require("nr-test-utils").require("@node-red/nodes/core/network/lib/proxyHelper.js");
var express = require("express");
var bodyParser = require('body-parser');
var stoppable = require('stoppable');
@@ -493,6 +495,7 @@ describe('HTTP Request Node', function() {
});
afterEach(function() {
+ sinon.restore();
process.env.http_proxy = preEnvHttpProxyLowerCase;
process.env.HTTP_PROXY = preEnvHttpProxyUpperCase;
// On Windows, if environment variable of NO_PROXY that includes lower cases
@@ -1799,27 +1802,80 @@ describe('HTTP Request Node', function() {
})
});
- //Removing HTTP Proxy testcases as GOT + Proxy_Agent doesn't work with mock'd proxy
- /* */
- it('should use http_proxy', function(done) {
- var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')},
- {id:"n2", type:"helper"}];
+ it('should use env var http_proxy', function(done) {
+ const url = getTestURL('/postInspect')
+ const proxyUrl = "http://localhost:" + testProxyPort
+
+ const flow = [
+ { id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: url },
+ { id: "n2", type: "helper" },
+ ];
+ const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
+ const testNode = [httpRequestNode, httpProxyNode];
deleteProxySetting();
- process.env.http_proxy = "http://localhost:" + testProxyPort;
- helper.load(httpRequestNode, flow, function() {
- var n1 = helper.getNode("n1");
- var n2 = helper.getNode("n2");
- n2.on("input", function(msg) {
- try {
- msg.should.have.property('statusCode',200);
- msg.payload.should.have.property('headers');
- //msg.payload.headers.should.have.property('x-testproxy-header','foobar');
- done();
- } catch(err) {
- done(err);
- }
- });
- n1.receive({payload:"foo"});
+ process.env.http_proxy = proxyUrl
+ helper.load(testNode, flow, function (msg) {
+ try {
+ // static URL set in the nodes configuration and the proxy will be setup upon initialisation
+ proxySpy.calledOnce.should.be.true()
+ proxySpy.calledWith(url, { }).should.be.true()
+ proxySpy.returnValues[0].should.be.equal(proxyUrl)
+ done()
+ } catch (err) {
+ done(err);
+ }
+ });
+ });
+
+ it('should use env var https_proxy', function(done) {
+ const url = getSslTestURL('/postInspect')
+ const proxyUrl = "http://localhost:" + testProxyPort
+
+ const flow = [
+ { id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: url },
+ { id: "n2", type: "helper" },
+ ];
+ const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
+ const testNode = [httpRequestNode, httpProxyNode];
+ deleteProxySetting();
+ process.env.https_proxy = proxyUrl
+ helper.load(testNode, flow, function (msg) {
+ try {
+ // static URL set in the nodes configuration and the proxy will be setup upon initialisation
+ proxySpy.calledOnce.should.be.true()
+ proxySpy.calledWith(url, { }).should.be.true()
+ proxySpy.returnValues[0].should.be.equal(proxyUrl)
+ done()
+ } catch (err) {
+ done(err);
+ }
+ });
+ });
+
+ it('should not use env var http*_proxy when no_proxy is set', function(done) {
+ const url = getSslTestURL('/postInspect')
+ const proxyUrl = "http://localhost:" + testProxyPort
+
+ const flow = [
+ { id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: url },
+ { id: "n2", type: "helper" },
+ ];
+ const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
+ const testNode = [httpRequestNode, httpProxyNode];
+ deleteProxySetting();
+ process.env.http_proxy = proxyUrl
+ process.env.https_proxy = proxyUrl
+ process.env.no_proxy = "localhost"
+ helper.load(testNode, flow, function (msg) {
+ try {
+ // static URL set in the nodes configuration and the proxy will be setup upon initialisation
+ proxySpy.calledOnce.should.be.true()
+ proxySpy.calledWith(url, { }).should.be.true()
+ proxySpy.returnValues[0].should.be.equal('')
+ done()
+ } catch (err) {
+ done(err);
+ }
});
});
@@ -1997,6 +2053,135 @@ describe('HTTP Request Node', function() {
});
});
+ it('should use UI proxy for statically configured URL', function (done) {
+ const url = getTestURL('/postInspect')
+ const proxyUrl = "http://localhost:" + testProxyPort
+ const flow = [
+ { id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: url, proxy: "n3" },
+ { id: "n2", type: "helper" },
+ { id: "n3", type: "http proxy", url: proxyUrl, noproxy: ["foo"] }
+ ];
+ const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
+ const testNode = [httpRequestNode, httpProxyNode];
+ deleteProxySetting();
+
+ // static URL set in the nodes configuration will cause the proxy setup to be called
+ // no no need to send a message to the node
+ helper.load(testNode, flow, function () {
+ try {
+ // ensure getProxyForUrl was called and returned the correct proxy URL
+ proxySpy.calledOnce.should.be.true()
+ proxySpy.calledWith(url, { env: { no_proxy: "foo", http_proxy: proxyUrl, https_proxy: proxyUrl } }).should.be.true()
+ proxySpy.returnValues[0].should.be.equal(proxyUrl)
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ });
+ it('should use UI proxy for HTTP URL passed in via msg', function (done) {
+ const url = getTestURL('/postInspect')
+ const proxyUrl = "http://localhost:" + testProxyPort
+ const flow = [
+ { id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: "", proxy: "n3" },
+ { id: "n2", type: "helper" },
+ { id: "n3", type: "http proxy", url: proxyUrl, noproxy: ["foo,bar"] }
+ ];
+ const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
+ const testNode = [httpRequestNode, httpProxyNode];
+ deleteProxySetting();
+ helper.load(testNode, flow, function () {
+ const n1 = helper.getNode("n1");
+ const n2 = helper.getNode("n2");
+ try {
+ proxySpy.calledOnce.should.be.false() // proxy setup should not be called when there is no URL to check needs proxying
+ } catch (err) {
+ done(err);
+ return
+ }
+ n2.on("input", function (msg) {
+ try {
+ // ensure getProxyForUrl was called and returned the correct proxy URL
+ proxySpy.calledOnce.should.be.true()
+ proxySpy.calledWith(url, { env: { no_proxy: "foo,bar", http_proxy: proxyUrl, https_proxy: proxyUrl } }).should.be.true()
+ proxySpy.returnValues[0].should.be.equal(proxyUrl)
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ n1.receive({ url: url });
+ });
+ });
+ it('should use UI proxy for HTTPS URL passed in via msg', function (done) {
+ const url = getSslTestURL('/postInspect')
+ const proxyUrl = "http://localhost:" + testProxyPort
+ const flow = [
+ { id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: "", proxy: "n3" },
+ { id: "n2", type: "helper" },
+ { id: "n3", type: "http proxy", url: proxyUrl, noproxy: ["foo,bar,baz"] }
+ ];
+ const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
+ const testNode = [httpRequestNode, httpProxyNode];
+ deleteProxySetting();
+ helper.load(testNode, flow, function () {
+ const n1 = helper.getNode("n1");
+ const n2 = helper.getNode("n2");
+ try {
+ proxySpy.calledOnce.should.be.false() // proxy setup should not be called when there is no URL to check needs proxying
+ } catch (err) {
+ done(err);
+ return
+ }
+ n2.on("input", function (msg) {
+ try {
+ // ensure getProxyForUrl was called and returned the correct proxy URL
+ proxySpy.calledOnce.should.be.true()
+ proxySpy.calledWith(url, { env: { no_proxy: "foo,bar,baz", http_proxy: proxyUrl, https_proxy: proxyUrl } }).should.be.true()
+ proxySpy.returnValues[0].should.be.equal(proxyUrl)
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ n1.receive({ url: url });
+ });
+ });
+ it('should not use UI proxy if noproxy excludes it', function (done) {
+ const url = getSslTestURL('/postInspect')
+ const proxyUrl = "http://localhost:" + testProxyPort
+ const flow = [
+ { id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: "", proxy: "n3" },
+ { id: "n2", type: "helper" },
+ { id: "n3", type: "http proxy", url: proxyUrl, noproxy: ["foo,localhost,baz"] }
+ ];
+ const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
+ const testNode = [httpRequestNode, httpProxyNode];
+ deleteProxySetting();
+ helper.load(testNode, flow, function () {
+ const n1 = helper.getNode("n1");
+ const n2 = helper.getNode("n2");
+ try {
+ proxySpy.calledOnce.should.be.false() // proxy setup should not be called when there is no URL to check needs proxying
+ } catch (err) {
+ done(err);
+ return
+ }
+ n2.on("input", function (msg) {
+ try {
+ // ensure getProxyForUrl was called and returned no proxy
+ proxySpy.calledOnce.should.be.true()
+ proxySpy.calledWith(url, { env: { no_proxy: "foo,localhost,baz", http_proxy: proxyUrl, https_proxy: proxyUrl } }).should.be.true()
+ proxySpy.returnValues[0].should.be.equal('')
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ n1.receive({ url: url });
+ });
+ });
+
});
describe('authentication', function() {
diff --git a/test/nodes/core/sequence/19-batch_spec.js b/test/nodes/core/sequence/19-batch_spec.js
index 2ebcb8d4d..b4025a3f8 100644
--- a/test/nodes/core/sequence/19-batch_spec.js
+++ b/test/nodes/core/sequence/19-batch_spec.js
@@ -98,7 +98,7 @@ describe('BATCH node', function() {
var n2 = helper.getNode("n2");
check_data(n1, n2, results, done);
for(var i = 0; i < 6; i++) {
- n1.receive({payload: i});
+ n1.receive({payload: i, parts: { count:6, index:i }});
}
});
}
@@ -168,6 +168,25 @@ describe('BATCH node', function() {
check_count(flow, results, done);
});
+ it('should create seq. with count (more sent than count)', function(done) {
+ var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 4, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
+ {id:"n2", type:"helper"}];
+ var results = [
+ [0, 1, 2, 3]
+ ];
+ check_count(flow, results, done);
+ });
+
+ it('should create seq. with count and terminate early if parts honoured', function(done) {
+ var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 4, overlap: 0, interval: 10, allowEmptySequence:false, honourParts:true, topics: [], wires:[["n2"]]},
+ {id:"n2", type:"helper"}];
+ var results = [
+ [0, 1, 2, 3],
+ [4, 5]
+ ];
+ check_count(flow, results, done);
+ });
+
it('should create seq. with count and overlap', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 3, overlap: 2, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}];
@@ -455,7 +474,7 @@ describe('BATCH node', function() {
function mapiDoneTestHelper(done, mode, count, overlap, interval, allowEmptySequence, msgAndTimings) {
const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js");
const catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-catch.js");
- const flow = [{id:"batchNode1", type:"batch", name: "BatchNode", mode, count, overlap, interval,
+ const flow = [{id:"batchNode1", type:"batch", name: "BatchNode", mode, count, overlap, interval,
allowEmptySequence, topics: [{topic: "TA"}], wires:[[]]},
{id:"completeNode1",type:"complete",scope: ["batchNode1"],uncaught:false,wires:[["helperNode1"]]},
{id:"catchNode1", type:"catch",scope: ["batchNode1"],uncaught:false,wires:[["helperNode1"]]},
@@ -482,13 +501,13 @@ describe('BATCH node', function() {
}
it('should call done() when message is sent (mode: count)', function(done) {
- mapiDoneTestHelper(done, "count", 2, 0, 2, false, [
+ mapiDoneTestHelper(done, "count", 2, 0, 2, false, [
{ msg: {payload: 0}, delay: 0, avr: 0, var: 100},
{ msg: {payload: 1}, delay: 0, avr: 0, var: 100}
]);
});
it('should call done() when reset (mode: count)', function(done) {
- mapiDoneTestHelper(done, "count", 2, 0, 2, false, [
+ mapiDoneTestHelper(done, "count", 2, 0, 2, false, [
{ msg: {payload: 0}, delay: 0, avr: 200, var: 100},
{ msg: {payload: 1, reset:true}, delay: 200, avr: 200, var: 100}
]);