File size: 1,827 Bytes
6111b2b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | "use client";
import { useEffect, useState } from "react";
export const DEFAULT_DISPLAY_BASE_URL = "http://localhost:20128";
function normalizeUrl(value?: string): string | null {
const trimmed = value?.trim();
if (!trimmed) return null;
return trimmed.replace(/\/+$/, "");
}
/**
* Returns the public base URL to display in the dashboard.
*
* Resolution chain:
* 1. NEXT_PUBLIC_BASE_URL (env, trimmed + slash-normalized) — wins if set.
* 2. window.location.origin after client mount — when env is unset.
* 3. DEFAULT_DISPLAY_BASE_URL ("http://localhost:20128") — SSR / first render fallback.
*
* DISPLAY ONLY — do NOT use this hook for OAuth `redirect_uri`.
* OAuth callers must read `process.env.NEXT_PUBLIC_BASE_URL` directly to avoid
* host-header attack surface. For server-side resolution, use
* `src/shared/utils/resolveOmniRouteBaseUrl.ts` instead.
*/
export function useDisplayBaseUrl(): string {
const envValue = normalizeUrl(process.env.NEXT_PUBLIC_BASE_URL);
const [url, setUrl] = useState<string>(envValue ?? DEFAULT_DISPLAY_BASE_URL);
useEffect(() => {
if (envValue) return;
const origin = normalizeUrl(window.location.origin) ?? DEFAULT_DISPLAY_BASE_URL;
// Schedule via queueMicrotask so setState is called inside a callback,
// not synchronously in the effect body (react-hooks/set-state-in-effect).
// The unmounted guard prevents a stale setState on a torn-down root
// (relevant under React strict mode's double-invoke, where cleanup runs
// before the microtask fires on the first effect invocation).
let unmounted = false;
queueMicrotask(() => {
if (!unmounted) setUrl(origin);
});
return () => {
unmounted = true;
};
}, [envValue]);
return url;
}
|