emailengine-app 2.68.1 → 2.70.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.
Files changed (95) hide show
  1. package/.github/workflows/deploy.yml +8 -3
  2. package/.github/workflows/release.yaml +6 -0
  3. package/CHANGELOG.md +59 -0
  4. package/Gruntfile.js +3 -1
  5. package/config/default.toml +2 -0
  6. package/data/google-crawlers.json +7 -1
  7. package/getswagger.sh +40 -4
  8. package/gettext-extract.js +163 -0
  9. package/lib/account.js +135 -72
  10. package/lib/api-routes/account-routes.js +684 -106
  11. package/lib/api-routes/blocklist-routes.js +344 -0
  12. package/lib/api-routes/chat-routes.js +32 -14
  13. package/lib/api-routes/delivery-test-routes.js +346 -0
  14. package/lib/api-routes/export-routes.js +28 -14
  15. package/lib/api-routes/gateway-routes.js +427 -0
  16. package/lib/api-routes/license-routes.js +156 -0
  17. package/lib/api-routes/mailbox-routes.js +344 -0
  18. package/lib/api-routes/message-routes.js +221 -187
  19. package/lib/api-routes/oauth2-app-routes.js +697 -0
  20. package/lib/api-routes/outbox-routes.js +185 -0
  21. package/lib/api-routes/pubsub-routes.js +102 -0
  22. package/lib/api-routes/route-helpers.js +58 -0
  23. package/lib/api-routes/settings-routes.js +357 -0
  24. package/lib/api-routes/stats-routes.js +111 -0
  25. package/lib/api-routes/submit-routes.js +461 -0
  26. package/lib/api-routes/template-routes.js +60 -75
  27. package/lib/api-routes/token-routes.js +297 -0
  28. package/lib/api-routes/webhook-route-routes.js +181 -0
  29. package/lib/autodetect-imap-settings.js +0 -2
  30. package/lib/consts.js +5 -0
  31. package/lib/email-client/base-client.js +28 -6
  32. package/lib/email-client/gmail-client.js +133 -112
  33. package/lib/email-client/imap/mailbox.js +34 -11
  34. package/lib/email-client/imap/subconnection.js +20 -13
  35. package/lib/email-client/imap/sync-operations.js +131 -3
  36. package/lib/email-client/imap-client.js +152 -75
  37. package/lib/email-client/notification-handler.js +1 -4
  38. package/lib/email-client/outlook-client.js +134 -75
  39. package/lib/export.js +97 -20
  40. package/lib/feature-flags.js +2 -2
  41. package/lib/gateway.js +4 -9
  42. package/lib/get-raw-email.js +5 -5
  43. package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
  44. package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
  45. package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -24
  46. package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
  47. package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
  48. package/lib/logger.js +24 -21
  49. package/lib/message-port-stream.js +113 -16
  50. package/lib/metrics-collector.js +0 -2
  51. package/lib/oauth2-apps.js +13 -4
  52. package/lib/outbox.js +24 -40
  53. package/lib/redis-operations.js +1 -1
  54. package/lib/reject-worker-calls.js +42 -0
  55. package/lib/routes-ui.js +37 -8778
  56. package/lib/schemas.js +429 -84
  57. package/lib/sentry.js +139 -0
  58. package/lib/settings.js +9 -3
  59. package/lib/stream-encrypt.js +1 -1
  60. package/lib/templates.js +1 -1
  61. package/lib/tokens.js +5 -3
  62. package/lib/tools.js +70 -4
  63. package/lib/ui-routes/account-routes.js +45 -212
  64. package/lib/ui-routes/admin-config-routes.js +928 -489
  65. package/lib/ui-routes/admin-entities-routes.js +1 -0
  66. package/lib/ui-routes/auth-routes.js +1339 -0
  67. package/lib/ui-routes/dashboard-routes.js +188 -0
  68. package/lib/ui-routes/document-store-routes.js +800 -0
  69. package/lib/ui-routes/export-routes.js +217 -0
  70. package/lib/ui-routes/internals-routes.js +354 -0
  71. package/lib/ui-routes/network-config-routes.js +759 -0
  72. package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +369 -91
  73. package/lib/ui-routes/route-helpers.js +314 -0
  74. package/lib/ui-routes/smtp-test-routes.js +236 -0
  75. package/lib/ui-routes/unsubscribe-routes.js +232 -0
  76. package/lib/webhook-request.js +36 -0
  77. package/lib/webhooks.js +8 -4
  78. package/package.json +13 -12
  79. package/sbom.json +1 -1
  80. package/server.js +222 -39
  81. package/static/licenses.html +160 -300
  82. package/translations/messages.pot +112 -132
  83. package/update-info.sh +19 -1
  84. package/views/config/logging.hbs +48 -0
  85. package/views/dashboard.hbs +7 -26
  86. package/views/internals/index.hbs +15 -0
  87. package/views/tokens/index.hbs +9 -0
  88. package/workers/api.js +200 -4424
  89. package/workers/documents.js +2 -22
  90. package/workers/export.js +103 -104
  91. package/workers/imap-proxy.js +3 -23
  92. package/workers/imap.js +32 -36
  93. package/workers/smtp.js +2 -22
  94. package/workers/submit.js +26 -35
  95. package/workers/webhooks.js +9 -43
package/lib/outbox.js CHANGED
@@ -3,6 +3,28 @@
3
3
  const { submitQueue, redis } = require('./db');
4
4
  const { REDIS_PREFIX } = require('../lib/consts');
5
5
 
6
+ function formatQueueEntry(job) {
7
+ let scheduled = job.timestamp + (Number(job.opts.delay) || 0);
8
+
9
+ let backoffDelay = Number(job.opts.backoff && job.opts.backoff.delay) || 0;
10
+ // attemptsMade already includes the failed attempt, so the retry delay is 2^(attemptsMade-1) * base
11
+ let nextAttempt = job.attemptsMade ? Math.round(job.processedOn + Math.pow(2, job.attemptsMade - 1) * backoffDelay) : scheduled;
12
+
13
+ if (job.opts.attempts <= job.attemptsMade) {
14
+ nextAttempt = false;
15
+ }
16
+
17
+ return Object.assign(job.data, {
18
+ created: new Date(Number(job.data.created || job.timestamp)).toISOString(),
19
+ //status: job.name,
20
+ progress: job.progress,
21
+ attemptsMade: job.attemptsMade,
22
+ attempts: job.opts.attempts,
23
+ scheduled: new Date(scheduled).toISOString(),
24
+ nextAttempt: nextAttempt ? new Date(nextAttempt).toISOString() : false
25
+ });
26
+ }
27
+
6
28
  async function list(options) {
7
29
  options = options || {};
8
30
  let page = Number(options.page) || 0;
@@ -23,26 +45,7 @@ async function list(options) {
23
45
  try {
24
46
  let job = await submitQueue.getJob(jobId);
25
47
  if (job) {
26
- let scheduled = job.timestamp + (Number(job.opts.delay) || 0);
27
-
28
- let backoffDelay = Number(job.opts.backoff && job.opts.backoff.delay) || 0;
29
- let nextAttempt = job.attemptsMade ? Math.round(job.processedOn + Math.pow(2, job.attemptsMade) * backoffDelay) : scheduled;
30
-
31
- if (job.opts.attempts <= job.attemptsMade) {
32
- nextAttempt = false;
33
- }
34
-
35
- messages.push(
36
- Object.assign(job.data, {
37
- created: new Date(Number(job.created || job.timestamp)).toISOString(),
38
- //status: job.name,
39
- progress: job.progress,
40
- attemptsMade: job.attemptsMade,
41
- attempts: job.opts.attempts,
42
- scheduled: new Date(scheduled).toISOString(),
43
- nextAttempt: nextAttempt ? new Date(nextAttempt).toISOString() : false
44
- })
45
- );
48
+ messages.push(formatQueueEntry(job));
46
49
  }
47
50
  } catch (err) {
48
51
  logger.error({ msg: 'Failed to retrieve message info from outbox', jobId, err });
@@ -105,15 +108,6 @@ async function get(options) {
105
108
  return false;
106
109
  }
107
110
 
108
- let scheduled = job.timestamp + (Number(job.opts.delay) || 0);
109
-
110
- let backoffDelay = Number(job.opts.backoff && job.opts.backoff.delay) || 0;
111
- let nextAttempt = job.attemptsMade ? Math.round(job.processedOn + Math.pow(2, job.attemptsMade) * backoffDelay) : scheduled;
112
-
113
- if (job.opts.attempts <= job.attemptsMade) {
114
- nextAttempt = false;
115
- }
116
-
117
111
  try {
118
112
  let queueEntryBuf = await redis.hgetBuffer(`${REDIS_PREFIX}iaq:${job.data.account}`, job.data.queueId);
119
113
  if (!queueEntryBuf) {
@@ -124,17 +118,7 @@ async function get(options) {
124
118
  throw err;
125
119
  }
126
120
 
127
- let response = Object.assign(job.data, {
128
- created: new Date(Number(job.created || job.timestamp)).toISOString(),
129
- //status: job.name,
130
- progress: job.progress,
131
- attemptsMade: job.attemptsMade,
132
- attempts: job.opts.attempts,
133
- scheduled: new Date(scheduled).toISOString(),
134
- nextAttempt: nextAttempt ? new Date(nextAttempt).toISOString() : false
135
- });
136
-
137
- return response;
121
+ return formatQueueEntry(job);
138
122
  }
139
123
 
140
124
  module.exports = { list, del, get };
@@ -228,7 +228,7 @@ class RedisTransaction {
228
228
  * @returns {Promise<Array>} Array of values from successful commands
229
229
  */
230
230
  async execOrThrow() {
231
- const { results, values, error } = await this.exec();
231
+ const { values, error } = await this.exec();
232
232
 
233
233
  if (error) {
234
234
  throw error;
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Reject every pending cross-thread call routed to a worker thread that has
5
+ * terminated. Without this, a call placed against a worker that then crashes or
6
+ * is force-restarted keeps waiting until its own timeout fires - which can be
7
+ * minutes (the submitMessage floor) or hours (a large X-EE-Timeout). Rejecting
8
+ * here lets the caller fail fast with a retryable error the instant the worker
9
+ * is known to be gone.
10
+ *
11
+ * The stored `reject` wrapper clears the entry's timeout, so we only need to
12
+ * drop the entry from the queue and invoke it.
13
+ *
14
+ * @param {Map} callQueue - mid -> { resolve, reject, timer, worker }
15
+ * @param {Worker} worker - The terminated worker thread
16
+ * @param {Error} err - Rejection reason. May be a shared instance reused across
17
+ * concurrent rejections, so callers must not attach per-call fields to it.
18
+ * @returns {number} Number of pending calls that were rejected
19
+ */
20
+ function rejectWorkerCalls(callQueue, worker, err) {
21
+ let rejected = 0;
22
+
23
+ // Deleting the current entry while iterating a Map is safe in JS.
24
+ for (let [mid, entry] of callQueue) {
25
+ if (entry.worker !== worker) {
26
+ continue;
27
+ }
28
+
29
+ callQueue.delete(mid);
30
+ try {
31
+ entry.reject(err);
32
+ } catch (rejectErr) {
33
+ // A consumer that throws synchronously from its rejection path must
34
+ // not stop us from cleaning up the remaining pending calls.
35
+ }
36
+ rejected++;
37
+ }
38
+
39
+ return rejected;
40
+ }
41
+
42
+ module.exports = { rejectWorkerCalls };