@jetit/publisher 5.2.2 → 5.3.2
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/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jetit/publisher",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.2",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@jetit/id": "^0.0.12",
|
|
7
|
-
"events": "3.3.0",
|
|
8
7
|
"ioredis": "^5.3.0",
|
|
9
8
|
"rxjs": "^7.8.0",
|
|
10
|
-
"tslib": "2.
|
|
9
|
+
"tslib": "2.7.0"
|
|
11
10
|
},
|
|
11
|
+
"types": "./src/index.d.ts",
|
|
12
12
|
"main": "./src/index.js"
|
|
13
|
-
}
|
|
13
|
+
}
|
|
@@ -111,33 +111,26 @@ class MetricsCollector {
|
|
|
111
111
|
const individualDepths = {};
|
|
112
112
|
let cursor = '0';
|
|
113
113
|
do {
|
|
114
|
-
const [nextCursor, keys] = await this.redisClient.scan(cursor, 'MATCH',
|
|
114
|
+
const [nextCursor, keys] = await this.redisClient.scan(cursor, 'MATCH', '*:cg-*', 'COUNT', 100);
|
|
115
115
|
cursor = nextCursor;
|
|
116
116
|
if (keys.length > 0) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const [ackCountErr, ackCount] = results[i + 1];
|
|
130
|
-
if (streamLengthErr) {
|
|
131
|
-
console.error(`Error getting length for key: ${streamLengthErr}`);
|
|
132
|
-
continue;
|
|
117
|
+
for (const streamKey of keys) {
|
|
118
|
+
try {
|
|
119
|
+
// Get stream length and pending info
|
|
120
|
+
const streamLength = await this.redisClient.xlen(streamKey);
|
|
121
|
+
// Extract consumer group name from stream key (format: eventName:cg-serviceName)
|
|
122
|
+
const consumerGroup = streamKey.split(':')[1];
|
|
123
|
+
// XPENDING returns [count, min-id, max-id, consumer-list]
|
|
124
|
+
const pendingInfo = await this.redisClient.xpending(streamKey, consumerGroup);
|
|
125
|
+
const totalPending = pendingInfo ? Number(pendingInfo[0]) : 0;
|
|
126
|
+
const queueDepth = Math.max(0, streamLength - totalPending);
|
|
127
|
+
totalDepth += queueDepth;
|
|
128
|
+
individualDepths[streamKey] = queueDepth;
|
|
133
129
|
}
|
|
134
|
-
|
|
135
|
-
console.error(`Error
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error(`Error processing key ${streamKey}:`, error);
|
|
136
132
|
continue;
|
|
137
133
|
}
|
|
138
|
-
const queueDepth = Math.max(0, streamLength - ackCount);
|
|
139
|
-
totalDepth += queueDepth;
|
|
140
|
-
individualDepths[key] = queueDepth;
|
|
141
134
|
}
|
|
142
135
|
}
|
|
143
136
|
} while (cursor !== '0');
|
package/src/lib/redis/batch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.publishBatch = publishBatch;
|
|
4
|
+
exports.publishScheduledBatch = publishScheduledBatch;
|
|
4
5
|
const id_1 = require("@jetit/id");
|
|
5
6
|
const tracker_1 = require("../monitoring/tracker");
|
|
6
7
|
const logger_1 = require("./logger");
|
|
@@ -72,8 +73,6 @@ async function publishBatchWithRetry(streams, events, options) {
|
|
|
72
73
|
function publishBatch(streams, events, options = {}) {
|
|
73
74
|
return publishBatchWithRetry(streams, events, options);
|
|
74
75
|
}
|
|
75
|
-
exports.publishBatch = publishBatch;
|
|
76
76
|
function publishScheduledBatch(streams, events, options) {
|
|
77
77
|
return publishBatchWithRetry(streams, events, options);
|
|
78
78
|
}
|
|
79
|
-
exports.publishScheduledBatch = publishScheduledBatch;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.RedisRegistry = void 0;
|
|
4
|
+
exports.setRedisConnectionSettings = setRedisConnectionSettings;
|
|
4
5
|
const ioredis_1 = require("ioredis");
|
|
5
6
|
const logger_1 = require("./logger");
|
|
6
7
|
class RedisRegistry {
|
|
@@ -103,4 +104,3 @@ RedisRegistry.options = {
|
|
|
103
104
|
function setRedisConnectionSettings(options) {
|
|
104
105
|
RedisRegistry.setOptions(options);
|
|
105
106
|
}
|
|
106
|
-
exports.setRedisConnectionSettings = setRedisConnectionSettings;
|
package/src/lib/redis/streams.js
CHANGED
|
@@ -257,60 +257,40 @@ class Streams {
|
|
|
257
257
|
const setKeyForK8sHandling = `instance:${this.instanceUniqueId}:consumerGroupName`;
|
|
258
258
|
this.eventsListened.push(eventName);
|
|
259
259
|
try {
|
|
260
|
-
//
|
|
261
|
-
|
|
260
|
+
// Try to create the consumer group and consumer in one go
|
|
261
|
+
// If group doesn't exist, this will create it with MKSTREAM
|
|
262
|
+
// If group exists but consumer doesn't, this will create just the consumer
|
|
263
|
+
// If both exist, this will be a no-op
|
|
262
264
|
try {
|
|
263
|
-
groupInfo = (await this.redisGroups.xinfo('GROUPS', streamName));
|
|
264
|
-
}
|
|
265
|
-
catch (e) {
|
|
266
|
-
// Do nothing
|
|
267
|
-
}
|
|
268
|
-
let groupExists = false;
|
|
269
|
-
for (const group of groupInfo) {
|
|
270
|
-
if (group[1] === this.consumerGroupName) {
|
|
271
|
-
groupExists = true;
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (!groupExists) {
|
|
276
265
|
await this.redisGroups.xgroup('CREATE', streamName, this.consumerGroupName, '0', 'MKSTREAM');
|
|
277
266
|
logger_1.PUBLISHER_LOGGER.log(`Group created for ${JSON.stringify({ streamName, cgn: this.consumerGroupName })}`);
|
|
278
267
|
}
|
|
279
|
-
|
|
268
|
+
catch (e) {
|
|
269
|
+
// BUSYGROUP error means group already exists, which is fine
|
|
270
|
+
if (!e.message.includes('BUSYGROUP')) {
|
|
271
|
+
throw e;
|
|
272
|
+
}
|
|
280
273
|
logger_1.PUBLISHER_LOGGER.log(`Group already exists for ${JSON.stringify({ streamName, cgn: this.consumerGroupName })}`);
|
|
281
274
|
}
|
|
275
|
+
// Create consumer (idempotent operation)
|
|
276
|
+
const createConsumerStatus = await this.redisGroups.xgroup('CREATECONSUMER', streamName, this.consumerGroupName, this.instanceId);
|
|
277
|
+
// Register event and consumer group in parallel
|
|
278
|
+
const [addToCGSet, addToFlushSet] = await Promise.all([
|
|
279
|
+
this.redisGroups.sadd(`${eventName}`, this.consumerGroupName),
|
|
280
|
+
this.redisGroups.set(setKeyForK8sHandling, this.consumerGroupName),
|
|
281
|
+
this.redisGroups.sadd(key, eventName),
|
|
282
|
+
]);
|
|
283
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Consumer Registered with ${this.instanceId} under ${this.consumerGroupName} with status ${JSON.stringify({
|
|
284
|
+
createConsumerStatus,
|
|
285
|
+
addToCGSet,
|
|
286
|
+
addToFlushSet,
|
|
287
|
+
})}`);
|
|
288
|
+
return true;
|
|
282
289
|
}
|
|
283
|
-
catch (
|
|
284
|
-
logger_1.PUBLISHER_LOGGER.error(`PUBLISHER:
|
|
285
|
-
|
|
286
|
-
// Check if the consumer already exists in the group
|
|
287
|
-
let consumers = [];
|
|
288
|
-
try {
|
|
289
|
-
consumers = (await this.redisGroups.xinfo('CONSUMERS', streamName, this.consumerGroupName));
|
|
290
|
-
}
|
|
291
|
-
catch (e) {
|
|
292
|
-
// Do nothing
|
|
293
|
-
}
|
|
294
|
-
let consumerExists = false;
|
|
295
|
-
for (const consumer of consumers) {
|
|
296
|
-
if (consumer[1] === this.instanceId) {
|
|
297
|
-
consumerExists = true;
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
let createConsumerStatus;
|
|
302
|
-
if (!consumerExists) {
|
|
303
|
-
createConsumerStatus = await this.redisGroups.xgroup('CREATECONSUMER', streamName, this.consumerGroupName, this.instanceId);
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
createConsumerStatus = 0; // Consumer already exists
|
|
307
|
-
logger_1.PUBLISHER_LOGGER.log(`Consumer already exists for ${JSON.stringify({ streamName, cgn: this.consumerGroupName, instanceId: this.instanceId })}`);
|
|
290
|
+
catch (error) {
|
|
291
|
+
logger_1.PUBLISHER_LOGGER.error(`PUBLISHER: Consumer registration failed for ${JSON.stringify({ streamName, cgn: this.consumerGroupName })}:`, error);
|
|
292
|
+
return false;
|
|
308
293
|
}
|
|
309
|
-
await this.redisGroups.sadd(key, eventName);
|
|
310
|
-
const addToCGSet = await this.redisGroups.sadd(`${eventName}`, this.consumerGroupName);
|
|
311
|
-
const addToFlushSet = await this.redisGroups.set(setKeyForK8sHandling, this.consumerGroupName);
|
|
312
|
-
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Consumer Registered and created with ${this.instanceId} under ${this.consumerGroupName} with ${createConsumerStatus} consumers and with the following status ${JSON.stringify({ addToCGSet, addToFlushSet })}`);
|
|
313
|
-
return createConsumerStatus === 0 || createConsumerStatus === 1;
|
|
314
294
|
}
|
|
315
295
|
listenInternals(eventName, subscriptionId, eventFilter, filterKeepAlive = 24 * 60 * 60 * 1000, publishOnceGuarantee = false, externalAcknowledgement = false) {
|
|
316
296
|
if (!this.subscriptions.has(eventName)) {
|
|
@@ -336,21 +316,22 @@ class Streams {
|
|
|
336
316
|
const processMessage = async (redisClient, messageId, tracker, multicast = false, processPending = false) => {
|
|
337
317
|
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Processing message ${messageId} for ${streamName}`);
|
|
338
318
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
319
|
+
// Skip XPENDING check for:
|
|
320
|
+
// 1. Multicast messages (meant for all consumers)
|
|
321
|
+
// 2. Initial message processing (messageId = '0')
|
|
322
|
+
// 3. Pending message reprocessing
|
|
323
|
+
if (!multicast && messageId !== '0' && !processPending) {
|
|
324
|
+
try {
|
|
325
|
+
const pendingDetails = await redisClient.xpending(streamName, this.consumerGroupName, messageId, messageId, 1);
|
|
326
|
+
if (pendingDetails[2] === 0) {
|
|
327
|
+
logger_1.PUBLISHER_LOGGER.warn(`PUBLISHER: Message ${messageId} already processed for ${streamName}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
// If XPENDING fails, continue with processing
|
|
333
|
+
logger_1.PUBLISHER_LOGGER.warn(`PUBLISHER: XPENDING check failed for ${messageId}, continuing with processing`);
|
|
348
334
|
}
|
|
349
|
-
}
|
|
350
|
-
catch (e) {
|
|
351
|
-
// Ignore the xpending error and continue
|
|
352
|
-
logger_1.PUBLISHER_LOGGER.error('XPENDING ERROR: To be handled');
|
|
353
|
-
logger_1.PUBLISHER_LOGGER.warn(JSON.stringify(e));
|
|
354
335
|
}
|
|
355
336
|
let eventData;
|
|
356
337
|
/**
|
|
@@ -416,7 +397,7 @@ class Streams {
|
|
|
416
397
|
}
|
|
417
398
|
}
|
|
418
399
|
if (!externalAcknowledgement)
|
|
419
|
-
this.acknowledgeMessage(ackKey);
|
|
400
|
+
await this.acknowledgeMessage(ackKey);
|
|
420
401
|
}
|
|
421
402
|
catch (processingError) {
|
|
422
403
|
logger_1.PUBLISHER_LOGGER.error(`Processing error for message ${messageId}:`, processingError);
|
|
@@ -551,16 +532,40 @@ class Streams {
|
|
|
551
532
|
async cleanupAcknowledgedMessages(eventName, interval = this.config.acknowledgedMessageCleanupInterval) {
|
|
552
533
|
const streamName = `${eventName}:${this.consumerGroupName}`;
|
|
553
534
|
const cleanupThreshold = Date.now() - interval;
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
//
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
535
|
+
const CHUNK_SIZE = 10000; // Process messages in chunks to avoid memory issues
|
|
536
|
+
try {
|
|
537
|
+
// Get pending info for this consumer group once
|
|
538
|
+
const pendingInfo = await this.redisGroups.xpending(streamName, this.consumerGroupName);
|
|
539
|
+
const hasPendingMessages = pendingInfo && pendingInfo[0] > 0;
|
|
540
|
+
let lastId = '-'; // Start from the beginning of the stream
|
|
541
|
+
while (true) {
|
|
542
|
+
// Get a chunk of messages
|
|
543
|
+
const messages = await this.redisGroups.xrange(streamName, lastId, cleanupThreshold.toString(), 'COUNT', CHUNK_SIZE);
|
|
544
|
+
if (!messages || messages.length === 0)
|
|
545
|
+
break;
|
|
546
|
+
// Update lastId for next iteration (exclusive)
|
|
547
|
+
lastId = messages[messages.length - 1][0];
|
|
548
|
+
if (hasPendingMessages) {
|
|
549
|
+
// If there are pending messages, we need to check each message
|
|
550
|
+
const pendingDetails = (await this.redisGroups.xpending(streamName, this.consumerGroupName, lastId, '+', CHUNK_SIZE, this.instanceId));
|
|
551
|
+
const pendingIds = new Set(pendingDetails.map((detail) => detail[0]));
|
|
552
|
+
const acknowledgedIds = messages.map((msg) => msg[0]).filter((id) => !pendingIds.has(id));
|
|
553
|
+
if (acknowledgedIds.length > 0) {
|
|
554
|
+
await this.redisGroups.xdel(streamName, ...acknowledgedIds);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
// If no pending messages, we can safely delete all messages in this chunk
|
|
559
|
+
const messageIds = messages.map((msg) => msg[0]);
|
|
560
|
+
await this.redisGroups.xdel(streamName, ...messageIds);
|
|
561
|
+
}
|
|
562
|
+
// If we got less than CHUNK_SIZE messages, we've reached the end
|
|
563
|
+
if (messages.length < CHUNK_SIZE)
|
|
564
|
+
break;
|
|
561
565
|
}
|
|
562
|
-
|
|
563
|
-
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
logger_1.PUBLISHER_LOGGER.error(`Error during cleanup for ${streamName}:`, error);
|
|
564
569
|
}
|
|
565
570
|
}
|
|
566
571
|
async getDiagnosticData(events) {
|
|
@@ -676,7 +681,6 @@ class Streams {
|
|
|
676
681
|
async acknowledgeMessage(ackKey) {
|
|
677
682
|
const { streamName, messageId } = this.demergeMessageKey(ackKey);
|
|
678
683
|
await this.redisGroups.xack(streamName, this.consumerGroupName, messageId);
|
|
679
|
-
await this.redisGroups.zadd(`ack:${streamName}`, Date.now().toString(), messageId);
|
|
680
684
|
}
|
|
681
685
|
frameMessageKey(streamName, messageId) {
|
|
682
686
|
return `${streamName}##${messageId}`;
|
package/src/lib/redis/utils.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UTILS =
|
|
3
|
+
exports.UTILS = void 0;
|
|
4
|
+
exports.getAllConsumerGroups = getAllConsumerGroups;
|
|
5
|
+
exports.getSummaryOnStreamConsumerGroup = getSummaryOnStreamConsumerGroup;
|
|
6
|
+
exports.getUnacknowledgedMessages = getUnacknowledgedMessages;
|
|
7
|
+
exports.getMessageStatesCount = getMessageStatesCount;
|
|
8
|
+
exports.notifySubscribers = notifySubscribers;
|
|
9
|
+
exports.removedScheduledJob = removedScheduledJob;
|
|
10
|
+
exports.encodeScheduledMessage = encodeScheduledMessage;
|
|
11
|
+
exports.decodeScheduledMessage = decodeScheduledMessage;
|
|
4
12
|
const logger_1 = require("./logger");
|
|
5
13
|
async function getAllConsumerGroups(eventName, redisConnection) {
|
|
6
14
|
const consumerGroups = await redisConnection.smembers(`${eventName}`);
|
|
7
15
|
return consumerGroups;
|
|
8
16
|
}
|
|
9
|
-
exports.getAllConsumerGroups = getAllConsumerGroups;
|
|
10
17
|
function* getSummaryOnStreamConsumerGroup(redisClient, consumerGroupName, streamName) {
|
|
11
18
|
const [count, , , consumers] = (yield redisClient.xpending(streamName, consumerGroupName));
|
|
12
19
|
yield {
|
|
@@ -21,7 +28,6 @@ function* getSummaryOnStreamConsumerGroup(redisClient, consumerGroupName, stream
|
|
|
21
28
|
: [],
|
|
22
29
|
};
|
|
23
30
|
}
|
|
24
|
-
exports.getSummaryOnStreamConsumerGroup = getSummaryOnStreamConsumerGroup;
|
|
25
31
|
async function getUnacknowledgedMessages(redisClient, consumerGroupName, streamName, consumerName, count = 500) {
|
|
26
32
|
try {
|
|
27
33
|
// Get pending messages summary
|
|
@@ -32,12 +38,16 @@ async function getUnacknowledgedMessages(redisClient, consumerGroupName, streamN
|
|
|
32
38
|
}
|
|
33
39
|
// Use the smallest and largest IDs to get a detailed range
|
|
34
40
|
const pendingMessageCount = summary[0];
|
|
35
|
-
//
|
|
36
|
-
|
|
41
|
+
// Create a message ID for 2 seconds ago to exclude recent messages
|
|
42
|
+
const oneSecondAgo = Date.now() - 2000;
|
|
43
|
+
const minId = '0-0'; // Start from beginning
|
|
44
|
+
const maxId = `${oneSecondAgo}-0`; // Only include messages older than 1 second
|
|
45
|
+
// Get detailed information in the range, excluding recent messages
|
|
46
|
+
let pendingMessages = (await redisClient.xpending(streamName, consumerGroupName, minId, maxId, count, consumerName));
|
|
37
47
|
/** If no pending messages on consumer, fetch messages from other consumers that haven't been claimed for more than 10s */
|
|
38
48
|
if (count > pendingMessages.length && pendingMessages.length === 0) {
|
|
39
49
|
await redisClient.xautoclaim(streamName, consumerGroupName, consumerName, 10000, '0-0', 'COUNT', 100);
|
|
40
|
-
pendingMessages = (await redisClient.xpending(streamName, consumerGroupName,
|
|
50
|
+
pendingMessages = (await redisClient.xpending(streamName, consumerGroupName, minId, maxId, count, consumerName));
|
|
41
51
|
}
|
|
42
52
|
return {
|
|
43
53
|
count: pendingMessageCount,
|
|
@@ -51,7 +61,6 @@ async function getUnacknowledgedMessages(redisClient, consumerGroupName, streamN
|
|
|
51
61
|
return { count: 0, messageIds: [] };
|
|
52
62
|
}
|
|
53
63
|
}
|
|
54
|
-
exports.getUnacknowledgedMessages = getUnacknowledgedMessages;
|
|
55
64
|
async function getMessageStatesCount(redisClient, streamName, consumerGroup) {
|
|
56
65
|
try {
|
|
57
66
|
const pendingInfo = (await redisClient.xpending(streamName, consumerGroup));
|
|
@@ -66,11 +75,9 @@ async function getMessageStatesCount(redisClient, streamName, consumerGroup) {
|
|
|
66
75
|
return { acknowledged: 0, unacknowledged: 0 };
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
|
-
exports.getMessageStatesCount = getMessageStatesCount;
|
|
70
78
|
async function notifySubscribers(redisClient, eventName, messageId, multicast = false) {
|
|
71
79
|
await redisClient.publish(eventName, JSON.stringify({ messageId, multicast }));
|
|
72
80
|
}
|
|
73
|
-
exports.notifySubscribers = notifySubscribers;
|
|
74
81
|
async function removedScheduledJob(redisClient, eventString) {
|
|
75
82
|
const currentTime = new Date().getTime();
|
|
76
83
|
const events = await redisClient.zrangebyscore('se', 0, currentTime);
|
|
@@ -79,7 +86,6 @@ async function removedScheduledJob(redisClient, eventString) {
|
|
|
79
86
|
const eventsLater = await redisClient.zrangebyscore('se', 0, currentTime);
|
|
80
87
|
logger_1.PUBLISHER_LOGGER.log(`Total Events in scheduled queue: ${eventsLater.length}`);
|
|
81
88
|
}
|
|
82
|
-
exports.removedScheduledJob = removedScheduledJob;
|
|
83
89
|
function encodeScheduledMessage(data) {
|
|
84
90
|
const eventName = data.eventName;
|
|
85
91
|
const eventData = JSON.stringify(data.data);
|
|
@@ -87,7 +93,6 @@ function encodeScheduledMessage(data) {
|
|
|
87
93
|
const eventDataBuffer = Buffer.from(eventData, 'utf8').toString('base64');
|
|
88
94
|
return `${eventName}%%${eventDataBuffer}%%${repeatInterval}`;
|
|
89
95
|
}
|
|
90
|
-
exports.encodeScheduledMessage = encodeScheduledMessage;
|
|
91
96
|
function decodeScheduledMessage(data) {
|
|
92
97
|
const parts = data.split('%%');
|
|
93
98
|
const eventName = parts[0];
|
|
@@ -97,7 +102,6 @@ function decodeScheduledMessage(data) {
|
|
|
97
102
|
repeatInterval: parseInt(parts[2]),
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
|
-
exports.decodeScheduledMessage = decodeScheduledMessage;
|
|
101
105
|
exports.UTILS = {
|
|
102
106
|
getMessageStatesCount,
|
|
103
107
|
getUnacknowledgedMessages,
|