Files
multica/apps/web/components/web-providers.tsx
Jiang Bohan a757f3a8c4 fix(selfhost): auto-derive WebSocket URL for LAN access (#896)
When NEXT_PUBLIC_WS_URL is not set, the WebSocket URL defaulted to
ws://localhost:8080/ws. This broke real-time features (chat streaming,
live updates, notifications) for self-hosted deployments accessed over
LAN — the browser tried connecting to localhost on the client machine
instead of the Docker host.

Now the web app derives the WebSocket URL from window.location, routing
through the existing Next.js /ws rewrite. This works for localhost, LAN,
and custom domain setups without any extra configuration.

Also adds NEXT_PUBLIC_WS_URL as a Docker build arg for explicit override,
and documents LAN access configuration in SELF_HOSTING_ADVANCED.md.

Closes #896
2026-04-14 01:42:42 +08:00

49 lines
1.8 KiB
TypeScript

"use client";
import { CoreProvider } from "@multica/core/platform";
import { WebNavigationProvider } from "@/platform/navigation";
import {
setLoggedInCookie,
clearLoggedInCookie,
} from "@/features/auth/auth-cookie";
// Legacy token in localStorage → keep this session in token mode so users who
// logged in before the cookie-auth migration stay authed. They migrate to
// cookie mode on their next logout/login cycle (logout clears multica_token).
// Sunset: once telemetry shows <1% of sessions still carry multica_token,
// delete this branch and hard-code `cookieAuth` — the localStorage token is
// XSS-exposed and is the exact thing the cookie migration exists to remove.
function hasLegacyToken(): boolean {
if (typeof window === "undefined") return false;
try {
return Boolean(window.localStorage.getItem("multica_token"));
} catch {
return false;
}
}
// Derive WebSocket URL from the page origin so self-hosted / LAN deployments
// work without explicit NEXT_PUBLIC_WS_URL. The Next.js rewrite rule
// (/ws → backend) handles proxying.
function deriveWsUrl(): string | undefined {
if (process.env.NEXT_PUBLIC_WS_URL) return process.env.NEXT_PUBLIC_WS_URL;
if (typeof window === "undefined") return undefined;
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${proto}//${window.location.host}/ws`;
}
export function WebProviders({ children }: { children: React.ReactNode }) {
const cookieAuth = !hasLegacyToken();
return (
<CoreProvider
apiBaseUrl={process.env.NEXT_PUBLIC_API_URL}
wsUrl={deriveWsUrl()}
cookieAuth={cookieAuth}
onLogin={setLoggedInCookie}
onLogout={clearLoggedInCookie}
>
<WebNavigationProvider>{children}</WebNavigationProvider>
</CoreProvider>
);
}