@5minds/node-red-contrib-processcube 1.16.1-feature-5e019f-mhojvcvu → 1.16.1-feature-b8d70b-mhvx8c1e

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.
@@ -3,10 +3,10 @@ module.exports = function (RED) {
3
3
  RED.nodes.createNode(this, config);
4
4
  var node = this;
5
5
 
6
- node.on('input', function (msg) {
6
+ node.on('input', function (msg) {
7
7
  const flowNodeInstanceId = msg.flowNodeInstanceId;
8
8
  const etw_input_node_id = msg.etw_input_node_id;
9
-
9
+
10
10
  if (!etw_input_node_id) {
11
11
  node.error('Error: The message did not contain the required etw_input_node_id.');
12
12
  } else {
@@ -24,7 +24,7 @@ module.exports = function (RED) {
24
24
  if (msgError.code) {
25
25
  errorCode = msgError.code;
26
26
  errorMessage = msgError.message;
27
- }
27
+ }
28
28
  } else if (msg.errorCode) {
29
29
  errorCode = msg.errorCode;
30
30
  errorMessage = msg.errorMessage;
@@ -1,9 +1,128 @@
1
1
  const EventEmitter = require('node:events');
2
2
 
3
+ class ExternalTaskNodeStates {
4
+ constructor(flowNodeInstanceId) {
5
+ this.flowNodeInstanceId = flowNodeInstanceId;
6
+ this.nodeStades = {}; // Track send calls per nodeId
7
+ }
8
+
9
+ markSended(nodeId) {
10
+ if (!this.nodeStades[nodeId]) {
11
+ this.nodeStades[nodeId] = { gotSend: false, gotCompleted: false };
12
+ }
13
+
14
+ console.log(`[DEBUG] markSended - flowNodeInstanceId: ${this.flowNodeInstanceId}, nodeId: ${nodeId}, before: ${JSON.stringify(this.nodeStades[nodeId])}`);
15
+ this.nodeStades[nodeId].gotSend = true;
16
+ console.log(`[DEBUG] markSended - after: ${JSON.stringify(this.nodeStades[nodeId])}`);
17
+ }
18
+
19
+ markCompleted(nodeId) {
20
+ if (!this.nodeStades[nodeId]) {
21
+ this.nodeStades[nodeId] = { gotSend: false, gotCompleted: false };
22
+ }
23
+
24
+ console.log(`[DEBUG] markCompleted - flowNodeInstanceId: ${this.flowNodeInstanceId}, nodeId: ${nodeId}, before: ${JSON.stringify(this.nodeStades[nodeId])}`);
25
+ this.nodeStades[nodeId].gotCompleted = true;
26
+ console.log(`[DEBUG] markCompleted - after: ${JSON.stringify(this.nodeStades[nodeId])}`);
27
+ }
28
+
29
+ checkIfCompletedWithoutSend(nodeId) {
30
+ const nodeState = this.nodeStades[nodeId];
31
+ const result = (nodeState && nodeState.gotCompleted && !nodeState.gotSend);
32
+
33
+ console.log(`[DEBUG] checkIfCompletedWithoutSend - flowNodeInstanceId: ${this.flowNodeInstanceId}, nodeId: ${nodeId}, nodeState: ${JSON.stringify(nodeState)}, result: ${result}`);
34
+ return result;
35
+ }
36
+ }
37
+
3
38
  module.exports = function (RED) {
4
39
 
5
40
  const os = require('os');
6
41
 
42
+ // Global dictionary for tracking external tasks by flowNodeInstanceId
43
+ const globalExternalTaskStates = {};
44
+
45
+ const raiseExternalTaskError = (flowNodeInstanceId, etwInputNodeId, nodeId) => {
46
+ const fullNode = RED.nodes.getNode(nodeId);
47
+
48
+ const wires = fullNode?.wires;
49
+ const hasConnectedOutputs = wires && wires.some(wireArray => wireArray && wireArray.length > 0);
50
+
51
+ console.log(`[DEBUG] raiseExternalTaskError called for flowNodeInstanceId: ${flowNodeInstanceId}, nodeId: ${nodeId}, hasConnectedOutputs: ${hasConnectedOutputs}`);
52
+
53
+ if (hasConnectedOutputs) {
54
+ const inputNode = RED.nodes.getNode(etwInputNodeId);
55
+
56
+ if (inputNode && inputNode.eventEmitter) {
57
+ const errorMessage = `Node ${nodeId} (${fullNode.name || fullNode.type}) completed without sending output`;
58
+ const error = new Error(errorMessage);
59
+ error.errorCode = 'NODE_NO_OUTPUT';
60
+ error.errorDetails = RED.util.encodeObject({
61
+ flowNodeInstanceId: flowNodeInstanceId,
62
+ nodeId: nodeId,
63
+ nodeName: fullNode.name,
64
+ nodeType: fullNode.type
65
+ });
66
+
67
+ console.log(`[DEBUG] Emitting error event for flowNodeInstanceId: ${flowNodeInstanceId}, error: ${errorMessage}`);
68
+ inputNode.eventEmitter.emit(`handle-${flowNodeInstanceId}`, error, true);
69
+ } else {
70
+ console.log(`[DEBUG] Cannot raise error - inputNode or eventEmitter not found for etwInputNodeId: ${etwInputNodeId}`);
71
+ }
72
+ }
73
+ };
74
+
75
+ // Example synchronous onSend hook
76
+ RED.hooks.add("onSend", (sendEvents) => {
77
+ for (const sendEvent of sendEvents) {
78
+
79
+ // Call send method on ExternalTaskState if this message has a flowNodeInstanceId
80
+ if (sendEvent.msg?.flowNodeInstanceId) {
81
+ let externalTaskNodeStates = globalExternalTaskStates[sendEvent.msg.flowNodeInstanceId];
82
+
83
+ console.log(`[DEBUG] onSend - flowNodeInstanceId: ${sendEvent.msg.flowNodeInstanceId}, nodeId: ${sendEvent.source.node.id}, stateExists: ${!!externalTaskNodeStates}`);
84
+
85
+ if (!externalTaskNodeStates) {
86
+ console.log(`[DEBUG] onSend - Creating NEW ExternalTaskNodeStates for flowNodeInstanceId: ${sendEvent.msg.flowNodeInstanceId}`);
87
+ externalTaskNodeStates = new ExternalTaskNodeStates(sendEvent.msg.flowNodeInstanceId);
88
+ globalExternalTaskStates[sendEvent.msg.flowNodeInstanceId] = externalTaskNodeStates;
89
+ }
90
+
91
+ externalTaskNodeStates.markSended(sendEvent.source.node.id)
92
+
93
+ if (externalTaskNodeStates.checkIfCompletedWithoutSend(sendEvent.source.node.id)) {
94
+ console.log(`[DEBUG] onSend - Node completed without send detected! Raising error for nodeId: ${sendEvent.source.node.id}`);
95
+ raiseExternalTaskError(sendEvent.msg.flowNodeInstanceId, sendEvent.msg.etw_input_node_id, sendEvent.source.node.id);
96
+ }
97
+ }
98
+ }
99
+ });
100
+
101
+ const onCompleted = (completeEvent) => {
102
+
103
+ // Check if this is an external task message
104
+ if (completeEvent.msg?.flowNodeInstanceId) {
105
+ let externalTaskNodeStates = globalExternalTaskStates[completeEvent.msg.flowNodeInstanceId];
106
+
107
+ console.log(`[DEBUG] onComplete - flowNodeInstanceId: ${completeEvent.msg.flowNodeInstanceId}, nodeId: ${completeEvent.node.id}, stateExists: ${!!externalTaskNodeStates}`);
108
+
109
+ if (!externalTaskNodeStates) {
110
+ console.log(`[DEBUG] onComplete - Creating NEW ExternalTaskNodeStates for flowNodeInstanceId: ${completeEvent.msg.flowNodeInstanceId}`);
111
+ externalTaskNodeStates = new ExternalTaskNodeStates(completeEvent.msg.flowNodeInstanceId);
112
+ globalExternalTaskStates[completeEvent.msg.flowNodeInstanceId] = externalTaskNodeStates;
113
+ }
114
+
115
+ externalTaskNodeStates.markCompleted(completeEvent.node.id);
116
+
117
+ if (externalTaskNodeStates.checkIfCompletedWithoutSend(completeEvent.node.id)) {
118
+ console.log(`[DEBUG] onComplete - Node completed without send detected! Raising error for nodeId: ${completeEvent.node.id}`);
119
+ raiseExternalTaskError(completeEvent.msg.flowNodeInstanceId, completeEvent.msg.etw_input_node_id, completeEvent.node.id);
120
+ }
121
+ }
122
+ }
123
+
124
+ RED.hooks.add("onComplete", onCompleted);
125
+
7
126
  function ExternalTaskInput(config) {
8
127
  RED.nodes.createNode(this, config);
9
128
  var node = this;
@@ -165,60 +284,68 @@ module.exports = function (RED) {
165
284
  };
166
285
 
167
286
  const onPreDeliver = (sendEvent) => {
168
- if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
169
-
170
- const sourceNode = sendEvent?.source?.node;
171
- const destinationNode = sendEvent?.destination?.node;
287
+ try {
288
+ if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
172
289
 
173
- node._step = `${destinationNode.name || destinationNode.type}`;
290
+ const sourceNode = sendEvent?.source?.node;
291
+ const destinationNode = sendEvent?.destination?.node;
174
292
 
175
- node.showStatus();
293
+ node._step = `${destinationNode.name || destinationNode.type}`;
176
294
 
177
- const debugMsg = {
178
- event: 'enter',
179
- sourceName: sourceNode.name,
180
- sourceType: sourceNode.type,
181
- destinationNodeName: destinationNode.name,
182
- destinationNodeType: destinationNode.type,
183
- timestamp: new Date().toISOString(),
184
- };
295
+ node.showStatus();
296
+
297
+ const debugMsg = {
298
+ event: 'enter',
299
+ sourceName: sourceNode.name,
300
+ sourceType: sourceNode.type,
301
+ destinationNodeName: destinationNode.name,
302
+ destinationNodeType: destinationNode.type,
303
+ timestamp: new Date().toISOString(),
304
+ };
185
305
 
186
- node.traceExecution(debugMsg);
306
+ node.traceExecution(debugMsg);
187
307
 
188
- if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
189
- node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
190
- node.log(`preDeliver: ${node._trace}`);
308
+ if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
309
+ node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
310
+ node.log(`preDeliver: ${node._trace}`);
311
+ }
191
312
  }
313
+ } catch (error) {
314
+ node.error(`Error in onPreDeliver: ${error?.message}`, { error: error });
192
315
  }
193
316
  };
194
- RED.hooks.add('preDeliver', onPreDeliver);
317
+ RED.hooks.add(`preDeliver.etw-input-${node.id}`, onPreDeliver);
195
318
 
196
319
  const onPostDeliver = (sendEvent) => {
197
- if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
198
- const sourceNode = sendEvent?.source?.node;
199
- const destinationNode = sendEvent?.destination?.node;
200
-
201
- node.decrMsgOnNode(sourceNode, sendEvent.msg);
202
- node.incMsgOnNode(destinationNode, sendEvent.msg);
203
-
204
- const debugMsg = {
205
- event: 'exit',
206
- sourceName: sourceNode.name,
207
- sourceType: sourceNode.type,
208
- destinationNodeName: destinationNode.name,
209
- destinationNodeType: destinationNode.type,
210
- timestamp: new Date().toISOString(),
211
- };
320
+ try {
321
+ if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
322
+ const sourceNode = sendEvent?.source?.node;
323
+ const destinationNode = sendEvent?.destination?.node;
324
+
325
+ node.decrMsgOnNode(sourceNode, sendEvent.msg);
326
+ node.incMsgOnNode(destinationNode, sendEvent.msg);
327
+
328
+ const debugMsg = {
329
+ event: 'exit',
330
+ sourceName: sourceNode.name,
331
+ sourceType: sourceNode.type,
332
+ destinationNodeName: destinationNode.name,
333
+ destinationNodeType: destinationNode.type,
334
+ timestamp: new Date().toISOString(),
335
+ };
212
336
 
213
- node.traceExecution(debugMsg);
337
+ node.traceExecution(debugMsg);
214
338
 
215
- if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
216
- node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
217
- node.log(`postDeliver: ${node._trace}`);
339
+ if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
340
+ node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
341
+ node.log(`postDeliver: ${node._trace}`);
342
+ }
218
343
  }
344
+ } catch (error) {
345
+ node.error(`Error in onPostDeliver: ${error?.message}`, { error: error });
219
346
  }
220
347
  };
221
- RED.hooks.add('postDeliver', onPostDeliver);
348
+ RED.hooks.add(`postDeliver.etw-input-${node.id}`, onPostDeliver);
222
349
 
223
350
  node.setSubscribedStatus = () => {
224
351
  this._subscribed = true;
@@ -349,15 +476,34 @@ module.exports = function (RED) {
349
476
  return;
350
477
  }
351
478
  const etwCallback = async (payload, externalTask) => {
479
+
480
+ console.log(`[DEBUG] etwCallback - NEW External Task received! flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, processInstanceId: ${externalTask.processInstanceId}`);
481
+ console.log(`[DEBUG] etwCallback - Creating NEW ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}`);
482
+ globalExternalTaskStates[externalTask.flowNodeInstanceId] = new ExternalTaskNodeStates(externalTask.flowNodeInstanceId);
483
+
352
484
  const saveHandleCallback = (data, callback, msg) => {
353
485
  try {
354
486
  callback(data);
355
487
  node.log(`send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}`);
488
+
489
+ // Remove ExternalTaskState from global dictionary
490
+ if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
491
+ console.log(`[DEBUG] saveHandleCallback SUCCESS - Deleting ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}`);
492
+ delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
493
+ }
494
+
356
495
  node.setFinishHandlingTaskStatus(externalTask);
357
496
  } catch (error) {
497
+ // Remove ExternalTaskState from global dictionary on error as well
498
+ if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
499
+ console.log(`[DEBUG] saveHandleCallback ERROR - Deleting ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, error: ${error?.message}`);
500
+ delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
501
+ }
502
+
358
503
  node.setErrorFinishHandlingTaskStatus(externalTask, error);
359
504
  msg.error = error;
360
505
  node.error(`failed send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}: ${error?.message}`, msg);
506
+ callback(error);
361
507
  }
362
508
  };
363
509
 
@@ -378,6 +524,7 @@ module.exports = function (RED) {
378
524
  };
379
525
 
380
526
  const handleErrorTask = (error) => {
527
+ console.log(`[DEBUG] handleErrorTask - flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, errorCode: ${error?.errorCode}, errorMessage: ${error?.message}`);
381
528
  node.log(
382
529
  `handle error event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' on *msg._msgid* '${error.errorDetails?._msgid}'.`
383
530
  );
@@ -390,6 +537,8 @@ module.exports = function (RED) {
390
537
  };
391
538
 
392
539
  node.eventEmitter.once(`handle-${externalTask.flowNodeInstanceId}`, (msg, isError = false) => {
540
+ console.log(`[DEBUG] eventEmitter handle event - flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, isError: ${isError}, msgId: ${msg._msgid}`);
541
+
393
542
  try {
394
543
  msg.etw_finished_at = new Date().toISOString();
395
544
 
@@ -397,7 +546,7 @@ module.exports = function (RED) {
397
546
  msg.etw_duration = new Date(msg.etw_finished_at) - new Date(msg.etw_started_at);
398
547
  }
399
548
  } catch (error) {
400
- node.error(`failed to calculate duration: ${error?.message}`, msg);
549
+ node.error(`failed to calculate duration: ${error?.message}`, msg);
401
550
  }
402
551
 
403
552
  node.log(
@@ -406,8 +555,10 @@ module.exports = function (RED) {
406
555
 
407
556
 
408
557
  if (isError) {
558
+ console.log(`[DEBUG] Routing to handleErrorTask`);
409
559
  handleErrorTask(msg);
410
560
  } else {
561
+ console.log(`[DEBUG] Routing to handleFinishTask`);
411
562
  handleFinishTask(msg);
412
563
  }
413
564
  });
@@ -429,6 +580,7 @@ module.exports = function (RED) {
429
580
  );
430
581
 
431
582
  node.send(msg);
583
+ console.log(`[DEBUG] etwCallback - Sent message for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, msgId: ${msg._msgid}`);
432
584
  });
433
585
  };
434
586
 
@@ -488,12 +640,12 @@ module.exports = function (RED) {
488
640
 
489
641
  node.on('close', () => {
490
642
  try {
491
- RED.hooks.remove('preDeliver', onPreDeliver);
492
- RED.hooks.remove('postDeliver', onPostDeliver);
493
643
  externalTaskWorker.stop();
644
+ RED.hooks.remove(`preDeliver.etw-input-${node.id}`);
645
+ RED.hooks.remove(`postDeliver.etw-input-${node.id}`);
494
646
  node.log('External Task Worker closed.');
495
- } catch {
496
- node.error('Client close failed', {});
647
+ } catch (error) {
648
+ node.error(`Client close failed: ${JSON.stringify(error)}`);
497
649
  }
498
650
  });
499
651
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@5minds/node-red-contrib-processcube",
3
- "version": "1.16.1-feature-5e019f-mhojvcvu",
3
+ "version": "1.16.1-feature-b8d70b-mhvx8c1e",
4
4
  "license": "MIT",
5
5
  "description": "Node-RED nodes for ProcessCube",
6
6
  "scripts": {
@@ -39,7 +39,6 @@
39
39
  "DataobjectInstanceQuery": "dataobject-instance-query.js",
40
40
  "EndEventFinishedListener": "endevent-finished-listener.js",
41
41
  "externaltaskInput": "externaltask-input.js",
42
- "externaltaskInject": "externaltask-inject.js",
43
42
  "externaltaskOutput": "externaltask-output.js",
44
43
  "externaltaskError": "externaltask-error.js",
45
44
  "externaltaskEventListener": "externaltask-event-listener.js",
@@ -1,123 +0,0 @@
1
- <script type="text/javascript">
2
- RED.nodes.registerType('externaltask-inject', {
3
- category: 'ProcessCube',
4
- color: '#02AFD6',
5
- defaults: {
6
- name: { value: '' },
7
- payloadType: { value: 'json' },
8
- payload: { value: '{}' },
9
- taskType: { value: 'json' },
10
- task: { value: '{}' }
11
- },
12
- inputs: 0,
13
- outputs: 1,
14
- icon: 'inject.svg',
15
- label: function () {
16
- return this.name || 'externaltask-inject';
17
- },
18
- button: {
19
- onclick: function() {
20
- var node = this;
21
-
22
- $.ajax({
23
- type: "POST",
24
- url: "externaltask-inject/trigger/" + node.id,
25
- success: function(resp) {
26
- RED.notify("Message injected", "success");
27
- },
28
- error: function(xhr, status, err) {
29
- RED.notify("Error injecting message: " + err, "error");
30
- }
31
- });
32
- }
33
- },
34
- oneditprepare: function () {
35
- // Initialize the typed input for payload
36
- $('#node-input-payload').typedInput({
37
- default: 'json',
38
- types: ['str', 'num', 'bool', 'json', 'jsonata', 'flow', 'global']
39
- });
40
- // Restore the saved type for payload from hidden input
41
- var payloadType = $('#node-input-payloadType').val();
42
- if (payloadType) {
43
- $('#node-input-payload').typedInput('type', payloadType);
44
- }
45
-
46
- // Initialize the typed input for task
47
- $('#node-input-task').typedInput({
48
- default: 'json',
49
- types: ['str', 'num', 'bool', 'json', 'jsonata', 'flow', 'global']
50
- });
51
- // Restore the saved type for task from hidden input
52
- var taskType = $('#node-input-taskType').val();
53
- if (taskType) {
54
- $('#node-input-task').typedInput('type', taskType);
55
- }
56
- },
57
- oneditsave: function () {
58
- var payloadValue = $('#node-input-payload').typedInput('value');
59
- var payloadType = $('#node-input-payload').typedInput('type');
60
- this.payload = payloadValue;
61
- this.payloadType = payloadType;
62
- // Persist type to hidden input field
63
- $('#node-input-payloadType').val(payloadType);
64
-
65
- var taskValue = $('#node-input-task').typedInput('value');
66
- var taskType = $('#node-input-task').typedInput('type');
67
- this.task = taskValue;
68
- this.taskType = taskType;
69
- // Persist type to hidden input field
70
- $('#node-input-taskType').val(taskType);
71
- }
72
- });
73
- </script>
74
-
75
- <script type="text/html" data-template-name="externaltask-inject">
76
- <div class="form-row">
77
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
78
- <input type="text" id="node-input-name" placeholder="Name" />
79
- </div>
80
- <div class="form-row">
81
- <label for="node-input-payload">msg.payload</label>
82
- <input type="hidden" id="node-input-payloadType">
83
- <input type="text" id="node-input-payload" style="width: 70%;">
84
- </div>
85
- <div class="form-row">
86
- <label for="node-input-task">msg.task</label>
87
- <input type="hidden" id="node-input-taskType">
88
- <input type="text" id="node-input-task" style="width: 70%;">
89
- </div>
90
- </script>
91
-
92
- <script type="text/markdown" data-help-name="externaltask-inject">
93
- A lightweight alternative to the standard Node-RED inject node for testing external task workflows. Use this node to inject test messages directly into an external task workflow without needing a real external task from the ProcessCube engine.
94
-
95
- This node is ideal for small-scale testing and debugging of external task workflows. For larger-scale testing with test cases or CI/CD integration, use the **externaltask-testing** node instead.
96
-
97
- ## Features
98
-
99
- - Injects messages with configurable payload and task metadata
100
- - Automatically generates `flowNodeInstanceId` and `processInstanceId` if not provided
101
- - Sets required fields for compatibility with externaltask-output and externaltask-error nodes
102
- - Supports multiple value types (JSON, string, number, boolean, JSONata, flow/global context)
103
-
104
- ## Configs
105
-
106
- : name (string) : The name of the node
107
- : payload (string | {}) : The payload to inject into the workflow
108
- : task (string | {}) : The task object to inject
109
-
110
- ## Outputs
111
-
112
- : payload (string | {}) : The injected payload with metadata (flowNodeInstanceId, processInstanceId, task)
113
- : task (object) : The injected task object
114
- : flowNodeInstanceId (string) : Unique identifier for the external task
115
- : processInstanceId (string) : Reference to the process instance
116
- : etw_input_node_id (string) : Reference to the injecting node
117
- : etw_started_at (string) : ISO timestamp of when the task was injected
118
-
119
- ## References
120
-
121
- - [The ProcessCube&copy; Developer Network](https://processcube.io) - All documentation for the ProcessCube&copy; platform
122
- - [ProcessCube&copy; LowCode Integration](https://processcube.io/docs/node-red) - LowCode integration in ProcessCube&copy;
123
- </script>
@@ -1,98 +0,0 @@
1
- const { v4: uuidv4 } = require('uuid');
2
- const EventEmitter = require('node:events');
3
-
4
- module.exports = function (RED) {
5
- function ExternalTaskInject(config) {
6
- RED.nodes.createNode(this, config);
7
- var node = this;
8
- node.config = config;
9
- node.eventEmitter = new EventEmitter();
10
-
11
- node.on('input', function (msg, send, done) {
12
- msg.flowNodeInstanceId = msg.flowNodeInstanceId ?? uuidv4();
13
- msg.processInstanceId = msg.processInstanceId ?? uuidv4();
14
-
15
- if (!msg.task) {
16
- // Use configured task if available, otherwise create default
17
- if (config.task) {
18
- msg.task = config.task;
19
- } else {
20
- msg.task = {
21
- flowNodeInstanceId: msg.flowNodeInstanceId,
22
- processInstanceId: msg.processInstanceId,
23
- task: {}
24
- }
25
- }
26
- }
27
- msg.etw_started_at = new Date().toISOString();
28
- msg.etw_input_node_id = node.id;
29
-
30
- send(msg);
31
- if (done) done();
32
- });
33
-
34
- }
35
-
36
- RED.nodes.registerType('externaltask-inject', ExternalTaskInject);
37
-
38
- // Helper function to parse typed values
39
- function parseTypedValue(value, type) {
40
- switch(type) {
41
- case 'str':
42
- return value || '';
43
- case 'num':
44
- return Number(value) || 0;
45
- case 'bool':
46
- return value === 'true' || value === true;
47
- case 'json':
48
- try {
49
- return JSON.parse(value || '{}');
50
- } catch(parseErr) {
51
- console.error("Invalid JSON: " + parseErr.message);
52
- return {};
53
- }
54
- case 'jsonata':
55
- // JSONata expressions would need to be evaluated in Node-RED context
56
- // For now, treat as string
57
- return value || '';
58
- case 'flow':
59
- case 'global':
60
- // These are context references - store as string for now
61
- return value || '';
62
- default:
63
- return value || '';
64
- }
65
- }
66
-
67
- // HTTP endpoint to handle button clicks
68
- RED.httpAdmin.post('/externaltask-inject/trigger/:id', function(req, res) {
69
- var node = RED.nodes.getNode(req.params.id);
70
- if (node !== null && typeof node !== 'undefined') {
71
- try {
72
- var payloadValue = node.config.payload;
73
- var payloadType = node.config.payloadType || 'json';
74
- var payloadData = parseTypedValue(payloadValue, payloadType);
75
-
76
- var taskValue = node.config.task;
77
- var taskType = node.config.taskType || 'json';
78
- var taskData = parseTypedValue(taskValue, taskType);
79
-
80
- // Create a message with the configured payload and task
81
- var msg = {
82
- payload: payloadData,
83
- task: taskData,
84
- _msgid: uuidv4()
85
- };
86
-
87
- // Trigger the node's input handler with the configured message
88
- node.receive(msg);
89
- res.sendStatus(200);
90
- } catch(err) {
91
- res.sendStatus(500);
92
- node.error("Error injecting message: " + err.message);
93
- }
94
- } else {
95
- res.sendStatus(404);
96
- }
97
- });
98
- };