@bobfrankston/mailx 1.0.411 → 1.0.415
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/bin/mailx.js +12 -7
- package/package.json +1 -1
- package/packages/mailx-imap/index.d.ts +6 -0
- package/packages/mailx-imap/index.js +44 -2
package/bin/mailx.js
CHANGED
|
@@ -1027,20 +1027,25 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
1027
1027
|
}
|
|
1028
1028
|
catch { /* no saved geometry — use defaults */ }
|
|
1029
1029
|
const rootPkgVersion = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, "..", "package.json"), "utf-8")).version;
|
|
1030
|
-
//
|
|
1031
|
-
//
|
|
1032
|
-
//
|
|
1033
|
-
//
|
|
1034
|
-
//
|
|
1030
|
+
// Pass the .png as the window-decode icon: msger uses the `image` crate
|
|
1031
|
+
// to decode `options.icon` for the Tao window icon, and PNG-in-ICO files
|
|
1032
|
+
// round-trip unreliably through the image-0.24 ICO decoder, which leaves
|
|
1033
|
+
// the taskbar entry showing the empty msgernative.exe default. PNG always
|
|
1034
|
+
// decodes cleanly. The .ico path is forwarded separately as
|
|
1035
|
+
// `relaunchIcon`, which msger uses for `PKEY_AppUserModel_RelaunchIconResource`
|
|
1036
|
+
// (the path is consumed verbatim — no decode — so PNG-in-ICO is fine
|
|
1037
|
+
// there). Falls back if either file is missing.
|
|
1035
1038
|
const __iconIco = path.join(clientDir, "icon.ico");
|
|
1036
1039
|
const __iconPng = path.join(clientDir, "icon.png");
|
|
1037
|
-
const
|
|
1040
|
+
const __iconPathRuntime = fs.existsSync(__iconPng) ? __iconPng : __iconIco;
|
|
1041
|
+
const __iconPathPin = fs.existsSync(__iconIco) ? __iconIco : undefined;
|
|
1038
1042
|
const handle = showService({
|
|
1039
1043
|
title: `mailx v${rootPkgVersion}`,
|
|
1040
1044
|
url: "index.html",
|
|
1041
1045
|
contentDir: clientDir,
|
|
1042
1046
|
initScript: mailxapiScript,
|
|
1043
|
-
icon:
|
|
1047
|
+
icon: __iconPathRuntime,
|
|
1048
|
+
relaunchIcon: __iconPathPin,
|
|
1044
1049
|
aumid: "com.frankston.mailx",
|
|
1045
1050
|
size: savedGeometry
|
|
1046
1051
|
? { width: savedGeometry.width, height: savedGeometry.height }
|
package/package.json
CHANGED
|
@@ -177,6 +177,12 @@ export declare class ImapManager extends EventEmitter {
|
|
|
177
177
|
private reconnectOps;
|
|
178
178
|
/** Handle sync errors — classify and emit appropriate UI events */
|
|
179
179
|
private handleSyncError;
|
|
180
|
+
/** Fetch ONLY new messages above highestUid for one account's INBOX —
|
|
181
|
+
* the IDLE callback's hot path. Skips gap detection, backfill, and the
|
|
182
|
+
* server reconcile (each of which fetches a full UID list — multi-second
|
|
183
|
+
* on a large mailbox). The 5-minute STATUS poll path still runs full
|
|
184
|
+
* `syncFolder` so deletions and gaps eventually reconcile. */
|
|
185
|
+
syncInboxNewOnly(accountId: string): Promise<void>;
|
|
180
186
|
/** Sync just INBOX for each account (fast check for new mail) */
|
|
181
187
|
syncInbox(): Promise<void>;
|
|
182
188
|
/** Quick inbox check — per-account lightweight probe.
|
|
@@ -1540,6 +1540,46 @@ export class ImapManager extends EventEmitter {
|
|
|
1540
1540
|
this.emit("accountError", accountId, errMsg, errMsg, isOAuth);
|
|
1541
1541
|
}
|
|
1542
1542
|
}
|
|
1543
|
+
/** Fetch ONLY new messages above highestUid for one account's INBOX —
|
|
1544
|
+
* the IDLE callback's hot path. Skips gap detection, backfill, and the
|
|
1545
|
+
* server reconcile (each of which fetches a full UID list — multi-second
|
|
1546
|
+
* on a large mailbox). The 5-minute STATUS poll path still runs full
|
|
1547
|
+
* `syncFolder` so deletions and gaps eventually reconcile. */
|
|
1548
|
+
async syncInboxNewOnly(accountId) {
|
|
1549
|
+
if (this.isGmailAccount(accountId))
|
|
1550
|
+
return; // IDLE is IMAP-only
|
|
1551
|
+
const inbox = this.db.getFolders(accountId).find(f => f.specialUse === "inbox");
|
|
1552
|
+
if (!inbox)
|
|
1553
|
+
return;
|
|
1554
|
+
try {
|
|
1555
|
+
await this.withConnection(accountId, async (client) => {
|
|
1556
|
+
const highestUid = this.db.getHighestUid(accountId, inbox.id);
|
|
1557
|
+
if (highestUid === 0) {
|
|
1558
|
+
// First sync — fall through to full path so the date-windowed
|
|
1559
|
+
// backfill runs. `syncFolder` handles the no-highestUid case.
|
|
1560
|
+
await this.syncFolder(accountId, inbox.id, client);
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
const fetched = await client.fetchMessagesSinceUid(inbox.path, highestUid, { source: false });
|
|
1564
|
+
const fresh = fetched.filter((m) => m.uid > highestUid);
|
|
1565
|
+
if (fresh.length === 0)
|
|
1566
|
+
return;
|
|
1567
|
+
const stored = await this.storeMessages(accountId, inbox.id, inbox, fresh, highestUid);
|
|
1568
|
+
if (stored > 0) {
|
|
1569
|
+
this.db.recalcFolderCounts(inbox.id);
|
|
1570
|
+
const updated = this.db.getFolders(accountId).find(f => f.id === inbox.id);
|
|
1571
|
+
this.emit("folderCountsChanged", accountId, {
|
|
1572
|
+
[inbox.id]: { total: updated?.totalCount || 0, unread: updated?.unreadCount || 0 }
|
|
1573
|
+
});
|
|
1574
|
+
this.emit("folderSynced", accountId, inbox.id, Date.now());
|
|
1575
|
+
console.log(` [idle-fast] ${accountId}: stored ${stored} new message(s)`);
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
catch (e) {
|
|
1580
|
+
console.error(` [idle-fast] ${accountId}: ${e.message}`);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1543
1583
|
/** Sync just INBOX for each account (fast check for new mail) */
|
|
1544
1584
|
async syncInbox() {
|
|
1545
1585
|
if (this.inboxSyncing)
|
|
@@ -1783,8 +1823,10 @@ export class ImapManager extends EventEmitter {
|
|
|
1783
1823
|
const watchClient = this.createClient(accountId);
|
|
1784
1824
|
const stop = await watchClient.watchMailbox("INBOX", (newCount) => {
|
|
1785
1825
|
console.log(` [idle] ${accountId}: ${newCount} new message(s)`);
|
|
1786
|
-
//
|
|
1787
|
-
|
|
1826
|
+
// Fetch only the new UIDs — the heavyweight gap/reconcile
|
|
1827
|
+
// path runs on the 5-minute STATUS poll, so EXISTS lands
|
|
1828
|
+
// in the UI in roughly one round-trip.
|
|
1829
|
+
this.syncInboxNewOnly(accountId).catch(e => console.error(` [idle] sync error: ${e.message}`));
|
|
1788
1830
|
});
|
|
1789
1831
|
this.watchers.set(accountId, async () => {
|
|
1790
1832
|
await stop();
|