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.

", "project_empty": "

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).

", "project_not_found": "

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 ", "desc": "Description", @@ -468,7 +470,7 @@ "select": "sélection", "checkbox": "case à cocher", "spinner": "valeurs à défiler", - "none": "aucune", + "none": "aucun", "hidden": "masquer la propriété" }, "types": { @@ -496,7 +498,7 @@ "max": "Maximum" }, "errors": { - "scopeChange": "La modification de la portée la rendra indisponible pour les noeuds d'autres flux qui l'utilisent", + "scopeChange": "La modification de la portée rendra indisponible ce noeud de configuration aux noeuds d'autres flux qui l'utilisent", "invalidProperties": "Propriétés invalides :", "credentialLoadFailed": "Échec du chargement des identifiants du noeud" } @@ -510,7 +512,7 @@ "unassigned": "Non attribué", "global": "Global", "workspace": "Espace de travail", - "editor": "Boîte de dialogue d'édition", + "editor": "Boîte d'édition", "selectAll": "Tout sélectionner", "selectNone": "Ne rien sélectionner", "selectAllConnected": "Sélectionner tous les éléments connectés", @@ -541,7 +543,7 @@ "openLibrary": "Ouvrir la bibliothèque...", "saveToLibrary": "Enregistrer dans la bibliothèque...", "typeLibrary": "__type__ bibliothèque", - "unnamedType": "Innomé __type__", + "unnamedType": "Sans nom __type__", "exportedToLibrary": "Noeuds exportés vers la bibliothèque", "dialogSaveOverwrite": "Une __libraryType__ appelée __libraryName__ existe déjà. Écraser ?", "invalidFilename": "Nom de fichier non valide", @@ -558,7 +560,7 @@ "noInfo": "Pas d'information disponible", "filter": "Rechercher le noeud", "search": "Rechercher les modules", - "addCategory": "Ajouter un nouveau...", + "addCategory": "Ajouter une nouvelle...", "label": { "subflows": "Sous-flux", "network": "Réseau", @@ -638,7 +640,7 @@ "sortAZ": "A-Z", "sortRecent": "Récent", "more": "+ __count__ en plus", - "upload": "Charger le fichier tgz du module", + "upload": "Charger le fichier .tgz du module", "refresh": "Actualiser la liste des modules", "errors": { "catalogLoadFailed": "

É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.

Veuiller vous référer à la documentation pour savoir comment mettre à jour ce noeud." + "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.

Veuillez vous référer à la documentation pour savoir comment mettre à jour ce noeud." }, "button": { "review": "Ouvrir la documentation", @@ -708,8 +710,8 @@ "nodeHelp": "Aide sur les noeuds", "none": "Aucun", "arrayItems": "__count__ éléments", - "showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres", - "outline": "Plan", + "showTips": "Vous pouvez afficher les astuces à partir du panneau des paramètres", + "outline": "Contour", "empty": "Vide", "globalConfig": "Noeuds de configuration globale", "triggerAction": "Déclencher une action", @@ -722,7 +724,7 @@ "help": { "name": "Aide", "label": "Aide", - "search": "Aide à la recherche", + "search": "Rechercher l'aide", "nodeHelp": "Aide sur les noeuds", "showHelp": "Afficher l'aide", "showInOutline": "Afficher dans les grandes lignes", @@ -801,7 +803,7 @@ "branches": "Branches", "noBranches": "Pas de branche", "deleteConfirm": "Êtes-vous sûr de vouloir supprimer la branche locale '__name__' ? Ça ne peut pas être annulé.", - "unmergedConfirm": "La branche locale '__name__' contient des modifications non fusionnées qui seront perdues. Etes-vous sûr de vouloir la supprimer?", + "unmergedConfirm": "La branche locale '__name__' contient des modifications non fusionnées qui seront perdues. Êtes-vous sûr de vouloir la supprimer?", "deleteUnmergedBranch": "Supprimer la branche non fusionnée", "gitRemotes": "Git distant", "addRemote": "Ajout distant", @@ -845,17 +847,17 @@ "deleteConfirm": "Êtes-vous sûr de vouloir supprimer la clé SSH __name__ ? Ça ne peut pas être annulé." }, "versionControl": { - "unstagedChanges": "Abandon des changements", - "stagedChanges": "Changement mis en place", - "unstageChange": "Ne pas mettre en place le changement", - "stageChange": "Mettre en place le changement", - "unstageAllChange": "Ne pas mettre en place tous les changements", - "stageAllChange": "Mettre en place tous les changements", + "unstagedChanges": "Changements non indexés", + "stagedChanges": "Changements indexés", + "unstageChange": "Annuler l'indexation des changements", + "stageChange": "Indexer les changements", + "unstageAllChange": "Annuler l'indexation de tous les changements", + "stageAllChange": "Indexer tous les changements", "commitChanges": "Valider les changements", "resolveConflicts": "Résoudre les conflits", "head": "En-tête", - "staged": "Mis en place", - "unstaged": "Non mis en place", + "staged": "Indexé", + "unstaged": "Non indexé", "local": "Local", "remote": "Distant", "revert": "Voulez-vous vraiment annuler les modifications apportées à '__file__' ? Ça ne peut pas être annulé.", @@ -889,11 +891,11 @@ "pushFailed": "L'envoi a échoué car la branche a des validations plus récentes. Tirer et fusionner d'abord, puis envoyer à nouveau.", "push": "Envoyer", "pull": "Tirer", - "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 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": "

Mode de compatibilité JSONata

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.

", + "compatModeDesc": "

Mode de compatibilité JSONata

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.

", "noMatch": "Aucun résultat correspondant", "errors": { "invalid-expr": "Expression JSONata non valide :\n __message__", @@ -997,7 +999,7 @@ }, "jsonEditor": { "title": "Éditeur JSON", - "format": "Format JSON", + "format": "Formatter JSON", "rawMode": "Modifier JSON", "uiMode": "Afficher l'éditeur", "rawMode-readonly": "JSON", @@ -1016,7 +1018,7 @@ "markdownEditor": { "title": "Éditeur Markdown", "expand": "Développer", - "format": "Formaté avec Markdown", + "format": "Formatter avec Markdown", "heading1": "Rubrique 1", "heading2": "Rubrique 2", "heading3": "Rubrique 3", @@ -1090,7 +1092,7 @@ "credential-key": "Clé de chiffrement des identifiants", "cant-get-ssh-key": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.", "already-exists2": "Existe déjà", - "git-error": "Erreur git", + "git-error": "Erreur Git", "connection-failed": "La connexion a échoué", "not-git-repo": "Ce n'est pas un dépôt Git", "repo-not-found": "Référentiel introuvable" @@ -1104,7 +1106,7 @@ "credentials-file": "Fichier d'identifiants" }, "encryption-config": { - "setup": "Configuration du chiffrage de votre fichier d'informations d'identification", + "setup": "Configuration du chiffrement de votre fichier d'informations d'identification", "desc0": "Votre fichier d'informations d'identification de flux peut être chiffré pour sécuriser son contenu.", "desc1": "Si vous souhaitez stocker ces identifiants dans un référentiel Git public, vous devez les chiffrer en fournissant une phrase clé secrète.", "desc2": "Votre fichier d'identifiants de flux n'est actuellement pas chiffré.", @@ -1161,9 +1163,9 @@ "add-ssh-key": "Ajouter une clé ssh", "credentials-encryption-key": "Clé de chiffrement des identifiants", "already-exists-2": "Existe déjà", - "git-error": "Erreur git", + "git-error": "Erreur Git", "con-failed": "La connexion a échoué", - "not-git": "Ce n'est pas un dépôt git", + "not-git": "Ce n'est pas un dépôt Git", "no-resource": "Référentiel introuvable", "cant-get-ssh-key-path": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.", "unexpected_error": "Erreur inattendue", @@ -1201,7 +1203,7 @@ }, "errors": { "no-username-email": "Votre client Git n'est pas configuré avec un nom d'utilisateur/e-mail.", - "unexpected": "Une erreur inattendue est apparue", + "unexpected": "Une erreur inattendue est survenue", "code": "Code" } }, @@ -1270,7 +1272,7 @@ "list-modified-nodes": "Afficher les flux modifiés", "list-hidden-flows": "Afficher les flux cachés", "list-flows": "Lister les flux", - "list-subflows": "Liste les sous-flux", + "list-subflows": "Lister les sous-flux", "go-to-previous-location": "Aller à l'emplacement précédent", "go-to-next-location": "Aller à l'emplacement suivant", "copy-selection-to-internal-clipboard": "Copier la sélection dans le presse-papiers", @@ -1330,8 +1332,8 @@ "align-selection-to-bottom": "Aligner la sélection vers le bas", "align-selection-to-middle": "Aligner la sélection au centre verticalement", "align-selection-to-center": "Aligner la sélection au centre horizontalement", - "distribute-selection-horizontally": "Distribuer la sélection horizontalement", - "distribute-selection-vertical": "Distribuer la sélection verticalement", + "distribute-selection-horizontally": "Répartir la sélection horizontalement", + "distribute-selection-vertical": "Répartir la sélection verticalement", "wire-series-of-nodes": "Connecter les noeuds en série", "wire-node-to-multiple": "Connecter les noeuds à plusieurs", "wire-multiple-to-node": "Connecter plusieurs au noeud", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index a2ee81767..ab19d459e 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -27,7 +27,8 @@ "lock": "固定", "unlock": "固定を解除", "locked": "固定済み", - "unlocked": "固定なし" + "unlocked": "固定なし", + "format": "形式" }, "type": { "string": "文字列", @@ -281,8 +282,8 @@ "selected": "選択したフロー", "current": "現在のタブ", "all": "全てのタブ", - "compact": "インデントのないJSONフォーマット", - "formatted": "インデント付きのJSONフォーマット", + "compact": "インデントなし", + "formatted": "インデント付き", "copy": "書き出し", "export": "ライブラリに書き出し", "exportAs": "書き出し先", @@ -923,6 +924,8 @@ } }, "typedInput": { + "selected": "__count__個を選択", + "selected_plural": "__count__個を選択", "type": { "str": "文字列", "num": "数値", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 08e8227b1..c50b279a1 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "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/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index dae3f6fa6..60615671e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -32,24 +32,28 @@ RED.contextMenu = (function () { const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode; if (hasSelection) { - selection.nodes.forEach(n => { + const nodes = selection.nodes.slice(); + while (nodes.length) { + const n = nodes.shift(); if (n.type === 'group') { hasGroup = true; + nodes.push(...n.nodes); } else { isAllGroups = false; - } - if (n.d) { - hasDisabledNode = true; - } else { - hasEnabledNode = true; + if (n.d) { + hasDisabledNode = true; + } else { + hasEnabledNode = true; + } } if (n.l === undefined || n.l) { hasLabeledNode = true; } else { hasUnlabeledNode = true; } - }); + } } + const offset = $("#red-ui-workspace-chart").offset() let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index f11dd45d9..30c329c41 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -157,6 +157,12 @@ RED.editor = (function() { } } if (valid && "validate" in definition[property]) { + if (definition[property].hasOwnProperty("required") && + definition[property].required === false) { + if (value === "") { + return true; + } + } try { var opt = {}; if (label) { @@ -183,6 +189,11 @@ RED.editor = (function() { }); } } else if (valid) { + if (definition[property].hasOwnProperty("required") && definition[property].required === false) { + if (value === "") { + return true; + } + } // If the validator is not provided in node property => Check if the input has a validator if ("category" in node._def) { const isConfig = node._def.category === "config"; @@ -413,11 +424,8 @@ RED.editor = (function() { if (selectedOpt?.data('env')) { disableButton(addButton, true); disableButton(editButton, true); - // disable the edit button if no options available - } else if (optionsLength === 1 && selectedOpt.val() === "_ADD_") { - disableButton(addButton, false); - disableButton(editButton, true); - } else if (selectedOpt.val() === "") { + // disable the edit button if no options available or 'none' selected + } else if (optionsLength === 1 || selectedOpt.val() === "_ADD_") { disableButton(addButton, false); disableButton(editButton, true); } else { @@ -426,14 +434,9 @@ RED.editor = (function() { } }); - var label = ""; - var configNode = RED.nodes.node(nodeValue); - - if (configNode) { - label = RED.utils.getNodeLabel(configNode, configNode.id); - } - - input.val(label); + // If the value is "", 'add new...' option if no config node available or 'none' option + // Otherwise, it's a config node + select.val(nodeValue || '_ADD_'); } /** @@ -934,9 +937,11 @@ RED.editor = (function() { } if (!configNodes.length) { + // Add 'add new...' option select.append(''); } else { - select.append(''); + // Add 'none' option + select.append(''); } window.setTimeout(function() { select.trigger("change");},50); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js index cbeecd512..b9f586944 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js @@ -165,7 +165,13 @@ RED.editor.codeEditor.monaco = (function() { //Handles orphaned models //ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed RED.events.on("editor:close",function() { - let models = window.monaco ? monaco.editor.getModels() : null; + if (!window.monaco) { return; } + const editors = window.monaco.editor.getEditors() + const orphanEditors = editors.filter(editor => editor && !document.body.contains(editor.getDomNode())) + orphanEditors.forEach(editor => { + editor.dispose(); + }); + let models = monaco.editor.getModels() if(models && models.length) { console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().") for (let index = 0; index < models.length; index++) { @@ -1124,6 +1130,7 @@ RED.editor.codeEditor.monaco = (function() { $(el).remove(); $(toolbarRow).remove(); + ed.dispose(); } ed.resize = function resize() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js index 0c1597919..b4ae5d52d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js @@ -11,9 +11,22 @@ RED.editor.mermaid = (function () { if (!initializing) { initializing = true - $.getScript( - 'vendor/mermaid/mermaid.min.js', - function (data, stat, jqxhr) { + // Find the cache-buster: + let cacheBuster + $('script').each(function (i, el) { + if (!cacheBuster) { + const src = el.getAttribute('src') + const m = /\?v=(.+)$/.exec(src) + if (m) { + cacheBuster = m[1] + } + } + }) + $.ajax({ + url: `vendor/mermaid/mermaid.min.js?v=${cacheBuster}`, + dataType: "script", + cache: true, + success: function (data, stat, jqxhr) { mermaid.initialize({ startOnLoad: false, theme: RED.settings.get('mermaid', {}).theme @@ -24,7 +37,7 @@ RED.editor.mermaid = (function () { render(pending) } } - ) + }); } } else { const nodes = document.querySelectorAll(selector) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 68e949f68..751bf9cfb 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -1100,7 +1100,7 @@ RED.subflow = (function() { input.val(val.value); break; case "cred": - input = $('').css('width','70%').appendTo(row); + input = $('').css('width','70%').attr('id', elId).appendTo(row); if (node.credentials) { if (node.credentials[tenv.name]) { input.val(node.credentials[tenv.name]); @@ -1346,7 +1346,7 @@ RED.subflow = (function() { } break; case "cred": - item.value = input.val(); + item.value = input.typedInput('value'); item.type = 'cred'; break; case "spinner": diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js index 7f2ed78be..bb9faa0a6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js @@ -103,7 +103,7 @@ RED.sidebar.info.outliner = (function() { evt.stopPropagation(); RED.search.show("type:subflow:"+n.id); }) - // RED.popover.tooltip(userCountBadge,function() { return RED._('editor.nodesUse',{count:n.users.length})}); + RED.popover.tooltip(subflowInstanceBadge,function() { return RED._('subflow.subflowInstances',{count:n.instances.length})}); } if (n._def.category === "config" && n.type !== "group") { var userCountBadge = $('').text(n.users.length).appendTo(controls).on("click",function(evt) { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index f0a69e00a..8e03b7f0d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -259,7 +259,7 @@ $deploy-button-background-disabled-hover: #555; $header-background: #000; $header-button-background-active: #121212; -$header-accent: #d41313; +$header-accent: #C02020; $header-menu-color: #eee; $header-menu-color-disabled: #666; $header-menu-heading-color: #fff; diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index ad9739095..90c4134a4 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -108,12 +108,13 @@ in your Node-RED user directory (${RED.settings.userDir}). if (n.proxy && proxyConfig) { proxyOptions.env = { no_proxy: (proxyConfig.noproxy || []).join(','), - http_proxy: (proxyConfig.url) + http_proxy: (proxyConfig.url), + https_proxy: (proxyConfig.url) } } return getProxyForUrl(url, proxyOptions) } - let prox = getProxy(nodeUrl || '') + let prox = nodeUrl ? getProxy(nodeUrl) : null let timingLog = false; if (RED.settings.hasOwnProperty("httpRequestTimingLog")) { @@ -534,9 +535,7 @@ in your Node-RED user directory (${RED.settings.userDir}). opts.headers[clSet] = opts.headers['content-length']; delete opts.headers['content-length']; } - if (!opts.headers.hasOwnProperty('user-agent')) { - opts.headers['user-agent'] = 'Mozilla/5.0 (Node-RED)'; - } + if (proxyUrl) { const match = proxyUrl.match(/^(https?:\/\/)?(.+)?:([0-9]+)?/i); if (match) { @@ -566,7 +565,7 @@ in your Node-RED user directory (${RED.settings.userDir}). //need both incase of http -> https redirect opts.agent = { http: new HttpProxyAgent(proxyOptions), - https: new HttpProxyAgent(proxyOptions) + https: new HttpsProxyAgent(proxyOptions) }; } else { diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.html b/packages/node_modules/@node-red/nodes/core/sequence/17-split.html index 9296b6e33..b754700cd 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.html +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.html @@ -17,7 +17,11 @@ @@ -234,6 +240,7 @@ }, joiner: { value:"\\n"}, joinerType: { value:"str"}, + useparts: { value:false }, accumulate: { value:"false" }, timeout: {value:""}, count: {value:""}, @@ -259,6 +266,12 @@ }, oneditprepare: function() { var node = this; + $("#node-input-useparts").on("change", function(e) { + if (node.useparts === undefined) { + node.useparts = true; + $("#node-input-useparts").attr('checked', true); + } + }); $("#node-input-mode").on("change", function(e) { var val = $(this).val(); diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index 6e9add270..46ecb2636 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -444,6 +444,8 @@ module.exports = function(RED) { this.count = Number(n.count || 0); this.joiner = n.joiner||""; this.joinerType = n.joinerType||"str"; + if (n.useparts === undefined) { this.useparts = true; } + else { this.useparts = n.useparts || false; } this.reduce = (this.mode === "reduce"); if (this.reduce) { @@ -611,7 +613,7 @@ module.exports = function(RED) { return; } - if (node.mode === 'custom' && msg.hasOwnProperty('parts')) { + if (node.mode === 'custom' && msg.hasOwnProperty('parts') && node.useparts === false ) { if (msg.parts.hasOwnProperty('parts')) { msg.parts = { parts: msg.parts.parts }; } diff --git a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html index 9afb84205..91e6567c5 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html +++ b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html @@ -36,6 +36,10 @@ +
+ + +
@@ -45,7 +49,7 @@
- +
@@ -101,6 +105,7 @@ } }, allowEmptySequence: {value:false}, + honourParts: {value:false}, topics: {value:[{topic:""}]} }, inputs:1, diff --git a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js index f3f29df6a..5cc97e6a9 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js @@ -181,6 +181,8 @@ module.exports = function(RED) { RED.nodes.createNode(this,n); var node = this; var mode = n.mode || "count"; + var eof = false; + node.honourParts = n.honourParts || false; node.pending_count = 0; if (mode === "count") { @@ -201,9 +203,12 @@ module.exports = function(RED) { return; } var queue = node.pending; + if (node.honourParts && msg.hasOwnProperty("parts")) { + if (msg.parts.index + 1 === msg.parts.count) { eof = true; } + } queue.push({msg, send, done}); node.pending_count++; - if (queue.length === count) { + if (queue.length === count || eof === true) { send_msgs(node, queue, is_overlap); for (let i = 0; i < queue.length-overlap; i++) { queue[i].done(); @@ -211,6 +216,7 @@ module.exports = function(RED) { node.pending = (overlap === 0) ? [] : queue.slice(-overlap); node.pending_count = 0; + eof = false; } var max_msgs = max_kept_msgs_count(node); if ((max_msgs > 0) && (node.pending_count > max_msgs)) { diff --git a/packages/node_modules/@node-red/nodes/locales/de/function/89-delay.html b/packages/node_modules/@node-red/nodes/locales/de/function/89-delay.html index cc2722db6..553ef1e71 100644 --- a/packages/node_modules/@node-red/nodes/locales/de/function/89-delay.html +++ b/packages/node_modules/@node-red/nodes/locales/de/function/89-delay.html @@ -20,12 +20,26 @@
delay number
Legt die Verzögerung in Millisekunden fest, die auf die Nachricht angewendet werden soll. Zur Nutzung dieser Option muss Verzög. mit msg.delay überschreibbar aktiviert sein.
+
rate number
+
Setzt die Verzögerung in Millisekunden zwischen den Nachrichten. Diese Node überschreibt die + bestehende Verzögerung die in der Node konfiguration, wenn die empfangende Nachricht 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.
reset
Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist, werden alle im Node gepufferten Nachrichten gelöscht.
flush
Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist, werden alle im Node gepufferten Nachrichten sofort gesendet.
+
flush
+
Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen numerischen Wert gesetzt ist, + wird diese Anzahl an Nachrichten sofort gesendet. Wenn ein anderer Typ gesetzt ist (z.B. Boolean), + werden alle in der Node gepufferten Nachrichten gesendet.
+
toFront
+
Wenn diese Eigenschaft im Ratenbegrenzungsmodus für die empfangene Nachricht auf den booleschen Wert + 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. +

Details

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} ]);