@bobfrankston/rmfmail 1.2.296 → 1.2.297
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/.commitmsg +28 -25
- package/client/app.bundle.js +20 -0
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +27 -1
- package/client/app.js.map +1 -1
- package/client/app.ts +25 -1
- package/client/index.html +5 -0
- package/client/styles/components.css +22 -5
- package/npmchanges.md +32 -0
- package/package.json +7 -7
- package/packages/mailx-imap/index.d.ts +1 -0
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +34 -4
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +32 -5
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-40440 → node_modules.npmglobalize-stash-77248}/.package-lock.json +0 -0
|
@@ -843,7 +843,7 @@ export class ImapManager extends EventEmitter {
|
|
|
843
843
|
async withConnection<T>(
|
|
844
844
|
accountId: string,
|
|
845
845
|
fn: (client: any) => Promise<T>,
|
|
846
|
-
opts: { slow?: boolean; lane?: string; timeoutMs?: number } = {},
|
|
846
|
+
opts: { slow?: boolean; lane?: string; timeoutMs?: number; priority?: boolean } = {},
|
|
847
847
|
): Promise<T> {
|
|
848
848
|
const lane: string = opts.lane ?? (opts.slow ? "slow" : "fast");
|
|
849
849
|
let perAccount = this.laneQueues.get(accountId);
|
|
@@ -886,7 +886,17 @@ export class ImapManager extends EventEmitter {
|
|
|
886
886
|
reject(e);
|
|
887
887
|
}
|
|
888
888
|
};
|
|
889
|
-
|
|
889
|
+
// A task the user is waiting on goes to the FRONT of its lane.
|
|
890
|
+
// Lanes are strictly sequential, so without this a click-time body
|
|
891
|
+
// fetch sits behind whatever background work was queued first and
|
|
892
|
+
// can only start after all of it — Bob 2026-08-29: "if I look at a
|
|
893
|
+
// letter while sync things get confused … viewing the body should
|
|
894
|
+
// get priority instead of waiting for sync". It cannot preempt the
|
|
895
|
+
// command already in flight (an IMAP command cannot be cancelled
|
|
896
|
+
// without dropping the connection), but it no longer waits for the
|
|
897
|
+
// whole backlog.
|
|
898
|
+
if (opts.priority) queue!.tasks.unshift(task);
|
|
899
|
+
else queue!.tasks.push(task);
|
|
890
900
|
this.drainOpsQueue(accountId);
|
|
891
901
|
});
|
|
892
902
|
}
|
|
@@ -3743,13 +3753,20 @@ export class ImapManager extends EventEmitter {
|
|
|
3743
3753
|
// IMAP: fast lane on the ops queue. One try; if the socket is stale,
|
|
3744
3754
|
// withConnection's discard-on-error logic drops the client so the
|
|
3745
3755
|
// next attempt (caller-driven retry) gets a fresh one.
|
|
3756
|
+
//
|
|
3757
|
+
// An interactive fetch (`force`) jumps the lane queue and gets a
|
|
3758
|
+
// tighter cap: the reader is staring at a blank pane, and 90 s of
|
|
3759
|
+
// silence followed by a banner is the worst of both. 30 s is long
|
|
3760
|
+
// enough for a big body on a slow server and short enough that a
|
|
3761
|
+
// wedged lane becomes a RETRYABLE timeout while the reader is still
|
|
3762
|
+
// looking at the message.
|
|
3746
3763
|
try {
|
|
3747
3764
|
const raw = await this.withConnection(accountId, async (client) => {
|
|
3748
3765
|
const msg: any = await client.fetchMessageByUid(folder.path, uid, { source: true });
|
|
3749
3766
|
if (!msg) throw makeNotFoundError(accountId, folderId, uid);
|
|
3750
3767
|
if (!msg.source) return null;
|
|
3751
3768
|
return Buffer.from(msg.source, "utf-8");
|
|
3752
|
-
});
|
|
3769
|
+
}, force ? { priority: true, timeoutMs: 30_000 } : {});
|
|
3753
3770
|
if (!raw) return null;
|
|
3754
3771
|
const bodyPath = await this.bodyStore.putMessage(accountId, folderId, uid, raw);
|
|
3755
3772
|
this.db.updateBodyPath(accountId, folderId, uid, bodyPath);
|
|
@@ -3761,8 +3778,18 @@ export class ImapManager extends EventEmitter {
|
|
|
3761
3778
|
} catch (e: any) {
|
|
3762
3779
|
if (e?.isNotFound) throw e;
|
|
3763
3780
|
console.error(` Body fetch error (${accountId}/${uid}): ${e?.message || e}`);
|
|
3764
|
-
//
|
|
3765
|
-
//
|
|
3781
|
+
// A lane timeout / dropped socket says nothing about whether this
|
|
3782
|
+
// message HAS a body. Swallowing it into `null` told the caller
|
|
3783
|
+
// "the server has no body for this" — the reconciler classifies
|
|
3784
|
+
// that as non-transient, skips its retry budget, and banners.
|
|
3785
|
+
// That is the "eventually reports an error rather than retrying"
|
|
3786
|
+
// Bob hit (2026-08-29). Rethrow instead, so the reconciler's
|
|
3787
|
+
// transient test sees the word "timeout" and retries with backoff.
|
|
3788
|
+
const transient = /timeout|discarding client|connection|not connected|ECONN|ETIMEDOUT|ENOTFOUND|socket/i
|
|
3789
|
+
.test(String(e?.message || ""));
|
|
3790
|
+
if (transient) throw e;
|
|
3791
|
+
// A genuine "no body here" — arm the backoff so prefetch and the
|
|
3792
|
+
// next view don't re-attempt a fetch the server won't satisfy.
|
|
3766
3793
|
try { this.markPrefetchEmpty(accountId, folderId, uid); } catch { /* */ }
|
|
3767
3794
|
return null;
|
|
3768
3795
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.170",
|
|
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.170",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/iflow-direct": "^0.1.27",
|