authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-06-22 19:14:16+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-22 10:14:16-07:00
logc1e2408e1f317bba2fbb48a27498b4ae4bef47d8
treeb571e46068d16fcf660fe64af6569757cfb0582d
parent8b993bc9ff79c52be691f56f077abdac26f0e69f
signature Signed by PGP key B5690EEEBB952194

[codex] fix: preserve localhost preview hosts (#3499)

Co-authored-by: Codex <codex@openai.com>

2 files changed, 19 insertions(+), 0 deletions(-)

apps/web/src/browser/browserTargetResolver.test.ts+8
...@@ -47,6 +47,14 @@ describe("browser target resolver", () => {...@@ -47,6 +47,14 @@ describe("browser target resolver", () => {
47 ).toBe("http://localhost:3000/app");47 ).toBe("http://localhost:3000/app");
48 });48 });
4949
50 it("preserves localhost server-picker values when the prepared base is 127.0.0.1", async () => {
51 readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://127.0.0.1:3773" });
52 const { resolveDiscoveredServerUrl } = await import("./browserTargetResolver");
53 expect(
54 resolveDiscoveredServerUrl(EnvironmentId.make("environment-1"), "localhost:5173/app?x=1#top"),
55 ).toBe("http://localhost:5173/app?x=1#top");
56 });
57
50 it("normalizes public URLs without treating them as environment ports", async () => {58 it("normalizes public URLs without treating them as environment ports", async () => {
51 const { resolveDiscoveredServerUrl } = await import("./browserTargetResolver");59 const { resolveDiscoveredServerUrl } = await import("./browserTargetResolver");
52 expect(resolveDiscoveredServerUrl(EnvironmentId.make("environment-1"), "example.com/app")).toBe(60 expect(resolveDiscoveredServerUrl(EnvironmentId.make("environment-1"), "example.com/app")).toBe(
apps/web/src/browser/browserTargetResolver.ts+11
...@@ -24,6 +24,11 @@ const isPrivateNetworkHost = (host: string): boolean => {...@@ -24,6 +24,11 @@ const isPrivateNetworkHost = (host: string): boolean => {
24 );24 );
25};25};
2626
27const isLocalLoopbackHost = (host: string): boolean => {
28 const normalized = host.toLowerCase().replace(/^\[|\]$/g, "");
29 return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
30};
31
27export function resolveBrowserNavigationTarget(32export function resolveBrowserNavigationTarget(
28 environmentId: EnvironmentId,33 environmentId: EnvironmentId,
29 target: BrowserNavigationTarget,34 target: BrowserNavigationTarget,
...@@ -68,6 +73,12 @@ export function resolveDiscoveredServerUrl(environmentId: EnvironmentId, rawUrl:...@@ -68,6 +73,12 @@ export function resolveDiscoveredServerUrl(environmentId: EnvironmentId, rawUrl:
68 const normalizedUrl = normalizePreviewUrl(rawUrl);73 const normalizedUrl = normalizePreviewUrl(rawUrl);
69 const parsed = new URL(normalizedUrl);74 const parsed = new URL(normalizedUrl);
70 if (!isLoopbackHost(parsed.hostname)) return normalizedUrl;75 if (!isLoopbackHost(parsed.hostname)) return normalizedUrl;
76 const connection = readPreparedConnection(environmentId);
77 if (!connection) throw new Error(`Environment ${environmentId} is not connected.`);
78 const environmentUrl = new URL(connection.httpBaseUrl);
79 if (parsed.hostname !== "0.0.0.0" && isLocalLoopbackHost(environmentUrl.hostname)) {
80 return normalizedUrl;
81 }
71 const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));82 const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
72 return resolveBrowserNavigationTarget(environmentId, {83 return resolveBrowserNavigationTarget(environmentId, {
73 kind: "environment-port",84 kind: "environment-port",