@bobfrankston/mailx 1.0.164 → 1.0.166
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
|
@@ -645,7 +645,8 @@ async function main() {
|
|
|
645
645
|
}
|
|
646
646
|
}
|
|
647
647
|
const db = new MailxDB(getConfigDir());
|
|
648
|
-
const
|
|
648
|
+
const { NodeTransport } = await import("@bobfrankston/iflow-node");
|
|
649
|
+
const imapManager = new ImapManager(db, () => new NodeTransport());
|
|
649
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.166",
|
|
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,11 +20,11 @@
|
|
|
20
20
|
"postinstall": "node bin/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
24
|
-
"@bobfrankston/iflow-node": "^0.1.
|
|
23
|
+
"@bobfrankston/iflow-direct": "^0.1.2",
|
|
24
|
+
"@bobfrankston/iflow-node": "^0.1.1",
|
|
25
25
|
"@bobfrankston/miscinfo": "^1.0.7",
|
|
26
26
|
"@bobfrankston/oauthsupport": "^1.0.20",
|
|
27
|
-
"@bobfrankston/msger": "^0.1.
|
|
27
|
+
"@bobfrankston/msger": "^0.1.216",
|
|
28
28
|
"@capacitor/android": "^8.3.0",
|
|
29
29
|
"@capacitor/cli": "^8.3.0",
|
|
30
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -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,6 +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 { type TransportFactory } from "@bobfrankston/iflow-direct";
|
|
6
7
|
import { MailxDB, FileMessageStore } from "@bobfrankston/mailx-store";
|
|
7
8
|
import type { AccountConfig, MessageEnvelope, Folder } from "@bobfrankston/mailx-types";
|
|
8
9
|
import { EventEmitter } from "node:events";
|
|
@@ -33,7 +34,8 @@ export declare class ImapManager extends EventEmitter {
|
|
|
33
34
|
useNativeClient: boolean;
|
|
34
35
|
/** Accounts hitting connection limits — back off until this time */
|
|
35
36
|
private connectionBackoff;
|
|
36
|
-
|
|
37
|
+
private transportFactory;
|
|
38
|
+
constructor(db: MailxDB, transportFactory: TransportFactory);
|
|
37
39
|
/** Get OAuth access token for an account (for SMTP auth) */
|
|
38
40
|
getOAuthToken(accountId: string): Promise<string | null>;
|
|
39
41
|
/** Accounts currently re-authenticating — all operations skip these */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Syncs messages to local store, emits events for new mail.
|
|
5
5
|
*/
|
|
6
6
|
import { createAutoImapConfig, CompatImapClient } from "@bobfrankston/iflow-direct";
|
|
7
|
-
import {
|
|
7
|
+
import { authenticateOAuth } from "@bobfrankston/oauthsupport";
|
|
8
8
|
import { FileMessageStore } from "@bobfrankston/mailx-store";
|
|
9
9
|
import { loadSettings, getStorePath, getConfigDir, getHistoryDays } from "@bobfrankston/mailx-settings";
|
|
10
10
|
import { EventEmitter } from "node:events";
|
|
@@ -108,9 +108,11 @@ export class ImapManager extends EventEmitter {
|
|
|
108
108
|
/** Accounts hitting connection limits — back off until this time */
|
|
109
109
|
connectionBackoff = new Map();
|
|
110
110
|
// Connection management: see withConnection() below — no semaphore needed
|
|
111
|
-
|
|
111
|
+
transportFactory;
|
|
112
|
+
constructor(db, transportFactory) {
|
|
112
113
|
super();
|
|
113
114
|
this.db = db;
|
|
115
|
+
this.transportFactory = transportFactory;
|
|
114
116
|
const storePath = getStorePath();
|
|
115
117
|
this.bodyStore = new FileMessageStore(storePath);
|
|
116
118
|
}
|
|
@@ -281,7 +283,7 @@ export class ImapManager extends EventEmitter {
|
|
|
281
283
|
const config = this.configs.get(accountId);
|
|
282
284
|
if (!config)
|
|
283
285
|
throw new Error(`No config for account ${accountId}`);
|
|
284
|
-
return new CompatImapClient(config,
|
|
286
|
+
return new CompatImapClient(config, this.transportFactory);
|
|
285
287
|
}
|
|
286
288
|
/** Disconnect the persistent operational connection for an account */
|
|
287
289
|
async disconnectOps(accountId) {
|
|
@@ -313,13 +315,26 @@ export class ImapManager extends EventEmitter {
|
|
|
313
315
|
if (this.configs.has(account.id))
|
|
314
316
|
return;
|
|
315
317
|
// createAutoImapConfig auto-detects Gmail from server/username and sets up OAuth
|
|
316
|
-
//
|
|
318
|
+
// For OAuth accounts, provide a tokenProvider using oauthsupport
|
|
319
|
+
let tokenProvider;
|
|
320
|
+
if (account.imap.auth === "oauth2" || (!account.imap.password && account.imap.host?.includes("gmail"))) {
|
|
321
|
+
const credPath = path.join(getConfigDir(), "google-credentials.json");
|
|
322
|
+
const tokenDir = path.join(getConfigDir(), "tokens", account.imap.user.replace(/[@.]/g, "_"));
|
|
323
|
+
tokenProvider = async () => {
|
|
324
|
+
const result = await authenticateOAuth(credPath, {
|
|
325
|
+
scope: "https://mail.google.com/ https://www.googleapis.com/auth/contacts.readonly",
|
|
326
|
+
tokenDirectory: tokenDir,
|
|
327
|
+
loginHint: account.imap.user,
|
|
328
|
+
});
|
|
329
|
+
return result?.access_token || "";
|
|
330
|
+
};
|
|
331
|
+
}
|
|
317
332
|
const config = createAutoImapConfig({
|
|
318
333
|
server: account.imap.host,
|
|
319
334
|
port: account.imap.port,
|
|
320
335
|
username: account.imap.user,
|
|
321
336
|
password: account.imap.password,
|
|
322
|
-
|
|
337
|
+
tokenProvider,
|
|
323
338
|
});
|
|
324
339
|
this.configs.set(account.id, config);
|
|
325
340
|
// Register account in DB
|
|
@@ -387,6 +402,11 @@ export class ImapManager extends EventEmitter {
|
|
|
387
402
|
this.db.beginTransaction();
|
|
388
403
|
try {
|
|
389
404
|
for (const msg of msgs) {
|
|
405
|
+
// Debug: log subjects with non-ASCII to trace encoding issues
|
|
406
|
+
if (msg.subject && /[^\x00-\x7F]/.test(msg.subject)) {
|
|
407
|
+
const hex = Buffer.from(msg.subject, "utf-8").subarray(0, 40).toString("hex");
|
|
408
|
+
console.log(` [encoding] subject: "${msg.subject.substring(0, 60)}" hex: ${hex}`);
|
|
409
|
+
}
|
|
390
410
|
if (msg.uid <= highestUid)
|
|
391
411
|
continue; // already have it
|
|
392
412
|
const source = msg.source || "";
|
|
@@ -499,7 +519,8 @@ export class ImapManager extends EventEmitter {
|
|
|
499
519
|
this.emit("folderCountsChanged", accountId, {});
|
|
500
520
|
}
|
|
501
521
|
};
|
|
502
|
-
|
|
522
|
+
const tomorrow = new Date(Date.now() + 86400000); // IMAP BEFORE is exclusive
|
|
523
|
+
messages = await client.fetchMessageByDate(folder.path, startDate, tomorrow, { source: false }, onChunk);
|
|
503
524
|
if (totalStored > 0) {
|
|
504
525
|
console.log(` ${folder.path}: ${totalStored} messages (streamed)`);
|
|
505
526
|
this.db.recalcFolderCounts(folderId);
|
|
@@ -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" }));
|