├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples └── simple-test.json ├── gulpfile.js ├── index.d.ts ├── nodes ├── jktest-server.html ├── jktest-server.js ├── jktest.html ├── jktest.js ├── jktest2.html ├── jktest2.js ├── jktestEditableList.html └── jktestEditableList.js ├── package-lock.json ├── package.json └── typedefs.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /** JavaScript Versions 3 | * 5 is minimum -> Last IE11 4 | * 6 = 2015 -> Node >8.10, iOS12+ 5 | * 7 = 2016 -> FF78+, 6 | * 8 = 2017 -> Node 10.9+ 7 | * 9 = 2018 -> Node 12.11+ 8 | * 10 = 2019 9 | * 11 = 2020 10 | * 12 = 2021 11 | */ 12 | module.exports = { 13 | env: { 14 | browser: false, 15 | commonjs: true, 16 | jquery: false, 17 | node: true, 18 | 'shared-node-browser': false 19 | }, 20 | parserOptions: { 21 | ecmaVersion: 2018, 22 | sourceType: 'script' 23 | }, 24 | root: true, 25 | globals: { 26 | Set: true, // Not sure why eslint doesn't recognise this as it is part of node.js since v0.12 27 | }, 28 | plugins: [ 29 | 'html', // Check scripts in HTML. https://www.npmjs.com/package/eslint-plugin-html 30 | 'es', // Help avoid js that is too new. https://eslint-plugin-es.mysticatea.dev/ 31 | 'jsdoc', // JSDoc. https://www.npmjs.com/package/eslint-plugin-jsdoc 32 | 'promise', // Better promises. https://www.npmjs.com/package/eslint-plugin-promise 33 | 'sonarjs' // Detect bugs and suspicious patterns. https://github.com/SonarSource/eslint-plugin-sonarjs 34 | //'prettier', // https://www.npmjs.com/package/eslint-plugin-prettier 35 | ], 36 | extends: [ 37 | 'eslint:recommended', 38 | 'plugin:es/restrict-to-es2018', 39 | 'plugin:jsdoc/recommended', 40 | 'plugin:promise/recommended', 41 | 'plugin:sonarjs/recommended', 42 | //'plugin:prettier/recommended', 43 | ], 44 | // settings: { 45 | // jsdoc: { 46 | // mode: 'permissive' 47 | // } 48 | // }, 49 | rules: { 50 | 'jsdoc/multiline-blocks': 0, 51 | 'jsdoc/newline-after-description': 0, 52 | 'jsdoc/no-multi-asterisks': 0, 53 | 'jsdoc/tag-lines': 0, 54 | 'jsdoc/no-undefined-types': ['error'|'warn', {'definedTypes':['Promise']}], 55 | 56 | 'sonarjs/cognitive-complexity': ['error', 50], // default is 15! Need to try and improve this :-) 57 | 58 | quotes: [ 59 | 'warn', 60 | 'single' 61 | ], 62 | 63 | 'accessor-pairs': 'error', 64 | 'array-bracket-newline': 'error', 65 | 'array-bracket-spacing': 0, 66 | 'array-callback-return': 'off', 67 | 'array-element-newline': 'off', 68 | 'arrow-body-style': 'error', 69 | 'arrow-parens': 'off', 70 | 'arrow-spacing': [ 71 | 'error', 72 | { 73 | after: true, 74 | before: true 75 | } 76 | ], 77 | 'block-scoped-var': 'error', 78 | 'block-spacing': [ 79 | 'error', 80 | 'always' 81 | ], 82 | 'brace-style': [ 83 | 'error', 84 | '1tbs', 85 | { 86 | allowSingleLine: true 87 | } 88 | ], 89 | 'callback-return': 'error', 90 | camelcase: 'off', 91 | 'capitalized-comments': 'off', 92 | 'class-methods-use-this': 'error', 93 | 'comma-dangle': 'off', 94 | 'comma-spacing': 'off', 95 | 'comma-style': [ 96 | 'error', 97 | 'last' 98 | ], 99 | complexity: 'off', 100 | 'computed-property-spacing': [ 101 | 'error', 102 | 'never' 103 | ], 104 | 'consistent-return': 'off', 105 | 'consistent-this': 'off', 106 | curly: 'off', 107 | 'default-case': 'off', 108 | 'default-case-last': 'error', 109 | 'default-param-last': 'error', 110 | 'dot-location': [ 111 | 'error', 112 | 'property' 113 | ], 114 | 'dot-notation': [ 115 | 'error', 116 | { 117 | allowKeywords: true 118 | } 119 | ], 120 | 'eol-last': 'error', 121 | eqeqeq: 'error', 122 | 'func-call-spacing': 'error', 123 | 'func-name-matching': 'error', 124 | 'func-names': 'off', 125 | 'func-style': 'off', 126 | 'function-call-argument-newline': 'off', 127 | 'function-paren-newline': 'off', 128 | 'generator-star-spacing': 'error', 129 | 'global-require': 'off', 130 | 'grouped-accessor-pairs': 'error', 131 | 'guard-for-in': 'error', 132 | 'handle-callback-err': 'error', 133 | 'id-blacklist': 'error', 134 | 'id-denylist': 'error', 135 | 'id-length': 'off', 136 | 'id-match': 'error', 137 | 'implicit-arrow-linebreak': [ 138 | 'error', 139 | 'beside' 140 | ], 141 | indent: [ 142 | 'warn', 143 | 4, 144 | {'SwitchCase': 1} 145 | ], 146 | 'indent-legacy': 'off', 147 | 'init-declarations': 'off', 148 | 'jsx-quotes': 'error', 149 | 'key-spacing': 'off', 150 | 'keyword-spacing': 'off', 151 | 'line-comment-position': 'off', 152 | 'linebreak-style': [ 153 | 'error', 154 | 'unix' 155 | ], 156 | 'lines-around-comment': 'off', 157 | 'lines-around-directive': 'off', 158 | 'lines-between-class-members': 'error', 159 | 'max-classes-per-file': 'error', 160 | 'max-depth': 'error', 161 | 'max-len': 'off', 162 | 'max-lines': 'off', 163 | 'max-lines-per-function': 'off', 164 | 'max-nested-callbacks': 'error', 165 | 'max-params': ['error', 6], 166 | 'max-statements': 'off', 167 | 'max-statements-per-line': 'off', 168 | 'multiline-comment-style': 'off', 169 | 'new-cap': 'error', 170 | 'new-parens': 'error', 171 | 'newline-after-var': 'off', 172 | 'newline-before-return': 'off', 173 | 'newline-per-chained-call': 'error', 174 | 'no-alert': 'error', 175 | 'no-array-constructor': 'error', 176 | 'no-await-in-loop': 'error', 177 | 'no-bitwise': 'off', 178 | 'no-buffer-constructor': 'error', 179 | 'no-caller': 'error', 180 | 'no-catch-shadow': 'error', 181 | 'no-confusing-arrow': 'error', 182 | 'no-console': 'off', 183 | 'no-constructor-return': 'error', 184 | 'no-continue': 'error', 185 | 'no-div-regex': 'error', 186 | 'no-duplicate-imports': 'error', 187 | 'no-else-return': [ 188 | 'error', 189 | { 190 | allowElseIf: true 191 | } 192 | ], 193 | 'no-empty-function': 'off', 194 | 'no-eq-null': 'error', 195 | 'no-eval': 'error', 196 | 'no-extend-native': 'error', 197 | 'no-extra-bind': 'error', 198 | 'no-extra-label': 'error', 199 | 'no-extra-parens': 'off', 200 | 'no-floating-decimal': 'error', 201 | 'no-implicit-coercion': 'error', 202 | 'no-implicit-globals': 'error', 203 | 'no-implied-eval': 'error', 204 | 'no-inline-comments': 'off', 205 | 'no-inner-declarations': [ 206 | 'error', 207 | 'functions' 208 | ], 209 | 'no-invalid-this': 'off', 210 | 'no-iterator': 'error', 211 | 'no-label-var': 'error', 212 | 'no-labels': 'error', 213 | 'no-lone-blocks': 'error', 214 | 'no-lonely-if': 'error', 215 | 'no-loop-func': 'error', 216 | 'no-loss-of-precision': 'error', 217 | 'no-magic-numbers': 'off', 218 | 'no-mixed-operators': 'error', 219 | 'no-mixed-requires': 'error', 220 | 'no-multi-assign': 'off', 221 | 'no-multi-spaces': 'off', 222 | 'no-multi-str': 'error', 223 | 'no-multiple-empty-lines': 'error', 224 | 'no-native-reassign': 'error', 225 | 'no-negated-condition': 'off', 226 | 'no-negated-in-lhs': 'error', 227 | 'no-nested-ternary': 'error', 228 | 'no-new': 'error', 229 | 'no-new-func': 'error', 230 | 'no-new-object': 'error', 231 | 'no-new-require': 'error', 232 | 'no-new-wrappers': 'error', 233 | 'no-nonoctal-decimal-escape': 'error', 234 | 'no-octal-escape': 'error', 235 | 'no-param-reassign': 'off', 236 | 'no-path-concat': 'error', 237 | 'no-plusplus': 'off', 238 | 'no-process-env': 'off', 239 | 'no-process-exit': 'error', 240 | 'no-promise-executor-return': 'error', 241 | 'no-proto': 'error', 242 | 'no-restricted-exports': 'error', 243 | 'no-restricted-globals': 'error', 244 | 'no-restricted-imports': 'error', 245 | 'no-restricted-modules': 'error', 246 | 'no-restricted-properties': 'error', 247 | 'no-restricted-syntax': 'error', 248 | 'no-return-assign': 'error', 249 | 'no-return-await': 'error', 250 | 'no-script-url': 'error', 251 | 'no-self-compare': 'error', 252 | 'no-sequences': 'error', 253 | 'no-shadow': 'off', 254 | 'no-spaced-func': 'error', 255 | 'no-sync': 'off', 256 | 'no-tabs': 'error', 257 | 'no-template-curly-in-string': 'error', 258 | 'no-ternary': 'off', 259 | 'no-throw-literal': 'error', 260 | 'no-trailing-spaces': 'off', 261 | 'no-undef-init': 'error', 262 | 'no-undefined': 'off', 263 | 'no-underscore-dangle': 'off', 264 | 'no-unmodified-loop-condition': 'error', 265 | 'no-unneeded-ternary': 'off', 266 | 'no-unreachable-loop': 'error', 267 | 'no-unsafe-optional-chaining': 'error', 268 | 'no-use-before-define': 'error', 269 | 'no-useless-backreference': 'error', 270 | 'no-useless-call': 'error', 271 | 'no-useless-computed-key': 'error', 272 | 'no-useless-concat': 'error', 273 | 'no-useless-constructor': 'error', 274 | 'no-useless-rename': 'error', 275 | 'no-useless-return': 'off', 276 | 'no-var': 'off', 277 | 'no-void': 'error', 278 | 'no-warning-comments': 'off', 279 | 'no-whitespace-before-property': 'error', 280 | 'nonblock-statement-body-position': [ 281 | 'error', 282 | 'any' 283 | ], 284 | 'object-curly-newline': 'error', 285 | 'object-curly-spacing': 'off', 286 | 'object-shorthand': 'off', 287 | 'one-var': 'off', 288 | 'one-var-declaration-per-line': 'off', 289 | 'operator-assignment': [ 290 | 'error', 291 | 'always' 292 | ], 293 | 'operator-linebreak': 'error', 294 | 'padded-blocks': 'off', 295 | 'padding-line-between-statements': 'error', 296 | 'prefer-arrow-callback': 'off', 297 | 'prefer-const': 'off', 298 | 'prefer-destructuring': 'off', 299 | 'prefer-exponentiation-operator': 'error', 300 | 'prefer-named-capture-group': 'error', 301 | 'prefer-numeric-literals': 'error', 302 | 'prefer-object-spread': 'off', 303 | 'prefer-promise-reject-errors': 'error', 304 | 'prefer-reflect': 'off', 305 | 'prefer-regex-literals': 'error', 306 | 'prefer-rest-params': 'off', 307 | 'prefer-spread': 'off', 308 | 'prefer-template': 'off', 309 | 'quote-props': 'off', 310 | radix: 'error', 311 | 'require-atomic-updates': 'error', 312 | 'require-await': 'error', 313 | 'require-jsdoc': 'error', 314 | 'require-unicode-regexp': 'off', 315 | 'rest-spread-spacing': 'error', 316 | semi: [ 317 | 'warn', 318 | 'never' 319 | ], 320 | 'semi-spacing': 'error', 321 | 'semi-style': 'error', 322 | 'sort-imports': 'error', 323 | 'sort-keys': 'off', 324 | 'sort-vars': 'off', 325 | 'space-before-blocks': 'off', 326 | 'space-before-function-paren': 'off', 327 | 'space-in-parens': 'off', 328 | 'space-infix-ops': 'off', 329 | 'space-unary-ops': 'off', 330 | 'spaced-comment': 'off', 331 | strict: 'error', 332 | 'switch-colon-spacing': [ 333 | 'error', 334 | { 335 | after: true, 336 | before: false 337 | } 338 | ], 339 | 'symbol-description': 'error', 340 | 'template-curly-spacing': 0, 341 | 'template-tag-spacing': 'error', 342 | 'unicode-bom': [ 343 | 'error', 344 | 'never' 345 | ], 346 | 'valid-jsdoc': 'off', 347 | 'vars-on-top': 'off', 348 | 'wrap-iife': 'error', 349 | 'wrap-regex': 'error', 350 | 'yield-star-spacing': 'error', 351 | yoda: [ 352 | 'error', 353 | 'never' 354 | ] 355 | }, 356 | } 357 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # VScode 2 | .vscode 3 | .vscode/cSpell.json 4 | .vscode/settings.json 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # next.js build output 66 | .next 67 | 68 | # ========================= 69 | # Operating System Files 70 | # ========================= 71 | 72 | # OSX 73 | # ========================= 74 | 75 | .DS_Store 76 | .AppleDouble 77 | .LSOverride 78 | 79 | # Thumbnails 80 | ._* 81 | 82 | # Files that might appear in the root of a volume 83 | .DocumentRevisions-V100 84 | .fseventsd 85 | .Spotlight-V100 86 | .TemporaryItems 87 | .Trashes 88 | .VolumeIcon.icns 89 | 90 | # Directories potentially created on remote AFP share 91 | .AppleDB 92 | .AppleDesktop 93 | Network Trash Folder 94 | Temporary Items 95 | .apdisk 96 | 97 | # Windows 98 | # ========================= 99 | 100 | # Windows image file caches 101 | Thumbs.db 102 | ehthumbs.db 103 | 104 | # Folder config file 105 | Desktop.ini 106 | 107 | # Recycle Bin used on file shares 108 | $RECYCLE.BIN/ 109 | 110 | # Windows Installer files 111 | *.cab 112 | *.msi 113 | *.msm 114 | *.msp 115 | 116 | # Windows shortcuts 117 | *.lnk 118 | 119 | # No point in committing these 120 | _node_modules 121 | 122 | # These are private 123 | *.code-workspace 124 | 125 | # We shouldn't commit files with names starting with _ 126 | _* -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.3.2 - Minor enhancements 2 | 3 | - Enhance Reporting, improve comments, rename nodeGo() to nodeInstance() for clarity 4 | 5 | ### 0.3.1 - Bug fix & code tidy 6 | 7 | * Properly isolate the JS code in the html file to prevent name clashes 8 | * Change `text/x-red` to `text/html` in html file 9 | * Breaking change: use of ES6 means that minimum Node.js version is now v10 10 | 11 | ### 0.2.0 - Update for Node.JS v8 and Node-RED v0.19 12 | 13 | * Tidied up the code 14 | * Improved code by splitting out complex vars/fns 15 | * Improved JSDoc comments 16 | * Added some links to official documentation 17 | * Improve some of the logic 18 | * Added an example configuration node 19 | 20 | NB: The Editable List node is still not working. Sorry, I'll try to get to that next. 21 | 22 | ### 0.1.1 - Minor tweaks 23 | 24 | ### 0.0.0 - not a release 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-red-contrib-jktesting 2 | 3 | An EXPERIMENTAL Node-RED testing/learning set of nodes. 4 | 5 | Designed as an *experimental* learning and testing tool for trying out different things when designing Node-RED nodes. 6 | 7 | You can also use it as a template for writing your own nodes. 8 | 9 | This node has several purposes: 10 | 1. Learn the order of execution and events for learning how to program you own nodes. 11 | 2. Show some best practices and standards for code when developing your own nodes. 12 | 3. Act as a template I or you can use for your own nodes. 13 | 14 | ## Node Creation Workflow 15 | 16 | The workflow I use to create new nodes is: 17 | 18 | 1. Create a new repo on GitHub - don't yet tag it with node-red until you are ready to go public 19 | 2. Clone the repo to my source files folder on the dev machine 20 | 3. Manually create a soft-link from the new folder into ~/.node-red/node_modules (I don't use npm link as that does strange things like creating a global link) 21 | 4. Create the package.json, README.md, LICENCE, CHANGELOG.md, .gitignore files in my source folder. Create a sub-folder called "nodes" and create the html and js files in there. 22 | 5. In the source folder, do npm install if you need to install any dependencies 23 | 6. Restart Node-RED. 24 | 25 | ## Design 26 | 27 | ### Physical file/folder location summary 28 | 29 | Folders and files for resources on the device running Node-RED are: 30 | 31 | - 32 | 33 | ## Known Issues 34 | 35 | - 36 | 37 | ## To Do 38 | 39 | - 40 | 41 | ## Changes 42 | 43 | See the [Change Log](CHANGELOG) for details. 44 | 45 | 46 | ## Pre-requisites 47 | 48 | See the package.json file for details. 49 | 50 | Requires at least Node.JS v6 and Node-RED v0.16 51 | 52 | ## Install 53 | 54 | Run the following command in your Node-RED user directory (typically `~/.node-red`): 55 | 56 | ``` 57 | npm install node-red-contrib-jktesting 58 | ``` 59 | 60 | Run Node-RED and add an instance of the test node. 61 | 62 | There is also a simple example in the library. 63 | 64 | ## Nodes 65 | 66 | ### JK Simple Test (jktest) 67 | 68 | #### Node Instance Settings 69 | 70 | Each instance of the test node has the following settings available. 71 | 72 |
73 |
Name
74 |
A short description shown in the admin interface
75 |
Topic
76 |
A topic name to use if the incoming msg does not contain one.
77 |
Server
78 |
An example of using a configuration node.
79 |
80 | 81 | #### Inputs 82 |
83 |
payload `string | buffer`
84 |
The payload of the message to publish.
85 |
topic (optional) `string`
86 |
The MQTT topic to publish to.
87 |
88 | 89 | #### Outputs 90 | - Standard output 91 | 92 |
93 |
_msgcounter integer
94 |
The number of messages recieved by the node instance since either the last reset of Node-RED or the last deployment of the node instance.
95 |
payload string | buffer
96 |
A copy of any inbound payload.
97 |
topic string
98 |
A copy of any inbound topic if present. Otherwise, the topic from the node's settings.
99 |
config object
100 |
A copy of the name and server from the configuration.
101 |
102 | 103 | ## Discussions and suggestions 104 | 105 | Use the [Node-RED google group](https://groups.google.com/forum/#!forum/node-red) for general discussion about this node. Or use the 106 | [GitHub issues log](https://github.com/TotallyInformation/node-red-contrib-jktesting/issues) for raising issues or contributing suggestions and enhancements. 107 | 108 | ## Contributing 109 | 110 | If you would like to contribute to this node, you can contact Totally Information via GitHub or raise a request in the GitHub issues log. 111 | 112 | ## Developers/Contributors 113 | 114 | - [Julian Knight](https://github.com/TotallyInformation) 115 | -------------------------------------------------------------------------------- /examples/simple-test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "8e83faec.dc03f8", 4 | "type": "inject", 5 | "z": "595546f3.5fb258", 6 | "name": "", 7 | "topic": "", 8 | "payload": "", 9 | "payloadType": "date", 10 | "repeat": "", 11 | "crontab": "", 12 | "once": false, 13 | "onceDelay": 0.1, 14 | "x": 140, 15 | "y": 180, 16 | "wires": [ 17 | [ 18 | "a9fadf76.9c6fb" 19 | ] 20 | ] 21 | }, 22 | { 23 | "id": "9c5a199f.631d98", 24 | "type": "debug", 25 | "z": "595546f3.5fb258", 26 | "name": "", 27 | "active": true, 28 | "tosidebar": true, 29 | "console": false, 30 | "tostatus": false, 31 | "complete": "true", 32 | "x": 560, 33 | "y": 180, 34 | "wires": [] 35 | }, 36 | { 37 | "id": "a9fadf76.9c6fb", 38 | "type": "jktest", 39 | "z": "595546f3.5fb258", 40 | "name": "Test Node", 41 | "topic": "", 42 | "server": "ed33c58d.140968", 43 | "x": 300, 44 | "y": 180, 45 | "wires": [ 46 | [ 47 | "9c5a199f.631d98" 48 | ] 49 | ] 50 | }, 51 | { 52 | "id": "ed33c58d.140968", 53 | "type": "jktest-server", 54 | "z": "", 55 | "protocol": "https", 56 | "host": "localhost", 57 | "port": "9090" 58 | } 59 | ] 60 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable sonarjs/no-duplicate-string */ 2 | /* eslint-disable jsdoc/newline-after-description, jsdoc/require-param */ 3 | 4 | /** 5 | * https://semaphoreci.com/community/tutorials/getting-started-with-gulp-js 6 | * https://gulpjs.com/plugins/ 7 | * https://gulpjs.com/docs/en/api/concepts/ 8 | * Plugins 9 | * https://www.npmjs.com/package/gulp-include - source file inline replacements 10 | * https://www.npmjs.com/package/gulp-uglify - Minify 11 | * https://www.npmjs.com/package/gulp-rename - Rename source filename on output 12 | * https://www.npmjs.com/package/gulp-once - Only do things if files have changed 13 | * https://www.npmjs.com/package/gulp-replace - String replacer 14 | * https://www.npmjs.com/package/gulp-debug 15 | * 16 | * https://www.npmjs.com/package/gulp-concat 17 | * https://www.npmjs.com/package/gulp-sourcemaps 18 | * https://www.npmjs.com/package/gulp-prompt - get input from user 19 | * https://www.npmjs.com/package/gulp-if-else 20 | * https://www.npmjs.com/package/gulp-minify-inline 21 | * https://www.npmjs.com/package/gulp-tap - Easily tap into a pipeline. Could replace gulp-replace 22 | * https://www.npmjs.com/package/webpack-stream - Use WebPack with gulp 23 | * https://www.npmjs.com/package/tinyify - runs various optimizations 24 | * 25 | * ❌https://www.npmjs.com/package/gulp-changed - Does not work as expected 26 | */ 27 | 28 | 'use strict' 29 | 30 | //const { src, dest, series, watch, /* parallel, */ } = require('gulp') 31 | // const uglify = require('gulp-uglify') 32 | // const rename = require('gulp-rename') 33 | // const include = require('gulp-include') 34 | // const once = require('gulp-once') 35 | //const prompt = require('gulp-prompt') 36 | // const replace = require('gulp-replace') 37 | // const debug = require('gulp-debug') 38 | const execa = require('execa') 39 | const fs = require('fs') 40 | //const { promisify } = require('util') 41 | //const dotenv = require('dotenv') 42 | 43 | // const nodeDest = 'nodes' 44 | 45 | // print output of commands into the terminal 46 | const stdio = 'inherit' 47 | 48 | const { version } = JSON.parse(fs.readFileSync('package.json')) 49 | 50 | // What release/version do we want to end up with? 51 | const release = '0.4.0' 52 | 53 | console.log(`Current Version: ${version}. Requested Version: ${release}`) 54 | 55 | /** 56 | * TODO 57 | * - Add text replace to ensure 2021 in (c) blocks is current year 58 | */ 59 | 60 | /** Combine the parts of uibuilder.html */ 61 | // function combineHtml(cb) { 62 | // src('src/editor/main.html') 63 | // .pipe(include()) 64 | // .pipe(once()) 65 | // .pipe(rename('uibuilder.html')) 66 | // .pipe(dest(nodeDest)) 67 | 68 | // cb() 69 | // } 70 | 71 | /** Watch for changes during development of uibuilderfe & editor */ 72 | // function watchme(cb) { 73 | // // Re-combine uibuilder.html if the source changes 74 | // watch('src/editor/*', combineHtml) 75 | 76 | // cb() 77 | // } 78 | 79 | /** Set uibuilder version in package.json */ 80 | async function setPackageVersion() { 81 | if (version !== release) { 82 | // bump version without committing and tagging: npm version 4.2.1 --no-git-tag-version --allow-same-version 83 | await execa('npm', ['version', release, '--no-git-tag-version'], {stdio}) 84 | } else { 85 | console.log('Requested version is same as current version - nothing will change') 86 | } 87 | } 88 | 89 | /** Create a new GitHub tag for a release (only if release ver # different to last committed tag) */ 90 | async function createTag(cb) { 91 | //Get the last committed tag: git describe --tags --abbrev=0 92 | const lastTag = (await execa('git', ['describe', '--tags', '--abbrev=0'])).stdout 93 | console.log(`Last committed tag: ${lastTag}`) 94 | 95 | // If the last committed tag is different to the required release ... 96 | if ( lastTag.replace('v','') !== release ) { 97 | //const commitMsg = `chore: release ${release}` 98 | //await execa('git', ['add', '.'], { stdio }) 99 | //await execa('git', ['commit', '--message', commitMsg], { stdio }) 100 | await execa('git', ['tag', `v${release}`], { stdio }) 101 | await execa('git', ['push', '--follow-tags'], { stdio }) 102 | await execa('git', ['push', 'origin', '--tags'], { stdio }) 103 | } else { 104 | console.log('Requested release version is same as the latest tag - not creating tag') 105 | } 106 | cb() 107 | } 108 | 109 | 110 | //exports.default = series( packfe, combineHtml ) // series(runLinter,parallel(generateCSS,generateHTML),runTests) 111 | // exports.watch = watchme 112 | // exports.combineHtml = combineHtml 113 | exports.createTag = createTag 114 | exports.setVersion = setPackageVersion //series( setPackageVersion, setFeVersion, setFeVersionMin ) 115 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** standard object definitions 2 | * Used by code editors & typescript to validate data. 3 | */ 4 | 5 | /// 6 | /// 7 | /// 8 | 9 | import { Node } from 'node-red'; 10 | 11 | declare global { 12 | var RED:any; 13 | } 14 | 15 | /** 16 | * @typedef {object} myNode Local copy of the node instance config + other info 17 | * @property {String} id Unique identifier for this instance 18 | * @property {String} type What type of node is this an instance of? (uibuilder) 19 | * --- Node's config properties 20 | * @property {String} name Descriptive name, only used by Editor 21 | * @property {String} topic msg.topic overrides incoming msg.topic 22 | * --- Node functions 23 | * @property {Function} send Send a Node-RED msg to an output port 24 | * @property {Function=} done Dummy done function for pre-Node-RED 1.0 servers 25 | * @property {Function=} on Event handler 26 | * @property {Function=} removeListener Event handling 27 | * z, wires 28 | */ 29 | export interface myNode extends Node {} 30 | 31 | 32 | -------------------------------------------------------------------------------- /nodes/jktest-server.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 61 | 62 | 81 | 82 | 99 | -------------------------------------------------------------------------------- /nodes/jktest-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Julian Knight (Totally Information) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the 'License'); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an 'AS IS' BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict' 18 | 19 | /** ==== THIS IS AN EXAMPLE CONFIGURATION NODE ==== 20 | * It doesn't actually do anything, it is just an example 21 | * @see https://nodered.org/docs/creating-nodes/config-nodes 22 | **/ 23 | 24 | // Node name must match this nodes html file name AND the nodeType in the html file 25 | const nodeName = 'jktest-server' 26 | 27 | // THIS FUNCTION IS EXECUTED ONLY ONCE AS NODE-RED IS LOADING 28 | module.exports = function(RED) { 29 | 'use strict' 30 | 31 | /** RED, parent object set by Node-RED 32 | * @external RED 33 | * @see https://nodered.org/docs/creating-nodes/node-js 34 | **/ 35 | 36 | /** The node's instance definition. 37 | * THIS FUNCTION IS RUN ON (RE)DEPLOYMENT - FOR EACH INSTANCE OF THIS NODE TYPE 38 | * -------------- -------- 39 | * this/node var is rebuilt on every redeployment 40 | * @param {object} config - The config vars defined in the matching html file used in the admin interface 41 | */ 42 | function nodeGo(config) { 43 | 44 | // Create the node instance 45 | RED.nodes.createNode(this, config) 46 | 47 | // copy 'this' object in case we need it in context of callbacks of other functions. 48 | const node = this 49 | 50 | /** Create local copies of the node configuration (as defined in the .html file) 51 | * NB: Best to use defaults here as well as in the html file for safety 52 | **/ 53 | node.protocol = config.protocol || 'http' 54 | node.host = config.host || '' 55 | node.port = config.port || '' 56 | 57 | // See jktest.js for additional processing code if required 58 | // This simple example just stores the settings above 59 | // they might be shared by all instances of the parent node. 60 | 61 | } // ---- End of nodeGo (initialised node instance) ---- // 62 | 63 | /** Register the node by name. This must be called before overriding any of the node functions. 64 | * @param {string} nodeName - Name used in the matching html file that defines the admin ui 65 | **/ 66 | RED.nodes.registerType(nodeName, nodeGo) 67 | 68 | } // ---- End of module.exports ---- // 69 | 70 | // EOF 71 | -------------------------------------------------------------------------------- /nodes/jktest.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 147 | 148 | 201 | 202 | 269 | -------------------------------------------------------------------------------- /nodes/jktest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Julian Knight (Totally Information) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the 'License'); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an 'AS IS' BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict' 18 | 19 | /** The HTML file defines the settings and help information for this node type 20 | * The JS file defines the processing for each instance of this node type 21 | **/ 22 | 23 | const nodeVersion = require('../package.json').version 24 | 25 | // THIS OUTER SECTION IS EXECUTED ONLY ONCE AS NODE-RED IS LOADING 26 | console.log(`[jktesting] *** jktest.js loaded into memory and executed. Module Ver: ${nodeVersion} ***`) 27 | 28 | // Node name must match this nodes html file name AND the nodeType in the html file 29 | const nodeName = 'jktest' 30 | 31 | // Set to false to prevent debugging output 32 | var debug = true 33 | 34 | //debug && console.log( 'node-red-contrib-' + nodeName + ' - initialising module. Module Version: ' + nodeVersion ) 35 | 36 | // Keep track of how many times each instance of this node is deployed 37 | const deployments = {} 38 | 39 | //const events = require('events') 40 | //var ev = new events.EventEmitter(); 41 | //ev.setMaxListeners(0); 42 | 43 | // THIS FUNCTION IS EXECUTED ONLY ONCE AS NODE-RED IS LOADING 44 | module.exports = function(RED) { 45 | 'use strict' 46 | 47 | /** Some useful properties of the RED object 48 | * RED.events 49 | * See below 50 | * 'registry:plugin-added', 'event-log' 51 | * RED.settings 52 | * NB: entries in settings.js are read-only and shouldn't be read using RED.settings.get, that is only for settings that can change in-flight. 53 | * see Node-RED issue #1543. 54 | * RED.httpNode || RED.httpAdmin 55 | * References to the ExpressJS app service for user-facing || Editor/admin-facing servers 56 | * RED.nodes 57 | * createNode(), getNode(), eachNode(), addCredentials(), getCredentials(), deleteCredentials(), registerType(), registerSubflow() 58 | * RED.comms 59 | * RED.comms.publish (topic, data, retain) 60 | * Consume in Editor as 61 | * RED.comms.subscribe("status/#",function(topic,msg) {...}) 62 | * RED.hooks - https://github.com/node-red/designs/tree/master/designs/pluggable-message-routing 63 | * 'has', 'clear', 'add', 'remove', 'trigger' 64 | * RED.util 65 | * 'encodeObject', 'ensureString', 'ensureBuffer', 'cloneMessage', 'compareObjects', 66 | * 'generateId', 'getMessageProperty', 'setMessageProperty', 'getObjectProperty', 'setObjectProperty', 67 | * 'evaluateNodeProperty', 'normalisePropertyExpression', 'normaliseNodeTypeName', 'prepareJSONataExpression', 68 | * 'evaluateJSONataExpression', 'parseContextStore' 69 | * 70 | * Other props 71 | * 'log', 'util', 'version', 'require', 'plugins', 'library', 'httpNode', 'httpAdmin', 'server', 'auth', '_' 72 | */ 73 | 74 | //debug && RED.log.debug( 'node-red-contrib-' + nodeName + ' - loading module' ) 75 | console.log('[jktesting] *** jktest.js module.exports function executed (RED object now available) ***') 76 | //console.log(RED.nodes) 77 | 78 | /** Node-RED Events 79 | RED.events.on('flows:started',function() { 80 | console.log('[jktesting:jktest.js] ****** All nodes have started ******') 81 | }) 82 | RED.events.on('flows:stopped',function() { 83 | console.log('[jktesting:jktest.js] ****** All nodes have stopped ******') 84 | }) 85 | */ 86 | /** Show when Node-RED runtime events that relate to this node happen */ 87 | RED.events.on('runtime-event', function(event) { 88 | console.log('[jktesting] jktest.js runtime-event: ', event) 89 | if (event.id === 'project-update' && event.payload.action === 'loaded') { 90 | console.log('A new project has been loaded') 91 | } 92 | }) 93 | 94 | /** Settings that can be set in settings.js and that are shared with the node editor 95 | * Passed to the `RED.nodes.registerType` function 96 | * @see https://nodered.org/docs/creating-nodes/node-js#custom-node-settings 97 | * Also must contain any credentials used in the admin ui in a ... credentials: {} ... object 98 | * @see https://nodered.org/docs/creating-nodes/credentials 99 | **/ 100 | const nodeSettings = { 101 | 'settings': { 102 | 'jktestNodeEnv': { 'value': process.env.NODE_ENV, 'exportable': true } 103 | }, 104 | // 'credentials': { 105 | // id: {type:text}, 106 | // pw: {type:password}, 107 | // } 108 | } 109 | 110 | /** RED, parent object set by Node-RED 111 | * @external RED 112 | * @see https://nodered.org/docs/creating-nodes/node-js 113 | **/ 114 | 115 | /** The node's instance definition. 116 | * THIS FUNCTION IS RUN ON (RE)DEPLOYMENT - FOR EACH INSTANCE OF THIS NODE TYPE 117 | * -------------- -------- 118 | * 119 | * this/node var is rebuilt on every redeployment 120 | * 121 | * @param {object} config - The config vars defined in the matching html file used in the admin interface 122 | * 123 | * node.xxx != var xxx - though the way that NR processes this fn makes them very similar in this case. 124 | * node.xxx vars would be accessible wherever the node object is referenced. 125 | * var xxx vars are only accessible inside this function. 126 | */ 127 | function nodeInstance(config) { 128 | 129 | //debug && RED.log.debug( 'node-red-contrib-' + nodeName + ' - Starting nodeInstance, node instance being deployed ' ) 130 | 131 | /** Create the node instance 132 | * @param {Object} this A node instance object of type `nodeInstance` in this case 133 | * @param {Object} config Configuration data passed from the Editor @see https://nodered.org/docs/creating-nodes/properties 134 | */ 135 | RED.nodes.createNode(this, config) 136 | 137 | // copy 'this' object in case we need it in context of callbacks of other functions. 138 | const node = this 139 | 140 | console.log(`[jktesting] *** jktest.js/nodeInstance() - Creating instance of jktest node - ID: ${this.id} ***`) //, config, node) 141 | 142 | /** Create local copies of the node configuration (as defined in the .html file) 143 | * NB: Best to use defaults here as well as in the html file for safety 144 | **/ 145 | node.name = config.name || '' 146 | node.topic = config.topic || '' // NB: will be overwritten by msg.topic if received 147 | 148 | // example configuration node - config node with 3 fields 149 | let configObj = RED.nodes.getNode(config.server) 150 | node.server = configObj.protocol + '://' + configObj.host 151 | if ( configObj.port !== '' ) node.server += ':' + configObj.port 152 | 153 | /** Built-in attributes: 154 | * node.id // the node instance unique id, also available as config.id 155 | * node.type // the module name 156 | **/ 157 | 158 | /** Use this if you want to see what keys (properties) are available in either the config or node objects 159 | * console.dir(Object.keys(config)) 160 | * console.dir(Object.keys(node)) 161 | **/ 162 | 163 | /** Access context/flow/global variables 164 | * NB: context vars are reset on node, flow and full redeployments - eventually, context vars may get a persistence mechanism 165 | * node.context().get('varname') 166 | * node.context().set('varname', someContent) 167 | * NB: flow and global vars are NOT reset on any kind of redeployment 168 | * node.context().flow.get('varname') 169 | * node.context().global.get('varname') 170 | **/ 171 | 172 | // Track how many messages recieved (using ES6 generator function) - not essential but nice for tracking 173 | node.msgCounter = rcvCounter() 174 | 175 | // Set to true if you want additional debug output to the console 176 | debug = RED.settings.debug || true 177 | 178 | // Keep track of the number of times each instance is deployed. 179 | // The initial deployment = 1 180 | if ( deployments.hasOwnProperty(node.id) ) { 181 | deployments[node.id]++ 182 | } else { 183 | deployments[node.id] = 1 184 | } 185 | 186 | debug && RED.log.debug( 187 | 'node-red-contrib-' + nodeName + ', # Deployments: ' + deployments[node.id] + 188 | ', node.ID: ' + node.id + ', node.type: ' + node.type + 189 | ', Instance Name: ' + node.name) 190 | 191 | /** If we need an Express app server to serve a web page 192 | * const app = RED.httpNode || RED.httpAdmin 193 | * If we just need the http server 194 | * const httpServer = RED.server 195 | **/ 196 | 197 | /** These ONLY log to the NR console (audit is only shown when requested in NR settings) 198 | * RED.log.info('Some Text') // also info, log, warn, error, trace, debug 199 | * RED.log.audit({ 'TEST': 'An Object' }) 200 | * These show up in the NR console AND in the NR debug window 201 | * this.log("Something happened"); 202 | * this.warn("Something happened you should know about"); 203 | * this.error("Oh no, something bad happened"); 204 | * this.error("Oh no, something bad happened", msg); // halts current flow, triggers catch node 205 | **/ 206 | 207 | // Set a status line under the node instance in the admin interface 208 | setNodeStatus( { fill: 'blue', shape: 'dot', text: 'Node Initialised' }, node ) 209 | 210 | /** Handler function for node input events (when a node instance receives a msg) 211 | * @param {Object} msg - The input msg object 212 | **/ 213 | function nodeInputHandler(msg) { 214 | debug && RED.log.debug('TEST:nodeInstance:nodeInputHandler') //debug 215 | 216 | // If msg is null, nothing will be sent 217 | if ( msg === null ) return 218 | 219 | // if msg isn't an object 220 | // NOTE: This is paranoid and shouldn't be possible! 221 | if ( typeof msg !== 'object' ) { 222 | // Force msg to be an object with payload of original msg 223 | msg = { 'payload': msg } 224 | } 225 | 226 | // Add topic from node config if present and not present in msg 227 | if ( !(msg.hasOwnProperty('topic')) || msg.topic === '' ) { 228 | if ( node.topic !== '' ) msg.topic = node.topic 229 | else msg.topic = 'TEST/' + nodeName 230 | } 231 | 232 | // Keep this fn small for readability so offload 233 | // any further, more customised code to another fn 234 | inputHandler(msg, node, RED) 235 | 236 | } // -- end of msg received processing -- // 237 | 238 | // Whenever the node instance receives a msg, the function is triggered 239 | node.on('input', nodeInputHandler) 240 | 241 | /** Do something when Node-RED is closing down 242 | * which includes when this node instance is redeployed 243 | * NOTE: function(done) MUST be used if needing to do async processing 244 | * in close, BUT if used, done() MUST be called because Node-RED 245 | * will wait otherwise and timeout with an error after 15 sec. 246 | **/ 247 | node.on('close', function(removed, done) { 248 | debug && RED.log.debug('TEST:nodeInstance:on-close') //debug 249 | 250 | // Tidy up the event listener (that listens for new msg's) 251 | node.removeListener('input', nodeInputHandler) 252 | 253 | // Do any complex close processing here if needed - MUST BE LAST 254 | // the function MUST also process done() 255 | processClose(removed, done, node, RED) 256 | 257 | }) // --- End of on close --- // 258 | 259 | } // ---- End of nodeInstance (initialised node instance) ---- // 260 | 261 | /** Register the node by name. This must be called before overriding any of the node functions. 262 | * @param {string} nodeName - Name used in the matching html file that defines the admin ui 263 | * @param {function} nodeInstance - Name of the function that provides the processing for each instance of this node type 264 | * @param {Object=} nodeSettings - An optional object defining settings that can be set in Node-RED's settings.js file. @see https://nodered.org/docs/creating-nodes/node-js#exposing-settings-to-the-editor 265 | **/ 266 | RED.nodes.registerType(nodeName, nodeInstance, nodeSettings) 267 | //RED.nodes.registerType(nodeName, nodeInstance, { credentials: { username: {type:"text"}, password: {type:"password"} } }) 268 | 269 | } // ---- End of module.exports ---- // 270 | 271 | /** ========== UTILITY FUNCTIONS ============== 272 | * Don't forget to pass msg, node, RED, etc. 273 | * as arguments because these functions are 274 | * outside the scope of the exports function 275 | * =========================================== */ 276 | 277 | /** Complex, custom code when processing an incoming msg should go here 278 | * Needs to output the msg object (if needed) before ending. 279 | * - use RED.util.getMessageProperty(msg,expr) to get any element of the msg 280 | * as this lets you retrieve deep info such as msg.payload.sub.deep. 281 | * - use RED.util.setMessageProperty(msg,prop,value,createMissing) to set an element on the msg, 282 | * it will fill in any missing intermediate properties. 283 | * - use RED.comms.publish('A message to admin') to send a message to the admin interface, 284 | * appears as a pop-over message box at the top of the screen. 285 | * @param {Object} msg - The incoming msg object 286 | * @param {Object} node - The node definition object 287 | * @param {Object} RED - The RED object from Node-RED 288 | **/ 289 | function inputHandler(msg, node, RED) { 290 | var msgCount = node.msgCounter.next().value 291 | RED.util.setMessageProperty(msg, '_msgcounter', msgCount, false) // iterate counter and attach to msg 292 | setNodeStatus({fill: 'yellow', shape: 'dot', text: 'Message Recieved #' + msgCount}, node) 293 | 294 | //debug && console.dir(msg) //debug 295 | 296 | // Simple example adding the node configuration data to the output message 297 | msg.config = { 298 | 'name': node.name, 299 | 'server': node.server 300 | } 301 | 302 | // Send on the input msg to output 303 | node.send(msg) 304 | 305 | } // ---- End of inputHandler function ---- // 306 | 307 | /** Do any complex, custom node closure code here 308 | * e.g. remove websocket connections 309 | * @param {boolean=} removed - True if the node instance has been removed (@since v0.17) 310 | * @param {any=} done - An internal function if needing async processing or null otherwise 311 | * @param {Object} node - The node definition object 312 | * @param {Object} RED - The RED object from Node-RED 313 | **/ 314 | function processClose(removed = null, done = null, node, RED) { 315 | setNodeStatus({fill: 'red', shape: 'ring', text: 'CLOSED'}, node) 316 | 317 | if ( removed ) { 318 | debug && RED.log.debug('TEST:processClose:node instance removed') //debug 319 | } else { 320 | debug && RED.log.debug('TEST:processClose:node instance restarted/deployed') //debug 321 | } 322 | 323 | // This should be executed last if present. `done` is the data returned from the 'close' 324 | // event and is used to resolve async callbacks to allow Node-RED to close 325 | if (done) done() 326 | } // ---- End of processClose function ---- // 327 | 328 | /** Simple fn to set a node status in the admin interface 329 | * @param {string|Object} status - Status object. If a string, will be turned into a default status object 330 | * @param {Object} node - The node definition object 331 | * fill: red, green, yellow, blue or grey 332 | * shape: ring or dot 333 | **/ 334 | function setNodeStatus( status, node ) { 335 | if ( typeof status !== 'object' ) status = {fill: 'grey', shape: 'ring', text: status} 336 | 337 | node.status(status) 338 | } 339 | 340 | /** Use an ES6 generator function to track how many messages have been received since the last 341 | * restart of NR or redeploy of this node instance. 342 | * This is obviously TOTALLY OVERKILL since a simple variable would have done just as well but hey, gotta start somewhere, right? ;-) 343 | **/ 344 | function* rcvCounter() { 345 | var msgCounter = 1 // start counting from 1 346 | while(true) yield msgCounter++ // keeps going forever! 347 | } 348 | 349 | /** Join any number of string arguments into a valid URL format 350 | * @see http://stackoverflow.com/a/28592528/3016654 351 | * @param {Array.string} urls - a list of strings to join 352 | **/ 353 | function urlJoin() { 354 | const trimRegex = new RegExp('^\\/|\\/$','g') 355 | var paths = Array.prototype.slice.call(arguments) 356 | return '/'+paths.map(function(e){return e.replace(trimRegex,'')}).filter(function(e){return e}).join('/') 357 | } 358 | 359 | // EOF 360 | -------------------------------------------------------------------------------- /nodes/jktest2.html: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | -------------------------------------------------------------------------------- /nodes/jktest2.js: -------------------------------------------------------------------------------- 1 | /** A simple template for defining custom nodes 2 | * Destructured to make for easier and more consistent logic. 3 | */ 4 | 'use strict' 5 | 6 | /** --- Type Defs --- 7 | * @typedef {import('../typedefs.js').runtimeRED} runtimeRED 8 | * @typedef {import('../typedefs.js').runtimeNodeConfig} runtimeNodeConfig 9 | * @typedef {import('../typedefs.js').runtimeNode} runtimeNode 10 | * typedef {import('../typedefs.js').myNode} myNode 11 | */ 12 | 13 | //#region ----- Module level variables ---- // 14 | 15 | /** Main (module) variables - acts as a configuration object 16 | * that can easily be passed around. 17 | */ 18 | const mod = { 19 | /** @type {runtimeRED} Reference to the master RED instance */ 20 | RED: undefined, 21 | /** @type {string} Custom Node Name - has to match with html file and package.json `red` section */ 22 | nodeName: 'jktest2', 23 | 24 | // Add anything else here that you may wish 25 | // to access from any function. 26 | // Having one object also makes it much easier 27 | // to pass this to external modules as needed. 28 | } 29 | 30 | //#endregion ----- Module level variables ---- // 31 | 32 | //#region ----- Module-level support functions ----- // 33 | 34 | /** 1a) Runs once when Node-RED (re)starts or when the node package is first added */ 35 | function moduleSetup() { 36 | // As a module-level named function, it will inherit `mod` and other module-level variables 37 | //const RED = mod.RED 38 | 39 | console.log('>>>=[1a]=>>> [moduleSetup] Startng') 40 | 41 | // Do stuff here that only needs doing once 42 | // Don't forget to push anything that might be needed by other functions and modules 43 | // into the `mod` variable so that it is easily accessible and can be passed on. 44 | } 45 | 46 | /** 3) Run whenever a node instance receives a new input msg 47 | * NOTE: `this` context is still the parent (nodeInstance). 48 | * See https://nodered.org/blog/2019/09/20/node-done 49 | * @param {object} msg The msg object received. 50 | * @param {Function} send Per msg send function, node-red v1+ 51 | * @param {Function} done Per msg finish function, node-red v1+ 52 | */ 53 | function inputMsgHandler(msg, send, done) { 54 | // As a module-level named function, it will inherit `mod` and other module-level variables 55 | 56 | // If you need it - or just use mod.RED if you prefer: 57 | //const RED = mod.RED 58 | 59 | console.log('>>>=[3]=>>> [inputMsgHandler] Startng', msg) //, this) 60 | send(msg) 61 | done() 62 | } 63 | 64 | /** 2) This is run when an actual instance of our node is committed to a flow 65 | * @param {runtimeNodeConfig} config The Node-RED node instance config object 66 | */ 67 | function nodeInstance(config) { 68 | // As a module-level named function, it will inherit `mod` and other module-level variables 69 | 70 | // If you need it - which you will here - or just use mod.RED if you prefer: 71 | const RED = mod.RED 72 | 73 | console.log('>>>=[2]=>>> [nodeInstance] Startng') 74 | //console.log('>>>=[2a]=>>>', config) 75 | 76 | // Create the node instance - `this` can only be referenced AFTER here 77 | RED.nodes.createNode(this, config) 78 | 79 | // Transfer config items from the Editor panel to the runtime 80 | this.name = config.name 81 | this.topic = config.topic 82 | 83 | /** Handle incoming msg's - note that the handler fn inherits `this` 84 | * The inputMsgHandler function is executed every time this instance 85 | * of the node receives a msg in a flow. 86 | */ 87 | this.on('input', inputMsgHandler) 88 | 89 | 90 | /** Put things here if you need to do anything when a node instance is removed 91 | * Or if Node-RED is shutting down. 92 | * Note the use of an arrow function, ensures that the function keeps the 93 | * same `this` context and so has access to all of the node instance properties. 94 | */ 95 | this.on('close', (removed, done) => { 96 | console.log('>>>=[4]=>>> [nodeInstance:close] Closing. Removed?: ', removed) 97 | 98 | 99 | // Give Node-RED a clue when you have finished (more important if your shutdown 100 | // process includes an async task, make sure done() is executed when the async 101 | // task completes, not when this function ends). 102 | done() 103 | }) 104 | 105 | /** Properties of `this` 106 | * Methods: updateWires(wires), context(), on(event,callback), emit(event,...args), removeListener(name,listener), removeAllListeners(name), close(removed) 107 | * send(msg), receive(msg), log(msg), warn(msg), error(logMessage,msg), debug(msg), trace(msg), metric(eventname, msg, metricValue), status(status) 108 | * Other: credentials, id, type, z, wires, x, y 109 | * + any props added manually from config, typically at least name and topic 110 | */ 111 | //console.log('>>>=[2b]=>>>', this) 112 | } 113 | 114 | //#endregion ----- Module-level support functions ----- // 115 | 116 | /** 1) Complete module definition for our Node. This is where things actually start. 117 | * @param {runtimeRED} RED The Node-RED runtime object 118 | */ 119 | function Test2(RED) { 120 | // As a module-level named function, it will inherit `mod` and other module-level variables 121 | 122 | // Save a reference to the RED runtime for convenience 123 | // This allows you to access it from any other function 124 | // defined above. 125 | mod.RED = RED 126 | 127 | // Add function calls here for setup that only needs doing 128 | // once. Node-RED loads this once no matter how many instances 129 | // of you node you add to flows. 130 | moduleSetup() // (1a) 131 | 132 | // Register a new instance of the specified node type (2) 133 | RED.nodes.registerType(mod.nodeName, nodeInstance) 134 | } 135 | 136 | // Export the module definition (1), this is consumed by Node-RED on startup. 137 | module.exports = Test2 138 | 139 | //EOF 140 | -------------------------------------------------------------------------------- /nodes/jktestEditableList.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 186 | 187 | 212 | 213 | 260 | -------------------------------------------------------------------------------- /nodes/jktestEditableList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Julian Knight (Totally Information) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the 'License'); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an 'AS IS' BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict' 18 | 19 | // Module name must match this nodes html file 20 | const moduleName = 'jktestEditableList' 21 | const nodeVersion = require('../package.json').version 22 | 23 | var debug = true 24 | 25 | debug && console.log( 'node-red-contrib-' + moduleName + ' - initialising module. Module Version: ' + nodeVersion ) 26 | 27 | //const events = require('events') 28 | //var ev = new events.EventEmitter(); 29 | //ev.setMaxListeners(0); 30 | 31 | module.exports = function(RED) { 32 | 'use strict' 33 | 34 | debug && console.log( 'node-red-contrib-' + moduleName + ' - loading module' ) 35 | 36 | function nodeGo(config) { 37 | // This fn is run when NR starts and when this node instance is 38 | // deployed after changes to settings 39 | debug && console.log( 'node-red-contrib-' + moduleName + ' - Starting nodeGo, node being registered ' ) 40 | 41 | // Create the node 42 | RED.nodes.createNode(this, config) 43 | 44 | // copy 'this' object in case we need it in context of callbacks of other functions. 45 | const node = this 46 | 47 | // Create local copies of the node configuration (as defined in the .html file) 48 | node.name = config.name || '' 49 | node.topic = config.topic || '' // NB: will be overwritten by msg.topic if recived 50 | 51 | // Track how many messages recieved (using ES6 generator function) 52 | node.msgCounter = rcvCounter() 53 | 54 | // NOTE that this nodes context variables are available from (node.context()).get('varname') 55 | // The node's flow variables are available from (node.context().flow).get('varname') 56 | // The global variables are available from (node.context().global).get('varname') 57 | 58 | // Set to true if you want additional debug output to the console 59 | debug = RED.settings.debug || true 60 | 61 | // If we need an http server to serve a web page 62 | //const app = RED.httpNode || RED.httpAdmin 63 | 64 | // These ONLY log to the NR console (audit is only shown when requested in NR settings) 65 | // RED.log.info('Some Text') 66 | // RED.log.audit({ 'TEST': 'An Object' }) 67 | // These show up in the NR console AND in the NR debug window 68 | //this.log("Something happened"); 69 | //this.warn("Something happened you should know about"); 70 | //this.error("Oh no, something bad happened"); 71 | //this.error("Oh no, something bad happened", msg); // halts current flow, triggers catch node 72 | 73 | setNodeStatus( { fill: 'blue', shape: 'dot', text: 'Node Initialised' }, node ) 74 | 75 | // handler function for node input events (when a node instance receives a msg) 76 | function nodeInputHandler(msg) { 77 | debug && RED.log.info('TEST:nodeGo:nodeInputHandler') //debug 78 | 79 | // If msg is null, nothing will be sent 80 | if ( msg !== null ) { 81 | // if msg isn't null and isn't an object 82 | // NOTE: This is paranoid and shouldn't be possible! 83 | if ( typeof msg !== 'object' ) { 84 | // Force msg to be an object with payload of original msg 85 | msg = { 'payload': msg } 86 | } 87 | // Add topic from node config if present and not present in msg 88 | if ( !(msg.hasOwnProperty('topic')) || msg.topic === '' ) { 89 | if ( node.topic !== '' ) msg.topic = node.topic 90 | } 91 | } 92 | 93 | // Keep this fn small for readability so offload 94 | // any further, more customised code to another fn 95 | msg = inputHandler(msg, node, RED) 96 | 97 | // Send on the input msg to output 98 | node.send(msg) 99 | 100 | } // -- end of msg recieved processing -- // 101 | node.on('input', nodeInputHandler) 102 | 103 | // Do something when Node-RED is closing down 104 | // which includes when this node instance is redeployed 105 | // NOTE: function(done) MUST be used if needing to do async processing 106 | // in close, BUT if used, done() MUST be called because Node-RED 107 | // will wait otherwise. 108 | // node.on('close', function(done) { 109 | node.on('close', function() { 110 | debug && RED.log.info('TEST:nodeGo:on-close') //debug 111 | 112 | // Resolve for async callbacks (done must be the sole param in fn define) ... 113 | //done() 114 | 115 | // Tidy up the event listener (that listens for new msg's) 116 | node.removeListener('input', nodeInputHandler) 117 | 118 | // Do any complex close processing here if needed - MUST BE LAST 119 | processClose(null, node, RED) // swap with below if needing async 120 | //processClose(done, node, RED) 121 | 122 | }) // --- End of on close --- // 123 | 124 | } // ---- End of nodeGo (initialised node instance) ---- // 125 | 126 | // Register the node by name. This must be called before overriding any of the node functions. 127 | RED.nodes.registerType(moduleName, nodeGo) 128 | //RED.nodes.registerType(moduleName, nodeGo, { credentials: { username: {type:"text"}, password: {type:"password"} } }) 129 | } 130 | 131 | // ========== UTILITY FUNCTIONS ================ // 132 | 133 | // Complex, custom code when processing an incoming msg should go here 134 | // Needs to return the msg object 135 | function inputHandler(msg, node, RED) { 136 | msg._msgcounter = node.msgCounter.next().value // iterate counter and attach to msg 137 | setNodeStatus({fill: 'yellow', shape: 'dot', text: 'Message Recieved #' + msg._msgcounter}, node) 138 | 139 | //debug && console.dir(msg) //debug 140 | 141 | return msg 142 | } // ---- End of inputHandler function ---- // 143 | 144 | // Do any complex, custom node closure code here 145 | function processClose(done = null, node, RED) { 146 | setNodeStatus({fill: 'red', shape: 'ring', text: 'CLOSED'}, node) 147 | 148 | // This should be executed last if present. `done` is the data returned from the 'close' 149 | // event and is used to resolve async callbacks to allow Node-RED to close 150 | if (done) done() 151 | } // ---- End of processClose function ---- // 152 | 153 | // Simple fn to set a node status in the admin interface 154 | // fill: red, green, yellow, blue or grey 155 | // shape: ring or dot 156 | function setNodeStatus( status, node ) { 157 | if ( typeof status !== 'object' ) status = {fill: 'grey', shape: 'ring', text: status} 158 | 159 | node.status(status) 160 | } 161 | 162 | // Use an ES6 generator function to track how many messages have been recieved since the last 163 | // restart of NR or redeploy of this node instance. 164 | // This is obviously TOTALLY OVERKILL since a simple variable would have done just as well but hey, gotta start somewhere, right? ;-) 165 | function* rcvCounter() { 166 | var msgCounter = 1 // start counting from 1 167 | while(true) yield msgCounter++ 168 | } 169 | 170 | //from: http://stackoverflow.com/a/28592528/3016654 171 | function urlJoin() { 172 | const trimRegex = new RegExp('^\\/|\\/$','g') 173 | var paths = Array.prototype.slice.call(arguments) 174 | return '/'+paths.map(function(e){return e.replace(trimRegex,'')}).filter(function(e){return e}).join('/') 175 | } 176 | 177 | // EOF 178 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-jktesting", 3 | "version": "0.4.0", 4 | "description": "An experimental node for Node-RED for learning and testing node creation ideas. Can also be used as a template for your own nodes.", 5 | "keywords": [ 6 | "node-red", 7 | "testing", 8 | "test", 9 | "learning" 10 | ], 11 | "main": "none", 12 | "scripts": { 13 | "build": "gulp build", 14 | "npmtags": "npm dist-tag ls node-red-contrib-uibuilder", 15 | "npmtagnext": "npm dist-tag add node-red-contrib-uibuilder@$npm_package_version next", 16 | "gittags": "git tag", 17 | "gittag": "git tag -a v%npm_package_version%", 18 | "gitpushtags": "git push origin --tags" 19 | }, 20 | "homepage": "https://github.com/TotallyInformation/node-red-contrib-jktesting", 21 | "bugs": "https://github.com/TotallyInformation/node-red-contrib-jktesting/issues", 22 | "author": { 23 | "name": "Julian Knight", 24 | "url": "https://github.com/TotallyInformation" 25 | }, 26 | "contributors": [ 27 | { 28 | "name": "Julian Knight", 29 | "url": "https://github.com/TotallyInformation" 30 | } 31 | ], 32 | "license": "Apache-2.0", 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/TotallyInformation/node-red-contrib-jktesting.git" 36 | }, 37 | "node-red": { 38 | "version": ">=2.0.0", 39 | "nodes": { 40 | "jktest": "nodes/jktest.js", 41 | "jktest2": "nodes/jktest2.js", 42 | "jktestEditableList": "nodes/jktestEditableList.js", 43 | "jktest-server": "nodes/jktest-server.js" 44 | }, 45 | "notyetnodes": {} 46 | }, 47 | "dependencies": { 48 | }, 49 | "devDependencies": { 50 | "@types/express": "^4.17.13", 51 | "@types/jquery": "^3.5.6", 52 | "@types/node": "^14.17.14", 53 | "@types/node-red": "^1.1.1", 54 | "eslint": "^7.32.0", 55 | "eslint-plugin-es": "^4.1.0", 56 | "eslint-plugin-html": "^6.1.2", 57 | "eslint-plugin-jsdoc": "^36.0.8", 58 | "eslint-plugin-promise": "^5.1.0", 59 | "eslint-plugin-sonarjs": "^0.10.0", 60 | "execa": "^5.1.1", 61 | "gulp": "^4.0.2", 62 | "gulp-changed": "^4.0.3", 63 | "gulp-include": "^2.4.1", 64 | "gulp-rename": "^2.0.0", 65 | "gulp-uglify": "^3.0.2" 66 | }, 67 | "engines": { 68 | "node": ">=12.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /typedefs.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-irregular-whitespace */ 2 | /** Define typedefs for linting and JSDoc/ts checks - does not actually contain live code 3 | * 4 | * Copyright (c) 2017-2021 Julian Knight (Totally Information) 5 | * https://it.knightnet.org.uk, https://github.com/TotallyInformation/node-red-contrib-uibuilder 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the 'License'); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an 'AS IS' BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | 'use strict' 21 | 22 | /** editorRED 23 | * typedef {object} editorRED The Node-RED core object available to a custom node's .html file 24 | * 25 | */ 26 | 27 | /** runtimeSettings - See settings.js for static settings. 28 | * @typedef {object} runtimeSettings Static and Dynamic settings for Node-RED runtime 29 | * 30 | * @property {string} uiPort The port used by Node-RED (default=1880) 31 | * @property {string} uiHost The host IP used by Node-RED (default=0.0.0.0) 32 | * @property {string} userDir The userDir folder 33 | * @property {string} httpNodeRoot Optional base URL. All user url's will be under this. Default empty string. 34 | * @property {object} FunctionGlobalContext Add values, Functions, packages to the Global context variable store. 35 | * @property {Function} mqttReconnectTime : [Getter/Setter], 36 | * @property {Function} serialReconnectTime : [Getter/Setter], 37 | * @property {Function} debugMaxLength : [Getter/Setter], 38 | * @property {Function} debugUseColors : [Getter/Setter], 39 | * @property {string} flowFile : [Getter/Setter], 40 | * @property {Function} flowFilePretty : [Getter/Setter], 41 | * @property {string} credentialSecret : [Getter/Setter], 42 | * @property {string} httpAdminRoot : [Getter/Setter], 43 | * @property {string} httpStatic : [Getter/Setter], 44 | * @property {Function} adminAuth : [Getter/Setter], 45 | * @property {Function} httpNodeMiddleware : [Getter/Setter], 46 | * @property {Function} httpAdminMiddleware : [Getter/Setter], 47 | * @property {Function} httpServerOptions : [Getter/Setter], 48 | * @property {Function} webSocketNodeVerifyClient : [Getter/Setter], 49 | * @property {Function} exportGlobalContextKeys : [Getter/Setter], 50 | * @property {Function} contextStorage : [Getter/Setter], 51 | * @property {Function} editorTheme : [Getter/Setter], 52 | * @property {string} settingsFile : [Getter/Setter], 53 | * @property {string} httpRoot : [Getter/Setter], 54 | * @property {Function} disableEditor : [Getter/Setter], 55 | * @property {Function} httpAdminAuth : [Getter/Setter], 56 | * @property {Function} httpNodeAuth : [Getter/Setter], 57 | * @property {object|Function} [https] If present, https will be used for ExpressJS servers. 58 | * @property {object} [uibuilder] Optional uibuilder specific Node-RED settings 59 | * @property {number} [uibuilder.port] Port number if uib is using its own ExpressJS instance 60 | * @property {string} [uibuilder.uibRoot] Folder name that will hold all uib runtime and instance folders 61 | * 62 | * @property {string} coreNodesDir Folder containing Node-RED core nodes 63 | * @property {string} version Node-RED version 64 | * 65 | * @property {object} logging Controls the type and amount of logging output 66 | * @property {object} logging.console Controls output levels and types to the console log 67 | * @property {string} logging.console.level What level of output? (fatal, error, warn, info, debug, trace) 68 | * @property {boolean} logging.console.metrics Should metrics also be shown? 69 | * @property {boolean} logging.console.audit Should audit also be shown? 70 | * 71 | * @property {Function} get Get dynamic settings. NB: entries in settings.js are read-only and shouldn't be read using RED.settings.get, that is only for settings that can change in-flight. 72 | * @property {Function} set Set dynamic settings 73 | * @property {Function} delete . 74 | * @property {Function} available . 75 | * 76 | * @property {Function} registerNodeSettings : [Function: registerNodeSettings], 77 | * @property {Function} exportNodeSettings : [Function: exportNodeSettings], 78 | * @property {Function} enableNodeSettings : [Function: enableNodeSettings], 79 | * @property {Function} disableNodeSettings : [Function: disableNodeSettings], 80 | * 81 | * @property {Function} getUserSettings : [Function: getUserSettings], 82 | * @property {Function} setUserSettings : [Function: setUserSettings], 83 | */ 84 | 85 | /** runtimeLogging 86 | * @typedef {object} runtimeLogging Logging. Levels that are output to the Node-RED log are controlled by the logging.console.level setting in settings.js 87 | * @property {Function} fatal Lvel 0. Lowest level, things that have broken Node-RED only. 88 | * @property {Function} error Level 1. Copy is sent to Editor debug panel as well as error log. 89 | * @property {Function} warn Level 2. 90 | * @property {Function} info Level 3. 91 | * @property {Function} debug Level 4. 92 | * @property {Function} trace Level 5. Very verbose output. Should tell the operator everything that is going on. 93 | * @property {Function} metric Log metrics (timings) 94 | * @property {Function} audit Audit log 95 | * @property {Function} addHandler Adds a log handler 96 | * @property {Function} removeHandler Removes a log handler 97 | */ 98 | 99 | /** runtimeNodes 100 | * @typedef {object} runtimeNodes Gives access to other active nodes in the flows. 101 | * @property {Function} registerType Register a new type of node to Node-RED. 102 | * @property {Function} createNode Create a node instance (called from within registerType Function). 103 | * @property {Function} getNode Get a reference to another node instance in the current flows. Can then access its properties. 104 | * @property {Function} eachNode . 105 | * @property {Function} addCredentials . 106 | * @property {Function} getCredentials . 107 | * @property {Function} deleteCredentials . 108 | */ 109 | 110 | /** runtimeRED 111 | * @typedef {object} runtimeRED The core Node-RED runtime object 112 | * @property {expressApp} httpAdmin Reference to the ExpressJS app for Node-RED Admin including the Editor 113 | * @property {expressApp} httpNode Reference to the ExpressJS app for Node-RED user-facing nodes including http-in/-out and Dashboard 114 | * @property {object} server Node.js http(s) Server object 115 | * @property {runtimeLogging} log Logging. 116 | * @property {runtimeNodes} nodes Gives access to other active nodes in the flows. 117 | * @property {runtimeSettings} settings Static and Dynamic settings for Node-RED runtime 118 | * 119 | * @property {Function} version Get the Node-RED version 120 | * @property {Function} require : [Function: requireModule], 121 | * @property {Function} comms : { publish: [Function: publish] }, 122 | * @property {Function} library : { register: [Function: register] }, 123 | * @property {Function} auth : { needsPermission: [Function: needsPermission] }, 124 | * 125 | * @property {object} events Event handler object 126 | * @property {Function} events.on Event Listener Function. Types: 'nodes-started', 'nodes-stopped' 127 | * @property {Function} events.once . 128 | * @property {Function} events.addListener . 129 | * 130 | * @property {object} hooks . 131 | * @property {Function} hooks.has . 132 | * @property {Function} hooks.clear . 133 | * @property {Function} hooks.add . 134 | * @property {Function} hooks.remove . 135 | * @property {Function} hooks.trigger . 136 | * 137 | * @property {object} util . 138 | * @property {Function} util.encodeobject : [Function: encodeobject], 139 | * @property {Function} util.ensurestring : [Function: ensurestring], 140 | * @property {Function} util.ensureBuffer : [Function: ensureBuffer], 141 | * @property {Function} util.cloneMessage : [Function: cloneMessage], 142 | * @property {Function} util.compareobjects : [Function: compareobjects], 143 | * @property {Function} util.generateId : [Function: generateId], 144 | * @property {Function} util.getMessageProperty : [Function: getMessageProperty], 145 | * @property {Function} util.setMessageProperty : [Function: setMessageProperty], 146 | * @property {Function} util.getobjectProperty : [Function: getobjectProperty], 147 | * @property {Function} util.setobjectProperty : [Function: setobjectProperty], 148 | * @property {Function} util.evaluateNodeProperty : [Function: evaluateNodeProperty], 149 | * @property {Function} util.normalisePropertyExpression : [Function: normalisePropertyExpression], 150 | * @property {Function} util.normaliseNodeTypeName : [Function: normaliseNodeTypeName], 151 | * @property {Function} util.prepareJSONataExpression : [Function: prepareJSONataExpression], 152 | * @property {Function} util.evaluateJSONataExpression : [Function: evaluateJSONataExpression], 153 | * @property {Function} util.parseContextStore : [Function: parseContextStore] 154 | */ 155 | 156 | /** runtimeNode 157 | * @typedef {object} runtimeNode Local copy of the node instance config + other info 158 | * @property {Function} send Send a Node-RED msg to an output port 159 | * @property {Function} done Dummy done Function for pre-Node-RED 1.0 servers 160 | * @property {Function} context get/set context data. Also .flow and .global contexts 161 | * @property {Function} on Event listeners for the node instance ('input', 'close') 162 | * @property {Function} removeListener Event handling 163 | * @property {Function} error Error log output, also logs to the Editor's debug panel 164 | * @property {object=} credentials Optional secured credentials 165 | * @property {object=} name Internal. 166 | * @property {object=} id Internal. uid of node instance. 167 | * @property {object=} type Internal. Type of node instance. 168 | * @property {object=} z Internal. uid of ??? 169 | * @property {[Array]=} wires Internal. Array of Array of strings. The wires attached to this node instance (uid's) 170 | */ 171 | 172 | /** runtimeNodeConfig 173 | * @typedef {object} runtimeNodeConfig Configuration of node instance. Will also have Editor panel's defined variables as properties. 174 | * @property {object=} id Internal. uid of node instance. 175 | * @property {object=} type Internal. Type of node instance. 176 | * @property {object=} x Internal 177 | * @property {object=} y Internal 178 | * @property {object=} z Internal 179 | * @property {object=} wires Internal. The wires attached to this node instance (uid's) 180 | */ 181 | 182 | /** uibNode 183 | * @typedef {object} uibNode Local copy of the node instance config + other info 184 | * @property {object} uibNode . 185 | * @property {string} uibNode.id Unique identifier for this instance 186 | * @property {string} uibNode.type What type of node is this an instance of? (uibuilder) 187 | * @property {string} uibNode.name Descriptive name, only used by Editor 188 | * @property {string} uibNode.topic msg.topic overrides incoming msg.topic 189 | * @property {string} uibNode.url The url path (and folder path) to be used by this instance 190 | * @property {string} uibNode.oldUrl The PREVIOUS url path (and folder path) after a url rename 191 | * @property {boolean} uibNode.fwdInMessages Forward input msgs to output #1? 192 | * @property {boolean} uibNode.allowScripts Allow scripts to be sent to front-end via msg? WARNING: can be a security issue. 193 | * @property {boolean} uibNode.allowStyles Allow CSS to be sent to the front-end via msg? WARNING: can be a security issue. 194 | * @property {boolean} uibNode.copyIndex DEPRECATED Copy index.(html|js|css) files from templates if they don't exist? 195 | * @property {string} uibNode.templateFolder Folder name for the source of the chosen template 196 | * @property {string} uibNode.extTemplate Degit url reference for an external template (e.g. from GitHub) 197 | * @property {boolean} uibNode.showfolder Provide a folder index web page? 198 | * @property {boolean} uibNode.useSecurity Use uibuilder's built-in security features? 199 | * @property {boolean} uibNode.tokenAutoExtend Extend token life when msg's received from client? 200 | * @property {number} uibNode.sessionLength Lifespan of token (in seconds) 201 | * @property {boolean} uibNode.reload If true, notify all clients to reload on a change to any source file 202 | * @property {string} uibNode.sourceFolder (src or dist) the instance FE code folder to be served by ExpressJS 203 | * @property {string} uibNode.jwtSecret Seed string for encryption of JWT 204 | * @property {string} uibNode.customFolder Name of the fs path used to hold custom files & folders for THIS INSTANCE 205 | * @property {number} uibNode.ioClientsCount How many Socket clients connected to this instance? 206 | * @property {number} uibNode.rcvMsgCount How many msg's received since last reset or redeploy? 207 | * @property {object} uibNode.ioChannels The channel names for Socket.IO 208 | * @property {string} uibNode.ioChannels.control SIO Control channel name 'uiBuilderControl' 209 | * @property {string} uibNode.ioChannels.client SIO Client channel name 'uiBuilderClient' 210 | * @property {string} uibNode.ioChannels.server SIO Server channel name 'uiBuilder' 211 | * @property {string} uibNode.ioNamespace Make sure each node instance uses a separate Socket.IO namespace 212 | * @property {Function} uibNode.send Send a Node-RED msg to an output port 213 | * @property {Function=} uibNode.done Dummy done Function for pre-Node-RED 1.0 servers 214 | * @property {Function=} uibNode.on Event handler 215 | * @property {Function=} uibNode.removeListener Event handling 216 | * @property {object=} uibNode.credentials Optional secured credentials 217 | * @property {object=} uibNode.z Internal 218 | * @property {object=} uibNode.wires Internal. The wires attached to this node instance (uid's) 219 | * 220 | * @property {boolean} uibNode.commonStaticLoaded Whether the common static folder has been added 221 | * @property {boolean} uibNode.initCopyDone Has the initial template copy been done? 222 | * 223 | * @property {Function} uibNode.warn Output warn level info to node-red console and to editor debug 224 | */ 225 | 226 | /** MsgAuth 227 | * @typedef {object} MsgAuth The standard auth object used by uibuilder security. See docs for details. 228 | * Note that any other data may be passed from your front-end code in the _auth.info object. 229 | * _auth.info.error, _auth.info.validJwt, _auth.info.message, _auth.info.warning 230 | * @property {object} MsgAuth . 231 | * @property {string} MsgAuth.id Required. A unique user identifier. 232 | * @property {string} [MsgAuth.password] Required for login only. 233 | * @property {string} [MsgAuth.jwt] Required if logged in. Needed for ongoing session validation and management. 234 | * @property {number} [MsgAuth.sessionExpiry] Required if logged in. Milliseconds since 1970. Needed for ongoing session validation and management. 235 | * @property {boolean} [MsgAuth.userValidated] Required after user validation. Whether the input ID (and optional additional data from the _auth object) validated correctly or not. 236 | * @property {object=} [MsgAuth.info] Optional metadata about the user. 237 | */ 238 | 239 | /** userValidation 240 | * @typedef {object} userValidation Optional return object that is able to pass on additional use metadata back to the client. 241 | * @property {object} userValidation Optional return object that is able to pass on additional use metadata back to the client. 242 | * @property {boolean} userValidation.userValidated Required. Whether the input ID (and optional additional data from the _auth object) validated correctly or not. 243 | * @property {userMetadata} [userValidation.authData] Optional return metadata about the user. Will be added to the output msg's _auth object 244 | */ 245 | 246 | /** userMetadata 247 | * @typedef {object} userMetadata Optional. Metadata about the user. Will be added to the output msg's _auth object. 248 | * This is just an example of what you might want to return, you can send anything you like. 249 | * @property {object} userMetadata Optional. Metadata about the user. Will be added to the output msg's _auth object. 250 | * @property {string} [userMetadata.name] Users full name or screen name. 251 | * @property {string} [userMetadata.message] A message that the front-end code could use to display to the user when they log in. 252 | * @property {string} [userMetadata.level] Users authorisation level (admin, gold, silver, reader, editor, author, ...). 253 | * @property {string} [userMetadata.location] Users location. 254 | * @property {Date} [userMetadata.passwordExpiry] Date/time the users password expires. 255 | * @property {Date} [userMetadata.subsExpiry] Date/time the users subscription expires. 256 | */ 257 | 258 | /** Props define attributes on a virtual node. 259 | * @typedef {object. | {}} Props 260 | * @property {object} Props . 261 | * @property {Children} Props.children . 262 | */ 263 | /** The vnode children of a virtual node. 264 | * @typedef {VNode[]} Children 265 | */ 266 | /** Define a custom type for virtual nodes: 267 | * @typedef {string | number | Function} Type 268 | * @typedef {object.} VNode 269 | * @property {object.} VNode . 270 | * @property {Type} VNode.type . 271 | * @property {Props} VNode.props . 272 | * @property {Children} VNode.children . 273 | * @property {string} [VNode.key] . 274 | */ 275 | 276 | // ==== vvv These need some work vvv ==== // 277 | 278 | // ExpressJS App 279 | /** 280 | * @typedef {object} expressApp ExpessJS `app` object 281 | * @property {object} _events : [object: null prototype] { mount: [Function: onmount] }, 282 | * @property {number} _eventsCount : 1, 283 | * @property {number} _maxListeners : undefined, 284 | * @property {Function} setMaxListeners : [Function: setMaxListeners], 285 | * @property {Function} getMaxListeners : [Function: getMaxListeners], 286 | * @property {Function} emit : [Function: emit], 287 | * @property {Function} addListener : [Function: addListener], 288 | * @property {Function} on : [Function: addListener], 289 | * @property {Function} prependListener : [Function: prependListener], 290 | * @property {Function} once : [Function: once], 291 | * @property {Function} prependOnceListener : [Function: prependOnceListener], 292 | * @property {Function} removeListener : [Function: removeListener], 293 | * @property {Function} off : [Function: removeListener], 294 | * @property {Function} removeAllListeners : [Function: removeAllListeners], 295 | * @property {Function} listeners : [Function: listeners], 296 | * @property {Function} rawListeners : [Function: rawListeners], 297 | * @property {Function} listenerCount : [Function: listenerCount], 298 | * @property {Function} eventNames : [Function: eventNames], 299 | * @property {Function} init : [Function: init], 300 | * @property {Function} defaultConfiguration : [Function: defaultConfiguration], 301 | * @property {Function} lazyrouter : [Function: lazyrouter], 302 | * @property {Function} handle : [Function: handle], 303 | * @property {Function} use : [Function: use], 304 | * @property {Function} route : [Function: route], 305 | * @property {Function} engine : [Function: engine], 306 | * @property {Function} param : [Function: param], 307 | * @property {Function} set : [Function: set], 308 | * @property {Function} path : [Function: path], 309 | * @property {Function} enabled : [Function: enabled], 310 | * @property {Function} disabled : [Function: disabled], 311 | * @property {Function} enable : [Function: enable], 312 | * @property {Function} disable : [Function: disable], 313 | * @property {Function} acl : [Function (anonymous)], 314 | * @property {Function} bind : [Function (anonymous)], 315 | * @property {Function} checkout : [Function (anonymous)], 316 | * @property {Function} connect : [Function (anonymous)], 317 | * @property {Function} copy : [Function (anonymous)], 318 | * @property {Function} delete : [Function (anonymous)], 319 | * @property {Function} get : [Function (anonymous)], 320 | * @property {Function} head : [Function (anonymous)], 321 | * @property {Function} link : [Function (anonymous)], 322 | * @property {Function} lock : [Function (anonymous)], 323 | * @property {Function} 'm-search' : [Function (anonymous)], 324 | * @property {Function} merge : [Function (anonymous)], 325 | * @property {Function} mkactivity : [Function (anonymous)], 326 | * @property {Function} mkcalendar : [Function (anonymous)], 327 | * @property {Function} mkcol : [Function (anonymous)], 328 | * @property {Function} move : [Function (anonymous)], 329 | * @property {Function} notify : [Function (anonymous)], 330 | * @property {Function} options : [Function (anonymous)], 331 | * @property {Function} patch : [Function (anonymous)], 332 | * @property {Function} post : [Function (anonymous)], 333 | * @property {Function} pri : [Function (anonymous)], 334 | * @property {Function} propfind : [Function (anonymous)], 335 | * @property {Function} proppatch : [Function (anonymous)], 336 | * @property {Function} purge : [Function (anonymous)], 337 | * @property {Function} put : [Function (anonymous)], 338 | * @property {Function} rebind : [Function (anonymous)], 339 | * @property {Function} report : [Function (anonymous)], 340 | * @property {Function} search : [Function (anonymous)], 341 | * @property {Function} source : [Function (anonymous)], 342 | * @property {Function} subscribe : [Function (anonymous)], 343 | * @property {Function} trace : [Function (anonymous)], 344 | * @property {Function} unbind : [Function (anonymous)], 345 | * @property {Function} unlink : [Function (anonymous)], 346 | * @property {Function} unlock : [Function (anonymous)], 347 | * @property {Function} unsubscribe : [Function (anonymous)], 348 | * @property {Function} all : [Function: all], 349 | * @property {Function} del : [Function (anonymous)], 350 | * @property {Function} render : [Function: render], 351 | * @property {Function} listen : [Function: listen], 352 | * @property {Function} request : IncomingMessage { app: [Circular *1] }, 353 | * @property {Function} response : ServerResponse { app: [Circular *1] }, 354 | * @property {object} cache : {}, 355 | * @property {object} engines : {}, 356 | * 357 | * @property {object} settings : { 358 | * @property {boolean} settings.'x-powered-by' : true, 359 | * @property {string} settings.etag : 'weak', 360 | * @property {Function} settings."etag fn" : [Function: generateETag], 361 | * @property {string} settings.env : 'development', 362 | * @property {string} settings.'query parser' : 'extended', 363 | * @property {Function} settings.'query parser fn' : [Function: parseExtendedQuerystring], 364 | * @property {number} settings.'subdomain offset' : 2, 365 | * @property {Function} settings.view : [Function: View], 366 | * @property {string} settings.views : 'C:\\src\\nr2\\views', 367 | * @property {string} settings.'jsonp callback name' : 'callback' 368 | * 369 | * @property {object} locals : [object: null prototype] { settings: [object] }, 370 | * @property {string} mountpath : '/nr/', 371 | * 372 | * @property {Function} parent : [Function: app] { 373 | * @property {Function} parent._events : [object: null prototype], 374 | * @property {Function} parent._eventsCount : 1, 375 | * @property {Function} parent._maxListeners : undefined, 376 | * @property {Function} parent.setMaxListeners : [Function: setMaxListeners], 377 | * @property {Function} parent.getMaxListeners : [Function: getMaxListeners], 378 | * @property {Function} parent.emit : [Function: emit], 379 | * @property {Function} parent.addListener : [Function: addListener], 380 | * @property {Function} parent.on : [Function: addListener], 381 | * @property {Function} parent.prependListener : [Function: prependListener], 382 | * @property {Function} parent.once : [Function: once], 383 | * @property {Function} parent.prependOnceListener : [Function: prependOnceListener], 384 | * @property {Function} parent.removeListener : [Function: removeListener], 385 | * @property {Function} parent.off : [Function: removeListener], 386 | * @property {Function} parent.removeAllListeners : [Function: removeAllListeners], 387 | * @property {Function} parent.listeners : [Function: listeners], 388 | * @property {Function} parent.rawListeners : [Function: rawListeners], 389 | * @property {Function} parent.listenerCount : [Function: listenerCount], 390 | * @property {Function} parent.eventNames : [Function: eventNames], 391 | * @property {Function} parent.init : [Function: init], 392 | * @property {Function} parent.defaultConfiguration : [Function: defaultConfiguration], 393 | * @property {Function} parent.lazyrouter : [Function: lazyrouter], 394 | * @property {Function} parent.handle : [Function: handle], 395 | * @property {Function} parent.use : [Function: use], 396 | * @property {Function} parent.route : [Function: route], 397 | * @property {Function} parent.engine : [Function: engine], 398 | * @property {Function} parent.param : [Function: param], 399 | * @property {Function} parent.set : [Function: set], 400 | * @property {Function} parent.path : [Function: path], 401 | * @property {Function} parent.enabled : [Function: enabled], 402 | * @property {Function} parent.disabled : [Function: disabled], 403 | * @property {Function} parent.enable : [Function: enable], 404 | * @property {Function} parent.disable : [Function: disable], 405 | * @property {Function} parent.acl : [Function (anonymous)], 406 | * @property {Function} parent.bind : [Function (anonymous)], 407 | * @property {Function} parent.checkout : [Function (anonymous)], 408 | * @property {Function} parent.connect : [Function (anonymous)], 409 | * @property {Function} parent.copy : [Function (anonymous)], 410 | * @property {Function} parent.delete : [Function (anonymous)], 411 | * @property {Function} parent.get : [Function (anonymous)], 412 | * @property {Function} parent.head : [Function (anonymous)], 413 | * @property {Function} parent.link : [Function (anonymous)], 414 | * @property {Function} parent.lock : [Function (anonymous)], 415 | * @property {Function} parent.'m-search' : [Function (anonymous)], 416 | * @property {Function} parent.merge : [Function (anonymous)], 417 | * @property {Function} parent.mkactivity : [Function (anonymous)], 418 | * @property {Function} locals : [object: null prototype], 419 | * @property {Function} mountpath : '/', 420 | * @property {Function} _router : [Function] 421 | */ 422 | 423 | module.exports = {} 424 | 425 | /* RED 426 | { 427 | "loader": {}, 428 | "events": {}, 429 | "i18n": {}, 430 | "settings": { 431 | "apiRootUrl": "", 432 | "httpNodeRoot": "/nr/", 433 | "version": "1.2.7", 434 | "user": { 435 | "anonymous": true, 436 | "permissions": "*" 437 | }, 438 | "context": { 439 | "default": "default", 440 | "stores": [ 441 | "default", 442 | "file" 443 | ] 444 | }, 445 | "flowFilePretty": true, 446 | "flowEncryptionType": "user", 447 | "tlsConfigDisableLocalFiles": false, 448 | "uibuilderNodeEnv": "development", // === CUSTOM === 449 | "uibuilderTemplates": {}, // === CUSTOM === 450 | "uibuilderPort": 3000, // === CUSTOM === 451 | "editorTheme": {}, 452 | 453 | "get": Function, 454 | init: Function, 455 | load: Function, 456 | loadUserSettings: Function, 457 | remove: Function, 458 | set: Function, 459 | theme: Function, 460 | "user": {}, 461 | "comms": {}, 462 | "text": {}, 463 | "state": {}, 464 | "nodes": {}, 465 | "history": {}, 466 | "validators": {}, 467 | "utils": {}, 468 | "menu": {}, 469 | "panels": {}, 470 | "popover": {}, 471 | "tabs": {}, 472 | "stack": {}, 473 | "colorPicker": {}, 474 | "actions": {}, 475 | "deploy": {}, 476 | "diff": {}, 477 | "keyboard": {}, 478 | "workspaces": {}, 479 | "statusBar": {}, 480 | "view": { 481 | "navigator": {}, 482 | "tools": {} 483 | }, 484 | "sidebar": {}, 485 | "palette": {}, 486 | "editor": {}, 487 | "eventLog": {}, 488 | "tray": {}, 489 | "clipboard": {}, 490 | "library": {}, 491 | "notifications": {}, 492 | "search": {}, 493 | "actionList": {}, 494 | "typeSearch": {}, 495 | "subflow": {}, 496 | "group": {}, 497 | "userSettings": {}, 498 | "projects": {}, 499 | "touch": {}, 500 | "debug": {} 501 | } 502 | */ 503 | /* this 504 | { 505 | name: "" 506 | topic: "" 507 | //... other vars ...// 508 | 509 | credentials: { has_jwtSecret: false, _: { … } } 510 | 511 | changed: false 512 | dirty: false 513 | icon: undefined 514 | id: "b18a50dd.f7e5c" 515 | info: undefined 516 | infoEditor: w { $toDestroy: Array(46), container: div.red - ui - editor - text - container.ace_editor.ace_hidpi.red - ui - editor - text - container - toolbar.ace - tomo…, renderer: y, id: "editor2", commands: o, … } 517 | inputLabels: "" 518 | inputs: 1 519 | outputLabels: ['',''] 520 | outputs: 2 521 | resize: false 522 | selected: true 523 | status: { text: "Node Initialised", fill: "blue", shape: "dot" } 524 | type: "uibuilder" 525 | valid: true 526 | validationErrors: [] 527 | 528 | g: "c49c82f3.7e716" 529 | h: 30 530 | l: true 531 | w: 120 532 | x: 530 533 | y: 120 534 | z: "18cb249f.38bafb" 535 | 536 | _: ƒ() 537 | __outputs: 2 538 | _config: { name: """", topic: """", url: ""vue - file"", fwdInMessages: "false", allowScripts: "false", … } 539 | _def: { category: "uibuilder", color: "#E6E0F8", defaults: { … }, credentials: { … }, inputs: 1, … } 540 | } 541 | */ 542 | --------------------------------------------------------------------------------