@crowdin/app-project-module 0.94.2 → 0.94.3
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.
|
@@ -47,6 +47,9 @@ const index_1 = require("../../../util/index");
|
|
|
47
47
|
const logger_1 = require("../../../util/logger");
|
|
48
48
|
const prefetchCount = 10;
|
|
49
49
|
const forceProcessDelay = 10000;
|
|
50
|
+
const maxReconnectAttempts = 5;
|
|
51
|
+
const baseReconnectDelay = 1000;
|
|
52
|
+
const connectionHeartbeat = 60;
|
|
50
53
|
exports.HookEvents = {
|
|
51
54
|
fileAdded: 'file.added',
|
|
52
55
|
fileDeleted: 'file.deleted',
|
|
@@ -341,27 +344,52 @@ function updateCrowdinFromWebhookRequest(args) {
|
|
|
341
344
|
exports.updateCrowdinFromWebhookRequest = updateCrowdinFromWebhookRequest;
|
|
342
345
|
function listenQueueMessage({ config, integration, queueName, queueUrl, }) {
|
|
343
346
|
return __awaiter(this, void 0, void 0, function* () {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
347
|
+
let reconnectAttempts = 0;
|
|
348
|
+
const connect = () => __awaiter(this, void 0, void 0, function* () {
|
|
349
|
+
try {
|
|
350
|
+
const connection = yield amqplib_1.default.connect(queueUrl, {
|
|
351
|
+
heartbeat: connectionHeartbeat, // 60 seconds heartbeat
|
|
352
|
+
});
|
|
353
|
+
// Reset reconnect attempts on successful connection
|
|
354
|
+
reconnectAttempts = 0;
|
|
355
|
+
connection.on('error', (err) => {
|
|
356
|
+
(0, logger_1.logError)(`AMQP connection error: ${err.message}`);
|
|
357
|
+
});
|
|
358
|
+
connection.on('close', () => __awaiter(this, void 0, void 0, function* () {
|
|
359
|
+
(0, logger_1.logError)('AMQP connection closed, attempting to reconnect...');
|
|
360
|
+
yield scheduleReconnect();
|
|
361
|
+
}));
|
|
362
|
+
const channel = yield connection.createChannel();
|
|
363
|
+
if (channel) {
|
|
364
|
+
yield channel.assertQueue(queueName, { durable: true });
|
|
365
|
+
yield channel.prefetch(prefetchCount);
|
|
366
|
+
channel.on('error', (err) => {
|
|
367
|
+
(0, logger_1.logError)(`AMQP channel error: ${err.message}`);
|
|
368
|
+
});
|
|
369
|
+
channel.on('close', () => {
|
|
370
|
+
(0, logger_1.logError)('AMQP channel closed');
|
|
371
|
+
});
|
|
372
|
+
const onMessage = consumer({ channel, config, integration });
|
|
373
|
+
yield channel.consume(queueName, onMessage, { noAck: false });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (e) {
|
|
377
|
+
(0, logger_1.logError)(`Failed to connect to AMQP: ${e}`);
|
|
378
|
+
yield scheduleReconnect();
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
const scheduleReconnect = () => __awaiter(this, void 0, void 0, function* () {
|
|
382
|
+
if (reconnectAttempts >= maxReconnectAttempts) {
|
|
383
|
+
(0, logger_1.logError)(`Max reconnection attempts (${maxReconnectAttempts}) reached. Stopping reconnection.`);
|
|
350
384
|
return;
|
|
351
|
-
});
|
|
352
|
-
const channel = yield connection.createChannel();
|
|
353
|
-
if (channel) {
|
|
354
|
-
yield channel.assertQueue(queueName, { durable: true });
|
|
355
|
-
yield channel.prefetch(prefetchCount);
|
|
356
|
-
const onMessage = consumer({ channel, config, integration });
|
|
357
|
-
yield channel.consume(queueName, onMessage, { noAck: false });
|
|
358
385
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
setTimeout(() => {
|
|
362
|
-
|
|
363
|
-
},
|
|
364
|
-
}
|
|
386
|
+
reconnectAttempts++;
|
|
387
|
+
const delay = Math.min(baseReconnectDelay * Math.pow(2, reconnectAttempts - 1), 30000); // Exponential backoff up to 30s
|
|
388
|
+
setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
389
|
+
yield connect();
|
|
390
|
+
}), delay);
|
|
391
|
+
});
|
|
392
|
+
yield connect();
|
|
365
393
|
});
|
|
366
394
|
}
|
|
367
395
|
exports.listenQueueMessage = listenQueueMessage;
|
|
@@ -370,18 +398,21 @@ function consumer({ channel, config, integration, }) {
|
|
|
370
398
|
let webhooksInfo = {};
|
|
371
399
|
let webhooksData = [];
|
|
372
400
|
let timeoutId;
|
|
401
|
+
let messagesToAck = []; // Track messages for individual acknowledgment
|
|
373
402
|
const resetStateVariables = () => {
|
|
374
403
|
messagesCounter = 0;
|
|
375
404
|
webhooksInfo = {};
|
|
376
405
|
webhooksData = [];
|
|
406
|
+
messagesToAck = [];
|
|
377
407
|
};
|
|
378
408
|
return function (msg) {
|
|
379
409
|
var _a;
|
|
380
410
|
return __awaiter(this, void 0, void 0, function* () {
|
|
381
|
-
messagesCounter++;
|
|
382
411
|
if (!msg) {
|
|
383
412
|
return;
|
|
384
413
|
}
|
|
414
|
+
messagesCounter++;
|
|
415
|
+
messagesToAck.push(msg); // Add message to acknowledgment queue
|
|
385
416
|
clearTimeout(timeoutId);
|
|
386
417
|
try {
|
|
387
418
|
const data = JSON.parse(msg.content.toString());
|
|
@@ -413,7 +444,7 @@ function consumer({ channel, config, integration, }) {
|
|
|
413
444
|
webhooksData,
|
|
414
445
|
webhooksInfo,
|
|
415
446
|
channel,
|
|
416
|
-
|
|
447
|
+
messagesToAck,
|
|
417
448
|
});
|
|
418
449
|
resetStateVariables();
|
|
419
450
|
}), forceProcessDelay);
|
|
@@ -424,34 +455,65 @@ function consumer({ channel, config, integration, }) {
|
|
|
424
455
|
webhooksData,
|
|
425
456
|
webhooksInfo,
|
|
426
457
|
channel,
|
|
427
|
-
|
|
458
|
+
messagesToAck,
|
|
428
459
|
});
|
|
429
460
|
resetStateVariables();
|
|
430
461
|
}
|
|
431
462
|
catch (e) {
|
|
432
|
-
(0, logger_1.logError)(e);
|
|
463
|
+
(0, logger_1.logError)(`Error processing message: ${e}`);
|
|
464
|
+
// Acknowledge the current message even if there's an error to prevent reprocessing
|
|
465
|
+
try {
|
|
466
|
+
channel.ack(msg);
|
|
467
|
+
}
|
|
468
|
+
catch (ackError) {
|
|
469
|
+
(0, logger_1.logError)(`Error acknowledging message: ${ackError}`);
|
|
470
|
+
}
|
|
433
471
|
}
|
|
434
472
|
});
|
|
435
473
|
};
|
|
436
474
|
}
|
|
437
|
-
function processMessages({ channel,
|
|
475
|
+
function processMessages({ channel, messagesToAck, webhooksData, webhooksInfo, }) {
|
|
438
476
|
return __awaiter(this, void 0, void 0, function* () {
|
|
439
477
|
try {
|
|
478
|
+
// Prepare all webhook data first
|
|
440
479
|
yield Promise.all(webhooksData);
|
|
480
|
+
// Process each client's webhook data individually
|
|
441
481
|
for (const { data, integration, webhookData } of Object.values(webhooksInfo)) {
|
|
442
482
|
if (webhookData && webhookData.crowdinClient) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
483
|
+
try {
|
|
484
|
+
yield updateCrowdinFromWebhookRequest({
|
|
485
|
+
integration: integration,
|
|
486
|
+
webhookData: webhookData,
|
|
487
|
+
req: data,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
catch (processingError) {
|
|
491
|
+
(0, logger_1.logError)(`Error processing webhook request: ${processingError}`);
|
|
492
|
+
// Continue processing other clients even if one fails
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Acknowledge all messages individually after successful processing
|
|
497
|
+
for (const msg of messagesToAck) {
|
|
498
|
+
try {
|
|
499
|
+
channel.ack(msg);
|
|
500
|
+
}
|
|
501
|
+
catch (ackError) {
|
|
502
|
+
(0, logger_1.logError)(`Error acknowledging individual message: ${ackError}`);
|
|
448
503
|
}
|
|
449
504
|
}
|
|
450
|
-
channel.ack(msg, true);
|
|
451
505
|
}
|
|
452
506
|
catch (e) {
|
|
453
|
-
(0, logger_1.logError)(e);
|
|
454
|
-
|
|
507
|
+
(0, logger_1.logError)(`Error in processMessages: ${e}`);
|
|
508
|
+
// On critical error, nack all messages to requeue them
|
|
509
|
+
for (const msg of messagesToAck) {
|
|
510
|
+
try {
|
|
511
|
+
channel.nack(msg, false, true); // requeue the message
|
|
512
|
+
}
|
|
513
|
+
catch (nackError) {
|
|
514
|
+
(0, logger_1.logError)(`Error nacking message: ${nackError}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
455
517
|
}
|
|
456
518
|
});
|
|
457
519
|
}
|
package/package.json
CHANGED