272 lines · 9.7 KB · TypeScript
RawDownload
import type { DesktopSshEnvironmentTarget, EnvironmentId } from "@t3tools/contracts";import { resolveRemotePairingTarget } from "@t3tools/shared/remote";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import * as SubscriptionRef from "effect/SubscriptionRef";import * as HttpClient from "effect/unstable/http/HttpClient"; import { bootstrapRemoteBearerSession } from "../authorization/remote.ts";import { deriveWsBaseUrl, normalizeHttpBaseUrl } from "../environment/endpoint.ts";import { fetchRemoteEnvironmentDescriptor } from "../environment/descriptor.ts";import * as ClientCapabilities from "../platform/capabilities.ts";import {  BearerConnectionCredential,  BearerConnectionProfile,  BearerConnectionRegistration,  type ConnectionCatalogEntry,  type ConnectionCredential,  SshConnectionProfile,  SshConnectionRegistration,} from "./catalog.ts";import * as ConnectionCredentialStore from "./credentialStore.ts";import { mapRemoteEnvironmentError } from "./errors.ts";import {  BearerConnectionTarget,  ConnectionBlockedError,  SshConnectionTarget,  type ConnectionAttemptError,} from "./model.ts";import * as Persistence from "../platform/persistence.ts";import * as EnvironmentRegistry from "./registry.ts"; export interface PairingConnectionInput {  readonly pairingUrl?: string;  readonly host?: string;  readonly pairingCode?: string;} export interface SshConnectionInput {  readonly target: DesktopSshEnvironmentTarget;  readonly label?: string;} export interface BearerConnectionUpdateInput {  readonly environmentId: EnvironmentId;  readonly label: string;  readonly httpBaseUrl: string;} export class ConnectionOnboarding extends Context.Service<  ConnectionOnboarding,  {    readonly registerPairing: (      input: PairingConnectionInput,    ) => Effect.Effect<      EnvironmentId,      ConnectionAttemptError | Persistence.ConnectionPersistenceError    >;    readonly registerSsh: (      input: SshConnectionInput,    ) => Effect.Effect<      EnvironmentId,      ConnectionAttemptError | Persistence.ConnectionPersistenceError    >;    readonly updateBearer: (      input: BearerConnectionUpdateInput,    ) => Effect.Effect<void, ConnectionAttemptError | Persistence.ConnectionPersistenceError>;  }>()("@t3tools/client-runtime/connection/onboarding/ConnectionOnboarding") {} const resolvePairingTarget = Effect.fn("clientRuntime.connection.onboarding.resolvePairingTarget")(  function* (input: PairingConnectionInput) {    return yield* Effect.try({      try: () => resolveRemotePairingTarget(input),      catch: (cause) =>        new ConnectionBlockedError({          reason: "configuration",          detail: cause instanceof Error ? cause.message : "The pairing details are invalid.",        }),    });  },); export const preparePairingRegistration = Effect.fn(  "clientRuntime.connection.onboarding.preparePairingRegistration",)(function* (input: PairingConnectionInput) {  const target = yield* resolvePairingTarget(input);  const presentation = yield* ClientCapabilities.ClientPresentation;  const descriptor = yield* fetchRemoteEnvironmentDescriptor({    httpBaseUrl: target.httpBaseUrl,  }).pipe(Effect.mapError(mapRemoteEnvironmentError));  const access = yield* bootstrapRemoteBearerSession({    httpBaseUrl: target.httpBaseUrl,    credential: target.credential,    scopes: presentation.scopes,    clientMetadata: presentation.metadata,  }).pipe(Effect.mapError(mapRemoteEnvironmentError));  const connectionId = `bearer:${descriptor.environmentId}`;   return new BearerConnectionRegistration({    target: new BearerConnectionTarget({      environmentId: descriptor.environmentId,      label: descriptor.label,      connectionId,    }),    profile: new BearerConnectionProfile({      connectionId,      environmentId: descriptor.environmentId,      label: descriptor.label,      httpBaseUrl: target.httpBaseUrl,      wsBaseUrl: target.wsBaseUrl,    }),    credential: new BearerConnectionCredential({      token: access.access_token,    }),  });}); export const registerPairingConnection = Effect.fn(  "clientRuntime.connection.onboarding.registerPairingConnection",)(function* (input: PairingConnectionInput) {  const registration = yield* preparePairingRegistration(input);  const registry = yield* EnvironmentRegistry.EnvironmentRegistry;  yield* registry.register(registration);  return registration.target.environmentId;}); const isBearerCredential = Schema.is(BearerConnectionCredential);const isBearerProfile = Schema.is(BearerConnectionProfile); export const updateBearerConnection = Effect.fn(  "clientRuntime.connection.onboarding.updateBearerConnection",)(function* (input: BearerConnectionUpdateInput) {  const registry = yield* EnvironmentRegistry.EnvironmentRegistry;  const credentials = yield* ConnectionCredentialStore.ConnectionCredentialStore;  const entry = (yield* SubscriptionRef.get(registry.entries)).get(input.environmentId);  const credential =    entry?.target._tag === "BearerConnectionTarget"      ? yield* credentials.get(entry.target.connectionId)      : Option.none();  const registration = yield* prepareBearerConnectionUpdate({    input,    entry: Option.fromUndefinedOr(entry),    credential,  });  yield* registry.register(registration);}); export const prepareBearerConnectionUpdate = Effect.fn(  "clientRuntime.connection.onboarding.prepareBearerConnectionUpdate",)(function* (options: {  readonly input: BearerConnectionUpdateInput;  readonly entry: Option.Option<ConnectionCatalogEntry>;  readonly credential: Option.Option<ConnectionCredential>;}) {  const entry = Option.getOrNull(options.entry);  if (    entry === undefined ||    entry === null ||    entry.target._tag !== "BearerConnectionTarget" ||    Option.isNone(entry.profile) ||    !isBearerProfile(entry.profile.value)  ) {    return yield* new ConnectionBlockedError({      reason: "configuration",      detail: "Only saved bearer environments can be edited.",    });  }   const credential = options.credential;  if (Option.isNone(credential) || !isBearerCredential(credential.value)) {    return yield* new ConnectionBlockedError({      reason: "authentication",      detail: "The saved bearer credential is unavailable.",    });  }   const label = options.input.label.trim();  if (label === "") {    return yield* new ConnectionBlockedError({      reason: "configuration",      detail: "Environment label cannot be empty.",    });  }  const httpBaseUrl = yield* Effect.try({    try: () => normalizeHttpBaseUrl(options.input.httpBaseUrl),    catch: (cause) =>      new ConnectionBlockedError({        reason: "configuration",        detail: cause instanceof Error ? cause.message : "The environment URL is invalid.",      }),  });  const connectionId = entry.target.connectionId;  return new BearerConnectionRegistration({    target: new BearerConnectionTarget({      environmentId: options.input.environmentId,      label,      connectionId,    }),    profile: new BearerConnectionProfile({      connectionId,      environmentId: options.input.environmentId,      label,      httpBaseUrl,      wsBaseUrl: deriveWsBaseUrl(httpBaseUrl),    }),    credential: credential.value,  });}); export const prepareSshRegistration = Effect.fn(  "clientRuntime.connection.onboarding.prepareSshRegistration",)(function* (input: SshConnectionInput) {  const gateway = yield* ClientCapabilities.SshEnvironmentGateway;  const provisioned = yield* gateway.provision(input.target);  const connectionId = `ssh:${provisioned.environmentId}`;  const label = input.label?.trim() || provisioned.label || provisioned.bootstrap.target.alias;   return new SshConnectionRegistration({    target: new SshConnectionTarget({      environmentId: provisioned.environmentId,      label,      connectionId,    }),    profile: new SshConnectionProfile({      connectionId,      environmentId: provisioned.environmentId,      label,      target: provisioned.bootstrap.target,    }),  });}); export const registerSshConnection = Effect.fn(  "clientRuntime.connection.onboarding.registerSshConnection",)(function* (input: SshConnectionInput) {  const registration = yield* prepareSshRegistration(input);  const registry = yield* EnvironmentRegistry.EnvironmentRegistry;  yield* registry.register(registration);  return registration.target.environmentId;}); export const make = Effect.gen(function* () {  const registry = yield* EnvironmentRegistry.EnvironmentRegistry;  const presentation = yield* ClientCapabilities.ClientPresentation;  const httpClient = yield* HttpClient.HttpClient;  const ssh = yield* ClientCapabilities.SshEnvironmentGateway;  const credentials = yield* ConnectionCredentialStore.ConnectionCredentialStore;   return ConnectionOnboarding.of({    registerPairing: (input) =>      registerPairingConnection(input).pipe(        Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, registry),        Effect.provideService(ClientCapabilities.ClientPresentation, presentation),        Effect.provideService(HttpClient.HttpClient, httpClient),      ),    registerSsh: (input) =>      registerSshConnection(input).pipe(        Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, registry),        Effect.provideService(ClientCapabilities.SshEnvironmentGateway, ssh),      ),    updateBearer: (input) =>      updateBearerConnection(input).pipe(        Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, registry),        Effect.provideService(ConnectionCredentialStore.ConnectionCredentialStore, credentials),      ),  });}); export const layer = Layer.effect(ConnectionOnboarding, make);