fix: reject duplicate WebSocket logins to prevent reconnect ping-pong

Server now checks IsUserConnected before accepting a new WebSocket and
returns an auth_error with a clear message instead of silently replacing
the old session. Client dispatcher surfaces the error via transient UI
state so the ConnectPage can display it.
This commit is contained in:
jevb
2026-03-18 02:41:55 +01:00
parent 6f35973c1b
commit b53c729f89
4 changed files with 28 additions and 23 deletions
@@ -4,6 +4,7 @@
import type { WsClient } from "./ws";
import { authStore, setAuth, clearAuth } from "@stores/auth.store";
import { setTransientError } from "@stores/ui.store";
import {
setChannels,
setActiveChannel,
@@ -67,6 +68,7 @@ export function wireDispatcher(ws: WsClient): DispatcherCleanup {
unsubs.push(
ws.on("auth_error", (payload) => {
log.error("Auth failed", { message: payload.message });
setTransientError(payload.message);
clearAuth();
}),
);
+8 -1
View File
@@ -9,7 +9,7 @@ import {
qs,
} from "@lib/dom";
import type { MountableComponent } from "@lib/safe-render";
import { openSettings, closeSettings } from "@stores/ui.store";
import { openSettings, closeSettings, uiStore, setTransientError } from "@stores/ui.store";
import { createSettingsOverlay } from "@components/SettingsOverlay";
import type { HealthStatus, ServerProfile } from "@lib/profiles";
import { loadCredential } from "@lib/credentials";
@@ -788,6 +788,13 @@ export function createConnectPage(
});
settingsOverlay.mount(rootEl);
// Show any pending auth error (e.g. "already connected from another client")
const pendingError = uiStore.getState().transientError;
if (pendingError) {
transitionTo("error", pendingError);
setTransientError(null);
}
// Focus the first input
hostInput.focus();
}