@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 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="Restart server and reload page">
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="Restart the server process">Restart server</button>
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.165",
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.215",
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
- // Token directory in ~/.mailx/ so tokens persist across npm reinstalls
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
- // tokenDirectory is handled by oauthsupport, not iflow-direct
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
- messages = await client.fetchMessageByDate(folder.path, startDate, new Date(), { source: false }, onChunk);
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);