@bobfrankston/mailx 1.0.163 → 1.0.165
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 -11
- package/package.json +6 -4
- package/packages/mailx-core/index.d.ts +1 -1
- package/packages/mailx-core/index.js +2 -2
- package/packages/mailx-imap/index.d.ts +5 -4
- package/packages/mailx-imap/index.js +8 -9
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-server/index.js +2 -9
- package/packages/mailx-settings/cloud.js +6 -4
package/bin/mailx.js
CHANGED
|
@@ -231,7 +231,7 @@ if (hasFlag("v") || hasFlag("version")) {
|
|
|
231
231
|
console.log("mailx (version unknown)");
|
|
232
232
|
}
|
|
233
233
|
console.log(` node ${process.version}`);
|
|
234
|
-
console.log(` iflow
|
|
234
|
+
console.log(` iflow-direct ${ver("@bobfrankston/iflow-direct")}`);
|
|
235
235
|
console.log(` miscinfo ${ver("@bobfrankston/miscinfo")}`);
|
|
236
236
|
console.log(` oauth ${ver("@bobfrankston/oauthsupport")}`);
|
|
237
237
|
console.log(` store ${ver("@bobfrankston/mailx-store")}`);
|
|
@@ -500,14 +500,15 @@ async function runTest() {
|
|
|
500
500
|
console.log(`Testing ${account.label || account.id} (${account.email}):`);
|
|
501
501
|
// Test IMAP
|
|
502
502
|
try {
|
|
503
|
-
const {
|
|
503
|
+
const { createAutoImapConfig, CompatImapClient } = await import("@bobfrankston/iflow-direct");
|
|
504
|
+
const { NodeTransport } = await import("@bobfrankston/iflow-node");
|
|
504
505
|
const config = createAutoImapConfig({
|
|
505
506
|
server: account.imap.host,
|
|
506
507
|
port: account.imap.port,
|
|
507
508
|
username: account.imap.user,
|
|
508
509
|
password: account.imap.password
|
|
509
510
|
});
|
|
510
|
-
const client = new
|
|
511
|
+
const client = new CompatImapClient(config, () => new NodeTransport());
|
|
511
512
|
const folders = await client.getFolderList();
|
|
512
513
|
await client.logout();
|
|
513
514
|
console.log(` IMAP: OK (${folders.length} folders)`);
|
|
@@ -524,7 +525,7 @@ async function runTest() {
|
|
|
524
525
|
}
|
|
525
526
|
else if (account.smtp.auth === "oauth2") {
|
|
526
527
|
// Try to get OAuth token
|
|
527
|
-
const { createAutoImapConfig } = await import("@bobfrankston/iflow");
|
|
528
|
+
const { createAutoImapConfig } = await import("@bobfrankston/iflow-direct");
|
|
528
529
|
const config = createAutoImapConfig({
|
|
529
530
|
server: account.imap.host,
|
|
530
531
|
port: account.imap.port,
|
|
@@ -599,11 +600,10 @@ async function main() {
|
|
|
599
600
|
if (setupMode || !hasConfig()) {
|
|
600
601
|
if (!setupMode)
|
|
601
602
|
console.log("No mailx configuration found.");
|
|
602
|
-
//
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|| (
|
|
606
|
-
|| (hasFlag("mail") ? args[args.indexOf("--mail") + 1] || args[args.indexOf("-mail") + 1] : undefined);
|
|
603
|
+
// -email or -mail flag skips the interactive prompt
|
|
604
|
+
const emailFlag = args.findIndex(a => a === "-email" || a === "--email" || a === "-mail" || a === "--mail");
|
|
605
|
+
const emailArg = args.find(a => a.startsWith("-email=") || a.startsWith("--email=") || a.startsWith("-mail=") || a.startsWith("--mail="))?.split("=")[1]
|
|
606
|
+
|| (emailFlag >= 0 ? args[emailFlag + 1] : undefined);
|
|
607
607
|
await runSetup(emailArg);
|
|
608
608
|
}
|
|
609
609
|
// Redirect console to log file — keep terminal clean
|
|
@@ -645,8 +645,9 @@ async function main() {
|
|
|
645
645
|
}
|
|
646
646
|
}
|
|
647
647
|
const db = new MailxDB(getConfigDir());
|
|
648
|
-
const
|
|
649
|
-
imapManager
|
|
648
|
+
const { NodeTransport } = await import("@bobfrankston/iflow-node");
|
|
649
|
+
const imapManager = new ImapManager(db, () => new NodeTransport());
|
|
650
|
+
// Native client is the only option (iflow-direct)
|
|
650
651
|
const svc = new MailxService(db, imapManager);
|
|
651
652
|
// Open msger in service mode — custom protocol serves files from client dir
|
|
652
653
|
const clientDir = path.join(import.meta.dirname, "..", "client");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.165",
|
|
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,10 +20,11 @@
|
|
|
20
20
|
"postinstall": "node bin/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bobfrankston/iflow": "^1.
|
|
23
|
+
"@bobfrankston/iflow-direct": "^0.1.2",
|
|
24
|
+
"@bobfrankston/iflow-node": "^0.1.1",
|
|
24
25
|
"@bobfrankston/miscinfo": "^1.0.7",
|
|
25
26
|
"@bobfrankston/oauthsupport": "^1.0.20",
|
|
26
|
-
"@bobfrankston/msger": "^0.1.
|
|
27
|
+
"@bobfrankston/msger": "^0.1.215",
|
|
27
28
|
"@capacitor/android": "^8.3.0",
|
|
28
29
|
"@capacitor/cli": "^8.3.0",
|
|
29
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -54,7 +55,8 @@
|
|
|
54
55
|
"url": "https://github.com/BobFrankston/mailx.git"
|
|
55
56
|
},
|
|
56
57
|
".dependencies": {
|
|
57
|
-
"@bobfrankston/iflow": "file:../MailApps/iflow",
|
|
58
|
+
"@bobfrankston/iflow-direct": "file:../MailApps/iflow-direct",
|
|
59
|
+
"@bobfrankston/iflow-node": "file:../MailApps/iflow-node",
|
|
58
60
|
"@bobfrankston/miscinfo": "file:../../projects/npm/miscinfo",
|
|
59
61
|
"@bobfrankston/oauthsupport": "file:../../projects/oauth/oauthsupport",
|
|
60
62
|
"@bobfrankston/msger": "file:../../utils/msgx/msger",
|
|
@@ -9,7 +9,7 @@ import type { ComposeMessage } from "@bobfrankston/mailx-types";
|
|
|
9
9
|
declare let db: MailxDB;
|
|
10
10
|
declare let imapManager: ImapManager;
|
|
11
11
|
export declare function onEvent(handler: (event: any) => void): void;
|
|
12
|
-
export declare function initialize(): Promise<void>;
|
|
12
|
+
export declare function initialize(transportFactory?: any): Promise<void>;
|
|
13
13
|
export declare function shutdown(): Promise<void>;
|
|
14
14
|
export declare function getAccounts(): {
|
|
15
15
|
id: string;
|
|
@@ -22,11 +22,11 @@ function emit(event) {
|
|
|
22
22
|
catch { /* ignore handler errors */ }
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
export async function initialize() {
|
|
25
|
+
export async function initialize(transportFactory) {
|
|
26
26
|
initLocalConfig();
|
|
27
27
|
const dbDir = getConfigDir();
|
|
28
28
|
db = new MailxDB(dbDir);
|
|
29
|
-
imapManager = new ImapManager(db);
|
|
29
|
+
imapManager = new ImapManager(db, transportFactory || (() => { throw new Error("No transport factory provided"); }));
|
|
30
30
|
// Seed contacts
|
|
31
31
|
const seeded = db.seedContactsFromMessages();
|
|
32
32
|
if (seeded > 0)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Multi-account IMAP management wrapping iflow.
|
|
4
4
|
* Syncs messages to local store, emits events for new mail.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { type TransportFactory } from "@bobfrankston/iflow-direct";
|
|
7
7
|
import { MailxDB, FileMessageStore } from "@bobfrankston/mailx-store";
|
|
8
8
|
import type { AccountConfig, MessageEnvelope, Folder } from "@bobfrankston/mailx-types";
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
@@ -34,7 +34,8 @@ export declare class ImapManager extends EventEmitter {
|
|
|
34
34
|
useNativeClient: boolean;
|
|
35
35
|
/** Accounts hitting connection limits — back off until this time */
|
|
36
36
|
private connectionBackoff;
|
|
37
|
-
|
|
37
|
+
private transportFactory;
|
|
38
|
+
constructor(db: MailxDB, transportFactory: TransportFactory);
|
|
38
39
|
/** Get OAuth access token for an account (for SMTP auth) */
|
|
39
40
|
getOAuthToken(accountId: string): Promise<string | null>;
|
|
40
41
|
/** Accounts currently re-authenticating — all operations skip these */
|
|
@@ -73,11 +74,11 @@ export declare class ImapManager extends EventEmitter {
|
|
|
73
74
|
/** Register an account */
|
|
74
75
|
addAccount(account: AccountConfig): Promise<void>;
|
|
75
76
|
/** Sync folder list for an account */
|
|
76
|
-
syncFolders(accountId: string, client?:
|
|
77
|
+
syncFolders(accountId: string, client?: any): Promise<Folder[]>;
|
|
77
78
|
/** Store a batch of messages to DB immediately — used by onChunk for incremental sync */
|
|
78
79
|
private storeMessages;
|
|
79
80
|
/** Sync messages for a specific folder */
|
|
80
|
-
syncFolder(accountId: string, folderId: number, client?:
|
|
81
|
+
syncFolder(accountId: string, folderId: number, client?: any): Promise<number>;
|
|
81
82
|
/** Sync all folders for all accounts */
|
|
82
83
|
syncAll(): Promise<void>;
|
|
83
84
|
private _syncAll;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Multi-account IMAP management wrapping iflow.
|
|
4
4
|
* Syncs messages to local store, emits events for new mail.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { createAutoImapConfig, CompatImapClient } from "@bobfrankston/iflow-direct";
|
|
7
7
|
import { FileMessageStore } from "@bobfrankston/mailx-store";
|
|
8
8
|
import { loadSettings, getStorePath, getConfigDir, getHistoryDays } from "@bobfrankston/mailx-settings";
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
@@ -107,9 +107,11 @@ export class ImapManager extends EventEmitter {
|
|
|
107
107
|
/** Accounts hitting connection limits — back off until this time */
|
|
108
108
|
connectionBackoff = new Map();
|
|
109
109
|
// Connection management: see withConnection() below — no semaphore needed
|
|
110
|
-
|
|
110
|
+
transportFactory;
|
|
111
|
+
constructor(db, transportFactory) {
|
|
111
112
|
super();
|
|
112
113
|
this.db = db;
|
|
114
|
+
this.transportFactory = transportFactory;
|
|
113
115
|
const storePath = getStorePath();
|
|
114
116
|
this.bodyStore = new FileMessageStore(storePath);
|
|
115
117
|
}
|
|
@@ -280,10 +282,7 @@ export class ImapManager extends EventEmitter {
|
|
|
280
282
|
const config = this.configs.get(accountId);
|
|
281
283
|
if (!config)
|
|
282
284
|
throw new Error(`No config for account ${accountId}`);
|
|
283
|
-
|
|
284
|
-
return new CompatImapClient(config, () => new NodeTransport({ rejectUnauthorized: config.rejectUnauthorized !== false }));
|
|
285
|
-
}
|
|
286
|
-
return new ImapClient(config);
|
|
285
|
+
return new CompatImapClient(config, this.transportFactory);
|
|
287
286
|
}
|
|
288
287
|
/** Disconnect the persistent operational connection for an account */
|
|
289
288
|
async disconnectOps(accountId) {
|
|
@@ -321,7 +320,7 @@ export class ImapManager extends EventEmitter {
|
|
|
321
320
|
port: account.imap.port,
|
|
322
321
|
username: account.imap.user,
|
|
323
322
|
password: account.imap.password,
|
|
324
|
-
tokenDirectory
|
|
323
|
+
// tokenDirectory is handled by oauthsupport, not iflow-direct
|
|
325
324
|
});
|
|
326
325
|
this.configs.set(account.id, config);
|
|
327
326
|
// Register account in DB
|
|
@@ -447,7 +446,7 @@ export class ImapManager extends EventEmitter {
|
|
|
447
446
|
// Incremental: fetch new messages — metadata only for speed, bodies on demand
|
|
448
447
|
const fetched = await client.fetchMessagesSinceUid(folder.path, highestUid, { source: false });
|
|
449
448
|
// Filter out the last known message (IMAP * always returns at least one)
|
|
450
|
-
messages = fetched.filter(m => m.uid > highestUid);
|
|
449
|
+
messages = fetched.filter((m) => m.uid > highestUid);
|
|
451
450
|
// Gap detection: check for missing UIDs within the range we've already synced
|
|
452
451
|
// Only reconcile between our lowest and highest UID — don't try to fetch the entire folder history
|
|
453
452
|
const existingUids = this.db.getUidsForFolder(accountId, folderId);
|
|
@@ -483,7 +482,7 @@ export class ImapManager extends EventEmitter {
|
|
|
483
482
|
if (oldestDate > 0 && startDate.getTime() < oldestDate) {
|
|
484
483
|
const existingUids = new Set(this.db.getUidsForFolder(accountId, folderId));
|
|
485
484
|
const backfill = await client.fetchMessageByDate(folder.path, startDate, new Date(oldestDate), { source: false });
|
|
486
|
-
const newBackfill = backfill.filter(m => !existingUids.has(m.uid));
|
|
485
|
+
const newBackfill = backfill.filter((m) => !existingUids.has(m.uid));
|
|
487
486
|
if (newBackfill.length > 0) {
|
|
488
487
|
console.log(` ${folder.path}: backfilling ${newBackfill.length} older messages`);
|
|
489
488
|
messages.push(...newBackfill);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@bobfrankston/mailx-types": "file:../mailx-types",
|
|
13
13
|
"@bobfrankston/mailx-settings": "file:../mailx-settings",
|
|
14
14
|
"@bobfrankston/mailx-store": "file:../mailx-store",
|
|
15
|
-
"@bobfrankston/iflow": "file:../../../MailApps/iflow",
|
|
15
|
+
"@bobfrankston/iflow-direct": "file:../../../MailApps/iflow-direct",
|
|
16
16
|
"@bobfrankston/oauthsupport": "file:../../../../projects/oauth/oauthsupport",
|
|
17
17
|
"nodemailer": "^7.0.0"
|
|
18
18
|
},
|
|
@@ -61,15 +61,8 @@ if (settings.accounts.length === 0) {
|
|
|
61
61
|
}
|
|
62
62
|
const dbDir = getConfigDir();
|
|
63
63
|
const db = new MailxDB(dbDir);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (process.argv.includes("--legacy-imap") || process.argv.includes("-legacy-imap") || process.env.MAILX_LEGACY_IMAP) {
|
|
67
|
-
console.log(" Using legacy IMAP client (imapflow)");
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
imapManager.useNativeClient = true;
|
|
71
|
-
console.log(" Using native IMAP client (transport-agnostic)");
|
|
72
|
-
}
|
|
64
|
+
import { NodeTransport } from "@bobfrankston/iflow-node";
|
|
65
|
+
const imapManager = new ImapManager(db, () => new NodeTransport());
|
|
73
66
|
// ── Express App ──
|
|
74
67
|
const app = express();
|
|
75
68
|
app.use(express.json({ limit: "Infinity" }));
|
|
@@ -30,10 +30,12 @@ function findGoogleCredentials() {
|
|
|
30
30
|
try {
|
|
31
31
|
let dir = import.meta.dirname;
|
|
32
32
|
for (let i = 0; i < 5; i++) {
|
|
33
|
-
for (const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
for (const pkg of ["iflow-direct", "iflow"]) {
|
|
34
|
+
for (const name of ["iflow-credentials.json", "credentials.json"]) {
|
|
35
|
+
const p = path.join(dir, "node_modules", "@bobfrankston", pkg, name);
|
|
36
|
+
if (fs.existsSync(p))
|
|
37
|
+
return p;
|
|
38
|
+
}
|
|
37
39
|
}
|
|
38
40
|
const parent = path.dirname(dir);
|
|
39
41
|
if (parent === dir)
|