diff --git a/app/src/hooks/__tests__/useBackendReachable.test.tsx b/app/src/hooks/__tests__/useBackendReachable.test.tsx deleted file mode 100644 index 3463c5166..000000000 --- a/app/src/hooks/__tests__/useBackendReachable.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { renderHook, waitFor } from '@testing-library/react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; - -import { useBackendReachable } from '../useBackendReachable'; - -vi.mock('../../services/backendUrl', () => ({ - getBackendUrl: vi.fn().mockResolvedValue('http://localhost:5005'), -})); - -describe('useBackendReachable', () => { - beforeEach(() => { - vi.restoreAllMocks(); - }); - - it('reports reachable when fetch resolves with any HTTP response', async () => { - vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(null, { status: 200 })); - - const { result } = renderHook(() => useBackendReachable()); - expect(result.current).toBe('probing'); - await waitFor(() => expect(result.current).toBe('reachable')); - }); - - it('reports reachable on 4xx (host answered)', async () => { - vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(null, { status: 404 })); - - const { result } = renderHook(() => useBackendReachable()); - await waitFor(() => expect(result.current).toBe('reachable')); - }); - - it('reports unreachable when fetch rejects (network error)', async () => { - vi.spyOn(globalThis, 'fetch').mockRejectedValue(new TypeError('Failed to fetch')); - - const { result } = renderHook(() => useBackendReachable()); - await waitFor(() => expect(result.current).toBe('unreachable')); - }); -}); diff --git a/app/src/hooks/useBackendReachable.ts b/app/src/hooks/useBackendReachable.ts deleted file mode 100644 index fb48ae629..000000000 --- a/app/src/hooks/useBackendReachable.ts +++ /dev/null @@ -1,59 +0,0 @@ -import createDebug from 'debug'; -import { useEffect, useState } from 'react'; - -import { getBackendUrl } from '../services/backendUrl'; - -const log = createDebug('app:backend-probe'); - -export type BackendProbeStatus = 'probing' | 'reachable' | 'unreachable'; - -const PROBE_TIMEOUT_MS = 2500; - -/** - * Probes the configured backend URL once on mount and reports whether it is - * reachable. The "Continue locally" CTA on the Welcome screen is gated on - * `unreachable` so users only see it when the backend OAuth flow can't be - * completed (per issue #2037 AC). - * - * We treat any successful HTTP response (incl. 4xx) as reachable — the goal is - * to confirm the host is online, not that a specific route exists. - */ -export function useBackendReachable(): BackendProbeStatus { - const [status, setStatus] = useState('probing'); - - useEffect(() => { - let cancelled = false; - const controller = new AbortController(); - const timeoutId = window.setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS); - - void (async () => { - try { - const base = await getBackendUrl(); - log('[probe] fetching %s/health', base); - const response = await fetch(`${base}/health`, { - method: 'GET', - signal: controller.signal, - cache: 'no-store', - }); - if (cancelled) return; - // Any HTTP response (even 404) means the host answered — it's online. - log('[probe] response status=%d → reachable', response.status); - setStatus('reachable'); - } catch (err) { - if (cancelled) return; - log('[probe] failed → unreachable: %o', err); - setStatus('unreachable'); - } finally { - window.clearTimeout(timeoutId); - } - })(); - - return () => { - cancelled = true; - controller.abort(); - window.clearTimeout(timeoutId); - }; - }, []); - - return status; -}