1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

Rework derived state (#7290)

* Remove derived state from state classes

* Create provider for derived state

Derived state is automatically stored to memory storage, but can be derived from any observable.

* Fixup state provider method definitions

* Test `DefaultDerivedState`

* remove implementation notes

* Write docs for derived state

* fixup derived state provider types

* Implement buffered delayUntil operator

* Move state types to a common module

* Move mock ports to centra location

* Alias DerivedStateDependency type

* Add dependencies to browser

* Prefer internal rxjs operators for ref counting

* WIP

* Ensure complete on subjects

* Foreground/background messaging for browser

Defers work for browser to the background

* Test foreground port behaviors

* Inject foreground and background derived state services

* remove unnecessary class field

* Adhere to required options

* Add dderived state to CLI

* Prefer type definition in type parameters to options

* Prefer instance method

* Implements factory methods for common uses

* Remove nothing test

* Remove share subject reference

Share manages connector subjects internally and will reuse them until
refcount is 0 and the cleanup time has passed. Saving our own reference
just risks memory leaks without real testability benefits.

* Fix interaction state
This commit is contained in:
Matt Gibson
2024-01-04 14:47:49 -05:00
committed by GitHub
parent 8e46ef1ae5
commit 06affa9654
33 changed files with 1182 additions and 79 deletions

View File

@@ -0,0 +1,29 @@
import { mockDeep } from "jest-mock-extended";
/**
* Mocks a chrome.runtime.Port set up to send messages through `postMessage` to `onMessage.addListener` callbacks.
* @param name - The name of the port.
* @param immediateOnConnectExecution - Whether to immediately execute the onConnect callbacks against the new port.
* Defaults to false. If true, the creator of the port will not have had a chance to set up listeners yet.
* @returns a mock chrome.runtime.Port
*/
export function mockPorts() {
// notify listeners of a new port
(chrome.runtime.connect as jest.Mock).mockImplementation((portInfo) => {
const port = mockDeep<chrome.runtime.Port>();
port.name = portInfo.name;
// set message broadcast
(port.postMessage as jest.Mock).mockImplementation((message) => {
(port.onMessage.addListener as jest.Mock).mock.calls.forEach(([callbackFn]) => {
callbackFn(message, port);
});
});
(chrome.runtime.onConnect.addListener as jest.Mock).mock.calls.forEach(([callbackFn]) => {
callbackFn(port);
});
return port;
});
}

View File

@@ -65,18 +65,17 @@ import { SystemService } from "@bitwarden/common/platform/services/system.servic
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
import {
ActiveUserStateProvider,
DerivedStateProvider,
GlobalStateProvider,
SingleUserStateProvider,
StateProvider,
} from "@bitwarden/common/platform/state";
// eslint-disable-next-line import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
// eslint-disable-next-line import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
// eslint-disable-next-line import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
// eslint-disable-next-line import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
/* eslint-enable import/no-restricted-paths */
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
import { ApiService } from "@bitwarden/common/services/api.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
@@ -162,6 +161,7 @@ import BrowserPlatformUtilsService from "../platform/services/browser-platform-u
import { BrowserStateService } from "../platform/services/browser-state.service";
import { KeyGenerationService } from "../platform/services/key-generation.service";
import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service";
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
import { BrowserSendService } from "../services/browser-send.service";
import { BrowserSettingsService } from "../services/browser-settings.service";
@@ -248,6 +248,7 @@ export default class MainBackground {
globalStateProvider: GlobalStateProvider;
singleUserStateProvider: SingleUserStateProvider;
activeUserStateProvider: ActiveUserStateProvider;
derivedStateProvider: DerivedStateProvider;
stateProvider: StateProvider;
fido2Service: Fido2ServiceAbstraction;
@@ -335,10 +336,14 @@ export default class MainBackground {
this.memoryStorageService as BackgroundMemoryStorageService,
this.storageService as BrowserLocalStorageService,
);
this.derivedStateProvider = new BackgroundDerivedStateProvider(
this.memoryStorageService as BackgroundMemoryStorageService,
);
this.stateProvider = new DefaultStateProvider(
this.activeUserStateProvider,
this.singleUserStateProvider,
this.globalStateProvider,
this.derivedStateProvider,
);
this.stateService = new BrowserStateService(
this.storageService,

View File

@@ -0,0 +1,27 @@
import { DerivedStateProvider } from "@bitwarden/common/platform/state";
import { BackgroundDerivedStateProvider } from "../../state/background-derived-state.provider";
import { CachedServices, FactoryOptions, factory } from "./factory-options";
import {
MemoryStorageServiceInitOptions,
observableMemoryStorageServiceFactory,
} from "./storage-service.factory";
type DerivedStateProviderFactoryOptions = FactoryOptions;
export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions &
MemoryStorageServiceInitOptions;
export async function derivedStateProviderFactory(
cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices,
opts: DerivedStateProviderInitOptions,
): Promise<DerivedStateProvider> {
return factory(
cache,
"derivedStateProvider",
opts,
async () =>
new BackgroundDerivedStateProvider(await observableMemoryStorageServiceFactory(cache, opts)),
);
}

View File

@@ -6,6 +6,10 @@ import {
ActiveUserStateProviderInitOptions,
activeUserStateProviderFactory,
} from "./active-user-state-provider.factory";
import {
DerivedStateProviderInitOptions,
derivedStateProviderFactory,
} from "./derived-state-provider.factory";
import { CachedServices, FactoryOptions, factory } from "./factory-options";
import {
GlobalStateProviderInitOptions,
@@ -21,7 +25,8 @@ type StateProviderFactoryOptions = FactoryOptions;
export type StateProviderInitOptions = StateProviderFactoryOptions &
GlobalStateProviderInitOptions &
ActiveUserStateProviderInitOptions &
SingleUserStateProviderInitOptions;
SingleUserStateProviderInitOptions &
DerivedStateProviderInitOptions;
export async function stateProviderFactory(
cache: { stateProvider?: StateProvider } & CachedServices,
@@ -36,6 +41,7 @@ export async function stateProviderFactory(
await activeUserStateProviderFactory(cache, opts),
await singleUserStateProviderFactory(cache, opts),
await globalStateProviderFactory(cache, opts),
await derivedStateProviderFactory(cache, opts),
),
);
}

View File

@@ -0,0 +1,23 @@
import { Observable } from "rxjs";
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";
import { ShapeToInstances, Type } from "@bitwarden/common/src/types/state";
import { BackgroundDerivedState } from "./background-derived-state";
export class BackgroundDerivedStateProvider extends DefaultDerivedStateProvider {
override buildDerivedState<TFrom, TTo, TDeps extends Record<string, Type<unknown>>>(
parentState$: Observable<TFrom>,
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
dependencies: ShapeToInstances<TDeps>,
): DerivedState<TTo> {
return new BackgroundDerivedState(
parentState$,
deriveDefinition,
this.memoryStorage,
dependencies,
);
}
}

View File

@@ -0,0 +1,131 @@
import { Observable, Subject, Subscription } 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";
import { ShapeToInstances, Type } from "@bitwarden/common/types/state";
import { BrowserApi } from "../browser/browser-api";
export class BackgroundDerivedState<
TFrom,
TTo,
TDeps extends Record<string, Type<unknown>>,
> extends DefaultDerivedState<TFrom, TTo, TDeps> {
private portSubscriptions: Map<
chrome.runtime.Port,
{ subscription: Subscription; delaySubject: Subject<void> }
> = new Map();
constructor(
parentState$: Observable<TFrom>,
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
memoryStorage: AbstractStorageService & ObservableStorageService,
dependencies: ShapeToInstances<TDeps>,
) {
super(parentState$, deriveDefinition, memoryStorage, dependencies);
const portName = deriveDefinition.buildCacheKey();
// listen for foreground derived states to connect
BrowserApi.addListener(chrome.runtime.onConnect, (port) => {
if (port.name !== portName) {
return;
}
const listenerCallback = this.onMessageFromForeground.bind(this);
port.onDisconnect.addListener(() => {
const { subscription, delaySubject } = this.portSubscriptions.get(port) ?? {
subscription: null,
delaySubject: null,
};
subscription?.unsubscribe();
delaySubject?.complete();
this.portSubscriptions.delete(port);
port.onMessage.removeListener(listenerCallback);
});
port.onMessage.addListener(listenerCallback);
const delaySubject = new Subject<void>();
const stateSubscription = this.state$.subscribe((state) => {
// delay to allow the foreground to connect. This may just be needed for testing
setTimeout(() => {
this.sendNewMessage(
{
action: "nextState",
data: JSON.stringify(state),
},
port,
);
}, 0);
});
this.portSubscriptions.set(port, { subscription: stateSubscription, delaySubject });
});
}
private async onMessageFromForeground(message: DerivedStateMessage, port: chrome.runtime.Port) {
if (message.originator === "background") {
return;
}
switch (message.action) {
case "nextState": {
const dataObj = JSON.parse(message.data) as Jsonify<TTo>;
const data = this.deriveDefinition.deserialize(dataObj);
await this.forceValue(data);
await this.sendResponse(
message,
{
action: "resolve",
},
port,
);
break;
}
}
}
private async sendNewMessage(
message: Omit<DerivedStateMessage, "originator" | "id">,
port: chrome.runtime.Port,
) {
const id = Utils.newGuid();
this.sendMessage(
{
...message,
id: id,
},
port,
);
}
private async sendResponse(
originalMessage: DerivedStateMessage,
response: Omit<DerivedStateMessage, "originator" | "id">,
port: chrome.runtime.Port,
) {
this.sendMessage(
{
...response,
id: originalMessage.id,
},
port,
);
}
private async sendMessage(
message: Omit<DerivedStateMessage, "originator">,
port: chrome.runtime.Port,
) {
port.postMessage({
...message,
originator: "background",
});
}
}

View File

@@ -0,0 +1,112 @@
/**
* need to update test environment so structuredClone works appropriately
* @jest-environment ../../libs/shared/test.environment.ts
*/
import { FakeStorageService } from "@bitwarden/common/../spec/fake-storage.service";
import { awaitAsync, trackEmissions } from "@bitwarden/common/../spec/utils";
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 { Type } from "@bitwarden/common/types/state";
import { mockPorts } from "../../../spec/mock-port.spec-util";
import { BackgroundDerivedState } from "./background-derived-state";
import { ForegroundDerivedState } from "./foreground-derived-state";
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)),
});
describe("foreground background derived state interactions", () => {
let foreground: ForegroundDerivedState<Date>;
let background: BackgroundDerivedState<string, Date, Record<string, Type<unknown>>>;
let parentState$: Subject<string>;
let memoryStorage: FakeStorageService;
const initialParent = "2020-01-01";
beforeEach(() => {
mockPorts();
parentState$ = new Subject<string>();
memoryStorage = new FakeStorageService();
background = new BackgroundDerivedState(parentState$, deriveDefinition, memoryStorage, {});
foreground = new ForegroundDerivedState(deriveDefinition);
});
afterEach(() => {
parentState$.complete();
jest.resetAllMocks();
});
it("should connect between foreground and background", async () => {
const foregroundEmissions = trackEmissions(foreground.state$);
const backgroundEmissions = trackEmissions(background.state$);
parentState$.next(initialParent);
await awaitAsync(10);
expect(foregroundEmissions).toEqual([new Date(initialParent)]);
expect(backgroundEmissions).toEqual([new Date(initialParent)]);
});
it("should initialize a late-connected foreground", async () => {
const newForeground = new ForegroundDerivedState(deriveDefinition);
const backgroundEmissions = trackEmissions(background.state$);
parentState$.next(initialParent);
await awaitAsync();
const foregroundEmissions = trackEmissions(newForeground.state$);
await awaitAsync(10);
expect(backgroundEmissions).toEqual([new Date(initialParent)]);
expect(foregroundEmissions).toEqual([new Date(initialParent)]);
});
describe("forceValue", () => {
it("should force the value to the background", async () => {
const dateString = "2020-12-12";
const emissions = trackEmissions(background.state$);
foreground.forceValue(new Date(dateString));
await awaitAsync();
expect(emissions).toEqual([new Date(dateString)]);
});
it("should not create new ports if already connected", async () => {
// establish port with subscription
trackEmissions(foreground.state$);
const connectMock = chrome.runtime.connect as jest.Mock;
const initialConnectCalls = connectMock.mock.calls.length;
expect(foreground["port"]).toBeDefined();
const newDate = new Date();
foreground.forceValue(newDate);
await awaitAsync();
expect(connectMock.mock.calls.length).toBe(initialConnectCalls);
expect(await firstValueFrom(background.state$)).toEqual(newDate);
});
it("should create a port if not connected", async () => {
const connectMock = chrome.runtime.connect as jest.Mock;
const initialConnectCalls = connectMock.mock.calls.length;
expect(foreground["port"]).toBeUndefined();
const newDate = new Date();
foreground.forceValue(newDate);
await awaitAsync();
expect(connectMock.mock.calls.length).toBe(initialConnectCalls + 1);
expect(foreground["port"]).toBeNull();
expect(await firstValueFrom(background.state$)).toEqual(newDate);
});
});
});

View File

@@ -0,0 +1,18 @@
import { Observable } from "rxjs";
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";
import { ShapeToInstances, Type } from "@bitwarden/common/src/types/state";
import { ForegroundDerivedState } from "./foreground-derived-state";
export class ForegroundDerivedStateProvider extends DefaultDerivedStateProvider {
override buildDerivedState<TFrom, TTo, TDeps extends Record<string, Type<unknown>>>(
_parentState$: Observable<TFrom>,
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
_dependencies: ShapeToInstances<TDeps>,
): DerivedState<TTo> {
return new ForegroundDerivedState(deriveDefinition);
}
}

View File

@@ -0,0 +1,61 @@
import { awaitAsync } from "@bitwarden/common/../spec/utils";
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 { mockPorts } from "../../../spec/mock-port.spec-util";
import { ForegroundDerivedState } from "./foreground-derived-state";
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: 1,
});
describe("ForegroundDerivedState", () => {
let sut: ForegroundDerivedState<Date>;
beforeEach(() => {
mockPorts();
sut = new ForegroundDerivedState(deriveDefinition);
});
afterEach(() => {
jest.resetAllMocks();
});
it("should not connect a port until subscribed", async () => {
expect(sut["port"]).toBeUndefined();
const subscription = sut.state$.subscribe();
expect(sut["port"]).toBeDefined();
subscription.unsubscribe();
});
it("should disconnect its port when unsubscribed", async () => {
const subscription = sut.state$.subscribe();
expect(sut["port"]).toBeDefined();
const disconnectSpy = jest.spyOn(sut["port"], "disconnect");
subscription.unsubscribe();
// wait for the cleanup delay
await awaitAsync(deriveDefinition.cleanupDelayMs * 2);
expect(disconnectSpy).toHaveBeenCalled();
expect(sut["port"]).toBeNull();
});
it("should complete its replay subject when torn down", async () => {
const subscription = sut.state$.subscribe();
const completeSpy = jest.spyOn(sut["replaySubject"], "complete");
subscription.unsubscribe();
// wait for the cleanup delay
await awaitAsync(deriveDefinition.cleanupDelayMs * 2);
expect(completeSpy).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,112 @@
import {
Observable,
ReplaySubject,
defer,
filter,
firstValueFrom,
map,
share,
tap,
timer,
} from "rxjs";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
import { Type } from "@bitwarden/common/types/state";
import { fromChromeEvent } from "../browser/from-chrome-event";
export class ForegroundDerivedState<TTo> implements DerivedState<TTo> {
private port: chrome.runtime.Port;
// For testing purposes
private replaySubject: ReplaySubject<TTo>;
private backgroundResponses$: Observable<DerivedStateMessage>;
state$: Observable<TTo>;
constructor(
private deriveDefinition: DeriveDefinition<unknown, TTo, Record<string, Type<unknown>>>,
) {
this.state$ = defer(() => this.initializePort()).pipe(
filter((message) => message.action === "nextState"),
map((message) => this.hydrateNext(message.data)),
share({
connector: () => {
this.replaySubject = new ReplaySubject<TTo>(1);
return this.replaySubject;
},
resetOnRefCountZero: () =>
timer(this.deriveDefinition.cleanupDelayMs).pipe(tap(() => this.tearDown())),
}),
);
}
async forceValue(value: TTo): Promise<TTo> {
let cleanPort = false;
if (this.port == null) {
this.initializePort();
cleanPort = true;
}
await this.delegateToBackground("nextState", value);
if (cleanPort) {
this.tearDownPort();
}
return value;
}
private initializePort(): Observable<DerivedStateMessage> {
if (this.port != null) {
return;
}
this.port = chrome.runtime.connect({ name: this.deriveDefinition.buildCacheKey() });
this.backgroundResponses$ = fromChromeEvent(this.port.onMessage).pipe(
map(([message]) => message as DerivedStateMessage),
filter((message) => message.originator === "background"),
);
return this.backgroundResponses$;
}
private async delegateToBackground(action: DerivedStateActions, data: TTo): Promise<void> {
const id = Utils.newGuid();
// listen for response before request
const response = firstValueFrom(
this.backgroundResponses$.pipe(filter((message) => message.id === id)),
);
this.sendMessage({
id,
action,
data: JSON.stringify(data),
});
await response;
}
private sendMessage(message: Omit<DerivedStateMessage, "originator">) {
this.port.postMessage({
...message,
originator: "foreground",
});
}
private hydrateNext(value: string): TTo {
const jsonObj = JSON.parse(value);
return this.deriveDefinition.deserialize(jsonObj);
}
private tearDownPort() {
if (this.port == null) {
return;
}
this.port.disconnect();
this.port = null;
this.backgroundResponses$ = null;
}
private tearDown() {
this.tearDownPort();
this.replaySubject.complete();
}
}

View File

@@ -0,0 +1,7 @@
type DerivedStateActions = "nextState" | "resolve";
type DerivedStateMessage = {
id: string;
action: DerivedStateActions;
data?: string; // Json stringified TTo
originator: "foreground" | "background";
};

View File

@@ -5,9 +5,10 @@
import { trackEmissions } from "@bitwarden/common/../spec/utils";
import { mockPorts } from "../../../spec/mock-port.spec-util";
import { BackgroundMemoryStorageService } from "./background-memory-storage.service";
import { ForegroundMemoryStorageService } from "./foreground-memory-storage.service";
import { mockPorts } from "./mock-ports.spec-util";
describe("foreground background memory storage interaction", () => {
let foreground: ForegroundMemoryStorageService;

View File

@@ -67,6 +67,7 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta
import { ConfigService } from "@bitwarden/common/platform/services/config/config.service";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { DerivedStateProvider } from "@bitwarden/common/platform/state";
import { SearchService } from "@bitwarden/common/services/search.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
@@ -109,6 +110,7 @@ import BrowserLocalStorageService from "../../platform/services/browser-local-st
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 { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
import { BrowserSendService } from "../../services/browser-send.service";
import { BrowserSettingsService } from "../../services/browser-settings.service";
@@ -552,6 +554,11 @@ function getBgService<T>(service: keyof MainBackground) {
},
deps: [PlatformUtilsService],
},
{
provide: DerivedStateProvider,
useClass: ForegroundDerivedStateProvider,
deps: [OBSERVABLE_MEMORY_STORAGE],
},
],
})
export class ServicesModule {}

View File

@@ -48,18 +48,18 @@ import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-m
import { StateService } from "@bitwarden/common/platform/services/state.service";
import {
ActiveUserStateProvider,
DerivedStateProvider,
GlobalStateProvider,
SingleUserStateProvider,
StateProvider,
} from "@bitwarden/common/platform/state";
// eslint-disable-next-line import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
// eslint-disable-next-line import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
// eslint-disable-next-line import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
// eslint-disable-next-line import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
/* eslint-enable import/no-restricted-paths */
import { AuditService } from "@bitwarden/common/services/audit.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
@@ -179,6 +179,7 @@ export class Main {
globalStateProvider: GlobalStateProvider;
singleUserStateProvider: SingleUserStateProvider;
activeUserStateProvider: ActiveUserStateProvider;
derivedStateProvider: DerivedStateProvider;
stateProvider: StateProvider;
constructor() {
@@ -245,10 +246,13 @@ export class Main {
this.storageService,
);
this.derivedStateProvider = new DefaultDerivedStateProvider(this.memoryStorageService);
this.stateProvider = new DefaultStateProvider(
this.activeUserStateProvider,
this.singleUserStateProvider,
this.globalStateProvider,
this.derivedStateProvider,
);
this.stateService = new StateService(