170 lines · 6.0 KB · TypeScript
RawDownload
import type {  DesktopDiscoveredSshHost,  DesktopSshEnvironmentBootstrap,  DesktopSshEnvironmentTarget,} from "@t3tools/contracts";import * as NetService from "@t3tools/shared/Net";import * as SshAuth from "@t3tools/ssh/auth";import { discoverSshHosts } from "@t3tools/ssh/config";import {  SshCommandError,  SshHostDiscoveryError,  SshInvalidTargetError,  SshLaunchError,  SshPairingError,  SshPasswordPromptError,  SshReadinessError,} from "@t3tools/ssh/errors";import * as SshTunnel from "@t3tools/ssh/tunnel";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path";import * as HttpClient from "effect/unstable/http/HttpClient";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import * as DesktopSshPasswordPrompts from "./DesktopSshPasswordPrompts.ts"; export type DesktopSshEnvironmentRuntimeServices =  | ChildProcessSpawner.ChildProcessSpawner  | FileSystem.FileSystem  | Path.Path  | HttpClient.HttpClient  | NetService.NetService; export type DesktopSshEnvironmentOperationError =  | SshCommandError  | SshInvalidTargetError  | SshLaunchError  | SshPairingError  | SshReadinessError  | SshPasswordPromptError  | NetService.NetError; export type DesktopSshEnvironmentDiscoverError = SshHostDiscoveryError; export type DesktopSshEnvironmentError =  | DesktopSshEnvironmentDiscoverError  | DesktopSshEnvironmentOperationError; export class DesktopSshEnvironment extends Context.Service<  DesktopSshEnvironment,  {    readonly discoverHosts: (input?: {      readonly homeDir?: string;    }) => Effect.Effect<readonly DesktopDiscoveredSshHost[], DesktopSshEnvironmentDiscoverError>;    readonly ensureEnvironment: (      target: DesktopSshEnvironmentTarget,      options?: { readonly issuePairingToken?: boolean },    ) => Effect.Effect<DesktopSshEnvironmentBootstrap, DesktopSshEnvironmentOperationError>;    readonly disconnectEnvironment: (      target: DesktopSshEnvironmentTarget,    ) => Effect.Effect<void, DesktopSshEnvironmentOperationError>;  }>()("@t3tools/desktop/ssh/DesktopSshEnvironment") {} export interface DesktopSshEnvironmentLayerOptions {  readonly resolveCliPackageSpec?: () => string;  readonly resolveCliRunner?: Effect.Effect<SshTunnel.RemoteT3RunnerOptions>;} function discoverDesktopSshHostsEffect(input?: { readonly homeDir?: string }) {  return discoverSshHosts(input ?? {});} export function isDesktopSshPasswordPromptCancellation(  error: unknown,): error is SshPasswordPromptError {  return (    error instanceof SshPasswordPromptError &&    DesktopSshPasswordPrompts.isDesktopSshPasswordPromptCancellation(error.cause)  );} function unexpectedPasswordPromptError(error: never): never {  throw new Error(`Unhandled desktop SSH password prompt error: ${String(error)}`);} export function toSshPasswordPromptError(  cause: DesktopSshPasswordPrompts.DesktopSshPasswordPromptRequestError,): SshPasswordPromptError {  let message: string;  switch (cause._tag) {    case "DesktopSshPromptRequestIdGenerationError":      message = "Secure randomness is unavailable.";      break;    case "DesktopSshPromptWindowUnavailableError":    case "DesktopSshPromptPresentationError":      message = "T3 Code window is not available for SSH authentication.";      break;    case "DesktopSshPromptTimedOutError":      message = `SSH authentication timed out for ${cause.destination}.`;      break;    case "DesktopSshPromptCancelledError":      message = `SSH authentication cancelled for ${cause.destination}.`;      break;    case "DesktopSshPromptWindowClosedError":      message = "SSH authentication was cancelled because the app window closed.";      break;    case "DesktopSshPromptServiceStoppedError":      message = "SSH password prompt service stopped.";      break;    default:      return unexpectedPasswordPromptError(cause);  }  return new SshPasswordPromptError({ message, cause });} const makePasswordPrompt = (  prompts: DesktopSshPasswordPrompts.DesktopSshPasswordPrompts["Service"],): SshAuth.SshPasswordPrompt["Service"] => ({  isAvailable: true,  request: (request: SshAuth.SshPasswordRequest) =>    prompts.request(request).pipe(Effect.mapError(toSshPasswordPromptError)),}); export const make = Effect.gen(function* () {  const manager = yield* SshTunnel.SshEnvironmentManager;  const prompts = yield* DesktopSshPasswordPrompts.DesktopSshPasswordPrompts;  const runtimeContext = yield* Effect.context<DesktopSshEnvironmentRuntimeServices>();  const passwordPrompt = SshAuth.SshPasswordPrompt.of(makePasswordPrompt(prompts));   return DesktopSshEnvironment.of({    discoverHosts: (input) =>      discoverDesktopSshHostsEffect(input).pipe(        Effect.provide(runtimeContext),        Effect.withSpan("desktop.ssh.discoverHosts"),      ),    ensureEnvironment: (target, ensureOptions) =>      manager        .ensureEnvironment(target, ensureOptions)        .pipe(          Effect.provideService(SshAuth.SshPasswordPrompt, passwordPrompt),          Effect.provide(runtimeContext),          Effect.withSpan("desktop.ssh.ensureEnvironment"),        ),    disconnectEnvironment: (target) =>      manager        .disconnectEnvironment(target)        .pipe(          Effect.provideService(SshAuth.SshPasswordPrompt, passwordPrompt),          Effect.provide(runtimeContext),          Effect.withSpan("desktop.ssh.disconnectEnvironment"),        ),  });}); export const layer = (options: DesktopSshEnvironmentLayerOptions = {}) =>  Layer.effect(DesktopSshEnvironment, make).pipe(    Layer.provide(      SshTunnel.SshEnvironmentManager.layer({        ...(options.resolveCliPackageSpec === undefined          ? {}          : { resolveCliPackageSpec: options.resolveCliPackageSpec }),        ...(options.resolveCliRunner === undefined          ? {}          : { resolveCliRunner: options.resolveCliRunner }),      }),    ),  );