@bobfrankston/mailx 1.0.288 → 1.0.290
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/app.js
CHANGED
|
@@ -1019,7 +1019,11 @@ onWsEvent((event) => {
|
|
|
1019
1019
|
}
|
|
1020
1020
|
break;
|
|
1021
1021
|
case "folderCountsChanged": {
|
|
1022
|
-
// Update folder badges +
|
|
1022
|
+
// Update folder badges + full tree refresh (not just counts) so
|
|
1023
|
+
// newly-synced folders appear. Without this, the first sync after
|
|
1024
|
+
// setup creates DB folders but the tree never picks them up because
|
|
1025
|
+
// syncComplete fires during page reload and gets lost.
|
|
1026
|
+
refreshFolderTree();
|
|
1023
1027
|
updateFolderCounts();
|
|
1024
1028
|
updateNewMessageCount();
|
|
1025
1029
|
// Debounced silent reload — preserves scroll position, selection, and viewer
|
|
@@ -482,22 +482,34 @@ async function loadFolderTree(container) {
|
|
|
482
482
|
"aol.com": "Use an app password: AOL Settings → Account Security → Generate app password",
|
|
483
483
|
"icloud.com": "Use an app-specific password: appleid.apple.com → Sign-In and Security → App-Specific Passwords",
|
|
484
484
|
};
|
|
485
|
+
let oauthAutoFired = false;
|
|
485
486
|
emailInput?.addEventListener("input", () => {
|
|
486
487
|
const email = emailInput.value.trim();
|
|
487
488
|
const domain = email.split("@")[1]?.toLowerCase() || "";
|
|
488
489
|
const hasAt = email.includes("@") && domain.length > 0;
|
|
489
490
|
const isOAuth = ["gmail.com", "googlemail.com", "outlook.com", "hotmail.com", "live.com"].includes(domain);
|
|
490
|
-
|
|
491
|
-
//
|
|
491
|
+
const isGmailLike = ["gmail.com", "googlemail.com"].includes(domain);
|
|
492
|
+
// OAuth providers: auto-fire setup immediately once domain
|
|
493
|
+
// is recognized — don't show name/password (name is auto-
|
|
494
|
+
// detected from Google profile, no password needed). This
|
|
495
|
+
// eliminates the "form flash" where fields briefly appear
|
|
496
|
+
// before the page reloads.
|
|
497
|
+
if (hasAt && isOAuth && !oauthAutoFired && !setupTriggered) {
|
|
498
|
+
oauthAutoFired = true;
|
|
499
|
+
statusEl.textContent = `Connecting to ${isGmailLike ? "Gmail" : "Outlook"}...`;
|
|
500
|
+
trySetup();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
// Non-OAuth: progressive reveal of name + password + submit.
|
|
492
504
|
const nameRow = document.getElementById("setup-name-row");
|
|
493
505
|
const pwRow = document.getElementById("setup-password-row");
|
|
494
506
|
const submitBtn = document.getElementById("setup-submit");
|
|
495
507
|
if (nameRow)
|
|
496
|
-
nameRow.style.display = hasAt ? "block" : "none";
|
|
508
|
+
nameRow.style.display = hasAt && !isOAuth ? "block" : "none";
|
|
497
509
|
if (pwRow)
|
|
498
510
|
pwRow.style.display = hasAt && !isOAuth ? "block" : "none";
|
|
499
511
|
if (submitBtn)
|
|
500
|
-
submitBtn.style.display = hasAt ? "block" : "none";
|
|
512
|
+
submitBtn.style.display = hasAt && !isOAuth ? "block" : "none";
|
|
501
513
|
const helpEl = document.getElementById("setup-app-password-help");
|
|
502
514
|
if (helpEl) {
|
|
503
515
|
const help = APP_PASSWORD_HELP[domain];
|
package/package.json
CHANGED
|
@@ -58,18 +58,25 @@ async function getGoogleDriveToken() {
|
|
|
58
58
|
console.error(" [cloud] No Google credentials found (checked ~/.mailx/google-credentials.json and iflow package)");
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
|
-
// Delete stale token if scope changed (drive → drive.file or vice versa)
|
|
62
61
|
const tokenPath = path.join(GDRIVE_TOKEN_DIR, "token.json");
|
|
62
|
+
// Log token state so failures are diagnosable
|
|
63
63
|
if (fs.existsSync(tokenPath)) {
|
|
64
64
|
try {
|
|
65
65
|
const existing = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
|
|
66
|
+
const expired = existing.expires_at ? Date.now() >= existing.expires_at : true;
|
|
67
|
+
const hasRefresh = !!existing.refresh_token;
|
|
68
|
+
console.log(` [cloud] GDrive token: ${expired ? "expired" : "valid"}, refresh_token: ${hasRefresh ? "yes" : "NO"}, scope: ${existing.scope || "?"}`);
|
|
69
|
+
// Delete stale token if scope changed (drive → drive.file or vice versa)
|
|
66
70
|
if (existing.scope && existing.scope !== GDRIVE_SCOPES) {
|
|
67
|
-
console.log(` [cloud] Scope changed — re-authenticating...`);
|
|
71
|
+
console.log(` [cloud] Scope changed (${existing.scope} → ${GDRIVE_SCOPES}) — re-authenticating...`);
|
|
68
72
|
fs.unlinkSync(tokenPath);
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
catch { /* ignore parse errors */ }
|
|
72
76
|
}
|
|
77
|
+
else {
|
|
78
|
+
console.log(` [cloud] GDrive token: not found at ${tokenPath}`);
|
|
79
|
+
}
|
|
73
80
|
try {
|
|
74
81
|
const token = await authenticateOAuth(creds, {
|
|
75
82
|
scope: GDRIVE_SCOPES,
|
|
@@ -78,10 +85,16 @@ async function getGoogleDriveToken() {
|
|
|
78
85
|
credentialsKey: "installed",
|
|
79
86
|
includeOfflineAccess: true,
|
|
80
87
|
});
|
|
88
|
+
if (token?.access_token) {
|
|
89
|
+
console.log(` [cloud] GDrive auth: success (token refreshed/valid)`);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.error(` [cloud] GDrive auth: returned null — OAuth callback likely timed out or was denied`);
|
|
93
|
+
}
|
|
81
94
|
return token?.access_token || null;
|
|
82
95
|
}
|
|
83
96
|
catch (e) {
|
|
84
|
-
console.error(` [cloud]
|
|
97
|
+
console.error(` [cloud] GDrive auth FAILED: ${e.message}`);
|
|
85
98
|
return null;
|
|
86
99
|
}
|
|
87
100
|
}
|
|
@@ -145,7 +145,8 @@ export async function cloudRead(filename) {
|
|
|
145
145
|
* and the onCloudError listener. */
|
|
146
146
|
export async function cloudWrite(filename, content) {
|
|
147
147
|
if (!pendingCloudConfig) {
|
|
148
|
-
const err = `
|
|
148
|
+
const err = `Cloud not initialized yet — cannot save ${filename} (pendingCloudConfig is null; loadSettings may not have run, or config.jsonc has no sharedDir)`;
|
|
149
|
+
console.error(` [cloud] cloudWrite: ${err}`);
|
|
149
150
|
setCloudError(err, { op: "write", filename });
|
|
150
151
|
throw new Error(err);
|
|
151
152
|
}
|