node-red-contrib-teamogy-api 0.0.2 → 0.0.6

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/teamogy-client.js CHANGED
@@ -1,7 +1,75 @@
1
1
  function isEmpty(value) { return (value == null || (typeof value === "string" && value.trim().length === 0)); }
2
2
 
3
3
  module.exports = function(RED) {
4
-
4
+
5
+ if (typeof globalNextAvailableSlot === 'undefined') {
6
+ var globalNextAvailableSlot = Date.now();
7
+ }
8
+
9
+ async function fetchWithRetry(url, options, retries, delay, node, limit) {
10
+
11
+ const minInterval = 60000 / limit;
12
+
13
+ for (let i = 0; i <= retries; i++) {
14
+ try {
15
+ let now = Date.now();
16
+ let waitTime = 0;
17
+
18
+ let scheduledTime = Math.max(now, globalNextAvailableSlot);
19
+ globalNextAvailableSlot = scheduledTime + minInterval;
20
+
21
+ waitTime = scheduledTime - now;
22
+
23
+ if (waitTime > 0) {
24
+ await new Promise(resolve => setTimeout(resolve, waitTime));
25
+ }
26
+
27
+ if (node.closingId == node.id) {
28
+ return { status: 0 };
29
+ }
30
+
31
+ const response = await fetch(url, options);
32
+
33
+ if (response.status >= 200 && response.status < 300) {
34
+ return response;
35
+ }
36
+
37
+ let o = {
38
+ status: response.status,
39
+ message: await response.text() ?? '',
40
+ attempt: i + 1,
41
+ delay: delay / 1000
42
+ };
43
+ node.warn(o);
44
+
45
+ await new Promise(resolve => setTimeout(resolve, delay));
46
+
47
+ } catch (error) {
48
+ let o = {
49
+ status: 0,
50
+ message: 'Request failed: ' + error.message,
51
+ attempt: i + 1,
52
+ delay: delay / 1000
53
+ };
54
+ node.warn(o);
55
+ await new Promise(resolve => setTimeout(resolve, delay));
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+
61
+ function getConfigId(name) {
62
+ var configNodes = [];
63
+ RED.nodes.eachNode(function(node) {
64
+ if (node.type === "teamogy-config") {
65
+ configNodes.push(node);
66
+ }
67
+ });
68
+
69
+ const configId = configNodes.find(item => item.name === name)?.id;
70
+ return configId;
71
+ }
72
+
5
73
  function ConnectionNode(n) {
6
74
  try {
7
75
  RED.nodes.createNode(this, n);
@@ -19,12 +87,16 @@ module.exports = function(RED) {
19
87
  let cache = []
20
88
  this.context().global.set('cache_' + this.host, cache)
21
89
  }
90
+
91
+ if(typeof this.context().global.get('count_' + this.host ) == 'undefined') {
92
+ this.context().global.set('count_' + this.host, 0)
93
+ }
22
94
 
23
95
  } catch (e) {
24
- node.error(e);
96
+ this.error(e.message);
25
97
  }
26
98
  }
27
-
99
+
28
100
  RED.nodes.registerType('teamogy-config', ConnectionNode, {
29
101
  credentials: {
30
102
  token: {type: 'password'}
@@ -34,7 +106,9 @@ module.exports = function(RED) {
34
106
  function teamogyClient(data) {
35
107
  try {
36
108
  var node = this;
37
-
109
+
110
+ node.closingId = '';
111
+
38
112
  RED.nodes.createNode(node,data);
39
113
 
40
114
  this.config = RED.nodes.getNode(data.configuration);
@@ -42,9 +116,12 @@ module.exports = function(RED) {
42
116
  let token = this.config.credentials.token
43
117
  let host = this.config.host.replace(/^https?:\/\//, '').split('/')[0];
44
118
  let unit = this.config.unit
119
+ let connName = this.config.name
120
+ let apiLimit = this.config.apilimit
45
121
 
46
122
  let clientid = data.id
47
123
  let c = this.context().global.get('cache_' + host)
124
+ let ct = this.context().global.get('count_' + host)
48
125
  let st = null;
49
126
 
50
127
  async function sendmsg(mesg) {
@@ -56,7 +133,12 @@ module.exports = function(RED) {
56
133
  let mlimit = 0
57
134
  let mpaging = 1000
58
135
  let moffset = 0
59
-
136
+ let entity = ''
137
+ let method = 'GET'
138
+ let delay = 0;
139
+ let repeat = 5;
140
+ let rdelay = 30;
141
+
60
142
  if(data.source=='dynamic') {
61
143
  if(typeof msg.params == 'string') { mparams = msg.params }
62
144
  if(typeof msg.params == 'object') {
@@ -74,11 +156,30 @@ module.exports = function(RED) {
74
156
  if(typeof msg[data.bodysource] == 'string') { body = msg[data.bodysource] }
75
157
  if(typeof msg[data.bodysource] == 'object') { body = JSON.stringify(msg[data.bodysource]) }
76
158
 
77
- if(typeof msg.merge == 'boolean') { mmerge = msg.merge }
78
- if(typeof msg.limit == 'number') { mlimit = msg.limit }
79
- if(typeof msg.paging == 'number') { mpaging = msg.paging }
80
- if(typeof msg.offset == 'number') { moffset = msg.offset }
159
+ if(typeof msg.merge == 'boolean') { mmerge = msg.merge }
160
+ if(typeof msg.limit == 'number') { mlimit = msg.limit }
161
+ if(typeof msg.paging == 'number') { mpaging = msg.paging }
162
+ if(typeof msg.offset == 'number') { moffset = msg.offset }
163
+ if(typeof msg.unit == 'number') { unit = msg.unit }
164
+ if(typeof msg.entity == 'string') { entity = msg.entity } else { entity = data.entity }
165
+ if(typeof msg.method == 'string') { method = msg.method } else { method = data.method }
166
+ if(typeof msg.delay == 'number') { delay = msg.delay * 1000 } else { delay = data.delay * 1000 }
167
+ if(typeof msg.repeat == 'number') { repeat = msg.repeat ?? 5 } else { repeat = data.repeat ?? 5 }
168
+ if(typeof msg.rdelay == 'number') { rdelay = msg.rdelay * 1000 } else { rdelay = data.rdelay * 1000 }
81
169
 
170
+ if(typeof msg.connection == 'string') {
171
+ if(msg.connection) {
172
+ const customConfig = RED.nodes.getNode(getConfigId(msg.connection));
173
+ if(customConfig) {
174
+ token = customConfig.credentials.token;
175
+ host = customConfig.host.replace(/^https?:\/\//, '').split('/')[0];
176
+ unit = customConfig.unit;
177
+ } else {
178
+ node.warn("Specified connection not found: " + msg.connection + ", using default: " + connName);
179
+ }
180
+ }
181
+ }
182
+
82
183
  }
83
184
 
84
185
  if(data.source=='static') {
@@ -95,50 +196,54 @@ module.exports = function(RED) {
95
196
  mlimit = data.limit
96
197
  mpaging = data.paging
97
198
  moffset = data.offset
199
+ entity = data.entity
200
+ method = data.method
201
+ delay = data.delay * 1000
202
+ repeat = data.repeat ?? 5
203
+ rdelay = data.rdelay * 1000
98
204
  }
99
-
205
+
100
206
  const headers = {
101
207
  'Authorization': 'Bearer ' + token,
102
208
  'Accept': 'application/json',
103
209
  'Content-type': 'application/json'
104
210
  };
105
-
211
+
106
212
  let url = `https://${host}/rest/v1/${unit}/`
107
213
 
108
- if(data.entity.split('_')[0] == 'v') { url = url + 'views/'}
214
+ if(entity.split('_')[0] == 'v') { url = url + 'views/'}
109
215
 
110
- url = url + data.entity.split('_')[1].replaceAll('-','.')
216
+ url = url + entity.substring(entity.indexOf('_') + 1).replaceAll('-','.')
111
217
 
112
218
  if(!isEmpty(mparams)) { url = url + '?' + mparams }
113
-
219
+
114
220
  const doAsyncJobs = async () => {
115
221
  try {
116
222
 
117
- let newMsg = JSON.parse(JSON.stringify(mesg.msg));
118
-
119
223
  let metadata = {};
120
224
  metadata.count = 0
121
225
  metadata.limit = 0
122
226
  let rdata = [];
123
227
 
124
- let method = data.method
125
228
  let offset = moffset
126
229
 
127
230
  if(method == 'GET') { body = null }
128
231
 
129
- if(data.entity.split('_')[0] == 'v') {
232
+ if(entity.split('_')[0] == 'v') {
130
233
  if(isEmpty(mparams)) { url = url + '?' } else { url = url + '&' }
131
234
 
132
- while (offset != null) {
235
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
236
+
237
+ while (offset != null && node.closingId != clientid) {
133
238
 
134
239
  if(parseInt(mlimit) == 0) { mlimit = 1000000000}
135
240
  if(parseInt(mlimit) < parseInt(mpaging)) { mpaging = mlimit }
136
241
 
137
242
  eurl = encodeURI(url + 'limit=' + mpaging +'&offset=' + offset)
138
243
 
139
- const response = await fetch(eurl, {headers, method, body});
244
+ const response = await fetchWithRetry(eurl, { headers, method, body }, repeat, rdelay, node, apiLimit);
140
245
 
141
- if(response.status >= 200 && response.status < 300) {
246
+ if(response && response.status >= 200 && response.status < 300) {
142
247
 
143
248
  const body = await response.json();
144
249
  offset = body?.metadata?.nextOffset
@@ -149,9 +254,9 @@ module.exports = function(RED) {
149
254
  if(mlimit - metadata.count < mpaging) { mpaging = mlimit - metadata.count}
150
255
 
151
256
  if(mmerge == false) {
152
- newMsg.payload=body;
153
- newMsg.payload.count=body.data.length;
154
- node.send(JSON.parse(JSON.stringify(newMsg)));
257
+ msg.payload = body;
258
+ msg.count = body.data.length;
259
+ node.send(msg);
155
260
  }
156
261
 
157
262
  if(mmerge == true) {
@@ -161,10 +266,22 @@ module.exports = function(RED) {
161
266
  }
162
267
 
163
268
  if(mlimit <= metadata.count) { break; }
164
-
269
+
270
+ if (offset != null && delay > 0) { await sleep(delay); }
271
+
165
272
  } else {
166
- node.error('Response status: ' + response.status);
167
- node.error('Response status: ' + await response.text());
273
+ if (!response) {
274
+ msg.error = 'Request failed after all attempts.'
275
+ msg.payload = null;
276
+ if(msg.res){
277
+ msg.payload = JSON.stringify('Request failed after all attempts.');
278
+ msg.statusCode = 500;
279
+ }
280
+ node.send(msg);
281
+ } else if(response.status != 0) {
282
+ node.error('Response status: ' + response.status);
283
+ node.error('Response text: ' + await response.text());
284
+ }
168
285
  break;
169
286
  }
170
287
  }
@@ -174,38 +291,44 @@ module.exports = function(RED) {
174
291
  metadata.limit = parseInt(data.paging)
175
292
  body.metadata = metadata
176
293
  body.data = rdata
177
- body.count = rdata.length
178
- newMsg.payload = body
179
- node.send(JSON.parse(JSON.stringify(newMsg)));
294
+ msg.payload = body
295
+ msg.count = rdata.length
296
+ node.send(msg);
180
297
  }
181
298
  }
182
299
 
183
- if(data.entity.split('_')[0] == 'r') {
300
+ if(entity.split('_')[0] == 'r') {
184
301
 
185
- const response = await fetch(encodeURI(url), {headers, method, body});
302
+ const response = await fetchWithRetry(encodeURI(url), { headers, method, body }, repeat, rdelay, node, apiLimit);
186
303
 
187
- if(response.status >= 200 && response.status < 300) {
304
+ if(response && response.status >= 200 && response.status < 300) {
188
305
  const body = await response.json();
189
- newMsg.payload = body
190
- node.send(JSON.parse(JSON.stringify(newMsg)));
306
+ msg.payload = body
307
+ node.send(msg);
191
308
  } else {
192
- let payload = {}
193
- payload.status = response.status
194
- payload.text = await response.text()
195
-
196
- newMsg.payload = payload
197
- node.send(JSON.parse(JSON.stringify(newMsg)));
309
+ if (!response) {
310
+ msg.error = 'Request failed after all attempts.'
311
+ msg.payload = null;
312
+ if(msg.res){
313
+ msg.payload = JSON.stringify('Request failed after all attempts.');
314
+ msg.statusCode = 500;
315
+ }
316
+ node.send(msg);
317
+ } else if(response.status != 0) {
318
+ node.error('Response status: ' + response.status);
319
+ node.error('Response text: ' + await response.text());
320
+ }
198
321
  }
199
322
  }
200
323
  } catch (e) {
201
- node.error(e);
324
+ node.error(e.message);
202
325
  }
203
326
  }
204
327
 
205
- const r = await doAsyncJobs()
328
+ await doAsyncJobs();
206
329
 
207
330
  } catch (e) {
208
- node.error(e);
331
+ node.error(e.message);
209
332
  }
210
333
  }
211
334
 
@@ -216,12 +339,20 @@ module.exports = function(RED) {
216
339
  });
217
340
  return na.length
218
341
  } catch (e) {
219
- node.error(e);
342
+ node.error(e.message);
220
343
  }
221
344
  }
222
345
 
223
346
  async function setTimer(host) {
224
347
  try {
348
+ if(node.closingId == clientid) {
349
+ for (let i = c.length - 1; i >= 0; i--) {
350
+ if (c[i].clientid === clientid) {
351
+ c.splice(i, 1);
352
+ }
353
+ }
354
+ }
355
+
225
356
  if(c.length > 0) {
226
357
  let nd = new Date()
227
358
  if(nd.getTime() > c[0].stime) {
@@ -239,16 +370,17 @@ module.exports = function(RED) {
239
370
  node.status({fill: "yellow", shape: "dot", text: fal + " waiting messages"});
240
371
  }
241
372
  } else {
373
+ node.status({fill: "green", shape: "dot", text: "0 waiting messages"});
242
374
  clearInterval(st)
243
375
  st = null
244
376
  }
245
377
  }
246
378
  catch (e) {
247
- node.error(e);
379
+ node.error(e.message);
248
380
  }
249
381
  }
250
382
 
251
- node.on('input', async function(msg) {
383
+ node.on('input', async function(msg, send, done) {
252
384
  try {
253
385
  if(st == null){ st = setInterval(function () { setTimer(host) }, 1000)}
254
386
 
@@ -273,16 +405,27 @@ module.exports = function(RED) {
273
405
  } else {
274
406
  node.send(msg)
275
407
  }
408
+ if (done) {
409
+ done();
410
+ }
276
411
  }
277
412
  catch (e) {
278
- node.error(e);
413
+ node.error(e.message);
279
414
  }
280
415
  });
281
416
 
282
417
  } catch (e) {
283
- node.error(e);
418
+ node.error(e.message);
284
419
  }
420
+
421
+ node.on('close', function(done) {
422
+ node.closingId = this.id;
423
+
424
+ if (done) {
425
+ done();
426
+ }
427
+ });
285
428
  }
286
429
 
287
430
  RED.nodes.registerType("teamogy-client",teamogyClient);
288
- }
431
+ }