authorgravatar for gbarros1095@gmail.comGuilherme Barros <gbarros1095@gmail.com> 2026-08-18 19:39:08+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-18 13:39:08-04:00
loga87f691bd40c9e3c87715e3ce99ab39a584bdd1e
tree0a728cd04f1da7e39e62e6b59132bbd781ca6802
parent1896f39a38f6fe980ea137fbc402dde8be17b83a
signature Signed by PGP key B5690EEEBB952194

fix(preview): open local environment ports on localhost (#7300)


2 files changed, 20 insertions(+), 4 deletions(-)

apps/web/src/browser/browserTargetResolver.test.ts+13-1
...@@ -173,7 +173,19 @@ describe("browser target resolver", () => {...@@ -173,7 +173,19 @@ describe("browser target resolver", () => {
173 kind: "environment-port",173 kind: "environment-port",
174 port: 5173,174 port: 5173,
175 }).resolvedUrl,175 }).resolvedUrl,
176 ).toBe("http://[::1]:5173/");176 ).toBe("http://localhost:5173/");
177 });
178
179 it("maps local IPv4 environment ports onto localhost for dual-stack guests", async () => {
180 readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://127.0.0.1:3773" });
181 const { resolveBrowserNavigationTarget } = await import("./browserTargetResolver");
182 expect(
183 resolveBrowserNavigationTarget(EnvironmentId.make("environment-1"), {
184 kind: "environment-port",
185 port: 5173,
186 path: "/app",
187 }).resolvedUrl,
188 ).toBe("http://localhost:5173/app");
177 });189 });
178190
179 it("leaves malformed input for the normal navigation error path", async () => {191 it("leaves malformed input for the normal navigation error path", async () => {
apps/web/src/browser/browserTargetResolver.ts+7-3
...@@ -178,9 +178,13 @@ const resolveEnvironmentPortTarget = (...@@ -178,9 +178,13 @@ const resolveEnvironmentPortTarget = (
178 const protocol = target.protocol ?? "http";178 const protocol = target.protocol ?? "http";
179 const path = target.path?.startsWith("/") ? target.path : `/${target.path ?? ""}`;179 const path = target.path?.startsWith("/") ? target.path : `/${target.path ?? ""}`;
180 const normalizedEnvironmentHost = environmentUrl.hostname.replace(/^\[|\]$/g, "");180 const normalizedEnvironmentHost = environmentUrl.hostname.replace(/^\[|\]$/g, "");
181 const resolvedHost = normalizedEnvironmentHost.includes(":")181 // Local loopback environments should advertise `localhost` so Chromium
182 ? `[${normalizedEnvironmentHost}]`182 // dual-stack lookup can reach a Vite server bound only to ::1 or 127.0.0.1.
183 : normalizedEnvironmentHost;183 const resolvedHost = isLocalLoopbackHost(normalizedEnvironmentHost)
184 ? "localhost"
185 : normalizedEnvironmentHost.includes(":")
186 ? `[${normalizedEnvironmentHost}]`
187 : normalizedEnvironmentHost;
184 const resolved = sourceUrl188 const resolved = sourceUrl
185 ? new URL(sourceUrl)189 ? new URL(sourceUrl)
186 : new URL(path, `${protocol}://${resolvedHost}:${target.port}`);190 : new URL(path, `${protocol}://${resolvedHost}:${target.port}`);