@bobfrankston/mailx 1.0.241 → 1.0.243
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"height":1344,"width":2151,"x":
|
|
1
|
+
{"height":1344,"width":2151,"x":419,"y":152}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.243",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"postinstall": "node bin/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
23
|
+
"@bobfrankston/iflow-direct": "^0.1.12",
|
|
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.305",
|
|
28
28
|
"@capacitor/android": "^8.3.0",
|
|
29
29
|
"@capacitor/cli": "^8.3.0",
|
|
30
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -74,11 +74,11 @@
|
|
|
74
74
|
},
|
|
75
75
|
".transformedSnapshot": {
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
77
|
+
"@bobfrankston/iflow-direct": "^0.1.12",
|
|
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.305",
|
|
82
82
|
"@capacitor/android": "^8.3.0",
|
|
83
83
|
"@capacitor/cli": "^8.3.0",
|
|
84
84
|
"@capacitor/core": "^8.3.0",
|
|
@@ -788,15 +788,21 @@ export class ImapManager extends EventEmitter {
|
|
|
788
788
|
}
|
|
789
789
|
async _syncAll() {
|
|
790
790
|
const priorityOrder = ["sent", "drafts", "archive", "junk", "trash"];
|
|
791
|
-
// Sync all accounts in parallel — each manages its own connection
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
//
|
|
795
|
-
|
|
796
|
-
|
|
791
|
+
// Sync all accounts in parallel — each manages its own connection.
|
|
792
|
+
// Prefetch runs per-account immediately after that account's sync
|
|
793
|
+
// completes, NOT after all accounts finish. This way a slow account
|
|
794
|
+
// (bobma with 300s timeouts) doesn't block prefetch for a fast account
|
|
795
|
+
// (Gmail). The old code put prefetch after `allSettled`, but syncAll
|
|
796
|
+
// has a 10-minute wall-clock timeout that killed it first — so
|
|
797
|
+
// prefetch never ran.
|
|
798
|
+
const syncAndPrefetch = async (accountId) => {
|
|
799
|
+
await this.syncAccount(accountId, priorityOrder);
|
|
800
|
+
if (getPrefetch()) {
|
|
797
801
|
this.prefetchBodies(accountId).catch(e => console.error(` [prefetch] ${accountId}: ${e.message}`));
|
|
798
802
|
}
|
|
799
|
-
}
|
|
803
|
+
};
|
|
804
|
+
const syncPromises = [...this.configs.keys()].map(syncAndPrefetch);
|
|
805
|
+
await Promise.allSettled(syncPromises);
|
|
800
806
|
}
|
|
801
807
|
/** Sync a single account — manages its own connection lifecycle */
|
|
802
808
|
async syncAccount(accountId, priorityOrder) {
|
|
@@ -1424,7 +1430,13 @@ export class ImapManager extends EventEmitter {
|
|
|
1424
1430
|
let totalFetched = 0;
|
|
1425
1431
|
let deleted = 0;
|
|
1426
1432
|
let errors = 0;
|
|
1433
|
+
let rateLimited = false;
|
|
1427
1434
|
const ERROR_BUDGET = 20;
|
|
1435
|
+
// Pace body fetches to avoid slamming Gmail's rate limit. Without a
|
|
1436
|
+
// delay, 500+ body-fetch API calls fire in a burst, every one hits 429,
|
|
1437
|
+
// and the error budget drains before any bodies land.
|
|
1438
|
+
const FETCH_DELAY_MS = this.isGmailAccount(accountId) ? 1000 : 200;
|
|
1439
|
+
const RATE_LIMIT_PAUSE_MS = 30000;
|
|
1428
1440
|
while (true) {
|
|
1429
1441
|
const missing = this.db.getMessagesWithoutBody(accountId, 100);
|
|
1430
1442
|
if (missing.length === 0)
|
|
@@ -1433,6 +1445,15 @@ export class ImapManager extends EventEmitter {
|
|
|
1433
1445
|
console.log(` [prefetch] ${accountId}: ${missing.length}+ bodies to fetch`);
|
|
1434
1446
|
let madeProgress = false;
|
|
1435
1447
|
for (const msg of missing) {
|
|
1448
|
+
// If we hit a rate limit, pause before the next fetch
|
|
1449
|
+
if (rateLimited) {
|
|
1450
|
+
console.log(` [prefetch] ${accountId}: rate-limited — pausing ${RATE_LIMIT_PAUSE_MS / 1000}s`);
|
|
1451
|
+
await new Promise(r => setTimeout(r, RATE_LIMIT_PAUSE_MS));
|
|
1452
|
+
rateLimited = false;
|
|
1453
|
+
}
|
|
1454
|
+
else if (FETCH_DELAY_MS > 0) {
|
|
1455
|
+
await new Promise(r => setTimeout(r, FETCH_DELAY_MS));
|
|
1456
|
+
}
|
|
1436
1457
|
try {
|
|
1437
1458
|
const result = await this.fetchMessageBody(accountId, msg.folderId, msg.uid);
|
|
1438
1459
|
if (result) {
|
|
@@ -1454,7 +1475,15 @@ export class ImapManager extends EventEmitter {
|
|
|
1454
1475
|
catch { /* ignore */ }
|
|
1455
1476
|
continue;
|
|
1456
1477
|
}
|
|
1457
|
-
|
|
1478
|
+
// If the error is a rate limit (429), don't count against
|
|
1479
|
+
// the budget — just slow down. The API will accept requests
|
|
1480
|
+
// again after a brief pause.
|
|
1481
|
+
if (/429|rate|too many/i.test(String(e?.message || ""))) {
|
|
1482
|
+
rateLimited = true;
|
|
1483
|
+
}
|
|
1484
|
+
else {
|
|
1485
|
+
errors++;
|
|
1486
|
+
}
|
|
1458
1487
|
if (errors >= ERROR_BUDGET) {
|
|
1459
1488
|
console.error(` [prefetch] ${accountId}: stopping after ${errors} errors (${totalFetched} cached, ${deleted} pruned)`);
|
|
1460
1489
|
return;
|