@bobfrankston/mailx 1.0.236 → 1.0.237
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.237",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@bobfrankston/iflow-node": "^0.1.2",
|
|
25
25
|
"@bobfrankston/miscinfo": "^1.0.8",
|
|
26
26
|
"@bobfrankston/oauthsupport": "^1.0.22",
|
|
27
|
-
"@bobfrankston/msger": "^0.1.
|
|
27
|
+
"@bobfrankston/msger": "^0.1.299",
|
|
28
28
|
"@capacitor/android": "^8.3.0",
|
|
29
29
|
"@capacitor/cli": "^8.3.0",
|
|
30
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@bobfrankston/iflow-node": "^0.1.2",
|
|
79
79
|
"@bobfrankston/miscinfo": "^1.0.8",
|
|
80
80
|
"@bobfrankston/oauthsupport": "^1.0.22",
|
|
81
|
-
"@bobfrankston/msger": "^0.1.
|
|
81
|
+
"@bobfrankston/msger": "^0.1.299",
|
|
82
82
|
"@capacitor/android": "^8.3.0",
|
|
83
83
|
"@capacitor/cli": "^8.3.0",
|
|
84
84
|
"@capacitor/core": "^8.3.0",
|
|
@@ -137,7 +137,11 @@ export declare class ImapManager extends EventEmitter {
|
|
|
137
137
|
* (deleted from another device, for example). The caller uses that to
|
|
138
138
|
* delete the stale row locally instead of showing a generic error. */
|
|
139
139
|
private fetchMessageBodyViaApi;
|
|
140
|
-
/** Background body prefetch — download bodies for messages that don't have them
|
|
140
|
+
/** Background body prefetch — download bodies for messages that don't have them.
|
|
141
|
+
* Server-side deletions (isNotFound) aren't errors here: we delete the
|
|
142
|
+
* stale row locally and keep going. Only unrelated errors (network,
|
|
143
|
+
* auth, rate limits) count against the error budget, and the budget is
|
|
144
|
+
* generous so a few transient failures don't kill the whole run. */
|
|
141
145
|
private prefetchBodies;
|
|
142
146
|
/** Get the body store for direct access */
|
|
143
147
|
getBodyStore(): FileMessageStore;
|
|
@@ -1402,34 +1402,60 @@ export class ImapManager extends EventEmitter {
|
|
|
1402
1402
|
return null;
|
|
1403
1403
|
}
|
|
1404
1404
|
}
|
|
1405
|
-
/** Background body prefetch — download bodies for messages that don't have them
|
|
1405
|
+
/** Background body prefetch — download bodies for messages that don't have them.
|
|
1406
|
+
* Server-side deletions (isNotFound) aren't errors here: we delete the
|
|
1407
|
+
* stale row locally and keep going. Only unrelated errors (network,
|
|
1408
|
+
* auth, rate limits) count against the error budget, and the budget is
|
|
1409
|
+
* generous so a few transient failures don't kill the whole run. */
|
|
1406
1410
|
async prefetchBodies(accountId) {
|
|
1407
|
-
// Fetch ALL missing bodies in one pass — don't wait for next sync cycle
|
|
1408
1411
|
let totalFetched = 0;
|
|
1412
|
+
let deleted = 0;
|
|
1409
1413
|
let errors = 0;
|
|
1414
|
+
const ERROR_BUDGET = 20;
|
|
1410
1415
|
while (true) {
|
|
1411
1416
|
const missing = this.db.getMessagesWithoutBody(accountId, 100);
|
|
1412
1417
|
if (missing.length === 0)
|
|
1413
1418
|
break;
|
|
1414
|
-
if (totalFetched === 0)
|
|
1419
|
+
if (totalFetched === 0 && deleted === 0)
|
|
1415
1420
|
console.log(` [prefetch] ${accountId}: ${missing.length}+ bodies to fetch`);
|
|
1421
|
+
let madeProgress = false;
|
|
1416
1422
|
for (const msg of missing) {
|
|
1417
1423
|
try {
|
|
1418
1424
|
const result = await this.fetchMessageBody(accountId, msg.folderId, msg.uid);
|
|
1419
|
-
if (result)
|
|
1425
|
+
if (result) {
|
|
1420
1426
|
totalFetched++;
|
|
1427
|
+
madeProgress = true;
|
|
1428
|
+
}
|
|
1421
1429
|
}
|
|
1422
1430
|
catch (e) {
|
|
1431
|
+
if (e?.isNotFound) {
|
|
1432
|
+
// Message deleted on the server — drop the stale row so
|
|
1433
|
+
// we stop re-asking. This also moves the loop forward
|
|
1434
|
+
// (next getMessagesWithoutBody call won't return it).
|
|
1435
|
+
try {
|
|
1436
|
+
this.db.deleteMessage(accountId, msg.uid);
|
|
1437
|
+
this.bodyStore.deleteMessage(accountId, msg.folderId, msg.uid).catch(() => { });
|
|
1438
|
+
deleted++;
|
|
1439
|
+
madeProgress = true;
|
|
1440
|
+
}
|
|
1441
|
+
catch { /* ignore */ }
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1423
1444
|
errors++;
|
|
1424
|
-
if (errors >=
|
|
1425
|
-
console.error(` [prefetch] ${accountId}: stopping after ${errors} errors (${totalFetched} cached)`);
|
|
1445
|
+
if (errors >= ERROR_BUDGET) {
|
|
1446
|
+
console.error(` [prefetch] ${accountId}: stopping after ${errors} errors (${totalFetched} cached, ${deleted} pruned)`);
|
|
1426
1447
|
return;
|
|
1427
1448
|
}
|
|
1428
1449
|
}
|
|
1429
1450
|
}
|
|
1451
|
+
// Safety: if we made zero progress this iteration, bail — otherwise
|
|
1452
|
+
// we'd loop forever on rows that keep failing without isNotFound.
|
|
1453
|
+
if (!madeProgress)
|
|
1454
|
+
break;
|
|
1455
|
+
}
|
|
1456
|
+
if (totalFetched > 0 || deleted > 0) {
|
|
1457
|
+
console.log(` [prefetch] ${accountId}: ${totalFetched} bodies cached, ${deleted} stale rows pruned (done)`);
|
|
1430
1458
|
}
|
|
1431
|
-
if (totalFetched > 0)
|
|
1432
|
-
console.log(` [prefetch] ${accountId}: ${totalFetched} bodies cached (done)`);
|
|
1433
1459
|
}
|
|
1434
1460
|
/** Get the body store for direct access */
|
|
1435
1461
|
getBodyStore() {
|