@bobfrankston/mailx-imap 0.1.34 → 0.1.36
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 +74 -25
- package/package.json +5 -5
package/index.js
CHANGED
|
@@ -2181,15 +2181,43 @@ export class ImapManager extends EventEmitter {
|
|
|
2181
2181
|
// it can't share the ops queue. Counts against the per-host
|
|
2182
2182
|
// semaphore (one slot for the IDLE socket).
|
|
2183
2183
|
const watchClient = await this.createClient(accountId, "idle");
|
|
2184
|
+
// RFC 5465 NOTIFY: when the server supports it (Dovecot does;
|
|
2185
|
+
// gmail-IMAP and Outlook IMAP typically don't), engage
|
|
2186
|
+
// cross-mailbox push so changes in Sent/Drafts/subfolders
|
|
2187
|
+
// surface in real time instead of waiting for the next
|
|
2188
|
+
// periodic sync. SELECTED group keeps INBOX events flowing
|
|
2189
|
+
// through the existing IDLE/EXISTS path; PERSONAL group
|
|
2190
|
+
// pushes STATUS for every other mailbox the user owns.
|
|
2191
|
+
const caps = typeof watchClient.getCapabilities === "function"
|
|
2192
|
+
? watchClient.getCapabilities()
|
|
2193
|
+
: new Set();
|
|
2194
|
+
const useNotify = caps.has("NOTIFY");
|
|
2195
|
+
const notifySpec = useNotify
|
|
2196
|
+
? "(SELECTED (MessageNew MessageExpunge FlagChange)) (PERSONAL (MessageNew MessageExpunge FlagChange MailboxName))"
|
|
2197
|
+
: undefined;
|
|
2198
|
+
const onMailboxStatus = useNotify
|
|
2199
|
+
? (mailboxPath, _data) => {
|
|
2200
|
+
// Find the local folder row by IMAP path and sync it.
|
|
2201
|
+
// STATUS pushes are lightweight count hints — the real
|
|
2202
|
+
// change set is fetched by syncFolder.
|
|
2203
|
+
const folder = this.db.getFolders(accountId).find(f => f.path === mailboxPath);
|
|
2204
|
+
if (!folder) {
|
|
2205
|
+
console.log(` [notify] ${accountId}: STATUS for unknown mailbox "${mailboxPath}" — skipping`);
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
console.log(` [notify] ${accountId}: ${mailboxPath} changed → syncFolder`);
|
|
2209
|
+
this.syncFolder(accountId, folder.id).catch(e => console.error(` [notify] sync ${mailboxPath} failed: ${e.message}`));
|
|
2210
|
+
}
|
|
2211
|
+
: undefined;
|
|
2184
2212
|
const stop = await watchClient.watchMailbox("INBOX", (newCount) => {
|
|
2185
2213
|
console.log(` [idle] ${accountId}: ${newCount} new message(s)`);
|
|
2186
2214
|
this.syncInboxNewOnly(accountId).catch(e => console.error(` [idle] sync error: ${e.message}`));
|
|
2187
|
-
});
|
|
2215
|
+
}, { notifySpec, onMailboxStatus });
|
|
2188
2216
|
this.watchers.set(accountId, async () => {
|
|
2189
2217
|
await stop();
|
|
2190
2218
|
await watchClient.logout();
|
|
2191
2219
|
});
|
|
2192
|
-
console.log(` [idle] Watching INBOX for ${accountId}`);
|
|
2220
|
+
console.log(` [idle] Watching INBOX for ${accountId}${useNotify ? " (+NOTIFY personal mailboxes)" : ""}`);
|
|
2193
2221
|
}
|
|
2194
2222
|
catch (e) {
|
|
2195
2223
|
console.error(` [idle] Failed to watch ${accountId}: ${e.message}`);
|
|
@@ -3124,33 +3152,54 @@ export class ImapManager extends EventEmitter {
|
|
|
3124
3152
|
return;
|
|
3125
3153
|
if (!draftUid && !draftId)
|
|
3126
3154
|
return;
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3155
|
+
let succeeded = false;
|
|
3156
|
+
try {
|
|
3157
|
+
await this.withConnection(accountId, async (client) => {
|
|
3158
|
+
if (draftUid) {
|
|
3159
|
+
try {
|
|
3160
|
+
await client.deleteMessageByUid(drafts.path, draftUid);
|
|
3161
|
+
console.log(` [drafts] Deleted draft UID ${draftUid}`);
|
|
3162
|
+
succeeded = true;
|
|
3163
|
+
}
|
|
3164
|
+
catch (e) {
|
|
3165
|
+
console.error(` [drafts] Delete by UID ${draftUid} failed: ${e.message}`);
|
|
3166
|
+
}
|
|
3135
3167
|
}
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3168
|
+
if (draftId) {
|
|
3169
|
+
try {
|
|
3170
|
+
const uids = await client.searchByHeader(drafts.path, "X-Mailx-Draft-ID", draftId);
|
|
3171
|
+
for (const uid of uids) {
|
|
3172
|
+
try {
|
|
3173
|
+
await client.deleteMessageByUid(drafts.path, uid);
|
|
3174
|
+
succeeded = true;
|
|
3175
|
+
}
|
|
3176
|
+
catch { /* next */ }
|
|
3143
3177
|
}
|
|
3144
|
-
|
|
3178
|
+
if (uids.length > 0)
|
|
3179
|
+
console.log(` [drafts] Deleted ${uids.length} draft(s) by ID ${draftId}`);
|
|
3180
|
+
}
|
|
3181
|
+
catch (e) {
|
|
3182
|
+
console.error(` [drafts] searchByHeader for ${draftId} failed: ${e.message}`);
|
|
3145
3183
|
}
|
|
3146
|
-
if (uids.length > 0)
|
|
3147
|
-
console.log(` [drafts] Deleted ${uids.length} draft(s) by ID ${draftId}`);
|
|
3148
|
-
}
|
|
3149
|
-
catch (e) {
|
|
3150
|
-
console.error(` [drafts] searchByHeader for ${draftId} failed: ${e.message}`);
|
|
3151
3184
|
}
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3187
|
+
catch (e) {
|
|
3188
|
+
// withConnection itself failed (no socket, auth refresh wedged) —
|
|
3189
|
+
// fall through to queue a retry if we have a UID to anchor on.
|
|
3190
|
+
console.error(` [drafts] withConnection failed for ${accountId}: ${e?.message || e}`);
|
|
3191
|
+
}
|
|
3192
|
+
// Reliable cleanup: if the inline attempt didn't actually delete the
|
|
3193
|
+
// draft, queue a sync_action so the regular processSyncActions loop
|
|
3194
|
+
// retries it later. Without this, transient IMAP failures left stale
|
|
3195
|
+
// drafts in the folder ("draft droppings" that pile up after sends).
|
|
3196
|
+
// Only the UID path can be queued — searchByHeader retries on every
|
|
3197
|
+
// future call anyway, and queuing a header-lookup isn't a real
|
|
3198
|
+
// sync_action shape.
|
|
3199
|
+
if (!succeeded && draftUid) {
|
|
3200
|
+
console.log(` [drafts] Queueing sync_action for retry: delete UID ${draftUid} in ${drafts.path}`);
|
|
3201
|
+
this.db.queueSyncAction(accountId, "delete", draftUid, drafts.id);
|
|
3202
|
+
}
|
|
3154
3203
|
}
|
|
3155
3204
|
/** Queue outgoing message locally — never fails, worker handles IMAP+SMTP.
|
|
3156
3205
|
* Single path: write `~/.mailx/outbox/<acct>/*.ltr` synchronously, then
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/mailx-types": "^0.1.10",
|
|
13
13
|
"@bobfrankston/mailx-settings": "^0.1.14",
|
|
14
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
15
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
14
|
+
"@bobfrankston/mailx-store": "^0.1.18",
|
|
15
|
+
"@bobfrankston/iflow-direct": "^0.1.40",
|
|
16
16
|
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
17
17
|
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
18
18
|
"@bobfrankston/mailx-sync": "^0.1.16",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bobfrankston/mailx-types": "^0.1.10",
|
|
41
41
|
"@bobfrankston/mailx-settings": "^0.1.14",
|
|
42
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
43
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
42
|
+
"@bobfrankston/mailx-store": "^0.1.18",
|
|
43
|
+
"@bobfrankston/iflow-direct": "^0.1.40",
|
|
44
44
|
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
45
45
|
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
46
46
|
"@bobfrankston/mailx-sync": "^0.1.16",
|