| author | |
| committer | |
| log | c1e2408e1f317bba2fbb48a27498b4ae4bef47d8 |
| tree | b571e46068d16fcf660fe64af6569757cfb0582d |
| parent | 8b993bc9ff79c52be691f56f077abdac26f0e69f |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | 47 | ).toBe("http://localhost:3000/app"); |
| 48 | 48 | }); |
| 49 | 49 | |
| 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 | 58 | it("normalizes public URLs without treating them as environment ports", async () => { |
| 51 | 59 | const { resolveDiscoveredServerUrl } = await import("./browserTargetResolver"); |
| 52 | 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 | 24 | ); |
| 25 | 25 | }; |
| 26 | 26 | |
| 27 | const isLocalLoopbackHost = (host: string): boolean => { | |
| 28 | const normalized = host.toLowerCase().replace(/^\[|\]$/g, ""); | |
| 29 | return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1"; | |
| 30 | }; | |
| 31 | ||
| 27 | 32 | export function resolveBrowserNavigationTarget( |
| 28 | 33 | environmentId: EnvironmentId, |
| 29 | 34 | target: BrowserNavigationTarget, |
| ... | ... | @@ -68,6 +73,12 @@ export function resolveDiscoveredServerUrl(environmentId: EnvironmentId, rawUrl: |
| 68 | 73 | const normalizedUrl = normalizePreviewUrl(rawUrl); |
| 69 | 74 | const parsed = new URL(normalizedUrl); |
| 70 | 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 | 82 | const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80)); |
| 72 | 83 | return resolveBrowserNavigationTarget(environmentId, { |
| 73 | 84 | kind: "environment-port", |