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/README.md +10 -2
- package/examples/examples.json +88 -0
- package/package.json +5 -4
- package/teamogy-client.html +815 -539
- package/teamogy-client.js +193 -50
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
|
-
|
|
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(
|
|
214
|
+
if(entity.split('_')[0] == 'v') { url = url + 'views/'}
|
|
109
215
|
|
|
110
|
-
url = url +
|
|
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(
|
|
232
|
+
if(entity.split('_')[0] == 'v') {
|
|
130
233
|
if(isEmpty(mparams)) { url = url + '?' } else { url = url + '&' }
|
|
131
234
|
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
node.send(
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
node.send(
|
|
294
|
+
msg.payload = body
|
|
295
|
+
msg.count = rdata.length
|
|
296
|
+
node.send(msg);
|
|
180
297
|
}
|
|
181
298
|
}
|
|
182
299
|
|
|
183
|
-
if(
|
|
300
|
+
if(entity.split('_')[0] == 'r') {
|
|
184
301
|
|
|
185
|
-
|
|
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
|
-
|
|
190
|
-
node.send(
|
|
306
|
+
msg.payload = body
|
|
307
|
+
node.send(msg);
|
|
191
308
|
} else {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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
|
+
}
|