From 3d052242dfa97412056376f982c754505e00ecda Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:30:39 -0400 Subject: [PATCH] [PM-5578] [PM-5579] [PM-5580] [PM-5581] Send Browser State Provider (#8232) * Replacing state service with state provider * Documentation indicating the differences between the 2 states used. * Creating key definition, updating comments, and modifying test cases * Adding the key definitions tests * Documenting the observables * Fixing the test issue with the awaitAsync import * Removing browser state service stuff for merge fix * no need to redefine interface members * Renaming to DefaultBrowserStateService --- .../notification.background.spec.ts | 4 +- .../background/overlay.background.spec.ts | 4 +- .../browser/src/background/main.background.ts | 4 +- .../state-service.factory.ts | 8 +-- .../browser-session.decorator.spec.ts | 6 +- .../abstractions/browser-state.service.ts | 16 +---- .../services/browser-state.service.spec.ts | 44 ++++--------- ...ce.ts => default-browser-state.service.ts} | 48 +------------- apps/browser/src/popup/app.component.ts | 6 +- .../src/popup/services/services.module.ts | 10 ++- .../popup/send/send-groupings.component.ts | 4 +- .../tools/popup/send/send-type.component.ts | 4 +- .../browser-send-state.service.spec.ts | 61 +++++++++++++++++ .../services/browser-send-state.service.ts | 65 +++++++++++++++++++ .../popup/services/key-definitions.spec.ts | 40 ++++++++++++ .../tools/popup/services/key-definitions.ts | 23 +++++++ .../src/platform/state/state-definitions.ts | 1 + 17 files changed, 236 insertions(+), 112 deletions(-) rename apps/browser/src/platform/services/{browser-state.service.ts => default-browser-state.service.ts} (74%) create mode 100644 apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts create mode 100644 apps/browser/src/tools/popup/services/browser-send-state.service.ts create mode 100644 apps/browser/src/tools/popup/services/key-definitions.spec.ts create mode 100644 apps/browser/src/tools/popup/services/key-definitions.ts diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index fd15ea6e93..93750ece07 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -17,7 +17,7 @@ import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; import { BrowserApi } from "../../platform/browser/browser-api"; -import { BrowserStateService } from "../../platform/services/browser-state.service"; +import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service"; import { NotificationQueueMessageType } from "../enums/notification-queue-message-type.enum"; import { FormData } from "../services/abstractions/autofill.service"; import AutofillService from "../services/autofill.service"; @@ -49,7 +49,7 @@ describe("NotificationBackground", () => { const authService = mock(); const policyService = mock(); const folderService = mock(); - const stateService = mock(); + const stateService = mock(); const userNotificationSettingsService = mock(); const domainSettingsService = mock(); const environmentService = mock(); diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index c06df6603b..2599c1825e 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -33,7 +33,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { BrowserApi } from "../../platform/browser/browser-api"; -import { BrowserStateService } from "../../platform/services/browser-state.service"; +import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service"; import { BrowserPlatformUtilsService } from "../../platform/services/platform-utils/browser-platform-utils.service"; import { AutofillService } from "../services/abstractions/autofill.service"; import { @@ -72,7 +72,7 @@ describe("OverlayBackground", () => { urls: { icons: "https://icons.bitwarden.com/" }, }), ); - const stateService = mock(); + const stateService = mock(); const autofillSettingsService = mock(); const i18nService = mock(); const platformUtilsService = mock(); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f8a8e4fdb9..a7fadc6d6f 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -211,7 +211,7 @@ import BrowserLocalStorageService from "../platform/services/browser-local-stora import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service"; import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service"; import BrowserMessagingService from "../platform/services/browser-messaging.service"; -import { BrowserStateService } from "../platform/services/browser-state.service"; +import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service"; import I18nService from "../platform/services/i18n.service"; import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service"; import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service"; @@ -466,7 +466,7 @@ export default class MainBackground { new MigrationBuilderService(), ); - this.stateService = new BrowserStateService( + this.stateService = new DefaultBrowserStateService( this.storageService, this.secureStorageService, this.memoryStorageService, diff --git a/apps/browser/src/platform/background/service-factories/state-service.factory.ts b/apps/browser/src/platform/background/service-factories/state-service.factory.ts index 20a9ac074a..5567e00990 100644 --- a/apps/browser/src/platform/background/service-factories/state-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/state-service.factory.ts @@ -10,7 +10,7 @@ import { TokenServiceInitOptions, } from "../../../auth/background/service-factories/token-service.factory"; import { Account } from "../../../models/account"; -import { BrowserStateService } from "../../services/browser-state.service"; +import { DefaultBrowserStateService } from "../../services/default-browser-state.service"; import { environmentServiceFactory, @@ -46,15 +46,15 @@ export type StateServiceInitOptions = StateServiceFactoryOptions & MigrationRunnerInitOptions; export async function stateServiceFactory( - cache: { stateService?: BrowserStateService } & CachedServices, + cache: { stateService?: DefaultBrowserStateService } & CachedServices, opts: StateServiceInitOptions, -): Promise { +): Promise { const service = await factory( cache, "stateService", opts, async () => - new BrowserStateService( + new DefaultBrowserStateService( await diskStorageServiceFactory(cache, opts), await secureStorageServiceFactory(cache, opts), await memoryStorageServiceFactory(cache, opts), diff --git a/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.spec.ts b/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.spec.ts index 4b0226d54e..2092f6992b 100644 --- a/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.spec.ts +++ b/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.spec.ts @@ -3,7 +3,7 @@ import { BehaviorSubject } from "rxjs"; import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; -import { BrowserStateService } from "../../services/browser-state.service"; +import { DefaultBrowserStateService } from "../../services/default-browser-state.service"; import { browserSession } from "./browser-session.decorator"; import { SessionStorable } from "./session-storable"; @@ -25,7 +25,7 @@ describe("browserSession decorator", () => { }); it("should create if StateService is a constructor argument", () => { - const stateService = Object.create(BrowserStateService.prototype, { + const stateService = Object.create(DefaultBrowserStateService.prototype, { memoryStorageService: { value: Object.create(MemoryStorageService.prototype, { type: { value: MemoryStorageService.TYPE }, @@ -35,7 +35,7 @@ describe("browserSession decorator", () => { @browserSession class TestClass { - constructor(private stateService: BrowserStateService) {} + constructor(private stateService: DefaultBrowserStateService) {} } expect(new TestClass(stateService)).toBeDefined(); diff --git a/apps/browser/src/platform/services/abstractions/browser-state.service.ts b/apps/browser/src/platform/services/abstractions/browser-state.service.ts index 82ec54975a..c8e2c502e7 100644 --- a/apps/browser/src/platform/services/abstractions/browser-state.service.ts +++ b/apps/browser/src/platform/services/abstractions/browser-state.service.ts @@ -1,19 +1,5 @@ import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; -import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; import { Account } from "../../../models/account"; -import { BrowserComponentState } from "../../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; -export abstract class BrowserStateService extends BaseStateServiceAbstraction { - getBrowserSendComponentState: (options?: StorageOptions) => Promise; - setBrowserSendComponentState: ( - value: BrowserSendComponentState, - options?: StorageOptions, - ) => Promise; - getBrowserSendTypeComponentState: (options?: StorageOptions) => Promise; - setBrowserSendTypeComponentState: ( - value: BrowserComponentState, - options?: StorageOptions, - ) => Promise; -} +export abstract class BrowserStateService extends BaseStateServiceAbstraction {} diff --git a/apps/browser/src/platform/services/browser-state.service.spec.ts b/apps/browser/src/platform/services/browser-state.service.spec.ts index 7e75b9b707..8f43998321 100644 --- a/apps/browser/src/platform/services/browser-state.service.spec.ts +++ b/apps/browser/src/platform/services/browser-state.service.spec.ts @@ -1,4 +1,5 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -12,15 +13,11 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta import { State } from "@bitwarden/common/platform/models/domain/state"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; import { mockAccountServiceWith } from "@bitwarden/common/spec"; -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { UserId } from "@bitwarden/common/types/guid"; import { Account } from "../../models/account"; -import { BrowserComponentState } from "../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../models/browserSendComponentState"; -import { BrowserStateService } from "./browser-state.service"; +import { DefaultBrowserStateService } from "./default-browser-state.service"; // disable session syncing to just test class jest.mock("../decorators/session-sync-observable/"); @@ -39,7 +36,7 @@ describe("Browser State Service", () => { const userId = "userId" as UserId; const accountService = mockAccountServiceWith(userId); - let sut: BrowserStateService; + let sut: DefaultBrowserStateService; beforeEach(() => { secureStorageService = mock(); @@ -71,7 +68,7 @@ describe("Browser State Service", () => { const stateGetter = (key: string) => Promise.resolve(state); memoryStorageService.get.mockImplementation(stateGetter); - sut = new BrowserStateService( + sut = new DefaultBrowserStateService( diskStorageService, secureStorageService, memoryStorageService, @@ -85,32 +82,17 @@ describe("Browser State Service", () => { ); }); - describe("getBrowserSendComponentState", () => { - it("should return a BrowserSendComponentState", async () => { - const sendState = new BrowserSendComponentState(); - sendState.sends = [new SendView(), new SendView()]; - sendState.typeCounts = new Map([ - [SendType.File, 3], - [SendType.Text, 5], - ]); - state.accounts[userId].send = sendState; - (global as any)["watch"] = state; + describe("add Account", () => { + it("should add account", async () => { + const newUserId = "newUserId" as UserId; + const newAcct = new Account({ + profile: { userId: newUserId }, + }); - const actual = await sut.getBrowserSendComponentState(); - expect(actual).toBeInstanceOf(BrowserSendComponentState); - expect(actual).toMatchObject(sendState); - }); - }); + await sut.addAccount(newAcct); - describe("getBrowserSendTypeComponentState", () => { - it("should return a BrowserComponentState", async () => { - const componentState = new BrowserComponentState(); - componentState.scrollY = 0; - componentState.searchText = "test"; - state.accounts[userId].sendType = componentState; - - const actual = await sut.getBrowserSendTypeComponentState(); - expect(actual).toStrictEqual(componentState); + const accts = await firstValueFrom(sut.accounts$); + expect(accts[newUserId]).toBeDefined(); }); }); }); diff --git a/apps/browser/src/platform/services/browser-state.service.ts b/apps/browser/src/platform/services/default-browser-state.service.ts similarity index 74% rename from apps/browser/src/platform/services/browser-state.service.ts rename to apps/browser/src/platform/services/default-browser-state.service.ts index ea410ee83a..f1f306dbc0 100644 --- a/apps/browser/src/platform/services/browser-state.service.ts +++ b/apps/browser/src/platform/services/default-browser-state.service.ts @@ -15,17 +15,15 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service"; import { Account } from "../../models/account"; -import { BrowserComponentState } from "../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../models/browserSendComponentState"; import { BrowserApi } from "../browser/browser-api"; import { browserSession, sessionSync } from "../decorators/session-sync-observable"; -import { BrowserStateService as StateServiceAbstraction } from "./abstractions/browser-state.service"; +import { BrowserStateService } from "./abstractions/browser-state.service"; @browserSession -export class BrowserStateService +export class DefaultBrowserStateService extends BaseStateService - implements StateServiceAbstraction + implements BrowserStateService { @sessionSync({ initializer: Account.fromJSON as any, // TODO: Remove this any when all any types are removed from Account @@ -115,46 +113,6 @@ export class BrowserStateService ); } - async getBrowserSendComponentState(options?: StorageOptions): Promise { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) - )?.send; - } - - async setBrowserSendComponentState( - value: BrowserSendComponentState, - options?: StorageOptions, - ): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.send = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - - async getBrowserSendTypeComponentState(options?: StorageOptions): Promise { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) - )?.sendType; - } - - async setBrowserSendTypeComponentState( - value: BrowserComponentState, - options?: StorageOptions, - ): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.sendType = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - // Overriding the base class to prevent deleting the cache on save. We register a storage listener // to delete the cache in the constructor above. protected override async saveAccountToDisk( diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index e0d898481b..03ac1612f1 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -12,6 +12,7 @@ import { BrowserApi } from "../platform/browser/browser-api"; import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service"; import { BrowserStateService } from "../platform/services/abstractions/browser-state.service"; import { ForegroundPlatformUtilsService } from "../platform/services/platform-utils/foreground-platform-utils.service"; +import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; import { routerTransition } from "./app-routing.animations"; @@ -38,6 +39,7 @@ export class AppComponent implements OnInit, OnDestroy { private i18nService: I18nService, private router: Router, private stateService: BrowserStateService, + private browserSendStateService: BrowserSendStateService, private vaultBrowserStateService: VaultBrowserStateService, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, @@ -231,8 +233,8 @@ export class AppComponent implements OnInit, OnDestroy { await Promise.all([ this.vaultBrowserStateService.setBrowserGroupingsComponentState(null), this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null), - this.stateService.setBrowserSendComponentState(null), - this.stateService.setBrowserSendTypeComponentState(null), + this.browserSendStateService.setBrowserSendComponentState(null), + this.browserSendStateService.setBrowserSendTypeComponentState(null), ]); } } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index e68873e31c..037246d3c4 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -99,11 +99,12 @@ import { BrowserEnvironmentService } from "../../platform/services/browser-envir import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service"; import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service"; import BrowserMessagingService from "../../platform/services/browser-messaging.service"; -import { BrowserStateService } from "../../platform/services/browser-state.service"; +import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service"; import I18nService from "../../platform/services/i18n.service"; import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service"; import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; +import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service"; import { VaultFilterService } from "../../vault/services/vault-filter.service"; @@ -412,7 +413,7 @@ const safeProviders: SafeProvider[] = [ tokenService: TokenService, migrationRunner: MigrationRunner, ) => { - return new BrowserStateService( + return new DefaultBrowserStateService( storageService, secureStorageService, memoryStorageService, @@ -487,6 +488,11 @@ const safeProviders: SafeProvider[] = [ useClass: UserNotificationSettingsService, deps: [StateProvider], }), + safeProvider({ + provide: BrowserSendStateService, + useClass: BrowserSendStateService, + deps: [StateProvider], + }), ]; @NgModule({ diff --git a/apps/browser/src/tools/popup/send/send-groupings.component.ts b/apps/browser/src/tools/popup/send/send-groupings.component.ts index 25fa67d51a..9b3ecc7163 100644 --- a/apps/browser/src/tools/popup/send/send-groupings.component.ts +++ b/apps/browser/src/tools/popup/send/send-groupings.component.ts @@ -18,7 +18,7 @@ import { DialogService } from "@bitwarden/components"; import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { BrowserStateService } from "../../../platform/services/abstractions/browser-state.service"; +import { BrowserSendStateService } from "../services/browser-send-state.service"; const ComponentId = "SendComponent"; @@ -43,7 +43,7 @@ export class SendGroupingsComponent extends BaseSendComponent { ngZone: NgZone, policyService: PolicyService, searchService: SearchService, - private stateService: BrowserStateService, + private stateService: BrowserSendStateService, private router: Router, private syncService: SyncService, private changeDetectorRef: ChangeDetectorRef, diff --git a/apps/browser/src/tools/popup/send/send-type.component.ts b/apps/browser/src/tools/popup/send/send-type.component.ts index 4b27edc043..aca02587de 100644 --- a/apps/browser/src/tools/popup/send/send-type.component.ts +++ b/apps/browser/src/tools/popup/send/send-type.component.ts @@ -19,7 +19,7 @@ import { DialogService } from "@bitwarden/components"; import { BrowserComponentState } from "../../../models/browserComponentState"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { BrowserStateService } from "../../../platform/services/abstractions/browser-state.service"; +import { BrowserSendStateService } from "../services/browser-send-state.service"; const ComponentId = "SendTypeComponent"; @@ -42,7 +42,7 @@ export class SendTypeComponent extends BaseSendComponent { ngZone: NgZone, policyService: PolicyService, searchService: SearchService, - private stateService: BrowserStateService, + private stateService: BrowserSendStateService, private route: ActivatedRoute, private location: Location, private changeDetectorRef: ChangeDetectorRef, diff --git a/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts b/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts new file mode 100644 index 0000000000..3dafc0934a --- /dev/null +++ b/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts @@ -0,0 +1,61 @@ +import { + FakeAccountService, + mockAccountServiceWith, +} from "@bitwarden/common/../spec/fake-account-service"; +import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; +import { awaitAsync } from "@bitwarden/common/../spec/utils"; + +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { BrowserComponentState } from "../../../models/browserComponentState"; +import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; + +import { BrowserSendStateService } from "./browser-send-state.service"; + +describe("Browser Send State Service", () => { + let stateProvider: FakeStateProvider; + + let accountService: FakeAccountService; + let stateService: BrowserSendStateService; + const mockUserId = Utils.newGuid() as UserId; + + beforeEach(() => { + accountService = mockAccountServiceWith(mockUserId); + stateProvider = new FakeStateProvider(accountService); + + stateService = new BrowserSendStateService(stateProvider); + }); + + describe("getBrowserSendComponentState", () => { + it("should return BrowserSendComponentState", async () => { + const state = new BrowserSendComponentState(); + state.scrollY = 0; + state.searchText = "test"; + state.typeCounts = new Map().set(SendType.File, 1); + + await stateService.setBrowserSendComponentState(state); + + await awaitAsync(); + + const actual = await stateService.getBrowserSendComponentState(); + expect(actual).toStrictEqual(state); + }); + }); + + describe("getBrowserSendTypeComponentState", () => { + it("should return BrowserComponentState", async () => { + const state = new BrowserComponentState(); + state.scrollY = 0; + state.searchText = "test"; + + await stateService.setBrowserSendTypeComponentState(state); + + await awaitAsync(); + + const actual = await stateService.getBrowserSendTypeComponentState(); + expect(actual).toStrictEqual(state); + }); + }); +}); diff --git a/apps/browser/src/tools/popup/services/browser-send-state.service.ts b/apps/browser/src/tools/popup/services/browser-send-state.service.ts new file mode 100644 index 0000000000..b814ee5bc9 --- /dev/null +++ b/apps/browser/src/tools/popup/services/browser-send-state.service.ts @@ -0,0 +1,65 @@ +import { Observable, firstValueFrom } from "rxjs"; + +import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; + +import { BrowserComponentState } from "../../../models/browserComponentState"; +import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; + +import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions"; + +/** Get or set the active user's component state for the Send browser component + */ +export class BrowserSendStateService { + /** Observable that contains the current state for active user Sends including the send data and type counts + * along with the search text and scroll position + */ + browserSendComponentState$: Observable; + + /** Observable that contains the current state for active user Sends that only includes the search text + * and scroll position + */ + browserSendTypeComponentState$: Observable; + + private activeUserBrowserSendComponentState: ActiveUserState; + private activeUserBrowserSendTypeComponentState: ActiveUserState; + + constructor(protected stateProvider: StateProvider) { + this.activeUserBrowserSendComponentState = this.stateProvider.getActive(BROWSER_SEND_COMPONENT); + this.browserSendComponentState$ = this.activeUserBrowserSendComponentState.state$; + + this.activeUserBrowserSendTypeComponentState = this.stateProvider.getActive( + BROWSER_SEND_TYPE_COMPONENT, + ); + this.browserSendTypeComponentState$ = this.activeUserBrowserSendTypeComponentState.state$; + } + + /** Get the active user's browser send component state + * @returns { BrowserSendComponentState } contains the sends and type counts along with the scroll position and search text for the + * send component on the browser + */ + async getBrowserSendComponentState(): Promise { + return await firstValueFrom(this.browserSendComponentState$); + } + + /** Set the active user's browser send component state + * @param { BrowserSendComponentState } value sets the sends and type counts along with the scroll position and search text for + * the send component on the browser + */ + async setBrowserSendComponentState(value: BrowserSendComponentState): Promise { + await this.activeUserBrowserSendComponentState.update(() => value); + } + + /** Get the active user's browser component state + * @returns { BrowserComponentState } contains the scroll position and search text for the sends menu on the browser + */ + async getBrowserSendTypeComponentState(): Promise { + return await firstValueFrom(this.browserSendTypeComponentState$); + } + + /** Set the active user's browser component state + * @param { BrowserComponentState } value set the scroll position and search text for the send component on the browser + */ + async setBrowserSendTypeComponentState(value: BrowserComponentState): Promise { + await this.activeUserBrowserSendTypeComponentState.update(() => value); + } +} diff --git a/apps/browser/src/tools/popup/services/key-definitions.spec.ts b/apps/browser/src/tools/popup/services/key-definitions.spec.ts new file mode 100644 index 0000000000..3ba574efa3 --- /dev/null +++ b/apps/browser/src/tools/popup/services/key-definitions.spec.ts @@ -0,0 +1,40 @@ +import { Jsonify } from "type-fest"; + +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; + +import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; + +import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions"; + +describe("Key definitions", () => { + describe("BROWSER_SEND_COMPONENT", () => { + it("should deserialize BrowserSendComponentState", () => { + const keyDef = BROWSER_SEND_COMPONENT; + + const expectedState = { + typeCounts: new Map(), + }; + + const result = keyDef.deserializer( + JSON.parse(JSON.stringify(expectedState)) as Jsonify, + ); + + expect(result).toEqual(expectedState); + }); + }); + + describe("BROWSER_SEND_TYPE_COMPONENT", () => { + it("should deserialize BrowserComponentState", () => { + const keyDef = BROWSER_SEND_TYPE_COMPONENT; + + const expectedState = { + scrollY: 0, + searchText: "test", + }; + + const result = keyDef.deserializer(JSON.parse(JSON.stringify(expectedState))); + + expect(result).toEqual(expectedState); + }); + }); +}); diff --git a/apps/browser/src/tools/popup/services/key-definitions.ts b/apps/browser/src/tools/popup/services/key-definitions.ts new file mode 100644 index 0000000000..9b256073f3 --- /dev/null +++ b/apps/browser/src/tools/popup/services/key-definitions.ts @@ -0,0 +1,23 @@ +import { Jsonify } from "type-fest"; + +import { BROWSER_SEND_MEMORY, KeyDefinition } from "@bitwarden/common/platform/state"; + +import { BrowserComponentState } from "../../../models/browserComponentState"; +import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; + +export const BROWSER_SEND_COMPONENT = new KeyDefinition( + BROWSER_SEND_MEMORY, + "browser_send_component", + { + deserializer: (obj: Jsonify) => + BrowserSendComponentState.fromJSON(obj), + }, +); + +export const BROWSER_SEND_TYPE_COMPONENT = new KeyDefinition( + BROWSER_SEND_MEMORY, + "browser_send_type_component", + { + deserializer: (obj: Jsonify) => BrowserComponentState.fromJSON(obj), + }, +); diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 979321c1e3..d9265cf10c 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -102,6 +102,7 @@ export const SM_ONBOARDING_DISK = new StateDefinition("smOnboarding", "disk", { export const GENERATOR_DISK = new StateDefinition("generator", "disk"); export const GENERATOR_MEMORY = new StateDefinition("generator", "memory"); +export const BROWSER_SEND_MEMORY = new StateDefinition("sendBrowser", "memory"); export const EVENT_COLLECTION_DISK = new StateDefinition("eventCollection", "disk"); export const SEND_DISK = new StateDefinition("encryptedSend", "disk", { web: "memory",