import { PRIMARY_LOCAL_ENVIRONMENT_ID, type DesktopEnvironmentBootstrap } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; const PrimaryEnvironmentTargetSource = Schema.Literals([ "configured", "window-origin", "desktop-managed", ]); type PrimaryEnvironmentTargetSource = typeof PrimaryEnvironmentTargetSource.Type; const PrimaryEnvironmentUrlKind = Schema.Literals([ "http-base-url", "websocket-base-url", "development-server-url", "window-location-url", ]); type PrimaryEnvironmentUrlKind = typeof PrimaryEnvironmentUrlKind.Type; export class PrimaryEnvironmentUrlInvalidError extends Schema.TaggedErrorClass()( "PrimaryEnvironmentUrlInvalidError", { source: PrimaryEnvironmentTargetSource, urlKind: PrimaryEnvironmentUrlKind, cause: Schema.Defect(), }, ) { override get message(): string { return `Could not parse ${this.urlKind} for the ${this.source} primary environment target.`; } } export class PrimaryEnvironmentProtocolUnsupportedError extends Schema.TaggedErrorClass()( "PrimaryEnvironmentProtocolUnsupportedError", { source: PrimaryEnvironmentTargetSource, protocol: Schema.String, }, ) { override get message(): string { return `The ${this.source} primary environment target uses unsupported protocol ${this.protocol}.`; } } export class DesktopEnvironmentBootstrapIncompleteError extends Schema.TaggedErrorClass()( "DesktopEnvironmentBootstrapIncompleteError", { hasHttpBaseUrl: Schema.Boolean, hasWsBaseUrl: Schema.Boolean, }, ) { override get message(): string { const missing = [ ...(this.hasHttpBaseUrl ? [] : ["httpBaseUrl"]), ...(this.hasWsBaseUrl ? [] : ["wsBaseUrl"]), ]; return `Desktop bootstrap is missing ${missing.join(" and ")} for the local environment.`; } } export const isPrimaryEnvironmentUrlInvalidError = Schema.is(PrimaryEnvironmentUrlInvalidError); export const isPrimaryEnvironmentProtocolUnsupportedError = Schema.is( PrimaryEnvironmentProtocolUnsupportedError, ); export const isDesktopEnvironmentBootstrapIncompleteError = Schema.is( DesktopEnvironmentBootstrapIncompleteError, ); export interface PrimaryEnvironmentTarget { readonly source: PrimaryEnvironmentTargetSource; readonly target: { readonly httpBaseUrl: string; readonly wsBaseUrl: string; }; } const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "::1", "localhost"]); function getDesktopLocalEnvironmentBootstrap(): DesktopEnvironmentBootstrap | null { // The primary (Windows-native) backend keeps the "primary" id. The // plural list may include a second WSL entry; the primary-target // resolver only cares about the primary, so just find it. const bootstraps = window.desktopBridge?.getLocalEnvironmentBootstraps() ?? []; return bootstraps.find((entry) => entry.id === PRIMARY_LOCAL_ENVIRONMENT_ID) ?? null; } function parseTargetUrl(input: { readonly rawValue: string; readonly baseUrl?: string; readonly source: PrimaryEnvironmentTargetSource; readonly urlKind: PrimaryEnvironmentUrlKind; }): URL { try { return input.baseUrl === undefined ? new URL(input.rawValue) : new URL(input.rawValue, input.baseUrl); } catch (cause) { throw new PrimaryEnvironmentUrlInvalidError({ source: input.source, urlKind: input.urlKind, cause, }); } } function normalizeBaseUrl( rawValue: string, source: PrimaryEnvironmentTargetSource, urlKind: PrimaryEnvironmentUrlKind, ): string { return parseTargetUrl({ rawValue, baseUrl: window.location.origin, source, urlKind, }).toString(); } function swapBaseUrlProtocol( rawValue: string, nextProtocol: "http:" | "https:" | "ws:" | "wss:", urlKind: PrimaryEnvironmentUrlKind, ): string { const url = parseTargetUrl({ rawValue, baseUrl: window.location.origin, source: "configured", urlKind, }); url.protocol = nextProtocol; return url.toString(); } function normalizeHostname(hostname: string): string { return hostname .trim() .toLowerCase() .replace(/^\[(.*)\]$/, "$1"); } export function isLoopbackHostname(hostname: string): boolean { return LOOPBACK_HOSTNAMES.has(normalizeHostname(hostname)); } function resolveHttpRequestBaseUrl(primaryTarget: PrimaryEnvironmentTarget): string { const httpBaseUrl = primaryTarget.target.httpBaseUrl; const configuredDevServerUrl = import.meta.env.VITE_DEV_SERVER_URL?.trim(); if (!configuredDevServerUrl) { return httpBaseUrl; } const currentUrl = parseTargetUrl({ rawValue: window.location.href, source: "window-origin", urlKind: "window-location-url", }); const targetUrl = parseTargetUrl({ rawValue: httpBaseUrl, source: primaryTarget.source, urlKind: "http-base-url", }); const devServerUrl = parseTargetUrl({ rawValue: configuredDevServerUrl, baseUrl: currentUrl.origin, source: "configured", urlKind: "development-server-url", }); const isCurrentOriginDevServer = (currentUrl.protocol === "http:" || currentUrl.protocol === "https:") && currentUrl.origin === devServerUrl.origin; if ( !isCurrentOriginDevServer || currentUrl.origin === targetUrl.origin || !isLoopbackHostname(currentUrl.hostname) || !isLoopbackHostname(targetUrl.hostname) ) { return httpBaseUrl; } return currentUrl.origin; } function resolveConfiguredPrimaryTarget(): PrimaryEnvironmentTarget | null { const configuredHttpBaseUrl = import.meta.env.VITE_HTTP_URL?.trim() || undefined; const configuredWsBaseUrl = import.meta.env.VITE_WS_URL?.trim() || undefined; if (!configuredHttpBaseUrl && !configuredWsBaseUrl) { return null; } const resolvedHttpBaseUrl = configuredHttpBaseUrl ?? (configuredWsBaseUrl?.startsWith("wss:") ? swapBaseUrlProtocol(configuredWsBaseUrl, "https:", "websocket-base-url") : swapBaseUrlProtocol(configuredWsBaseUrl!, "http:", "websocket-base-url")); const resolvedWsBaseUrl = configuredWsBaseUrl ?? (configuredHttpBaseUrl?.startsWith("https:") ? swapBaseUrlProtocol(configuredHttpBaseUrl, "wss:", "http-base-url") : swapBaseUrlProtocol(configuredHttpBaseUrl!, "ws:", "http-base-url")); return { source: "configured", target: { httpBaseUrl: normalizeBaseUrl(resolvedHttpBaseUrl, "configured", "http-base-url"), wsBaseUrl: normalizeBaseUrl(resolvedWsBaseUrl, "configured", "websocket-base-url"), }, }; } function resolveWindowOriginPrimaryTarget(): PrimaryEnvironmentTarget { const url = parseTargetUrl({ rawValue: window.location.origin, source: "window-origin", urlKind: "http-base-url", }); const httpBaseUrl = url.toString(); if (url.protocol === "http:") { url.protocol = "ws:"; } else if (url.protocol === "https:") { url.protocol = "wss:"; } else { throw new PrimaryEnvironmentProtocolUnsupportedError({ source: "window-origin", protocol: url.protocol, }); } return { source: "window-origin", target: { httpBaseUrl, wsBaseUrl: url.toString(), }, }; } function resolveDesktopPrimaryTarget(): PrimaryEnvironmentTarget | null { const desktopBootstrap = getDesktopLocalEnvironmentBootstrap(); if (!desktopBootstrap) { return null; } if (!desktopBootstrap.httpBaseUrl && !desktopBootstrap.wsBaseUrl) { return null; } if (!desktopBootstrap.httpBaseUrl || !desktopBootstrap.wsBaseUrl) { throw new DesktopEnvironmentBootstrapIncompleteError({ hasHttpBaseUrl: Boolean(desktopBootstrap.httpBaseUrl), hasWsBaseUrl: Boolean(desktopBootstrap.wsBaseUrl), }); } return { source: "desktop-managed", target: { httpBaseUrl: normalizeBaseUrl( desktopBootstrap.httpBaseUrl, "desktop-managed", "http-base-url", ), wsBaseUrl: normalizeBaseUrl( desktopBootstrap.wsBaseUrl, "desktop-managed", "websocket-base-url", ), }, }; } export function resolvePrimaryEnvironmentHttpUrl( pathname: string, searchParams?: Record, ): string { const primaryTarget = readPrimaryEnvironmentTarget(); const url = parseTargetUrl({ rawValue: resolveHttpRequestBaseUrl(primaryTarget), source: primaryTarget.source, urlKind: "http-base-url", }); url.pathname = pathname; if (searchParams) { url.search = new URLSearchParams(searchParams).toString(); } return url.toString(); } export function readPrimaryEnvironmentTarget(): PrimaryEnvironmentTarget { return ( resolveDesktopPrimaryTarget() ?? resolveConfiguredPrimaryTarget() ?? resolveWindowOriginPrimaryTarget() ); }