import { createAdvertisedEndpoint } from "@t3tools/shared/advertisedEndpoint"; import type { AdvertisedEndpoint, AdvertisedEndpointProvider } from "@t3tools/contracts"; import { buildTailscaleHttpsBaseUrl, isTailscaleIpv4Address, parseTailscaleMagicDnsName, probeTailscaleHttpsEndpoint, readTailscaleStatus, } from "@t3tools/tailscale"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import { HttpClient } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; import type { DesktopNetworkInterfaces } from "./DesktopServerExposure.ts"; export { isTailscaleIpv4Address, parseTailscaleMagicDnsName } from "@t3tools/tailscale"; const TAILSCALE_ENDPOINT_PROVIDER: AdvertisedEndpointProvider = { id: "tailscale", label: "Tailscale", kind: "private-network", isAddon: true, }; function resolveTailscaleIpAdvertisedEndpoints(input: { readonly port: number; readonly networkInterfaces: DesktopNetworkInterfaces; }): readonly AdvertisedEndpoint[] { const seen = new Set(); const endpoints: AdvertisedEndpoint[] = []; for (const interfaceAddresses of Object.values(input.networkInterfaces)) { if (!interfaceAddresses) continue; for (const address of interfaceAddresses) { if (address.internal) continue; if (address.family !== "IPv4") continue; if (!isTailscaleIpv4Address(address.address)) continue; if (seen.has(address.address)) continue; seen.add(address.address); endpoints.push( createAdvertisedEndpoint({ provider: TAILSCALE_ENDPOINT_PROVIDER, source: "desktop-addon", id: `tailscale-ip:http://${address.address}:${input.port}`, label: "Tailscale IP", httpBaseUrl: `http://${address.address}:${input.port}`, reachability: "private-network", status: "available", description: "Reachable from devices on the same Tailnet.", }), ); } } return endpoints; } const resolveTailscaleMagicDnsAdvertisedEndpoint = Effect.fn( "resolveTailscaleMagicDnsAdvertisedEndpoint", )(function* (input: { readonly dnsName: string | null; readonly serveEnabled: boolean; readonly servePort?: number; readonly probe?: (baseUrl: string) => Effect.Effect; }): Effect.fn.Return, never, HttpClient.HttpClient> { if (!input.dnsName) { return Option.none(); } const httpBaseUrl = buildTailscaleHttpsBaseUrl({ magicDnsName: input.dnsName, ...(input.servePort === undefined ? {} : { servePort: input.servePort }), }); const probe = input.probe?.(httpBaseUrl) ?? probeTailscaleHttpsEndpoint({ baseUrl: httpBaseUrl, }); const isReachable = input.serveEnabled ? yield* probe : false; return Option.some( createAdvertisedEndpoint({ provider: TAILSCALE_ENDPOINT_PROVIDER, source: "desktop-addon", id: `tailscale-magicdns:${httpBaseUrl}`, label: "Tailscale HTTPS", httpBaseUrl, reachability: "private-network", hostedHttpsCompatibility: isReachable ? "compatible" : "requires-configuration", status: isReachable ? "available" : "unavailable", description: isReachable ? "HTTPS endpoint served by Tailscale Serve." : "MagicDNS hostname. Configure Tailscale Serve for HTTPS access.", }), ); }); export const resolveTailscaleAdvertisedEndpoints = Effect.fn("resolveTailscaleAdvertisedEndpoints")( function* (input: { readonly port: number; readonly serveEnabled?: boolean; readonly servePort?: number; readonly networkInterfaces: DesktopNetworkInterfaces; readonly statusJson?: string | null; readonly readMagicDnsName?: Effect.Effect< string | null, never, ChildProcessSpawner.ChildProcessSpawner >; readonly probe?: (baseUrl: string) => Effect.Effect; }): Effect.fn.Return< readonly AdvertisedEndpoint[], never, ChildProcessSpawner.ChildProcessSpawner | HttpClient.HttpClient > { const ipEndpoints = resolveTailscaleIpAdvertisedEndpoints(input); const readDnsName = input.readMagicDnsName ?? readTailscaleStatus.pipe( Effect.map((status) => status.magicDnsName), Effect.catch(() => Effect.succeed(null)), ); const dnsName = input.statusJson === undefined ? yield* readDnsName : input.statusJson ? yield* parseTailscaleMagicDnsName(input.statusJson).pipe( Effect.orElseSucceed(() => null), ) : null; const magicDnsEndpoint = yield* resolveTailscaleMagicDnsAdvertisedEndpoint({ dnsName, serveEnabled: input.serveEnabled === true, ...(input.servePort === undefined ? {} : { servePort: input.servePort }), ...(input.probe === undefined ? {} : { probe: input.probe }), }); return Option.match(magicDnsEndpoint, { onNone: () => ipEndpoints, onSome: (endpoint) => [...ipEndpoints, endpoint], }); }, );