nothumanallowed 15.1.44 → 15.1.45
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 +54 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "15.1.
|
|
3
|
+
"version": "15.1.45",
|
|
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.45';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -85,37 +85,50 @@ function createImapClient(label, creds, accountId, secureOverride) {
|
|
|
85
85
|
return client;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// Match the most common "wrong TLS mode" failure modes from ImapFlow
|
|
88
|
+
// Match the most common "wrong TLS mode" failure modes from ImapFlow / node
|
|
89
|
+
// tls. Errors can surface as cleartext OpenSSL output ("wrong version
|
|
90
|
+
// number"), as ImapFlow strings ("Failed to receive greeting"), or as plain
|
|
91
|
+
// socket errors when the server closed the connection mid-handshake.
|
|
89
92
|
function _looksLikeTlsMismatch(err) {
|
|
90
93
|
const msg = (err && err.message ? err.message : String(err)).toLowerCase();
|
|
91
|
-
return /greeting|tls|ssl|wrong
|
|
94
|
+
return /(greeting|tls|ssl|wrong\s*version|version\s*number|protocol|enotconn|econnreset|epipe|handshake|record_header|tlsany|alert|cert|disconnected|connection\s*closed)/i.test(msg);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
// Per-account memory of which TLS mode worked last time. This avoids paying
|
|
98
|
+
// the fallback cost on every sync once we've discovered the correct mode for
|
|
99
|
+
// a given server, and ensures the cached `syncClients` entry stays usable.
|
|
100
|
+
const lastGoodSecure = new Map(); // accountId → boolean
|
|
101
|
+
|
|
102
|
+
export async function getImapClient(accountId, override) {
|
|
95
103
|
const existing = syncClients.get(accountId);
|
|
96
|
-
if (existing?.usable) return existing;
|
|
104
|
+
if (existing?.usable && override === undefined) return existing;
|
|
97
105
|
if (existing) { syncClients.delete(accountId); try { await existing.logout(); } catch {} }
|
|
98
106
|
|
|
99
107
|
const creds = getAccountCredentials(accountId);
|
|
100
108
|
if (!creds || !creds.imap_host) throw new Error(`No IMAP credentials for account ${accountId}`);
|
|
101
109
|
const label = `sync:${accountId.slice(0, 8)}`;
|
|
102
110
|
|
|
103
|
-
//
|
|
104
|
-
|
|
111
|
+
// Pick initial TLS mode: explicit override > remembered-good > port heuristic.
|
|
112
|
+
const port = parseInt(creds.imap_port, 10) || 993;
|
|
113
|
+
const heuristicSecure = port === 993 || port === 465;
|
|
114
|
+
const remembered = lastGoodSecure.get(accountId);
|
|
115
|
+
const firstSecure = (typeof override === 'boolean')
|
|
116
|
+
? override
|
|
117
|
+
: (typeof remembered === 'boolean' ? remembered : heuristicSecure);
|
|
118
|
+
|
|
119
|
+
// First attempt.
|
|
120
|
+
let client = createImapClient(label, creds, accountId, firstSecure);
|
|
105
121
|
try {
|
|
106
122
|
await client.connect();
|
|
123
|
+
lastGoodSecure.set(accountId, firstSecure);
|
|
107
124
|
} catch (err) {
|
|
108
|
-
|
|
109
|
-
// with the opposite mode before bubbling the error up to the user. The
|
|
110
|
-
// most common case is port 993 misclassified (or a non-standard port
|
|
111
|
-
// with implicit TLS) — the second try usually succeeds.
|
|
112
|
-
if (_looksLikeTlsMismatch(err)) {
|
|
125
|
+
if (_looksLikeTlsMismatch(err) && override === undefined) {
|
|
113
126
|
try { await client.logout(); } catch {}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
console.warn(`[email:imap] ${label} TLS mismatch (${err.message}). Retrying with secure=${fallbackSecure}`);
|
|
127
|
+
const fallbackSecure = !firstSecure;
|
|
128
|
+
console.warn(`[email:imap] ${label} TLS mismatch (${err.message.slice(0, 100)}). Retrying with secure=${fallbackSecure}`);
|
|
117
129
|
client = createImapClient(label, creds, accountId, fallbackSecure);
|
|
118
130
|
await client.connect();
|
|
131
|
+
lastGoodSecure.set(accountId, fallbackSecure);
|
|
119
132
|
} else {
|
|
120
133
|
throw err;
|
|
121
134
|
}
|
|
@@ -392,12 +405,25 @@ export async function syncFolder(accountId, folderPath, fullResync, limitMessage
|
|
|
392
405
|
}
|
|
393
406
|
}
|
|
394
407
|
|
|
395
|
-
//
|
|
396
|
-
|
|
408
|
+
// Download EVERY message on the server by default. Previously capped at 200
|
|
409
|
+
// to keep first-sync fast, but that surprised users who expected an Outlook-
|
|
410
|
+
// style "give me my whole mailbox". The sync still streams messages
|
|
411
|
+
// incrementally (headers → bodies) so the UI keeps updating, and never
|
|
412
|
+
// modifies the server — only the local SQLite mirror.
|
|
413
|
+
const FIRST_SYNC_LIMIT = 0;
|
|
397
414
|
|
|
398
415
|
export async function syncAccount(accountId, opts = {}) {
|
|
399
416
|
setSyncStatus(accountId, 'syncing', null);
|
|
417
|
+
// _retried is set by the recursion below — it forces the *opposite* TLS
|
|
418
|
+
// mode on the second attempt so we never spin twice on the same setting.
|
|
419
|
+
const tlsOverride = (typeof opts._forceSecure === 'boolean') ? opts._forceSecure : undefined;
|
|
400
420
|
try {
|
|
421
|
+
// Prime the connection with the (optional) explicit TLS mode so any
|
|
422
|
+
// cached `syncClients` entry uses it for the whole sync.
|
|
423
|
+
if (tlsOverride !== undefined) {
|
|
424
|
+
await closeImapClient(accountId);
|
|
425
|
+
await getImapClient(accountId, tlsOverride);
|
|
426
|
+
}
|
|
401
427
|
const folders = await listImapFolders(accountId);
|
|
402
428
|
const priority = ['inbox', 'sent'];
|
|
403
429
|
const toSync = [
|
|
@@ -408,17 +434,28 @@ export async function syncAccount(accountId, opts = {}) {
|
|
|
408
434
|
let totalSynced = 0;
|
|
409
435
|
for (const f of toSync) {
|
|
410
436
|
try {
|
|
411
|
-
const limit = opts.full ?
|
|
437
|
+
const limit = opts.full === false ? 200 : FIRST_SYNC_LIMIT;
|
|
412
438
|
const result = await syncFolder(accountId, f.path, false, limit, f.folderType);
|
|
413
439
|
totalSynced += result.synced;
|
|
414
440
|
console.log(`[email:sync] ${f.path}: ${result.synced} new messages (total on server: ${result.total})`);
|
|
415
441
|
} catch (err) {
|
|
416
442
|
console.warn(`[email:imap] Sync folder ${f.path} failed:`, err.message);
|
|
443
|
+
// Per-folder TLS mismatch propagates up so the outer catch can
|
|
444
|
+
// restart the whole sync with the opposite secure mode.
|
|
445
|
+
if (_looksLikeTlsMismatch(err) && opts._retried !== true) throw err;
|
|
417
446
|
}
|
|
418
447
|
}
|
|
419
448
|
setSyncStatus(accountId, 'idle', null);
|
|
420
449
|
return { synced: totalSynced };
|
|
421
450
|
} catch (err) {
|
|
451
|
+
if (_looksLikeTlsMismatch(err) && opts._retried !== true) {
|
|
452
|
+
const port = parseInt(getAccountCredentials(accountId)?.imap_port, 10) || 993;
|
|
453
|
+
const heuristic = port === 993 || port === 465;
|
|
454
|
+
const opposite = (typeof tlsOverride === 'boolean') ? !tlsOverride : !heuristic;
|
|
455
|
+
console.warn(`[email:imap] sync ${accountId.slice(0, 8)} TLS mismatch — retrying entire sync with secure=${opposite}`);
|
|
456
|
+
await closeImapClient(accountId);
|
|
457
|
+
return syncAccount(accountId, { ...opts, _retried: true, _forceSecure: opposite });
|
|
458
|
+
}
|
|
422
459
|
setSyncStatus(accountId, 'error', err.message);
|
|
423
460
|
throw err;
|
|
424
461
|
}
|