Files
OmniRoute/src/shared/utils/basePath.ts
Rouzbeh† 8e5dc0de1b fix(client): rewrite absolute fetch/EventSource paths under basePath (#8515)
* fix(client): rewrite absolute fetch/EventSource paths under basePath

Absolute browser calls like fetch("/api/...") and new EventSource("/api/...")
do not honor Next.js basePath, so subpath deploys (OMNIROUTE_BASE_PATH) break
dashboard health checks, settings APIs, and SSE unless a reverse proxy rewrites
the domain root.

- Add withBasePath / getDeployBasePath helpers
- Install ref-counted fetch + EventSource rewrite when basePath is set
  (same pattern as installDashboardCsrfFetch)
- Mount BasePathNetworkProvider at the root so login works too
- Mirror OMNIROUTE_BASE_PATH to NEXT_PUBLIC_OMNIROUTE_BASE_PATH for the client
- Document in .env.example; unit tests for rewrite rules

* docs(changelog): add fragment for #8515 basePath client fetch

* test(client): move basePath tests into a scanned dir and fix no-op call-shape asserts

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(client-sweep-8515): restore CHANGELOG #8471 bullet and fix basePath TS2322

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-27 21:12:49 -03:00

71 lines
2.4 KiB
TypeScript

/**
* Client/server helpers for Next.js `basePath` / `OMNIROUTE_BASE_PATH` deploys.
*
* Next.js rewrites Link/router automatically, but absolute browser calls like
* `fetch("/api/...")` and `new EventSource("/api/...")` do not get the prefix.
* Under a reverse-proxy subpath those hit the domain root instead of the app.
*/
/** Normalize to leading slash, no trailing slash. Empty / root → `""`. */
export function normalizeBasePath(value?: string | null): string {
const trimmed = value?.trim() ?? "";
if (!trimmed || trimmed === "/") return "";
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
return withSlash.replace(/\/+$/, "");
}
/**
* Deploy basePath as seen by the client bundle.
* Set via next.config `env.NEXT_PUBLIC_OMNIROUTE_BASE_PATH` from `OMNIROUTE_BASE_PATH`.
*/
export function getDeployBasePath(
env: NodeJS.ProcessEnv = typeof process !== "undefined" ? process.env : ({} as NodeJS.ProcessEnv)
): string {
return normalizeBasePath(
env.NEXT_PUBLIC_OMNIROUTE_BASE_PATH || env.OMNIROUTE_BASE_PATH || ""
);
}
/**
* Prefix a same-origin app path with the deploy basePath when needed.
*
* - Relative absolute paths: `/api/health/ping` → `/omniroute/api/health/ping`
* - Absolute same-origin URLs: `https://host/api/x` → `https://host/omniroute/api/x`
* - Already-prefixed paths, external URLs, and protocol-relative URLs are unchanged
*/
export function withBasePath(
input: string,
basePath: string = getDeployBasePath(),
origin?: string
): string {
if (!basePath) return input;
if (!input) return input;
// Protocol-relative or non-path forms
if (input.startsWith("//")) return input;
// Absolute path on this origin
if (input.startsWith("/")) {
if (input === basePath || input.startsWith(`${basePath}/`)) return input;
return `${basePath}${input}`;
}
// Absolute URL — only rewrite same-origin
try {
const baseOrigin =
origin ||
(typeof window !== "undefined" ? window.location.origin : "http://localhost");
const url = new URL(input, baseOrigin);
const currentOrigin = new URL(baseOrigin).origin;
if (url.origin !== currentOrigin) return input;
if (url.pathname === basePath || url.pathname.startsWith(`${basePath}/`)) {
return url.toString();
}
url.pathname = `${basePath}${url.pathname.startsWith("/") ? url.pathname : `/${url.pathname}`}`;
return url.toString();
} catch {
return input;
}
}