import { ContextMenuItemSchema, DesktopAppBrandingSchema, DesktopEnvironmentBootstrapSchema, DesktopThemeSchema, PickFolderOptionsSchema, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Redacted from "effect/Redacted"; import * as Schema from "effect/Schema"; import * as DesktopBackendManager from "../../backend/DesktopBackendManager.ts"; import * as DesktopLocalEnvironmentAuth from "../../backend/DesktopLocalEnvironmentAuth.ts"; import * as DesktopEnvironment from "../../app/DesktopEnvironment.ts"; import * as ElectronDialog from "../../electron/ElectronDialog.ts"; import * as ElectronMenu from "../../electron/ElectronMenu.ts"; import * as ElectronShell from "../../electron/ElectronShell.ts"; import * as ElectronTheme from "../../electron/ElectronTheme.ts"; import * as ElectronWindow from "../../electron/ElectronWindow.ts"; import * as IpcChannels from "../channels.ts"; import * as DesktopIpc from "../DesktopIpc.ts"; const ContextMenuPosition = Schema.Struct({ x: Schema.Number, y: Schema.Number, }); const ContextMenuInput = Schema.Struct({ items: Schema.Array(ContextMenuItemSchema), position: Schema.optionalKey(ContextMenuPosition), }); function toWebSocketBaseUrl(httpBaseUrl: URL): string { const url = new URL(httpBaseUrl.href); url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; return url.href; } export const getAppBranding = DesktopIpc.makeSyncIpcMethod({ channel: IpcChannels.GET_APP_BRANDING_CHANNEL, result: Schema.NullOr(DesktopAppBrandingSchema), handler: Effect.fn("desktop.ipc.window.getAppBranding")(function* () { const environment = yield* DesktopEnvironment.DesktopEnvironment; return environment.branding; }), }); export const getLocalEnvironmentBootstrap = DesktopIpc.makeSyncIpcMethod({ channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAP_CHANNEL, result: Schema.NullOr(DesktopEnvironmentBootstrapSchema), handler: Effect.fn("desktop.ipc.window.getLocalEnvironmentBootstrap")(function* () { const backendManager = yield* DesktopBackendManager.DesktopBackendManager; const config = yield* backendManager.currentConfig; return Option.match(config, { onNone: () => null, onSome: ({ bootstrap, httpBaseUrl }) => ({ label: "Local environment", httpBaseUrl: httpBaseUrl.href, wsBaseUrl: toWebSocketBaseUrl(httpBaseUrl), ...(bootstrap.desktopBootstrapToken ? { bootstrapToken: bootstrap.desktopBootstrapToken } : {}), }), }); }), }); export const getLocalEnvironmentBearerToken = DesktopIpc.makeIpcMethod({ channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL, payload: Schema.Void, result: Schema.String, handler: Effect.fn("desktop.ipc.window.getLocalEnvironmentBearerToken")(function* () { const localAuth = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth; return yield* localAuth.getBearerToken; }), }); export const pickFolder = DesktopIpc.makeIpcMethod({ channel: IpcChannels.PICK_FOLDER_CHANNEL, payload: Schema.UndefinedOr(PickFolderOptionsSchema), result: Schema.NullOr(Schema.String), handler: Effect.fn("desktop.ipc.window.pickFolder")(function* (options) { const dialog = yield* ElectronDialog.ElectronDialog; const electronWindow = yield* ElectronWindow.ElectronWindow; const environment = yield* DesktopEnvironment.DesktopEnvironment; const selectedPath = yield* dialog.pickFolder({ owner: yield* electronWindow.focusedMainOrFirst, defaultPath: environment.resolvePickFolderDefaultPath(options), }); return Option.getOrNull(selectedPath); }), }); export const confirm = DesktopIpc.makeIpcMethod({ channel: IpcChannels.CONFIRM_CHANNEL, payload: Schema.String, result: Schema.Boolean, handler: Effect.fn("desktop.ipc.window.confirm")(function* (message) { const dialog = yield* ElectronDialog.ElectronDialog; const electronWindow = yield* ElectronWindow.ElectronWindow; return yield* electronWindow.focusedMainOrFirst.pipe( Effect.flatMap((owner) => dialog.confirm({ owner, message })), ); }), }); export const setTheme = DesktopIpc.makeIpcMethod({ channel: IpcChannels.SET_THEME_CHANNEL, payload: DesktopThemeSchema, result: Schema.Void, handler: Effect.fn("desktop.ipc.window.setTheme")(function* (theme) { const electronTheme = yield* ElectronTheme.ElectronTheme; yield* electronTheme.setSource(theme); }), }); export const showContextMenu = DesktopIpc.makeIpcMethod({ channel: IpcChannels.CONTEXT_MENU_CHANNEL, payload: ContextMenuInput, result: Schema.NullOr(Schema.String), handler: Effect.fn("desktop.ipc.window.showContextMenu")(function* (input) { const electronMenu = yield* ElectronMenu.ElectronMenu; const electronWindow = yield* ElectronWindow.ElectronWindow; const window = yield* electronWindow.focusedMainOrFirst; if (Option.isNone(window)) { return null; } const selectedItemId = yield* electronMenu.showContextMenu({ window: window.value, items: input.items, position: Option.fromNullishOr(input.position), }); return Option.getOrNull(selectedItemId); }), }); export const openExternal = DesktopIpc.makeIpcMethod({ channel: IpcChannels.OPEN_EXTERNAL_CHANNEL, payload: Schema.String, result: Schema.Boolean, handler: Effect.fn("desktop.ipc.window.openExternal")(function* (url) { const shell = yield* ElectronShell.ElectronShell; return yield* shell.openExternal(url).pipe( Effect.catchTag("ElectronShellOpenExternalError", (error) => Effect.logWarning(error.message).pipe( Effect.annotateLogs({ error: Redacted.make(error, { label: error._tag }), "error.type": error._tag, "desktop.external_url.hostname": error.urlHostname, "desktop.external_url.input_length": error.urlLength, "desktop.external_url.protocol": error.urlProtocol, }), Effect.as(false), ), ), ); }), });