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<typeof NodeOS.networkInterfaces>; 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<string> = []; 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;});
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<typeof NodeOS.networkInterfaces>;
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<string> = [];
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;
});