import * as NodeOS from "node:os"; import { QrCode } from "@t3tools/shared/qrCode"; import * as Effect from "effect/Effect"; import { HttpServer } from "effect/unstable/http"; import { ServerConfig } from "./config.ts"; import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; export interface HeadlessServeAccessInfo { readonly connectionString: string; readonly token: string; readonly pairingUrl: string; } type NetworkInterfacesMap = ReturnType; export const isLoopbackHost = (host: string | undefined): boolean => { if (!host || host.length === 0) { return true; } return ( host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]" || host.startsWith("127.") ); }; export const isWildcardHost = (host: string | undefined): boolean => host === "0.0.0.0" || host === "::" || host === "[::]"; export const formatHostForUrl = (host: string): string => host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; const normalizeHost = (host: string): string => host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host; const isIpv4Family = (family: string | number): boolean => family === "IPv4" || family === 4; const isIpv6Family = (family: string | number): boolean => family === "IPv6" || family === 6; export const resolveHeadlessConnectionHost = ( host: string | undefined, interfaces: NetworkInterfacesMap = NodeOS.networkInterfaces(), ): string => { if (!host) { return "localhost"; } if (!isWildcardHost(host)) { return normalizeHost(host); } const interfaceEntries = Object.values(interfaces).flatMap((entries) => entries ?? []); const externalIpv4 = interfaceEntries.find( (entry) => !entry.internal && isIpv4Family(entry.family), ); if (externalIpv4) { return externalIpv4.address; } const externalIpv6 = interfaceEntries.find( (entry) => !entry.internal && isIpv6Family(entry.family), ); return externalIpv6 ? normalizeHost(externalIpv6.address) : "localhost"; }; export const resolveHeadlessConnectionString = ( host: string | undefined, port: number, interfaces: NetworkInterfacesMap = NodeOS.networkInterfaces(), ): string => { const connectionHost = resolveHeadlessConnectionHost(host, interfaces); return `http://${formatHostForUrl(connectionHost)}:${port}`; }; export const resolveListeningPort = (address: unknown, fallbackPort: number): number => { if ( typeof address === "object" && address !== null && "port" in address && typeof address.port === "number" ) { return address.port; } return fallbackPort; }; export const buildPairingUrl = (connectionString: string, token: string): string => { const url = new URL(connectionString); url.pathname = "/pair"; url.searchParams.delete("token"); url.hash = new URLSearchParams([["token", token]]).toString(); return url.toString(); }; export const renderTerminalQrCode = (value: string, margin = 2): string => { const qrCode = QrCode.encodeText(value, QrCode.Ecc.MEDIUM); const rows: Array = []; const isDark = (x: number, y: number): boolean => x >= 0 && x < qrCode.size && y >= 0 && y < qrCode.size && qrCode.getModule(x, y); for (let y = -margin; y < qrCode.size + margin; y += 2) { let row = ""; for (let x = -margin; x < qrCode.size + margin; x += 1) { const topDark = isDark(x, y); const bottomDark = isDark(x, y + 1); row += topDark ? (bottomDark ? "█" : "▀") : bottomDark ? "▄" : " "; } rows.push(row); } return rows.join("\n"); }; export const formatHeadlessServeOutput = (accessInfo: HeadlessServeAccessInfo): string => [ "T3 Code server is ready.", `Connection string: ${accessInfo.connectionString}`, `Token: ${accessInfo.token}`, `Pairing URL: ${accessInfo.pairingUrl}`, "", renderTerminalQrCode(accessInfo.pairingUrl), "", ].join("\n"); export const issueHeadlessServeAccessInfo = Effect.fn("issueHeadlessServeAccessInfo")(function* () { const serverConfig = yield* ServerConfig; const httpServer = yield* HttpServer.HttpServer; const serverAuth = yield* EnvironmentAuth.EnvironmentAuth; const connectionString = resolveHeadlessConnectionString( serverConfig.host, resolveListeningPort(httpServer.address, serverConfig.port), ); const issued = yield* serverAuth.issueStartupPairingCredential(); return { connectionString, token: issued.credential, pairingUrl: buildPairingUrl(connectionString, issued.credential), } satisfies HeadlessServeAccessInfo; });