diff --git a/apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts b/apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts index 26c0c8f8943..e0be7f39b39 100644 --- a/apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts +++ b/apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts @@ -237,7 +237,6 @@ describe("DesktopConnectionCatalogStore", () => { error, DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreDocumentDecodeError, ); - assert.equal(error.operation, "decode-catalog-document"); assert.equal(error.catalogPath, catalogPath); assert.exists(error.cause); assert.equal(yield* fileSystem.readFileString(catalogPath), "{not-json"); @@ -272,7 +271,6 @@ describe("DesktopConnectionCatalogStore", () => { error, DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreReadError, ); - assert.equal(error.operation, "read-catalog"); assert.equal(error.catalogPath, `${baseDir}/userdata/connection-catalog.json`); assert.strictEqual(error.cause, permissionError); assert.equal( @@ -368,7 +366,6 @@ describe("DesktopConnectionCatalogStore", () => { error, DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreDecodeError, ); - assert.equal(error.operation, "decode-encrypted-catalog"); assert.equal(error.resource, "encryptedCatalog"); assert.equal(error.catalogPath, catalogPath); assert.exists(error.cause); diff --git a/apps/desktop/src/app/DesktopConnectionCatalogStore.ts b/apps/desktop/src/app/DesktopConnectionCatalogStore.ts index 7eaf3ec7cf6..8467fe3f077 100644 --- a/apps/desktop/src/app/DesktopConnectionCatalogStore.ts +++ b/apps/desktop/src/app/DesktopConnectionCatalogStore.ts @@ -92,7 +92,6 @@ const writeError = ( export class DesktopConnectionCatalogStoreDecodeError extends Schema.TaggedErrorClass()( "DesktopConnectionCatalogStoreDecodeError", { - operation: Schema.Literal("decode-encrypted-catalog"), resource: Schema.Literal("encryptedCatalog"), catalogPath: Schema.String, cause: Schema.Defect(), @@ -106,7 +105,6 @@ export class DesktopConnectionCatalogStoreDecodeError extends Schema.TaggedError export class DesktopConnectionCatalogStoreReadError extends Schema.TaggedErrorClass()( "DesktopConnectionCatalogStoreReadError", { - operation: Schema.Literal("read-catalog"), catalogPath: Schema.String, cause: Schema.Defect(), }, @@ -119,7 +117,6 @@ export class DesktopConnectionCatalogStoreReadError extends Schema.TaggedErrorCl export class DesktopConnectionCatalogStoreDocumentDecodeError extends Schema.TaggedErrorClass()( "DesktopConnectionCatalogStoreDocumentDecodeError", { - operation: Schema.Literal("decode-catalog-document"), catalogPath: Schema.String, cause: Schema.Defect(), }, @@ -167,16 +164,13 @@ export class DesktopConnectionCatalogStore extends Context.Service< | DesktopConnectionCatalogStoreDocumentDecodeError | DesktopConnectionCatalogStoreDecodeError | DesktopConnectionCatalogStoreMigrationError - | ElectronSafeStorage.ElectronSafeStorageAvailabilityError - | ElectronSafeStorage.ElectronSafeStorageDecryptError + | ElectronSafeStorage.ElectronSafeStorageError >; readonly set: ( catalog: string, ) => Effect.Effect< boolean, - | DesktopConnectionCatalogStoreWriteError - | ElectronSafeStorage.ElectronSafeStorageAvailabilityError - | ElectronSafeStorage.ElectronSafeStorageEncryptError + DesktopConnectionCatalogStoreWriteError | ElectronSafeStorage.ElectronSafeStorageError >; readonly clear: Effect.Effect; } @@ -190,7 +184,6 @@ function decodeSecretBytes( Effect.mapError( (cause) => new DesktopConnectionCatalogStoreDecodeError({ - operation: "decode-encrypted-catalog", resource: "encryptedCatalog", catalogPath, cause, @@ -212,7 +205,6 @@ const readDocument = ( ? Effect.succeed(null) : Effect.fail( new DesktopConnectionCatalogStoreReadError({ - operation: "read-catalog", catalogPath, cause: error, }), @@ -226,7 +218,6 @@ const readDocument = ( Effect.mapError( (cause) => new DesktopConnectionCatalogStoreDocumentDecodeError({ - operation: "decode-catalog-document", catalogPath, cause, }), diff --git a/apps/desktop/src/app/DesktopLifecycle.ts b/apps/desktop/src/app/DesktopLifecycle.ts index b62662ad27b..ad08d2f5a2e 100644 --- a/apps/desktop/src/app/DesktopLifecycle.ts +++ b/apps/desktop/src/app/DesktopLifecycle.ts @@ -8,7 +8,7 @@ import * as Scope from "effect/Scope"; import type * as Electron from "electron"; import * as DesktopEnvironment from "./DesktopEnvironment.ts"; -import * as DesktopObservability from "./DesktopObservability.ts"; +import { makeComponentLogger } from "./DesktopObservability.ts"; import * as DesktopShutdown from "./DesktopShutdown.ts"; import * as ElectronApp from "../electron/ElectronApp.ts"; import * as ElectronTheme from "../electron/ElectronTheme.ts"; @@ -37,7 +37,7 @@ export class DesktopLifecycle extends Context.Service< >()("@t3tools/desktop/app/DesktopLifecycle") {} const { logInfo: logLifecycleInfo, logError: logLifecycleError } = - DesktopObservability.makeComponentLogger("desktop-lifecycle"); + makeComponentLogger("desktop-lifecycle"); function addScopedListener>( target: unknown, @@ -122,7 +122,7 @@ function quitFromSignal( ); } -const make = DesktopLifecycle.of({ +export const make = DesktopLifecycle.of({ relaunch: Effect.fn("desktop.lifecycle.relaunch")(function* (reason) { const electronApp = yield* ElectronApp.ElectronApp; const environment = yield* DesktopEnvironment.DesktopEnvironment; diff --git a/apps/desktop/src/electron/ElectronApp.ts b/apps/desktop/src/electron/ElectronApp.ts index 49b432fd5dd..3e894001e10 100644 --- a/apps/desktop/src/electron/ElectronApp.ts +++ b/apps/desktop/src/electron/ElectronApp.ts @@ -13,41 +13,40 @@ export interface ElectronAppMetadata { readonly runningUnderArm64Translation: boolean; } -export interface ElectronAppShape { - 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 requestSingleInstanceLock: 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 on: >( - eventName: string, - listener: (...args: Args) => void, - ) => Effect.Effect; -} - -export class ElectronApp extends Context.Service()( - "@t3tools/desktop/electron/ElectronApp", -) {} +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 requestSingleInstanceLock: 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 on: >( + eventName: string, + listener: (...args: Args) => void, + ) => Effect.Effect; + } +>()("@t3tools/desktop/electron/ElectronApp") {} const addScopedAppListener = >( eventName: string, @@ -63,7 +62,7 @@ const addScopedAppListener = >( }), ).pipe(Effect.asVoid); -const make = ElectronApp.of({ +export const make = ElectronApp.of({ metadata: Effect.sync(() => ({ appVersion: Electron.app.getVersion(), appPath: Electron.app.getAppPath(), diff --git a/apps/desktop/src/electron/ElectronDialog.ts b/apps/desktop/src/electron/ElectronDialog.ts index 74e6ae58848..057817ec7e6 100644 --- a/apps/desktop/src/electron/ElectronDialog.ts +++ b/apps/desktop/src/electron/ElectronDialog.ts @@ -17,22 +17,21 @@ export interface ElectronDialogConfirmInput { readonly message: string; } -export interface ElectronDialogShape { - readonly pickFolder: ( - input: ElectronDialogPickFolderInput, - ) => Effect.Effect>; - readonly confirm: (input: ElectronDialogConfirmInput) => Effect.Effect; - readonly showMessageBox: ( - options: Electron.MessageBoxOptions, - ) => Effect.Effect; - readonly showErrorBox: (title: string, content: string) => Effect.Effect; -} - -export class ElectronDialog extends Context.Service()( - "@t3tools/desktop/electron/ElectronDialog", -) {} +export class ElectronDialog extends Context.Service< + ElectronDialog, + { + readonly pickFolder: ( + input: ElectronDialogPickFolderInput, + ) => Effect.Effect>; + readonly confirm: (input: ElectronDialogConfirmInput) => Effect.Effect; + readonly showMessageBox: ( + options: Electron.MessageBoxOptions, + ) => Effect.Effect; + readonly showErrorBox: (title: string, content: string) => Effect.Effect; + } +>()("@t3tools/desktop/electron/ElectronDialog") {} -const make = ElectronDialog.of({ +export const make = ElectronDialog.of({ pickFolder: Effect.fn("desktop.electron.dialog.pickFolder")(function* (input) { const openDialogOptions: Electron.OpenDialogOptions = Option.match(input.defaultPath, { onNone: () => ({ diff --git a/apps/desktop/src/electron/ElectronMenu.ts b/apps/desktop/src/electron/ElectronMenu.ts index 2ffda3dc507..d9eb3b22eff 100644 --- a/apps/desktop/src/electron/ElectronMenu.ts +++ b/apps/desktop/src/electron/ElectronMenu.ts @@ -1,11 +1,11 @@ import type { ContextMenuItem } from "@t3tools/contracts"; +import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; 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 Electron from "electron"; -import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; export interface ElectronMenuPosition { readonly x: number; @@ -23,19 +23,18 @@ export interface ElectronMenuTemplateInput { readonly template: readonly Electron.MenuItemConstructorOptions[]; } -export interface ElectronMenuShape { - readonly setApplicationMenu: ( - template: readonly Electron.MenuItemConstructorOptions[], - ) => Effect.Effect; - readonly showContextMenu: ( - input: ElectronMenuContextInput, - ) => Effect.Effect>; - readonly popupTemplate: (input: ElectronMenuTemplateInput) => Effect.Effect; -} - -export class ElectronMenu extends Context.Service()( - "@t3tools/desktop/electron/ElectronMenu", -) {} +export class ElectronMenu extends Context.Service< + ElectronMenu, + { + readonly setApplicationMenu: ( + template: readonly Electron.MenuItemConstructorOptions[], + ) => Effect.Effect; + readonly showContextMenu: ( + input: ElectronMenuContextInput, + ) => Effect.Effect>; + readonly popupTemplate: (input: ElectronMenuTemplateInput) => Effect.Effect; + } +>()("@t3tools/desktop/electron/ElectronMenu") {} function normalizeContextMenuItems(source: readonly ContextMenuItem[]): ContextMenuItem[] { const normalizedItems: ContextMenuItem[] = []; @@ -80,114 +79,113 @@ const normalizePosition = ( ({ x, y }) => Number.isFinite(x) && Number.isFinite(y) && x >= 0 && y >= 0, ).pipe(Option.map(({ x, y }) => ({ x: Math.floor(x), y: Math.floor(y) }))); -export const layer = Layer.effect( - ElectronMenu, - Effect.gen(function* () { - const platform = yield* HostProcessPlatform; - let destructiveMenuIconCache: Option.Option | undefined; +export const make = Effect.gen(function* () { + const platform = yield* HostProcessPlatform; + let destructiveMenuIconCache: Option.Option | undefined; - const getDestructiveMenuIcon = (): Option.Option => { - if (platform !== "darwin") { - return Option.none(); - } - if (destructiveMenuIconCache !== undefined) { - return destructiveMenuIconCache; - } + const getDestructiveMenuIcon = (): Option.Option => { + if (platform !== "darwin") { + return Option.none(); + } + if (destructiveMenuIconCache !== undefined) { + return destructiveMenuIconCache; + } - try { - const icon = Electron.nativeImage.createFromNamedImage("trash").resize({ - width: 12, - height: 12, - }); - icon.setTemplateImage(true); - destructiveMenuIconCache = icon.isEmpty() ? Option.none() : Option.some(icon); - } catch { - destructiveMenuIconCache = Option.none(); - } + try { + const icon = Electron.nativeImage.createFromNamedImage("trash").resize({ + width: 12, + height: 12, + }); + icon.setTemplateImage(true); + destructiveMenuIconCache = icon.isEmpty() ? Option.none() : Option.some(icon); + } catch { + destructiveMenuIconCache = Option.none(); + } - return destructiveMenuIconCache; - }; + return destructiveMenuIconCache; + }; - const buildTemplate = ( - entries: readonly ContextMenuItem[], - complete: (selectedItemId: Option.Option) => void, - ): Electron.MenuItemConstructorOptions[] => { - const template: Electron.MenuItemConstructorOptions[] = []; - let hasInsertedDestructiveSeparator = false; - - for (const item of entries) { - if (item.destructive && !hasInsertedDestructiveSeparator && template.length > 0) { - template.push({ type: "separator" }); - hasInsertedDestructiveSeparator = true; - } + const buildTemplate = ( + entries: readonly ContextMenuItem[], + complete: (selectedItemId: Option.Option) => void, + ): Electron.MenuItemConstructorOptions[] => { + const template: Electron.MenuItemConstructorOptions[] = []; + let hasInsertedDestructiveSeparator = false; - const itemOption: Electron.MenuItemConstructorOptions = { - label: item.label, - enabled: !item.disabled, - }; - if (item.children && item.children.length > 0) { - itemOption.submenu = buildTemplate(item.children, complete); - } else { - itemOption.click = () => complete(Option.some(item.id)); - } - if (item.destructive && (!item.children || item.children.length === 0)) { - const destructiveIcon = getDestructiveMenuIcon(); - if (Option.isSome(destructiveIcon)) { - itemOption.icon = destructiveIcon.value; - } - } + for (const item of entries) { + if (item.destructive && !hasInsertedDestructiveSeparator && template.length > 0) { + template.push({ type: "separator" }); + hasInsertedDestructiveSeparator = true; + } - template.push(itemOption); + const itemOption: Electron.MenuItemConstructorOptions = { + label: item.label, + enabled: !item.disabled, + }; + if (item.children && item.children.length > 0) { + itemOption.submenu = buildTemplate(item.children, complete); + } else { + itemOption.click = () => complete(Option.some(item.id)); + } + if (item.destructive && (!item.children || item.children.length === 0)) { + const destructiveIcon = getDestructiveMenuIcon(); + if (Option.isSome(destructiveIcon)) { + itemOption.icon = destructiveIcon.value; + } } - return template; - }; + template.push(itemOption); + } - return ElectronMenu.of({ - setApplicationMenu: (template) => - Effect.sync(() => { - Electron.Menu.setApplicationMenu(Electron.Menu.buildFromTemplate([...template])); - }), - popupTemplate: (input) => - Effect.sync(() => { - if (input.template.length === 0) { - return; - } - Electron.Menu.buildFromTemplate([...input.template]).popup({ window: input.window }); - }), - showContextMenu: (input) => - Effect.callback>((resume) => { - const normalizedItems = normalizeContextMenuItems(input.items); - if (normalizedItems.length === 0) { - resume(Effect.succeed(Option.none())); + return template; + }; + + return ElectronMenu.of({ + setApplicationMenu: (template) => + Effect.sync(() => { + Electron.Menu.setApplicationMenu(Electron.Menu.buildFromTemplate([...template])); + }), + popupTemplate: (input) => + Effect.sync(() => { + if (input.template.length === 0) { + return; + } + Electron.Menu.buildFromTemplate([...input.template]).popup({ window: input.window }); + }), + showContextMenu: (input) => + Effect.callback>((resume) => { + const normalizedItems = normalizeContextMenuItems(input.items); + if (normalizedItems.length === 0) { + resume(Effect.succeed(Option.none())); + return; + } + + let completed = false; + const complete = (selectedItemId: Option.Option) => { + if (completed) { return; } + completed = true; + resume(Effect.succeed(selectedItemId)); + }; + + const menu = Electron.Menu.buildFromTemplate(buildTemplate(normalizedItems, complete)); + const popupPosition = normalizePosition(input.position); + const popupOptions = Option.match(popupPosition, { + onNone: (): Electron.PopupOptions => ({ + window: input.window, + callback: () => complete(Option.none()), + }), + onSome: (position): Electron.PopupOptions => ({ + window: input.window, + x: position.x, + y: position.y, + callback: () => complete(Option.none()), + }), + }); + menu.popup(popupOptions); + }), + }); +}); - let completed = false; - const complete = (selectedItemId: Option.Option) => { - if (completed) { - return; - } - completed = true; - resume(Effect.succeed(selectedItemId)); - }; - - const menu = Electron.Menu.buildFromTemplate(buildTemplate(normalizedItems, complete)); - const popupPosition = normalizePosition(input.position); - const popupOptions = Option.match(popupPosition, { - onNone: (): Electron.PopupOptions => ({ - window: input.window, - callback: () => complete(Option.none()), - }), - onSome: (position): Electron.PopupOptions => ({ - window: input.window, - x: position.x, - y: position.y, - callback: () => complete(Option.none()), - }), - }); - menu.popup(popupOptions); - }), - }); - }), -); +export const layer = Layer.effect(ElectronMenu, make); diff --git a/apps/desktop/src/electron/ElectronProtocol.ts b/apps/desktop/src/electron/ElectronProtocol.ts index 3a3e9f180f7..4c80c2c4900 100644 --- a/apps/desktop/src/electron/ElectronProtocol.ts +++ b/apps/desktop/src/electron/ElectronProtocol.ts @@ -1,8 +1,8 @@ import * as Context from "effect/Context"; -import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Electron from "electron"; @@ -23,13 +23,14 @@ export function getDesktopUrl(isDevelopment: boolean): string { return `${getDesktopOrigin(isDevelopment)}/`; } -export class ElectronProtocolRegistrationError extends Data.TaggedError( +export class ElectronProtocolRegistrationError extends Schema.TaggedErrorClass()( "ElectronProtocolRegistrationError", -)<{ - readonly scheme: string; - readonly cause: unknown; -}> { - override get message() { + { + scheme: Schema.String, + cause: Schema.Defect(), + }, +) { + override get message(): string { return `Failed to register ${this.scheme}: protocol.`; } } @@ -41,15 +42,14 @@ export interface DesktopProtocolRegistrationInput { readonly clerkFrontendApiHostname: string | undefined; } -export interface ElectronProtocolShape { - readonly registerDesktopProtocol: ( - input: DesktopProtocolRegistrationInput, - ) => Effect.Effect; -} - -export class ElectronProtocol extends Context.Service()( - "@t3tools/desktop/electron/ElectronProtocol", -) {} +export class ElectronProtocol extends Context.Service< + ElectronProtocol, + { + readonly registerDesktopProtocol: ( + input: DesktopProtocolRegistrationInput, + ) => Effect.Effect; + } +>()("@t3tools/desktop/electron/ElectronProtocol") {} export function makeDesktopContentSecurityPolicy(input: DesktopProtocolRegistrationInput): string { const clerkOrigin = input.clerkFrontendApiHostname @@ -114,7 +114,7 @@ async function proxyRequest( return withContentSecurityPolicy(response, contentSecurityPolicy); } -const make = Effect.gen(function* () { +export const make = Effect.gen(function* () { const registered = yield* Ref.make(false); const registerDesktopProtocol = Effect.fn("desktop.electron.protocol.registerDesktopProtocol")( diff --git a/apps/desktop/src/electron/ElectronSafeStorage.ts b/apps/desktop/src/electron/ElectronSafeStorage.ts index 85313370547..76162c1647a 100644 --- a/apps/desktop/src/electron/ElectronSafeStorage.ts +++ b/apps/desktop/src/electron/ElectronSafeStorage.ts @@ -1,56 +1,69 @@ -import * as Electron from "electron"; - +import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import * as Context from "effect/Context"; -import * as Data from "effect/Data"; +import * as Schema from "effect/Schema"; -export class ElectronSafeStorageAvailabilityError extends Data.TaggedError( +import * as Electron from "electron"; + +const electronSafeStorageErrorFields = { + cause: Schema.Defect(), +}; + +export class ElectronSafeStorageAvailabilityError extends Schema.TaggedErrorClass()( "ElectronSafeStorageAvailabilityError", -)<{ - readonly cause: unknown; -}> { - override get message() { + { + ...electronSafeStorageErrorFields, + }, +) { + override get message(): string { return "Electron safe storage failed to check encryption availability."; } } -export class ElectronSafeStorageEncryptError extends Data.TaggedError( +export class ElectronSafeStorageEncryptError extends Schema.TaggedErrorClass()( "ElectronSafeStorageEncryptError", -)<{ - readonly cause: unknown; -}> { - override get message() { + { + ...electronSafeStorageErrorFields, + }, +) { + override get message(): string { return "Electron safe storage failed to encrypt a string."; } } -export class ElectronSafeStorageDecryptError extends Data.TaggedError( +export class ElectronSafeStorageDecryptError extends Schema.TaggedErrorClass()( "ElectronSafeStorageDecryptError", -)<{ - readonly cause: unknown; -}> { - override get message() { + { + ...electronSafeStorageErrorFields, + }, +) { + override get message(): string { return "Electron safe storage failed to decrypt a string."; } } -export interface ElectronSafeStorageShape { - readonly isEncryptionAvailable: Effect.Effect; - readonly encryptString: ( - value: string, - ) => Effect.Effect; - readonly decryptString: ( - value: Uint8Array, - ) => Effect.Effect; -} +export const ElectronSafeStorageError = Schema.Union([ + ElectronSafeStorageAvailabilityError, + ElectronSafeStorageEncryptError, + ElectronSafeStorageDecryptError, +]); +export type ElectronSafeStorageError = typeof ElectronSafeStorageError.Type; +export const isElectronSafeStorageError = Schema.is(ElectronSafeStorageError); export class ElectronSafeStorage extends Context.Service< ElectronSafeStorage, - ElectronSafeStorageShape + { + readonly isEncryptionAvailable: Effect.Effect; + readonly encryptString: ( + value: string, + ) => Effect.Effect; + readonly decryptString: ( + value: Uint8Array, + ) => Effect.Effect; + } >()("@t3tools/desktop/electron/ElectronSafeStorage") {} -const make = ElectronSafeStorage.of({ +export const make = ElectronSafeStorage.of({ isEncryptionAvailable: Effect.try({ try: () => Electron.safeStorage.isEncryptionAvailable(), catch: (cause) => new ElectronSafeStorageAvailabilityError({ cause }), diff --git a/apps/desktop/src/electron/ElectronShell.ts b/apps/desktop/src/electron/ElectronShell.ts index 0ecce3bf70e..316d3138bfa 100644 --- a/apps/desktop/src/electron/ElectronShell.ts +++ b/apps/desktop/src/electron/ElectronShell.ts @@ -20,16 +20,15 @@ export function parseSafeExternalUrl(rawUrl: unknown): Option.Option { } } -export interface ElectronShellShape { - readonly openExternal: (rawUrl: unknown) => Effect.Effect; - readonly copyText: (text: string) => Effect.Effect; -} - -export class ElectronShell extends Context.Service()( - "@t3tools/desktop/electron/ElectronShell", -) {} +export class ElectronShell extends Context.Service< + ElectronShell, + { + readonly openExternal: (rawUrl: unknown) => Effect.Effect; + readonly copyText: (text: string) => Effect.Effect; + } +>()("@t3tools/desktop/electron/ElectronShell") {} -const make = ElectronShell.of({ +export const make = ElectronShell.of({ openExternal: (rawUrl) => Option.match(parseSafeExternalUrl(rawUrl), { onNone: () => Effect.succeed(false), diff --git a/apps/desktop/src/electron/ElectronTheme.ts b/apps/desktop/src/electron/ElectronTheme.ts index 1e23d228504..ef99a31067a 100644 --- a/apps/desktop/src/electron/ElectronTheme.ts +++ b/apps/desktop/src/electron/ElectronTheme.ts @@ -6,17 +6,16 @@ import * as Scope from "effect/Scope"; import * as Electron from "electron"; -export interface ElectronThemeShape { - readonly shouldUseDarkColors: Effect.Effect; - readonly setSource: (theme: DesktopTheme) => Effect.Effect; - readonly onUpdated: (listener: () => void) => Effect.Effect; -} +export class ElectronTheme extends Context.Service< + ElectronTheme, + { + readonly shouldUseDarkColors: Effect.Effect; + readonly setSource: (theme: DesktopTheme) => Effect.Effect; + readonly onUpdated: (listener: () => void) => Effect.Effect; + } +>()("@t3tools/desktop/electron/ElectronTheme") {} -export class ElectronTheme extends Context.Service()( - "@t3tools/desktop/electron/ElectronTheme", -) {} - -const make = ElectronTheme.of({ +export const make = ElectronTheme.of({ shouldUseDarkColors: Effect.sync(() => Electron.nativeTheme.shouldUseDarkColors), setSource: (theme) => Effect.suspend(() => { diff --git a/apps/desktop/src/electron/ElectronUpdater.test.ts b/apps/desktop/src/electron/ElectronUpdater.test.ts index d2d3edd3696..43a3c84dcd4 100644 --- a/apps/desktop/src/electron/ElectronUpdater.test.ts +++ b/apps/desktop/src/electron/ElectronUpdater.test.ts @@ -73,6 +73,7 @@ describe("ElectronUpdater", () => { const error = Cause.squash(exit.cause); assert.instanceOf(error, ElectronUpdater.ElectronUpdaterCheckForUpdatesError); assert.equal(error.cause, cause); + assert.equal(error.message, "Electron updater failed to check for updates."); } }).pipe(Effect.provide(ElectronUpdater.layer)), ); diff --git a/apps/desktop/src/electron/ElectronUpdater.ts b/apps/desktop/src/electron/ElectronUpdater.ts index 7f3edf02aa8..8a468a15c20 100644 --- a/apps/desktop/src/electron/ElectronUpdater.ts +++ b/apps/desktop/src/electron/ElectronUpdater.ts @@ -1,7 +1,7 @@ import * as Context from "effect/Context"; -import * as Data from "effect/Data"; 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 { autoUpdater } from "electron-updater"; @@ -10,67 +10,76 @@ type AutoUpdater = typeof autoUpdater; export type ElectronUpdaterFeedUrl = Parameters[0]; -export class ElectronUpdaterCheckForUpdatesError extends Data.TaggedError( +const electronUpdaterErrorFields = { + cause: Schema.Defect(), +}; + +export class ElectronUpdaterCheckForUpdatesError extends Schema.TaggedErrorClass()( "ElectronUpdaterCheckForUpdatesError", -)<{ - readonly cause: unknown; -}> { - override get message() { + { + ...electronUpdaterErrorFields, + }, +) { + override get message(): string { return "Electron updater failed to check for updates."; } } -export class ElectronUpdaterDownloadUpdateError extends Data.TaggedError( +export class ElectronUpdaterDownloadUpdateError extends Schema.TaggedErrorClass()( "ElectronUpdaterDownloadUpdateError", -)<{ - readonly cause: unknown; -}> { - override get message() { + { + ...electronUpdaterErrorFields, + }, +) { + override get message(): string { return "Electron updater failed to download the update."; } } -export class ElectronUpdaterQuitAndInstallError extends Data.TaggedError( +export class ElectronUpdaterQuitAndInstallError extends Schema.TaggedErrorClass()( "ElectronUpdaterQuitAndInstallError", -)<{ - readonly cause: unknown; -}> { - override get message() { + { + ...electronUpdaterErrorFields, + }, +) { + override get message(): string { return "Electron updater failed to quit and install the update."; } } -export type ElectronUpdaterError = - | ElectronUpdaterCheckForUpdatesError - | ElectronUpdaterDownloadUpdateError - | ElectronUpdaterQuitAndInstallError; +export const ElectronUpdaterError = Schema.Union([ + ElectronUpdaterCheckForUpdatesError, + ElectronUpdaterDownloadUpdateError, + ElectronUpdaterQuitAndInstallError, +]); +export type ElectronUpdaterError = typeof ElectronUpdaterError.Type; +export const isElectronUpdaterError = Schema.is(ElectronUpdaterError); -export interface ElectronUpdaterShape { - readonly setFeedURL: (options: ElectronUpdaterFeedUrl) => Effect.Effect; - readonly setAutoDownload: (value: boolean) => Effect.Effect; - readonly setAutoInstallOnAppQuit: (value: boolean) => Effect.Effect; - readonly setChannel: (channel: string) => Effect.Effect; - readonly setAllowPrerelease: (value: boolean) => Effect.Effect; - readonly allowDowngrade: Effect.Effect; - readonly setAllowDowngrade: (value: boolean) => Effect.Effect; - readonly setDisableDifferentialDownload: (value: boolean) => Effect.Effect; - readonly checkForUpdates: Effect.Effect; - readonly downloadUpdate: Effect.Effect; - readonly quitAndInstall: (options: { - readonly isSilent: boolean; - readonly isForceRunAfter: boolean; - }) => Effect.Effect; - readonly on: >( - eventName: string, - listener: (...args: Args) => void, - ) => Effect.Effect; -} - -export class ElectronUpdater extends Context.Service()( - "@t3tools/desktop/electron/ElectronUpdater", -) {} +export class ElectronUpdater extends Context.Service< + ElectronUpdater, + { + readonly setFeedURL: (options: ElectronUpdaterFeedUrl) => Effect.Effect; + readonly setAutoDownload: (value: boolean) => Effect.Effect; + readonly setAutoInstallOnAppQuit: (value: boolean) => Effect.Effect; + readonly setChannel: (channel: string) => Effect.Effect; + readonly setAllowPrerelease: (value: boolean) => Effect.Effect; + readonly allowDowngrade: Effect.Effect; + readonly setAllowDowngrade: (value: boolean) => Effect.Effect; + readonly setDisableDifferentialDownload: (value: boolean) => Effect.Effect; + readonly checkForUpdates: Effect.Effect; + readonly downloadUpdate: Effect.Effect; + readonly quitAndInstall: (options: { + readonly isSilent: boolean; + readonly isForceRunAfter: boolean; + }) => Effect.Effect; + readonly on: >( + eventName: string, + listener: (...args: Args) => void, + ) => Effect.Effect; + } +>()("@t3tools/desktop/electron/ElectronUpdater") {} -export const layer = Layer.succeed(ElectronUpdater, { +export const make = ElectronUpdater.of({ setFeedURL: (options) => Effect.suspend(() => { autoUpdater.setFeedURL(options); @@ -136,4 +145,6 @@ export const layer = Layer.succeed(ElectronUpdater, { }), ).pipe(Effect.asVoid); }, -} satisfies ElectronUpdaterShape); +}); + +export const layer = Layer.succeed(ElectronUpdater, make); diff --git a/apps/desktop/src/electron/ElectronWindow.ts b/apps/desktop/src/electron/ElectronWindow.ts index 35c1fbc5faa..0bf98a9610e 100644 --- a/apps/desktop/src/electron/ElectronWindow.ts +++ b/apps/desktop/src/electron/ElectronWindow.ts @@ -1,43 +1,45 @@ +import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as Context from "effect/Context"; -import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; import * as Electron from "electron"; -import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; -export class ElectronWindowCreateError extends Data.TaggedError("ElectronWindowCreateError")<{ - readonly cause: unknown; -}> { - override get message() { +export class ElectronWindowCreateError extends Schema.TaggedErrorClass()( + "ElectronWindowCreateError", + { + cause: Schema.Defect(), + }, +) { + override get message(): string { return "Failed to create Electron BrowserWindow."; } } -export interface ElectronWindowShape { - readonly create: ( - options: Electron.BrowserWindowConstructorOptions, - ) => Effect.Effect; - readonly main: Effect.Effect>; - readonly currentMainOrFirst: Effect.Effect>; - readonly focusedMainOrFirst: Effect.Effect>; - readonly setMain: (window: Electron.BrowserWindow) => Effect.Effect; - readonly clearMain: (window: Option.Option) => Effect.Effect; - readonly reveal: (window: Electron.BrowserWindow) => Effect.Effect; - readonly sendAll: (channel: string, ...args: readonly unknown[]) => Effect.Effect; - readonly destroyAll: Effect.Effect; - readonly syncAllAppearance: ( - sync: (window: Electron.BrowserWindow) => Effect.Effect, - ) => Effect.Effect; -} - -export class ElectronWindow extends Context.Service()( - "@t3tools/desktop/electron/ElectronWindow", -) {} +export class ElectronWindow extends Context.Service< + ElectronWindow, + { + readonly create: ( + options: Electron.BrowserWindowConstructorOptions, + ) => Effect.Effect; + readonly main: Effect.Effect>; + readonly currentMainOrFirst: Effect.Effect>; + readonly focusedMainOrFirst: Effect.Effect>; + readonly setMain: (window: Electron.BrowserWindow) => Effect.Effect; + readonly clearMain: (window: Option.Option) => Effect.Effect; + readonly reveal: (window: Electron.BrowserWindow) => Effect.Effect; + readonly sendAll: (channel: string, ...args: readonly unknown[]) => Effect.Effect; + readonly destroyAll: Effect.Effect; + readonly syncAllAppearance: ( + sync: (window: Electron.BrowserWindow) => Effect.Effect, + ) => Effect.Effect; + } +>()("@t3tools/desktop/electron/ElectronWindow") {} -const make = Effect.gen(function* () { +export const make = Effect.gen(function* () { const platform = yield* HostProcessPlatform; const mainWindowRef = yield* Ref.make>(Option.none()); diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index a6ffd9cdab1..f4b32db07c7 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -1,7 +1,7 @@ import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import * as NodeOS from "node:os"; +import { homedir } from "node:os"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; @@ -19,7 +19,7 @@ import * as ElectronApp from "./electron/ElectronApp.ts"; import * as ElectronDialog from "./electron/ElectronDialog.ts"; import * as ElectronMenu from "./electron/ElectronMenu.ts"; import * as ElectronProtocol from "./electron/ElectronProtocol.ts"; -import * as DesktopSecretStorage from "./electron/ElectronSafeStorage.ts"; +import * as ElectronSafeStorage from "./electron/ElectronSafeStorage.ts"; import * as ElectronShell from "./electron/ElectronShell.ts"; import * as ElectronTheme from "./electron/ElectronTheme.ts"; import * as ElectronUpdater from "./electron/ElectronUpdater.ts"; @@ -60,7 +60,7 @@ const desktopEnvironmentLayer = Layer.unwrap( const processArch = yield* HostProcessArchitecture; return DesktopEnvironment.layer({ dirname: __dirname, - homeDirectory: NodeOS.homedir(), + homeDirectory: homedir(), platform, processArch, ...metadata, @@ -106,7 +106,7 @@ const electronLayer = Layer.mergeAll( ElectronDialog.layer, ElectronMenu.layer, ElectronProtocol.layer, - DesktopSecretStorage.layer, + ElectronSafeStorage.layer, ElectronShell.layer, ElectronTheme.layer, ElectronUpdater.layer, diff --git a/apps/desktop/src/settings/DesktopSavedEnvironments.test.ts b/apps/desktop/src/settings/DesktopSavedEnvironments.test.ts index 4e3c8d8ba1d..abd25a39f5b 100644 --- a/apps/desktop/src/settings/DesktopSavedEnvironments.test.ts +++ b/apps/desktop/src/settings/DesktopSavedEnvironments.test.ts @@ -241,7 +241,6 @@ describe("DesktopSavedEnvironments", () => { .getSecret(savedRegistryRecord.environmentId) .pipe(Effect.flip); assert.instanceOf(error, DesktopSavedEnvironments.DesktopSavedEnvironmentSecretDecodeError); - assert.equal(error.operation, "decode-secret"); assert.equal(error.environmentId, savedRegistryRecord.environmentId); assert.equal(error.registryPath, environment.savedEnvironmentRegistryPath); assert.equal(error.field, "encryptedBearerToken"); @@ -363,7 +362,6 @@ describe("DesktopSavedEnvironments", () => { registryError, DesktopSavedEnvironments.DesktopSavedEnvironmentsDocumentDecodeError, ); - assert.equal(registryError.operation, "decode-registry"); assert.equal(registryError.registryPath, environment.savedEnvironmentRegistryPath); assert.exists(registryError.cause); const secretError = yield* savedEnvironments @@ -409,7 +407,6 @@ describe("DesktopSavedEnvironments", () => { const error = yield* savedEnvironments.getRegistry.pipe(Effect.flip); assert.instanceOf(error, DesktopSavedEnvironments.DesktopSavedEnvironmentsReadError); - assert.equal(error.operation, "read-registry"); assert.equal(error.registryPath, registryPath); assert.strictEqual(error.cause, permissionError); assert.equal(error.message, `Failed to read desktop saved environments at ${registryPath}.`); diff --git a/apps/desktop/src/settings/DesktopSavedEnvironments.ts b/apps/desktop/src/settings/DesktopSavedEnvironments.ts index 490777e9e84..64c40d39f0e 100644 --- a/apps/desktop/src/settings/DesktopSavedEnvironments.ts +++ b/apps/desktop/src/settings/DesktopSavedEnvironments.ts @@ -106,7 +106,6 @@ const writeError = ( export class DesktopSavedEnvironmentsReadError extends Schema.TaggedErrorClass()( "DesktopSavedEnvironmentsReadError", { - operation: Schema.Literal("read-registry"), registryPath: Schema.String, cause: Schema.Defect(), }, @@ -119,7 +118,6 @@ export class DesktopSavedEnvironmentsReadError extends Schema.TaggedErrorClass()( "DesktopSavedEnvironmentsDocumentDecodeError", { - operation: Schema.Literal("decode-registry"), registryPath: Schema.String, cause: Schema.Defect(), }, @@ -132,7 +130,6 @@ export class DesktopSavedEnvironmentsDocumentDecodeError extends Schema.TaggedEr export class DesktopSavedEnvironmentSecretDecodeError extends Schema.TaggedErrorClass()( "DesktopSavedEnvironmentSecretDecodeError", { - operation: Schema.Literal("decode-secret"), environmentId: Schema.String, registryPath: Schema.String, field: Schema.Literal("encryptedBearerToken"), @@ -155,13 +152,11 @@ export type DesktopSavedEnvironmentsMutationError = export type DesktopSavedEnvironmentsGetSecretError = | DesktopSavedEnvironmentsReadRegistryError | DesktopSavedEnvironmentSecretDecodeError - | ElectronSafeStorage.ElectronSafeStorageAvailabilityError - | ElectronSafeStorage.ElectronSafeStorageDecryptError; + | ElectronSafeStorage.ElectronSafeStorageError; export type DesktopSavedEnvironmentsSetSecretError = | DesktopSavedEnvironmentsMutationError - | ElectronSafeStorage.ElectronSafeStorageAvailabilityError - | ElectronSafeStorage.ElectronSafeStorageEncryptError; + | ElectronSafeStorage.ElectronSafeStorageError; export class DesktopSavedEnvironments extends Context.Service< DesktopSavedEnvironments, @@ -248,7 +243,6 @@ function readRegistryDocument( ? Effect.succeed(null) : Effect.fail( new DesktopSavedEnvironmentsReadError({ - operation: "read-registry", registryPath, cause: error, }), @@ -262,7 +256,6 @@ function readRegistryDocument( Effect.mapError( (cause) => new DesktopSavedEnvironmentsDocumentDecodeError({ - operation: "decode-registry", registryPath, cause, }), @@ -329,7 +322,6 @@ function decodeSecretBytes( Effect.mapError( (cause) => new DesktopSavedEnvironmentSecretDecodeError({ - operation: "decode-secret", environmentId, registryPath, field: "encryptedBearerToken", diff --git a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts index 080a2fe465d..f0b5b1bd8ef 100644 --- a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts +++ b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts @@ -9,7 +9,7 @@ import * as TestClock from "effect/testing/TestClock"; import type * as Electron from "electron"; import * as ElectronWindow from "../electron/ElectronWindow.ts"; -import * as IpcChannels from "../ipc/channels.ts"; +import { SSH_PASSWORD_PROMPT_CHANNEL } from "../ipc/channels.ts"; import * as DesktopSshPasswordPrompts from "./DesktopSshPasswordPrompts.ts"; interface SentMessage { @@ -111,7 +111,7 @@ describe("DesktopSshPasswordPrompts", () => { assert.equal(testWindow.sentMessages.length, 1); const sent = testWindow.sentMessages[0]; assert.ok(sent); - assert.equal(sent.channel, IpcChannels.SSH_PASSWORD_PROMPT_CHANNEL); + assert.equal(sent.channel, SSH_PASSWORD_PROMPT_CHANNEL); const request = sent.args[0] as { readonly requestId: string; readonly destination: string }; assert.equal(request.destination, "devbox"); assert.equal(testWindow.isRestored(), true); diff --git a/apps/desktop/src/updates/DesktopUpdates.test.ts b/apps/desktop/src/updates/DesktopUpdates.test.ts index 34d18f11a77..ad234df0bb5 100644 --- a/apps/desktop/src/updates/DesktopUpdates.test.ts +++ b/apps/desktop/src/updates/DesktopUpdates.test.ts @@ -83,7 +83,7 @@ function makeHarness(options: UpdatesHarnessOptions = {}) { removeListener(eventName, listener as unknown as (...args: readonly unknown[]) => void); }), ).pipe(Effect.asVoid), - } satisfies ElectronUpdater.ElectronUpdaterShape); + } satisfies ElectronUpdater.ElectronUpdater["Service"]); const windowLayer = Layer.succeed(ElectronWindow.ElectronWindow, { create: () => Effect.die("unexpected BrowserWindow creation"), @@ -99,7 +99,7 @@ function makeHarness(options: UpdatesHarnessOptions = {}) { }), destroyAll: Effect.void, syncAllAppearance: () => Effect.void, - } satisfies ElectronWindow.ElectronWindowShape); + } satisfies ElectronWindow.ElectronWindow["Service"]); const backendLayer = Layer.succeed(DesktopBackendManager.DesktopBackendManager, { start: Effect.void, diff --git a/apps/desktop/src/window/DesktopApplicationMenu.test.ts b/apps/desktop/src/window/DesktopApplicationMenu.test.ts index f3444c629f7..04a1971ce46 100644 --- a/apps/desktop/src/window/DesktopApplicationMenu.test.ts +++ b/apps/desktop/src/window/DesktopApplicationMenu.test.ts @@ -46,14 +46,14 @@ const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, { setDockIcon: () => Effect.void, appendCommandLineSwitch: () => Effect.void, on: () => Effect.void, -} satisfies ElectronApp.ElectronAppShape); +} satisfies ElectronApp.ElectronApp["Service"]); const electronDialogLayer = Layer.succeed(ElectronDialog.ElectronDialog, { pickFolder: () => Effect.succeed(Option.none()), confirm: () => Effect.succeed(false), showMessageBox: () => Effect.succeed({ response: 0, checkboxChecked: false }), showErrorBox: () => Effect.void, -} satisfies ElectronDialog.ElectronDialogShape); +} satisfies ElectronDialog.ElectronDialog["Service"]); const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, { getState: Effect.die("unexpected getState"), @@ -86,7 +86,7 @@ const makeElectronMenuLayer = ( Deferred.succeed(applicationMenuTemplate, template).pipe(Effect.asVoid), popupTemplate: () => Effect.void, showContextMenu: () => Effect.succeed(Option.none()), - } satisfies ElectronMenu.ElectronMenuShape); + } satisfies ElectronMenu.ElectronMenu["Service"]); describe("DesktopApplicationMenu", () => { it.effect("installs the native menu and routes Settings through DesktopWindow", () => diff --git a/apps/desktop/src/window/DesktopApplicationMenu.ts b/apps/desktop/src/window/DesktopApplicationMenu.ts index 04b9c833e44..09cd6bc48c6 100644 --- a/apps/desktop/src/window/DesktopApplicationMenu.ts +++ b/apps/desktop/src/window/DesktopApplicationMenu.ts @@ -6,7 +6,7 @@ import * as Option from "effect/Option"; import type * as Electron from "electron"; -import * as DesktopObservability from "../app/DesktopObservability.ts"; +import { makeComponentLogger } from "../app/DesktopObservability.ts"; import * as ElectronApp from "../electron/ElectronApp.ts"; import * as ElectronDialog from "../electron/ElectronDialog.ts"; import * as ElectronMenu from "../electron/ElectronMenu.ts"; @@ -26,9 +26,9 @@ type DesktopApplicationMenuRuntimeServices = | DesktopWindow.DesktopWindow | ElectronDialog.ElectronDialog; -const { logInfo: logUpdaterInfo } = DesktopObservability.makeComponentLogger("desktop-updater"); +const { logInfo: logUpdaterInfo } = makeComponentLogger("desktop-updater"); -const { logError: logMenuError } = DesktopObservability.makeComponentLogger("desktop-menu"); +const { logError: logMenuError } = makeComponentLogger("desktop-menu"); const dispatchMenuAction = Effect.fn("desktop.menu.dispatchMenuAction")(function* ( action: string, diff --git a/apps/desktop/src/window/DesktopWindow.ts b/apps/desktop/src/window/DesktopWindow.ts index 1822bb0c98e..e6cfce3c54f 100644 --- a/apps/desktop/src/window/DesktopWindow.ts +++ b/apps/desktop/src/window/DesktopWindow.ts @@ -8,15 +8,15 @@ import type * as Electron from "electron"; import * as DesktopAssets from "../app/DesktopAssets.ts"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; -import * as DesktopObservability from "../app/DesktopObservability.ts"; +import { makeComponentLogger } from "../app/DesktopObservability.ts"; import * as DesktopState from "../app/DesktopState.ts"; -import * as PreviewManager from "../preview/Manager.ts"; import * as ElectronMenu from "../electron/ElectronMenu.ts"; +import { getDesktopUrl } from "../electron/ElectronProtocol.ts"; import * as ElectronShell from "../electron/ElectronShell.ts"; import * as ElectronTheme from "../electron/ElectronTheme.ts"; import * as ElectronWindow from "../electron/ElectronWindow.ts"; -import { getDesktopUrl } from "../electron/ElectronProtocol.ts"; -import * as IpcChannels from "../ipc/channels.ts"; +import { MENU_ACTION_CHANNEL } from "../ipc/channels.ts"; +import * as PreviewManager from "../preview/Manager.ts"; const TITLEBAR_HEIGHT = 40; const TITLEBAR_COLOR = "#01000000"; // #00000000 does not work correctly on Linux @@ -57,7 +57,7 @@ export class DesktopWindow extends Context.Service< >()("@t3tools/desktop/window/DesktopWindow") {} const { logInfo: logWindowInfo, logWarning: logWindowWarning } = - DesktopObservability.makeComponentLogger("desktop-window"); + makeComponentLogger("desktop-window"); function getIconOption( iconPaths: DesktopAssets.DesktopIconPaths, @@ -380,7 +380,7 @@ export const make = Effect.gen(function* () { const send = () => { if (targetWindow.isDestroyed()) return; - targetWindow.webContents.send(IpcChannels.MENU_ACTION_CHANNEL, action); + targetWindow.webContents.send(MENU_ACTION_CHANNEL, action); void runPromise(electronWindow.reveal(targetWindow)); };