mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* 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>
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { afterEach, describe, it, mock } from "node:test";
|
|
import {
|
|
__resetBasePathFetchForTests,
|
|
installBasePathFetch,
|
|
} from "../../../src/shared/utils/basePathFetch";
|
|
|
|
describe("installBasePathFetch", () => {
|
|
afterEach(() => {
|
|
__resetBasePathFetchForTests();
|
|
});
|
|
|
|
it("is a no-op when basePath is empty", async () => {
|
|
const native = mock.fn(async () => new Response("ok"));
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = native as unknown as typeof fetch;
|
|
try {
|
|
const uninstall = installBasePathFetch("");
|
|
await fetch("/api/health/ping");
|
|
// No-op install never wraps fetch, so the call keeps its original single-argument
|
|
// shape (the caller passed no `init`) instead of the two-argument shape the wrapper
|
|
// produces once basePath is set.
|
|
assert.deepEqual(native.mock.calls[0].arguments, ["/api/health/ping"]);
|
|
uninstall();
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
it("prefixes absolute paths on fetch when basePath is set", async () => {
|
|
const native = mock.fn(async () => new Response("ok"));
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = native as unknown as typeof fetch;
|
|
try {
|
|
const uninstall = installBasePathFetch("/omniroute");
|
|
await fetch("/api/health/ping");
|
|
assert.deepEqual(native.mock.calls[0].arguments, ["/omniroute/api/health/ping", undefined]);
|
|
uninstall();
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
it("does not double-prefix already-prefixed paths", async () => {
|
|
const native = mock.fn(async () => new Response("ok"));
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = native as unknown as typeof fetch;
|
|
try {
|
|
const uninstall = installBasePathFetch("/omniroute");
|
|
await fetch("/omniroute/api/health/ping");
|
|
assert.deepEqual(native.mock.calls[0].arguments, ["/omniroute/api/health/ping", undefined]);
|
|
uninstall();
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
it("restores native fetch after last uninstall", async () => {
|
|
const native = mock.fn(async () => new Response("ok"));
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = native as unknown as typeof fetch;
|
|
try {
|
|
const a = installBasePathFetch("/omniroute");
|
|
const b = installBasePathFetch("/omniroute");
|
|
a();
|
|
await fetch("/api/x");
|
|
assert.deepEqual(native.mock.calls[0].arguments, ["/omniroute/api/x", undefined]);
|
|
b();
|
|
native.mock.resetCalls();
|
|
await fetch("/api/x");
|
|
// Once the last consumer uninstalls, fetch is native again — same single-argument
|
|
// call shape as the no-op case above.
|
|
assert.deepEqual(native.mock.calls[0].arguments, ["/api/x"]);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
});
|