Julius Marmingerm service indirectionebda0b64 months agoHistory
328 lines · 11.4 KB · TypeScript
RawDownload
import {  BearerConnectionCredential,  BearerConnectionProfile,  BearerConnectionTarget,  RelayConnectionTarget,  SshConnectionProfile,  SshConnectionTarget,} from "@t3tools/client-runtime/connection";import {  ConnectionCatalogDocument as RuntimeConnectionCatalogDocument,  type ConnectionCatalogDocument as RuntimeConnectionCatalogDocumentType,} from "@t3tools/client-runtime/platform";import type { PersistedSavedEnvironmentRecord } from "@t3tools/contracts";import { fromLenientJson } from "@t3tools/shared/schemaJson";import * as Context from "effect/Context";import * as Crypto from "effect/Crypto";import * as Data from "effect/Data";import * as Effect from "effect/Effect";import * as Encoding from "effect/Encoding";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as PlatformError from "effect/PlatformError";import * as Schema from "effect/Schema"; import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts";import * as DesktopSavedEnvironments from "../settings/DesktopSavedEnvironments.ts";import * as DesktopEnvironment from "./DesktopEnvironment.ts"; const EncryptedConnectionCatalogDocument = Schema.Struct({  version: Schema.Literal(1),  encryptedCatalog: Schema.String,});type EncryptedConnectionCatalogDocument = typeof EncryptedConnectionCatalogDocument.Type; const EncryptedConnectionCatalogDocumentJson = fromLenientJson(EncryptedConnectionCatalogDocument);const decodeEncryptedConnectionCatalogDocumentJson = Schema.decodeEffect(  EncryptedConnectionCatalogDocumentJson,);const encodeEncryptedConnectionCatalogDocumentJson = Schema.encodeEffect(  EncryptedConnectionCatalogDocumentJson,);const RuntimeConnectionCatalogDocumentJson = Schema.fromJsonString(  RuntimeConnectionCatalogDocument,);const encodeRuntimeConnectionCatalogDocumentJson = Schema.encodeEffect(  RuntimeConnectionCatalogDocumentJson,); export class DesktopConnectionCatalogStoreWriteError extends Data.TaggedError(  "DesktopConnectionCatalogStoreWriteError",)<{  readonly cause: PlatformError.PlatformError | Schema.SchemaError;}> {  override get message() {    return `Failed to write desktop connection catalog: ${this.cause.message}`;  }} export class DesktopConnectionCatalogStoreDecodeError extends Data.TaggedError(  "DesktopConnectionCatalogStoreDecodeError",)<{  readonly cause: Encoding.EncodingError;}> {  override get message() {    return "Failed to decode the desktop connection catalog.";  }} export class DesktopConnectionCatalogStoreReadError extends Data.TaggedError(  "DesktopConnectionCatalogStoreReadError",)<{  readonly cause: PlatformError.PlatformError | Schema.SchemaError;}> {  override get message() {    return `Failed to read desktop connection catalog: ${this.cause.message}`;  }} export class DesktopConnectionCatalogStoreMigrationError extends Data.TaggedError(  "DesktopConnectionCatalogStoreMigrationError",)<{  readonly cause: unknown;}> {  override get message() {    return "Failed to migrate legacy desktop saved environments.";  }} export interface DesktopConnectionCatalogStoreShape {  readonly get: Effect.Effect<    Option.Option<string>,    | DesktopConnectionCatalogStoreReadError    | DesktopConnectionCatalogStoreDecodeError    | DesktopConnectionCatalogStoreMigrationError    | ElectronSafeStorage.ElectronSafeStorageAvailabilityError    | ElectronSafeStorage.ElectronSafeStorageDecryptError  >;  readonly set: (    catalog: string,  ) => Effect.Effect<    boolean,    | DesktopConnectionCatalogStoreWriteError    | ElectronSafeStorage.ElectronSafeStorageAvailabilityError    | ElectronSafeStorage.ElectronSafeStorageEncryptError  >;  readonly clear: Effect.Effect<void>;} export class DesktopConnectionCatalogStore extends Context.Service<  DesktopConnectionCatalogStore,  DesktopConnectionCatalogStoreShape>()("@t3tools/desktop/app/DesktopConnectionCatalogStore") {} function decodeSecretBytes(  encoded: string,): Effect.Effect<Uint8Array, DesktopConnectionCatalogStoreDecodeError> {  return Effect.fromResult(Encoding.decodeBase64(encoded)).pipe(    Effect.mapError((cause) => new DesktopConnectionCatalogStoreDecodeError({ cause })),  );} const readDocument = (  fileSystem: FileSystem.FileSystem,  catalogPath: string,): Effect.Effect<  Option.Option<EncryptedConnectionCatalogDocument>,  PlatformError.PlatformError | Schema.SchemaError> =>  fileSystem.readFileString(catalogPath).pipe(    Effect.catch((error) =>      error.reason._tag === "NotFound" ? Effect.succeed<string | null>(null) : Effect.fail(error),    ),    Effect.flatMap((raw) =>      raw === null        ? Effect.succeed(Option.none<EncryptedConnectionCatalogDocument>())        : decodeEncryptedConnectionCatalogDocumentJson(raw).pipe(Effect.map(Option.some)),    ),  ); const writeDocument = Effect.fn("desktop.connectionCatalogStore.writeDocument")(function* (input: {  readonly fileSystem: FileSystem.FileSystem;  readonly path: Path.Path;  readonly catalogPath: string;  readonly document: EncryptedConnectionCatalogDocument;  readonly suffix: string;}): Effect.fn.Return<void, PlatformError.PlatformError | Schema.SchemaError> {  const directory = input.path.dirname(input.catalogPath);  const tempPath = `${input.catalogPath}.${process.pid}.${input.suffix}.tmp`;  const encoded = yield* encodeEncryptedConnectionCatalogDocumentJson(input.document);  yield* input.fileSystem.makeDirectory(directory, { recursive: true });  yield* Effect.gen(function* () {    yield* input.fileSystem.writeFileString(tempPath, `${encoded}\n`);    yield* input.fileSystem.rename(tempPath, input.catalogPath);  }).pipe(    Effect.ensuring(      input.fileSystem.remove(tempPath, { force: true }).pipe(        Effect.catch((error) =>          Effect.logWarning("Could not remove a temporary connection catalog file.", {            tempPath,            error,          }),        ),      ),    ),  );}); function connectionId(prefix: "bearer" | "ssh", environmentId: string): string {  return `${prefix}:${environmentId}`;} const migrateSavedEnvironmentRecords = Effect.fn(  "desktop.connectionCatalogStore.migrateSavedEnvironmentRecords",)(function* (  records: readonly PersistedSavedEnvironmentRecord[],  savedEnvironments: DesktopSavedEnvironments.DesktopSavedEnvironmentsShape,): Effect.fn.Return<  RuntimeConnectionCatalogDocumentType,  DesktopSavedEnvironments.DesktopSavedEnvironmentsGetSecretError> {  const targets: Array<RuntimeConnectionCatalogDocumentType["targets"][number]> = [];  const profiles: Array<RuntimeConnectionCatalogDocumentType["profiles"][number]> = [];  const credentials: Array<RuntimeConnectionCatalogDocumentType["credentials"][number]> = [];   for (const record of records) {    if (record.relayManaged !== undefined) {      targets.push(        new RelayConnectionTarget({          environmentId: record.environmentId,          label: record.label,        }),      );      continue;    }     if (record.desktopSsh !== undefined) {      const id = connectionId("ssh", record.environmentId);      targets.push(        new SshConnectionTarget({          environmentId: record.environmentId,          label: record.label,          connectionId: id,        }),      );      profiles.push(        new SshConnectionProfile({          connectionId: id,          environmentId: record.environmentId,          label: record.label,          target: record.desktopSsh,        }),      );      continue;    }     const id = connectionId("bearer", record.environmentId);    targets.push(      new BearerConnectionTarget({        environmentId: record.environmentId,        label: record.label,        connectionId: id,      }),    );    profiles.push(      new BearerConnectionProfile({        connectionId: id,        environmentId: record.environmentId,        label: record.label,        httpBaseUrl: record.httpBaseUrl,        wsBaseUrl: record.wsBaseUrl,      }),    );    const token = yield* savedEnvironments.getSecret(record.environmentId);    if (Option.isSome(token)) {      credentials.push({        connectionId: id,        credential: new BearerConnectionCredential({ token: token.value }),      });    }  }   return {    schemaVersion: 1,    targets,    profiles,    credentials,    remoteDpopTokens: [],  };}); export const layer = Layer.effect(  DesktopConnectionCatalogStore,  Effect.gen(function* () {    const environment = yield* DesktopEnvironment.DesktopEnvironment;    const fileSystem = yield* FileSystem.FileSystem;    const path = yield* Path.Path;    const safeStorage = yield* ElectronSafeStorage.ElectronSafeStorage;    const crypto = yield* Crypto.Crypto;    const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;    const catalogPath = path.join(environment.stateDir, "connection-catalog.json");     const writeCatalog = Effect.fn("desktop.connectionCatalogStore.writeCatalog")(function* (      catalog: string,    ) {      const encryptedCatalog = Encoding.encodeBase64(yield* safeStorage.encryptString(catalog));      const suffix = (yield* crypto.randomUUIDv4.pipe(        Effect.mapError((cause) => new DesktopConnectionCatalogStoreWriteError({ cause })),      )).replace(/-/g, "");      yield* writeDocument({        fileSystem,        path,        catalogPath,        document: { version: 1, encryptedCatalog },        suffix,      }).pipe(Effect.mapError((cause) => new DesktopConnectionCatalogStoreWriteError({ cause })));    });     const migrateLegacyCatalog = Effect.gen(function* () {      if (!(yield* safeStorage.isEncryptionAvailable)) {        return Option.none<string>();      }      const records = yield* savedEnvironments.getRegistry;      if (records.length === 0) {        return Option.none<string>();      }      const catalog = yield* migrateSavedEnvironmentRecords(records, savedEnvironments);      const encoded = yield* encodeRuntimeConnectionCatalogDocumentJson(catalog);      yield* writeCatalog(encoded);      return Option.some(encoded);    }).pipe(Effect.mapError((cause) => new DesktopConnectionCatalogStoreMigrationError({ cause })));     return DesktopConnectionCatalogStore.of({      get: Effect.gen(function* () {        const document = yield* readDocument(fileSystem, catalogPath).pipe(          Effect.mapError((cause) => new DesktopConnectionCatalogStoreReadError({ cause })),        );        if (Option.isNone(document)) {          return yield* migrateLegacyCatalog;        }        if (!(yield* safeStorage.isEncryptionAvailable)) {          return Option.none<string>();        }        const decrypted = yield* decodeSecretBytes(document.value.encryptedCatalog).pipe(          Effect.flatMap(safeStorage.decryptString),        );        return Option.some(decrypted);      }).pipe(Effect.withSpan("desktop.connectionCatalogStore.get")),      set: Effect.fn("desktop.connectionCatalogStore.set")(function* (catalog) {        if (!(yield* safeStorage.isEncryptionAvailable)) {          return false;        }        yield* writeCatalog(catalog);        return true;      }),      clear: fileSystem.remove(catalogPath, { force: true }).pipe(        Effect.catch((error) =>          Effect.logWarning("Could not clear the desktop connection catalog.", {            catalogPath,            error,          }),        ),        Effect.withSpan("desktop.connectionCatalogStore.clear"),      ),    });  }),);