@bobfrankston/mailx 1.0.64 → 1.0.66
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.66",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"postinstall": "node launcher/builder/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bobfrankston/iflow": "^1.0.
|
|
23
|
+
"@bobfrankston/iflow": "^1.0.31",
|
|
24
24
|
"@bobfrankston/miscinfo": "^1.0.6",
|
|
25
|
-
"@bobfrankston/oauthsupport": "^1.0.
|
|
25
|
+
"@bobfrankston/oauthsupport": "^1.0.16",
|
|
26
26
|
"@bobfrankston/rust-builder": "^0.1.2",
|
|
27
27
|
"mailparser": "^3.7.2",
|
|
28
28
|
"quill": "^2.0.3",
|
|
@@ -30,6 +30,8 @@ export declare class ImapManager extends EventEmitter {
|
|
|
30
30
|
constructor(db: MailxDB);
|
|
31
31
|
/** Get OAuth access token for an account (for SMTP auth) */
|
|
32
32
|
getOAuthToken(accountId: string): Promise<string | null>;
|
|
33
|
+
/** Accounts currently re-authenticating — all operations skip these */
|
|
34
|
+
private reauthenticating;
|
|
33
35
|
/** Force re-authentication for an OAuth account — deletes cached IMAP token, triggers browser consent */
|
|
34
36
|
reauthenticate(accountId: string): Promise<boolean>;
|
|
35
37
|
/** Delete a message directly on the IMAP server (for stuck outbox messages not in local DB) */
|
|
@@ -72,29 +72,42 @@ export class ImapManager extends EventEmitter {
|
|
|
72
72
|
return null;
|
|
73
73
|
return config.tokenProvider();
|
|
74
74
|
}
|
|
75
|
+
/** Accounts currently re-authenticating — all operations skip these */
|
|
76
|
+
reauthenticating = new Set();
|
|
75
77
|
/** Force re-authentication for an OAuth account — deletes cached IMAP token, triggers browser consent */
|
|
76
78
|
async reauthenticate(accountId) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return false;
|
|
81
|
-
// Delete only the IMAP token (not contacts — separate scope, separate consent)
|
|
82
|
-
const accountDir = account.imap.user.replace(/[@.]/g, "_");
|
|
83
|
-
const tokenDir = path.join(getConfigDir(), "tokens", accountDir);
|
|
84
|
-
const tokenPath = path.join(tokenDir, "token.json");
|
|
85
|
-
if (fs.existsSync(tokenPath)) {
|
|
86
|
-
fs.unlinkSync(tokenPath);
|
|
87
|
-
console.log(` [reauth] Deleted ${tokenPath}`);
|
|
88
|
-
}
|
|
89
|
-
// Re-register the account to get a fresh config with new tokenProvider
|
|
90
|
-
this.configs.delete(accountId);
|
|
91
|
-
await this.addAccount(account);
|
|
92
|
-
// Trigger the OAuth flow by requesting a token
|
|
79
|
+
if (this.reauthenticating.has(accountId))
|
|
80
|
+
return false; // already in progress
|
|
81
|
+
this.reauthenticating.add(accountId);
|
|
93
82
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
83
|
+
const settings = loadSettings();
|
|
84
|
+
const account = settings.accounts.find(a => a.id === accountId);
|
|
85
|
+
if (!account)
|
|
86
|
+
return false;
|
|
87
|
+
// Stop IDLE watcher for this account
|
|
88
|
+
const stopWatcher = this.watchers.get(accountId);
|
|
89
|
+
if (stopWatcher) {
|
|
90
|
+
try {
|
|
91
|
+
await stopWatcher();
|
|
92
|
+
}
|
|
93
|
+
catch { /* */ }
|
|
94
|
+
this.watchers.delete(accountId);
|
|
95
|
+
}
|
|
96
|
+
// Delete only the IMAP token (not contacts — separate scope, separate consent)
|
|
97
|
+
const accountDir = account.imap.user.replace(/[@.]/g, "_");
|
|
98
|
+
const tokenDir = path.join(getConfigDir(), "tokens", accountDir);
|
|
99
|
+
const tokenPath = path.join(tokenDir, "token.json");
|
|
100
|
+
if (fs.existsSync(tokenPath)) {
|
|
101
|
+
fs.unlinkSync(tokenPath);
|
|
102
|
+
console.log(` [reauth] Deleted ${tokenPath}`);
|
|
103
|
+
}
|
|
104
|
+
// Re-register the account to get a fresh config with new tokenProvider
|
|
105
|
+
this.configs.delete(accountId);
|
|
106
|
+
await this.addAccount(account);
|
|
107
|
+
// addAccount already pre-validates the token (opens browser if needed)
|
|
108
|
+
const config = this.configs.get(accountId);
|
|
109
|
+
if (config?.tokenProvider) {
|
|
96
110
|
console.log(` [reauth] ${accountId}: success`);
|
|
97
|
-
// Trigger a sync now that auth works
|
|
98
111
|
this.syncInbox().catch(() => { });
|
|
99
112
|
return true;
|
|
100
113
|
}
|
|
@@ -102,6 +115,9 @@ export class ImapManager extends EventEmitter {
|
|
|
102
115
|
catch (e) {
|
|
103
116
|
console.error(` [reauth] ${accountId}: ${e.message}`);
|
|
104
117
|
}
|
|
118
|
+
finally {
|
|
119
|
+
this.reauthenticating.delete(accountId);
|
|
120
|
+
}
|
|
105
121
|
return false;
|
|
106
122
|
}
|
|
107
123
|
/** Delete a message directly on the IMAP server (for stuck outbox messages not in local DB) */
|
|
@@ -137,6 +153,8 @@ export class ImapManager extends EventEmitter {
|
|
|
137
153
|
}
|
|
138
154
|
/** Create a fresh ImapClient for an account (disposable, single-use) */
|
|
139
155
|
createClient(accountId) {
|
|
156
|
+
if (this.reauthenticating.has(accountId))
|
|
157
|
+
throw new Error(`Account ${accountId} is re-authenticating`);
|
|
140
158
|
const config = this.configs.get(accountId);
|
|
141
159
|
if (!config)
|
|
142
160
|
throw new Error(`No config for account ${accountId}`);
|
|
@@ -539,6 +557,8 @@ export class ImapManager extends EventEmitter {
|
|
|
539
557
|
lastInboxCounts = new Map();
|
|
540
558
|
async quickInboxCheck() {
|
|
541
559
|
for (const [accountId] of this.configs) {
|
|
560
|
+
if (this.reauthenticating.has(accountId))
|
|
561
|
+
continue;
|
|
542
562
|
let client = null;
|
|
543
563
|
try {
|
|
544
564
|
const inbox = this.db.getFolders(accountId).find(f => f.specialUse === "inbox");
|