@bobfrankston/mailx 1.0.152 → 1.0.153
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 +4 -3
- package/client/app.js +16 -4
- package/package.json +2 -2
- package/packages/mailx-imap/index.js +1 -1
- package/packages/mailx-settings/index.js +25 -13
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
|
-
//
|
|
405
|
-
const existing = await gdrive.read("accounts.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
|
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
|
-
|
|
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
|
|
922
|
+
// Tooltip: show Drive path on hover
|
|
911
923
|
if (storage.provider && storage.provider !== "local") {
|
|
912
|
-
const
|
|
913
|
-
el.title =
|
|
924
|
+
const drivePath = storage.cloudPath ? `My Drive/${storage.cloudPath}/` : storage.provider;
|
|
925
|
+
el.title = drivePath;
|
|
914
926
|
}
|
|
915
927
|
}
|
|
916
928
|
if (d.settingsError) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.153",
|
|
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.
|
|
26
|
+
"@bobfrankston/msger": "^0.1.203",
|
|
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 =
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
}
|