From 29f8a864d1c3c1c343e387bbfdf333b34e24ddf6 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 17 Oct 2025 13:57:05 +0200 Subject: [PATCH] feat: scaffold ipc service on desktop --- apps/desktop/src/app/services/init.service.ts | 3 + .../src/app/services/services.module.ts | 7 ++ apps/desktop/src/main.ts | 6 ++ .../platform/services/ipc-renderer.service.ts | 90 ++++++++++++++++++ .../src/platform/services/ipc.main.service.ts | 95 +++++++++++++++++++ 5 files changed, 201 insertions(+) create mode 100644 apps/desktop/src/platform/services/ipc-renderer.service.ts create mode 100644 apps/desktop/src/platform/services/ipc.main.service.ts diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 6b511ff366d..914a76ef096 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -13,6 +13,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; +import { IpcService } from "@bitwarden/common/platform/ipc"; import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; @@ -52,6 +53,7 @@ export class InitService { private autofillService: DesktopAutofillService, private autotypeService: DesktopAutotypeService, private sdkLoadService: SdkLoadService, + private ipcService: IpcService, @Inject(DOCUMENT) private document: Document, private readonly migrationRunner: MigrationRunner, ) {} @@ -94,6 +96,7 @@ export class InitService { await this.autofillService.init(); await this.autotypeService.init(); + await this.ipcService.init(); }; } } diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 9f2bb1acc90..418f6635942 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -83,6 +83,7 @@ import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk- import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service"; +import { IpcService } from "@bitwarden/common/platform/ipc"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency injection import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; @@ -133,6 +134,7 @@ import { ElectronRendererMessageSender } from "../../platform/services/electron- import { ElectronRendererSecureStorageService } from "../../platform/services/electron-renderer-secure-storage.service"; import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service"; import { I18nRendererService } from "../../platform/services/i18n.renderer.service"; +import { IpcRendererService } from "../../platform/services/ipc-renderer.service"; import { fromIpcMessaging } from "../../platform/utils/from-ipc-messaging"; import { fromIpcSystemTheme } from "../../platform/utils/from-ipc-system-theme"; import { BiometricMessageHandlerService } from "../../services/biometric-message-handler.service"; @@ -476,6 +478,11 @@ const safeProviders: SafeProvider[] = [ useClass: DesktopAutotypeDefaultSettingPolicy, deps: [AccountServiceAbstraction, AuthServiceAbstraction, InternalPolicyService, ConfigService], }), + safeProvider({ + provide: IpcService, + useClass: IpcRendererService, + deps: [], + }), ]; @NgModule({ diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index d5484213a90..4635a688727 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -14,6 +14,7 @@ import { ClientType } from "@bitwarden/common/enums"; import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { IpcService } from "@bitwarden/common/platform/ipc"; import { Message, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- For dependency creation import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; @@ -56,6 +57,7 @@ import { ElectronLogMainService } from "./platform/services/electron-log.main.se import { ElectronStorageService } from "./platform/services/electron-storage.service"; import { EphemeralValueStorageService } from "./platform/services/ephemeral-value-storage.main.service"; import { I18nMainService } from "./platform/services/i18n.main.service"; +import { IpcMainService } from "./platform/services/ipc.main.service"; import { SSOLocalhostCallbackService } from "./platform/services/sso-localhost-callback.service"; import { ElectronMainMessagingService } from "./services/electron-main-messaging.service"; import { MainSdkLoadService } from "./services/main-sdk-load-service"; @@ -91,6 +93,7 @@ export class Main { sshAgentService: MainSshAgentService; sdkLoadService: SdkLoadService; mainDesktopAutotypeService: MainDesktopAutotypeService; + ipcService: IpcService; constructor() { // Set paths for portable builds @@ -311,6 +314,8 @@ export class Main { this.windowMain, ); + this.ipcService = new IpcMainService(this.logService, app); + app .whenReady() .then(() => { @@ -405,6 +410,7 @@ export class Main { }); await this.sdkLoadService.loadAndInit(); + await this.ipcService.init(); }, (e: any) => { this.logService.error("Error while running migrations:", e); diff --git a/apps/desktop/src/platform/services/ipc-renderer.service.ts b/apps/desktop/src/platform/services/ipc-renderer.service.ts new file mode 100644 index 00000000000..b7336d0e64c --- /dev/null +++ b/apps/desktop/src/platform/services/ipc-renderer.service.ts @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { inject, Injectable } from "@angular/core"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { IpcMessage, IpcService, isIpcMessage } from "@bitwarden/common/platform/ipc"; +import { + IncomingMessage, + IpcClient, + IpcCommunicationBackend, + ipcRegisterDiscoverHandler, + OutgoingMessage, +} from "@bitwarden/sdk-internal"; + +export class IpcRendererService extends IpcService { + private logService = inject(LogService); + private platformUtilsService = inject(PlatformUtilsService); + private communicationBackend?: IpcCommunicationBackend; + + override async init() { + try { + // This function uses classes and functions defined in the SDK, so we need to wait for the SDK to load. + await SdkLoadService.Ready; + + this.communicationBackend = new IpcCommunicationBackend({ + async send(message: OutgoingMessage): Promise { + if (message.destination === "DesktopRenderer") { + throw new Error( + `Destination not supported: ${message.destination} (cannot send messages to self)`, + ); + } + + throw new Error("Not implemented"); + // if (message.destination === "BrowserBackground" || message.destination === "ElectronMain") { + // window.postMessage( + // { + // type: "bitwarden-ipc-message", + // message: { + // destination: message.destination, + // payload: [...message.payload], + // topic: message.topic, + // }, + // } satisfies IpcMessage, + // window.location.origin, + // ); + // return; + // } + }, + }); + + // window.addEventListener("message", async (event: MessageEvent) => { + // if (event.origin !== window.origin) { + // return; + // } + + // const message = event.data; + // if (!isIpcMessage(message)) { + // return; + // } + + // if ( + // typeof message.message.destination !== "object" || + // message.message.destination.Web == undefined + // ) { + // return; + // } + + // this.communicationBackend?.receive( + // new IncomingMessage( + // new Uint8Array(message.message.payload), + // message.message.destination, + // "BrowserBackground", + // message.message.topic, + // ), + // ); + // }); + + await super.initWithClient(new IpcClient(this.communicationBackend)); + + if (this.platformUtilsService.isDev()) { + await ipcRegisterDiscoverHandler(this.client, { + version: await this.platformUtilsService.getApplicationVersion(), + }); + } + } catch (e) { + this.logService.error("[IPC] Initialization failed", e); + } + } +} diff --git a/apps/desktop/src/platform/services/ipc.main.service.ts b/apps/desktop/src/platform/services/ipc.main.service.ts new file mode 100644 index 00000000000..c37e46d6a11 --- /dev/null +++ b/apps/desktop/src/platform/services/ipc.main.service.ts @@ -0,0 +1,95 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { IpcMessage, IpcService, isIpcMessage } from "@bitwarden/common/platform/ipc"; +import { + IncomingMessage, + IpcClient, + IpcCommunicationBackend, + ipcRegisterDiscoverHandler, + OutgoingMessage, +} from "@bitwarden/sdk-internal"; + +import { isDev } from "../../utils"; + +export class IpcMainService extends IpcService { + private communicationBackend?: IpcCommunicationBackend; + + constructor( + private logService: LogService, + private app: Electron.App, + ) { + super(); + } + + override async init() { + try { + // This function uses classes and functions defined in the SDK, so we need to wait for the SDK to load. + await SdkLoadService.Ready; + + this.communicationBackend = new IpcCommunicationBackend({ + async send(message: OutgoingMessage): Promise { + if (message.destination === "DesktopMain") { + throw new Error( + `Destination not supported: ${message.destination} (cannot send messages to self)`, + ); + } + + throw new Error("Not implemented"); + // if (message.destination === "BrowserBackground" || message.destination === "ElectronMain") { + // window.postMessage( + // { + // type: "bitwarden-ipc-message", + // message: { + // destination: message.destination, + // payload: [...message.payload], + // topic: message.topic, + // }, + // } satisfies IpcMessage, + // window.location.origin, + // ); + // return; + // } + }, + }); + + // window.addEventListener("message", async (event: MessageEvent) => { + // if (event.origin !== window.origin) { + // return; + // } + + // const message = event.data; + // if (!isIpcMessage(message)) { + // return; + // } + + // if ( + // typeof message.message.destination !== "object" || + // message.message.destination.Web == undefined + // ) { + // return; + // } + + // this.communicationBackend?.receive( + // new IncomingMessage( + // new Uint8Array(message.message.payload), + // message.message.destination, + // "BrowserBackground", + // message.message.topic, + // ), + // ); + // }); + + await super.initWithClient(new IpcClient(this.communicationBackend)); + + if (isDev()) { + await ipcRegisterDiscoverHandler(this.client, { + version: await this.app.getVersion(), + }); + } + } catch (e) { + this.logService.error("[IPC] Initialization failed", e); + } + } +}