emailengine-app 2.69.0 → 2.71.0
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/.github/workflows/deploy.yml +6 -3
- package/.github/workflows/release.yaml +2 -0
- package/.github/workflows/test.yml +73 -12
- package/.ncurc.js +3 -3
- package/CHANGELOG.md +37 -0
- package/Gruntfile.js +21 -23
- package/bin/emailengine.js +8 -1
- package/config/default.toml +5 -0
- package/config/test.toml +5 -0
- package/data/google-crawlers.json +1 -1
- package/getswagger.sh +44 -4
- package/gettext-extract.js +163 -0
- package/lib/account.js +104 -72
- package/lib/api-routes/account-routes.js +231 -71
- package/lib/api-routes/blocklist-routes.js +25 -18
- package/lib/api-routes/chat-routes.js +32 -14
- package/lib/api-routes/delivery-test-routes.js +30 -5
- package/lib/api-routes/export-routes.js +27 -2
- package/lib/api-routes/gateway-routes.js +63 -12
- package/lib/api-routes/license-routes.js +18 -4
- package/lib/api-routes/mailbox-routes.js +33 -7
- package/lib/api-routes/message-routes.js +291 -145
- package/lib/api-routes/oauth2-app-routes.js +90 -24
- package/lib/api-routes/outbox-routes.js +16 -4
- package/lib/api-routes/pubsub-routes.js +8 -4
- package/lib/api-routes/route-helpers.js +14 -1
- package/lib/api-routes/settings-routes.js +51 -25
- package/lib/api-routes/stats-routes.js +37 -3
- package/lib/api-routes/submit-routes.js +31 -42
- package/lib/api-routes/template-routes.js +54 -21
- package/lib/api-routes/token-routes.js +67 -67
- package/lib/api-routes/webhook-route-routes.js +37 -8
- package/lib/autodetect-imap-settings.js +0 -2
- package/lib/consts.js +5 -0
- package/lib/document-store.js +22 -1
- package/lib/email-client/base-client.js +31 -8
- package/lib/email-client/gmail-client.js +119 -112
- package/lib/email-client/imap/mailbox.js +2 -2
- package/lib/email-client/imap/subconnection.js +0 -1
- package/lib/email-client/imap/sync-operations.js +1 -1
- package/lib/email-client/imap-client.js +36 -17
- package/lib/email-client/notification-handler.js +3 -6
- package/lib/email-client/outlook-client.js +49 -62
- package/lib/export.js +49 -1
- package/lib/feature-flags.js +8 -2
- package/lib/gateway.js +4 -9
- package/lib/get-raw-email.js +5 -5
- package/lib/imapproxy/imap-core/lib/imap-connection.js +0 -1
- package/lib/license-beacon.js +367 -0
- package/lib/logger.js +35 -22
- package/lib/metrics-collector.js +0 -2
- package/lib/oauth2-apps.js +13 -4
- package/lib/outbox.js +24 -40
- package/lib/redis-operations.js +1 -1
- package/lib/routes-ui.js +2 -1
- package/lib/schemas.js +403 -83
- package/lib/sentry.js +139 -0
- package/lib/settings.js +9 -3
- package/lib/stream-encrypt.js +1 -1
- package/lib/templates.js +1 -1
- package/lib/tokens.js +5 -3
- package/lib/tools.js +28 -6
- package/lib/ui-routes/account-routes.js +7 -4
- package/lib/ui-routes/admin-config-routes.js +20 -6
- package/lib/ui-routes/document-store-routes.js +7 -1
- package/lib/ui-routes/oauth-config-routes.js +0 -2
- package/lib/ui-routes/route-helpers.js +0 -2
- package/lib/ui-routes/unsubscribe-routes.js +0 -2
- package/lib/webhooks.js +8 -4
- package/package.json +23 -19
- package/sbom.json +1 -1
- package/server.js +38 -31
- package/static/licenses.html +171 -391
- package/translations/de.mo +0 -0
- package/translations/de.po +154 -142
- package/translations/et.mo +0 -0
- package/translations/et.po +129 -131
- package/translations/fr.mo +0 -0
- package/translations/fr.po +133 -136
- package/translations/ja.mo +0 -0
- package/translations/ja.po +126 -129
- package/translations/messages.pot +107 -107
- package/translations/nl.mo +0 -0
- package/translations/nl.po +128 -130
- package/translations/pl.mo +0 -0
- package/translations/pl.po +125 -128
- package/update-info.sh +19 -1
- package/views/config/logging.hbs +48 -0
- package/views/dashboard.hbs +22 -0
- package/workers/api.js +33 -37
- package/workers/documents.js +2 -22
- package/workers/export.js +73 -92
- package/workers/imap-proxy.js +3 -23
- package/workers/imap.js +2 -22
- package/workers/smtp.js +2 -22
- package/workers/submit.js +6 -24
- package/workers/webhooks.js +2 -22
package/workers/documents.js
CHANGED
|
@@ -14,28 +14,8 @@ const GB_COLLECT_DELAY = 6 * 3600 * 1000; // 6h
|
|
|
14
14
|
const GB_FAILURE_DELAY = 3 * 1000;
|
|
15
15
|
const GB_EMPTY_DELAY = 10 * 1000;
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
Bugsnag.start({
|
|
20
|
-
apiKey: readEnvValue('BUGSNAG_API_KEY'),
|
|
21
|
-
appVersion: packageData.version,
|
|
22
|
-
logger: {
|
|
23
|
-
debug(...args) {
|
|
24
|
-
logger.debug({ msg: args.shift(), worker: 'documents', source: 'bugsnag', args: args.length ? args : undefined });
|
|
25
|
-
},
|
|
26
|
-
info(...args) {
|
|
27
|
-
logger.debug({ msg: args.shift(), worker: 'documents', source: 'bugsnag', args: args.length ? args : undefined });
|
|
28
|
-
},
|
|
29
|
-
warn(...args) {
|
|
30
|
-
logger.warn({ msg: args.shift(), worker: 'documents', source: 'bugsnag', args: args.length ? args : undefined });
|
|
31
|
-
},
|
|
32
|
-
error(...args) {
|
|
33
|
-
logger.error({ msg: args.shift(), worker: 'documents', source: 'bugsnag', args: args.length ? args : undefined });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
38
|
-
}
|
|
17
|
+
const { initSentry } = require('../lib/sentry');
|
|
18
|
+
initSentry('documents');
|
|
39
19
|
|
|
40
20
|
const { redis, queueConf } = require('../lib/db');
|
|
41
21
|
const { Worker } = require('bullmq');
|
package/workers/export.js
CHANGED
|
@@ -20,30 +20,10 @@ const {
|
|
|
20
20
|
const { getDuration, readEnvValue, threadStats, maybeReloadHttpProxyAgent } = require('../lib/tools');
|
|
21
21
|
const { webhooks: Webhooks } = require('../lib/webhooks');
|
|
22
22
|
const settings = require('../lib/settings');
|
|
23
|
-
const { Export } = require('../lib/export');
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
Bugsnag.start({
|
|
28
|
-
apiKey: readEnvValue('BUGSNAG_API_KEY'),
|
|
29
|
-
appVersion: packageData.version,
|
|
30
|
-
logger: {
|
|
31
|
-
debug(...args) {
|
|
32
|
-
logger.debug({ msg: args.shift(), worker: 'export', source: 'bugsnag', args: args.length ? args : undefined });
|
|
33
|
-
},
|
|
34
|
-
info(...args) {
|
|
35
|
-
logger.debug({ msg: args.shift(), worker: 'export', source: 'bugsnag', args: args.length ? args : undefined });
|
|
36
|
-
},
|
|
37
|
-
warn(...args) {
|
|
38
|
-
logger.warn({ msg: args.shift(), worker: 'export', source: 'bugsnag', args: args.length ? args : undefined });
|
|
39
|
-
},
|
|
40
|
-
error(...args) {
|
|
41
|
-
logger.error({ msg: args.shift(), worker: 'export', source: 'bugsnag', args: args.length ? args : undefined });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
46
|
-
}
|
|
23
|
+
const { Export, isTransientError, isSkippableError, isFolderMissingError, isRetryableError } = require('../lib/export');
|
|
24
|
+
|
|
25
|
+
const { initSentry } = require('../lib/sentry');
|
|
26
|
+
initSentry('export');
|
|
47
27
|
|
|
48
28
|
const { redis, queueConf } = require('../lib/db');
|
|
49
29
|
const { Worker } = require('bullmq');
|
|
@@ -74,23 +54,6 @@ const IMAP_MESSAGE_RETRY_BASE_DELAY = 2000;
|
|
|
74
54
|
const ACCOUNT_CHECK_INTERVAL = 60 * 1000;
|
|
75
55
|
const LOCK_EXTENSION_INTERVAL = 5 * 60 * 1000;
|
|
76
56
|
|
|
77
|
-
function isTransientError(err) {
|
|
78
|
-
if (['ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'EPIPE', 'EHOSTUNREACH'].includes(err.code)) {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
if (err.statusCode >= 500 && err.statusCode < 600) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
if (err.code === 'Timeout' || err.message?.includes('timeout')) {
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isSkippableError(err) {
|
|
91
|
-
return err.code === 'MessageNotFound' || err.statusCode === 404 || err.message?.includes('Failed to generate message ID');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
57
|
let callQueue = new Map();
|
|
95
58
|
let mids = 0;
|
|
96
59
|
|
|
@@ -229,10 +192,9 @@ async function indexMessages(job, exportData) {
|
|
|
229
192
|
}
|
|
230
193
|
|
|
231
194
|
const folderPath = foldersToProcess[i];
|
|
232
|
-
let
|
|
233
|
-
let lastError = null;
|
|
195
|
+
let attempt = 0;
|
|
234
196
|
|
|
235
|
-
while (
|
|
197
|
+
while (true) {
|
|
236
198
|
try {
|
|
237
199
|
const remaining = maxMessages ? maxMessages - totalIndexed : 0;
|
|
238
200
|
const queued = await indexFolder(accountObject, account, exportId, folderPath, startDate, endDate, indexingStartTime, remaining);
|
|
@@ -248,40 +210,43 @@ async function indexMessages(job, exportData) {
|
|
|
248
210
|
totalIndexed
|
|
249
211
|
});
|
|
250
212
|
|
|
251
|
-
lastError = null;
|
|
252
213
|
break;
|
|
253
214
|
} catch (err) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
215
|
+
attempt++;
|
|
216
|
+
// A folder that was deleted mid-export is handled inside indexFolder (treated as empty),
|
|
217
|
+
// so any error reaching here is a real failure. Only transient errors are worth retrying;
|
|
218
|
+
// a permanent error (deleted account, auth failure, unexpected server response) is not
|
|
219
|
+
// specific to this folder and would repeat for the rest, so fail the whole export. Silently
|
|
220
|
+
// skipping folders and marking an incomplete export "completed" hides data loss from the caller.
|
|
221
|
+
if (!isTransientError(err) || attempt >= FOLDER_INDEX_MAX_RETRIES) {
|
|
222
|
+
logger.error({
|
|
223
|
+
msg: 'Failed to index folder, failing export',
|
|
261
224
|
account,
|
|
262
225
|
exportId,
|
|
263
226
|
folder: folderPath,
|
|
264
|
-
|
|
265
|
-
delayMs: delay,
|
|
227
|
+
attempts: attempt,
|
|
266
228
|
err
|
|
267
229
|
});
|
|
268
|
-
|
|
230
|
+
err.message = `Failed to index folder "${folderPath}": ${err.message}`;
|
|
231
|
+
throw err;
|
|
269
232
|
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
233
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
234
|
+
const delay = FOLDER_INDEX_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
|
|
235
|
+
logger.warn({
|
|
236
|
+
msg: 'Folder indexing failed, retrying',
|
|
237
|
+
account,
|
|
238
|
+
exportId,
|
|
239
|
+
folder: folderPath,
|
|
240
|
+
attempt,
|
|
241
|
+
maxRetries: FOLDER_INDEX_MAX_RETRIES,
|
|
242
|
+
delayMs: delay,
|
|
243
|
+
err
|
|
244
|
+
});
|
|
245
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
246
|
+
}
|
|
282
247
|
}
|
|
283
248
|
|
|
284
|
-
//
|
|
249
|
+
// The folder indexed successfully (failures throw above); count it toward progress.
|
|
285
250
|
await Export.update(account, exportId, { foldersScanned: i + 1 });
|
|
286
251
|
|
|
287
252
|
if (maxMessages && totalIndexed >= maxMessages) {
|
|
@@ -341,7 +306,18 @@ async function indexFolder(accountObject, account, exportId, folderPath, startDa
|
|
|
341
306
|
cursor
|
|
342
307
|
};
|
|
343
308
|
|
|
344
|
-
|
|
309
|
+
let result;
|
|
310
|
+
try {
|
|
311
|
+
result = await accountObject.listMessages(listOptions);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
// Other errors (e.g. a deleted account) propagate
|
|
314
|
+
if (isFolderMissingError(err)) {
|
|
315
|
+
// The folder disappeared mid-export - treat it as empty instead of failing the export
|
|
316
|
+
logger.warn({ msg: 'Export folder was not found, skipping', account, exportId, folder: folderPath, err });
|
|
317
|
+
return queued;
|
|
318
|
+
}
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
345
321
|
|
|
346
322
|
for (const msg of result.messages || []) {
|
|
347
323
|
if (maxMessages && queued >= maxMessages) {
|
|
@@ -446,14 +422,14 @@ async function exportMessages(job, exportData) {
|
|
|
446
422
|
|
|
447
423
|
let lastAccountCheck = Date.now();
|
|
448
424
|
// Refreshes the job lock and export key expiry on a fixed cadence; called from the main loop and
|
|
449
|
-
// from inside the
|
|
425
|
+
// from inside the retry backoff so a batch stuck retrying does not let the lock lapse.
|
|
450
426
|
const maybeExtendLease = createLeaseExtender(job, account, exportId);
|
|
451
427
|
|
|
452
428
|
const accountData = await accountObject.loadAccountData(account);
|
|
453
429
|
const isApiAccount = await accountObject.isApiClient(accountData);
|
|
454
430
|
const MESSAGE_FETCH_BATCH_SIZE = 10; // Batch size for parallel message fetching
|
|
455
|
-
const
|
|
456
|
-
const
|
|
431
|
+
const MAX_BATCH_RETRIES = 5; // Max retries for rate-limited or transient per-message errors within a batch
|
|
432
|
+
const BATCH_RETRY_BASE_DELAY = 5000; // Base delay for batch retry backoff (5 seconds)
|
|
457
433
|
|
|
458
434
|
async function processMessage(message, entry) {
|
|
459
435
|
message.path = entry.folder;
|
|
@@ -557,7 +533,7 @@ async function exportMessages(job, exportData) {
|
|
|
557
533
|
}
|
|
558
534
|
|
|
559
535
|
let fetchBatch = entriesToFetch.slice(i, i + MESSAGE_FETCH_BATCH_SIZE);
|
|
560
|
-
let
|
|
536
|
+
let batchRetry = 0;
|
|
561
537
|
|
|
562
538
|
while (fetchBatch.length > 0) {
|
|
563
539
|
const messageIds = fetchBatch.map(e => e.messageId);
|
|
@@ -568,14 +544,17 @@ async function exportMessages(job, exportData) {
|
|
|
568
544
|
resultMap.set(result.messageId, result);
|
|
569
545
|
}
|
|
570
546
|
|
|
571
|
-
const
|
|
547
|
+
const retryEntries = [];
|
|
572
548
|
|
|
573
549
|
for (const entry of fetchBatch) {
|
|
574
550
|
const result = resultMap.get(entry.messageId);
|
|
575
551
|
|
|
576
552
|
if (result && result.error) {
|
|
577
553
|
const err = result.error;
|
|
578
|
-
|
|
554
|
+
// A single transient blip (rate limit, dropped batch response, network/5xx)
|
|
555
|
+
// must not fail an entire multi-message export; only give up once the retry
|
|
556
|
+
// budget is exhausted. See isRetryableError for the full classification.
|
|
557
|
+
const isRetryable = isRetryableError(err);
|
|
579
558
|
|
|
580
559
|
if (isSkippableError(err)) {
|
|
581
560
|
logger.warn({
|
|
@@ -587,8 +566,8 @@ async function exportMessages(job, exportData) {
|
|
|
587
566
|
reason: err.message || err.code
|
|
588
567
|
});
|
|
589
568
|
await Export.incrementSkipped(account, exportId);
|
|
590
|
-
} else if (
|
|
591
|
-
|
|
569
|
+
} else if (isRetryable && batchRetry < MAX_BATCH_RETRIES) {
|
|
570
|
+
retryEntries.push(entry);
|
|
592
571
|
} else {
|
|
593
572
|
const error = new Error(err.message);
|
|
594
573
|
error.code = err.code;
|
|
@@ -618,21 +597,21 @@ async function exportMessages(job, exportData) {
|
|
|
618
597
|
break;
|
|
619
598
|
}
|
|
620
599
|
|
|
621
|
-
if (
|
|
622
|
-
|
|
623
|
-
const delay =
|
|
600
|
+
if (retryEntries.length > 0) {
|
|
601
|
+
batchRetry++;
|
|
602
|
+
const delay = BATCH_RETRY_BASE_DELAY * Math.pow(2, batchRetry - 1) + Math.random() * 1000;
|
|
624
603
|
logger.warn({
|
|
625
|
-
msg: '
|
|
604
|
+
msg: 'Retrying failed messages during export batch',
|
|
626
605
|
account,
|
|
627
606
|
exportId,
|
|
628
|
-
|
|
629
|
-
attempt:
|
|
630
|
-
maxAttempts:
|
|
607
|
+
retryCount: retryEntries.length,
|
|
608
|
+
attempt: batchRetry,
|
|
609
|
+
maxAttempts: MAX_BATCH_RETRIES,
|
|
631
610
|
delayMs: Math.round(delay)
|
|
632
611
|
});
|
|
633
612
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
634
613
|
await maybeExtendLease();
|
|
635
|
-
fetchBatch =
|
|
614
|
+
fetchBatch = retryEntries;
|
|
636
615
|
} else {
|
|
637
616
|
break;
|
|
638
617
|
}
|
|
@@ -833,6 +812,11 @@ const exportWorker = new Worker(
|
|
|
833
812
|
lockDuration: 10 * 60 * 1000,
|
|
834
813
|
stalledInterval: 2 * 60 * 1000,
|
|
835
814
|
maxStalledCount: 5,
|
|
815
|
+
// Do not start consuming jobs at construction. Startup recovery (markInterruptedAsFailed +
|
|
816
|
+
// cleanup) must finish first, otherwise the worker can pick up a queued export and start
|
|
817
|
+
// processing it while recovery concurrently marks that same export failed and deletes its
|
|
818
|
+
// file. run() is called from the startup IIFE once recovery completes.
|
|
819
|
+
autorun: false,
|
|
836
820
|
...queueConf
|
|
837
821
|
}
|
|
838
822
|
);
|
|
@@ -883,6 +867,11 @@ function onCommand(command) {
|
|
|
883
867
|
logger.error({ msg: 'Failed to clean up export files', err });
|
|
884
868
|
}
|
|
885
869
|
|
|
870
|
+
// Now that interrupted exports have been reconciled and orphaned files cleaned, start consuming
|
|
871
|
+
// jobs. Mirrors BullMQ's own autorun: a fatal run() failure is surfaced as an 'error' event,
|
|
872
|
+
// which (no listener) crashes the worker thread so the main process can restart it.
|
|
873
|
+
exportWorker.run().catch(error => exportWorker.emit('error', error));
|
|
874
|
+
|
|
886
875
|
setInterval(() => {
|
|
887
876
|
try {
|
|
888
877
|
parentPort.postMessage({ cmd: 'heartbeat' });
|
|
@@ -960,11 +949,3 @@ parentPort.on('message', message => {
|
|
|
960
949
|
});
|
|
961
950
|
|
|
962
951
|
logger.info({ msg: 'Started export worker thread', version: packageData.version });
|
|
963
|
-
|
|
964
|
-
module.exports = {
|
|
965
|
-
isTransientError,
|
|
966
|
-
isSkippableError,
|
|
967
|
-
IMAP_MESSAGE_MAX_RETRIES,
|
|
968
|
-
IMAP_MESSAGE_RETRY_BASE_DELAY,
|
|
969
|
-
ACCOUNT_CHECK_INTERVAL
|
|
970
|
-
};
|
package/workers/imap-proxy.js
CHANGED
|
@@ -5,32 +5,12 @@ const { parentPort } = require('worker_threads');
|
|
|
5
5
|
const packageData = require('../package.json');
|
|
6
6
|
const logger = require('../lib/logger');
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const { threadStats } = require('../lib/tools');
|
|
9
9
|
|
|
10
10
|
const { run } = require('../lib/imapproxy/imap-server');
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
Bugsnag.start({
|
|
15
|
-
apiKey: readEnvValue('BUGSNAG_API_KEY'),
|
|
16
|
-
appVersion: packageData.version,
|
|
17
|
-
logger: {
|
|
18
|
-
debug(...args) {
|
|
19
|
-
logger.debug({ msg: args.shift(), worker: 'imapProxy', source: 'bugsnag', args: args.length ? args : undefined });
|
|
20
|
-
},
|
|
21
|
-
info(...args) {
|
|
22
|
-
logger.debug({ msg: args.shift(), worker: 'imapProxy', source: 'bugsnag', args: args.length ? args : undefined });
|
|
23
|
-
},
|
|
24
|
-
warn(...args) {
|
|
25
|
-
logger.warn({ msg: args.shift(), worker: 'imapProxy', source: 'bugsnag', args: args.length ? args : undefined });
|
|
26
|
-
},
|
|
27
|
-
error(...args) {
|
|
28
|
-
logger.error({ msg: args.shift(), worker: 'imapProxy', source: 'bugsnag', args: args.length ? args : undefined });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
33
|
-
}
|
|
12
|
+
const { initSentry } = require('../lib/sentry');
|
|
13
|
+
initSentry('imapProxy');
|
|
34
14
|
|
|
35
15
|
async function onCommand(command) {
|
|
36
16
|
switch (command.cmd) {
|
package/workers/imap.js
CHANGED
|
@@ -9,28 +9,8 @@ const { REDIS_PREFIX } = require('../lib/consts');
|
|
|
9
9
|
|
|
10
10
|
const { getDuration, getBoolean, emitChangeEvent, readEnvValue, hasEnvValue, threadStats, maybeReloadHttpProxyAgent } = require('../lib/tools');
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
Bugsnag.start({
|
|
15
|
-
apiKey: readEnvValue('BUGSNAG_API_KEY'),
|
|
16
|
-
appVersion: packageData.version,
|
|
17
|
-
logger: {
|
|
18
|
-
debug(...args) {
|
|
19
|
-
logger.debug({ msg: args.shift(), worker: 'imap', source: 'bugsnag', args: args.length ? args : undefined });
|
|
20
|
-
},
|
|
21
|
-
info(...args) {
|
|
22
|
-
logger.debug({ msg: args.shift(), worker: 'imap', source: 'bugsnag', args: args.length ? args : undefined });
|
|
23
|
-
},
|
|
24
|
-
warn(...args) {
|
|
25
|
-
logger.warn({ msg: args.shift(), worker: 'imap', source: 'bugsnag', args: args.length ? args : undefined });
|
|
26
|
-
},
|
|
27
|
-
error(...args) {
|
|
28
|
-
logger.error({ msg: args.shift(), worker: 'imap', source: 'bugsnag', args: args.length ? args : undefined });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
33
|
-
}
|
|
12
|
+
const { initSentry } = require('../lib/sentry');
|
|
13
|
+
initSentry('imap');
|
|
34
14
|
|
|
35
15
|
const { IMAPClient } = require('../lib/email-client/imap-client');
|
|
36
16
|
const { GmailClient } = require('../lib/email-client/gmail-client');
|
package/workers/smtp.js
CHANGED
|
@@ -9,28 +9,8 @@ const logger = require('../lib/logger');
|
|
|
9
9
|
const { getDuration, emitChangeEvent, readEnvValue, threadStats, loadTlsConfig, getByteSize } = require('../lib/tools');
|
|
10
10
|
const { matchIp } = require('../lib/utils/network');
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
Bugsnag.start({
|
|
15
|
-
apiKey: readEnvValue('BUGSNAG_API_KEY'),
|
|
16
|
-
appVersion: packageData.version,
|
|
17
|
-
logger: {
|
|
18
|
-
debug(...args) {
|
|
19
|
-
logger.debug({ msg: args.shift(), worker: 'smtp', source: 'bugsnag', args: args.length ? args : undefined });
|
|
20
|
-
},
|
|
21
|
-
info(...args) {
|
|
22
|
-
logger.debug({ msg: args.shift(), worker: 'smtp', source: 'bugsnag', args: args.length ? args : undefined });
|
|
23
|
-
},
|
|
24
|
-
warn(...args) {
|
|
25
|
-
logger.warn({ msg: args.shift(), worker: 'smtp', source: 'bugsnag', args: args.length ? args : undefined });
|
|
26
|
-
},
|
|
27
|
-
error(...args) {
|
|
28
|
-
logger.error({ msg: args.shift(), worker: 'smtp', source: 'bugsnag', args: args.length ? args : undefined });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
33
|
-
}
|
|
12
|
+
const { initSentry } = require('../lib/sentry');
|
|
13
|
+
initSentry('smtp');
|
|
34
14
|
|
|
35
15
|
const { SMTPServer } = require('smtp-server');
|
|
36
16
|
const util = require('util');
|
package/workers/submit.js
CHANGED
|
@@ -11,28 +11,8 @@ const { getDuration, readEnvValue, threadStats, maybeReloadHttpProxyAgent } = re
|
|
|
11
11
|
const { webhooks: Webhooks } = require('../lib/webhooks');
|
|
12
12
|
const settings = require('../lib/settings');
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
Bugsnag.start({
|
|
17
|
-
apiKey: readEnvValue('BUGSNAG_API_KEY'),
|
|
18
|
-
appVersion: packageData.version,
|
|
19
|
-
logger: {
|
|
20
|
-
debug(...args) {
|
|
21
|
-
logger.debug({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
|
|
22
|
-
},
|
|
23
|
-
info(...args) {
|
|
24
|
-
logger.debug({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
|
|
25
|
-
},
|
|
26
|
-
warn(...args) {
|
|
27
|
-
logger.warn({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
|
|
28
|
-
},
|
|
29
|
-
error(...args) {
|
|
30
|
-
logger.error({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
35
|
-
}
|
|
14
|
+
const { initSentry } = require('../lib/sentry');
|
|
15
|
+
initSentry('submit');
|
|
36
16
|
|
|
37
17
|
const util = require('util');
|
|
38
18
|
const { redis, queueConf, submitQueue } = require('../lib/db');
|
|
@@ -222,13 +202,15 @@ const submitWorker = new Worker(
|
|
|
222
202
|
}
|
|
223
203
|
|
|
224
204
|
let backoffDelay = Number(job.opts.backoff && job.opts.backoff.delay) || 0;
|
|
225
|
-
|
|
205
|
+
// job.attemptsMade is not yet incremented for the ongoing attempt, so the
|
|
206
|
+
// next retry (if this attempt fails) is delayed by 2^attemptsMade * base
|
|
207
|
+
let nextAttempt = job.attemptsMade + 1 < job.opts.attempts ? Math.round(job.processedOn + Math.pow(2, job.attemptsMade) * backoffDelay) : false;
|
|
226
208
|
|
|
227
209
|
queueEntry.job = {
|
|
228
210
|
id: job.id,
|
|
229
211
|
attemptsMade: job.attemptsMade,
|
|
230
212
|
attempts: job.opts.attempts,
|
|
231
|
-
nextAttempt: new Date(nextAttempt).toISOString()
|
|
213
|
+
nextAttempt: nextAttempt ? new Date(nextAttempt).toISOString() : false
|
|
232
214
|
};
|
|
233
215
|
|
|
234
216
|
let res = await accountObject.submitMessage(queueEntry);
|
package/workers/webhooks.js
CHANGED
|
@@ -13,28 +13,8 @@ const { GooglePubSub } = require('../lib/oauth/pubsub/google');
|
|
|
13
13
|
const { readEnvValue, threadStats, getDuration, httpAgent, getServiceSecret, maybeReloadHttpProxyAgent } = require('../lib/tools');
|
|
14
14
|
const { sendWebhookRequest } = require('../lib/webhook-request');
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
Bugsnag.start({
|
|
19
|
-
apiKey: readEnvValue('BUGSNAG_API_KEY'),
|
|
20
|
-
appVersion: packageData.version,
|
|
21
|
-
logger: {
|
|
22
|
-
debug(...args) {
|
|
23
|
-
logger.debug({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
|
|
24
|
-
},
|
|
25
|
-
info(...args) {
|
|
26
|
-
logger.debug({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
|
|
27
|
-
},
|
|
28
|
-
warn(...args) {
|
|
29
|
-
logger.warn({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
|
|
30
|
-
},
|
|
31
|
-
error(...args) {
|
|
32
|
-
logger.error({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
37
|
-
}
|
|
16
|
+
const { initSentry } = require('../lib/sentry');
|
|
17
|
+
initSentry('webhooks');
|
|
38
18
|
|
|
39
19
|
const { redis, queueConf } = require('../lib/db');
|
|
40
20
|
const { Worker } = require('bullmq');
|