@node-red/runtime 2.1.4 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright JS Foundation and other contributors, http://js.foundation
1
+ Copyright OpenJS Foundation and other contributors, https://openjsf.org/
2
2
 
3
3
  Apache License
4
4
  Version 2.0, January 2004
@@ -99,6 +99,7 @@ var api = module.exports = {
99
99
  * @param {Object} opts
100
100
  * @param {User} opts.user - the user calling the api
101
101
  * @param {String} opts.id - the id of the project to activate
102
+ * @param {boolean} opts.clearContext - whether to clear context
102
103
  * @param {Object} opts.req - the request to log (optional)
103
104
  * @return {Promise<Object>} - resolves when complete
104
105
  * @memberof @node-red/runtime_projects
@@ -107,7 +108,7 @@ var api = module.exports = {
107
108
  var currentProject = runtime.storage.projects.getActiveProject(opts.user);
108
109
  runtime.log.audit({event: "projects.set",id:opts.id}, opts.req);
109
110
  if (!currentProject || opts.id !== currentProject.name) {
110
- return runtime.storage.projects.setActiveProject(opts.user, opts.id);
111
+ return runtime.storage.projects.setActiveProject(opts.user, opts.id, opts.clearContext);
111
112
  }
112
113
  },
113
114
 
package/lib/flows/Flow.js CHANGED
@@ -424,6 +424,17 @@ class Flow {
424
424
  */
425
425
  getGroupEnvSetting(node, group, name) {
426
426
  if (group) {
427
+ if (name === "NR_GROUP_NAME") {
428
+ return [{
429
+ val: group.name
430
+ }, null];
431
+ }
432
+ if (name === "NR_GROUP_ID") {
433
+ return [{
434
+ val: group.id
435
+ }, null];
436
+ }
437
+
427
438
  if (group.credentials === undefined) {
428
439
  group.credentials = credentials.get(group.id) || {};
429
440
  }
@@ -498,7 +509,13 @@ class Flow {
498
509
  * @return {[type]} [description]
499
510
  */
500
511
  getSetting(key) {
501
- const flow = this.flow;
512
+ const flow = this.flow;
513
+ if (key === "NR_FLOW_NAME") {
514
+ return flow.label;
515
+ }
516
+ if (key === "NR_FLOW_ID") {
517
+ return flow.id;
518
+ }
502
519
  if (flow.credentials === undefined) {
503
520
  flow.credentials = credentials.get(flow.id) || {};
504
521
  }
@@ -371,6 +371,17 @@ class Subflow extends Flow {
371
371
  name = name.substring(8);
372
372
  }
373
373
  const node = this.subflowInstance;
374
+ if (node) {
375
+ if (name === "NR_NODE_NAME") {
376
+ return node.name;
377
+ }
378
+ if (name === "NR_NODE_ID") {
379
+ return node.id;
380
+ }
381
+ if (name === "NR_NODE_PATH") {
382
+ return node._path;
383
+ }
384
+ }
374
385
  if (node.g) {
375
386
  const group = this.getGroupNode(node.g);
376
387
  const [result, newName] = this.getGroupEnvSetting(node, group, name);
package/lib/flows/util.js CHANGED
@@ -83,7 +83,9 @@ function createNode(flow,config) {
83
83
  }
84
84
  }
85
85
  try {
86
+ Object.defineProperty(conf,'_module', {value: typeRegistry.getNodeInfo(type), enumerable: false, writable: true })
86
87
  Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true })
88
+ Object.defineProperty(conf,'_path', {value: `${flow.path}/${config._alias||config.id}`, enumerable: false, writable: true })
87
89
  newNode = new nodeTypeConstructor(conf);
88
90
  } catch (err) {
89
91
  Log.log({
package/lib/nodes/Node.js CHANGED
@@ -59,6 +59,12 @@ function Node(n) {
59
59
  // which we can tolerate as they are the same object.
60
60
  Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true })
61
61
  }
62
+ if (n._module) {
63
+ Object.defineProperty(this,'_module', {value: n._module, enumerable: false, writable: true })
64
+ }
65
+ if (n._path) {
66
+ Object.defineProperty(this,'_path', {value: n._path, enumerable: false, writable: true })
67
+ }
62
68
  this.updateWires(n.wires);
63
69
  }
64
70
 
@@ -496,7 +502,12 @@ function log_helper(self, level, msg) {
496
502
  if (self.name) {
497
503
  o.name = self.name;
498
504
  }
499
- self._flow.log(o);
505
+ // See https://github.com/node-red/node-red/issues/3327
506
+ try {
507
+ self._flow.log(o);
508
+ } catch(err) {
509
+ logUnexpectedError(self, err)
510
+ }
500
511
  }
501
512
  /**
502
513
  * Log an INFO level message
@@ -576,4 +587,59 @@ Node.prototype.status = function(status) {
576
587
  this._flow.handleStatus(this,status);
577
588
  };
578
589
 
590
+
591
+ function inspectObject(flow) {
592
+ try {
593
+ let properties = new Set()
594
+ let currentObj = flow
595
+ do {
596
+ if (!Object.getPrototypeOf(currentObj)) { break }
597
+ Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
598
+ } while ((currentObj = Object.getPrototypeOf(currentObj)))
599
+ let propList = [...properties.keys()].map(item => `${item}[${(typeof flow[item])[0]}]`)
600
+ propList.sort();
601
+ let result = [];
602
+ let line = "";
603
+ while (propList.length > 0) {
604
+ let prop = propList.shift()
605
+ if (line.length+prop.length > 80) {
606
+ result.push(line)
607
+ line = "";
608
+ } else {
609
+ line += " "+prop
610
+ }
611
+ }
612
+ if (line.length > 0) {
613
+ result.push(line);
614
+ }
615
+ return result.join("\n ")
616
+
617
+ } catch(err) {
618
+ return "Failed to capture object properties: "+err.toString()
619
+ }
620
+ }
621
+
622
+ function logUnexpectedError(node, error) {
623
+ let moduleInfo = node._module?`${node._module.module}@${node._module.version}`:"undefined"
624
+ Log.error(`
625
+ ********************************************************************
626
+ Unexpected Node Error
627
+ ${error.stack}
628
+ Node:
629
+ Type: ${node.type}
630
+ Module: ${moduleInfo}
631
+ ID: ${node._alias||node.id}
632
+ Properties:
633
+ ${inspectObject(node)}
634
+ Flow: ${node._flow?node._flow.path:'undefined'}
635
+ Type: ${node._flow?node._flow.TYPE:'undefined'}
636
+ Properties:
637
+ ${node._flow?inspectObject(node._flow):'undefined'}
638
+
639
+ Please report this issue, including the information logged above:
640
+ https://github.com/node-red/node-red/issues/
641
+ ********************************************************************
642
+ `)
643
+ }
644
+
579
645
  module.exports = Node;
@@ -14,30 +14,30 @@
14
14
  * limitations under the License.
15
15
  **/
16
16
 
17
- var clone = require("clone");
18
- var log = require("@node-red/util").log;
19
- var util = require("@node-red/util").util;
20
- var memory = require("./memory");
17
+ const clone = require("clone");
18
+ const log = require("@node-red/util").log;
19
+ const util = require("@node-red/util").util;
20
+ const memory = require("./memory");
21
21
 
22
- var settings;
22
+ let settings;
23
23
 
24
24
  // A map of scope id to context instance
25
- var contexts = {};
25
+ let contexts = {};
26
26
 
27
27
  // A map of store name to instance
28
- var stores = {};
29
- var storeList = [];
30
- var defaultStore;
28
+ let stores = {};
29
+ let storeList = [];
30
+ let defaultStore;
31
31
 
32
32
  // Whether there context storage has been configured or left as default
33
- var hasConfiguredStore = false;
33
+ let hasConfiguredStore = false;
34
34
 
35
35
  // Unknown Stores
36
- var unknownStores = {};
36
+ let unknownStores = {};
37
37
 
38
38
  function logUnknownStore(name) {
39
39
  if (name) {
40
- var count = unknownStores[name] || 0;
40
+ let count = unknownStores[name] || 0;
41
41
  if (count == 0) {
42
42
  log.warn(log._("context.unknown-store", {name: name}));
43
43
  count++;
@@ -52,8 +52,8 @@ function init(_settings) {
52
52
  stores = {};
53
53
  storeList = [];
54
54
  hasConfiguredStore = false;
55
- var seed = settings.functionGlobalContext || {};
56
- contexts['global'] = createContext("global",seed);
55
+ initialiseGlobalContext();
56
+
57
57
  // create a default memory store - used by the unit tests that skip the full
58
58
  // `load()` initialisation sequence.
59
59
  // If the user has any stores configured, this will be disgarded
@@ -61,6 +61,11 @@ function init(_settings) {
61
61
  defaultStore = "memory";
62
62
  }
63
63
 
64
+ function initialiseGlobalContext() {
65
+ const seed = settings.functionGlobalContext || {};
66
+ contexts['global'] = createContext("global",seed);
67
+ }
68
+
64
69
  function load() {
65
70
  return new Promise(function(resolve,reject) {
66
71
  // load & init plugins in settings.contextStorage
@@ -233,12 +238,15 @@ function validateContextKey(key) {
233
238
 
234
239
  function createContext(id,seed,parent) {
235
240
  // Seed is only set for global context - sourced from functionGlobalContext
236
- var scope = id;
237
- var obj = seed || {};
238
- var seedKeys;
239
- var insertSeedValues;
241
+ const scope = id;
242
+ const obj = {};
243
+ let seedKeys;
244
+ let insertSeedValues;
240
245
  if (seed) {
241
246
  seedKeys = Object.keys(seed);
247
+ seedKeys.forEach(key => {
248
+ obj[key] = seed[key];
249
+ })
242
250
  insertSeedValues = function(keys,values) {
243
251
  if (!Array.isArray(keys)) {
244
252
  if (values[0] === undefined) {
@@ -540,8 +548,28 @@ function getContext(nodeId, flowId) {
540
548
  return newContext;
541
549
  }
542
550
 
551
+ /**
552
+ * Delete the context of the given node/flow/global
553
+ *
554
+ * If the user has configured a context store, this
555
+ * will no-op a request to delete node/flow context.
556
+ */
543
557
  function deleteContext(id,flowId) {
544
- if(!hasConfiguredStore){
558
+ if (id === "global") {
559
+ // 1. delete global from all configured stores
560
+ var promises = [];
561
+ for(var plugin in stores){
562
+ if(stores.hasOwnProperty(plugin)){
563
+ promises.push(stores[plugin].delete('global'));
564
+ }
565
+ }
566
+ return Promise.all(promises).then(function() {
567
+ // 2. delete global context
568
+ delete contexts['global'];
569
+ // 3. reinitialise global context
570
+ initialiseGlobalContext();
571
+ })
572
+ } else if (!hasConfiguredStore) {
545
573
  // only delete context if there's no configured storage.
546
574
  var contextId = id;
547
575
  if (flowId) {
@@ -549,12 +577,19 @@ function deleteContext(id,flowId) {
549
577
  }
550
578
  delete contexts[contextId];
551
579
  return stores["_"].delete(contextId);
552
- }else{
580
+ } else {
553
581
  return Promise.resolve();
554
582
  }
555
583
  }
556
584
 
585
+ /**
586
+ * Delete any contexts that are no longer in use
587
+ * @param flowConfig object includes allNodes as object of id->node
588
+ *
589
+ * If flowConfig is undefined, all flow/node contexts will be removed
590
+ **/
557
591
  function clean(flowConfig) {
592
+ flowConfig = flowConfig || { allNodes: {} };
558
593
  var promises = [];
559
594
  for(var plugin in stores){
560
595
  if(stores.hasOwnProperty(plugin)){
@@ -572,6 +607,16 @@ function clean(flowConfig) {
572
607
  return Promise.all(promises);
573
608
  }
574
609
 
610
+ /**
611
+ * Deletes all contexts, including global and reinitialises global to
612
+ * initial state.
613
+ */
614
+ function clear() {
615
+ return clean().then(function() {
616
+ return deleteContext('global')
617
+ })
618
+ }
619
+
575
620
  function close() {
576
621
  var promises = [];
577
622
  for(var plugin in stores){
@@ -594,5 +639,6 @@ module.exports = {
594
639
  getFlowContext:getFlowContext,
595
640
  delete: deleteContext,
596
641
  clean: clean,
642
+ clear: clear,
597
643
  close: close
598
644
  };
@@ -206,6 +206,7 @@ module.exports = {
206
206
  eachNode: flows.eachNode,
207
207
  getContext: context.get,
208
208
 
209
+ clearContext: context.clear,
209
210
 
210
211
  installerEnabled: registry.installerEnabled,
211
212
  installModule: installModule,
@@ -377,8 +377,17 @@ function getActiveProject(user) {
377
377
  return activeProject;
378
378
  }
379
379
 
380
- function reloadActiveProject(action) {
380
+ function reloadActiveProject(action, clearContext) {
381
+ // Stop the current flows
381
382
  return runtime.nodes.stopFlows().then(function() {
383
+ if (clearContext) {
384
+ // Reset context to remove any old values
385
+ return runtime.nodes.clearContext()
386
+ } else {
387
+ return Promise.resolve()
388
+ }
389
+ }).then(function() {
390
+ // Load the new project flows and start them
382
391
  return runtime.nodes.loadFlows(true).then(function() {
383
392
  events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
384
393
  }).catch(function(err) {
@@ -387,6 +396,9 @@ function reloadActiveProject(action) {
387
396
  events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
388
397
  throw err;
389
398
  });
399
+ }).catch(function(err) {
400
+ console.log(err.stack);
401
+ throw err;
390
402
  });
391
403
  }
392
404
  function createProject(user, metadata) {
@@ -424,7 +436,7 @@ function createProject(user, metadata) {
424
436
  return getProject(user, metadata.name);
425
437
  })
426
438
  }
427
- function setActiveProject(user, projectName) {
439
+ function setActiveProject(user, projectName, clearContext) {
428
440
  return loadProject(projectName).then(function(project) {
429
441
  var globalProjectSettings = settings.get("projects")||{};
430
442
  globalProjectSettings.activeProject = project.name;
@@ -434,7 +446,7 @@ function setActiveProject(user, projectName) {
434
446
  // console.log("Updated file targets to");
435
447
  // console.log(flowsFullPath)
436
448
  // console.log(credentialsFile)
437
- return reloadActiveProject("loaded");
449
+ return reloadActiveProject("loaded", clearContext);
438
450
  })
439
451
  });
440
452
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-red/runtime",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./lib/index.js",
6
6
  "repository": {
@@ -16,11 +16,11 @@
16
16
  }
17
17
  ],
18
18
  "dependencies": {
19
- "@node-red/registry": "2.1.4",
20
- "@node-red/util": "2.1.4",
19
+ "@node-red/registry": "2.2.0",
20
+ "@node-red/util": "2.2.0",
21
21
  "async-mutex": "0.3.2",
22
22
  "clone": "2.1.2",
23
- "express": "4.17.1",
23
+ "express": "4.17.2",
24
24
  "fs-extra": "10.0.0",
25
25
  "json-stringify-safe": "5.0.1"
26
26
  }