@5minds/node-red-contrib-processcube 0.10.3 → 0.11.0-develop-76d190-lyed2q9i

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ {
2
+ "$": "4d3947a7155e180f5e1361182578e6ca2miDlL35ToUWYsN6HIEZN1KBk9Ec3/4="
3
+ }
@@ -2,11 +2,8 @@
2
2
  "name": "node-red-contrib-processcube-sample",
3
3
  "version": "1.0.0",
4
4
  "description": "A sample for the processcube node-red contrib package",
5
- "scripts": {
6
- "start": "./node_modules/.bin/node-red --settings=./settings.js node-red-contrib-processcube-flows.json"
7
- },
8
5
  "dependencies": {
9
- "@5minds/node-red-contrib-processcube": "file:..",
6
+ "@5minds/node-red-contrib-processcube": "file:/src/node-red-contrib-processcube/",
10
7
  "@5minds/node-red-dashboard-2-processcube-dynamic-form": "^1.0.4",
11
8
  "@5minds/node-red-dashboard-2-processcube-usertask-table": "^1.0.7",
12
9
  "@flowfuse/node-red-dashboard": "^1.11.1",
@@ -41,7 +41,7 @@ module.exports = {
41
41
  * node-red from being able to decrypt your existing credentials and they will be
42
42
  * lost.
43
43
  */
44
- //credentialSecret: "a-secret-key",
44
+ credentialSecret: "a-secret-key",
45
45
 
46
46
  /** By default, the flow JSON will be formatted over multiple lines making
47
47
  * it easier to compare changes when using version control.
@@ -53,7 +53,7 @@ module.exports = {
53
53
  * the user's home directory. To use a different location, the following
54
54
  * property can be used
55
55
  */
56
- //userDir: '/home/nol/.node-red/',
56
+ userDir: '/nodered/',
57
57
 
58
58
  /** Node-RED scans the `nodes` directory in the userDir to find local node files.
59
59
  * The following property can be used to specify an additional directory to scan.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@5minds/node-red-contrib-processcube",
3
- "version": "0.10.3",
3
+ "version": "0.11.0-develop-76d190-lyed2q9i",
4
4
  "license": "MIT",
5
5
  "description": "Node-RED nodes for ProcessCube",
6
6
  "authors": [
@@ -48,7 +48,9 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@5minds/processcube_engine_client": "^5.0.0"
51
+ "@5minds/processcube_engine_client": "^5.0.0",
52
+ "jwt-decode": "^4.0.0",
53
+ "openid-client": "^5.5.0"
52
54
  },
53
55
  "keywords": [
54
56
  "node-red",
package/process-start.js CHANGED
@@ -27,7 +27,7 @@ module.exports = function(RED) {
27
27
  initialToken: msg.payload
28
28
  };
29
29
 
30
- client.processDefinitions.startProcessInstance(startParameters).then((result) => {
30
+ client.processDefinitions.startProcessInstance(startParameters, node.engine.identity).then((result) => {
31
31
 
32
32
  msg.payload = result;
33
33
 
@@ -6,6 +6,10 @@
6
6
  },
7
7
  label: function() {
8
8
  return this.url;
9
+ },
10
+ credentials: {
11
+ clientId: { type: "text" },
12
+ clientSecret: { type: "password" }
9
13
  }
10
14
  });
11
15
  </script>
@@ -15,4 +19,12 @@
15
19
  <label for="node-config-input-url"><i class="fa fa-bookmark"></i> Url</label>
16
20
  <input type="text" id="node-config-input-url">
17
21
  </div>
22
+ <div class="form-row">
23
+ <label for="node-config-input-clientId"><i class="fa fa-bookmark"></i> Client id</label>
24
+ <input type="text" id="node-config-input-clientId">
25
+ </div>
26
+ <div class="form-row">
27
+ <label for="node-config-input-clientSecret"><i class="fa fa-bookmark"></i> Client secret</label>
28
+ <input type="password" id="node-config-input-clientSecret">
29
+ </div>
18
30
  </script>
@@ -1,7 +1,121 @@
1
+ const engine_client = require('@5minds/processcube_engine_client');
2
+ const jwt = require('jwt-decode');
3
+ const oidc = require('openid-client');
4
+
5
+ const DELAY_FACTOR = 0.85;
6
+
1
7
  module.exports = function(RED) {
2
8
  function ProcessCubeEngineNode(n) {
3
9
  RED.nodes.createNode(this, n);
10
+ const identityChangedCallbacks = [];
4
11
  this.url = n.url;
12
+ this.identity = null;
13
+ this.registerOnIdentityChanged = function (callback) {
14
+ identityChangedCallbacks.push(callback);
15
+ }
16
+ this.setIdentity = (identity) => {
17
+ this.identity = identity;
18
+
19
+ for (const callback of identityChangedCallbacks) {
20
+ callback(identity);
21
+ }
22
+ }
23
+
24
+ if (this.credentials.clientId && this.credentials.clientSecret) {
25
+ const engineClient = new engine_client.EngineClient(this.url);
26
+
27
+ engineClient.applicationInfo.getAuthorityAddress().then(authorityUrl => {
28
+ startRefreshingIdentityCycle(this.credentials.clientId, this.credentials.clientSecret, authorityUrl, this);
29
+ });
30
+ }
5
31
  }
6
- RED.nodes.registerType("processcube-engine-config", ProcessCubeEngineNode);
32
+ RED.nodes.registerType("processcube-engine-config", ProcessCubeEngineNode, {
33
+ credentials: {
34
+ clientId: { type: "text" },
35
+ clientSecret: { type: "password" }
36
+ }
37
+ });
38
+ }
39
+
40
+ async function getFreshTokenSet(clientId, clientSecret, authorityUrl) {
41
+ const issuer = await oidc.Issuer.discover(authorityUrl);
42
+
43
+ const client = new issuer.Client({
44
+ client_id: clientId,
45
+ client_secret: clientSecret,
46
+ });
47
+
48
+ const tokenSet = await client.grant({
49
+ grant_type: 'client_credentials',
50
+ scope: 'engine_etw engine_read engine_write',
51
+ });
52
+
53
+ return tokenSet;
54
+ }
55
+
56
+ function getIdentityForExternalTaskWorkers(tokenSet) {
57
+
58
+ const accessToken = tokenSet.access_token;
59
+ const decodedToken = jwt.jwtDecode(accessToken);
60
+
61
+ return {
62
+ token: tokenSet.access_token,
63
+ userId: decodedToken.sub,
64
+ };
65
+ }
66
+
67
+ async function getExpiresInForExternalTaskWorkers(tokenSet) {
68
+ let expiresIn = tokenSet.expires_in;
69
+
70
+ if (!expiresIn && tokenSet.expires_at) {
71
+ expiresIn = Math.floor(tokenSet.expires_at - Date.now() / 1000);
72
+ }
73
+
74
+ if (expiresIn === undefined) {
75
+ throw new Error('Could not determine the time until the access token for external task workers expires');
76
+ }
77
+
78
+ return expiresIn;
79
+ }
80
+
81
+ /**
82
+ * Start refreshing the identity in regular intervals.
83
+ * @param {TokenSet | null} tokenSet The token set to refresh the identity for
84
+ * @returns {Promise<void>} A promise that resolves when the timer for refreshing the identity is initialized
85
+ * */
86
+ async function startRefreshingIdentityCycle(clientId, clientSecret, authorityUrl, configNode) {
87
+ let retries = 5;
88
+
89
+ const refresh = async () => {
90
+ try {
91
+ const newTokenSet = await getFreshTokenSet(clientId, clientSecret, authorityUrl);
92
+ const expiresIn = await getExpiresInForExternalTaskWorkers(newTokenSet);
93
+ const delay = expiresIn * DELAY_FACTOR * 1000;
94
+
95
+ freshIdentity = getIdentityForExternalTaskWorkers(newTokenSet);
96
+
97
+ configNode.setIdentity(freshIdentity);
98
+
99
+ retries = 5;
100
+ setTimeout(refresh, delay);
101
+ } catch (error) {
102
+ if (retries === 0) {
103
+ console.error(
104
+ 'Could not refresh identity for external task worker processes. Stopping all external task workers.',
105
+ { error },
106
+ );
107
+ return;
108
+ }
109
+ console.error('Could not refresh identity for external task worker processes.', {
110
+ error,
111
+ retryCount: retries,
112
+ });
113
+ retries--;
114
+
115
+ const delay = 2 * 1000;
116
+ setTimeout(refresh, delay);
117
+ }
118
+ };
119
+
120
+ await refresh();
7
121
  }
@@ -34,8 +34,12 @@ module.exports = function(RED) {
34
34
  });
35
35
 
36
36
  node.on('input', function(msg) {
37
- const query = RED.util.evaluateNodeProperty(config.query, config.query_type, node, msg)
38
-
37
+ let query = RED.util.evaluateNodeProperty(config.query, config.query_type, node, msg)
38
+ query = {
39
+ ...query,
40
+ identity: node.server.identity
41
+ };
42
+
39
43
  client.processDefinitions.getAll(query).then((matchingProcessDefinitions) => {
40
44
 
41
45
 
@@ -8,9 +8,8 @@
8
8
  <bpmn:lane id="Lane_1xzf0d3">
9
9
  <bpmn:flowNodeRef>StartEvent_1</bpmn:flowNodeRef>
10
10
  <bpmn:flowNodeRef>Activity_1h843f0</bpmn:flowNodeRef>
11
- <bpmn:flowNodeRef>Event_0z1wtnd</bpmn:flowNodeRef>
12
- <bpmn:flowNodeRef>Activity_1y52hvq</bpmn:flowNodeRef>
13
11
  <bpmn:flowNodeRef>Activity_1duxvq1</bpmn:flowNodeRef>
12
+ <bpmn:flowNodeRef>Event_0z1wtnd</bpmn:flowNodeRef>
14
13
  </bpmn:lane>
15
14
  </bpmn:laneSet>
16
15
  <bpmn:startEvent id="StartEvent_1" name="Start">
@@ -27,27 +26,22 @@
27
26
  <bpmn:outgoing>Flow_110cohh</bpmn:outgoing>
28
27
  </bpmn:serviceTask>
29
28
  <bpmn:sequenceFlow id="Flow_110cohh" sourceRef="Activity_1h843f0" targetRef="Activity_1duxvq1" />
30
- <bpmn:sequenceFlow id="Flow_1u1afua" sourceRef="Activity_1duxvq1" targetRef="Activity_1y52hvq" />
31
- <bpmn:endEvent id="Event_0z1wtnd">
32
- <bpmn:incoming>Flow_1oiihw7</bpmn:incoming>
33
- </bpmn:endEvent>
34
- <bpmn:task id="Activity_1y52hvq" name="do wait">
35
- <bpmn:incoming>Flow_1u1afua</bpmn:incoming>
36
- <bpmn:outgoing>Flow_1oiihw7</bpmn:outgoing>
37
- </bpmn:task>
38
- <bpmn:sequenceFlow id="Flow_1oiihw7" sourceRef="Activity_1y52hvq" targetRef="Event_0z1wtnd" />
39
- <bpmn:serviceTask id="Activity_1duxvq1" name="get process models" camunda:type="external" camunda:topic="processmodels">
29
+ <bpmn:serviceTask id="Activity_1duxvq1" name="SampleError" camunda:type="external" camunda:topic="SampleError">
40
30
  <bpmn:incoming>Flow_110cohh</bpmn:incoming>
41
31
  <bpmn:outgoing>Flow_1u1afua</bpmn:outgoing>
42
32
  </bpmn:serviceTask>
33
+ <bpmn:sequenceFlow id="Flow_1u1afua" sourceRef="Activity_1duxvq1" targetRef="Event_0z1wtnd" />
34
+ <bpmn:endEvent id="Event_0z1wtnd">
35
+ <bpmn:incoming>Flow_1u1afua</bpmn:incoming>
36
+ </bpmn:endEvent>
43
37
  </bpmn:process>
44
38
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
45
39
  <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_1cidyxu">
46
40
  <bpmndi:BPMNShape id="Participant_0px403d_di" bpmnElement="Participant_0px403d" isHorizontal="true">
47
- <dc:Bounds x="5" y="4" width="885" height="346" />
41
+ <dc:Bounds x="5" y="4" width="695" height="346" />
48
42
  </bpmndi:BPMNShape>
49
43
  <bpmndi:BPMNShape id="Lane_1xzf0d3_di" bpmnElement="Lane_1xzf0d3" isHorizontal="true">
50
- <dc:Bounds x="35" y="4" width="855" height="346" />
44
+ <dc:Bounds x="35" y="4" width="665" height="346" />
51
45
  </bpmndi:BPMNShape>
52
46
  <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
53
47
  <dc:Bounds x="92" y="152" width="36" height="36" />
@@ -59,17 +53,13 @@
59
53
  <dc:Bounds x="180" y="130" width="100" height="80" />
60
54
  <bpmndi:BPMNLabel />
61
55
  </bpmndi:BPMNShape>
62
- <bpmndi:BPMNShape id="Event_0z1wtnd_di" bpmnElement="Event_0z1wtnd">
63
- <dc:Bounds x="692" y="152" width="36" height="36" />
64
- </bpmndi:BPMNShape>
65
- <bpmndi:BPMNShape id="Activity_1y52hvq_di" bpmnElement="Activity_1y52hvq">
66
- <dc:Bounds x="540" y="130" width="100" height="80" />
67
- <bpmndi:BPMNLabel />
68
- </bpmndi:BPMNShape>
69
56
  <bpmndi:BPMNShape id="Activity_1ohliek_di" bpmnElement="Activity_1duxvq1">
70
57
  <dc:Bounds x="370" y="130" width="100" height="80" />
71
58
  <bpmndi:BPMNLabel />
72
59
  </bpmndi:BPMNShape>
60
+ <bpmndi:BPMNShape id="Event_0z1wtnd_di" bpmnElement="Event_0z1wtnd">
61
+ <dc:Bounds x="562" y="152" width="36" height="36" />
62
+ </bpmndi:BPMNShape>
73
63
  <bpmndi:BPMNEdge id="Flow_1xfzejj_di" bpmnElement="Flow_1xfzejj">
74
64
  <di:waypoint x="128" y="170" />
75
65
  <di:waypoint x="180" y="170" />
@@ -80,11 +70,7 @@
80
70
  </bpmndi:BPMNEdge>
81
71
  <bpmndi:BPMNEdge id="Flow_1u1afua_di" bpmnElement="Flow_1u1afua">
82
72
  <di:waypoint x="470" y="170" />
83
- <di:waypoint x="540" y="170" />
84
- </bpmndi:BPMNEdge>
85
- <bpmndi:BPMNEdge id="Flow_1oiihw7_di" bpmnElement="Flow_1oiihw7">
86
- <di:waypoint x="640" y="170" />
87
- <di:waypoint x="692" y="170" />
73
+ <di:waypoint x="562" y="170" />
88
74
  </bpmndi:BPMNEdge>
89
75
  </bpmndi:BPMNPlane>
90
76
  </bpmndi:BPMNDiagram>
@@ -34,9 +34,9 @@ module.exports = function(RED) {
34
34
  });
35
35
 
36
36
  node.on('input', function(msg) {
37
- const query = RED.util.evaluateNodeProperty(config.query, config.query_type, node, msg)
37
+ let query = RED.util.evaluateNodeProperty(config.query, config.query_type, node, msg)
38
38
 
39
- client.processInstances.query(query).then((matchingInstances) => {
39
+ client.processInstances.query(query, { identity: node.server.identity }).then((matchingInstances) => {
40
40
 
41
41
  msg.payload = matchingInstances;
42
42
 
@@ -25,7 +25,8 @@ module.exports = function(RED) {
25
25
  config.signalname,
26
26
  {
27
27
  processInstanceId: config.processinstanceid,
28
- payload: msg.payload
28
+ payload: msg.payload,
29
+ identity: node.server.identity
29
30
  }
30
31
 
31
32
  ).then((result) => {
@@ -28,14 +28,29 @@ module.exports = function(RED) {
28
28
  eventEmitter = flowContext.get('emitter');
29
29
  }
30
30
 
31
- node.on("close", async () => {
32
- client.dispose();
33
- client = null;
34
- });
35
-
36
- client.userTasks.onUserTaskFinished((userTaskFinishedNotification) => {
37
- node.send({ payload: { flowNodeInstanceId: userTaskFinishedNotification.flowNodeInstanceId, action: "finished", type: "usertask" } });
38
- });
31
+ const register = async () => {
32
+ let currentIdentity = node.server.identity;
33
+ let subscription = await client.userTasks.onUserTaskFinished((userTaskFinishedNotification) => {
34
+ node.send({ payload: { flowNodeInstanceId: userTaskFinishedNotification.flowNodeInstanceId, action: "finished", type: "usertask" } });
35
+ }, { identity: currentIdentity });
36
+
37
+ node.server.registerOnIdentityChanged(async (identity) => {
38
+ client.userTasks.removeSubscription(subscription, currentIdentity);
39
+ currentIdentity = identity;
40
+
41
+ subscription = await client.userTasks.onUserTaskFinished((userTaskFinishedNotification) => {
42
+ node.send({ payload: { flowNodeInstanceId: userTaskFinishedNotification.flowNodeInstanceId, action: "finished", type: "usertask" } });
43
+ }, { identity: currentIdentity });
44
+ });
45
+
46
+ node.on("close", async () => {
47
+ client.userTasks.removeSubscription(subscription, currentIdentity);
48
+ client.dispose();
49
+ client = null;
50
+ });
51
+ }
52
+
53
+ register();
39
54
  }
40
55
  RED.nodes.registerType("usertask-finished-listener", UserTaskFinishedListener);
41
56
  }
package/usertask-input.js CHANGED
@@ -43,8 +43,11 @@ module.exports = function(RED) {
43
43
  });
44
44
 
45
45
  node.on('input', function(msg) {
46
- const query = RED.util.evaluateNodeProperty(config.query, config.query_type, node, msg)
47
-
46
+ let query = RED.util.evaluateNodeProperty(config.query, config.query_type, node, msg)
47
+ query = {
48
+ ...query,
49
+ identity: node.server.identity
50
+ }
48
51
  client.userTasks.query(query).then((matchingFlowNodes) => {
49
52
 
50
53
  if (!config.force_send_array && matchingFlowNodes && matchingFlowNodes.userTasks && matchingFlowNodes.userTasks.length == 1) {
@@ -27,15 +27,31 @@ module.exports = function(RED) {
27
27
  flowContext.set('emitter', new EventEmitter());
28
28
  eventEmitter = flowContext.get('emitter');
29
29
  }
30
+
31
+ const register = async () => {
32
+ let currentIdentity = node.server.identity;
33
+ let subscription = await client.userTasks.onUserTaskWaiting((userTaskWaitingNotification) => {
34
+ node.send({ payload: { flowNodeInstanceId: userTaskWaitingNotification.flowNodeInstanceId, action: "new", type: "usertask" } });
35
+ }, { identity: currentIdentity });
36
+
37
+ node.server.registerOnIdentityChanged(async (identity) => {
38
+ client.userTasks.removeSubscription(subscription, currentIdentity);
39
+ currentIdentity = identity;
40
+
41
+ subscription = await client.userTasks.onUserTaskWaiting((userTaskWaitingNotification) => {
42
+ node.send({ payload: { flowNodeInstanceId: userTaskWaitingNotification.flowNodeInstanceId, action: "new", type: "usertask" } });
43
+ }, { identity: currentIdentity });
44
+ });
45
+
46
+ node.on("close", async () => {
47
+ client.userTasks.removeSubscription(subscription, currentIdentity);
48
+ client.dispose();
49
+ client = null;
50
+ });
51
+ }
52
+
53
+ register();
30
54
 
31
- node.on("close", async () => {
32
- client.dispose();
33
- client = null;
34
- });
35
-
36
- client.userTasks.onUserTaskWaiting((userTaskWaitingNotification) => {
37
- node.send({ payload: { flowNodeInstanceId: userTaskWaitingNotification.flowNodeInstanceIdaction, action: "new", type: "usertask" } });
38
- });
39
55
  }
40
56
  RED.nodes.registerType("usertask-new-listener", UserTaskNewListener);
41
57
  }
@@ -37,7 +37,7 @@ module.exports = function(RED) {
37
37
 
38
38
  const userTaskResult = RED.util.evaluateNodeProperty(config.result, config.result_type, node, msg);
39
39
 
40
- client.userTasks.finishUserTask(flowNodeInstanceId, userTaskResult).then(() => {
40
+ client.userTasks.finishUserTask(flowNodeInstanceId, userTaskResult, node.server.identity).then(() => {
41
41
 
42
42
  node.send(msg);
43
43
  }).catch(error => {