@bobfrankston/mailx 1.0.152 → 1.0.154

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
@@ -399,16 +399,17 @@ async function runSetup() {
399
399
  const { gDriveFindOrCreateFolder, getCloudProvider } = await import("@bobfrankston/mailx-settings/cloud.js");
400
400
  const folderId = await gDriveFindOrCreateFolder();
401
401
  if (folderId) {
402
+ console.log(` Drive folder: My Drive/mailx/ (${folderId})`);
402
403
  const gdrive = getCloudProvider("gdrive", folderId);
403
404
  if (gdrive) {
404
- // Check for existing accounts on Drive
405
- const existing = await gdrive.read("accounts.jsonc") || await gdrive.read("settings.jsonc");
405
+ // Read accounts.jsonc (canonical) ignore legacy settings.jsonc
406
+ const existing = await gdrive.read("accounts.jsonc");
406
407
  if (existing) {
407
408
  const { parse: parseJsonc } = await import("jsonc-parser");
408
409
  const data = parseJsonc(existing);
409
410
  const accts = data?.accounts || (Array.isArray(data) ? data : []);
410
411
  if (accts.length > 0) {
411
- console.log(`\nFound ${accts.length} existing account(s) on Google Drive!`);
412
+ console.log(`\nFound ${accts.length} existing account(s) on Google Drive (My Drive/mailx/accounts.jsonc):`);
412
413
  for (const a of accts)
413
414
  console.log(` • ${a.label || a.name || a.email}`);
414
415
  // Save config pointing to Drive — no prompts needed
@@ -685,7 +686,7 @@ async function main() {
685
686
  });
686
687
  // Wait for WebView2 initialization before starting IMAP (stdin writes during init crash wry)
687
688
  await new Promise(r => setTimeout(r, 2000));
688
- // Add accounts and start sync
689
+ // Register all accounts (OAuth may open browser for Gmail — event loop stays free for IPC)
689
690
  for (const account of settings.accounts) {
690
691
  if (!account.enabled)
691
692
  continue;
@@ -697,6 +698,7 @@ async function main() {
697
698
  console.error(` Failed: ${account.id}: ${e.message}`);
698
699
  }
699
700
  }
701
+ // Start sync in background — don't block
700
702
  if (settings.accounts.some(a => a.enabled)) {
701
703
  imapManager.syncAll().catch(e => console.error(` Sync error: ${e.message}`));
702
704
  }
package/client/app.js CHANGED
@@ -898,7 +898,19 @@ optAutocomplete?.addEventListener("change", () => {
898
898
  }).catch(() => { });
899
899
  });
900
900
  const isApp = typeof mailxapi !== "undefined" && mailxapi?.isApp;
901
- const versionPromise = getVersion();
901
+ // Retry getVersion — IPC may not be ready on first call
902
+ async function getVersionWithRetry() {
903
+ for (let i = 0; i < 5; i++) {
904
+ try {
905
+ return await getVersion();
906
+ }
907
+ catch {
908
+ await new Promise(r => setTimeout(r, 1000));
909
+ }
910
+ }
911
+ return { version: "?", storage: {} };
912
+ }
913
+ const versionPromise = getVersionWithRetry();
902
914
  versionPromise.then((d) => {
903
915
  const el = document.getElementById("app-version");
904
916
  const storage = d.storage || {};
@@ -907,10 +919,10 @@ versionPromise.then((d) => {
907
919
  : "";
908
920
  if (el) {
909
921
  el.textContent = `mailx v${d.version}${storageLabel}${isApp ? "" : " [browser]"}`;
910
- // Tooltip: show cloud path and access mode on hover
922
+ // Tooltip: show Drive path on hover
911
923
  if (storage.provider && storage.provider !== "local") {
912
- const modeDesc = storage.mode === "api" ? "API" : "mount";
913
- el.title = `${storage.cloudPath || storage.provider} (${modeDesc})`;
924
+ const drivePath = storage.cloudPath ? `My Drive/${storage.cloudPath}/` : storage.provider;
925
+ el.title = drivePath;
914
926
  }
915
927
  }
916
928
  if (d.settingsError) {
@@ -19,7 +19,7 @@
19
19
  var timer = setTimeout(function() {
20
20
  delete _callbacks[id];
21
21
  reject(new Error("mailxapi timeout: " + action));
22
- }, 30000);
22
+ }, 120000);
23
23
  _callbacks[id] = { resolve: resolve, reject: reject, timer: timer };
24
24
  var msg = Object.assign({ _action: action, _cbid: id }, params || {});
25
25
  if (window.ipc && window.ipc.postMessage) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.152",
3
+ "version": "1.0.154",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -23,7 +23,7 @@
23
23
  "@bobfrankston/iflow": "^1.0.53",
24
24
  "@bobfrankston/miscinfo": "^1.0.7",
25
25
  "@bobfrankston/oauthsupport": "^1.0.20",
26
- "@bobfrankston/msger": "^0.1.202",
26
+ "@bobfrankston/msger": "^0.1.204",
27
27
  "@capacitor/android": "^8.3.0",
28
28
  "@capacitor/cli": "^8.3.0",
29
29
  "@capacitor/core": "^8.3.0",
@@ -108,7 +108,7 @@ export class ImapManager extends EventEmitter {
108
108
  connectionBackoff = new Map();
109
109
  /** Per-account connection semaphore — limits concurrent IMAP connections */
110
110
  connectionSemaphore = new Map();
111
- static MAX_CONNECTIONS = 5; // sync, fetch, IDLE, outbox, sync-actions
111
+ static MAX_CONNECTIONS = 2; // 1 for all operations (sequential), 1 for IDLE
112
112
  constructor(db) {
113
113
  super();
114
114
  this.db = db;
@@ -78,13 +78,11 @@ function getSharedDir() {
78
78
  if (resolved)
79
79
  return resolved;
80
80
  }
81
- // Nothing mounted save last provider entry for API fallback (log once)
81
+ // Set pending cloud config for API access
82
82
  if (!pendingCloudConfig) {
83
83
  const lastProvider = [...entries].reverse().find(e => typeof e !== "string");
84
- if (lastProvider) {
84
+ if (lastProvider)
85
85
  pendingCloudConfig = lastProvider;
86
- console.log(` No cloud drive mounted — will try ${lastProvider.provider} API`);
87
- }
88
86
  }
89
87
  }
90
88
  // Legacy settingsPath no longer used for shared dir — use loadLegacySettings() for reading only.
@@ -388,7 +386,7 @@ export function loadAccounts() {
388
386
  }
389
387
  const raw = accounts.accounts || accounts;
390
388
  const globalName = accounts.name || "";
391
- const result = raw.map((a) => normalizeAccount(a, globalName));
389
+ const result = deduplicateAccounts(raw.map((a) => normalizeAccount(a, globalName)));
392
390
  return applyAccountOverrides(result);
393
391
  }
394
392
  // Legacy: read from settings.jsonc
@@ -397,6 +395,27 @@ export function loadAccounts() {
397
395
  return applyAccountOverrides(legacy.accounts.map((a) => normalizeAccount(a, legacy.name)));
398
396
  return DEFAULT_ACCOUNTS;
399
397
  }
398
+ /** Normalize email for dedup — Gmail ignores dots before @ */
399
+ function normalizeEmail(email) {
400
+ const [local, domain] = email.toLowerCase().split("@");
401
+ if (!domain)
402
+ return email.toLowerCase();
403
+ if (domain === "gmail.com" || domain === "googlemail.com") {
404
+ return local.replace(/\./g, "") + "@gmail.com";
405
+ }
406
+ return `${local}@${domain}`;
407
+ }
408
+ /** Remove duplicate accounts (same email after normalization) */
409
+ function deduplicateAccounts(accounts) {
410
+ const seen = new Set();
411
+ return accounts.filter(a => {
412
+ const key = normalizeEmail(a.email);
413
+ if (seen.has(key))
414
+ return false;
415
+ seen.add(key);
416
+ return true;
417
+ });
418
+ }
400
419
  /** Apply local per-account overrides (enabled, etc.) */
401
420
  function applyAccountOverrides(accounts) {
402
421
  const localConfig = readLocalConfig();
@@ -430,14 +449,7 @@ export async function loadAccountsAsync() {
430
449
  return applyAccountOverrides(raw.map((a) => normalizeAccount(a, globalName)));
431
450
  }
432
451
  }
433
- // Also try legacy settings.jsonc
434
- const legacy = await cloudRead("settings.jsonc");
435
- if (legacy) {
436
- const data = parseJsonc(legacy);
437
- if (data?.accounts) {
438
- return applyAccountOverrides(data.accounts.map((a) => normalizeAccount(a, data.name)));
439
- }
440
- }
452
+ // Legacy settings.jsonc is no longer read — use accounts.jsonc only
441
453
  }
442
454
  return [];
443
455
  }