nothumanallowed 15.1.45 → 15.1.46
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/package.json +1 -1
- package/src/constants.mjs +1 -1
- package/src/services/email-imap.mjs +95 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "15.1.
|
|
3
|
+
"version": "15.1.46",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '15.1.
|
|
8
|
+
export const VERSION = '15.1.46';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -57,13 +57,21 @@ function notifyIdle(accountId, folder) {
|
|
|
57
57
|
for (const h of idleHandlers) { try { h(accountId, folder); } catch {} }
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function createImapClient(label, creds, accountId, secureOverride) {
|
|
60
|
+
function createImapClient(label, creds, accountId, secureOverride, opts = {}) {
|
|
61
61
|
const port = parseInt(creds.imap_port, 10) || 993;
|
|
62
62
|
// Default heuristic: 993/465 use implicit TLS, anything else uses STARTTLS.
|
|
63
63
|
// `secureOverride` lets the auto-fallback path force the opposite mode.
|
|
64
64
|
const isSecure = typeof secureOverride === 'boolean'
|
|
65
65
|
? secureOverride
|
|
66
66
|
: (port === 993 || port === 465);
|
|
67
|
+
// TLS hardening configurable per attempt. The `legacy` flag drops the
|
|
68
|
+
// minimum TLS version to v1.0 — required by some old / self-hosted IMAP
|
|
69
|
+
// servers (mail.dimensione-server.it, postfix on legacy CentOS, etc.)
|
|
70
|
+
// that still refuse TLS 1.2+ ClientHello.
|
|
71
|
+
const tlsOpts = {
|
|
72
|
+
rejectUnauthorized: false,
|
|
73
|
+
...(opts.legacy ? { minVersion: 'TLSv1', maxVersion: 'TLSv1.3' } : {}),
|
|
74
|
+
};
|
|
67
75
|
const client = new ImapFlow({
|
|
68
76
|
host: creds.imap_host,
|
|
69
77
|
port,
|
|
@@ -72,11 +80,13 @@ function createImapClient(label, creds, accountId, secureOverride) {
|
|
|
72
80
|
logger: false,
|
|
73
81
|
clientInfo: { name: 'NHA-Mail', version: '1.0.0' },
|
|
74
82
|
emitLogs: false,
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
// Generous timeouts — first sync from a slow server can take a while
|
|
84
|
+
// before it even responds with the greeting. The previous 10s was too
|
|
85
|
+
// tight for ISP-hosted mailservers with throttling on cold connections.
|
|
86
|
+
connectionTimeout: 30000,
|
|
87
|
+
greetingTimeout: 30000,
|
|
88
|
+
socketTimeout: 120000,
|
|
89
|
+
tls: tlsOpts,
|
|
80
90
|
});
|
|
81
91
|
client.on('error', (err) => {
|
|
82
92
|
console.error(`[email:imap] ${label} error:`, err.message);
|
|
@@ -99,6 +109,10 @@ function _looksLikeTlsMismatch(err) {
|
|
|
99
109
|
// a given server, and ensures the cached `syncClients` entry stays usable.
|
|
100
110
|
const lastGoodSecure = new Map(); // accountId → boolean
|
|
101
111
|
|
|
112
|
+
// Per-account memory of the FULL working profile, not just secure flag.
|
|
113
|
+
// Some servers need legacy TLS — we don't want to re-discover that every sync.
|
|
114
|
+
const lastGoodProfile = new Map(); // accountId → { secure, legacy }
|
|
115
|
+
|
|
102
116
|
export async function getImapClient(accountId, override) {
|
|
103
117
|
const existing = syncClients.get(accountId);
|
|
104
118
|
if (existing?.usable && override === undefined) return existing;
|
|
@@ -108,67 +122,102 @@ export async function getImapClient(accountId, override) {
|
|
|
108
122
|
if (!creds || !creds.imap_host) throw new Error(`No IMAP credentials for account ${accountId}`);
|
|
109
123
|
const label = `sync:${accountId.slice(0, 8)}`;
|
|
110
124
|
|
|
111
|
-
//
|
|
125
|
+
// Build the candidate list — up to 4 strategies, ordered most→least likely:
|
|
126
|
+
// 1. heuristic secure + modern TLS
|
|
127
|
+
// 2. opposite secure + modern TLS
|
|
128
|
+
// 3. heuristic secure + legacy TLS (TLSv1.0+)
|
|
129
|
+
// 4. opposite secure + legacy TLS
|
|
130
|
+
// If a remembered-good profile exists, prepend it so warm syncs are 1 attempt.
|
|
112
131
|
const port = parseInt(creds.imap_port, 10) || 993;
|
|
113
132
|
const heuristicSecure = port === 993 || port === 465;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
let strategies;
|
|
134
|
+
if (typeof override === 'boolean') {
|
|
135
|
+
// Explicit override (used by the outer syncAccount retry): obey it
|
|
136
|
+
// exactly, but still try legacy TLS as a fallback within that mode.
|
|
137
|
+
strategies = [
|
|
138
|
+
{ secure: override, legacy: false, why: 'override-modern' },
|
|
139
|
+
{ secure: override, legacy: true, why: 'override-legacy' },
|
|
140
|
+
];
|
|
141
|
+
} else {
|
|
142
|
+
strategies = [
|
|
143
|
+
{ secure: heuristicSecure, legacy: false, why: 'heuristic-modern' },
|
|
144
|
+
{ secure: !heuristicSecure, legacy: false, why: 'opposite-modern' },
|
|
145
|
+
{ secure: heuristicSecure, legacy: true, why: 'heuristic-legacy' },
|
|
146
|
+
{ secure: !heuristicSecure, legacy: true, why: 'opposite-legacy' },
|
|
147
|
+
];
|
|
148
|
+
const remembered = lastGoodProfile.get(accountId);
|
|
149
|
+
if (remembered) {
|
|
150
|
+
strategies = [
|
|
151
|
+
{ secure: remembered.secure, legacy: !!remembered.legacy, why: 'remembered' },
|
|
152
|
+
...strategies.filter(s => !(s.secure === remembered.secure && s.legacy === !!remembered.legacy)),
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
118
156
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
lastGoodSecure.set(accountId, firstSecure);
|
|
124
|
-
} catch (err) {
|
|
125
|
-
if (_looksLikeTlsMismatch(err) && override === undefined) {
|
|
126
|
-
try { await client.logout(); } catch {}
|
|
127
|
-
const fallbackSecure = !firstSecure;
|
|
128
|
-
console.warn(`[email:imap] ${label} TLS mismatch (${err.message.slice(0, 100)}). Retrying with secure=${fallbackSecure}`);
|
|
129
|
-
client = createImapClient(label, creds, accountId, fallbackSecure);
|
|
157
|
+
const errors = [];
|
|
158
|
+
for (const s of strategies) {
|
|
159
|
+
const client = createImapClient(label, creds, accountId, s.secure, { legacy: s.legacy });
|
|
160
|
+
try {
|
|
130
161
|
await client.connect();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
162
|
+
lastGoodProfile.set(accountId, { secure: s.secure, legacy: s.legacy });
|
|
163
|
+
if (s.why !== 'remembered' && s.why !== 'heuristic-modern') {
|
|
164
|
+
console.warn(`[email:imap] ${label} connected via ${s.why} (secure=${s.secure}, legacy=${s.legacy})`);
|
|
165
|
+
}
|
|
166
|
+
syncClients.set(accountId, client);
|
|
167
|
+
return client;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
errors.push(`${s.why}: ${err.message.slice(0, 120)}`);
|
|
170
|
+
try { await client.logout(); } catch {}
|
|
171
|
+
// If the error is clearly NOT TLS-related (e.g. auth failure, DNS),
|
|
172
|
+
// stop trying — more TLS combos won't help.
|
|
173
|
+
if (!_looksLikeTlsMismatch(err) && !/timeout|hang/i.test(err.message)) {
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
134
176
|
}
|
|
135
177
|
}
|
|
136
|
-
|
|
137
|
-
return client;
|
|
178
|
+
throw new Error(`IMAP connection failed after ${strategies.length} attempts:\n${errors.join('\n')}`);
|
|
138
179
|
}
|
|
139
180
|
|
|
140
181
|
// Dry-run connectivity test used by Settings UI (no DB writes, no persistent
|
|
141
|
-
// client).
|
|
182
|
+
// client). Tries up to 4 TLS-mode combinations and returns the first that
|
|
183
|
+
// works, along with which one. Errors include the full attempt log so the
|
|
184
|
+
// user can paste it back to support if everything failed.
|
|
142
185
|
export async function testImapConnection(creds) {
|
|
143
186
|
if (!creds?.imap_host) throw new Error('imap_host required');
|
|
144
187
|
if (!creds?.username || !creds?.password) throw new Error('Username and password required');
|
|
145
188
|
const port = parseInt(creds.imap_port, 10) || 993;
|
|
146
|
-
const
|
|
147
|
-
|
|
189
|
+
const heuristicSecure = port === 993 || port === 465;
|
|
190
|
+
const strategies = [
|
|
191
|
+
{ secure: heuristicSecure, legacy: false, why: 'heuristic-modern' },
|
|
192
|
+
{ secure: !heuristicSecure, legacy: false, why: 'opposite-modern' },
|
|
193
|
+
{ secure: heuristicSecure, legacy: true, why: 'heuristic-legacy' },
|
|
194
|
+
{ secure: !heuristicSecure, legacy: true, why: 'opposite-legacy' },
|
|
195
|
+
];
|
|
196
|
+
const errors = [];
|
|
197
|
+
for (const s of strategies) {
|
|
198
|
+
const client = createImapClient('test', creds, null, s.secure, { legacy: s.legacy });
|
|
148
199
|
try {
|
|
149
200
|
await client.connect();
|
|
150
201
|
const list = await client.list();
|
|
151
|
-
await client.logout();
|
|
152
|
-
|
|
202
|
+
try { await client.logout(); } catch {}
|
|
203
|
+
const mode = `${s.secure ? 'implicit TLS' : 'STARTTLS'}${s.legacy ? ' (legacy ≥TLSv1.0)' : ''}`;
|
|
204
|
+
return {
|
|
205
|
+
ok: true,
|
|
206
|
+
secure: s.secure,
|
|
207
|
+
legacy: s.legacy,
|
|
208
|
+
folderCount: list.length,
|
|
209
|
+
message: `Connesso con modalità: ${mode}`,
|
|
210
|
+
};
|
|
153
211
|
} catch (err) {
|
|
212
|
+
errors.push(`• ${s.why}: ${err.message.slice(0, 160)}`);
|
|
154
213
|
try { await client.logout(); } catch {}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const firstSecure = port === 993 || port === 465;
|
|
159
|
-
try {
|
|
160
|
-
return await tryConnect(firstSecure);
|
|
161
|
-
} catch (err) {
|
|
162
|
-
if (_looksLikeTlsMismatch(err)) {
|
|
163
|
-
try {
|
|
164
|
-
const r = await tryConnect(!firstSecure);
|
|
165
|
-
return { ...r, message: `Fallback TLS=${!firstSecure} (heuristic was wrong for this server)` };
|
|
166
|
-
} catch (err2) {
|
|
167
|
-
throw new Error(`IMAP test failed (both TLS modes): ${err.message} / ${err2.message}`);
|
|
214
|
+
if (!_looksLikeTlsMismatch(err) && !/timeout|hang/i.test(err.message)) {
|
|
215
|
+
// Hard error (auth / DNS): bail with that exact message.
|
|
216
|
+
throw err;
|
|
168
217
|
}
|
|
169
218
|
}
|
|
170
|
-
throw err;
|
|
171
219
|
}
|
|
220
|
+
throw new Error(`IMAP test failed dopo ${strategies.length} tentativi:\n${errors.join('\n')}`);
|
|
172
221
|
}
|
|
173
222
|
|
|
174
223
|
export async function closeImapClient(accountId) {
|