@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 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 ${ver("@bobfrankston/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 { ImapClient, createAutoImapConfig } = await import("@bobfrankston/iflow");
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 ImapClient(config);
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
- // --email (or -mail) flag skips the interactive prompt
603
- const emailArg = args.find(a => a.startsWith("--email="))?.split("=")[1]
604
- || args.find(a => a.startsWith("-mail="))?.split("=")[1]
605
- || (hasFlag("email") ? args[args.indexOf("--email") + 1] || args[args.indexOf("-email") + 1] : undefined)
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 imapManager = new ImapManager(db);
649
- imapManager.useNativeClient = true;
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.163",
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.0.56",
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.213",
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 { ImapClient } from "@bobfrankston/iflow";
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
- constructor(db: MailxDB);
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?: ImapClient): Promise<Folder[]>;
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?: ImapClient): Promise<number>;
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 { ImapClient, createAutoImapConfig, CompatImapClient, NodeTransport } from "@bobfrankston/iflow";
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
- constructor(db) {
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
- if (this.useNativeClient) {
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: getConfigDir()
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
- const imapManager = new ImapManager(db);
65
- // Native IMAP client is default. Use --legacy-imap to fall back to imapflow.
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 name of ["iflow-credentials.json", "credentials.json"]) {
34
- const p = path.join(dir, "node_modules", "@bobfrankston", "iflow", name);
35
- if (fs.existsSync(p))
36
- return p;
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)