@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.
- package/bin/mailx.js +182 -6
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +185 -6
- package/client/android-bootstrap.bundle.js +28 -9
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +16 -10
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-viewer.js +21 -12
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +18 -12
- package/package.json +7 -7
- package/packages/mailx-core/index.js +1 -1
- package/packages/mailx-core/index.js.map +1 -1
- package/packages/mailx-core/index.ts +1 -1
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +60 -8
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +52 -8
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.js +1 -1
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +1 -1
- package/packages/mailx-service/reconciler.js +1 -1
- package/packages/mailx-service/reconciler.js.map +1 -1
- package/packages/mailx-service/reconciler.ts +1 -1
- package/packages/mailx-store/db.d.ts +25 -4
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +36 -9
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +48 -15
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/store.js +2 -2
- package/packages/mailx-store/store.js.map +1 -1
- package/packages/mailx-store/store.ts +2 -2
- package/packages/mailx-store-web/android-bootstrap.js +2 -2
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.ts +2 -2
- package/packages/mailx-store-web/db.d.ts +5 -1
- package/packages/mailx-store-web/db.d.ts.map +1 -1
- package/packages/mailx-store-web/db.js +7 -5
- package/packages/mailx-store-web/db.js.map +1 -1
- package/packages/mailx-store-web/db.ts +7 -4
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/sync-manager.js +3 -3
- package/packages/mailx-store-web/sync-manager.js.map +1 -1
- package/packages/mailx-store-web/sync-manager.ts +3 -3
- /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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
9
|
+
"version": "0.1.67",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/iflow-direct": "^0.1.27",
|
|
@@ -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);
|