diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 23f4bd35f1..f924c5c98e 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -164,6 +164,10 @@ jobs: run: npm run dist:mv3 working-directory: browser-source/apps/browser + - name: Build Chrome Manifest v3 Beta + run: npm run dist:chrome:beta + working-directory: browser-source/apps/browser + - name: Gulp run: gulp ci working-directory: browser-source/apps/browser @@ -196,6 +200,13 @@ jobs: path: browser-source/apps/browser/dist/dist-chrome-mv3.zip if-no-files-found: error + - name: Upload Chrome MV3 Beta artifact (DO NOT USE FOR PROD) + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: DO-NOT-USE-FOR-PROD-dist-chrome-MV3-beta-${{ env._BUILD_NUMBER }}.zip + path: browser-source/apps/browser/dist/dist-chrome-mv3-beta.zip + if-no-files-found: error + - name: Upload Firefox artifact uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: diff --git a/apps/browser/gulpfile.js b/apps/browser/gulpfile.js index 6a0980fc27..d5b29ffc38 100644 --- a/apps/browser/gulpfile.js +++ b/apps/browser/gulpfile.js @@ -35,6 +35,9 @@ function buildString() { if (process.env.MANIFEST_VERSION) { build = `-mv${process.env.MANIFEST_VERSION}`; } + if (process.env.BETA_BUILD === "1") { + build += "-beta"; + } if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") { build = `-${process.env.BUILD_NUMBER}`; } @@ -65,6 +68,9 @@ function distFirefox() { manifest.optional_permissions = manifest.optional_permissions.filter( (permission) => permission !== "privacy", ); + if (process.env.BETA_BUILD === "1") { + manifest = applyBetaLabels(manifest); + } return manifest; }); } @@ -72,6 +78,9 @@ function distFirefox() { function distOpera() { return dist("opera", (manifest) => { delete manifest.applications; + if (process.env.BETA_BUILD === "1") { + manifest = applyBetaLabels(manifest); + } return manifest; }); } @@ -81,6 +90,9 @@ function distChrome() { delete manifest.applications; delete manifest.sidebar_action; delete manifest.commands._execute_sidebar_action; + if (process.env.BETA_BUILD === "1") { + manifest = applyBetaLabels(manifest); + } return manifest; }); } @@ -90,6 +102,9 @@ function distEdge() { delete manifest.applications; delete manifest.sidebar_action; delete manifest.commands._execute_sidebar_action; + if (process.env.BETA_BUILD === "1") { + manifest = applyBetaLabels(manifest); + } return manifest; }); } @@ -210,6 +225,9 @@ async function safariCopyBuild(source, dest) { delete manifest.commands._execute_sidebar_action; delete manifest.optional_permissions; manifest.permissions.push("nativeMessaging"); + if (process.env.BETA_BUILD === "1") { + manifest = applyBetaLabels(manifest); + } return manifest; }), ), @@ -235,6 +253,19 @@ async function ciCoverage(cb) { .pipe(gulp.dest(paths.coverage)); } +function applyBetaLabels(manifest) { + manifest.name = "Bitwarden Password Manager BETA"; + manifest.short_name = "Bitwarden BETA"; + manifest.description = "THIS EXTENSION IS FOR BETA TESTING BITWARDEN."; + if (process.env.GITHUB_RUN_ID) { + manifest.version_name = `${manifest.version} beta - ${process.env.GITHUB_SHA.slice(0, 8)}`; + manifest.version = `${manifest.version}.${parseInt(process.env.GITHUB_RUN_ID.slice(-4))}`; + } else { + manifest.version = `${manifest.version}.0`; + } + return manifest; +} + exports["dist:firefox"] = distFirefox; exports["dist:chrome"] = distChrome; exports["dist:opera"] = distOpera; diff --git a/apps/browser/package.json b/apps/browser/package.json index 506f19f279..580acfc3d0 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -7,10 +7,14 @@ "build:watch": "webpack --watch", "build:watch:mv3": "cross-env MANIFEST_VERSION=3 webpack --watch", "build:prod": "cross-env NODE_ENV=production webpack", + "build:prod:beta": "cross-env BETA_BUILD=1 NODE_ENV=production webpack", "build:prod:watch": "cross-env NODE_ENV=production webpack --watch", "dist": "npm run build:prod && gulp dist", + "dist:beta": "npm run build:prod:beta && cross-env BETA_BUILD=1 gulp dist", "dist:mv3": "cross-env MANIFEST_VERSION=3 npm run build:prod && cross-env MANIFEST_VERSION=3 gulp dist", + "dist:mv3:beta": "cross-env MANIFEST_VERSION=3 npm run build:prod:beta && cross-env MANIFEST_VERSION=3 BETA_BUILD=1 gulp dist", "dist:chrome": "npm run build:prod && gulp dist:chrome", + "dist:chrome:beta": "cross-env MANIFEST_VERSION=3 npm run build:prod:beta && cross-env MANIFEST_VERSION=3 BETA_BUILD=1 gulp dist:chrome", "dist:firefox": "npm run build:prod && gulp dist:firefox", "dist:opera": "npm run build:prod && gulp dist:opera", "dist:safari": "npm run build:prod && gulp dist:safari", diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 758c226bc3..82fda90489 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -490,7 +490,7 @@ export default class MainBackground { this.accountService, this.singleUserStateProvider, ); - this.derivedStateProvider = new BackgroundDerivedStateProvider(storageServiceProvider); + this.derivedStateProvider = new BackgroundDerivedStateProvider(); this.stateProvider = new DefaultStateProvider( this.activeUserStateProvider, this.singleUserStateProvider, diff --git a/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts b/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts index 4025d01950..3c3900144b 100644 --- a/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts +++ b/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts @@ -3,15 +3,10 @@ import { DerivedStateProvider } from "@bitwarden/common/platform/state"; import { BackgroundDerivedStateProvider } from "../../state/background-derived-state.provider"; import { CachedServices, FactoryOptions, factory } from "./factory-options"; -import { - StorageServiceProviderInitOptions, - storageServiceProviderFactory, -} from "./storage-service-provider.factory"; type DerivedStateProviderFactoryOptions = FactoryOptions; -export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions & - StorageServiceProviderInitOptions; +export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions; export async function derivedStateProviderFactory( cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices, @@ -21,7 +16,6 @@ export async function derivedStateProviderFactory( cache, "derivedStateProvider", opts, - async () => - new BackgroundDerivedStateProvider(await storageServiceProviderFactory(cache, opts)), + async () => new BackgroundDerivedStateProvider(), ); } diff --git a/apps/browser/src/platform/state/background-derived-state.provider.ts b/apps/browser/src/platform/state/background-derived-state.provider.ts index 834ae59249..cbc5a34b37 100644 --- a/apps/browser/src/platform/state/background-derived-state.provider.ts +++ b/apps/browser/src/platform/state/background-derived-state.provider.ts @@ -1,9 +1,5 @@ import { Observable } from "rxjs"; -import { - AbstractStorageService, - ObservableStorageService, -} from "@bitwarden/common/platform/abstractions/storage.service"; import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- extending this class for this client import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider"; @@ -16,14 +12,11 @@ export class BackgroundDerivedStateProvider extends DefaultDerivedStateProvider parentState$: Observable, deriveDefinition: DeriveDefinition, dependencies: TDeps, - storageLocation: [string, AbstractStorageService & ObservableStorageService], ): DerivedState { - const [location, storageService] = storageLocation; return new BackgroundDerivedState( parentState$, deriveDefinition, - storageService, - deriveDefinition.buildCacheKey(location), + deriveDefinition.buildCacheKey(), dependencies, ); } diff --git a/apps/browser/src/platform/state/background-derived-state.ts b/apps/browser/src/platform/state/background-derived-state.ts index c62795acdc..61768cb970 100644 --- a/apps/browser/src/platform/state/background-derived-state.ts +++ b/apps/browser/src/platform/state/background-derived-state.ts @@ -1,10 +1,7 @@ -import { Observable, Subscription } from "rxjs"; +import { Observable, Subscription, concatMap } from "rxjs"; import { Jsonify } from "type-fest"; -import { - AbstractStorageService, - ObservableStorageService, -} from "@bitwarden/common/platform/abstractions/storage.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DeriveDefinition } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- extending this class for this client import { DefaultDerivedState } from "@bitwarden/common/platform/state/implementations/default-derived-state"; @@ -22,11 +19,10 @@ export class BackgroundDerivedState< constructor( parentState$: Observable, deriveDefinition: DeriveDefinition, - memoryStorage: AbstractStorageService & ObservableStorageService, portName: string, dependencies: TDeps, ) { - super(parentState$, deriveDefinition, memoryStorage, dependencies); + super(parentState$, deriveDefinition, dependencies); // listen for foreground derived states to connect BrowserApi.addListener(chrome.runtime.onConnect, (port) => { @@ -42,7 +38,20 @@ export class BackgroundDerivedState< }); port.onMessage.addListener(listenerCallback); - const stateSubscription = this.state$.subscribe(); + const stateSubscription = this.state$ + .pipe( + concatMap(async (state) => { + await this.sendMessage( + { + action: "nextState", + data: JSON.stringify(state), + id: Utils.newGuid(), + }, + port, + ); + }), + ) + .subscribe(); this.portSubscriptions.set(port, stateSubscription); }); diff --git a/apps/browser/src/platform/state/derived-state-interactions.spec.ts b/apps/browser/src/platform/state/derived-state-interactions.spec.ts index a5df01bc98..823c071a4c 100644 --- a/apps/browser/src/platform/state/derived-state-interactions.spec.ts +++ b/apps/browser/src/platform/state/derived-state-interactions.spec.ts @@ -4,14 +4,13 @@ */ import { NgZone } from "@angular/core"; -import { FakeStorageService } from "@bitwarden/common/../spec/fake-storage.service"; -import { awaitAsync, trackEmissions } from "@bitwarden/common/../spec/utils"; import { mock } from "jest-mock-extended"; import { Subject, firstValueFrom } from "rxjs"; import { DeriveDefinition } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- needed to define a derive definition import { StateDefinition } from "@bitwarden/common/platform/state/state-definition"; +import { awaitAsync, trackEmissions, ObservableTracker } from "@bitwarden/common/spec"; import { mockPorts } from "../../../spec/mock-port.spec-util"; @@ -22,6 +21,7 @@ const stateDefinition = new StateDefinition("test", "memory"); const deriveDefinition = new DeriveDefinition(stateDefinition, "test", { derive: (dateString: string) => (dateString == null ? null : new Date(dateString)), deserializer: (dateString: string) => (dateString == null ? null : new Date(dateString)), + cleanupDelayMs: 1000, }); // Mock out the runInsideAngular operator so we don't have to deal with zone.js @@ -35,7 +35,6 @@ describe("foreground background derived state interactions", () => { let foreground: ForegroundDerivedState; let background: BackgroundDerivedState>; let parentState$: Subject; - let memoryStorage: FakeStorageService; const initialParent = "2020-01-01"; const ngZone = mock(); const portName = "testPort"; @@ -43,16 +42,9 @@ describe("foreground background derived state interactions", () => { beforeEach(() => { mockPorts(); parentState$ = new Subject(); - memoryStorage = new FakeStorageService(); - background = new BackgroundDerivedState( - parentState$, - deriveDefinition, - memoryStorage, - portName, - {}, - ); - foreground = new ForegroundDerivedState(deriveDefinition, memoryStorage, portName, ngZone); + background = new BackgroundDerivedState(parentState$, deriveDefinition, portName, {}); + foreground = new ForegroundDerivedState(deriveDefinition, portName, ngZone); }); afterEach(() => { @@ -72,21 +64,13 @@ describe("foreground background derived state interactions", () => { }); it("should initialize a late-connected foreground", async () => { - const newForeground = new ForegroundDerivedState( - deriveDefinition, - memoryStorage, - portName, - ngZone, - ); - const backgroundEmissions = trackEmissions(background.state$); + const newForeground = new ForegroundDerivedState(deriveDefinition, portName, ngZone); + const backgroundTracker = new ObservableTracker(background.state$); parentState$.next(initialParent); - await awaitAsync(); + const foregroundTracker = new ObservableTracker(newForeground.state$); - const foregroundEmissions = trackEmissions(newForeground.state$); - await awaitAsync(10); - - expect(backgroundEmissions).toEqual([new Date(initialParent)]); - expect(foregroundEmissions).toEqual([new Date(initialParent)]); + expect(await backgroundTracker.expectEmission()).toEqual(new Date(initialParent)); + expect(await foregroundTracker.expectEmission()).toEqual(new Date(initialParent)); }); describe("forceValue", () => { diff --git a/apps/browser/src/platform/state/foreground-derived-state.provider.ts b/apps/browser/src/platform/state/foreground-derived-state.provider.ts index f8b7b2e708..8b8d82b914 100644 --- a/apps/browser/src/platform/state/foreground-derived-state.provider.ts +++ b/apps/browser/src/platform/state/foreground-derived-state.provider.ts @@ -1,11 +1,6 @@ import { NgZone } from "@angular/core"; import { Observable } from "rxjs"; -import { - AbstractStorageService, - ObservableStorageService, -} from "@bitwarden/common/platform/abstractions/storage.service"; -import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- extending this class for this client import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider"; @@ -14,23 +9,17 @@ import { DerivedStateDependencies } from "@bitwarden/common/src/types/state"; import { ForegroundDerivedState } from "./foreground-derived-state"; export class ForegroundDerivedStateProvider extends DefaultDerivedStateProvider { - constructor( - storageServiceProvider: StorageServiceProvider, - private ngZone: NgZone, - ) { - super(storageServiceProvider); + constructor(private ngZone: NgZone) { + super(); } override buildDerivedState( _parentState$: Observable, deriveDefinition: DeriveDefinition, _dependencies: TDeps, - storageLocation: [string, AbstractStorageService & ObservableStorageService], ): DerivedState { - const [location, storageService] = storageLocation; return new ForegroundDerivedState( deriveDefinition, - storageService, - deriveDefinition.buildCacheKey(location), + deriveDefinition.buildCacheKey(), this.ngZone, ); } diff --git a/apps/browser/src/platform/state/foreground-derived-state.spec.ts b/apps/browser/src/platform/state/foreground-derived-state.spec.ts index 2c29f39bc1..ee224540c1 100644 --- a/apps/browser/src/platform/state/foreground-derived-state.spec.ts +++ b/apps/browser/src/platform/state/foreground-derived-state.spec.ts @@ -1,11 +1,5 @@ -/** - * need to update test environment so structuredClone works appropriately - * @jest-environment ../../libs/shared/test.environment.ts - */ - import { NgZone } from "@angular/core"; -import { awaitAsync, trackEmissions } from "@bitwarden/common/../spec"; -import { FakeStorageService } from "@bitwarden/common/../spec/fake-storage.service"; +import { awaitAsync } from "@bitwarden/common/../spec"; import { mock } from "jest-mock-extended"; import { DeriveDefinition } from "@bitwarden/common/platform/state"; @@ -32,15 +26,12 @@ jest.mock("../browser/run-inside-angular.operator", () => { describe("ForegroundDerivedState", () => { let sut: ForegroundDerivedState; - let memoryStorage: FakeStorageService; const portName = "testPort"; const ngZone = mock(); beforeEach(() => { - memoryStorage = new FakeStorageService(); - memoryStorage.internalUpdateValuesRequireDeserialization(true); mockPorts(); - sut = new ForegroundDerivedState(deriveDefinition, memoryStorage, portName, ngZone); + sut = new ForegroundDerivedState(deriveDefinition, portName, ngZone); }); afterEach(() => { @@ -67,18 +58,4 @@ describe("ForegroundDerivedState", () => { expect(disconnectSpy).toHaveBeenCalled(); expect(sut["port"]).toBeNull(); }); - - it("should emit when the memory storage updates", async () => { - const dateString = "2020-01-01"; - const emissions = trackEmissions(sut.state$); - - await memoryStorage.save(deriveDefinition.storageKey, { - derived: true, - value: new Date(dateString), - }); - - await awaitAsync(); - - expect(emissions).toEqual([new Date(dateString)]); - }); }); diff --git a/apps/browser/src/platform/state/foreground-derived-state.ts b/apps/browser/src/platform/state/foreground-derived-state.ts index b9dda763df..6abe363876 100644 --- a/apps/browser/src/platform/state/foreground-derived-state.ts +++ b/apps/browser/src/platform/state/foreground-derived-state.ts @@ -6,19 +6,14 @@ import { filter, firstValueFrom, map, - merge, of, share, switchMap, tap, timer, } from "rxjs"; -import { Jsonify, JsonObject } from "type-fest"; +import { Jsonify } from "type-fest"; -import { - AbstractStorageService, - ObservableStorageService, -} from "@bitwarden/common/platform/abstractions/storage.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state"; import { DerivedStateDependencies } from "@bitwarden/common/types/state"; @@ -27,41 +22,28 @@ import { fromChromeEvent } from "../browser/from-chrome-event"; import { runInsideAngular } from "../browser/run-inside-angular.operator"; export class ForegroundDerivedState implements DerivedState { - private storageKey: string; private port: chrome.runtime.Port; private backgroundResponses$: Observable; state$: Observable; constructor( private deriveDefinition: DeriveDefinition, - private memoryStorage: AbstractStorageService & ObservableStorageService, private portName: string, private ngZone: NgZone, ) { - this.storageKey = deriveDefinition.storageKey; - - const initialStorageGet$ = defer(() => { - return this.getStoredValue(); - }).pipe( - filter((s) => s.derived), - map((s) => s.value), - ); - - const latestStorage$ = this.memoryStorage.updates$.pipe( - filter((s) => s.key === this.storageKey), - switchMap(async (storageUpdate) => { - if (storageUpdate.updateType === "remove") { - return null; - } - - return await this.getStoredValue(); - }), - filter((s) => s.derived), - map((s) => s.value), - ); + const latestValueFromPort$ = (port: chrome.runtime.Port) => { + return fromChromeEvent(port.onMessage).pipe( + map(([message]) => message as DerivedStateMessage), + filter((message) => message.originator === "background" && message.action === "nextState"), + map((message) => { + const json = JSON.parse(message.data) as Jsonify; + return this.deriveDefinition.deserialize(json); + }), + ); + }; this.state$ = defer(() => of(this.initializePort())).pipe( - switchMap(() => merge(initialStorageGet$, latestStorage$)), + switchMap(() => latestValueFromPort$(this.port)), share({ connector: () => new ReplaySubject(1), resetOnRefCountZero: () => @@ -130,28 +112,4 @@ export class ForegroundDerivedState implements DerivedState { this.port = null; this.backgroundResponses$ = null; } - - protected async getStoredValue(): Promise<{ derived: boolean; value: TTo | null }> { - if (this.memoryStorage.valuesRequireDeserialization) { - const storedJson = await this.memoryStorage.get< - Jsonify<{ derived: true; value: JsonObject }> - >(this.storageKey); - - if (!storedJson?.derived) { - return { derived: false, value: null }; - } - - const value = this.deriveDefinition.deserialize(storedJson.value as any); - - return { derived: true, value }; - } else { - const stored = await this.memoryStorage.get<{ derived: true; value: TTo }>(this.storageKey); - - if (!stored?.derived) { - return { derived: false, value: null }; - } - - return { derived: true, value: stored.value }; - } - } } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 052e341004..5944783232 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -473,7 +473,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: DerivedStateProvider, useClass: ForegroundDerivedStateProvider, - deps: [StorageServiceProvider, NgZone], + deps: [NgZone], }), safeProvider({ provide: AutofillSettingsServiceAbstraction, diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 45c394e912..85fba27089 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -314,7 +314,7 @@ export class Main { this.singleUserStateProvider, ); - this.derivedStateProvider = new DefaultDerivedStateProvider(storageServiceProvider); + this.derivedStateProvider = new DefaultDerivedStateProvider(); this.stateProvider = new DefaultStateProvider( this.activeUserStateProvider, diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index da4c14b4aa..0766af90b6 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -157,7 +157,7 @@ export class Main { activeUserStateProvider, singleUserStateProvider, globalStateProvider, - new DefaultDerivedStateProvider(storageServiceProvider), + new DefaultDerivedStateProvider(), ); this.environmentService = new DefaultEnvironmentService(stateProvider, accountService); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index c7b27a25c2..b28e475cb2 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1047,7 +1047,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: DerivedStateProvider, useClass: DefaultDerivedStateProvider, - deps: [StorageServiceProvider], + deps: [], }), safeProvider({ provide: StateProvider, diff --git a/libs/common/spec/fake-state-provider.ts b/libs/common/spec/fake-state-provider.ts index 306ae00c21..2078fe3abd 100644 --- a/libs/common/spec/fake-state-provider.ts +++ b/libs/common/spec/fake-state-provider.ts @@ -249,11 +249,11 @@ export class FakeDerivedStateProvider implements DerivedStateProvider { deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { - let result = this.states.get(deriveDefinition.buildCacheKey("memory")) as DerivedState; + let result = this.states.get(deriveDefinition.buildCacheKey()) as DerivedState; if (result == null) { result = new FakeDerivedState(parentState$, deriveDefinition, dependencies); - this.states.set(deriveDefinition.buildCacheKey("memory"), result); + this.states.set(deriveDefinition.buildCacheKey(), result); } return result; } diff --git a/libs/common/spec/index.ts b/libs/common/spec/index.ts index 6e9af8400e..90ee121896 100644 --- a/libs/common/spec/index.ts +++ b/libs/common/spec/index.ts @@ -5,3 +5,4 @@ export * from "./fake-state-provider"; export * from "./fake-state"; export * from "./fake-account-service"; export * from "./fake-storage.service"; +export * from "./observable-tracker"; diff --git a/libs/common/spec/observable-tracker.ts b/libs/common/spec/observable-tracker.ts index a6f3e6a879..588d3c3365 100644 --- a/libs/common/spec/observable-tracker.ts +++ b/libs/common/spec/observable-tracker.ts @@ -16,9 +16,11 @@ export class ObservableTracker { /** * Awaits the next emission from the observable, or throws if the timeout is exceeded * @param msTimeout The maximum time to wait for another emission before throwing + * @returns The next emission from the observable + * @throws If the timeout is exceeded */ - async expectEmission(msTimeout = 50) { - await firstValueFrom( + async expectEmission(msTimeout = 50): Promise { + return await firstValueFrom( this.observable.pipe( timeout({ first: msTimeout, diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index 83a2da5709..326ed5e8e8 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -10,7 +10,7 @@ import { CryptoService } from "../abstractions/crypto.service"; import { EncryptService } from "../abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; -const nodeURL = typeof window === "undefined" ? require("url") : null; +const nodeURL = typeof self === "undefined" ? require("url") : null; declare global { /* eslint-disable-next-line no-var */ diff --git a/libs/common/src/platform/state/derive-definition.ts b/libs/common/src/platform/state/derive-definition.ts index 9cb5eff3e8..8f62d3a342 100644 --- a/libs/common/src/platform/state/derive-definition.ts +++ b/libs/common/src/platform/state/derive-definition.ts @@ -171,8 +171,8 @@ export class DeriveDefinition> = {}; - constructor(protected storageServiceProvider: StorageServiceProvider) {} + constructor() {} get( parentState$: Observable, deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { - // TODO: we probably want to support optional normal memory storage for browser - const [location, storageService] = this.storageServiceProvider.get("memory", { - browser: "memory-large-object", - }); - const cacheKey = deriveDefinition.buildCacheKey(location); + const cacheKey = deriveDefinition.buildCacheKey(); const existingDerivedState = this.cache[cacheKey]; if (existingDerivedState != null) { // I have to cast out of the unknown generic but this should be safe if rules @@ -34,10 +25,7 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { return existingDerivedState as DefaultDerivedState; } - const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies, [ - location, - storageService, - ]); + const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies); this.cache[cacheKey] = newDerivedState; return newDerivedState; } @@ -46,13 +34,7 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { parentState$: Observable, deriveDefinition: DeriveDefinition, dependencies: TDeps, - storageLocation: [string, AbstractStorageService & ObservableStorageService], ): DerivedState { - return new DefaultDerivedState( - parentState$, - deriveDefinition, - storageLocation[1], - dependencies, - ); + return new DefaultDerivedState(parentState$, deriveDefinition, dependencies); } } diff --git a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts index e3b1587e3a..7e8d76bd20 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts @@ -5,7 +5,6 @@ import { Subject, firstValueFrom } from "rxjs"; import { awaitAsync, trackEmissions } from "../../../../spec"; -import { FakeStorageService } from "../../../../spec/fake-storage.service"; import { DeriveDefinition } from "../derive-definition"; import { StateDefinition } from "../state-definition"; @@ -29,7 +28,6 @@ const deriveDefinition = new DeriveDefinition( describe("DefaultDerivedState", () => { let parentState$: Subject; - let memoryStorage: FakeStorageService; let sut: DefaultDerivedState; const deps = { date: new Date(), @@ -38,8 +36,7 @@ describe("DefaultDerivedState", () => { beforeEach(() => { callCount = 0; parentState$ = new Subject(); - memoryStorage = new FakeStorageService(); - sut = new DefaultDerivedState(parentState$, deriveDefinition, memoryStorage, deps); + sut = new DefaultDerivedState(parentState$, deriveDefinition, deps); }); afterEach(() => { @@ -66,71 +63,33 @@ describe("DefaultDerivedState", () => { expect(callCount).toBe(1); }); - it("should store the derived state in memory", async () => { - const dateString = "2020-01-01"; - trackEmissions(sut.state$); - parentState$.next(dateString); - await awaitAsync(); - - expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( - derivedValue(new Date(dateString)), - ); - const calls = memoryStorage.mock.save.mock.calls; - expect(calls.length).toBe(1); - expect(calls[0][0]).toBe(deriveDefinition.storageKey); - expect(calls[0][1]).toEqual(derivedValue(new Date(dateString))); - }); - describe("forceValue", () => { const initialParentValue = "2020-01-01"; const forced = new Date("2020-02-02"); let emissions: Date[]; - describe("without observers", () => { - beforeEach(async () => { - parentState$.next(initialParentValue); - await awaitAsync(); - }); - - it("should store the forced value", async () => { - await sut.forceValue(forced); - expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( - derivedValue(forced), - ); - }); + beforeEach(async () => { + emissions = trackEmissions(sut.state$); + parentState$.next(initialParentValue); + await awaitAsync(); }); - describe("with observers", () => { - beforeEach(async () => { - emissions = trackEmissions(sut.state$); - parentState$.next(initialParentValue); - await awaitAsync(); - }); + it("should force the value", async () => { + await sut.forceValue(forced); + expect(emissions).toEqual([new Date(initialParentValue), forced]); + }); - it("should store the forced value", async () => { - await sut.forceValue(forced); - expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( - derivedValue(forced), - ); - }); + it("should only force the value once", async () => { + await sut.forceValue(forced); - it("should force the value", async () => { - await sut.forceValue(forced); - expect(emissions).toEqual([new Date(initialParentValue), forced]); - }); + parentState$.next(initialParentValue); + await awaitAsync(); - it("should only force the value once", async () => { - await sut.forceValue(forced); - - parentState$.next(initialParentValue); - await awaitAsync(); - - expect(emissions).toEqual([ - new Date(initialParentValue), - forced, - new Date(initialParentValue), - ]); - }); + expect(emissions).toEqual([ + new Date(initialParentValue), + forced, + new Date(initialParentValue), + ]); }); }); @@ -148,42 +107,6 @@ describe("DefaultDerivedState", () => { expect(parentState$.observed).toBe(false); }); - it("should clear state after cleanup", async () => { - const subscription = sut.state$.subscribe(); - parentState$.next(newDate); - await awaitAsync(); - - expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( - derivedValue(new Date(newDate)), - ); - - subscription.unsubscribe(); - // Wait for cleanup - await awaitAsync(cleanupDelayMs * 2); - - expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toBeUndefined(); - }); - - it("should not clear state after cleanup if clearOnCleanup is false", async () => { - deriveDefinition.options.clearOnCleanup = false; - - const subscription = sut.state$.subscribe(); - parentState$.next(newDate); - await awaitAsync(); - - expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( - derivedValue(new Date(newDate)), - ); - - subscription.unsubscribe(); - // Wait for cleanup - await awaitAsync(cleanupDelayMs * 2); - - expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( - derivedValue(new Date(newDate)), - ); - }); - it("should not cleanup if there are still subscribers", async () => { const subscription1 = sut.state$.subscribe(); const sub2Emissions: Date[] = []; @@ -260,7 +183,3 @@ describe("DefaultDerivedState", () => { }); }); }); - -function derivedValue(value: T) { - return { derived: true, value }; -} diff --git a/libs/common/src/platform/state/implementations/default-derived-state.ts b/libs/common/src/platform/state/implementations/default-derived-state.ts index 657df2bfdf..9abb299809 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.ts @@ -1,10 +1,6 @@ import { Observable, ReplaySubject, Subject, concatMap, merge, share, timer } from "rxjs"; import { DerivedStateDependencies } from "../../../types/state"; -import { - AbstractStorageService, - ObservableStorageService, -} from "../../abstractions/storage.service"; import { DeriveDefinition } from "../derive-definition"; import { DerivedState } from "../derived-state"; @@ -22,7 +18,6 @@ export class DefaultDerivedState, protected deriveDefinition: DeriveDefinition, - private memoryStorage: AbstractStorageService & ObservableStorageService, private dependencies: TDeps, ) { this.storageKey = deriveDefinition.storageKey; @@ -34,7 +29,6 @@ export class DefaultDerivedState { return new ReplaySubject(1); }, - resetOnRefCountZero: () => - timer(this.deriveDefinition.cleanupDelayMs).pipe( - concatMap(async () => { - if (this.deriveDefinition.clearOnCleanup) { - await this.memoryStorage.remove(this.storageKey); - } - return true; - }), - ), + resetOnRefCountZero: () => timer(this.deriveDefinition.cleanupDelayMs), }), ); } async forceValue(value: TTo) { - await this.storeValue(value); this.forcedValueSubject.next(value); return value; } - - private storeValue(value: TTo) { - return this.memoryStorage.save(this.storageKey, { derived: true, value }); - } } diff --git a/package-lock.json b/package-lock.json index ad27ada66b..ba2f4d9f6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,7 +120,7 @@ "@typescript-eslint/eslint-plugin": "7.4.0", "@typescript-eslint/parser": "7.4.0", "@webcomponents/custom-elements": "1.6.0", - "autoprefixer": "10.4.18", + "autoprefixer": "10.4.19", "base64-loader": "1.0.0", "chromatic": "10.9.6", "concurrently": "8.2.2", @@ -12930,9 +12930,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.18", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", - "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "dev": true, "funding": [ { @@ -12950,7 +12950,7 @@ ], "dependencies": { "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001591", + "caniuse-lite": "^1.0.30001599", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", diff --git a/package.json b/package.json index 09065b234e..1a547e0a85 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@typescript-eslint/eslint-plugin": "7.4.0", "@typescript-eslint/parser": "7.4.0", "@webcomponents/custom-elements": "1.6.0", - "autoprefixer": "10.4.18", + "autoprefixer": "10.4.19", "base64-loader": "1.0.0", "chromatic": "10.9.6", "concurrently": "8.2.2",