emailengine-app 2.62.0 → 2.62.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/.ncurc.js +4 -1
- package/CHANGELOG.md +18 -0
- package/data/google-crawlers.json +1 -1
- package/lib/autodetect-imap-settings.js +5 -5
- package/lib/email-client/gmail-client.js +1 -2
- package/lib/email-client/imap-client.js +0 -3
- package/lib/email-client/outlook-client.js +2 -4
- package/lib/oauth/gmail.js +5 -5
- package/lib/oauth/mail-ru.js +5 -5
- package/lib/oauth/outlook.js +5 -5
- package/lib/routes-ui.js +33 -8
- package/lib/schemas.js +14 -1
- package/lib/sub-script.js +2 -2
- package/lib/tools.js +166 -10
- package/lib/ui-routes/admin-config-routes.js +2 -2
- package/package.json +12 -9
- package/sbom.json +1 -1
- package/server.js +25 -2
- package/static/licenses.html +65 -86
- package/translations/messages.pot +41 -41
- package/views/config/network.hbs +45 -0
- package/workers/api.js +7 -3
- package/workers/documents.js +2 -2
- package/workers/export.js +9 -2
- package/workers/imap.js +5 -1
- package/workers/submit.js +8 -1
- package/workers/webhooks.js +9 -2
package/.ncurc.js
CHANGED
|
@@ -19,6 +19,9 @@ module.exports = {
|
|
|
19
19
|
'startbootstrap-sb-admin-2',
|
|
20
20
|
|
|
21
21
|
// Keep joi at version 17.x for hapi-swagger compatibility
|
|
22
|
-
'joi'
|
|
22
|
+
'joi',
|
|
23
|
+
|
|
24
|
+
// @asamuzakjp/css-color >=4.1.2 pulls in @csstools/* v4 which are pure ESM and break pkg bundling
|
|
25
|
+
'@asamuzakjp/css-color'
|
|
23
26
|
]
|
|
24
27
|
};
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.62.2](https://github.com/postalsys/emailengine/compare/v2.62.1...v2.62.2) (2026-02-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* include ESM [@csstools](https://github.com/csstools) packages as pkg assets ([df9f6d2](https://github.com/postalsys/emailengine/commit/df9f6d286b0fc2819edf0bf2f179dc0d6d2ce0d5))
|
|
9
|
+
* pin subdependency to avoid installing unsupported version ([045b8ce](https://github.com/postalsys/emailengine/commit/045b8ce1a28c9cf9eecb26723589c5dcb4a28c21))
|
|
10
|
+
|
|
11
|
+
## [2.62.1](https://github.com/postalsys/emailengine/compare/v2.62.0...v2.62.1) (2026-02-07)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* replace BigInt literal with constructor for gettext parser compatibility ([119ba69](https://github.com/postalsys/emailengine/commit/119ba6919768ff6d22161e6c7884b8020fab37f5))
|
|
17
|
+
* replace spread syntax with Object.assign in tools.js for gettext parser compatibility ([02ecc60](https://github.com/postalsys/emailengine/commit/02ecc60071c2a798eb6aae32ee6cdfe5a43933b2))
|
|
18
|
+
* resolve ESLint 10 lint errors ([7d397f6](https://github.com/postalsys/emailengine/commit/7d397f6d87c87c4a8bb456a52c9db264c7c14243))
|
|
19
|
+
* resolve HTTP proxy feature issues ([750619d](https://github.com/postalsys/emailengine/commit/750619d0145b60706dedec1d39ae94f9a2457ab7))
|
|
20
|
+
|
|
3
21
|
## [2.62.0](https://github.com/postalsys/emailengine/compare/v2.61.5...v2.62.0) (2026-02-06)
|
|
4
22
|
|
|
5
23
|
|
|
@@ -20,7 +20,7 @@ const parseXml = util.promisify((xml, cb) => {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
const { fetch: fetchCmd } = require('undici');
|
|
23
|
-
const {
|
|
23
|
+
const { httpAgent } = require('./tools');
|
|
24
24
|
|
|
25
25
|
const RESOLV_TIMEOUT = 5 * 1000;
|
|
26
26
|
|
|
@@ -191,7 +191,7 @@ async function resolveUsingMozillaDirectory(email, domain, source) {
|
|
|
191
191
|
headers: {
|
|
192
192
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
193
193
|
},
|
|
194
|
-
dispatcher:
|
|
194
|
+
dispatcher: httpAgent.retry
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
if (!res.ok) {
|
|
@@ -216,7 +216,7 @@ async function resolveUsingAutoconfig(email, domain, source) {
|
|
|
216
216
|
headers: {
|
|
217
217
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
218
218
|
},
|
|
219
|
-
dispatcher:
|
|
219
|
+
dispatcher: httpAgent.retry
|
|
220
220
|
});
|
|
221
221
|
|
|
222
222
|
if (!res.ok) {
|
|
@@ -241,7 +241,7 @@ async function resolveUsingWellKnown(email, domain, source) {
|
|
|
241
241
|
headers: {
|
|
242
242
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
243
243
|
},
|
|
244
|
-
dispatcher:
|
|
244
|
+
dispatcher: httpAgent.retry
|
|
245
245
|
});
|
|
246
246
|
|
|
247
247
|
if (!res.ok) {
|
|
@@ -640,7 +640,7 @@ async function resolveUsingAutodiscovery(email, domain, source) {
|
|
|
640
640
|
'Content-type': 'application/xml'
|
|
641
641
|
},
|
|
642
642
|
body,
|
|
643
|
-
dispatcher:
|
|
643
|
+
dispatcher: httpAgent.retry
|
|
644
644
|
});
|
|
645
645
|
|
|
646
646
|
if (!res.ok) {
|
|
@@ -680,6 +680,7 @@ class GmailClient extends BaseClient {
|
|
|
680
680
|
return false;
|
|
681
681
|
}
|
|
682
682
|
requestQuery.labelIds = [label.id];
|
|
683
|
+
path = query.path;
|
|
683
684
|
}
|
|
684
685
|
|
|
685
686
|
let messageList = [];
|
|
@@ -1053,13 +1054,11 @@ class GmailClient extends BaseClient {
|
|
|
1053
1054
|
messages = messages.concat(messageListResult?.messages);
|
|
1054
1055
|
if (messages.length >= maxMessages) {
|
|
1055
1056
|
messages = messages.slice(0, maxMessages);
|
|
1056
|
-
notDone = false;
|
|
1057
1057
|
break;
|
|
1058
1058
|
}
|
|
1059
1059
|
}
|
|
1060
1060
|
|
|
1061
1061
|
if (!messageListResult.nextPageCursor) {
|
|
1062
|
-
notDone = false;
|
|
1063
1062
|
break;
|
|
1064
1063
|
}
|
|
1065
1064
|
cursor = messageListResult.nextPageCursor;
|
|
@@ -520,7 +520,6 @@ class IMAPClient extends BaseClient {
|
|
|
520
520
|
}
|
|
521
521
|
|
|
522
522
|
await mailbox.clear();
|
|
523
|
-
mailbox = false;
|
|
524
523
|
}
|
|
525
524
|
|
|
526
525
|
/**
|
|
@@ -1272,7 +1271,6 @@ class IMAPClient extends BaseClient {
|
|
|
1272
1271
|
if (prevImapClient === this.imapClient) {
|
|
1273
1272
|
this.imapClient = null;
|
|
1274
1273
|
}
|
|
1275
|
-
prevImapClient = null;
|
|
1276
1274
|
}
|
|
1277
1275
|
}
|
|
1278
1276
|
|
|
@@ -2099,7 +2097,6 @@ class IMAPClient extends BaseClient {
|
|
|
2099
2097
|
if (this.mailboxes.has(normalizePath(path))) {
|
|
2100
2098
|
let mailbox = this.mailboxes.get(normalizePath(path));
|
|
2101
2099
|
await mailbox.clear();
|
|
2102
|
-
mailbox = false;
|
|
2103
2100
|
}
|
|
2104
2101
|
|
|
2105
2102
|
return result;
|
|
@@ -549,7 +549,7 @@ class OutlookClient extends BaseClient {
|
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
-
let messages
|
|
552
|
+
let messages;
|
|
553
553
|
let totalMessages;
|
|
554
554
|
|
|
555
555
|
// Execute the message list request
|
|
@@ -910,7 +910,7 @@ class OutlookClient extends BaseClient {
|
|
|
910
910
|
|
|
911
911
|
// Handle label (category) operations
|
|
912
912
|
if (updates.labels) {
|
|
913
|
-
let categories
|
|
913
|
+
let categories;
|
|
914
914
|
|
|
915
915
|
if (updates.labels.set) {
|
|
916
916
|
// Set replaces all categories
|
|
@@ -3831,13 +3831,11 @@ class OutlookClient extends BaseClient {
|
|
|
3831
3831
|
messages.push(...messageListResult.messages);
|
|
3832
3832
|
if (messages.length >= maxMessages) {
|
|
3833
3833
|
messages.length = maxMessages; // Truncate in place
|
|
3834
|
-
notDone = false;
|
|
3835
3834
|
break;
|
|
3836
3835
|
}
|
|
3837
3836
|
}
|
|
3838
3837
|
|
|
3839
3838
|
if (!messageListResult.nextPageCursor) {
|
|
3840
|
-
notDone = false;
|
|
3841
3839
|
break;
|
|
3842
3840
|
}
|
|
3843
3841
|
cursor = messageListResult.nextPageCursor;
|
package/lib/oauth/gmail.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const packageData = require('../../package.json');
|
|
4
|
-
const { formatPartialSecretKey, structuredClone,
|
|
4
|
+
const { formatPartialSecretKey, structuredClone, httpAgent, formatTokenError } = require('../tools');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
|
|
7
7
|
const { fetch: fetchCmd } = require('undici');
|
|
@@ -280,7 +280,7 @@ class GmailOauth {
|
|
|
280
280
|
let res = await fetchCmd(
|
|
281
281
|
requestUrl,
|
|
282
282
|
Object.assign(fetchOpts, {
|
|
283
|
-
dispatcher:
|
|
283
|
+
dispatcher: httpAgent.retry
|
|
284
284
|
})
|
|
285
285
|
);
|
|
286
286
|
|
|
@@ -395,7 +395,7 @@ class GmailOauth {
|
|
|
395
395
|
let res = await fetchCmd(
|
|
396
396
|
requestUrl,
|
|
397
397
|
Object.assign(fetchOpts, {
|
|
398
|
-
dispatcher:
|
|
398
|
+
dispatcher: httpAgent.retry
|
|
399
399
|
})
|
|
400
400
|
);
|
|
401
401
|
|
|
@@ -481,7 +481,7 @@ class GmailOauth {
|
|
|
481
481
|
Authorization: `Bearer ${accessToken}`,
|
|
482
482
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
483
483
|
},
|
|
484
|
-
dispatcher:
|
|
484
|
+
dispatcher: httpAgent.retry
|
|
485
485
|
};
|
|
486
486
|
|
|
487
487
|
if (payload && method !== 'get') {
|
|
@@ -495,7 +495,7 @@ class GmailOauth {
|
|
|
495
495
|
reqData.body = payload;
|
|
496
496
|
if (payload.length > 0) {
|
|
497
497
|
// Non-empty buffers use non-retry dispatcher to prevent ArrayBuffer detachment
|
|
498
|
-
reqData.dispatcher =
|
|
498
|
+
reqData.dispatcher = httpAgent.fetch;
|
|
499
499
|
}
|
|
500
500
|
}
|
|
501
501
|
} else if (payload && method === 'get') {
|
package/lib/oauth/mail-ru.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const packageData = require('../../package.json');
|
|
4
4
|
const { fetch: fetchCmd } = require('undici');
|
|
5
|
-
const { formatPartialSecretKey, structuredClone,
|
|
5
|
+
const { formatPartialSecretKey, structuredClone, httpAgent, formatTokenError } = require('../tools');
|
|
6
6
|
|
|
7
7
|
const MAIL_RU_SCOPES = ['userinfo', 'mail.imap'];
|
|
8
8
|
|
|
@@ -117,7 +117,7 @@ class MailRuOauth {
|
|
|
117
117
|
Authorization: `Basic ${Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')}`
|
|
118
118
|
},
|
|
119
119
|
body: tokenRequest.body,
|
|
120
|
-
dispatcher:
|
|
120
|
+
dispatcher: httpAgent.retry
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
let responseJson;
|
|
@@ -202,7 +202,7 @@ class MailRuOauth {
|
|
|
202
202
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
203
203
|
},
|
|
204
204
|
body: bodyString,
|
|
205
|
-
dispatcher:
|
|
205
|
+
dispatcher: httpAgent.retry
|
|
206
206
|
});
|
|
207
207
|
|
|
208
208
|
let responseJson;
|
|
@@ -271,7 +271,7 @@ class MailRuOauth {
|
|
|
271
271
|
headers: {
|
|
272
272
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
273
273
|
},
|
|
274
|
-
dispatcher:
|
|
274
|
+
dispatcher: httpAgent.retry
|
|
275
275
|
};
|
|
276
276
|
|
|
277
277
|
if (payload) {
|
|
@@ -285,7 +285,7 @@ class MailRuOauth {
|
|
|
285
285
|
reqData.body = payload;
|
|
286
286
|
if (payload.length > 0) {
|
|
287
287
|
// Non-empty buffers use non-retry dispatcher to prevent ArrayBuffer detachment
|
|
288
|
-
reqData.dispatcher =
|
|
288
|
+
reqData.dispatcher = httpAgent.fetch;
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
}
|
package/lib/oauth/outlook.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const packageData = require('../../package.json');
|
|
4
|
-
const { formatPartialSecretKey, structuredClone,
|
|
4
|
+
const { formatPartialSecretKey, structuredClone, httpAgent, formatTokenError } = require('../tools');
|
|
5
5
|
|
|
6
6
|
const { fetch: fetchCmd } = require('undici');
|
|
7
7
|
|
|
@@ -266,7 +266,7 @@ class OutlookOauth {
|
|
|
266
266
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
267
267
|
},
|
|
268
268
|
body: tokenRequest.body,
|
|
269
|
-
dispatcher:
|
|
269
|
+
dispatcher: httpAgent.retry
|
|
270
270
|
});
|
|
271
271
|
|
|
272
272
|
let responseJson;
|
|
@@ -363,7 +363,7 @@ class OutlookOauth {
|
|
|
363
363
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
364
364
|
},
|
|
365
365
|
body: bodyString,
|
|
366
|
-
dispatcher:
|
|
366
|
+
dispatcher: httpAgent.retry
|
|
367
367
|
});
|
|
368
368
|
|
|
369
369
|
let responseJson;
|
|
@@ -446,7 +446,7 @@ class OutlookOauth {
|
|
|
446
446
|
Authorization: `Bearer ${accessToken}`,
|
|
447
447
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
448
448
|
},
|
|
449
|
-
dispatcher:
|
|
449
|
+
dispatcher: httpAgent.retry
|
|
450
450
|
};
|
|
451
451
|
|
|
452
452
|
if (options.headers) {
|
|
@@ -464,7 +464,7 @@ class OutlookOauth {
|
|
|
464
464
|
reqData.body = payload;
|
|
465
465
|
if (payload.length > 0) {
|
|
466
466
|
// Non-empty buffers use non-retry dispatcher to prevent ArrayBuffer detachment
|
|
467
|
-
reqData.dispatcher =
|
|
467
|
+
reqData.dispatcher = httpAgent.fetch;
|
|
468
468
|
}
|
|
469
469
|
}
|
|
470
470
|
} else if (payload && method === 'get') {
|
package/lib/routes-ui.js
CHANGED
|
@@ -24,8 +24,10 @@ const {
|
|
|
24
24
|
getDuration,
|
|
25
25
|
parseSignedFormData,
|
|
26
26
|
hasEnvValue,
|
|
27
|
-
|
|
27
|
+
httpAgent,
|
|
28
|
+
reloadHttpProxyAgent
|
|
28
29
|
} = require('./tools');
|
|
30
|
+
const { parentPort } = require('worker_threads');
|
|
29
31
|
const { updatePublicInterfaces } = require('./utils/network');
|
|
30
32
|
const packageData = require('../package.json');
|
|
31
33
|
const he = require('he');
|
|
@@ -2441,7 +2443,7 @@ return true;`
|
|
|
2441
2443
|
}
|
|
2442
2444
|
}),
|
|
2443
2445
|
headers,
|
|
2444
|
-
dispatcher:
|
|
2446
|
+
dispatcher: httpAgent.retry
|
|
2445
2447
|
});
|
|
2446
2448
|
duration = Date.now() - start;
|
|
2447
2449
|
} catch (err) {
|
|
@@ -3504,7 +3506,7 @@ return true;`
|
|
|
3504
3506
|
url: (await settings.get('serviceUrl')) || ''
|
|
3505
3507
|
}),
|
|
3506
3508
|
headers,
|
|
3507
|
-
dispatcher:
|
|
3509
|
+
dispatcher: httpAgent.retry
|
|
3508
3510
|
});
|
|
3509
3511
|
|
|
3510
3512
|
if (!res.ok) {
|
|
@@ -6781,6 +6783,8 @@ return payload;`
|
|
|
6781
6783
|
let proxyEnabled = await settings.get('proxyEnabled');
|
|
6782
6784
|
let proxyUrl = await settings.get('proxyUrl');
|
|
6783
6785
|
let smtpEhloName = await settings.get('smtpEhloName');
|
|
6786
|
+
let httpProxyEnabled = await settings.get('httpProxyEnabled');
|
|
6787
|
+
let httpProxyUrl = await settings.get('httpProxyUrl');
|
|
6784
6788
|
|
|
6785
6789
|
let localAddresses = [].concat((await settings.get('localAddresses')) || []);
|
|
6786
6790
|
|
|
@@ -6800,7 +6804,9 @@ return payload;`
|
|
|
6800
6804
|
values: {
|
|
6801
6805
|
proxyEnabled,
|
|
6802
6806
|
proxyUrl,
|
|
6803
|
-
smtpEhloName
|
|
6807
|
+
smtpEhloName,
|
|
6808
|
+
httpProxyEnabled,
|
|
6809
|
+
httpProxyUrl
|
|
6804
6810
|
},
|
|
6805
6811
|
|
|
6806
6812
|
addresses: await listPublicInterfaces(localAddresses),
|
|
@@ -6853,10 +6859,26 @@ return payload;`
|
|
|
6853
6859
|
path: '/admin/config/network',
|
|
6854
6860
|
async handler(request, h) {
|
|
6855
6861
|
try {
|
|
6856
|
-
for (let key of [
|
|
6862
|
+
for (let key of [
|
|
6863
|
+
'smtpStrategy',
|
|
6864
|
+
'imapStrategy',
|
|
6865
|
+
'localAddresses',
|
|
6866
|
+
'proxyUrl',
|
|
6867
|
+
'smtpEhloName',
|
|
6868
|
+
'proxyEnabled',
|
|
6869
|
+
'httpProxyEnabled',
|
|
6870
|
+
'httpProxyUrl'
|
|
6871
|
+
]) {
|
|
6857
6872
|
await settings.set(key, request.payload[key]);
|
|
6858
6873
|
}
|
|
6859
6874
|
|
|
6875
|
+
await reloadHttpProxyAgent();
|
|
6876
|
+
|
|
6877
|
+
// Notify other workers about settings change
|
|
6878
|
+
if (parentPort) {
|
|
6879
|
+
parentPort.postMessage({ cmd: 'settings', data: request.payload });
|
|
6880
|
+
}
|
|
6881
|
+
|
|
6860
6882
|
await request.flash({ type: 'info', message: `Configuration updated` });
|
|
6861
6883
|
|
|
6862
6884
|
return h.redirect('/admin/config/network');
|
|
@@ -6940,7 +6962,10 @@ return payload;`
|
|
|
6940
6962
|
|
|
6941
6963
|
proxyUrl: settingsSchema.proxyUrl,
|
|
6942
6964
|
smtpEhloName: settingsSchema.smtpEhloName,
|
|
6943
|
-
proxyEnabled: settingsSchema.proxyEnabled
|
|
6965
|
+
proxyEnabled: settingsSchema.proxyEnabled,
|
|
6966
|
+
|
|
6967
|
+
httpProxyEnabled: settingsSchema.httpProxyEnabled,
|
|
6968
|
+
httpProxyUrl: settingsSchema.httpProxyUrl
|
|
6944
6969
|
})
|
|
6945
6970
|
}
|
|
6946
6971
|
}
|
|
@@ -7475,7 +7500,7 @@ Token: ${JSON.stringify(request.params.token)}`
|
|
|
7475
7500
|
requestor: '@postalsys/emailengine-app'
|
|
7476
7501
|
}),
|
|
7477
7502
|
headers,
|
|
7478
|
-
dispatcher:
|
|
7503
|
+
dispatcher: httpAgent.retry
|
|
7479
7504
|
});
|
|
7480
7505
|
|
|
7481
7506
|
if (!res.ok) {
|
|
@@ -7586,7 +7611,7 @@ ${now}`,
|
|
|
7586
7611
|
let res = await fetchCmd(`${SMTP_TEST_HOST}/test-address/${user}`, {
|
|
7587
7612
|
method: 'get',
|
|
7588
7613
|
headers,
|
|
7589
|
-
dispatcher:
|
|
7614
|
+
dispatcher: httpAgent.retry
|
|
7590
7615
|
});
|
|
7591
7616
|
|
|
7592
7617
|
if (!res.ok) {
|
package/lib/schemas.js
CHANGED
|
@@ -213,12 +213,25 @@ const settingsSchema = {
|
|
|
213
213
|
|
|
214
214
|
proxyEnabled: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').description('Route outbound connections through a proxy server'),
|
|
215
215
|
proxyUrl: Joi.string()
|
|
216
|
-
.uri({ scheme: ['http', 'https', 'socks', 'socks4', 'socks5'], allowRelative: false })
|
|
216
|
+
.uri({ scheme: ['http', 'https', 'socks', 'socks4', 'socks4a', 'socks5'], allowRelative: false })
|
|
217
217
|
.allow('')
|
|
218
218
|
.example('socks5://proxy.example.com:1080')
|
|
219
219
|
.description('Proxy server URL for outbound connections')
|
|
220
220
|
.label('ProxyURL'),
|
|
221
221
|
|
|
222
|
+
/* ────────────── HTTP Proxy ────────────── */
|
|
223
|
+
|
|
224
|
+
httpProxyEnabled: Joi.boolean()
|
|
225
|
+
.truthy('Y', 'true', '1', 'on')
|
|
226
|
+
.falsy('N', 'false', 0, '')
|
|
227
|
+
.description('Route outbound HTTP/HTTPS requests (webhooks, OAuth, API calls) through an HTTP proxy'),
|
|
228
|
+
httpProxyUrl: Joi.string()
|
|
229
|
+
.uri({ scheme: ['http', 'https', 'socks', 'socks4', 'socks4a', 'socks5'], allowRelative: false })
|
|
230
|
+
.allow('')
|
|
231
|
+
.example('http://proxy.example.com:8080')
|
|
232
|
+
.description('HTTP proxy URL for outbound HTTP/HTTPS requests')
|
|
233
|
+
.label('HttpProxyURL'),
|
|
234
|
+
|
|
222
235
|
/* ────────────── SMTP ────────────── */
|
|
223
236
|
|
|
224
237
|
smtpEhloName: Joi.string()
|
package/lib/sub-script.js
CHANGED
|
@@ -4,7 +4,7 @@ const crypto = require('crypto');
|
|
|
4
4
|
const vm = require('vm');
|
|
5
5
|
const logger = require('./logger');
|
|
6
6
|
const settings = require('./settings');
|
|
7
|
-
const {
|
|
7
|
+
const { httpAgent } = require('./tools');
|
|
8
8
|
|
|
9
9
|
const { SUBSCRIPT_RUNTIME_TIMEOUT } = require('./consts');
|
|
10
10
|
|
|
@@ -46,7 +46,7 @@ const wrappedFetch = (...args) => {
|
|
|
46
46
|
opts = args[1];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
return fetchCmd(args[0], Object.assign({}, opts, { dispatcher:
|
|
49
|
+
return fetchCmd(args[0], Object.assign({}, opts, { dispatcher: httpAgent.retry }));
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
class SubScript {
|
package/lib/tools.js
CHANGED
|
@@ -45,20 +45,168 @@ const bullmqPackage = require('bullmq/package.json');
|
|
|
45
45
|
// Network utilities - imported for internal use only
|
|
46
46
|
const { getLocalAddress } = require('./utils/network');
|
|
47
47
|
|
|
48
|
-
const { fetch: fetchCmd, Agent, RetryAgent } = require('undici');
|
|
48
|
+
const { fetch: fetchCmd, Agent, RetryAgent, ProxyAgent } = require('undici');
|
|
49
|
+
const tls = require('tls');
|
|
50
|
+
const { SocksClient } = require('socks');
|
|
49
51
|
|
|
50
|
-
const
|
|
52
|
+
const AGENT_OPTS = {
|
|
51
53
|
strictContentLength: false,
|
|
52
54
|
connectTimeout: Math.min(30000, URL_FETCH_TIMEOUT), // up to 30s for connection
|
|
53
55
|
headersTimeout: URL_FETCH_TIMEOUT, // Full timeout (90s)
|
|
54
56
|
bodyTimeout: URL_FETCH_TIMEOUT // Full timeout (90s)
|
|
55
|
-
}
|
|
57
|
+
};
|
|
56
58
|
|
|
57
|
-
const
|
|
59
|
+
const RETRY_OPTS = {
|
|
58
60
|
maxRetries: URL_FETCH_RETRY_MAX,
|
|
59
61
|
methods: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
|
|
60
62
|
statusCodes: [429] // do not retry 5xx errors
|
|
61
|
-
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Shared mutable object -- consumers import the object reference and access
|
|
66
|
+
// .fetch / .retry at call time, so replacing properties here is visible everywhere.
|
|
67
|
+
const httpAgent = {
|
|
68
|
+
fetch: new Agent(AGENT_OPTS),
|
|
69
|
+
retry: null
|
|
70
|
+
};
|
|
71
|
+
httpAgent.retry = new RetryAgent(httpAgent.fetch, RETRY_OPTS);
|
|
72
|
+
|
|
73
|
+
// Backward-compat aliases (read from httpAgent at call time via getter)
|
|
74
|
+
// backward-compat aliases are defined as getters on module.exports below
|
|
75
|
+
|
|
76
|
+
const SOCKS4_PROTOCOLS = new Set(['socks4:', 'socks4a:']);
|
|
77
|
+
|
|
78
|
+
function createSocksAgent(proxyUrl, agentOpts) {
|
|
79
|
+
let parsed = new URL(proxyUrl);
|
|
80
|
+
let proxyType = SOCKS4_PROTOCOLS.has(parsed.protocol) ? 4 : 5;
|
|
81
|
+
let proxyHost = parsed.hostname;
|
|
82
|
+
let proxyPort = Number(parsed.port) || 1080;
|
|
83
|
+
|
|
84
|
+
let proxy = {
|
|
85
|
+
host: proxyHost,
|
|
86
|
+
port: proxyPort,
|
|
87
|
+
type: proxyType
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (parsed.username) {
|
|
91
|
+
proxy.userId = decodeURIComponent(parsed.username);
|
|
92
|
+
proxy.password = parsed.password ? decodeURIComponent(parsed.password) : '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return new Agent(
|
|
96
|
+
Object.assign({}, agentOpts, {
|
|
97
|
+
connect: async opts => {
|
|
98
|
+
let { hostname, port, protocol } = opts;
|
|
99
|
+
let useTls = protocol === 'https:';
|
|
100
|
+
|
|
101
|
+
let info = await SocksClient.createConnection({
|
|
102
|
+
proxy,
|
|
103
|
+
command: 'connect',
|
|
104
|
+
destination: {
|
|
105
|
+
host: hostname,
|
|
106
|
+
port: Number(port) || (useTls ? 443 : 80)
|
|
107
|
+
},
|
|
108
|
+
timeout: agentOpts.connectTimeout || 30000
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
let socket = info.socket;
|
|
112
|
+
|
|
113
|
+
if (useTls) {
|
|
114
|
+
let rawSocket = socket;
|
|
115
|
+
socket = tls.connect(Object.assign({ socket: rawSocket, servername: hostname }, TLS_DEFAULTS));
|
|
116
|
+
try {
|
|
117
|
+
await new Promise((resolve, reject) => {
|
|
118
|
+
socket.once('secureConnect', resolve);
|
|
119
|
+
socket.once('error', reject);
|
|
120
|
+
});
|
|
121
|
+
} catch (err) {
|
|
122
|
+
socket.destroy();
|
|
123
|
+
rawSocket.destroy();
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return socket;
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let _reloadPromise = null;
|
|
135
|
+
|
|
136
|
+
async function reloadHttpProxyAgent() {
|
|
137
|
+
if (_reloadPromise) {
|
|
138
|
+
return _reloadPromise;
|
|
139
|
+
}
|
|
140
|
+
_reloadPromise = _doReloadHttpProxyAgent();
|
|
141
|
+
try {
|
|
142
|
+
return await _reloadPromise;
|
|
143
|
+
} finally {
|
|
144
|
+
_reloadPromise = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function _doReloadHttpProxyAgent() {
|
|
149
|
+
let enabled, proxyUrl;
|
|
150
|
+
try {
|
|
151
|
+
enabled = await settings.get('httpProxyEnabled');
|
|
152
|
+
proxyUrl = await settings.get('httpProxyUrl');
|
|
153
|
+
} catch (err) {
|
|
154
|
+
logger.error({ msg: 'Failed to read HTTP proxy settings', err });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Environment variable overrides
|
|
159
|
+
let envEnabled = process.env.EENGINE_HTTP_PROXY_ENABLED;
|
|
160
|
+
let envUrl = process.env.EENGINE_HTTP_PROXY_URL;
|
|
161
|
+
if (typeof envUrl === 'string' && envUrl) {
|
|
162
|
+
proxyUrl = envUrl;
|
|
163
|
+
}
|
|
164
|
+
if (typeof envEnabled === 'string' && envEnabled) {
|
|
165
|
+
enabled = /^(true|1|y|on|yes)$/i.test(envEnabled);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let oldFetch = httpAgent.fetch;
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
if (enabled && proxyUrl) {
|
|
172
|
+
let parsed = new URL(proxyUrl);
|
|
173
|
+
let scheme = parsed.protocol.replace(/:$/, '').toLowerCase();
|
|
174
|
+
|
|
175
|
+
if (['socks', 'socks4', 'socks4a', 'socks5'].includes(scheme)) {
|
|
176
|
+
httpAgent.fetch = createSocksAgent(proxyUrl, AGENT_OPTS);
|
|
177
|
+
} else {
|
|
178
|
+
httpAgent.fetch = new ProxyAgent(Object.assign({}, AGENT_OPTS, { uri: proxyUrl }));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Sanitize credentials for logging
|
|
182
|
+
if (parsed.username || parsed.password) {
|
|
183
|
+
parsed.username = '';
|
|
184
|
+
parsed.password = '';
|
|
185
|
+
}
|
|
186
|
+
proxyUrl = parsed.toString();
|
|
187
|
+
} else {
|
|
188
|
+
httpAgent.fetch = new Agent(AGENT_OPTS);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
httpAgent.retry = new RetryAgent(httpAgent.fetch, RETRY_OPTS);
|
|
192
|
+
|
|
193
|
+
logger.info({
|
|
194
|
+
msg: 'HTTP proxy agent reloaded',
|
|
195
|
+
httpProxyEnabled: !!enabled,
|
|
196
|
+
httpProxyUrl: enabled && proxyUrl ? proxyUrl : null
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (oldFetch && oldFetch !== httpAgent.fetch) {
|
|
200
|
+
try {
|
|
201
|
+
await oldFetch.close();
|
|
202
|
+
} catch (err) {
|
|
203
|
+
// ignore close errors
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch (err) {
|
|
207
|
+
logger.error({ msg: 'Failed to create HTTP proxy agent, keeping existing agent', err });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
62
210
|
|
|
63
211
|
class LRUCache extends Map {
|
|
64
212
|
constructor(maxSize = 1000) {
|
|
@@ -364,7 +512,7 @@ module.exports = {
|
|
|
364
512
|
parsed.searchParams.set('account', account);
|
|
365
513
|
parsed.searchParams.set('proto', proto);
|
|
366
514
|
|
|
367
|
-
let authResponse = await fetchCmd(parsed.toString(), { method: 'GET', headers, dispatcher:
|
|
515
|
+
let authResponse = await fetchCmd(parsed.toString(), { method: 'GET', headers, dispatcher: httpAgent.retry });
|
|
368
516
|
if (!authResponse.ok) {
|
|
369
517
|
throw new Error(`Invalid response: ${authResponse.status} ${authResponse.statusText}`);
|
|
370
518
|
}
|
|
@@ -1351,7 +1499,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
|
|
|
1351
1499
|
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
1352
1500
|
};
|
|
1353
1501
|
|
|
1354
|
-
let releaseResponse = await fetchCmd(releaseUrl, { method: 'GET', headers, dispatcher:
|
|
1502
|
+
let releaseResponse = await fetchCmd(releaseUrl, { method: 'GET', headers, dispatcher: httpAgent.retry });
|
|
1355
1503
|
if (!releaseResponse.ok) {
|
|
1356
1504
|
let err = new Error(`Failed loading release data`);
|
|
1357
1505
|
err.response = {
|
|
@@ -1399,7 +1547,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
|
|
|
1399
1547
|
|
|
1400
1548
|
validUidValidity(value) {
|
|
1401
1549
|
if (typeof value === 'bigint') {
|
|
1402
|
-
return value >
|
|
1550
|
+
return value > BigInt(0);
|
|
1403
1551
|
}
|
|
1404
1552
|
|
|
1405
1553
|
if (typeof value === 'number') {
|
|
@@ -1800,8 +1948,16 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
|
|
|
1800
1948
|
return urlObj.href;
|
|
1801
1949
|
},
|
|
1802
1950
|
|
|
1803
|
-
|
|
1804
|
-
|
|
1951
|
+
httpAgent,
|
|
1952
|
+
reloadHttpProxyAgent,
|
|
1953
|
+
createSocksAgent,
|
|
1954
|
+
|
|
1955
|
+
get fetchAgent() {
|
|
1956
|
+
return httpAgent.fetch;
|
|
1957
|
+
},
|
|
1958
|
+
get retryAgent() {
|
|
1959
|
+
return httpAgent.retry;
|
|
1960
|
+
},
|
|
1805
1961
|
|
|
1806
1962
|
LRUCache,
|
|
1807
1963
|
|