import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Electron from "electron"; export interface ElectronAppMetadata { readonly appVersion: string; readonly appPath: string; readonly isPackaged: boolean; readonly resourcesPath: string; readonly runningUnderArm64Translation: boolean; } export class ElectronAppMetadataReadError extends Schema.TaggedErrorClass()( "ElectronAppMetadataReadError", { property: Schema.Literals(["app-version", "app-path"]), cause: Schema.Defect(), }, ) { override get message(): string { return `Failed to read Electron app metadata property "${this.property}".`; } } export class ElectronAppWhenReadyError extends Schema.TaggedErrorClass()( "ElectronAppWhenReadyError", { isPackaged: Schema.Boolean, cause: Schema.Defect(), }, ) { override get message(): string { return `Failed to wait for the Electron app to become ready (packaged: ${this.isPackaged}).`; } } export class ElectronApp extends Context.Service< ElectronApp, { readonly metadata: Effect.Effect; readonly name: Effect.Effect; readonly whenReady: Effect.Effect; readonly quit: Effect.Effect; readonly exit: (code: number) => Effect.Effect; readonly relaunch: (options: Electron.RelaunchOptions) => Effect.Effect; readonly setPath: ( name: Parameters[0], path: string, ) => Effect.Effect; readonly setName: (name: string) => Effect.Effect; readonly setAboutPanelOptions: ( options: Electron.AboutPanelOptionsOptions, ) => Effect.Effect; readonly setAppUserModelId: (id: string) => Effect.Effect; readonly getAppMetrics: Effect.Effect>; readonly isDefaultProtocolClient: (protocol: string) => Effect.Effect; readonly setAsDefaultProtocolClient: ( protocol: string, path?: string, args?: readonly string[], ) => Effect.Effect; readonly setDesktopName: (desktopName: string) => Effect.Effect; readonly setDockIcon: (iconPath: string) => Effect.Effect; readonly appendCommandLineSwitch: (switchName: string, value?: string) => Effect.Effect; readonly onBeforeQuitForUpdate: ( listener: () => void, ) => Effect.Effect; readonly removeCommandLineSwitch: (switchName: string) => Effect.Effect; readonly on: >( eventName: string, listener: (...args: Args) => void, ) => Effect.Effect; } >()("@t3tools/desktop/electron/ElectronApp") {} const addScopedAppListener = >( eventName: string, listener: (...args: Args) => void, ): Effect.Effect => Effect.acquireRelease( Effect.sync(() => { Electron.app.on(eventName as any, listener as any); }), () => Effect.sync(() => { Electron.app.removeListener(eventName as any, listener as any); }), ).pipe(Effect.asVoid); export const make = ElectronApp.of({ metadata: Effect.gen(function* () { const appVersion = yield* Effect.try({ try: () => Electron.app.getVersion(), catch: (cause) => new ElectronAppMetadataReadError({ property: "app-version", cause, }), }); const appPath = yield* Effect.try({ try: () => Electron.app.getAppPath(), catch: (cause) => new ElectronAppMetadataReadError({ property: "app-path", cause, }), }); return { appVersion, appPath, isPackaged: Electron.app.isPackaged, resourcesPath: process.resourcesPath, runningUnderArm64Translation: Electron.app.runningUnderARM64Translation === true, }; }), name: Effect.sync(() => Electron.app.name), whenReady: Effect.gen(function* () { const isPackaged = Electron.app.isPackaged; yield* Effect.tryPromise({ try: () => Electron.app.whenReady(), catch: (cause) => new ElectronAppWhenReadyError({ isPackaged, cause }), }); }), quit: Effect.sync(() => { Electron.app.quit(); }), exit: (code) => Effect.sync(() => { Electron.app.exit(code); }), relaunch: (options) => Effect.sync(() => { Electron.app.relaunch(options); }), setPath: (name, path) => Effect.sync(() => { Electron.app.setPath(name, path); }), setName: (name) => Effect.sync(() => { Electron.app.setName(name); }), setAboutPanelOptions: (options) => Effect.sync(() => { Electron.app.setAboutPanelOptions(options); }), setAppUserModelId: (id) => Effect.sync(() => { Electron.app.setAppUserModelId(id); }), getAppMetrics: Effect.sync(() => Electron.app.getAppMetrics()), isDefaultProtocolClient: (protocol) => Effect.sync(() => Electron.app.isDefaultProtocolClient(protocol)), setAsDefaultProtocolClient: (protocol, path, args) => Effect.sync(() => { if (path === undefined) { return Electron.app.setAsDefaultProtocolClient(protocol); } return Electron.app.setAsDefaultProtocolClient(protocol, path, [...(args ?? [])]); }), setDesktopName: (desktopName) => Effect.sync(() => { const linuxApp = Electron.app as Electron.App & { setDesktopName?: (desktopName: string) => void; }; linuxApp.setDesktopName?.(desktopName); }), setDockIcon: (iconPath) => Effect.sync(() => { Electron.app.dock?.setIcon(iconPath); }), appendCommandLineSwitch: (switchName, value) => Effect.sync(() => { if (value === undefined) { Electron.app.commandLine.appendSwitch(switchName); return; } Electron.app.commandLine.appendSwitch(switchName, value); }), onBeforeQuitForUpdate: (listener) => Effect.acquireRelease( Effect.sync(() => { Electron.autoUpdater.on("before-quit-for-update", listener); }), () => Effect.sync(() => { Electron.autoUpdater.removeListener("before-quit-for-update", listener); }), ).pipe(Effect.asVoid), removeCommandLineSwitch: (switchName) => Effect.sync(() => { Electron.app.commandLine.removeSwitch(switchName); }), on: addScopedAppListener, }); export const layer = Layer.succeed(ElectronApp, make);