@bobfrankston/mailx 1.0.165 → 1.0.167
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/client/index.html +2 -2
- package/package.json +2 -2
- package/packages/mailx-imap/index.js +37 -3
package/client/index.html
CHANGED
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
<span class="tb-icon">↻</span> Sync
|
|
47
47
|
</button>
|
|
48
48
|
<div class="tb-menu" id="restart-menu">
|
|
49
|
-
<button class="tb-btn" id="btn-restart" title="
|
|
49
|
+
<button class="tb-btn" id="btn-restart" title="Reload">
|
|
50
50
|
<span class="tb-icon">⚡</span> Restart ▾
|
|
51
51
|
</button>
|
|
52
52
|
<div class="tb-menu-dropdown" id="restart-dropdown" hidden>
|
|
53
|
-
<button class="tb-menu-item" id="btn-restart-quick" title="
|
|
53
|
+
<button class="tb-menu-item" id="btn-restart-quick" title="Reload the page">Reload</button>
|
|
54
54
|
<button class="tb-menu-item" id="btn-rebuild" title="Wipe local DB and message cache, re-download everything. Accounts and settings are preserved. Safe and fast.">Rebuild local cache</button>
|
|
55
55
|
<hr class="tb-menu-sep">
|
|
56
56
|
<span class="tb-menu-hint">CLI: mailx --rebuild for full reset</span>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.167",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
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.217",
|
|
28
28
|
"@capacitor/android": "^8.3.0",
|
|
29
29
|
"@capacitor/cli": "^8.3.0",
|
|
30
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -4,6 +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 { authenticateOAuth } from "@bobfrankston/oauthsupport";
|
|
7
8
|
import { FileMessageStore } from "@bobfrankston/mailx-store";
|
|
8
9
|
import { loadSettings, getStorePath, getConfigDir, getHistoryDays } from "@bobfrankston/mailx-settings";
|
|
9
10
|
import { EventEmitter } from "node:events";
|
|
@@ -314,13 +315,40 @@ export class ImapManager extends EventEmitter {
|
|
|
314
315
|
if (this.configs.has(account.id))
|
|
315
316
|
return;
|
|
316
317
|
// createAutoImapConfig auto-detects Gmail from server/username and sets up OAuth
|
|
317
|
-
//
|
|
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
|
+
// Find Google OAuth credentials — check ~/.mailx first, then iflow-direct package
|
|
322
|
+
let credPath = path.join(getConfigDir(), "google-credentials.json");
|
|
323
|
+
if (!fs.existsSync(credPath)) {
|
|
324
|
+
try {
|
|
325
|
+
const pkgDir = path.dirname(import.meta.resolve("@bobfrankston/iflow-direct").replace("file:///", "").replace("file://", ""));
|
|
326
|
+
for (const name of ["credentials.json", "iflow-credentials.json"]) {
|
|
327
|
+
const p = path.join(pkgDir, name);
|
|
328
|
+
if (fs.existsSync(p)) {
|
|
329
|
+
credPath = p;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch { /* iflow-direct not resolvable */ }
|
|
335
|
+
}
|
|
336
|
+
const tokenDir = path.join(getConfigDir(), "tokens", account.imap.user.replace(/[@.]/g, "_"));
|
|
337
|
+
tokenProvider = async () => {
|
|
338
|
+
const result = await authenticateOAuth(credPath, {
|
|
339
|
+
scope: "https://mail.google.com/ https://www.googleapis.com/auth/contacts.readonly",
|
|
340
|
+
tokenDirectory: tokenDir,
|
|
341
|
+
loginHint: account.imap.user,
|
|
342
|
+
});
|
|
343
|
+
return result?.access_token || "";
|
|
344
|
+
};
|
|
345
|
+
}
|
|
318
346
|
const config = createAutoImapConfig({
|
|
319
347
|
server: account.imap.host,
|
|
320
348
|
port: account.imap.port,
|
|
321
349
|
username: account.imap.user,
|
|
322
350
|
password: account.imap.password,
|
|
323
|
-
|
|
351
|
+
tokenProvider,
|
|
324
352
|
});
|
|
325
353
|
this.configs.set(account.id, config);
|
|
326
354
|
// Register account in DB
|
|
@@ -388,6 +416,11 @@ export class ImapManager extends EventEmitter {
|
|
|
388
416
|
this.db.beginTransaction();
|
|
389
417
|
try {
|
|
390
418
|
for (const msg of msgs) {
|
|
419
|
+
// Debug: log subjects with non-ASCII to trace encoding issues
|
|
420
|
+
if (msg.subject && /[^\x00-\x7F]/.test(msg.subject)) {
|
|
421
|
+
const hex = Buffer.from(msg.subject, "utf-8").subarray(0, 40).toString("hex");
|
|
422
|
+
console.log(` [encoding] subject: "${msg.subject.substring(0, 60)}" hex: ${hex}`);
|
|
423
|
+
}
|
|
391
424
|
if (msg.uid <= highestUid)
|
|
392
425
|
continue; // already have it
|
|
393
426
|
const source = msg.source || "";
|
|
@@ -500,7 +533,8 @@ export class ImapManager extends EventEmitter {
|
|
|
500
533
|
this.emit("folderCountsChanged", accountId, {});
|
|
501
534
|
}
|
|
502
535
|
};
|
|
503
|
-
|
|
536
|
+
const tomorrow = new Date(Date.now() + 86400000); // IMAP BEFORE is exclusive
|
|
537
|
+
messages = await client.fetchMessageByDate(folder.path, startDate, tomorrow, { source: false }, onChunk);
|
|
504
538
|
if (totalStored > 0) {
|
|
505
539
|
console.log(` ${folder.path}: ${totalStored} messages (streamed)`);
|
|
506
540
|
this.db.recalcFolderCounts(folderId);
|