* fix: only show connection lost when the server check fails too

* fix: load the macOS tray icon from the right unpacked folder (#1728)

* fix: hide update and review buttons on disabled plugins (#1745)

* fix: save the database without blocking the server, bump to 26.11.0

* fix: try the key before the password for hosts with both (Plugin-SSH-Terminal#50)

* fix: add macOS Window menu item to reopen main window

* feat: upload Mac App Store screenshots on submit

* chore: drop internal note from changelog

* chore: update translations and plugin pins

* chore: lint, format, and bump version to 26.11.0
This commit is contained in:
Luke Gustafson authored and GitHub committed 2026-11-01 17:16:04 -06:00
1 parent c759a147c6
commit 1d4eac3e20
24 files changed
+337 -56

No files matched your search

+34 -6
View File
@@ -31,6 +31,8 @@ class DatabaseHealthMonitor {
private static instance: DatabaseHealthMonitor;
private listeners: Map<string, EventListener[]> = new Map();
private degradedActive: boolean = false;
private probe: (() => Promise<boolean>) | null = null;
private probing: Promise<void> | null = null;
private constructor() {}
@@ -83,22 +85,48 @@ class DatabaseHealthMonitor {
errorCode === "DATABASE_ERROR" ||
errorCode === "DB_CONNECTION_FAILED";
// A cancel is on purpose and says nothing about the backend.
if (errorCode === "ERR_CANCELED" || lowerMessage === "canceled") return;
const isBackendUnreachable =
errorCode === "ERR_NETWORK" ||
errorCode === "ECONNREFUSED" ||
errorCode === "ECONNABORTED" ||
errorCode === "ECONNRESET" ||
errorCode === "ETIMEDOUT" ||
errorCode === "ERR_CANCELED" ||
(lowerMessage.includes("network error") &&
errorLike.response === undefined) ||
errorLike.response === undefined);
// One slow request (like a plugin waiting on an SSH host) is not a lost
// backend, so a timeout only counts when a quick check fails too.
const isTimeout =
errorCode === "ECONNABORTED" ||
errorCode === "ETIMEDOUT" ||
lowerMessage.includes("request aborted") ||
lowerMessage.includes("timeout");
if (!(isDatabaseError || isBackendUnreachable)) {
return;
if (isDatabaseError || isBackendUnreachable) {
this.markDegraded(errorMessage, errorCode);
} else if (isTimeout) {
if (!this.probe) {
this.markDegraded(errorMessage, errorCode);
return;
}
this.probing ??= this.probe()
.catch(() => false)
.then((reachable) => {
if (!reachable) this.markDegraded(errorMessage, errorCode);
})
.finally(() => {
this.probing = null;
});
}
}
/** Sets the quick check used after a request times out. */
setReachabilityProbe(probe: (() => Promise<boolean>) | null) {
this.probe = probe;
}
private markDegraded(errorMessage: string, errorCode: string | undefined) {
if (!this.degradedActive) {
this.degradedActive = true;
this.emit("database-connection-degraded", {