183 lines · 6.5 KB · TypeScript
RawDownload
import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings";import * as Context from "effect/Context";import * as Crypto from "effect/Crypto";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 PlatformError from "effect/PlatformError";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema"; import * as DesktopBackendManager from "./DesktopBackendManager.ts";import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";import * as DesktopServerExposure from "./DesktopServerExposure.ts"; export class DesktopBackendObservabilitySettingsReadError extends Schema.TaggedErrorClass<DesktopBackendObservabilitySettingsReadError>()(  "DesktopBackendObservabilitySettingsReadError",  {    settingsPath: Schema.String,    cause: Schema.Defect(),  },) {  override get message(): string {    return `Failed to read persisted backend observability settings at ${this.settingsPath}.`;  }} export class DesktopBackendConfiguration extends Context.Service<  DesktopBackendConfiguration,  {    readonly resolve: Effect.Effect<      DesktopBackendManager.DesktopBackendStartConfig,      PlatformError.PlatformError    >;  }>()("@t3tools/desktop/backend/DesktopBackendConfiguration") {} interface BackendObservabilitySettings {  readonly otlpTracesUrl: Option.Option<string>;  readonly otlpMetricsUrl: Option.Option<string>;} const emptyBackendObservabilitySettings: BackendObservabilitySettings = {  otlpTracesUrl: Option.none(),  otlpMetricsUrl: Option.none(),}; const DESKTOP_BACKEND_ENV_NAMES = [  "T3CODE_PORT",  "T3CODE_MODE",  "T3CODE_NO_BROWSER",  "T3CODE_HOST",  "T3CODE_DESKTOP_WS_URL",  "T3CODE_DESKTOP_LAN_ACCESS",  "T3CODE_DESKTOP_LAN_HOST",  "T3CODE_DESKTOP_HTTPS_ENDPOINTS",  "T3CODE_TAILSCALE_SERVE",  "T3CODE_TAILSCALE_SERVE_PORT",] as const; const backendChildEnvPatch = (): Record<string, string | undefined> =>  Object.fromEntries(DESKTOP_BACKEND_ENV_NAMES.map((name) => [name, undefined])); const logBackendObservabilitySettingsReadFailure = (  settingsPath: string,  cause: PlatformError.PlatformError,) => {  const error = new DesktopBackendObservabilitySettingsReadError({ settingsPath, cause });  return Effect.logWarning(error).pipe(    Effect.annotateLogs({      component: "desktop-backend-configuration",      error,    }),  );}; const readPersistedBackendObservabilitySettings = Effect.gen(function* () {  const fileSystem = yield* FileSystem.FileSystem;  const environment = yield* DesktopEnvironment.DesktopEnvironment;  const raw = yield* fileSystem.readFileString(environment.serverSettingsPath).pipe(    Effect.map(Option.some),    Effect.catchTags({      PlatformError: (cause) =>        cause.reason._tag === "NotFound"          ? Effect.succeed(Option.none())          : logBackendObservabilitySettingsReadFailure(environment.serverSettingsPath, cause).pipe(              Effect.as(Option.none()),            ),    }),  );  if (Option.isNone(raw)) {    return emptyBackendObservabilitySettings;  }   const parsed = parsePersistedServerObservabilitySettings(raw.value);  return {    otlpTracesUrl: Option.fromNullishOr(parsed.otlpTracesUrl),    otlpMetricsUrl: Option.fromNullishOr(parsed.otlpMetricsUrl),  };}); const resolveBackendStartConfig = Effect.fn("desktop.backendConfiguration.resolveStartConfig")(  function* (input: {    readonly bootstrapToken: string;    readonly observabilitySettings: BackendObservabilitySettings;  }): Effect.fn.Return<    DesktopBackendManager.DesktopBackendStartConfig,    never,    DesktopEnvironment.DesktopEnvironment | DesktopServerExposure.DesktopServerExposure  > {    const environment = yield* DesktopEnvironment.DesktopEnvironment;    const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;    const backendExposure = yield* serverExposure.backendConfig;     return {      executablePath: process.execPath,      entryPath: environment.backendEntryPath,      cwd: environment.backendCwd,      env: {        ...backendChildEnvPatch(),        ELECTRON_RUN_AS_NODE: "1",      },      bootstrap: {        mode: "desktop",        noBrowser: true,        port: backendExposure.port,        t3Home: environment.baseDir,        host: backendExposure.bindHost,        desktopBootstrapToken: input.bootstrapToken,        tailscaleServeEnabled: backendExposure.tailscaleServeEnabled,        tailscaleServePort: backendExposure.tailscaleServePort,        ...Option.match(input.observabilitySettings.otlpTracesUrl, {          onNone: () => ({}),          onSome: (otlpTracesUrl) => ({ otlpTracesUrl }),        }),        ...Option.match(input.observabilitySettings.otlpMetricsUrl, {          onNone: () => ({}),          onSome: (otlpMetricsUrl) => ({ otlpMetricsUrl }),        }),      },      httpBaseUrl: backendExposure.httpBaseUrl,      captureOutput: true,    };  },); export const make = Effect.gen(function* () {  const environment = yield* DesktopEnvironment.DesktopEnvironment;  const fileSystem = yield* FileSystem.FileSystem;  const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;  const crypto = yield* Crypto.Crypto;  const tokenRef = yield* Ref.make(Option.none<string>());  const getOrCreateBootstrapToken = Effect.gen(function* () {    const existing = yield* Ref.get(tokenRef);    if (Option.isSome(existing)) {      return existing.value;    }     const token = Encoding.encodeHex(yield* crypto.randomBytes(24));    yield* Ref.set(tokenRef, Option.some(token));    return token;  });   return DesktopBackendConfiguration.of({    resolve: Effect.gen(function* () {      const bootstrapToken = yield* getOrCreateBootstrapToken;      const observabilitySettings = yield* readPersistedBackendObservabilitySettings.pipe(        Effect.provideService(FileSystem.FileSystem, fileSystem),        Effect.provideService(DesktopEnvironment.DesktopEnvironment, environment),      );      return yield* resolveBackendStartConfig({        bootstrapToken,        observabilitySettings,      }).pipe(        Effect.provideService(DesktopEnvironment.DesktopEnvironment, environment),        Effect.provideService(DesktopServerExposure.DesktopServerExposure, serverExposure),      );    }).pipe(Effect.withSpan("desktop.backendConfiguration.resolve")),  });}); export const layer = Layer.effect(DesktopBackendConfiguration, make);