@bobfrankston/mailx 1.0.93 → 1.0.94
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 +52 -23
- package/client/package.json +1 -1
- package/package.json +1 -1
- package/packages/mailx-imap/index.d.ts +1 -0
- package/packages/mailx-imap/index.js +3 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-server/index.js +13 -3
- package/packages/mailx-server/package.json +1 -1
- package/packages/mailx-types/index.d.ts +1 -0
- package/packages/mailx-types/package.json +1 -1
- package/showports.cmd +1 -0
package/client/app.js
CHANGED
|
@@ -562,41 +562,70 @@ onWsEvent((event) => {
|
|
|
562
562
|
showAlert(event.message, "ws-error");
|
|
563
563
|
break;
|
|
564
564
|
case "accountError": {
|
|
565
|
-
// Show
|
|
566
|
-
const msg = `${event.accountId}: ${event.
|
|
565
|
+
// Show actual error + hint in banner
|
|
566
|
+
const msg = `${event.accountId}: ${event.error}`;
|
|
567
567
|
showAlert(msg, `acct-${event.accountId}`);
|
|
568
|
-
// Add
|
|
568
|
+
// Add action button: Re-authenticate for OAuth, Retry for password accounts
|
|
569
569
|
const bannerText = document.getElementById("alert-text");
|
|
570
570
|
if (bannerText && bannerText.textContent === msg) {
|
|
571
571
|
const existing = bannerText.parentElement?.querySelector(".status-action");
|
|
572
572
|
if (!existing) {
|
|
573
573
|
const btn = document.createElement("button");
|
|
574
|
-
btn.textContent = "Re-authenticate";
|
|
575
574
|
btn.className = "status-action";
|
|
576
|
-
|
|
577
|
-
btn.
|
|
578
|
-
btn.
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
acctEl
|
|
587
|
-
acctEl
|
|
575
|
+
if (event.isOAuth) {
|
|
576
|
+
btn.textContent = "Re-authenticate";
|
|
577
|
+
btn.addEventListener("click", async () => {
|
|
578
|
+
btn.disabled = true;
|
|
579
|
+
btn.textContent = "Authenticating...";
|
|
580
|
+
try {
|
|
581
|
+
const res = await fetch(`/api/reauth/${event.accountId}`, { method: "POST" });
|
|
582
|
+
const data = await res.json();
|
|
583
|
+
if (data.ok) {
|
|
584
|
+
hideAlert();
|
|
585
|
+
const acctEl = document.getElementById("status-accounts");
|
|
586
|
+
if (acctEl) {
|
|
587
|
+
acctEl.textContent = `${event.accountId}: reconnected`;
|
|
588
|
+
acctEl.style.color = "";
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
btn.textContent = "Re-authenticate";
|
|
593
|
+
btn.disabled = false;
|
|
588
594
|
}
|
|
589
595
|
}
|
|
590
|
-
|
|
596
|
+
catch {
|
|
591
597
|
btn.textContent = "Re-authenticate";
|
|
592
598
|
btn.disabled = false;
|
|
593
599
|
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
btn.textContent = "Retry";
|
|
604
|
+
btn.addEventListener("click", async () => {
|
|
605
|
+
btn.disabled = true;
|
|
606
|
+
btn.textContent = "Syncing...";
|
|
607
|
+
try {
|
|
608
|
+
const res = await fetch(`/api/sync/${event.accountId}`, { method: "POST" });
|
|
609
|
+
const data = await res.json();
|
|
610
|
+
if (data.ok) {
|
|
611
|
+
hideAlert();
|
|
612
|
+
const acctEl = document.getElementById("status-accounts");
|
|
613
|
+
if (acctEl) {
|
|
614
|
+
acctEl.textContent = `${event.accountId}: reconnected`;
|
|
615
|
+
acctEl.style.color = "";
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
btn.textContent = "Retry";
|
|
620
|
+
btn.disabled = false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
btn.textContent = "Retry";
|
|
625
|
+
btn.disabled = false;
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
600
629
|
bannerText.parentElement?.insertBefore(btn, document.getElementById("alert-dismiss"));
|
|
601
630
|
}
|
|
602
631
|
}
|
package/client/package.json
CHANGED
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@ export interface ImapManagerEvents {
|
|
|
17
17
|
total: number;
|
|
18
18
|
unread: number;
|
|
19
19
|
}>) => void;
|
|
20
|
+
accountError: (accountId: string, error: string, hint: string, isOAuth: boolean) => void;
|
|
20
21
|
}
|
|
21
22
|
export declare class ImapManager extends EventEmitter {
|
|
22
23
|
private configs;
|
|
@@ -210,7 +210,7 @@ export class ImapManager extends EventEmitter {
|
|
|
210
210
|
console.error(` [auth] ${account.id}: ${imapError(e)}`);
|
|
211
211
|
if (!this.accountErrorShown.has(account.id)) {
|
|
212
212
|
this.accountErrorShown.add(account.id);
|
|
213
|
-
this.emit("accountError", account.id, imapError(e), "
|
|
213
|
+
this.emit("accountError", account.id, imapError(e), "Authentication may have expired", true);
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
}
|
|
@@ -480,7 +480,7 @@ export class ImapManager extends EventEmitter {
|
|
|
480
480
|
const hint = errMsg.includes("max_userip_connections") || errMsg.includes("Too many")
|
|
481
481
|
? "Too many connections — backing off"
|
|
482
482
|
: isOAuth ? "Authentication may have expired" : "Check server connectivity";
|
|
483
|
-
this.emit("accountError", accountId, errMsg, hint);
|
|
483
|
+
this.emit("accountError", accountId, errMsg, hint, isOAuth);
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
finally {
|
|
@@ -538,6 +538,7 @@ export class ImapManager extends EventEmitter {
|
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
540
|
}
|
|
541
|
+
this.accountErrorShown.delete(accountId);
|
|
541
542
|
this.emit("syncComplete", accountId);
|
|
542
543
|
}
|
|
543
544
|
}
|
|
@@ -122,6 +122,11 @@ ${accountInfo.map((a) => `<tr><td>${a.name}</td><td>${a.folders}</td><td>${a.inb
|
|
|
122
122
|
<p style="margin-top:2rem;font-size:0.8rem"><a href="/">Open mailx</a> | Auto-refreshes every 10s</p>
|
|
123
123
|
</body></html>`);
|
|
124
124
|
});
|
|
125
|
+
// Graceful exit — close IMAP, DB, HTTP then exit
|
|
126
|
+
app.post("/api/exit", (req, res) => {
|
|
127
|
+
res.json({ ok: true });
|
|
128
|
+
setTimeout(() => shutdown(), 100);
|
|
129
|
+
});
|
|
125
130
|
// Restart server + reload clients
|
|
126
131
|
app.post("/api/restart", (req, res) => {
|
|
127
132
|
res.json({ ok: true });
|
|
@@ -203,8 +208,8 @@ imapManager.on("folderCountsChanged", (accountId, counts) => {
|
|
|
203
208
|
imapManager.on("syncError", (accountId, error) => {
|
|
204
209
|
broadcast({ type: "error", message: `${accountId}: ${error}` });
|
|
205
210
|
});
|
|
206
|
-
imapManager.on("accountError", (accountId, error, hint) => {
|
|
207
|
-
broadcast({ type: "accountError", accountId, error, hint });
|
|
211
|
+
imapManager.on("accountError", (accountId, error, hint, isOAuth) => {
|
|
212
|
+
broadcast({ type: "accountError", accountId, error, hint, isOAuth });
|
|
208
213
|
});
|
|
209
214
|
// ── Startup ──
|
|
210
215
|
async function start() {
|
|
@@ -286,7 +291,12 @@ process.on("unhandledRejection", (err) => {
|
|
|
286
291
|
process.on("uncaughtException", (err) => {
|
|
287
292
|
console.error("FATAL uncaught exception:", err.message);
|
|
288
293
|
console.error(err.stack);
|
|
289
|
-
//
|
|
294
|
+
// EADDRINUSE = another instance holds the port — exit so node --watch can retry
|
|
295
|
+
if (err.code === "EADDRINUSE") {
|
|
296
|
+
console.error("Port in use — exiting so node --watch can retry");
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
// Other exceptions: stay alive, let node --watch handle file-change restarts
|
|
290
300
|
});
|
|
291
301
|
process.on("exit", (code) => {
|
|
292
302
|
console.log(`Process exiting with code ${code}`);
|
package/showports.cmd
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
netstat -ano | findstr :9333
|