diff --git a/apps/web/src/environments/primary/bootstrap.test.ts b/apps/web/src/environments/primary/bootstrap.test.ts index e200ecf2ff8..6cfac25cda5 100644 --- a/apps/web/src/environments/primary/bootstrap.test.ts +++ b/apps/web/src/environments/primary/bootstrap.test.ts @@ -60,6 +60,9 @@ describe("environmentBootstrap", () => { beforeEach(() => { vi.restoreAllMocks(); vi.useRealTimers(); + vi.stubEnv("VITE_HTTP_URL", ""); + vi.stubEnv("VITE_WS_URL", ""); + vi.stubEnv("VITE_DEV_SERVER_URL", ""); installTestBrowser("http://localhost/"); }); @@ -165,6 +168,22 @@ describe("environmentBootstrap", () => { ); }); + it("uses the current network origin over a configured loopback descriptor target", async () => { + vi.stubEnv("VITE_HTTP_URL", "http://127.0.0.1:4173"); + vi.stubEnv("VITE_WS_URL", "ws://127.0.0.1:4173"); + installTestBrowser("http://192.168.1.20:4173/"); + await installDescriptorApi(); + + await expect(resolveInitialPrimaryEnvironmentDescriptor()).resolves.toEqual(BASE_ENVIRONMENT); + expect(resolvePrimaryEnvironmentHttpUrl("/.well-known/t3/environment")).toBe( + "http://192.168.1.20:4173/.well-known/t3/environment", + ); + expect(getPrimaryKnownEnvironment()?.target).toEqual({ + httpBaseUrl: "http://192.168.1.20:4173/", + wsBaseUrl: "ws://192.168.1.20:4173/", + }); + }); + it("uses the vite proxy for desktop-managed loopback descriptor requests during local dev", async () => { vi.stubEnv("VITE_DEV_SERVER_URL", "http://127.0.0.1:5733"); vi.stubGlobal("window", { @@ -190,6 +209,13 @@ describe("environmentBootstrap", () => { expect(resolvePrimaryEnvironmentHttpUrl("/.well-known/t3/environment")).toBe( "http://127.0.0.1:5733/.well-known/t3/environment", ); + expect(readPrimaryEnvironmentTarget()).toEqual({ + source: "desktop-managed", + target: { + httpBaseUrl: "http://127.0.0.1:3773/", + wsBaseUrl: "ws://127.0.0.1:3773/", + }, + }); }); it("retains the URL parser cause without exposing the configured URL in its message", () => { @@ -212,7 +238,7 @@ describe("environmentBootstrap", () => { it("describes which desktop bootstrap endpoint is missing", () => { vi.stubGlobal("window", { - location: new URL("http://127.0.0.1:5733/"), + location: new URL("t3code-dev://app/"), history: { replaceState: vi.fn() }, desktopBridge: { getLocalEnvironmentBootstraps: () => [ diff --git a/apps/web/src/environments/primary/target.ts b/apps/web/src/environments/primary/target.ts index cc002419fe7..0e2b88456b7 100644 --- a/apps/web/src/environments/primary/target.ts +++ b/apps/web/src/environments/primary/target.ts @@ -267,6 +267,32 @@ function resolveDesktopPrimaryTarget(): PrimaryEnvironmentTarget | null { }; } +function isHttpWindowOrigin(): boolean { + return window.location.protocol === "http:" || window.location.protocol === "https:"; +} + +function shouldPreferWindowOriginOverConfiguredTarget(target: PrimaryEnvironmentTarget): boolean { + if (!isHttpWindowOrigin()) { + return false; + } + + const currentUrl = parseTargetUrl({ + rawValue: window.location.href, + source: "window-origin", + urlKind: "window-location-url", + }); + if (isLoopbackHostname(currentUrl.hostname)) { + return false; + } + + const targetUrl = parseTargetUrl({ + rawValue: target.target.httpBaseUrl, + source: target.source, + urlKind: "http-base-url", + }); + return isLoopbackHostname(targetUrl.hostname); +} + export function resolvePrimaryEnvironmentHttpUrl( pathname: string, searchParams?: Record, @@ -286,9 +312,17 @@ export function resolvePrimaryEnvironmentHttpUrl( } export function readPrimaryEnvironmentTarget(): PrimaryEnvironmentTarget { - return ( - resolveDesktopPrimaryTarget() ?? - resolveConfiguredPrimaryTarget() ?? - resolveWindowOriginPrimaryTarget() - ); + const configuredTarget = resolveConfiguredPrimaryTarget(); + if (configuredTarget) { + if (shouldPreferWindowOriginOverConfiguredTarget(configuredTarget)) { + return resolveWindowOriginPrimaryTarget(); + } + return configuredTarget; + } + + if (isHttpWindowOrigin() && !isLoopbackHostname(window.location.hostname)) { + return resolveWindowOriginPrimaryTarget(); + } + + return resolveDesktopPrimaryTarget() ?? resolveWindowOriginPrimaryTarget(); }