diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 97bfe80441..cff783942f 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -131,7 +131,7 @@ import { } from "@bitwarden/common/platform/abstractions/storage.service"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service"; import { ActionsService } from "@bitwarden/common/platform/actions/actions-service"; -import { IpcService } from "@bitwarden/common/platform/ipc"; +import { IpcService, IpcSessionRepository } from "@bitwarden/common/platform/ipc"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency creation import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; @@ -1476,7 +1476,12 @@ export default class MainBackground { ); this.ipcContentScriptManagerService = new IpcContentScriptManagerService(this.configService); - this.ipcService = new IpcBackgroundService(this.platformUtilsService, this.logService); + const ipcSessionRepository = new IpcSessionRepository(this.stateProvider); + this.ipcService = new IpcBackgroundService( + this.platformUtilsService, + this.logService, + ipcSessionRepository, + ); this.endUserNotificationService = new DefaultEndUserNotificationService( this.stateProvider, diff --git a/apps/browser/src/platform/ipc/ipc-background.service.ts b/apps/browser/src/platform/ipc/ipc-background.service.ts index 911ca931c7..9fc2ca24b6 100644 --- a/apps/browser/src/platform/ipc/ipc-background.service.ts +++ b/apps/browser/src/platform/ipc/ipc-background.service.ts @@ -8,6 +8,7 @@ import { OutgoingMessage, ipcRegisterDiscoverHandler, IpcClient, + IpcSessionRepository, } from "@bitwarden/sdk-internal"; import { BrowserApi } from "../browser/browser-api"; @@ -18,6 +19,7 @@ export class IpcBackgroundService extends IpcService { constructor( private platformUtilsService: PlatformUtilsService, private logService: LogService, + private sessionRepository: IpcSessionRepository, ) { super(); } @@ -70,7 +72,9 @@ export class IpcBackgroundService extends IpcService { ); }); - await super.initWithClient(new IpcClient(this.communicationBackend)); + await super.initWithClient( + IpcClient.newWithClientManagedSessions(this.communicationBackend, this.sessionRepository), + ); if (this.platformUtilsService.isDev()) { await ipcRegisterDiscoverHandler(this.client, { diff --git a/apps/web/src/app/platform/ipc/web-ipc.service.ts b/apps/web/src/app/platform/ipc/web-ipc.service.ts index 590c1f36cc..c6614759b4 100644 --- a/apps/web/src/app/platform/ipc/web-ipc.service.ts +++ b/apps/web/src/app/platform/ipc/web-ipc.service.ts @@ -3,7 +3,12 @@ import { inject } 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 { + IpcMessage, + IpcService, + isIpcMessage, + IpcSessionRepository, +} from "@bitwarden/common/platform/ipc"; import { IncomingMessage, IpcClient, @@ -15,6 +20,7 @@ import { export class WebIpcService extends IpcService { private logService = inject(LogService); private platformUtilsService = inject(PlatformUtilsService); + private sessionRepository = inject(IpcSessionRepository); private communicationBackend?: IpcCommunicationBackend; override async init() { @@ -70,7 +76,9 @@ export class WebIpcService extends IpcService { ); }); - await super.initWithClient(new IpcClient(this.communicationBackend)); + await super.initWithClient( + IpcClient.newWithClientManagedSessions(this.communicationBackend, this.sessionRepository), + ); if (this.platformUtilsService.isDev()) { await ipcRegisterDiscoverHandler(this.client, { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 18c21024a6..9dbc667996 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -228,6 +228,7 @@ import { SystemService } from "@bitwarden/common/platform/abstractions/system.se import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service"; import { ActionsService } from "@bitwarden/common/platform/actions"; import { UnsupportedActionsService } from "@bitwarden/common/platform/actions/unsupported-actions.service"; +import { IpcSessionRepository } 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"; @@ -1750,6 +1751,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultNewDeviceVerificationComponentService, deps: [], }), + safeProvider({ + provide: IpcSessionRepository, + useClass: IpcSessionRepository, + deps: [StateProvider], + }), safeProvider({ provide: PremiumInterestStateService, useClass: NoopPremiumInterestStateService, diff --git a/libs/common/src/platform/ipc/index.ts b/libs/common/src/platform/ipc/index.ts index f1acccdddb..3fa6aeb627 100644 --- a/libs/common/src/platform/ipc/index.ts +++ b/libs/common/src/platform/ipc/index.ts @@ -1,2 +1,3 @@ export * from "./ipc-message"; export * from "./ipc.service"; +export * from "./ipc-session-repository"; diff --git a/libs/common/src/platform/ipc/ipc-session-repository.spec.ts b/libs/common/src/platform/ipc/ipc-session-repository.spec.ts new file mode 100644 index 0000000000..62437455b0 --- /dev/null +++ b/libs/common/src/platform/ipc/ipc-session-repository.spec.ts @@ -0,0 +1,49 @@ +import { FakeActiveUserAccessor, FakeStateProvider } from "../../../spec"; +import { UserId } from "../../types/guid"; + +import { IpcSessionRepository } from "./ipc-session-repository"; + +describe("IpcSessionRepository", () => { + const userId = "user-id" as UserId; + let stateProvider!: FakeStateProvider; + let repository!: IpcSessionRepository; + + beforeEach(() => { + stateProvider = new FakeStateProvider(new FakeActiveUserAccessor(userId)); + repository = new IpcSessionRepository(stateProvider); + }); + + it("returns undefined when empty", async () => { + const result = await repository.get("BrowserBackground"); + + expect(result).toBeUndefined(); + }); + + it("saves and retrieves a session", async () => { + const session = { some: "data" }; + await repository.save("BrowserBackground", session); + + const result = await repository.get("BrowserBackground"); + + expect(result).toEqual(session); + }); + + it("saves and retrieves a web session", async () => { + const session = { some: "data" }; + await repository.save({ Web: { id: 9001 } }, session); + + const result = await repository.get({ Web: { id: 9001 } }); + + expect(result).toEqual(session); + }); + + it("removes a session", async () => { + const session = { some: "data" }; + await repository.save("BrowserBackground", session); + + await repository.remove("BrowserBackground"); + const result = await repository.get("BrowserBackground"); + + expect(result).toBeUndefined(); + }); +}); diff --git a/libs/common/src/platform/ipc/ipc-session-repository.ts b/libs/common/src/platform/ipc/ipc-session-repository.ts new file mode 100644 index 0000000000..c9f5fe4a35 --- /dev/null +++ b/libs/common/src/platform/ipc/ipc-session-repository.ts @@ -0,0 +1,51 @@ +import { firstValueFrom, map } from "rxjs"; + +import { Endpoint, IpcSessionRepository as SdkIpcSessionRepository } from "@bitwarden/sdk-internal"; + +import { GlobalState, IPC_MEMORY, KeyDefinition, StateProvider } from "../state"; + +const IPC_SESSIONS = KeyDefinition.record(IPC_MEMORY, "ipcSessions", { + deserializer: (value: object) => value, +}); + +/** + * Implementation of SDK-defined repository interface/trait. Do not use directly. + * All error handling is done by the caller (the SDK). + * For more information see IPC docs. + * + * Interface uses `any` type as defined by the SDK until we get a concrete session type. + */ +export class IpcSessionRepository implements SdkIpcSessionRepository { + private states: GlobalState>; + + constructor(private stateProvider: StateProvider) { + this.states = this.stateProvider.getGlobal(IPC_SESSIONS); + } + + get(endpoint: Endpoint): Promise { + return firstValueFrom(this.states.state$.pipe(map((s) => s?.[endpointToString(endpoint)]))); + } + + async save(endpoint: Endpoint, session: any): Promise { + await this.states.update((s) => ({ + ...s, + [endpointToString(endpoint)]: session, + })); + } + + async remove(endpoint: Endpoint): Promise { + await this.states.update((s) => { + const newState = { ...s }; + delete newState[endpointToString(endpoint)]; + return newState; + }); + } +} + +function endpointToString(endpoint: Endpoint): string { + if (typeof endpoint === "object" && "Web" in endpoint) { + return `Web(${endpoint.Web.id})`; + } + + return endpoint; +} diff --git a/libs/state/src/core/state-definitions.ts b/libs/state/src/core/state-definitions.ts index 7b1d75b298..9d404f14dd 100644 --- a/libs/state/src/core/state-definitions.ts +++ b/libs/state/src/core/state-definitions.ts @@ -127,6 +127,7 @@ export const CRYPTO_MEMORY = new StateDefinition("crypto", "memory"); export const DESKTOP_SETTINGS_DISK = new StateDefinition("desktopSettings", "disk"); export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk"); export const ENVIRONMENT_MEMORY = new StateDefinition("environment", "memory"); +export const IPC_MEMORY = new StateDefinition("interProcessCommunication", "memory"); export const POPUP_VIEW_MEMORY = new StateDefinition("popupView", "memory", { browser: "memory-large-object", }); diff --git a/package-lock.json b/package-lock.json index d2a034e05a..46b70931f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,8 @@ "@angular/platform-browser": "19.2.14", "@angular/platform-browser-dynamic": "19.2.14", "@angular/router": "19.2.14", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.374", - "@bitwarden/sdk-internal": "0.2.0-main.374", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.375", + "@bitwarden/sdk-internal": "0.2.0-main.375", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4607,9 +4607,9 @@ "link": true }, "node_modules/@bitwarden/commercial-sdk-internal": { - "version": "0.2.0-main.374", - "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.374.tgz", - "integrity": "sha512-OYNjEv9Z9Y1vCDWtlp7m49+Fu0WxCyJt+DDupF8T73JqWIl2SdY3ugLtLnCUnqause5VY7OAfa4eOxwn2ONKZg==", + "version": "0.2.0-main.375", + "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.375.tgz", + "integrity": "sha512-UMVfLjMh79+5et1if7qqOi+pSGP5Ay3AcGp4E5oLZ0p0yFsN2Q54UFv+SLju0/oI0qTvVZP1RkEtTJXHdNrpTg==", "license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT", "dependencies": { "type-fest": "^4.41.0" @@ -4712,9 +4712,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.374", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.374.tgz", - "integrity": "sha512-P9td//6M22Eg8YcVOVtcvkD9wfdbnwNe7lZ1HGn74o3CTgDtNq0mE5x00rDeNZq0ctBaUDaqw6XS0jC/tehcag==", + "version": "0.2.0-main.375", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.375.tgz", + "integrity": "sha512-kf2SKFkAdSmV2/ORo6u1eegwYW2ha62NHUsx2ij2uPWmm7mzXUoNa7z8mqhJV1ozg5o7yBqBuXd6Wqo9Ww+/RA==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index 3eb6b1619c..f9757aa1e6 100644 --- a/package.json +++ b/package.json @@ -160,8 +160,8 @@ "@angular/platform-browser": "19.2.14", "@angular/platform-browser-dynamic": "19.2.14", "@angular/router": "19.2.14", - "@bitwarden/sdk-internal": "0.2.0-main.374", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.374", + "@bitwarden/sdk-internal": "0.2.0-main.375", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.375", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0",