fix(app): remove test connection button that broke host status

The Test button in settings was creating a separate DaemonClientV2
instance without the Tauri WebSocket transport. In the desktop app,
this caused origin rejection (tauri://localhost) which then overwrote
the connection status to offline, even though the main session
connection was working fine.

Removed the redundant test functionality since:
- The daemon client auto-reconnects
- Connection status is already tracked and displayed via SessionProvider
- The test was actively harmful by creating a rogue client

Also simplified waitForDaemonRestart to rely on auto-reconnect instead
of polling with a separate connection.
This commit is contained in:
Mohamed Boudra
2026-01-22 21:31:45 +07:00
parent d41267e8a5
commit 6770e8d510
+12 -143
View File
@@ -22,8 +22,7 @@ import { formatConnectionStatus, getConnectionStatusTone } from "@/utils/daemons
import { theme as defaultTheme } from "@/styles/theme";
import { MenuHeader } from "@/components/headers/menu-header";
import { useSessionStore } from "@/stores/session-store";
import { DaemonClientV2 } from "@server/client/daemon-client-v2";
import { buildDaemonWebSocketUrl, normalizeHostPort } from "@/utils/daemon-endpoints";
import { normalizeHostPort } from "@/utils/daemon-endpoints";
const delay = (ms: number) =>
new Promise<void>((resolve) => {
@@ -374,10 +373,6 @@ const styles = StyleSheet.create((theme) => ({
},
}));
type DaemonTestState = {
status: "idle" | "testing" | "success" | "error";
message?: string;
};
export default function SettingsScreen() {
const { settings, isLoading: settingsLoading, updateSettings, resetSettings } = useAppSettings();
@@ -400,7 +395,6 @@ export default function SettingsScreen() {
offerUrl: string;
}>({ id: null, label: "", endpoint: "", offerUrl: "" });
const [isSavingDaemon, setIsSavingDaemon] = useState(false);
const [daemonTestStates, setDaemonTestStates] = useState<Map<string, DaemonTestState>>(() => new Map());
const isLoading = settingsLoading || daemonLoading;
const isMountedRef = useRef(true);
@@ -427,40 +421,6 @@ export default function SettingsScreen() {
[]
);
const testServerConnection = useCallback(async (url: string, timeoutMs = 5000) => {
const client = new DaemonClientV2({
url,
suppressSendErrors: true,
reconnect: { enabled: false },
});
let timeoutId: ReturnType<typeof setTimeout> | null = null;
try {
await new Promise<void>((resolve, reject) => {
timeoutId = setTimeout(() => {
reject(new Error("Connection timeout - server did not respond"));
}, timeoutMs);
client
.connect()
.then(resolve)
.catch((error) => {
const message =
error instanceof Error
? error.message
: "Connection failed - check URL and network";
console.error("[Settings] Daemon test error", { url, message });
reject(new Error(message));
});
});
} finally {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
await client.close();
}
}, []);
const handleOpenDaemonForm = useCallback((profile?: DaemonProfile) => {
if (profile) {
setDaemonFormMode("manual");
@@ -591,37 +551,6 @@ export default function SettingsScreen() {
[removeDaemon]
);
const updateDaemonTestState = useCallback((daemonId: string, state: { status: "idle" | "testing" | "success" | "error"; message?: string }) => {
setDaemonTestStates((prev) => {
const next = new Map(prev);
next.set(daemonId, state);
return next;
});
}, []);
const handleTestDaemonConnection = useCallback(
async (profile: DaemonProfile) => {
const endpoint = profile.endpoints?.[0] ?? null;
if (!endpoint) {
Alert.alert("Missing host", "This host has no endpoints configured.");
return;
}
const url = buildDaemonWebSocketUrl(endpoint);
updateDaemonTestState(profile.id, { status: "testing" });
updateConnectionStatus(profile.id, { status: "connecting" });
try {
await testServerConnection(url, 4000);
updateDaemonTestState(profile.id, { status: "success", message: "Reachable" });
updateConnectionStatus(profile.id, { status: "online", lastOnlineAt: new Date().toISOString() });
} catch (error) {
const message = error instanceof Error ? error.message : "Connection failed";
updateDaemonTestState(profile.id, { status: "error", message });
updateConnectionStatus(profile.id, { status: "offline", lastError: message });
}
},
[testServerConnection, updateConnectionStatus, updateDaemonTestState]
);
const handleToggleUseSpeaker = useCallback(
(value: boolean) => {
void updateSettings({ ...settings, useSpeaker: value });
@@ -707,20 +636,16 @@ export default function SettingsScreen() {
const connection = connectionStates.get(daemon.id);
const connectionStatus = connection?.status ?? "idle";
const lastConnectionError = connection?.lastError ?? null;
const testState = daemonTestStates.get(daemon.id);
return (
<DaemonCard
key={daemon.id}
daemon={daemon}
connectionStatus={connectionStatus}
lastError={lastConnectionError}
testState={testState}
onTestConnection={handleTestDaemonConnection}
onEdit={handleOpenDaemonForm}
onRemove={handleRemoveDaemon}
restartConfirmationMessage={restartConfirmationMessage}
waitForCondition={waitForCondition}
testServerConnection={testServerConnection}
isScreenMountedRef={isMountedRef}
/>
);
@@ -969,13 +894,10 @@ interface DaemonCardProps {
daemon: DaemonProfile;
connectionStatus: ConnectionStatus;
lastError: string | null;
testState?: DaemonTestState;
onTestConnection: (daemon: DaemonProfile) => void;
onEdit: (daemon: DaemonProfile) => void;
onRemove: (daemon: DaemonProfile) => void;
restartConfirmationMessage: string;
waitForCondition: (predicate: () => boolean, timeoutMs: number, intervalMs?: number) => Promise<boolean>;
testServerConnection: (url: string, timeoutMs?: number) => Promise<void>;
isScreenMountedRef: MutableRefObject<boolean>;
}
@@ -983,17 +905,13 @@ function DaemonCard({
daemon,
connectionStatus,
lastError,
testState,
onTestConnection,
onEdit,
onRemove,
restartConfirmationMessage,
waitForCondition,
testServerConnection,
isScreenMountedRef,
}: DaemonCardProps) {
const { theme } = useUnistyles();
const directWsUrl = buildDaemonWebSocketUrl(daemon.endpoints?.[0] ?? "localhost:6767");
const statusLabel = formatConnectionStatus(connectionStatus);
const statusTone = getConnectionStatusTone(connectionStatus);
const statusColor =
@@ -1013,56 +931,33 @@ function DaemonCard({
const [isRestarting, setIsRestarting] = useState(false);
const isConnected = daemonConnection?.isConnected ?? false;
const isConnectedRef = useRef(isConnected);
const isTesting = testState?.status === "testing";
useEffect(() => {
isConnectedRef.current = isConnected;
}, [isConnected]);
const waitForDaemonRestart = useCallback(async () => {
const maxAttempts = 12;
const retryDelayMs = 2500;
const disconnectTimeoutMs = 7000;
const reconnectTimeoutMs = 10000;
const reconnectTimeoutMs = 30000;
// Wait for disconnect first
if (isConnectedRef.current) {
await waitForCondition(() => !isConnectedRef.current, disconnectTimeoutMs);
}
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await testServerConnection(directWsUrl);
const reconnected = await waitForCondition(() => isConnectedRef.current, reconnectTimeoutMs);
// Wait for auto-reconnect
const reconnected = await waitForCondition(() => isConnectedRef.current, reconnectTimeoutMs);
if (isScreenMountedRef.current) {
setIsRestarting(false);
if (!reconnected) {
Alert.alert(
"Host reachable",
`${daemon.label} came back online but Paseo has not reconnected yet.`
);
}
}
return;
} catch (error) {
console.warn(
`[Settings] Restart poll attempt ${attempt}/${maxAttempts} failed for ${daemon.label}`,
error
if (isScreenMountedRef.current) {
setIsRestarting(false);
if (!reconnected) {
Alert.alert(
"Unable to reconnect",
`${daemon.label} did not come back online. Please verify it restarted.`
);
if (attempt === maxAttempts) {
if (isScreenMountedRef.current) {
setIsRestarting(false);
Alert.alert(
"Unable to reconnect",
`${daemon.label} did not come back online. Please verify it restarted.`
);
}
return;
}
await delay(retryDelayMs);
}
}
}, [daemon.label, directWsUrl, isScreenMountedRef, testServerConnection, waitForCondition]);
}, [daemon.label, isScreenMountedRef, waitForCondition]);
const beginServerRestart = useCallback(() => {
if (!restartServerFn) {
@@ -1141,13 +1036,6 @@ function DaemonCard({
? "rgba(248, 113, 113, 0.1)"
: "rgba(161, 161, 170, 0.1)";
const testResultColor =
testState?.status === "success"
? theme.colors.palette.green[400]
: testState?.status === "error"
? theme.colors.palette.red[300]
: theme.colors.foregroundMuted;
return (
<View style={styles.hostCard}>
<View style={styles.hostCardContent}>
@@ -1160,27 +1048,8 @@ function DaemonCard({
</View>
<Text style={styles.hostUrl}>{daemon.endpoints?.[0] ?? ""}</Text>
{connectionError ? <Text style={styles.hostError}>{connectionError}</Text> : null}
{testState && testState.status !== "idle" ? (
<Text style={[styles.testResultText, { color: testResultColor }]}>
{testState.message ?? (testState.status === "success" ? "Reachable" : "Testing...")}
</Text>
) : null}
</View>
<View style={styles.hostActionsRow}>
<Pressable
style={[
styles.hostActionButton,
styles.hostActionPrimary,
isTesting && styles.hostActionDisabled,
]}
onPress={() => onTestConnection(daemon)}
disabled={isTesting}
>
<Text style={[styles.hostActionText, styles.hostActionPrimaryText]}>
{isTesting ? "Testing..." : "Test"}
</Text>
</Pressable>
<View style={styles.hostActionSeparator} />
<Pressable
style={[styles.hostActionButton, isRestarting && styles.hostActionDisabled]}
onPress={handleRestartPress}