From 43d12fdc12e20e814364395786e47e4f629c2c90 Mon Sep 17 00:00:00 2001 From: Jeff Green Date: Wed, 10 Jun 2026 13:47:07 -0400 Subject: [PATCH 1/2] fix(dashboard): derive WebSocket URL from API base so prod doesn't fall back to localhost --- dashboard/lib/api.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dashboard/lib/api.ts b/dashboard/lib/api.ts index 113c497..5bf913e 100644 --- a/dashboard/lib/api.ts +++ b/dashboard/lib/api.ts @@ -4,7 +4,6 @@ */ const ENV_API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000/api'; -const WS_BASE = process.env.NEXT_PUBLIC_WS_URL ?? 'ws://localhost:3000/ws'; /** Resolve API base URL: localStorage override → env var → localhost default. */ export function getApiBase(): string { @@ -45,6 +44,18 @@ export function getServerBase(): string { return getApiBase().replace(/\/api$/, ''); } +/** + * WebSocket URL. Prefers an explicit `NEXT_PUBLIC_WS_URL`, otherwise derives it + * from the resolved server origin (`http(s)` → `ws(s)`, `/ws` path) so production + * never falls back to `ws://localhost:3000` when only the API URL is configured. + */ +export function getWsBase(): string { + if (process.env.NEXT_PUBLIC_WS_URL) { + return process.env.NEXT_PUBLIC_WS_URL; + } + return `${getServerBase().replace(/^http/, 'ws')}/ws`; +} + // --- Health --- export interface HealthResult { @@ -385,7 +396,7 @@ export type EventHandler = (event: { event: string; data: unknown }) => void; export function connectWebSocket(onEvent: EventHandler): WebSocket | null { const token = typeof window !== 'undefined' ? localStorage.getItem('textrawl_token') : null; - const url = WS_BASE; + const url = getWsBase(); try { // Auth via subprotocol to avoid exposing token in URL/logs From c845ced8a8fb1ecd0cea1a179814171815a9aa4b Mon Sep 17 00:00:00 2001 From: Jeff Green Date: Wed, 10 Jun 2026 13:51:35 -0400 Subject: [PATCH 2/2] fix(dashboard): normalize trailing /api when deriving WebSocket path --- dashboard/lib/api.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dashboard/lib/api.ts b/dashboard/lib/api.ts index 5bf913e..07bd813 100644 --- a/dashboard/lib/api.ts +++ b/dashboard/lib/api.ts @@ -53,7 +53,12 @@ export function getWsBase(): string { if (process.env.NEXT_PUBLIC_WS_URL) { return process.env.NEXT_PUBLIC_WS_URL; } - return `${getServerBase().replace(/^http/, 'ws')}/ws`; + // Normalize a trailing `/api` or `/api/` (and any stray trailing slash) so the + // derived path is exactly `/ws` — the server's upgrade handler rejects anything else. + const base = getServerBase() + .replace(/\/api\/?$/, '') + .replace(/\/+$/, ''); + return `${base.replace(/^http/, 'ws')}/ws`; } // --- Health ---