@bobfrankston/mailx 1.0.197 → 1.0.199
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":320,"y":22}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.199",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"@bobfrankston/iflow-direct": "^0.1.6",
|
|
24
24
|
"@bobfrankston/iflow-node": "^0.1.2",
|
|
25
25
|
"@bobfrankston/miscinfo": "^1.0.8",
|
|
26
|
-
"@bobfrankston/oauthsupport": "^1.0.
|
|
27
|
-
"@bobfrankston/msger": "^0.1.
|
|
26
|
+
"@bobfrankston/oauthsupport": "^1.0.22",
|
|
27
|
+
"@bobfrankston/msger": "^0.1.250",
|
|
28
28
|
"@capacitor/android": "^8.3.0",
|
|
29
29
|
"@capacitor/cli": "^8.3.0",
|
|
30
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -185,7 +185,7 @@ export declare class ImapManager extends EventEmitter {
|
|
|
185
185
|
private saveSendingCopy;
|
|
186
186
|
/** Queue a message for sending. Tries IMAP Outbox, falls back to local file. */
|
|
187
187
|
queueOutgoing(accountId: string, rawMessage: string | Buffer): Promise<void>;
|
|
188
|
-
/** Process local file queue —
|
|
188
|
+
/** Process local file queue — send from outbox/ and sending/queued/ */
|
|
189
189
|
private processLocalQueue;
|
|
190
190
|
/** Send a raw RFC 2822 message via SMTP for a given account */
|
|
191
191
|
private sendRawViaSMTP;
|
|
@@ -1719,23 +1719,33 @@ export class ImapManager extends EventEmitter {
|
|
|
1719
1719
|
fs.writeFileSync(path.join(localQueue, filename), rawMessage);
|
|
1720
1720
|
console.log(` [outbox] Saved locally: ${filename}`);
|
|
1721
1721
|
}
|
|
1722
|
-
/** Process local file queue —
|
|
1722
|
+
/** Process local file queue — send from outbox/ and sending/queued/ */
|
|
1723
1723
|
async processLocalQueue(accountId) {
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
const
|
|
1728
|
-
|
|
1724
|
+
// Collect files from both outbox/ (legacy .ltr) and sending/queued/ (drop-in)
|
|
1725
|
+
const outboxDir = path.join(getConfigDir(), "outbox", accountId);
|
|
1726
|
+
const queuedDir = path.join(getConfigDir(), "sending", accountId, "queued");
|
|
1727
|
+
const filesToSend = [];
|
|
1728
|
+
for (const dir of [outboxDir, queuedDir]) {
|
|
1729
|
+
if (!fs.existsSync(dir))
|
|
1730
|
+
continue;
|
|
1731
|
+
for (const file of fs.readdirSync(dir).filter(f => f.endsWith(".ltr") || f.endsWith(".eml"))) {
|
|
1732
|
+
filesToSend.push({ dir, file });
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (filesToSend.length === 0)
|
|
1729
1736
|
return;
|
|
1730
1737
|
// Gmail/API accounts: send directly via SMTP from local queue (no IMAP outbox)
|
|
1738
|
+
const sentDir = path.join(getConfigDir(), "sending", accountId, "sent");
|
|
1739
|
+
fs.mkdirSync(sentDir, { recursive: true });
|
|
1731
1740
|
if (this.isGmailAccount(accountId)) {
|
|
1732
|
-
for (const file of
|
|
1733
|
-
const filePath = path.join(
|
|
1741
|
+
for (const { dir, file } of filesToSend) {
|
|
1742
|
+
const filePath = path.join(dir, file);
|
|
1734
1743
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
1735
1744
|
try {
|
|
1736
1745
|
await this.sendRawViaSMTP(accountId, raw);
|
|
1737
|
-
|
|
1738
|
-
|
|
1746
|
+
// Move to sent/
|
|
1747
|
+
fs.renameSync(filePath, path.join(sentDir, file));
|
|
1748
|
+
console.log(` [outbox] Sent ${file} via SMTP → sent/`);
|
|
1739
1749
|
}
|
|
1740
1750
|
catch (e) {
|
|
1741
1751
|
console.error(` [outbox] Send failed for ${file}: ${e.message}`);
|
|
@@ -1748,12 +1758,13 @@ export class ImapManager extends EventEmitter {
|
|
|
1748
1758
|
const outboxPath = await this.ensureOutbox(accountId);
|
|
1749
1759
|
const client = await this.createClientWithLimit(accountId);
|
|
1750
1760
|
try {
|
|
1751
|
-
for (const file of
|
|
1752
|
-
const filePath = path.join(
|
|
1761
|
+
for (const { dir, file } of filesToSend) {
|
|
1762
|
+
const filePath = path.join(dir, file);
|
|
1753
1763
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
1754
1764
|
await client.appendMessage(outboxPath, raw, ["\\Seen"]);
|
|
1755
|
-
|
|
1756
|
-
|
|
1765
|
+
// Move to sent/
|
|
1766
|
+
fs.renameSync(filePath, path.join(sentDir, file));
|
|
1767
|
+
console.log(` [outbox] Moved ${file} to IMAP Outbox → sent/`);
|
|
1757
1768
|
}
|
|
1758
1769
|
}
|
|
1759
1770
|
finally {
|
|
@@ -396,11 +396,16 @@ export class MailxService {
|
|
|
396
396
|
const envelope = this.db.getMessageByUid(accountId, uid);
|
|
397
397
|
if (!envelope)
|
|
398
398
|
throw new Error("Message not found");
|
|
399
|
+
// Update local DB immediately (local-first)
|
|
400
|
+
this.db.updateMessageFolder(accountId, uid, targetFolderId);
|
|
401
|
+
this.db.recalcFolderCounts(envelope.folderId);
|
|
402
|
+
this.db.recalcFolderCounts(targetFolderId);
|
|
403
|
+
// Sync to server in background
|
|
399
404
|
if (targetAccountId && targetAccountId !== accountId) {
|
|
400
|
-
|
|
405
|
+
this.imapManager.moveMessageCrossAccount(accountId, envelope.uid, envelope.folderId, targetAccountId, targetFolderId).catch(e => console.error(` Move sync failed: ${e.message}`));
|
|
401
406
|
}
|
|
402
407
|
else {
|
|
403
|
-
|
|
408
|
+
this.imapManager.moveMessage(accountId, envelope.uid, envelope.folderId, targetFolderId).catch(e => console.error(` Move sync failed: ${e.message}`));
|
|
404
409
|
}
|
|
405
410
|
}
|
|
406
411
|
async moveMessages(accountId, uids, targetFolderId) {
|
|
@@ -410,7 +415,16 @@ export class MailxService {
|
|
|
410
415
|
return null;
|
|
411
416
|
return { uid: env.uid, folderId: env.folderId };
|
|
412
417
|
}).filter(m => m !== null);
|
|
413
|
-
|
|
418
|
+
// Update local DB immediately
|
|
419
|
+
for (const msg of messages) {
|
|
420
|
+
if (msg) {
|
|
421
|
+
this.db.updateMessageFolder(accountId, msg.uid, targetFolderId);
|
|
422
|
+
this.db.recalcFolderCounts(msg.folderId);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
this.db.recalcFolderCounts(targetFolderId);
|
|
426
|
+
// Sync to server in background
|
|
427
|
+
this.imapManager.moveMessages(accountId, messages, targetFolderId).catch(e => console.error(` Move sync failed: ${e.message}`));
|
|
414
428
|
}
|
|
415
429
|
async undeleteMessage(accountId, uid, folderId) {
|
|
416
430
|
await this.imapManager.undeleteMessage(accountId, uid, folderId);
|
|
@@ -57,6 +57,7 @@ export declare class MailxDB {
|
|
|
57
57
|
getMessageByUid(accountId: string, uid: number, folderId?: number): MessageEnvelope;
|
|
58
58
|
getMessageBodyPath(accountId: string, uid: number): string;
|
|
59
59
|
updateMessageFlags(accountId: string, uid: number, flags: string[]): void;
|
|
60
|
+
updateMessageFolder(accountId: string, uid: number, targetFolderId: number): void;
|
|
60
61
|
updateBodyPath(accountId: string, uid: number, bodyPath: string): void;
|
|
61
62
|
/** Get messages without cached bodies (for background prefetch) */
|
|
62
63
|
getMessagesWithoutBody(accountId: string, limit?: number): {
|
|
@@ -317,6 +317,9 @@ export class MailxDB {
|
|
|
317
317
|
updateMessageFlags(accountId, uid, flags) {
|
|
318
318
|
this.db.prepare("UPDATE messages SET flags_json = ? WHERE account_id = ? AND uid = ?").run(JSON.stringify(flags), accountId, uid);
|
|
319
319
|
}
|
|
320
|
+
updateMessageFolder(accountId, uid, targetFolderId) {
|
|
321
|
+
this.db.prepare("UPDATE messages SET folder_id = ? WHERE account_id = ? AND uid = ?").run(targetFolderId, accountId, uid);
|
|
322
|
+
}
|
|
320
323
|
updateBodyPath(accountId, uid, bodyPath) {
|
|
321
324
|
this.db.prepare("UPDATE messages SET body_path = ? WHERE account_id = ? AND uid = ?").run(bodyPath, accountId, uid);
|
|
322
325
|
}
|