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 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
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "creationTime": "2026-02-04T15:46:47.000000",
2
+ "creationTime": "2026-02-06T15:46:55.000000",
3
3
  "prefixes": [
4
4
  {
5
5
  "ipv6Prefix": "2001:4860:4801:2008::/64"
@@ -20,7 +20,7 @@ const parseXml = util.promisify((xml, cb) => {
20
20
  });
21
21
 
22
22
  const { fetch: fetchCmd } = require('undici');
23
- const { retryAgent } = require('./tools');
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: retryAgent
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: retryAgent
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: retryAgent
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: retryAgent
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;
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const packageData = require('../../package.json');
4
- const { formatPartialSecretKey, structuredClone, fetchAgent, retryAgent, formatTokenError } = require('../tools');
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: retryAgent
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: retryAgent
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: retryAgent
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 = fetchAgent;
498
+ reqData.dispatcher = httpAgent.fetch;
499
499
  }
500
500
  }
501
501
  } else if (payload && method === 'get') {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const packageData = require('../../package.json');
4
4
  const { fetch: fetchCmd } = require('undici');
5
- const { formatPartialSecretKey, structuredClone, fetchAgent, retryAgent, formatTokenError } = require('../tools');
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: retryAgent
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: retryAgent
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: retryAgent
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 = fetchAgent;
288
+ reqData.dispatcher = httpAgent.fetch;
289
289
  }
290
290
  }
291
291
  }
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const packageData = require('../../package.json');
4
- const { formatPartialSecretKey, structuredClone, fetchAgent, retryAgent, formatTokenError } = require('../tools');
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: retryAgent
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: retryAgent
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: retryAgent
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 = fetchAgent;
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
- retryAgent
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: retryAgent
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: retryAgent
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 ['smtpStrategy', 'imapStrategy', 'localAddresses', 'proxyUrl', 'smtpEhloName', 'proxyEnabled']) {
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: retryAgent
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: retryAgent
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 { retryAgent } = require('./tools');
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: retryAgent }));
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 fetchAgent = new Agent({
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 retryAgent = new RetryAgent(fetchAgent, {
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: retryAgent });
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: retryAgent });
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 > 0n;
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
- fetchAgent,
1804
- retryAgent,
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