node-red-contrib-mobius-flow-base 3.2.0 → 3.4.0-beta.1

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.
@@ -48,8 +48,12 @@ class EngineApiCalls {
48
48
  callback(null, response.data);
49
49
  }
50
50
  catch (err) {
51
- console.log(err);
52
- callback(err);
51
+ if (err.response?.status === 404) {
52
+ callback(null, { ts: new Date().toISOString(), uri, data: [] });
53
+ }
54
+ else {
55
+ callback(err);
56
+ }
53
57
  }
54
58
  };
55
59
  this.loggedIn ? func() : this.messageQueue.queue.push(func);
@@ -228,11 +232,27 @@ class EngineApiCalls {
228
232
  if (!this.apiClient)
229
233
  throw new Error('No API client set');
230
234
  const discoveries = [];
235
+ const discoverOnPid = (pid) => {
236
+ return (async () => {
237
+ try {
238
+ const response = await this.apiClient({
239
+ method: 'get',
240
+ url: `/mobius/discover/${uri}/${pid}`,
241
+ });
242
+ return response;
243
+ }
244
+ catch (err) {
245
+ if (err.response?.status === 404) {
246
+ return { data: { data: [], uri: uri } };
247
+ }
248
+ else {
249
+ throw err;
250
+ }
251
+ }
252
+ })();
253
+ };
231
254
  for (let pid of pids) {
232
- discoveries.push(this.apiClient({
233
- method: 'get',
234
- url: `/mobius/discover/${uri}/${pid}`,
235
- }));
255
+ discoveries.push(discoverOnPid(pid));
236
256
  }
237
257
  const pidGroups = (await Promise.all(discoveries)).map((response) => ({
238
258
  uri: response.data.uri,
@@ -1,6 +1,6 @@
1
- import { AxiosInstance } from 'axios';
2
- import { EngineWebSocket } from './engine-websocket';
3
- import { EngineApiCalls } from './engine-api-calls';
1
+ import { AxiosInstance } from "axios";
2
+ import { EngineWebSocket } from "./engine-websocket";
3
+ import { EngineApiCalls } from "./engine-api-calls";
4
4
  export declare class EngineApiClient extends EngineApiCalls {
5
5
  private reloginInterval;
6
6
  client: AxiosInstance | null;
@@ -14,7 +14,7 @@ export declare class EngineApiClient extends EngineApiCalls {
14
14
  EngineWebSocket: EngineWebSocket;
15
15
  private hostname;
16
16
  private protocol;
17
- constructor(protocol: 'http' | 'https', host: string, username: string, password: string);
17
+ constructor(protocol: "http" | "https", host: string, username: string, password: string);
18
18
  private login;
19
19
  private refresh;
20
20
  close: () => void;
@@ -24,11 +24,11 @@ class EngineApiClient extends engine_api_calls_1.EngineApiCalls {
24
24
  this.password = password;
25
25
  this.hostname = host;
26
26
  this.protocol = protocol;
27
- const baseURL = `${this.protocol}://${this.hostname}/api/v1`;
27
+ const baseURL = `${this.protocol}://${this.hostname}/api/engine/v1`;
28
28
  this.client = axios_1.default.create({
29
29
  baseURL: baseURL,
30
30
  headers: {
31
- 'Content-Type': 'application/json',
31
+ "Content-Type": "application/json",
32
32
  },
33
33
  });
34
34
  this.setApiClient(this.client);
@@ -41,15 +41,15 @@ class EngineApiClient extends engine_api_calls_1.EngineApiCalls {
41
41
  this.login();
42
42
  }
43
43
  else {
44
- console.log('No username, password, host, or port provided. Not attempting to login to Mobius Engine API.');
44
+ console.log("No username, password, host, or port provided. Not attempting to login to Mobius Engine API.");
45
45
  this.reloginInterval && clearInterval(this.reloginInterval);
46
46
  this.reloginInterval = null;
47
47
  }
48
48
  // Add auth headers to all axios calls
49
49
  this.client.interceptors.request.use((config) => {
50
- if (!config.url?.endsWith('/auth/login')) {
50
+ if (!config.url?.endsWith("/auth/login")) {
51
51
  if (this.tokens && config.headers) {
52
- config.headers['Authorization'] = `Bearer ${this.tokens.token}`;
52
+ config.headers["Authorization"] = `Bearer ${this.tokens.token}`;
53
53
  }
54
54
  }
55
55
  return config;
@@ -62,7 +62,7 @@ class EngineApiClient extends engine_api_calls_1.EngineApiCalls {
62
62
  throw error;
63
63
  });
64
64
  this.EngineWebSocket = new engine_websocket_1.EngineWebSocket(this.hostname, this.protocol);
65
- this.EngineWebSocket.on('disconnected', () => {
65
+ this.EngineWebSocket.on("disconnected", () => {
66
66
  this.loggedIn = false;
67
67
  });
68
68
  }
@@ -70,8 +70,8 @@ class EngineApiClient extends engine_api_calls_1.EngineApiCalls {
70
70
  console.log(`(${this.hostname}) -> Attempting Mobius Engine API Login`);
71
71
  try {
72
72
  const response = await this.client({
73
- method: 'post',
74
- url: '/auth/login',
73
+ method: "post",
74
+ url: "/auth/login",
75
75
  data: {
76
76
  username: this.username,
77
77
  password: this.password,
@@ -97,8 +97,8 @@ class EngineApiClient extends engine_api_calls_1.EngineApiCalls {
97
97
  }
98
98
  try {
99
99
  const response = await this.client({
100
- method: 'post',
101
- url: '/auth/refresh',
100
+ method: "post",
101
+ url: "/auth/refresh",
102
102
  data: {
103
103
  refreshToken: this.tokens.refreshToken,
104
104
  },
@@ -574,9 +574,13 @@ function getMobsearch(mobiusSrvc, level, listed, opts) {
574
574
  .each(function (i) {
575
575
  let item = $(this);
576
576
  let secondField = item.find(`.${opts.containerSecondFieldName}`).val();
577
- tempList[i][Object.keys(tempList[i])[1]] = JSON.parse(
578
- JSON.stringify(secondField)
579
- );
577
+ if (secondField) {
578
+ tempList[i][Object.keys(tempList[i])[1]] = JSON.parse(
579
+ JSON.stringify(secondField)
580
+ );
581
+ } else {
582
+ tempList[i][Object.keys(tempList[i])[1]] = '';
583
+ }
580
584
  });
581
585
  $(`#${opts.containerName}`).editableList('empty');
582
586
  for (let i = 0; i < tempList.length; i++) {
@@ -35,6 +35,11 @@ module.exports = function (RED) {
35
35
  this.priority = n.priority;
36
36
  this.serviceConn = RED.nodes.getNode(n.connection);
37
37
  var node = this;
38
+
39
+ const uriValidTest = new RegExp(
40
+ `^[0-9A-Fa-f]{6}/[0-9A-Fa-f]{3}/[0-9A-Fa-f]{4}/[0-9A-Fa-f]{4}/[0-9A-Fa-f]{2}$`
41
+ );
42
+
38
43
  if (node.serviceConn) {
39
44
  node.status({
40
45
  fill: 'red',
@@ -43,83 +48,109 @@ module.exports = function (RED) {
43
48
  });
44
49
 
45
50
  node.on('input', function (msg) {
46
- let uri = node.resource;
47
51
  let pri = parseInt(node.priority);
52
+
53
+ let uri = '';
54
+ if (msg.hasOwnProperty('topic') && uriValidTest.test(msg.topic)) {
55
+ uri = msg.topic;
56
+ }
57
+ if (uriValidTest.test(node.resource)) {
58
+ uri = node.resource;
59
+ }
48
60
  if (
49
61
  msg.hasOwnProperty('topic') &&
50
- require('./validateURI')(msg.topic, 5)
62
+ uriValidTest.test(msg.topic) &&
63
+ uriValidTest.test(node.resource)
51
64
  ) {
52
- uri = msg.topic;
65
+ node.warn('URI defined in config and input topic. Using config URI.');
66
+ }
67
+ if (uri === '') {
68
+ node.status({
69
+ fill: 'yellow',
70
+ shape: 'dot',
71
+ text: 'Error: URI invalid',
72
+ });
73
+ node.error('Error: URI invalid');
74
+ return;
53
75
  }
76
+
54
77
  if (msg.hasOwnProperty('priority')) {
55
78
  pri = msg.priority;
56
79
  }
57
80
 
58
- if (uri) {
59
- node.status({
60
- fill: 'green',
61
- shape: 'ring',
62
- text: 'Busy',
63
- });
81
+ node.status({
82
+ fill: 'green',
83
+ shape: 'ring',
84
+ text: 'Busy',
85
+ });
64
86
 
65
- node.serviceConn.clearResourcePriority(uri, pri, (err, response) => {
66
- if (err) {
67
- node.status({
68
- fill: 'yellow',
69
- shape: 'dot',
70
- text: `Error: ${err.code ? err.code : 'Unknown'}`,
71
- });
72
- node.error(
73
- `Mobius error clearing resource priority: ${
74
- err.code ? err.code : 'Unknown'
75
- }, ${err.message ? err.message : ''}`
76
- );
77
- if (node.detailedOutput === true) {
78
- node.send([
79
- null,
80
- null,
81
- null,
82
- {
83
- ...msg,
84
- topic: uri,
85
- payload: { error: true, response: err },
86
- },
87
- ]);
88
- }
87
+ node.serviceConn.clearResourcePriority(uri, pri, (err, response) => {
88
+ if (err) {
89
+ node.status({
90
+ fill: 'yellow',
91
+ shape: 'dot',
92
+ text: (() => {
93
+ if (err.response.status === 404) {
94
+ return `Error: Resource not found`;
95
+ } else {
96
+ return `Error: ${err.code ? err.code : 'Unknown'}`;
97
+ }
98
+ })(),
99
+ });
100
+ node.error(
101
+ (() => {
102
+ if (err.response.status === 404) {
103
+ return `Error: Resource not found`;
104
+ } else {
105
+ return `Error: ${err.code ? err.code : 'Unknown'}`;
106
+ }
107
+ })()
108
+ );
109
+ if (node.detailedOutput === true) {
110
+ node.send([
111
+ null,
112
+ null,
113
+ null,
114
+ {
115
+ ...msg,
116
+ topic: uri,
117
+ payload: { error: true, response: err },
118
+ },
119
+ ]);
120
+ }
121
+ } else {
122
+ node.status({
123
+ fill: 'green',
124
+ shape: 'dot',
125
+ text: JSON.stringify(response.data.pv),
126
+ });
127
+ if (node.detailedOutput === true) {
128
+ node.send([
129
+ { ...msg, topic: uri, payload: response.data.pv },
130
+ {
131
+ ...msg,
132
+ topic: uri,
133
+ payload: { pv: response.data.pv, name: response.data.name },
134
+ },
135
+ { ...msg, topic: uri, payload: response.data },
136
+ {
137
+ ...msg,
138
+ topic: uri,
139
+ payload: { error: false, response: response },
140
+ },
141
+ ]);
89
142
  } else {
90
- node.status({
91
- fill: 'green',
92
- shape: 'dot',
93
- text: JSON.stringify(response.data.pv),
94
- });
95
- if (node.detailedOutput === true) {
96
- node.send([
97
- { ...msg, topic: uri, payload: response.data.pv },
98
- {
99
- ...msg,
100
- topic: uri,
101
- payload: { pv: response.data.pv, name: response.data.name },
102
- },
103
- { ...msg, topic: uri, payload: response.data },
104
- {
105
- ...msg,
106
- topic: uri,
107
- payload: { error: false, response: response },
108
- },
109
- ]);
110
- } else {
111
- node.send([
112
- { ...msg, topic: uri, payload: response.data.pv },
113
- {
114
- ...msg,
115
- topic: uri,
116
- payload: { pv: response.data.pv, name: response.data.name },
117
- },
118
- ]);
119
- }
143
+ node.send([
144
+ { ...msg, topic: uri, payload: response.data.pv },
145
+ {
146
+ ...msg,
147
+ topic: uri,
148
+ payload: { pv: response.data.pv, name: response.data.name },
149
+ },
150
+ ]);
120
151
  }
121
- });
122
- }
152
+ }
153
+ });
123
154
  });
124
155
 
125
156
  node.serviceConn.register(node);
@@ -24,11 +24,22 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24
24
  POSSIBILITY OF SUCH DAMAGE.
25
25
  -->
26
26
 
27
- <script type="text/x-red" data-template-name="mobius discover">
27
+ <script
28
+ type="text/x-red"
29
+ data-template-name="mobius discover"
30
+ >
28
31
  <div class="form-row">
29
32
  <label for="node-input-connection"><i class="fa fa-globe"></i> MOBiUS</label>
30
33
  <input type="text" id="node-input-connection">
31
34
  </div>
35
+ <hr>
36
+ <div id="instance-row">
37
+ <div class="form-row" id="input-row">
38
+ <label for="node-input-uri"><i class="fa fa-tasks"></i> URI</label>
39
+ <input type="text" id="node-input-uri" style="width:61%;" placeholder="Discovery URI">
40
+ </div>
41
+ </div>
42
+
32
43
  <hr>
33
44
  <div class="form-row">
34
45
  <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
@@ -37,34 +48,47 @@ POSSIBILITY OF SUCH DAMAGE.
37
48
  </script>
38
49
 
39
50
  <script type="text/javascript">
40
- RED.nodes.registerType('mobius discover',{
51
+ RED.nodes.registerType('mobius discover', {
41
52
  category: 'mobius',
42
- color: "#8abff5",
53
+ color: '#8abff5',
43
54
  defaults: {
44
- name: {value: ""},
45
- connection: {type: "mobius connection", required: true}
55
+ name: { value: '' },
56
+ uri: {
57
+ value: '',
58
+ required: false,
59
+ validate: function (v) {
60
+ if (v === '') return true;
61
+ return new RegExp(
62
+ `^[0-9A-Fa-f]{6}(?:/[0-9A-Fa-f]{3}(?:/[0-9A-Fa-f]{4}(?:/[0-9A-Fa-f]{4})?)?)?$`
63
+ ).test(v);
64
+ },
65
+ },
66
+ connection: { type: 'mobius connection', required: true },
46
67
  },
47
- inputs:1,
48
- outputs:1,
49
- inputLabels: "Trigger discovery",
50
- outputLabels: ["Full MOBiUS response"],
51
- icon: "mobius.png",
52
- align: "left",
53
- paletteLabel: "discover",
54
- label: function() {
55
- if(this.name) {
68
+ inputs: 1,
69
+ outputs: 1,
70
+ inputLabels: 'Trigger discovery',
71
+ outputLabels: ['Full MOBiUS response'],
72
+ icon: 'mobius.png',
73
+ align: 'left',
74
+ paletteLabel: 'discover',
75
+ label: function () {
76
+ if (this.name) {
56
77
  return this.name;
57
78
  } else {
58
- return "discover";
79
+ return 'discover';
59
80
  }
60
81
  },
61
- labelStyle: function() {
62
- return this.name ? "node_label_italic":"";
63
- }
82
+ labelStyle: function () {
83
+ return this.name ? 'node_label_italic' : '';
84
+ },
64
85
  });
65
86
  </script>
66
87
 
67
- <script type="text/x-red" data-help-name="mobius discover">
88
+ <script
89
+ type="text/x-red"
90
+ data-help-name="mobius discover"
91
+ >
68
92
  <p>Discover MOBiUS services, object profiles, and objects</p>
69
93
 
70
94
  <h3>Configuration Page</h3>
@@ -31,8 +31,13 @@ module.exports = function (RED) {
31
31
  function MobiusDiscoverNode(n) {
32
32
  RED.nodes.createNode(this, n);
33
33
  this.serviceConn = RED.nodes.getNode(n.connection);
34
+ this.configUri = n.uri;
34
35
  var node = this;
35
36
 
37
+ const uriValidTest = new RegExp(
38
+ `^[0-9A-Fa-f]{6}(?:/[0-9A-Fa-f]{3}(?:/[0-9A-Fa-f]{4}(?:/[0-9A-Fa-f]{4})?)?)?$`
39
+ );
40
+
36
41
  if (node.serviceConn) {
37
42
  node.status({
38
43
  fill: 'red',
@@ -45,6 +50,9 @@ module.exports = function (RED) {
45
50
  if (msg.hasOwnProperty('topic')) {
46
51
  path = msg.topic;
47
52
  }
53
+ if (uriValidTest.test(node.configUri)) {
54
+ path = node.configUri;
55
+ }
48
56
 
49
57
  if (path === '') {
50
58
  node.status({
@@ -41,6 +41,8 @@ module.exports = function (RED) {
41
41
  this.serviceConn = RED.nodes.getNode(n.connection);
42
42
  var node = this;
43
43
 
44
+ const uriValidTest = new RegExp(`^[0-9A-Fa-f]{6}/[0-9A-Fa-f]{3}$`);
45
+
44
46
  if (node.serviceConn) {
45
47
  node.status({
46
48
  fill: 'red',
@@ -49,16 +51,45 @@ module.exports = function (RED) {
49
51
  });
50
52
 
51
53
  node.on('input', function (msg) {
52
- let uri = node.service;
54
+ // check pids of is array of 4char hex strings
55
+ if (!checkPidsValid(node.pids)) {
56
+ node.status({
57
+ fill: 'yellow',
58
+ shape: 'dot',
59
+ text: 'Error: PIDs invalid',
60
+ });
61
+ node.error('Error: PIDs list invalid');
62
+ return;
63
+ }
64
+
53
65
  let pids = JSON.parse(node.pids);
54
66
  let rid = node.rid;
55
67
  let limit = node.limit;
56
68
  let func = node.func;
69
+ let uri = '';
70
+ if (msg.hasOwnProperty('topic') && uriValidTest.test(msg.topic)) {
71
+ uri = msg.topic;
72
+ }
73
+ if (uriValidTest.test(node.service)) {
74
+ uri = node.service;
75
+ }
57
76
  if (
58
77
  msg.hasOwnProperty('topic') &&
59
- require('./validateURI')(msg.topic, 2)
78
+ uriValidTest.test(msg.topic) &&
79
+ uriValidTest.test(node.service)
60
80
  ) {
61
- uri = msg.topic;
81
+ node.warn(
82
+ 'Service URI defined in config and input topic. Using config Service URI.'
83
+ );
84
+ }
85
+ if (uri === '') {
86
+ node.status({
87
+ fill: 'yellow',
88
+ shape: 'dot',
89
+ text: 'Error: URI invalid',
90
+ });
91
+ node.error('Error: URI invalid');
92
+ return;
62
93
  }
63
94
  if (msg.hasOwnProperty('payload')) {
64
95
  if (msg.payload.hasOwnProperty('pids')) {
@@ -144,3 +175,20 @@ module.exports = function (RED) {
144
175
 
145
176
  RED.nodes.registerType('mobius filter by rid value', MobiusFilterNode);
146
177
  };
178
+
179
+ const checkPidsValid = (pids) => {
180
+ try {
181
+ pids = JSON.parse(pids);
182
+ } catch {
183
+ return false;
184
+ }
185
+ if (!Array.isArray(pids) || pids.length === 0) {
186
+ return false;
187
+ }
188
+ for (let i = 0; i < pids.length; i++) {
189
+ if (!/^[0-9A-Fa-f]{4}$/.test(pids[i])) {
190
+ return false;
191
+ }
192
+ }
193
+ return true;
194
+ };
@@ -30,24 +30,46 @@
30
30
  const _ = require('lodash');
31
31
 
32
32
  module.exports = function (RED) {
33
- function MobiusFlowNode (n) {
33
+ function MobiusFlowNode(n) {
34
34
  RED.nodes.createNode(this, n);
35
35
  this.subscriptions = n.subscriptions;
36
36
  this.serviceConn = RED.nodes.getNode(n.connection);
37
37
  var node = this;
38
38
 
39
+ const uriValidTest = new RegExp(
40
+ `^([0-9A-Fa-f]{6}|\\+)(/([0-9A-Fa-f]{3}|\\+))?(/([0-9A-Fa-f]{4}|\\+))?(/([0-9A-Fa-f]{4}|\\+)|/\\#)?$`
41
+ );
42
+
39
43
  if (node.serviceConn) {
40
44
  node.status({
41
45
  fill: 'red',
42
46
  shape: 'ring',
43
- text: 'Disconnected'
47
+ text: 'Disconnected',
44
48
  });
45
49
 
46
50
  for (const subscription of node.subscriptions) {
47
- if (subscription.type === 'include' && validateSubscription(subscription.uri)) {
48
- node.serviceConn.subscribeToObjectCOV(subscription.uri, this.id, (response) => {
49
- handleSubscription(node, response);
51
+ if (!uriValidTest.test(subscription.uri)) {
52
+ node.status({
53
+ fill: 'yellow',
54
+ shape: 'dot',
55
+ text: 'Error: URIs not Valid',
50
56
  });
57
+ return;
58
+ }
59
+ }
60
+
61
+ for (const subscription of node.subscriptions) {
62
+ if (
63
+ subscription.type === 'include' &&
64
+ validateSubscription(subscription.uri)
65
+ ) {
66
+ node.serviceConn.subscribeToObjectCOV(
67
+ subscription.uri,
68
+ this.id,
69
+ (response) => {
70
+ handleSubscription(node, response);
71
+ }
72
+ );
51
73
  }
52
74
  }
53
75
 
@@ -61,7 +83,7 @@ module.exports = function (RED) {
61
83
  }
62
84
  }
63
85
 
64
- function validateSubscription (uri) {
86
+ function validateSubscription(uri) {
65
87
  let parts = uri.split('/');
66
88
  for (let lp = 0; lp < parts.length; lp++) {
67
89
  if (parts[lp] === null || parts[lp] === '') {
@@ -83,18 +105,24 @@ module.exports = function (RED) {
83
105
  * @param {string} messageTopic - The topic in an actual message
84
106
  * @return {boolean} - True if the topics match, else false
85
107
  */
86
- function matchTopic (topic, messageTopic) {
108
+ function matchTopic(topic, messageTopic) {
87
109
  if (topic === '#') {
88
110
  return true;
89
111
  }
90
112
  // eslint-disable-next-line no-useless-escape
91
- var regex = new RegExp('^' + topic.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g, '\\$1')
92
- // eslint-disable-next-line no-useless-escape
93
- .replace(/\+/g, '[^/]+').replace(/\/#$/, '(\/.*)?') + '$');
113
+ var regex = new RegExp(
114
+ '^' +
115
+ topic
116
+ .replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g, '\\$1')
117
+ // eslint-disable-next-line no-useless-escape
118
+ .replace(/\+/g, '[^/]+')
119
+ .replace(/\/#$/, '(/.*)?') +
120
+ '$'
121
+ );
94
122
  return regex.test(messageTopic);
95
123
  }
96
124
 
97
- function handleSubscription (node, response) {
125
+ function handleSubscription(node, response) {
98
126
  for (const subscription of node.subscriptions) {
99
127
  if (subscription.type === 'exclude') {
100
128
  if (matchTopic(subscription.uri, response.uri)) {
@@ -106,7 +134,7 @@ module.exports = function (RED) {
106
134
  node.status({
107
135
  fill: 'green',
108
136
  shape: 'dot',
109
- text: JSON.stringify(response.uri)
137
+ text: JSON.stringify(response.uri),
110
138
  });
111
139
  let outObject = response.value;
112
140
  try {