@bobfrankston/rmfmail 1.1.162 → 1.1.166
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/client/android-bootstrap.bundle.js +19 -2
- 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/client/compose/compose.bundle.js +24 -0
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/spellcheck.js +16 -0
- package/client/compose/spellcheck.js.map +1 -1
- package/client/compose/spellcheck.ts +15 -0
- package/client/lib/rmf-tiny.js +25 -1
- package/package.json +7 -7
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +66 -4
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +58 -4
- 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-settings/cloud.d.ts.map +1 -1
- package/packages/mailx-settings/cloud.js +46 -0
- package/packages/mailx-settings/cloud.js.map +1 -1
- package/packages/mailx-settings/cloud.ts +43 -0
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/db.d.ts +8 -1
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +11 -2
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +15 -4
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/store.js +1 -1
- package/packages/mailx-store/store.js.map +1 -1
- package/packages/mailx-store/store.ts +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-40852 → node_modules.npmglobalize-stash-56540}/.package-lock.json +0 -0
|
@@ -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
|
}
|
|
@@ -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);
|
|
@@ -4184,7 +4228,17 @@ export class ImapManager extends EventEmitter {
|
|
|
4184
4228
|
await client.addFlags(outboxFolder.path, uid, ["$Failed"]);
|
|
4185
4229
|
}, { slow: true });
|
|
4186
4230
|
} catch { /* best-effort */ }
|
|
4187
|
-
|
|
4231
|
+
// Suppress the banner for transient network errors. ETIMEDOUT
|
|
4232
|
+
// on a Dovecot send is the server being slow / a TCP idle
|
|
4233
|
+
// drop, NOT a user-actionable failure — the next outbox tick
|
|
4234
|
+
// retries with exponential backoff. Surfacing it as a sticky
|
|
4235
|
+
// "Send failed: ETIMEDOUT" banner trains the user to ignore
|
|
4236
|
+
// banners (Bob 2026-05-26 "timeout??"). handleSyncError uses
|
|
4237
|
+
// the same classifier; keep the two paths in sync.
|
|
4238
|
+
const isTransient = /timeout|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENETUNREACH|Too many|socket hang up|EPIPE|write after end/i.test(errMsg);
|
|
4239
|
+
if (!isTransient) {
|
|
4240
|
+
this.emit("accountError", accountId, `Send failed: ${errMsg}`, "Message kept in Outbox", false);
|
|
4241
|
+
}
|
|
4188
4242
|
if (/auth|login|credential|invalid/i.test(errMsg)) {
|
|
4189
4243
|
this.outboxBackoff.set(accountId, Date.now() + 3600000); // 1 hour
|
|
4190
4244
|
console.error(` [outbox] Auth failure for ${accountId} — outbox paused for 1 hour`);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.66",
|
|
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.66",
|
|
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);
|