diff --git a/.storybook/main.ts b/.storybook/main.ts index 544beb48c7..c71a74c2a7 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -38,9 +38,7 @@ const config: StorybookConfig = { }, env: (config) => ({ ...config, - FLAGS: JSON.stringify({ - secretsManager: true, - }), + FLAGS: JSON.stringify({}), }), webpackFinal: async (config, { configType }) => { if (config.resolve) { diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index f4019b1ce0..9cf9701074 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 d74de08473..ef2dc87852 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -34,7 +34,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 { @@ -77,7 +77,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 cb3a7011c1..2affc7a0d6 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/flags.ts b/apps/browser/src/platform/flags.ts index 71a20edc5e..36aa698a7b 100644 --- a/apps/browser/src/platform/flags.ts +++ b/apps/browser/src/platform/flags.ts @@ -11,13 +11,13 @@ import { GroupPolicyEnvironment } from "../admin-console/types/group-policy-envi import { BrowserApi } from "./browser/browser-api"; // required to avoid linting errors when there are no flags -/* eslint-disable-next-line @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = { accountSwitching?: boolean; } & SharedFlags; // required to avoid linting errors when there are no flags -/* eslint-disable-next-line @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = { storeSessionDecrypted?: boolean; managedEnvironment?: GroupPolicyEnvironment; diff --git a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts index 22ce8d4564..a5681d65c0 100644 --- a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts +++ b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts @@ -8,6 +8,23 @@ import { import { fromChromeEvent } from "../../browser/from-chrome-event"; +export const serializationIndicator = "__json__"; + +export const objToStore = (obj: any) => { + if (obj == null) { + return null; + } + + if (obj instanceof Set) { + obj = Array.from(obj); + } + + return { + [serializationIndicator]: true, + value: JSON.stringify(obj), + }; +}; + export default abstract class AbstractChromeStorageService implements AbstractStorageService, ObservableStorageService { @@ -44,7 +61,11 @@ export default abstract class AbstractChromeStorageService return new Promise((resolve) => { this.chromeStorageApi.get(key, (obj: any) => { if (obj != null && obj[key] != null) { - resolve(obj[key] as T); + let value = obj[key]; + if (value[serializationIndicator] && typeof value.value === "string") { + value = JSON.parse(value.value); + } + resolve(value as T); return; } resolve(null); @@ -57,14 +78,7 @@ export default abstract class AbstractChromeStorageService } async save(key: string, obj: any): Promise { - if (obj == null) { - // Fix safari not liking null in set - return this.remove(key); - } - - if (obj instanceof Set) { - obj = Array.from(obj); - } + obj = objToStore(obj); const keyedObj = { [key]: obj }; return new Promise((resolve) => { 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/abstractions/chrome-storage-api.service.spec.ts b/apps/browser/src/platform/services/abstractions/chrome-storage-api.service.spec.ts new file mode 100644 index 0000000000..bb89dc8a6a --- /dev/null +++ b/apps/browser/src/platform/services/abstractions/chrome-storage-api.service.spec.ts @@ -0,0 +1,99 @@ +import AbstractChromeStorageService, { + objToStore, + serializationIndicator, +} from "./abstract-chrome-storage-api.service"; + +class TestChromeStorageApiService extends AbstractChromeStorageService {} + +describe("objectToStore", () => { + it("converts an object to a tagged string", () => { + const obj = { key: "value" }; + const result = objToStore(obj); + expect(result).toEqual({ + [serializationIndicator]: true, + value: JSON.stringify(obj), + }); + }); + + it("converts a set to an array prior to serialization", () => { + const obj = new Set(["value"]); + const result = objToStore(obj); + expect(result).toEqual({ + [serializationIndicator]: true, + value: JSON.stringify(Array.from(obj)), + }); + }); + + it("does nothing to null", () => { + expect(objToStore(null)).toEqual(null); + }); +}); + +describe("ChromeStorageApiService", () => { + let service: TestChromeStorageApiService; + let store: Record; + + beforeEach(() => { + store = {}; + + service = new TestChromeStorageApiService(chrome.storage.local); + }); + + describe("save", () => { + let setMock: jest.Mock; + + beforeEach(() => { + // setup save + setMock = chrome.storage.local.set as jest.Mock; + setMock.mockImplementation((data, callback) => { + Object.assign(store, data); + callback(); + }); + }); + + it("uses `objToStore` to prepare a value for set", async () => { + const key = "key"; + const value = { key: "value" }; + await service.save(key, value); + expect(setMock).toHaveBeenCalledWith( + { + [key]: objToStore(value), + }, + expect.any(Function), + ); + }); + }); + + describe("get", () => { + let getMock: jest.Mock; + + beforeEach(() => { + // setup get + getMock = chrome.storage.local.get as jest.Mock; + getMock.mockImplementation((key, callback) => { + callback({ [key]: store[key] }); + }); + }); + + it("returns a stored value when it is serialized", async () => { + const key = "key"; + const value = { key: "value" }; + store[key] = objToStore(value); + const result = await service.get(key); + expect(result).toEqual(value); + }); + + it("returns a stored value when it is not serialized", async () => { + const key = "key"; + const value = "value"; + store[key] = value; + const result = await service.get(key); + expect(result).toEqual(value); + }); + + it("returns null when the key does not exist", async () => { + const result = await service.get("key"); + expect(result).toBeNull(); + }); + }); +}); 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/apps/cli/src/platform/flags.ts b/apps/cli/src/platform/flags.ts index 4e31e39e99..dc0103e243 100644 --- a/apps/cli/src/platform/flags.ts +++ b/apps/cli/src/platform/flags.ts @@ -7,11 +7,11 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -/* eslint-disable-next-line @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -/* eslint-disable-next-line @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/desktop/config/development.json b/apps/desktop/config/development.json index d2b1073812..7a8659feff 100644 --- a/apps/desktop/config/development.json +++ b/apps/desktop/config/development.json @@ -1,7 +1,6 @@ { "devFlags": {}, "flags": { - "showDDGSetting": true, "enableCipherKeyEncryption": false } } diff --git a/apps/desktop/config/production.json b/apps/desktop/config/production.json index 39b78094d0..f57c3d9bc3 100644 --- a/apps/desktop/config/production.json +++ b/apps/desktop/config/production.json @@ -1,6 +1,5 @@ { "flags": { - "showDDGSetting": true, "enableCipherKeyEncryption": false } } diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index a613328878..07fcc5d3b8 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -24,7 +24,6 @@ import { DialogService } from "@bitwarden/components"; import { SetPinComponent } from "../../auth/components/set-pin.component"; import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; -import { flagEnabled } from "../../platform/flags"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; @Component({ @@ -146,7 +145,7 @@ export class SettingsComponent implements OnInit { this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc"); // DuckDuckGo browser is only for macos initially - this.showDuckDuckGoIntegrationOption = flagEnabled("showDDGSetting") && isMac; + this.showDuckDuckGoIntegrationOption = isMac; this.vaultTimeoutOptions = [ // { name: i18nService.t('immediately'), value: 0 }, diff --git a/apps/desktop/src/platform/flags.ts b/apps/desktop/src/platform/flags.ts index 0481c8ce9b..dc0103e243 100644 --- a/apps/desktop/src/platform/flags.ts +++ b/apps/desktop/src/platform/flags.ts @@ -7,13 +7,11 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -/* eslint-disable-next-line @typescript-eslint/ban-types */ -export type Flags = { - showDDGSetting?: boolean; -} & SharedFlags; +// eslint-disable-next-line @typescript-eslint/ban-types +export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -/* eslint-disable-next-line @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/web/config/base.json b/apps/web/config/base.json index a377298c63..5dc03a4633 100644 --- a/apps/web/config/base.json +++ b/apps/web/config/base.json @@ -11,7 +11,6 @@ "allowedHosts": "auto" }, "flags": { - "secretsManager": false, "showPasswordless": false, "enableCipherKeyEncryption": false } diff --git a/apps/web/config/cloud.json b/apps/web/config/cloud.json index 6e5c65af1d..3faa292692 100644 --- a/apps/web/config/cloud.json +++ b/apps/web/config/cloud.json @@ -17,7 +17,6 @@ "proxyNotifications": "https://notifications.bitwarden.com" }, "flags": { - "secretsManager": true, "showPasswordless": true, "enableCipherKeyEncryption": false } diff --git a/apps/web/config/development.json b/apps/web/config/development.json index 97742aee3d..44391a7450 100644 --- a/apps/web/config/development.json +++ b/apps/web/config/development.json @@ -20,7 +20,6 @@ } ], "flags": { - "secretsManager": true, "showPasswordless": true, "enableCipherKeyEncryption": false }, diff --git a/apps/web/config/euprd.json b/apps/web/config/euprd.json index 4b6c9fa909..72f0c1857d 100644 --- a/apps/web/config/euprd.json +++ b/apps/web/config/euprd.json @@ -11,7 +11,6 @@ "buttonAction": "https://www.paypal.com/cgi-bin/webscr" }, "flags": { - "secretsManager": true, "showPasswordless": true, "enableCipherKeyEncryption": false } diff --git a/apps/web/config/euqa.json b/apps/web/config/euqa.json index 70caf3db62..5f74eb8829 100644 --- a/apps/web/config/euqa.json +++ b/apps/web/config/euqa.json @@ -21,7 +21,6 @@ } ], "flags": { - "secretsManager": true, "showPasswordless": true } } diff --git a/apps/web/config/qa.json b/apps/web/config/qa.json index 0ce5f3dc7f..ac36b10784 100644 --- a/apps/web/config/qa.json +++ b/apps/web/config/qa.json @@ -27,7 +27,6 @@ } ], "flags": { - "secretsManager": true, "showPasswordless": true, "enableCipherKeyEncryption": false } diff --git a/apps/web/config/selfhosted.json b/apps/web/config/selfhosted.json index e16df20ad5..7e916a1116 100644 --- a/apps/web/config/selfhosted.json +++ b/apps/web/config/selfhosted.json @@ -7,7 +7,6 @@ "port": 8081 }, "flags": { - "secretsManager": true, "showPasswordless": true, "enableCipherKeyEncryption": false } diff --git a/apps/web/config/usdev.json b/apps/web/config/usdev.json index 9b794d896d..af96a38c6a 100644 --- a/apps/web/config/usdev.json +++ b/apps/web/config/usdev.json @@ -5,7 +5,6 @@ "scim": "https://scim.usdev.bitwarden.pw" }, "flags": { - "secretsManager": true, "showPasswordless": true } } diff --git a/apps/web/package.json b/apps/web/package.json index 5b049dcb9d..99828bb543 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2024.3.1", + "version": "2024.4.0", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", @@ -8,7 +8,7 @@ "build:bit:watch": "webpack serve -c ../../bitwarden_license/bit-web/webpack.config.js", "build:bit:dev": "cross-env ENV=development npm run build:bit", "build:bit:dev:analyze": "cross-env LOGGING=false webpack -c ../../bitwarden_license/bit-web/webpack.config.js --profile --json > stats.json && npx webpack-bundle-analyzer stats.json build/", - "build:bit:dev:watch": "cross-env ENV=development npm run build:bit:watch", + "build:bit:dev:watch": "cross-env ENV=development NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:bit:watch", "build:bit:qa": "cross-env NODE_ENV=production ENV=qa npm run build:bit", "build:bit:euprd": "cross-env NODE_ENV=production ENV=euprd npm run build:bit", "build:bit:euqa": "cross-env NODE_ENV=production ENV=euqa npm run build:bit", diff --git a/apps/web/src/app/admin-console/organizations/members/people.component.ts b/apps/web/src/app/admin-console/organizations/members/people.component.ts index 8a303ddfe5..0da0ab79f0 100644 --- a/apps/web/src/app/admin-console/organizations/members/people.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/people.component.ts @@ -52,7 +52,6 @@ import { Collection } from "@bitwarden/common/vault/models/domain/collection"; import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response"; import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; -import { flagEnabled } from "../../../../utils/flags"; import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component"; import { BasePeopleComponent } from "../../common/base.people.component"; import { GroupService } from "../core"; @@ -148,9 +147,7 @@ export class PeopleComponent shareReplay({ refCount: true, bufferSize: 1 }), ); - this.canUseSecretsManager$ = organization$.pipe( - map((org) => org.useSecretsManager && flagEnabled("secretsManager")), - ); + this.canUseSecretsManager$ = organization$.pipe(map((org) => org.useSecretsManager)); const policies$ = organization$.pipe( switchMap((organization) => { diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.component.html b/apps/web/src/app/layouts/product-switcher/product-switcher.component.html index 449557b6f4..c9956f05e4 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.component.html +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.component.html @@ -1,10 +1,8 @@ - - - - + + diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts index a461785c31..eff5f08702 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts @@ -1,16 +1,11 @@ import { AfterViewInit, ChangeDetectorRef, Component, Input } from "@angular/core"; import { IconButtonType } from "@bitwarden/components/src/icon-button/icon-button.component"; - -import { flagEnabled } from "../../../utils/flags"; - @Component({ selector: "product-switcher", templateUrl: "./product-switcher.component.html", }) export class ProductSwitcherComponent implements AfterViewInit { - protected isEnabled = flagEnabled("secretsManager"); - /** * Passed to the product switcher's `bitIconButton` */ diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index 902159147e..9d3c25d5cc 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -7,14 +7,13 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -/* eslint-disable-next-line @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = { - secretsManager?: boolean; showPasswordless?: boolean; } & SharedFlags; // required to avoid linting errors when there are no flags -/* eslint-disable-next-line @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts index f9ddcdad78..55dc2f8b71 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts @@ -2,7 +2,6 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/auth/guards"; -import { buildFlaggedRoute } from "@bitwarden/web-vault/app/oss-routing.module"; import { organizationEnabledGuard } from "./guards/sm-org-enabled.guard"; import { canActivateSM } from "./guards/sm.guard"; @@ -17,7 +16,7 @@ import { OrgSuspendedComponent } from "./shared/org-suspended.component"; import { TrashModule } from "./trash/trash.module"; const routes: Routes = [ - buildFlaggedRoute("secretsManager", { + { path: "", children: [ { @@ -86,7 +85,7 @@ const routes: Routes = [ ], }, ], - }), + }, ]; @NgModule({ diff --git a/libs/common/src/platform/misc/flags.ts b/libs/common/src/platform/misc/flags.ts index c1f8d7757b..cc463b1060 100644 --- a/libs/common/src/platform/misc/flags.ts +++ b/libs/common/src/platform/misc/flags.ts @@ -1,5 +1,5 @@ // required to avoid linting errors when there are no flags -/* eslint-disable @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type SharedFlags = { multithreadDecryption: boolean; showPasswordless?: boolean; @@ -7,7 +7,7 @@ export type SharedFlags = { }; // required to avoid linting errors when there are no flags -/* eslint-disable @typescript-eslint/ban-types */ +// eslint-disable-next-line @typescript-eslint/ban-types export type SharedDevFlags = { noopNotifications: boolean; }; 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", diff --git a/package-lock.json b/package-lock.json index 4ec09e2b7c..540bc145e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -247,7 +247,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2024.3.1" + "version": "2024.4.0" }, "libs/admin-console": { "name": "@bitwarden/admin-console",