@bobfrankston/mailx-imap 0.1.65 → 0.1.67
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/index.js +60 -8
- package/package.json +9 -9
package/index.js
CHANGED
|
@@ -1238,7 +1238,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1238
1238
|
for (const uid of qr.vanishedUids) {
|
|
1239
1239
|
const env = this.db.getMessageByUid(accountId, uid, folderId);
|
|
1240
1240
|
if (env) {
|
|
1241
|
-
this.db.deleteMessage(accountId, uid, "server VANISHED via QRESYNC", "mailx-imap syncFolder/qresync");
|
|
1241
|
+
this.db.deleteMessage(accountId, folderId, uid, "server VANISHED via QRESYNC", "mailx-imap syncFolder/qresync");
|
|
1242
1242
|
vanishedApplied++;
|
|
1243
1243
|
}
|
|
1244
1244
|
}
|
|
@@ -2078,7 +2078,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2078
2078
|
const tag = env ? `msgid=${env.messageId || "?"} subj="${(env.subject || "").slice(0, 60)}"` : "unknown";
|
|
2079
2079
|
console.log(` [reconcile-delete] ${accountId}/${folder.path} uid=${uid} ${tag}`);
|
|
2080
2080
|
this.unlinkBodyFile(accountId, uid, folder.id).catch(() => { });
|
|
2081
|
-
this.db.deleteMessage(accountId, uid, "Gmail-API reconcile: server list missing this UID", `mailx-imap Gmail reconcile (${folder.path})`);
|
|
2081
|
+
this.db.deleteMessage(accountId, folder.id, uid, "Gmail-API reconcile: server list missing this UID", `mailx-imap Gmail reconcile (${folder.path})`);
|
|
2082
2082
|
}
|
|
2083
2083
|
if (toDelete.length > 0)
|
|
2084
2084
|
console.log(` [api] ${accountId}/${folder.path}: ${toDelete.length} deleted`);
|
|
@@ -2410,6 +2410,26 @@ export class ImapManager extends EventEmitter {
|
|
|
2410
2410
|
this.syncIntervals.set(`quick:${accountId}`, timer);
|
|
2411
2411
|
console.log(` [periodic] ${accountId}: STATUS check every ${interval / 1000}s (${isGmail ? "API" : "IMAP+IDLE"})`);
|
|
2412
2412
|
}
|
|
2413
|
+
// DEADMAN: IDLE silently dropped on 2026-05-27 at 10:07 (empty
|
|
2414
|
+
// error message swallowed the cause) and 90 minutes of new mail
|
|
2415
|
+
// went undelivered until manual reload. Every 60s, check that an
|
|
2416
|
+
// IDLE watcher exists for every IMAP-path account; if not, kick
|
|
2417
|
+
// startWatching() to re-establish. startWatching itself is
|
|
2418
|
+
// idempotent (the `watchers.has(accountId) continue` guard means
|
|
2419
|
+
// accounts with healthy IDLE are skipped). One log line per
|
|
2420
|
+
// detected gap so a chronically failing account is obvious in
|
|
2421
|
+
// the log.
|
|
2422
|
+
const deadmanInterval = setInterval(() => {
|
|
2423
|
+
for (const [accountId] of this.configs) {
|
|
2424
|
+
if (this.isGmailAccount(accountId))
|
|
2425
|
+
continue; // Gmail = API, no IDLE
|
|
2426
|
+
if (this.watchers.has(accountId))
|
|
2427
|
+
continue;
|
|
2428
|
+
console.log(` [idle-deadman] ${accountId}: no IDLE watcher — attempting restart`);
|
|
2429
|
+
this.startWatching().catch(e => console.error(` [idle-deadman] ${accountId} restart failed: ${e?.message || e}`));
|
|
2430
|
+
}
|
|
2431
|
+
}, 60_000);
|
|
2432
|
+
this.syncIntervals.set("idle-deadman", deadmanInterval);
|
|
2413
2433
|
// Sync actions (sends + flags/deletes/moves) every 30 seconds — skip during active sync
|
|
2414
2434
|
const actionsInterval = setInterval(async () => {
|
|
2415
2435
|
if (this.syncing)
|
|
@@ -2528,7 +2548,24 @@ export class ImapManager extends EventEmitter {
|
|
|
2528
2548
|
console.log(` [idle] Watching INBOX for ${accountId}${useNotify ? " (+NOTIFY personal mailboxes)" : ""}`);
|
|
2529
2549
|
}
|
|
2530
2550
|
catch (e) {
|
|
2531
|
-
|
|
2551
|
+
// Defensive: same empty-message bug class as the transport
|
|
2552
|
+
// path. `Failed to watch bobma: ` with nothing after the
|
|
2553
|
+
// colon hid 86 minutes of IDLE outage on 2026-05-27 — new
|
|
2554
|
+
// messages arrived in Thunderbird but mailx never noticed
|
|
2555
|
+
// because IDLE silently died and the log gave no clue why.
|
|
2556
|
+
const parts = [];
|
|
2557
|
+
if (e?.message)
|
|
2558
|
+
parts.push(String(e.message));
|
|
2559
|
+
if (e?.code)
|
|
2560
|
+
parts.push(`code=${e.code}`);
|
|
2561
|
+
if (e?.errno !== undefined)
|
|
2562
|
+
parts.push(`errno=${e.errno}`);
|
|
2563
|
+
if (e?.syscall)
|
|
2564
|
+
parts.push(`syscall=${e.syscall}`);
|
|
2565
|
+
const desc = parts.length > 0
|
|
2566
|
+
? parts.join(" ")
|
|
2567
|
+
: `<no message> ${typeof e} ${e?.constructor?.name || ""}`.trim();
|
|
2568
|
+
console.error(` [idle] Failed to watch ${accountId}: ${desc}`);
|
|
2532
2569
|
}
|
|
2533
2570
|
}
|
|
2534
2571
|
}
|
|
@@ -2595,7 +2632,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2595
2632
|
const tag = env.messageId ? `msgid=${env.messageId} subj="${(env.subject || "").slice(0, 60)}"` : "no-msgid";
|
|
2596
2633
|
console.log(` [reconcile-delete] ${accountId}/${folderPath} uid=${uid} ${tag} (legacy path)`);
|
|
2597
2634
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
|
|
2598
|
-
this.db.deleteMessage(accountId, uid, "reconcile: server missing this UID after grace (legacy)", `mailx-imap deferred reconcile (${folderPath})`);
|
|
2635
|
+
this.db.deleteMessage(accountId, folderId, uid, "reconcile: server missing this UID after grace (legacy)", `mailx-imap deferred reconcile (${folderPath})`);
|
|
2599
2636
|
}
|
|
2600
2637
|
this.db.recalcFolderCounts(folderId);
|
|
2601
2638
|
this.emit("folderCountsChanged", accountId, {});
|
|
@@ -2634,7 +2671,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2634
2671
|
const envelope = this.db.getMessageByUid(accountId, uid, folderId);
|
|
2635
2672
|
let storedPath = envelope?.bodyPath || "";
|
|
2636
2673
|
if (!storedPath)
|
|
2637
|
-
storedPath = this.db.getMessageBodyPath(accountId, uid) || "";
|
|
2674
|
+
storedPath = this.db.getMessageBodyPath(accountId, uid, folderId) || "";
|
|
2638
2675
|
if (storedPath && await this.bodyStore.hasByPath(storedPath)) {
|
|
2639
2676
|
return this.bodyStore.readByPath(storedPath);
|
|
2640
2677
|
}
|
|
@@ -3030,7 +3067,22 @@ export class ImapManager extends EventEmitter {
|
|
|
3030
3067
|
this.clearFolderErrors(accountId, folder.path);
|
|
3031
3068
|
}
|
|
3032
3069
|
catch (e) {
|
|
3033
|
-
|
|
3070
|
+
// Build a description that never collapses to "".
|
|
3071
|
+
// Even with the iflow transport fix below, defense
|
|
3072
|
+
// in depth: some error paths produce objects whose
|
|
3073
|
+
// .message is empty, or pass non-Error values.
|
|
3074
|
+
const parts = [];
|
|
3075
|
+
if (e?.message)
|
|
3076
|
+
parts.push(String(e.message));
|
|
3077
|
+
if (e?.code)
|
|
3078
|
+
parts.push(`code=${e.code}`);
|
|
3079
|
+
if (e?.errno !== undefined)
|
|
3080
|
+
parts.push(`errno=${e.errno}`);
|
|
3081
|
+
if (e?.syscall)
|
|
3082
|
+
parts.push(`syscall=${e.syscall}`);
|
|
3083
|
+
const msg = parts.length > 0
|
|
3084
|
+
? parts.join(" ")
|
|
3085
|
+
: `<no message> ${typeof e} ${e?.constructor?.name || ""}`.trim();
|
|
3034
3086
|
console.error(` [prefetch] ${accountId} folder ${folder.path} chunk ${chunkStart / PREFETCH_CHUNK_SIZE}: batch fetch failed: ${msg}`);
|
|
3035
3087
|
counters.errors++;
|
|
3036
3088
|
this.recordFolderError(accountId, folder.path);
|
|
@@ -3162,7 +3214,7 @@ export class ImapManager extends EventEmitter {
|
|
|
3162
3214
|
if (!msg)
|
|
3163
3215
|
throw new Error(`Message UID ${uid} not found in ${fromFolder.path}`);
|
|
3164
3216
|
await sourceClient.moveMessageToServer(msg, fromFolder.path, targetClient, toFolder.path);
|
|
3165
|
-
this.db.deleteMessage(fromAccountId, uid, `cross-account move to ${toAccountId}/${toFolder.path}`, "mailx-imap moveBetweenAccounts");
|
|
3217
|
+
this.db.deleteMessage(fromAccountId, fromFolder.id, uid, `cross-account move to ${toAccountId}/${toFolder.path}`, "mailx-imap moveBetweenAccounts");
|
|
3166
3218
|
console.log(` Cross-account move: ${fromAccountId}/${fromFolder.path} UID ${uid} → ${toAccountId}/${toFolder.path}`);
|
|
3167
3219
|
});
|
|
3168
3220
|
});
|
|
@@ -3509,7 +3561,7 @@ export class ImapManager extends EventEmitter {
|
|
|
3509
3561
|
const existing = this.db.getMessageByUid(accountId, draftUid, drafts.id);
|
|
3510
3562
|
if (existing) {
|
|
3511
3563
|
this.unlinkBodyFile(accountId, draftUid, drafts.id).catch(() => { });
|
|
3512
|
-
this.db.deleteMessage(accountId, draftUid, "user sent the message (draft cleanup)", "mailx-imap deleteDraft (local)");
|
|
3564
|
+
this.db.deleteMessage(accountId, drafts.id, draftUid, "user sent the message (draft cleanup)", "mailx-imap deleteDraft (local)");
|
|
3513
3565
|
localDeletedUid = draftUid;
|
|
3514
3566
|
}
|
|
3515
3567
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.67",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/mailx-types": "^0.1.18",
|
|
13
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
14
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
15
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
13
|
+
"@bobfrankston/mailx-settings": "^0.1.25",
|
|
14
|
+
"@bobfrankston/mailx-store": "^0.1.39",
|
|
15
|
+
"@bobfrankston/iflow-direct": "^0.1.51",
|
|
16
16
|
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
17
17
|
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
18
18
|
"@bobfrankston/mailx-sync": "^0.1.19",
|
|
19
|
-
"@bobfrankston/oauthsupport": "^1.0.
|
|
19
|
+
"@bobfrankston/oauthsupport": "^1.0.29"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
".transformedSnapshot": {
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bobfrankston/mailx-types": "^0.1.18",
|
|
41
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
42
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
43
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
41
|
+
"@bobfrankston/mailx-settings": "^0.1.25",
|
|
42
|
+
"@bobfrankston/mailx-store": "^0.1.39",
|
|
43
|
+
"@bobfrankston/iflow-direct": "^0.1.51",
|
|
44
44
|
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
45
45
|
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
46
46
|
"@bobfrankston/mailx-sync": "^0.1.19",
|
|
47
|
-
"@bobfrankston/oauthsupport": "^1.0.
|
|
47
|
+
"@bobfrankston/oauthsupport": "^1.0.29"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|