mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-18046] Implement session storage (#17346)
* feat: add support for IPC client managed session storage * feat: update SDK * fix: using undecorated service in jslib module directly * feat: add test case for web * chore: document why we use any type * fix: `ipc` too short * typo: omg * Revert "typo: omg" This reverts commit559b05eb5a. * Revert "fix: `ipc` too short" This reverts commit35fc99e10b. * fix: use camelCase
This commit is contained in:
@@ -131,7 +131,7 @@ import {
|
|||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
|
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
import { ActionsService } from "@bitwarden/common/platform/actions/actions-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";
|
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
||||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||||
@@ -1476,7 +1476,12 @@ export default class MainBackground {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.ipcContentScriptManagerService = new IpcContentScriptManagerService(this.configService);
|
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.endUserNotificationService = new DefaultEndUserNotificationService(
|
||||||
this.stateProvider,
|
this.stateProvider,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
OutgoingMessage,
|
OutgoingMessage,
|
||||||
ipcRegisterDiscoverHandler,
|
ipcRegisterDiscoverHandler,
|
||||||
IpcClient,
|
IpcClient,
|
||||||
|
IpcSessionRepository,
|
||||||
} from "@bitwarden/sdk-internal";
|
} from "@bitwarden/sdk-internal";
|
||||||
|
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
@@ -18,6 +19,7 @@ export class IpcBackgroundService extends IpcService {
|
|||||||
constructor(
|
constructor(
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
|
private sessionRepository: IpcSessionRepository,
|
||||||
) {
|
) {
|
||||||
super();
|
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()) {
|
if (this.platformUtilsService.isDev()) {
|
||||||
await ipcRegisterDiscoverHandler(this.client, {
|
await ipcRegisterDiscoverHandler(this.client, {
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import { inject } from "@angular/core";
|
|||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.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 {
|
import {
|
||||||
IncomingMessage,
|
IncomingMessage,
|
||||||
IpcClient,
|
IpcClient,
|
||||||
@@ -15,6 +20,7 @@ import {
|
|||||||
export class WebIpcService extends IpcService {
|
export class WebIpcService extends IpcService {
|
||||||
private logService = inject(LogService);
|
private logService = inject(LogService);
|
||||||
private platformUtilsService = inject(PlatformUtilsService);
|
private platformUtilsService = inject(PlatformUtilsService);
|
||||||
|
private sessionRepository = inject(IpcSessionRepository);
|
||||||
private communicationBackend?: IpcCommunicationBackend;
|
private communicationBackend?: IpcCommunicationBackend;
|
||||||
|
|
||||||
override async init() {
|
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()) {
|
if (this.platformUtilsService.isDev()) {
|
||||||
await ipcRegisterDiscoverHandler(this.client, {
|
await ipcRegisterDiscoverHandler(this.client, {
|
||||||
|
|||||||
@@ -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 { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { ActionsService } from "@bitwarden/common/platform/actions";
|
import { ActionsService } from "@bitwarden/common/platform/actions";
|
||||||
import { UnsupportedActionsService } from "@bitwarden/common/platform/actions/unsupported-actions.service";
|
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";
|
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
|
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
|
||||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||||
@@ -1750,6 +1751,11 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: DefaultNewDeviceVerificationComponentService,
|
useClass: DefaultNewDeviceVerificationComponentService,
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: IpcSessionRepository,
|
||||||
|
useClass: IpcSessionRepository,
|
||||||
|
deps: [StateProvider],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: PremiumInterestStateService,
|
provide: PremiumInterestStateService,
|
||||||
useClass: NoopPremiumInterestStateService,
|
useClass: NoopPremiumInterestStateService,
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./ipc-message";
|
export * from "./ipc-message";
|
||||||
export * from "./ipc.service";
|
export * from "./ipc.service";
|
||||||
|
export * from "./ipc-session-repository";
|
||||||
|
|||||||
49
libs/common/src/platform/ipc/ipc-session-repository.spec.ts
Normal file
49
libs/common/src/platform/ipc/ipc-session-repository.spec.ts
Normal file
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
51
libs/common/src/platform/ipc/ipc-session-repository.ts
Normal file
51
libs/common/src/platform/ipc/ipc-session-repository.ts
Normal file
@@ -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<object, string>(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<Record<string, any>>;
|
||||||
|
|
||||||
|
constructor(private stateProvider: StateProvider) {
|
||||||
|
this.states = this.stateProvider.getGlobal(IPC_SESSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(endpoint: Endpoint): Promise<any | undefined> {
|
||||||
|
return firstValueFrom(this.states.state$.pipe(map((s) => s?.[endpointToString(endpoint)])));
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(endpoint: Endpoint, session: any): Promise<void> {
|
||||||
|
await this.states.update((s) => ({
|
||||||
|
...s,
|
||||||
|
[endpointToString(endpoint)]: session,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(endpoint: Endpoint): Promise<void> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -127,6 +127,7 @@ export const CRYPTO_MEMORY = new StateDefinition("crypto", "memory");
|
|||||||
export const DESKTOP_SETTINGS_DISK = new StateDefinition("desktopSettings", "disk");
|
export const DESKTOP_SETTINGS_DISK = new StateDefinition("desktopSettings", "disk");
|
||||||
export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk");
|
export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk");
|
||||||
export const ENVIRONMENT_MEMORY = new StateDefinition("environment", "memory");
|
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", {
|
export const POPUP_VIEW_MEMORY = new StateDefinition("popupView", "memory", {
|
||||||
browser: "memory-large-object",
|
browser: "memory-large-object",
|
||||||
});
|
});
|
||||||
|
|||||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -23,8 +23,8 @@
|
|||||||
"@angular/platform-browser": "19.2.14",
|
"@angular/platform-browser": "19.2.14",
|
||||||
"@angular/platform-browser-dynamic": "19.2.14",
|
"@angular/platform-browser-dynamic": "19.2.14",
|
||||||
"@angular/router": "19.2.14",
|
"@angular/router": "19.2.14",
|
||||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.374",
|
"@bitwarden/commercial-sdk-internal": "0.2.0-main.375",
|
||||||
"@bitwarden/sdk-internal": "0.2.0-main.374",
|
"@bitwarden/sdk-internal": "0.2.0-main.375",
|
||||||
"@electron/fuses": "1.8.0",
|
"@electron/fuses": "1.8.0",
|
||||||
"@emotion/css": "11.13.5",
|
"@emotion/css": "11.13.5",
|
||||||
"@koa/multer": "4.0.0",
|
"@koa/multer": "4.0.0",
|
||||||
@@ -4607,9 +4607,9 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@bitwarden/commercial-sdk-internal": {
|
"node_modules/@bitwarden/commercial-sdk-internal": {
|
||||||
"version": "0.2.0-main.374",
|
"version": "0.2.0-main.375",
|
||||||
"resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.374.tgz",
|
"resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.375.tgz",
|
||||||
"integrity": "sha512-OYNjEv9Z9Y1vCDWtlp7m49+Fu0WxCyJt+DDupF8T73JqWIl2SdY3ugLtLnCUnqause5VY7OAfa4eOxwn2ONKZg==",
|
"integrity": "sha512-UMVfLjMh79+5et1if7qqOi+pSGP5Ay3AcGp4E5oLZ0p0yFsN2Q54UFv+SLju0/oI0qTvVZP1RkEtTJXHdNrpTg==",
|
||||||
"license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT",
|
"license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"type-fest": "^4.41.0"
|
"type-fest": "^4.41.0"
|
||||||
@@ -4712,9 +4712,9 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@bitwarden/sdk-internal": {
|
"node_modules/@bitwarden/sdk-internal": {
|
||||||
"version": "0.2.0-main.374",
|
"version": "0.2.0-main.375",
|
||||||
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.374.tgz",
|
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.375.tgz",
|
||||||
"integrity": "sha512-P9td//6M22Eg8YcVOVtcvkD9wfdbnwNe7lZ1HGn74o3CTgDtNq0mE5x00rDeNZq0ctBaUDaqw6XS0jC/tehcag==",
|
"integrity": "sha512-kf2SKFkAdSmV2/ORo6u1eegwYW2ha62NHUsx2ij2uPWmm7mzXUoNa7z8mqhJV1ozg5o7yBqBuXd6Wqo9Ww+/RA==",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"type-fest": "^4.41.0"
|
"type-fest": "^4.41.0"
|
||||||
|
|||||||
@@ -160,8 +160,8 @@
|
|||||||
"@angular/platform-browser": "19.2.14",
|
"@angular/platform-browser": "19.2.14",
|
||||||
"@angular/platform-browser-dynamic": "19.2.14",
|
"@angular/platform-browser-dynamic": "19.2.14",
|
||||||
"@angular/router": "19.2.14",
|
"@angular/router": "19.2.14",
|
||||||
"@bitwarden/sdk-internal": "0.2.0-main.374",
|
"@bitwarden/sdk-internal": "0.2.0-main.375",
|
||||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.374",
|
"@bitwarden/commercial-sdk-internal": "0.2.0-main.375",
|
||||||
"@electron/fuses": "1.8.0",
|
"@electron/fuses": "1.8.0",
|
||||||
"@emotion/css": "11.13.5",
|
"@emotion/css": "11.13.5",
|
||||||
"@koa/multer": "4.0.0",
|
"@koa/multer": "4.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user