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.
- package/.github/workflows/deploy.yml +8 -3
- package/.github/workflows/release.yaml +6 -0
- package/CHANGELOG.md +59 -0
- package/Gruntfile.js +3 -1
- package/config/default.toml +2 -0
- package/data/google-crawlers.json +7 -1
- package/getswagger.sh +40 -4
- package/gettext-extract.js +163 -0
- package/lib/account.js +135 -72
- package/lib/api-routes/account-routes.js +684 -106
- package/lib/api-routes/blocklist-routes.js +344 -0
- package/lib/api-routes/chat-routes.js +32 -14
- package/lib/api-routes/delivery-test-routes.js +346 -0
- package/lib/api-routes/export-routes.js +28 -14
- package/lib/api-routes/gateway-routes.js +427 -0
- package/lib/api-routes/license-routes.js +156 -0
- package/lib/api-routes/mailbox-routes.js +344 -0
- package/lib/api-routes/message-routes.js +221 -187
- package/lib/api-routes/oauth2-app-routes.js +697 -0
- package/lib/api-routes/outbox-routes.js +185 -0
- package/lib/api-routes/pubsub-routes.js +102 -0
- package/lib/api-routes/route-helpers.js +58 -0
- package/lib/api-routes/settings-routes.js +357 -0
- package/lib/api-routes/stats-routes.js +111 -0
- package/lib/api-routes/submit-routes.js +461 -0
- package/lib/api-routes/template-routes.js +60 -75
- package/lib/api-routes/token-routes.js +297 -0
- package/lib/api-routes/webhook-route-routes.js +181 -0
- package/lib/autodetect-imap-settings.js +0 -2
- package/lib/consts.js +5 -0
- package/lib/email-client/base-client.js +28 -6
- package/lib/email-client/gmail-client.js +133 -112
- package/lib/email-client/imap/mailbox.js +34 -11
- package/lib/email-client/imap/subconnection.js +20 -13
- package/lib/email-client/imap/sync-operations.js +131 -3
- package/lib/email-client/imap-client.js +152 -75
- package/lib/email-client/notification-handler.js +1 -4
- package/lib/email-client/outlook-client.js +134 -75
- package/lib/export.js +97 -20
- package/lib/feature-flags.js +2 -2
- package/lib/gateway.js +4 -9
- package/lib/get-raw-email.js +5 -5
- package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
- package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
- package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -24
- package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
- package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
- package/lib/logger.js +24 -21
- package/lib/message-port-stream.js +113 -16
- 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/reject-worker-calls.js +42 -0
- package/lib/routes-ui.js +37 -8778
- package/lib/schemas.js +429 -84
- 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 +70 -4
- package/lib/ui-routes/account-routes.js +45 -212
- package/lib/ui-routes/admin-config-routes.js +928 -489
- package/lib/ui-routes/admin-entities-routes.js +1 -0
- package/lib/ui-routes/auth-routes.js +1339 -0
- package/lib/ui-routes/dashboard-routes.js +188 -0
- package/lib/ui-routes/document-store-routes.js +800 -0
- package/lib/ui-routes/export-routes.js +217 -0
- package/lib/ui-routes/internals-routes.js +354 -0
- package/lib/ui-routes/network-config-routes.js +759 -0
- package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +369 -91
- package/lib/ui-routes/route-helpers.js +314 -0
- package/lib/ui-routes/smtp-test-routes.js +236 -0
- package/lib/ui-routes/unsubscribe-routes.js +232 -0
- package/lib/webhook-request.js +36 -0
- package/lib/webhooks.js +8 -4
- package/package.json +13 -12
- package/sbom.json +1 -1
- package/server.js +222 -39
- package/static/licenses.html +160 -300
- package/translations/messages.pot +112 -132
- package/update-info.sh +19 -1
- package/views/config/logging.hbs +48 -0
- package/views/dashboard.hbs +7 -26
- package/views/internals/index.hbs +15 -0
- package/views/tokens/index.hbs +9 -0
- package/workers/api.js +200 -4424
- package/workers/documents.js +2 -22
- package/workers/export.js +103 -104
- package/workers/imap-proxy.js +3 -23
- package/workers/imap.js +32 -36
- package/workers/smtp.js +2 -22
- package/workers/submit.js +26 -35
- package/workers/webhooks.js +9 -43
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Public (unauthenticated) subscription-management routes. These render the unsubscribe
|
|
4
|
+
// landing page reached from the List-Unsubscribe link in outgoing messages and process
|
|
5
|
+
// the subscribe/unsubscribe form submission. Extracted verbatim from lib/routes-ui.js.
|
|
6
|
+
// Both routes set `auth: false` and define their own validation failAction handlers.
|
|
7
|
+
|
|
8
|
+
const Joi = require('joi');
|
|
9
|
+
const Boom = require('@hapi/boom');
|
|
10
|
+
const { Account } = require('../account');
|
|
11
|
+
const { redis } = require('../db');
|
|
12
|
+
const { accountIdSchema } = require('../schemas');
|
|
13
|
+
const { REDIS_PREFIX } = require('../consts');
|
|
14
|
+
|
|
15
|
+
function init(args) {
|
|
16
|
+
const { server, call } = args;
|
|
17
|
+
|
|
18
|
+
server.route({
|
|
19
|
+
method: 'GET',
|
|
20
|
+
path: '/unsubscribe',
|
|
21
|
+
async handler(request, h) {
|
|
22
|
+
let data = Buffer.from(request.query.data, 'base64url').toString();
|
|
23
|
+
// do not check signature, validate fields in the submit step
|
|
24
|
+
|
|
25
|
+
data = JSON.parse(data);
|
|
26
|
+
|
|
27
|
+
if (!data || typeof data !== 'object' || data.act !== 'unsub') {
|
|
28
|
+
throw new Error(request.app.gt.gettext('Invalid input'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// throws if account does not exist
|
|
32
|
+
let accountObject = new Account({ redis, account: data.acc });
|
|
33
|
+
await accountObject.loadAccountData();
|
|
34
|
+
|
|
35
|
+
return h.view(
|
|
36
|
+
'unsubscribe',
|
|
37
|
+
{
|
|
38
|
+
pageTitleFull: request.app.gt.gettext('Subscription Management'),
|
|
39
|
+
|
|
40
|
+
unsubscribed: await redis.hexists(`${REDIS_PREFIX}lists:unsub:entries:${data.list}`, data.rcpt),
|
|
41
|
+
values: {
|
|
42
|
+
listId: data.list,
|
|
43
|
+
account: data.acc,
|
|
44
|
+
messageId: data.msg,
|
|
45
|
+
email: data.rcpt
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
layout: 'public'
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
auth: false,
|
|
55
|
+
|
|
56
|
+
validate: {
|
|
57
|
+
options: {
|
|
58
|
+
stripUnknown: true,
|
|
59
|
+
abortEarly: false,
|
|
60
|
+
convert: true
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async failAction(request, h, err) {
|
|
64
|
+
request.logger.error({ msg: 'Failed to validate request arguments', err });
|
|
65
|
+
let error = Boom.boomify(new Error(request.app.gt.gettext('Invalid request. Check your input and try again.')), { statusCode: 400 });
|
|
66
|
+
if (err.code) {
|
|
67
|
+
error.output.payload.code = err.code;
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
query: Joi.object({
|
|
73
|
+
data: Joi.string().base64({ paddingRequired: false, urlSafe: true }).required(),
|
|
74
|
+
sig: Joi.string().base64({ paddingRequired: false, urlSafe: true })
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
server.route({
|
|
81
|
+
method: 'POST',
|
|
82
|
+
path: '/unsubscribe/address',
|
|
83
|
+
async handler(request, h) {
|
|
84
|
+
try {
|
|
85
|
+
// throws if account does not exist
|
|
86
|
+
let accountObject = new Account({ redis, account: request.payload.account });
|
|
87
|
+
await accountObject.loadAccountData();
|
|
88
|
+
|
|
89
|
+
let reSubscribed = false;
|
|
90
|
+
|
|
91
|
+
switch (request.payload.action) {
|
|
92
|
+
case 'unsubscribe': {
|
|
93
|
+
let isNew = await redis.eeListAdd(
|
|
94
|
+
`${REDIS_PREFIX}lists:unsub:lists`,
|
|
95
|
+
`${REDIS_PREFIX}lists:unsub:entries:${request.payload.listId}`,
|
|
96
|
+
request.payload.listId,
|
|
97
|
+
request.payload.email.toLowerCase().trim(),
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
recipient: request.payload.email,
|
|
100
|
+
account: request.payload.account,
|
|
101
|
+
source: 'form',
|
|
102
|
+
reason: 'unsubscribe',
|
|
103
|
+
messageId: request.payload.messageId,
|
|
104
|
+
remoteAddress: request.info.remoteAddress,
|
|
105
|
+
userAgent: request.headers['user-agent'],
|
|
106
|
+
created: new Date().toISOString()
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (isNew) {
|
|
111
|
+
await call({
|
|
112
|
+
cmd: 'unsubscribe',
|
|
113
|
+
account: request.payload.account,
|
|
114
|
+
payload: {
|
|
115
|
+
recipient: request.payload.email,
|
|
116
|
+
messageId: request.payload.messageId,
|
|
117
|
+
listId: request.payload.listId,
|
|
118
|
+
remoteAddress: request.info.remoteAddress,
|
|
119
|
+
userAgent: request.headers['user-agent']
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case 'subscribe': {
|
|
127
|
+
let removed = await redis.eeListRemove(
|
|
128
|
+
`${REDIS_PREFIX}lists:unsub:lists`,
|
|
129
|
+
`${REDIS_PREFIX}lists:unsub:entries:${request.payload.listId}`,
|
|
130
|
+
request.payload.listId,
|
|
131
|
+
request.payload.email.toLowerCase().trim()
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (removed) {
|
|
135
|
+
await call({
|
|
136
|
+
cmd: 'subscribe',
|
|
137
|
+
account: request.payload.account,
|
|
138
|
+
payload: {
|
|
139
|
+
recipient: request.payload.email,
|
|
140
|
+
messageId: request.payload.messageId,
|
|
141
|
+
listId: request.payload.listId,
|
|
142
|
+
remoteAddress: request.info.remoteAddress,
|
|
143
|
+
userAgent: request.headers['user-agent']
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
reSubscribed = true;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return h.view(
|
|
154
|
+
'unsubscribe',
|
|
155
|
+
{
|
|
156
|
+
pageTitleFull: request.app.gt.gettext('Subscription Management'),
|
|
157
|
+
|
|
158
|
+
unsubscribed: await redis.hexists(`${REDIS_PREFIX}lists:unsub:entries:${request.payload.listId}`, request.payload.email),
|
|
159
|
+
values: request.payload,
|
|
160
|
+
reSubscribed
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
layout: 'public'
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
await request.flash({ type: 'danger', message: request.app.gt.gettext("Couldn't process request. Try again.") });
|
|
168
|
+
request.logger.error({ msg: 'Failed to process subscription request', err });
|
|
169
|
+
|
|
170
|
+
return h.view(
|
|
171
|
+
'unsubscribe',
|
|
172
|
+
{
|
|
173
|
+
pageTitleFull: request.app.gt.gettext('Subscription Management'),
|
|
174
|
+
unsubscribed: await redis.hexists(`${REDIS_PREFIX}lists:unsub:entries:${request.payload.listId}`, request.payload.email)
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
layout: 'public'
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
options: {
|
|
183
|
+
auth: false,
|
|
184
|
+
validate: {
|
|
185
|
+
options: {
|
|
186
|
+
stripUnknown: true,
|
|
187
|
+
abortEarly: false,
|
|
188
|
+
convert: true
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
async failAction(request, h, err) {
|
|
192
|
+
let errors = {};
|
|
193
|
+
|
|
194
|
+
if (err.details) {
|
|
195
|
+
err.details.forEach(detail => {
|
|
196
|
+
if (!errors[detail.path]) {
|
|
197
|
+
errors[detail.path] = detail.message;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await request.flash({ type: 'danger', message: request.app.gt.gettext("Couldn't process request. Try again.") });
|
|
203
|
+
request.logger.error({ msg: 'Failed to process subscription request', err });
|
|
204
|
+
|
|
205
|
+
return h
|
|
206
|
+
.view(
|
|
207
|
+
'unsubscribe',
|
|
208
|
+
{
|
|
209
|
+
pageTitleFull: request.app.gt.gettext('Subscription Management'),
|
|
210
|
+
unsubscribed: await redis.hexists(`${REDIS_PREFIX}lists:unsub:entries:${request.payload.listId}`, request.payload.email),
|
|
211
|
+
errors
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
layout: 'public'
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
.takeover();
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
payload: Joi.object({
|
|
221
|
+
action: Joi.string().valid('subscribe', 'unsubscribe').required(),
|
|
222
|
+
account: accountIdSchema.required(),
|
|
223
|
+
listId: Joi.string().hostname().empty('').example('test-list').label('List ID').required(),
|
|
224
|
+
email: Joi.string().email().empty('').required().description('Email address').required(),
|
|
225
|
+
messageId: Joi.string().empty('').max(996).example('<test123@example.com>').description('Message ID')
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = init;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Performs a single webhook HTTP delivery and always drains the response body.
|
|
5
|
+
*
|
|
6
|
+
* The webhook response body is never used, but with undici's keepAlive dispatcher
|
|
7
|
+
* a connection is only returned to the pool once its body has been consumed.
|
|
8
|
+
* Draining on every path (success and failure) prevents successful deliveries -
|
|
9
|
+
* the common, high-volume case - from pinning pooled sockets.
|
|
10
|
+
*
|
|
11
|
+
* @param {Function} fetchImpl - fetch implementation (undici fetch)
|
|
12
|
+
* @param {string} url - Destination URL
|
|
13
|
+
* @param {Object} options - fetch options (method, body, headers, dispatcher)
|
|
14
|
+
* @returns {Promise<number>} Resolves with the HTTP status code on success
|
|
15
|
+
* @throws {Error} On a non-2xx response; the error carries a `statusCode` property
|
|
16
|
+
*/
|
|
17
|
+
async function sendWebhookRequest(fetchImpl, url, options) {
|
|
18
|
+
const res = await fetchImpl(url, options);
|
|
19
|
+
|
|
20
|
+
// Drain the body regardless of status so the socket can be reused.
|
|
21
|
+
try {
|
|
22
|
+
await res.text();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
// ignore drain errors
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
let err = new Error(res.statusText || `Invalid response: ${res.status} ${res.statusText}`);
|
|
29
|
+
err.statusCode = res.status;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return res.status;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { sendWebhookRequest };
|
package/lib/webhooks.js
CHANGED
|
@@ -70,12 +70,12 @@ class WebhooksHandler {
|
|
|
70
70
|
|
|
71
71
|
try {
|
|
72
72
|
let webhookMeta = msgpack.decode(entry);
|
|
73
|
-
if (webhookErrorFlag && typeof webhookErrorFlag === 'object' && !Object.keys(webhookErrorFlag)) {
|
|
73
|
+
if (webhookErrorFlag && typeof webhookErrorFlag === 'object' && !Object.keys(webhookErrorFlag).length) {
|
|
74
74
|
webhookErrorFlag = null;
|
|
75
75
|
}
|
|
76
76
|
response.webhooks.push(Object.assign(webhookMeta, { tcount, webhookErrorFlag }));
|
|
77
77
|
} catch (err) {
|
|
78
|
-
logger.error({ msg: 'Failed to process webhook', entry: entry.toString('base64') });
|
|
78
|
+
logger.error({ msg: 'Failed to process webhook', entry: entry && entry.toString('base64'), err });
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -398,7 +398,6 @@ class WebhooksHandler {
|
|
|
398
398
|
v = Number(v) || 0;
|
|
399
399
|
if (v !== this.handlerCacheV) {
|
|
400
400
|
// changes detected
|
|
401
|
-
v = this.handlerCacheV;
|
|
402
401
|
|
|
403
402
|
let webhookIds = await this.redis.smembers(this.getWebhooksIndexKey());
|
|
404
403
|
webhookIds = [].concat(webhookIds || []).sort((a, b) => -a.localeCompare(b));
|
|
@@ -418,7 +417,8 @@ class WebhooksHandler {
|
|
|
418
417
|
this.handlerCache.push(handler);
|
|
419
418
|
} else {
|
|
420
419
|
// compare existing
|
|
421
|
-
|
|
420
|
+
// the per-route counter is stored as a string, existing.v is a number (see get())
|
|
421
|
+
let webhookV = Number(await this.redis.hget(this.getWebhooksContentKey(), `${webhookId}:v`)) || 0;
|
|
422
422
|
if (existing.v !== webhookV) {
|
|
423
423
|
// update
|
|
424
424
|
for (let i = this.handlerCache.length - 1; i >= 0; i--) {
|
|
@@ -430,6 +430,10 @@ class WebhooksHandler {
|
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
|
+
|
|
434
|
+
// mark the cache as current only after a successful refresh, so a failure above
|
|
435
|
+
// leaves the cache stale and the next call retries
|
|
436
|
+
this.handlerCacheV = v;
|
|
433
437
|
}
|
|
434
438
|
|
|
435
439
|
return this.handlerCache;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emailengine-app",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.70.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"productTitle": "EmailEngine",
|
|
6
6
|
"description": "Email Sync Engine",
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"single": "EE_OPENAPI_VERBOSE=true EENGINE_LOG_RAW=true EENGINE_SECRET=your-encryption-key EENGINE_WORKERS=1 node --inspect server --dbs.redis='redis://127.0.0.1:6379/6' --api.port=7003 --api.host=0.0.0.0 | tee $HOME/ee.log.single.txt | pino-pretty",
|
|
12
12
|
"gmail": "EE_OPENAPI_VERBOSE=true EENGINE_LOG_RAW=true EENGINE_SECRET=your-encryption-key EENGINE_WORKERS=2 EENGINE_CORS_ORIGIN='*' node --inspect server --dbs.redis='redis://127.0.0.1:6379/11' --api.port=7003 --api.host=0.0.0.0 | tee $HOME/ee.log.gmail.txt | pino-pretty",
|
|
13
13
|
"test": "NODE_ENV=test grunt",
|
|
14
|
-
"lint": "npx eslint lib/**/*.js workers/**/*.js test/**/*.js server.js Gruntfile.js",
|
|
14
|
+
"lint": "npx eslint 'lib/**/*.js' 'workers/**/*.js' 'test/**/*.js' server.js Gruntfile.js",
|
|
15
15
|
"swagger": "./getswagger.sh",
|
|
16
16
|
"build-source": "rm -rf node_modules && npm install && rm -rf node_modules && npm ci --omit=dev && rm -rf node_modules/ace-builds node_modules/@postalsys/ee-client && ./update-info.sh",
|
|
17
17
|
"build-dist": "pkg --compress Brotli package.json && npm install && node winconf.js",
|
|
18
18
|
"build-dist-fast": "pkg --debug package.json && npm install && node winconf.js",
|
|
19
19
|
"licenses": "license-checker --excludePackages 'emailengine-app' --json | node list-generate.js > static/licenses.html",
|
|
20
|
-
"gettext": "find ./views -name \"*.hbs\" -print0 | xargs -0 xgettext-template -L Handlebars -o translations/messages.pot --force-po &&
|
|
20
|
+
"gettext": "find ./views -name \"*.hbs\" -print0 | xargs -0 xgettext-template -L Handlebars -o translations/messages.pot --force-po && node gettext-extract.js",
|
|
21
21
|
"prepare-docker": "echo \"EE_DOCKER_LEGACY=$EE_DOCKER_LEGACY\" >> system.env && cat system.env",
|
|
22
22
|
"update": "rm -rf node_modules package-lock.json && ncu -u && npm install && ./copy-static-files.sh && npm run licenses && npm run gettext",
|
|
23
23
|
"test-gmail-api": "node lib/email-client/gmail-client.js --dbs.redis=redis://127.0.0.1/11",
|
|
@@ -43,9 +43,8 @@
|
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://emailengine.app/",
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"@bull-board/
|
|
48
|
-
"@bull-board/hapi": "7.1.5",
|
|
46
|
+
"@bull-board/api": "8.0.0",
|
|
47
|
+
"@bull-board/hapi": "8.0.0",
|
|
49
48
|
"@elastic/elasticsearch": "8.15.3",
|
|
50
49
|
"@hapi/accept": "6.0.3",
|
|
51
50
|
"@hapi/bell": "13.1.0",
|
|
@@ -64,13 +63,14 @@
|
|
|
64
63
|
"@postalsys/gettext": "4.1.1",
|
|
65
64
|
"@postalsys/joi-messages": "1.0.5",
|
|
66
65
|
"@postalsys/templates": "2.0.1",
|
|
66
|
+
"@sentry/node": "10.57.0",
|
|
67
67
|
"@simplewebauthn/browser": "13.3.0",
|
|
68
68
|
"@simplewebauthn/server": "13.3.1",
|
|
69
69
|
"@zone-eu/mailsplit": "5.4.12",
|
|
70
70
|
"@zone-eu/wild-config": "1.7.5",
|
|
71
71
|
"ace-builds": "1.44.0",
|
|
72
72
|
"base32.js": "0.1.0",
|
|
73
|
-
"bullmq": "5.
|
|
73
|
+
"bullmq": "5.78.0",
|
|
74
74
|
"compare-versions": "6.1.1",
|
|
75
75
|
"dotenv": "17.4.2",
|
|
76
76
|
"encoding-japanese": "2.2.0",
|
|
@@ -84,9 +84,9 @@
|
|
|
84
84
|
"html-to-text": "10.0.0",
|
|
85
85
|
"ical.js": "1.5.0",
|
|
86
86
|
"iconv-lite": "0.7.2",
|
|
87
|
-
"imapflow": "1.
|
|
87
|
+
"imapflow": "1.4.0",
|
|
88
88
|
"ioredfour": "1.4.1",
|
|
89
|
-
"ioredis": "5.11.
|
|
89
|
+
"ioredis": "5.11.1",
|
|
90
90
|
"ipaddr.js": "2.4.0",
|
|
91
91
|
"joi": "17.13.3",
|
|
92
92
|
"jquery": "4.0.0",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"msgpack5": "6.0.2",
|
|
101
101
|
"murmurhash": "2.0.1",
|
|
102
102
|
"nanoid": "3.3.8",
|
|
103
|
-
"nodemailer": "8.0.
|
|
103
|
+
"nodemailer": "8.0.11",
|
|
104
104
|
"pino": "10.3.1",
|
|
105
105
|
"popper.js": "1.16.1",
|
|
106
106
|
"prom-client": "15.1.3",
|
|
@@ -118,6 +118,8 @@
|
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@eslint/js": "10.0.1",
|
|
121
|
+
"acorn": "^8.16.0",
|
|
122
|
+
"acorn-walk": "^8.3.5",
|
|
121
123
|
"chai": "4.3.10",
|
|
122
124
|
"eerawlog": "1.5.3",
|
|
123
125
|
"eslint": "10.4.1",
|
|
@@ -125,9 +127,8 @@
|
|
|
125
127
|
"grunt-cli": "1.5.0",
|
|
126
128
|
"grunt-shell-spawn": "0.5.0",
|
|
127
129
|
"grunt-wait": "0.3.0",
|
|
128
|
-
"jsxgettext": "0.11.0",
|
|
129
130
|
"pino-pretty": "13.0.0",
|
|
130
|
-
"prettier": "3.8.
|
|
131
|
+
"prettier": "3.8.4",
|
|
131
132
|
"resedit": "3.0.2",
|
|
132
133
|
"spdx-satisfies": "6.0.0",
|
|
133
134
|
"supertest": "7.2.2",
|