@bobfrankston/mailx-imap 0.1.65 → 0.1.66

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.
Files changed (2) hide show
  1. package/index.js +55 -3
  2. package/package.json +7 -7
package/index.js CHANGED
@@ -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
- console.error(` [idle] Failed to watch ${accountId}: ${e.message}`);
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
  }
@@ -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
- const msg = String(e?.message || "");
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.65",
3
+ "version": "0.1.66",
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.24",
13
+ "@bobfrankston/mailx-settings": "^0.1.25",
14
14
  "@bobfrankston/mailx-store": "^0.1.38",
15
- "@bobfrankston/iflow-direct": "^0.1.50",
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.27"
19
+ "@bobfrankston/oauthsupport": "^1.0.28"
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.24",
41
+ "@bobfrankston/mailx-settings": "^0.1.25",
42
42
  "@bobfrankston/mailx-store": "^0.1.38",
43
- "@bobfrankston/iflow-direct": "^0.1.50",
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.27"
47
+ "@bobfrankston/oauthsupport": "^1.0.28"
48
48
  }
49
49
  }
50
50
  }