@bobfrankston/mailx-imap 0.1.166 → 0.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/index.js +41 -7
- package/package.json +9 -9
package/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { createAutoImapConfig, CompatImapClient } from "@bobfrankston/iflow-dire
|
|
|
7
7
|
import { authenticateOAuth, clearReauthBackoff } from "@bobfrankston/oauthsupport";
|
|
8
8
|
import { parseSerial, fixCharsetDeclString, storeBus } from "@bobfrankston/mailx-store";
|
|
9
9
|
import { loadSettings, getConfigDir, getHistoryDays, getPrefetch, tokenDirName } from "@bobfrankston/mailx-settings";
|
|
10
|
-
import { isJunkPreviewText } from "@bobfrankston/mailx-types";
|
|
10
|
+
import { isJunkPreviewText, logIfChanged } from "@bobfrankston/mailx-types";
|
|
11
11
|
import { EventEmitter } from "node:events";
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import * as path from "node:path";
|
|
@@ -2405,10 +2405,13 @@ export class ImapManager extends EventEmitter {
|
|
|
2405
2405
|
&& statusMessageCount !== null
|
|
2406
2406
|
&& serverUidsArr.length === statusMessageCount;
|
|
2407
2407
|
if (serverUidsArr.length === 0 && localUidsAll.length > 0 && !authoritative) {
|
|
2408
|
-
|
|
2408
|
+
// Same line every cycle for a folder stuck in this state —
|
|
2409
|
+
// logIfChanged prints the first one, each change, and an
|
|
2410
|
+
// hourly restatement. (Claude Code 2026-08-29)
|
|
2411
|
+
logIfChanged(`${accountId}/${folder.path}:reconcile`, ` [sync] ${accountId}/${folder.path}: reconcile skipped — server UID list empty but local has ${localUidsAll.length} (treating as transient)`);
|
|
2409
2412
|
}
|
|
2410
2413
|
else if (!authoritative && localUids.length > 0 && toDelete.length / localUids.length > 0.5) {
|
|
2411
|
-
|
|
2414
|
+
logIfChanged(`${accountId}/${folder.path}:reconcile`, ` [sync] ${accountId}/${folder.path}: reconcile REFUSED — would delete ${toDelete.length}/${localUids.length} (${Math.round(toDelete.length / localUids.length * 100)}%) — probably a sync bug, skipping`);
|
|
2412
2415
|
}
|
|
2413
2416
|
else if (toDelete.length > 0) {
|
|
2414
2417
|
// DEFERRED DELETE — DO NOT delete immediately. Server-side
|
|
@@ -2888,10 +2891,10 @@ export class ImapManager extends EventEmitter {
|
|
|
2888
2891
|
const serverUids = new Set(serverUidsArr);
|
|
2889
2892
|
const localUids = this.db.getUidsForFolder(accountId, folder.id);
|
|
2890
2893
|
if (serverUidsArr._truncated) {
|
|
2891
|
-
|
|
2894
|
+
logIfChanged(`${accountId}/${folder.path}:api-reconcile`, ` [api] ${accountId}/${folder.path}: reconcile skipped — server list truncated (${serverUidsArr.length} ids)`);
|
|
2892
2895
|
}
|
|
2893
2896
|
else if (serverUidsArr.length === 0 && localUids.length > 0) {
|
|
2894
|
-
|
|
2897
|
+
logIfChanged(`${accountId}/${folder.path}:api-reconcile`, ` [api] ${accountId}/${folder.path}: reconcile skipped — server list empty but local has ${localUids.length}`);
|
|
2895
2898
|
}
|
|
2896
2899
|
else {
|
|
2897
2900
|
const toDelete = localUids.filter(uid => !serverUids.has(uid));
|
|
@@ -2925,7 +2928,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2925
2928
|
}
|
|
2926
2929
|
}
|
|
2927
2930
|
if (!confirmed) {
|
|
2928
|
-
|
|
2931
|
+
logIfChanged(`${accountId}/${folder.path}:api-reconcile`, ` [api] ${accountId}/${folder.path}: reconcile refused — would delete ${toDelete.length}/${localUids.length} (${Math.round(toDelete.length / localUids.length * 100)}%) — probably a sync bug, skipping`);
|
|
2929
2932
|
}
|
|
2930
2933
|
else {
|
|
2931
2934
|
for (const uid of toDelete) {
|
|
@@ -3511,10 +3514,41 @@ export class ImapManager extends EventEmitter {
|
|
|
3511
3514
|
this.syncFolder(accountId, inbox.id).catch(e => console.error(` [idle] expunge reconcile failed: ${e.message}`));
|
|
3512
3515
|
}, 2000);
|
|
3513
3516
|
};
|
|
3517
|
+
// A flag changed on a message we already have — someone starred
|
|
3518
|
+
// or read it on another client. The server pushes it during
|
|
3519
|
+
// IDLE as an unsolicited FETCH; before v1.2.296 that was
|
|
3520
|
+
// dropped, so a star set on the phone reached the desktop only
|
|
3521
|
+
// on the next periodic sync of INBOX. Measured on Bob's own
|
|
3522
|
+
// log that was a median of 5 minutes and up to 20 (Bob
|
|
3523
|
+
// 2026-08-29: "how long should it take for a * on the phone to
|
|
3524
|
+
// get to my desktop?").
|
|
3525
|
+
//
|
|
3526
|
+
// The push says WHICH flags but by sequence number, which this
|
|
3527
|
+
// connection cannot map to a UID reliably — so it triggers the
|
|
3528
|
+
// folder's normal QRESYNC pass, which asks the server for
|
|
3529
|
+
// exactly what changed since our modseq and took 53 ms in the
|
|
3530
|
+
// same log. Debounced: marking ten messages read on the phone
|
|
3531
|
+
// arrives as ten pushes.
|
|
3532
|
+
let flagTimer = null;
|
|
3533
|
+
const onFlagChange = () => {
|
|
3534
|
+
if (flagTimer)
|
|
3535
|
+
clearTimeout(flagTimer);
|
|
3536
|
+
flagTimer = setTimeout(() => {
|
|
3537
|
+
flagTimer = null;
|
|
3538
|
+
// Our own STOREs bounce straight back as flag pushes;
|
|
3539
|
+
// the local store already has those.
|
|
3540
|
+
if (this.db.getPendingSyncActions(accountId).length > 0)
|
|
3541
|
+
return;
|
|
3542
|
+
const inbox = this.db.getFolders(accountId).find(f => f.specialUse === "inbox");
|
|
3543
|
+
if (!inbox)
|
|
3544
|
+
return;
|
|
3545
|
+
this.syncFolder(accountId, inbox.id).catch(e => console.error(` [idle] flag-change sync failed: ${e.message}`));
|
|
3546
|
+
}, 1500);
|
|
3547
|
+
};
|
|
3514
3548
|
const stop = await watchClient.watchMailbox("INBOX", (newCount) => {
|
|
3515
3549
|
console.log(` [idle] ${accountId}: ${newCount} new message(s)`);
|
|
3516
3550
|
this.syncInboxNewOnly(accountId).catch(e => console.error(` [idle] sync error: ${e.message}`));
|
|
3517
|
-
}, { notifySpec, onMailboxStatus, onExpunge });
|
|
3551
|
+
}, { notifySpec, onMailboxStatus, onExpunge, onFlagChange });
|
|
3518
3552
|
this.watchers.set(accountId, async () => {
|
|
3519
3553
|
await stop();
|
|
3520
3554
|
await watchClient.logout();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.169",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
},
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
13
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
14
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
15
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
12
|
+
"@bobfrankston/mailx-types": "^0.1.75",
|
|
13
|
+
"@bobfrankston/mailx-settings": "^0.1.72",
|
|
14
|
+
"@bobfrankston/mailx-store": "^0.1.108",
|
|
15
|
+
"@bobfrankston/iflow-direct": "^0.1.68",
|
|
16
16
|
"@bobfrankston/tcp-transport": "^0.1.8",
|
|
17
17
|
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
18
18
|
"@bobfrankston/mailx-sync": "^0.1.29",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
},
|
|
38
38
|
".transformedSnapshot": {
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
41
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
42
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
43
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
40
|
+
"@bobfrankston/mailx-types": "^0.1.75",
|
|
41
|
+
"@bobfrankston/mailx-settings": "^0.1.72",
|
|
42
|
+
"@bobfrankston/mailx-store": "^0.1.108",
|
|
43
|
+
"@bobfrankston/iflow-direct": "^0.1.68",
|
|
44
44
|
"@bobfrankston/tcp-transport": "^0.1.8",
|
|
45
45
|
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
46
46
|
"@bobfrankston/mailx-sync": "^0.1.29",
|