@bobfrankston/mailx 1.0.298 → 1.0.300
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/package.json
CHANGED
|
@@ -468,7 +468,7 @@ export class ImapManager extends EventEmitter {
|
|
|
468
468
|
// a hung OAuth server could block the entire sync thread indefinitely.
|
|
469
469
|
const TOKEN_FETCH_TIMEOUT_MS = 30000;
|
|
470
470
|
const authPromise = authenticateOAuth(credPath, {
|
|
471
|
-
scope: "https://mail.google.com/ https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/calendar",
|
|
471
|
+
scope: "https://mail.google.com/ https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/drive",
|
|
472
472
|
tokenDirectory: tokenDir,
|
|
473
473
|
credentialsKey: "installed",
|
|
474
474
|
loginHint: account.imap.user,
|
|
@@ -1060,8 +1060,20 @@ export class ImapManager extends EventEmitter {
|
|
|
1060
1060
|
console.log(` [api] ${accountId}/${folder.path}: syncing (highestUid=${highestUid})...`);
|
|
1061
1061
|
let messages;
|
|
1062
1062
|
if (highestUid > 0) {
|
|
1063
|
-
// Incremental: fetch messages since last known UID
|
|
1063
|
+
// Incremental: fetch messages since last known UID.
|
|
1064
|
+
// Gmail "UIDs" are hashed (not chronological), so fetchSince
|
|
1065
|
+
// returns messages in hash order — they can be from ANY date.
|
|
1066
|
+
// Filter by the history window so years-old messages don't
|
|
1067
|
+
// suddenly flood the inbox on every incremental sync.
|
|
1064
1068
|
messages = await api.fetchSince(folder.path, highestUid, { source: false });
|
|
1069
|
+
if (effectiveDays > 0) {
|
|
1070
|
+
const cutoff = startDate.getTime();
|
|
1071
|
+
const before = messages.length;
|
|
1072
|
+
messages = messages.filter(m => !m.date || m.date.getTime() >= cutoff);
|
|
1073
|
+
if (messages.length < before) {
|
|
1074
|
+
console.log(` [api] ${accountId}/${folder.path}: filtered ${before - messages.length} messages older than ${effectiveDays}d`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1065
1077
|
}
|
|
1066
1078
|
else {
|
|
1067
1079
|
// First sync: fetch by date range
|
|
@@ -59,30 +59,69 @@ const GDRIVE_SCOPES = "https://www.googleapis.com/auth/drive";
|
|
|
59
59
|
* is the auto-created default. First one that exists on GDrive wins. */
|
|
60
60
|
const GDRIVE_PATH_SEARCH_ORDER = ["home/.mailx", "mailx"];
|
|
61
61
|
// ── Token helpers ──
|
|
62
|
+
/** Get a GDrive-capable token. Prefers the Gmail token (which now includes
|
|
63
|
+
* drive scope) — avoids a second OAuth consent prompt. Falls back to the
|
|
64
|
+
* dedicated GDrive token dir for non-Gmail setups or when Gmail token
|
|
65
|
+
* doesn't have drive scope yet. */
|
|
62
66
|
async function getGoogleDriveToken() {
|
|
67
|
+
// Strategy 1: reuse a Gmail token that already has drive scope.
|
|
68
|
+
// Scan the tokens/ dir for any account token with drive in its scope.
|
|
69
|
+
const tokensDir = path.join(SETTINGS_DIR, "tokens");
|
|
70
|
+
if (fs.existsSync(tokensDir)) {
|
|
71
|
+
try {
|
|
72
|
+
for (const entry of fs.readdirSync(tokensDir)) {
|
|
73
|
+
if (entry === "gdrive")
|
|
74
|
+
continue;
|
|
75
|
+
const tokenPath = path.join(tokensDir, entry, "oauth-token.json");
|
|
76
|
+
if (!fs.existsSync(tokenPath))
|
|
77
|
+
continue;
|
|
78
|
+
try {
|
|
79
|
+
const tok = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
|
|
80
|
+
if (tok.scope?.includes("drive") && tok.refresh_token) {
|
|
81
|
+
console.log(` [cloud] Reusing Gmail token from ${entry} (has drive scope)`);
|
|
82
|
+
const creds = findGoogleCredentials();
|
|
83
|
+
if (!creds)
|
|
84
|
+
continue;
|
|
85
|
+
const refreshed = await authenticateOAuth(creds, {
|
|
86
|
+
scope: tok.scope,
|
|
87
|
+
tokenDirectory: path.join(tokensDir, entry),
|
|
88
|
+
tokenFileName: "oauth-token.json",
|
|
89
|
+
credentialsKey: "installed",
|
|
90
|
+
includeOfflineAccess: true,
|
|
91
|
+
});
|
|
92
|
+
if (refreshed?.access_token) {
|
|
93
|
+
console.log(` [cloud] GDrive auth: success (via Gmail token)`);
|
|
94
|
+
return refreshed.access_token;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch { /* skip this token */ }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch { /* tokens dir unreadable */ }
|
|
102
|
+
}
|
|
103
|
+
// Strategy 2: dedicated GDrive token (legacy path or non-Gmail setups)
|
|
63
104
|
const creds = findGoogleCredentials();
|
|
64
105
|
if (!creds) {
|
|
65
106
|
console.error(" [cloud] No Google credentials found (checked ~/.mailx/google-credentials.json and iflow package)");
|
|
66
107
|
return null;
|
|
67
108
|
}
|
|
68
109
|
const tokenPath = path.join(GDRIVE_TOKEN_DIR, "token.json");
|
|
69
|
-
// Log token state so failures are diagnosable
|
|
70
110
|
if (fs.existsSync(tokenPath)) {
|
|
71
111
|
try {
|
|
72
112
|
const existing = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
|
|
73
113
|
const expired = existing.expires_at ? Date.now() >= existing.expires_at : true;
|
|
74
114
|
const hasRefresh = !!existing.refresh_token;
|
|
75
|
-
console.log(` [cloud] GDrive token: ${expired ? "expired" : "valid"}, refresh_token: ${hasRefresh ? "yes" : "NO"}, scope: ${existing.scope || "?"}`);
|
|
76
|
-
// Delete stale token if scope changed (drive → drive.file or vice versa)
|
|
115
|
+
console.log(` [cloud] GDrive token (dedicated): ${expired ? "expired" : "valid"}, refresh_token: ${hasRefresh ? "yes" : "NO"}, scope: ${existing.scope || "?"}`);
|
|
77
116
|
if (existing.scope && existing.scope !== GDRIVE_SCOPES) {
|
|
78
117
|
console.log(` [cloud] Scope changed (${existing.scope} → ${GDRIVE_SCOPES}) — re-authenticating...`);
|
|
79
118
|
fs.unlinkSync(tokenPath);
|
|
80
119
|
}
|
|
81
120
|
}
|
|
82
|
-
catch { /* ignore
|
|
121
|
+
catch { /* ignore */ }
|
|
83
122
|
}
|
|
84
123
|
else {
|
|
85
|
-
console.log(` [cloud] GDrive token: not found at ${tokenPath}`);
|
|
124
|
+
console.log(` [cloud] GDrive token: not found at ${tokenPath} (will auth)`);
|
|
86
125
|
}
|
|
87
126
|
try {
|
|
88
127
|
const token = await authenticateOAuth(creds, {
|
|
@@ -93,10 +132,10 @@ async function getGoogleDriveToken() {
|
|
|
93
132
|
includeOfflineAccess: true,
|
|
94
133
|
});
|
|
95
134
|
if (token?.access_token) {
|
|
96
|
-
console.log(` [cloud] GDrive auth: success (token
|
|
135
|
+
console.log(` [cloud] GDrive auth: success (dedicated token)`);
|
|
97
136
|
}
|
|
98
137
|
else {
|
|
99
|
-
console.error(` [cloud] GDrive auth: returned null — OAuth
|
|
138
|
+
console.error(` [cloud] GDrive auth: returned null — OAuth likely timed out`);
|
|
100
139
|
}
|
|
101
140
|
return token?.access_token || null;
|
|
102
141
|
}
|