104 lines · 4.4 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:off - pre-ready Electron setup reads settings and prepares the Linux desktop entry synchronously before app services are available.import * as NodeFS from "node:fs";import * as NodeOS from "node:os";import * as NodePath from "node:path";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer"; import * as Electron from "electron";import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as DesktopEarlyElectronStartup from "./DesktopEarlyElectronStartup.ts";import { resolveDesktopAppBranding } from "./DesktopEnvironment.ts";import { renderUrlHandlerDesktopEntry } from "./DesktopLinuxUrlHandler.ts";import * as ElectronProtocol from "../electron/ElectronProtocol.ts"; export interface DesktopPreReadyCommandLineReader {  readonly hasSwitch: (switchName: string) => boolean;  readonly getSwitchValue: (switchName: string) => string;} function readCommandLineSwitchValue(  commandLine: DesktopPreReadyCommandLineReader,  switchName: string,): string | null {  if (!commandLine.hasSwitch(switchName)) {    return null;  }   const value = commandLine.getSwitchValue(switchName).trim();  return value.length > 0 ? value : null;} export const resolveEarlyLinuxElectronOptionsFromProcess =  (): DesktopEarlyElectronStartup.EarlyLinuxElectronOptions =>    DesktopEarlyElectronStartup.resolveEarlyLinuxElectronOptions({      env: process.env,      homeDirectory: NodeOS.homedir(),      joinPath: NodePath.posix.join,      readFileString: (path) => NodeFS.readFileSync(path, "utf8"),    }); export class DesktopPreReadyElectronOptions extends Context.Service<  DesktopPreReadyElectronOptions,  {    readonly linux: DesktopEarlyElectronStartup.EarlyLinuxElectronOptions | null;    readonly linuxPasswordStoreCommandLine: string | null;  }>()("@t3tools/desktop/app/DesktopPreReadyPlatform/DesktopPreReadyElectronOptions") {} /** @public Service construction is part of the canonical Effect module API. */export const make = Effect.gen(function* () {  const platform = yield* HostProcessPlatform;  return yield* Effect.sync((): DesktopPreReadyElectronOptions["Service"] => {    const linuxPasswordStoreCommandLine =      platform === "linux"        ? readCommandLineSwitchValue(Electron.app.commandLine, "password-store")        : null;    const linux = platform === "linux" ? resolveEarlyLinuxElectronOptionsFromProcess() : null;     if (linux !== null) {      // The portal also requires a valid desktop entry. An AppImage update may      // have removed the executable referenced by the previous launch's entry.      try {        const applicationsDir = NodePath.posix.join(          process.env.XDG_DATA_HOME?.trim() ||            NodePath.posix.join(NodeOS.homedir(), ".local", "share"),          "applications",        );        NodeFS.mkdirSync(applicationsDir, { recursive: true });        NodeFS.writeFileSync(          NodePath.posix.join(applicationsDir, linux.linuxDesktopEntryName),          renderUrlHandlerDesktopEntry({            displayName: resolveDesktopAppBranding({              isDevelopment: linux.isDevelopment,              appVersion: Electron.app.getVersion(),            }).displayName,            execTarget: process.env.APPIMAGE?.trim() || process.execPath,            scheme: ElectronProtocol.getDesktopScheme(linux.isDevelopment),          }),          "utf8",        );      } catch {        // The URL handler retries with the full environment and logs failures.      }      // Chromium caches its portal registration during startup. Set the identity      // before any asynchronous work can initialize it with Electron's default.      Electron.app.setDesktopName(linux.linuxDesktopEntryName);      Electron.app.commandLine.appendSwitch("class", linux.linuxWmClass);      if (linux.passwordStore !== null && linuxPasswordStoreCommandLine === null) {        Electron.app.commandLine.appendSwitch("password-store", linux.passwordStore);      }    }     return { linux, linuxPasswordStoreCommandLine };  });}).pipe(Effect.withSpan("desktop.electron.configureBeforeReady")); // Keep Electron's strict pre-ready setup isolated so later runtime layers cannot// observe app readiness before scheme privileges and command-line switches exist.export const layer = Layer.mergeAll(  ElectronProtocol.layerSchemePrivileges,  Layer.effect(DesktopPreReadyElectronOptions, make),);