@bobfrankston/rmfmail 1.1.163 → 1.1.169

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 (48) hide show
  1. package/bin/mailx.js +182 -6
  2. package/bin/mailx.js.map +1 -1
  3. package/bin/mailx.ts +185 -6
  4. package/client/android-bootstrap.bundle.js +28 -9
  5. package/client/android-bootstrap.bundle.js.map +2 -2
  6. package/client/app.bundle.js +16 -10
  7. package/client/app.bundle.js.map +2 -2
  8. package/client/components/message-viewer.js +21 -12
  9. package/client/components/message-viewer.js.map +1 -1
  10. package/client/components/message-viewer.ts +18 -12
  11. package/package.json +7 -7
  12. package/packages/mailx-core/index.js +1 -1
  13. package/packages/mailx-core/index.js.map +1 -1
  14. package/packages/mailx-core/index.ts +1 -1
  15. package/packages/mailx-imap/index.d.ts.map +1 -1
  16. package/packages/mailx-imap/index.js +60 -8
  17. package/packages/mailx-imap/index.js.map +1 -1
  18. package/packages/mailx-imap/index.ts +52 -8
  19. package/packages/mailx-imap/package-lock.json +2 -2
  20. package/packages/mailx-imap/package.json +1 -1
  21. package/packages/mailx-service/index.js +1 -1
  22. package/packages/mailx-service/index.js.map +1 -1
  23. package/packages/mailx-service/index.ts +1 -1
  24. package/packages/mailx-service/reconciler.js +1 -1
  25. package/packages/mailx-service/reconciler.js.map +1 -1
  26. package/packages/mailx-service/reconciler.ts +1 -1
  27. package/packages/mailx-store/db.d.ts +25 -4
  28. package/packages/mailx-store/db.d.ts.map +1 -1
  29. package/packages/mailx-store/db.js +36 -9
  30. package/packages/mailx-store/db.js.map +1 -1
  31. package/packages/mailx-store/db.ts +48 -15
  32. package/packages/mailx-store/package.json +1 -1
  33. package/packages/mailx-store/store.js +2 -2
  34. package/packages/mailx-store/store.js.map +1 -1
  35. package/packages/mailx-store/store.ts +2 -2
  36. package/packages/mailx-store-web/android-bootstrap.js +2 -2
  37. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  38. package/packages/mailx-store-web/android-bootstrap.ts +2 -2
  39. package/packages/mailx-store-web/db.d.ts +5 -1
  40. package/packages/mailx-store-web/db.d.ts.map +1 -1
  41. package/packages/mailx-store-web/db.js +7 -5
  42. package/packages/mailx-store-web/db.js.map +1 -1
  43. package/packages/mailx-store-web/db.ts +7 -4
  44. package/packages/mailx-store-web/package.json +1 -1
  45. package/packages/mailx-store-web/sync-manager.js +3 -3
  46. package/packages/mailx-store-web/sync-manager.js.map +1 -1
  47. package/packages/mailx-store-web/sync-manager.ts +3 -3
  48. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-51500 → node_modules.npmglobalize-stash-61952}/.package-lock.json +0 -0
@@ -1275,7 +1275,7 @@ export class ImapManager extends EventEmitter {
1275
1275
  for (const uid of qr.vanishedUids) {
1276
1276
  const env = this.db.getMessageByUid(accountId, uid, folderId);
1277
1277
  if (env) {
1278
- this.db.deleteMessage(accountId, uid, "server VANISHED via QRESYNC", "mailx-imap syncFolder/qresync");
1278
+ this.db.deleteMessage(accountId, folderId, uid, "server VANISHED via QRESYNC", "mailx-imap syncFolder/qresync");
1279
1279
  vanishedApplied++;
1280
1280
  }
1281
1281
  }
@@ -2130,7 +2130,7 @@ export class ImapManager extends EventEmitter {
2130
2130
  const tag = env ? `msgid=${env.messageId || "?"} subj="${(env.subject || "").slice(0, 60)}"` : "unknown";
2131
2131
  console.log(` [reconcile-delete] ${accountId}/${folder.path} uid=${uid} ${tag}`);
2132
2132
  this.unlinkBodyFile(accountId, uid, folder.id).catch(() => {});
2133
- this.db.deleteMessage(accountId, uid, "Gmail-API reconcile: server list missing this UID", `mailx-imap Gmail reconcile (${folder.path})`);
2133
+ this.db.deleteMessage(accountId, folder.id, uid, "Gmail-API reconcile: server list missing this UID", `mailx-imap Gmail reconcile (${folder.path})`);
2134
2134
  }
2135
2135
  if (toDelete.length > 0) console.log(` [api] ${accountId}/${folder.path}: ${toDelete.length} deleted`);
2136
2136
  }
@@ -2446,6 +2446,26 @@ export class ImapManager extends EventEmitter {
2446
2446
  console.log(` [periodic] ${accountId}: STATUS check every ${interval / 1000}s (${isGmail ? "API" : "IMAP+IDLE"})`);
2447
2447
  }
2448
2448
 
2449
+ // DEADMAN: IDLE silently dropped on 2026-05-27 at 10:07 (empty
2450
+ // error message swallowed the cause) and 90 minutes of new mail
2451
+ // went undelivered until manual reload. Every 60s, check that an
2452
+ // IDLE watcher exists for every IMAP-path account; if not, kick
2453
+ // startWatching() to re-establish. startWatching itself is
2454
+ // idempotent (the `watchers.has(accountId) continue` guard means
2455
+ // accounts with healthy IDLE are skipped). One log line per
2456
+ // detected gap so a chronically failing account is obvious in
2457
+ // the log.
2458
+ const deadmanInterval = setInterval(() => {
2459
+ for (const [accountId] of this.configs) {
2460
+ if (this.isGmailAccount(accountId)) continue; // Gmail = API, no IDLE
2461
+ if (this.watchers.has(accountId)) continue;
2462
+ console.log(` [idle-deadman] ${accountId}: no IDLE watcher — attempting restart`);
2463
+ this.startWatching().catch(e =>
2464
+ console.error(` [idle-deadman] ${accountId} restart failed: ${e?.message || e}`));
2465
+ }
2466
+ }, 60_000);
2467
+ this.syncIntervals.set("idle-deadman", deadmanInterval);
2468
+
2449
2469
  // Sync actions (sends + flags/deletes/moves) every 30 seconds — skip during active sync
2450
2470
  const actionsInterval = setInterval(async () => {
2451
2471
  if (this.syncing) return;
@@ -2572,7 +2592,20 @@ export class ImapManager extends EventEmitter {
2572
2592
  });
2573
2593
  console.log(` [idle] Watching INBOX for ${accountId}${useNotify ? " (+NOTIFY personal mailboxes)" : ""}`);
2574
2594
  } catch (e: any) {
2575
- console.error(` [idle] Failed to watch ${accountId}: ${e.message}`);
2595
+ // Defensive: same empty-message bug class as the transport
2596
+ // path. `Failed to watch bobma: ` with nothing after the
2597
+ // colon hid 86 minutes of IDLE outage on 2026-05-27 — new
2598
+ // messages arrived in Thunderbird but mailx never noticed
2599
+ // because IDLE silently died and the log gave no clue why.
2600
+ const parts: string[] = [];
2601
+ if (e?.message) parts.push(String(e.message));
2602
+ if (e?.code) parts.push(`code=${e.code}`);
2603
+ if (e?.errno !== undefined) parts.push(`errno=${e.errno}`);
2604
+ if (e?.syscall) parts.push(`syscall=${e.syscall}`);
2605
+ const desc = parts.length > 0
2606
+ ? parts.join(" ")
2607
+ : `<no message> ${typeof e} ${e?.constructor?.name || ""}`.trim();
2608
+ console.error(` [idle] Failed to watch ${accountId}: ${desc}`);
2576
2609
  }
2577
2610
  }
2578
2611
  }
@@ -2638,7 +2671,7 @@ export class ImapManager extends EventEmitter {
2638
2671
  const tag = env.messageId ? `msgid=${env.messageId} subj="${(env.subject || "").slice(0, 60)}"` : "no-msgid";
2639
2672
  console.log(` [reconcile-delete] ${accountId}/${folderPath} uid=${uid} ${tag} (legacy path)`);
2640
2673
  this.unlinkBodyFile(accountId, uid, folderId).catch(() => {});
2641
- this.db.deleteMessage(accountId, uid, "reconcile: server missing this UID after grace (legacy)", `mailx-imap deferred reconcile (${folderPath})`);
2674
+ this.db.deleteMessage(accountId, folderId, uid, "reconcile: server missing this UID after grace (legacy)", `mailx-imap deferred reconcile (${folderPath})`);
2642
2675
  }
2643
2676
  this.db.recalcFolderCounts(folderId);
2644
2677
  this.emit("folderCountsChanged", accountId, {});
@@ -2676,7 +2709,7 @@ export class ImapManager extends EventEmitter {
2676
2709
  }
2677
2710
  const envelope: any = this.db.getMessageByUid(accountId, uid, folderId);
2678
2711
  let storedPath = envelope?.bodyPath || "";
2679
- if (!storedPath) storedPath = this.db.getMessageBodyPath(accountId, uid) || "";
2712
+ if (!storedPath) storedPath = this.db.getMessageBodyPath(accountId, uid, folderId) || "";
2680
2713
  if (storedPath && await this.bodyStore.hasByPath(storedPath)) {
2681
2714
  return this.bodyStore.readByPath(storedPath);
2682
2715
  }
@@ -3046,7 +3079,18 @@ export class ImapManager extends EventEmitter {
3046
3079
  batchSucceeded = true;
3047
3080
  this.clearFolderErrors(accountId, folder.path);
3048
3081
  } catch (e: any) {
3049
- const msg = String(e?.message || "");
3082
+ // Build a description that never collapses to "".
3083
+ // Even with the iflow transport fix below, defense
3084
+ // in depth: some error paths produce objects whose
3085
+ // .message is empty, or pass non-Error values.
3086
+ const parts: string[] = [];
3087
+ if (e?.message) parts.push(String(e.message));
3088
+ if (e?.code) parts.push(`code=${e.code}`);
3089
+ if (e?.errno !== undefined) parts.push(`errno=${e.errno}`);
3090
+ if (e?.syscall) parts.push(`syscall=${e.syscall}`);
3091
+ const msg = parts.length > 0
3092
+ ? parts.join(" ")
3093
+ : `<no message> ${typeof e} ${e?.constructor?.name || ""}`.trim();
3050
3094
  console.error(` [prefetch] ${accountId} folder ${folder.path} chunk ${chunkStart / PREFETCH_CHUNK_SIZE}: batch fetch failed: ${msg}`);
3051
3095
  counters.errors++;
3052
3096
  this.recordFolderError(accountId, folder.path);
@@ -3173,7 +3217,7 @@ export class ImapManager extends EventEmitter {
3173
3217
  const msg = await sourceClient.fetchMessageByUid(fromFolder.path, uid, { source: true });
3174
3218
  if (!msg) throw new Error(`Message UID ${uid} not found in ${fromFolder.path}`);
3175
3219
  await sourceClient.moveMessageToServer(msg, fromFolder.path, targetClient, toFolder.path);
3176
- this.db.deleteMessage(fromAccountId, uid, `cross-account move to ${toAccountId}/${toFolder.path}`, "mailx-imap moveBetweenAccounts");
3220
+ this.db.deleteMessage(fromAccountId, fromFolder.id, uid, `cross-account move to ${toAccountId}/${toFolder.path}`, "mailx-imap moveBetweenAccounts");
3177
3221
  console.log(` Cross-account move: ${fromAccountId}/${fromFolder.path} UID ${uid} → ${toAccountId}/${toFolder.path}`);
3178
3222
  });
3179
3223
  });
@@ -3507,7 +3551,7 @@ export class ImapManager extends EventEmitter {
3507
3551
  const existing = this.db.getMessageByUid(accountId, draftUid, drafts.id);
3508
3552
  if (existing) {
3509
3553
  this.unlinkBodyFile(accountId, draftUid, drafts.id).catch(() => {});
3510
- this.db.deleteMessage(accountId, draftUid, "user sent the message (draft cleanup)", "mailx-imap deleteDraft (local)");
3554
+ this.db.deleteMessage(accountId, drafts.id, draftUid, "user sent the message (draft cleanup)", "mailx-imap deleteDraft (local)");
3511
3555
  localDeletedUid = draftUid;
3512
3556
  }
3513
3557
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bobfrankston/mailx-imap",
9
- "version": "0.1.65",
9
+ "version": "0.1.67",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@bobfrankston/iflow-direct": "^0.1.27",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -2549,7 +2549,7 @@ export class MailxService {
2549
2549
  // re-fetch here would be wasted IMAP work and risks racing.
2550
2550
  const bodyStore = this.imapManager.getBodyStore();
2551
2551
  let raw = null;
2552
- const storedPath = envelope.bodyPath || this.db.getMessageBodyPath(accountId, uid) || "";
2552
+ const storedPath = envelope.bodyPath || this.db.getMessageBodyPath(accountId, uid, folderId ?? envelope.folderId) || "";
2553
2553
  if (storedPath && await bodyStore.hasByPath(storedPath)) {
2554
2554
  try {
2555
2555
  raw = await bodyStore.readByPath(storedPath);