Files
multica/apps/web/components/web-providers.tsx
苗大 a90aa92d0c fix(web): resolve API and docs upstreams at runtime (#4840)
* fix(web): resolve upstream URLs at runtime

* fix(web): keep unconfigured upstreams same-origin

* fix(web): restore dev-only localhost fallbacks for API and docs upstreams

`next dev` on a developer machine now falls back to the conventional
http://localhost:8080 backend (honoring BACKEND_PORT) and
http://localhost:4000 docs origin when nothing is configured, so a bare
`pnpm dev:web` keeps proxying out of the box. Builds and the runtime
proxy keep the strict resolvers, so prebuilt images still leave unset
upstreams unproxied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:02:46 +08:00

92 lines
3.4 KiB
TypeScript

"use client";
import { useMemo } from "react";
import { CoreProvider } from "@multica/core/platform";
import { createBrowserCookieLocaleAdapter } from "@multica/core/i18n/browser";
import type { LocaleResources, SupportedLocale } from "@multica/core/i18n";
import { useWelcomeStore } from "@multica/core/onboarding";
import packageJson from "../package.json";
import { WebNavigationProvider } from "@/platform/navigation";
import {
setLoggedInCookie,
clearLoggedInCookie,
} from "@/features/auth/auth-cookie";
import { detectWebOS } from "@/platform/client-os";
// 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 an explicit runtime wsUrl. The Next.js runtime proxy handles
// /ws -> backend when the deployment keeps WebSockets same-origin.
function deriveWsUrl(): string | undefined {
if (typeof window === "undefined") return undefined;
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${proto}//${window.location.host}/ws`;
}
// Build-time version preferred (CI sets NEXT_PUBLIC_APP_VERSION to a git tag
// or sha so different deploys are distinguishable in server logs); fall back
// to the package.json version so local dev still reports something useful.
const WEB_VERSION =
process.env.NEXT_PUBLIC_APP_VERSION || packageJson.version || "dev";
export function WebProviders({
children,
locale,
resources,
apiBaseUrl,
wsUrl,
}: {
children: React.ReactNode;
locale: SupportedLocale;
resources: Record<string, LocaleResources>;
apiBaseUrl?: string;
wsUrl?: string;
}) {
const cookieAuth = !hasLegacyToken();
// Stable identity reference so downstream effects keyed on it don't see a
// new object on every parent render.
const identity = useMemo(
() => ({ platform: "web", version: WEB_VERSION, os: detectWebOS() }),
[],
);
const localeAdapter = useMemo(() => createBrowserCookieLocaleAdapter(), []);
return (
<CoreProvider
apiBaseUrl={apiBaseUrl}
wsUrl={wsUrl || deriveWsUrl()}
cookieAuth={cookieAuth}
onLogin={setLoggedInCookie}
onLogout={() => {
// welcome-store holds the transient post-onboarding signal. Must
// clear on logout so user B logging into the same browser doesn't
// inherit user A's signal and have <WelcomeAfterOnboarding /> fire
// listAgents / createIssue against a workspace user B doesn't even
// belong to. The store's own docstring promises this reset; this
// is where it gets wired.
useWelcomeStore.getState().reset();
clearLoggedInCookie();
}}
identity={identity}
locale={locale}
resources={resources}
localeAdapter={localeAdapter}
>
<WebNavigationProvider>{children}</WebNavigationProvider>
</CoreProvider>
);
}