mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
Ps/pm 2910/add browser storage services (#6849)
* Allow for update logic in state update callbacks * Prefer reading updates to sending in stream * Inform state providers when they must deserialize * Update DefaultGlobalState to act more like DefaultUserState * Fully Implement AbstractStorageService * Add KeyDefinitionOptions * Address PR feedback * Prefer testing interactions for ports * Synced memory storage for browser * Fix port handling * Do not stringify port message data * Use messaging storage * Initialize new foreground memory storage services This will need to be rethought for short-lived background pages, but for now the background is the source of truth for memory storage * Use global state for account service * Use BrowserApi listener to avoid safari memory leaks * Fix build errors: debugging and missed impls * Prefer bound arrow functions * JSON Stringify Messages * Prefer `useClass` * Use noop services * extract storage observable to new interface This also reverts changes for the existing services to use foreground/background services. Those are now used only in state providers * Fix web DI * Prefer initializing observable in constructor * Do not use jsonify as equality operator * Remove port listener to avoid memory leaks * Fix logic and type issues --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
@@ -8,14 +8,17 @@ export type StorageUpdate = {
|
||||
updateType: StorageUpdateType;
|
||||
};
|
||||
|
||||
export abstract class AbstractStorageService {
|
||||
abstract get valuesRequireDeserialization(): boolean;
|
||||
export interface ObservableStorageService {
|
||||
/**
|
||||
* Provides an {@link Observable} that represents a stream of updates that
|
||||
* have happened in this storage service or in the storage this service provides
|
||||
* an interface to.
|
||||
*/
|
||||
abstract get updates$(): Observable<StorageUpdate>;
|
||||
get updates$(): Observable<StorageUpdate>;
|
||||
}
|
||||
|
||||
export abstract class AbstractStorageService {
|
||||
abstract get valuesRequireDeserialization(): boolean;
|
||||
abstract get<T>(key: string, options?: StorageOptions): Promise<T>;
|
||||
abstract has(key: string, options?: StorageOptions): Promise<boolean>;
|
||||
abstract save<T>(key: string, obj: T, options?: StorageOptions): Promise<void>;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Subject } from "rxjs";
|
||||
import { AbstractMemoryStorageService, StorageUpdate } from "../abstractions/storage.service";
|
||||
|
||||
export class MemoryStorageService extends AbstractMemoryStorageService {
|
||||
private store = new Map<string, unknown>();
|
||||
protected store = new Map<string, unknown>();
|
||||
private updatesSubject = new Subject<StorageUpdate>();
|
||||
|
||||
get valuesRequireDeserialization(): boolean {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "../../abstractions/storage.service";
|
||||
import { GlobalState } from "../global-state";
|
||||
import { GlobalStateProvider } from "../global-state.provider";
|
||||
@@ -13,8 +14,8 @@ export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
||||
private globalStateCache: Record<string, GlobalState<unknown>> = {};
|
||||
|
||||
constructor(
|
||||
private memoryStorage: AbstractMemoryStorageService,
|
||||
private diskStorage: AbstractStorageService
|
||||
private memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
||||
private diskStorage: AbstractStorageService & ObservableStorageService
|
||||
) {}
|
||||
|
||||
get<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
||||
|
||||
@@ -10,7 +10,10 @@ import {
|
||||
timeout,
|
||||
} from "rxjs";
|
||||
|
||||
import { AbstractStorageService } from "../../abstractions/storage.service";
|
||||
import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "../../abstractions/storage.service";
|
||||
import { GlobalState } from "../global-state";
|
||||
import { KeyDefinition, globalKeyBuilder } from "../key-definition";
|
||||
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
||||
@@ -29,7 +32,7 @@ export class DefaultGlobalState<T> implements GlobalState<T> {
|
||||
|
||||
constructor(
|
||||
private keyDefinition: KeyDefinition<T>,
|
||||
private chosenLocation: AbstractStorageService
|
||||
private chosenLocation: AbstractStorageService & ObservableStorageService
|
||||
) {
|
||||
this.storageKey = globalKeyBuilder(this.keyDefinition);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { EncryptService } from "../../abstractions/encrypt.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "../../abstractions/storage.service";
|
||||
import { KeyDefinition } from "../key-definition";
|
||||
import { StorageLocation } from "../state-definition";
|
||||
@@ -17,8 +18,8 @@ export class DefaultUserStateProvider implements UserStateProvider {
|
||||
constructor(
|
||||
protected accountService: AccountService,
|
||||
protected encryptService: EncryptService,
|
||||
protected memoryStorage: AbstractMemoryStorageService,
|
||||
protected diskStorage: AbstractStorageService
|
||||
protected memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
||||
protected diskStorage: AbstractStorageService & ObservableStorageService
|
||||
) {}
|
||||
|
||||
get<T>(keyDefinition: KeyDefinition<T>): UserState<T> {
|
||||
|
||||
@@ -15,7 +15,10 @@ import {
|
||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||
import { AbstractStorageService } from "../../abstractions/storage.service";
|
||||
import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "../../abstractions/storage.service";
|
||||
import { DerivedUserState } from "../derived-user-state";
|
||||
import { KeyDefinition, userKeyBuilder } from "../key-definition";
|
||||
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
||||
@@ -40,7 +43,7 @@ export class DefaultUserState<T> implements UserState<T> {
|
||||
protected keyDefinition: KeyDefinition<T>,
|
||||
private accountService: AccountService,
|
||||
private encryptService: EncryptService,
|
||||
private chosenStorageLocation: AbstractStorageService
|
||||
private chosenStorageLocation: AbstractStorageService & ObservableStorageService
|
||||
) {
|
||||
this.formattedKey$ = this.accountService.activeAccount$.pipe(
|
||||
map((account) =>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export { DerivedUserState } from "./derived-user-state";
|
||||
export { DefaultGlobalStateProvider } from "./implementations/default-global-state.provider";
|
||||
export { DefaultUserStateProvider } from "./implementations/default-user-state.provider";
|
||||
export { GlobalState } from "./global-state";
|
||||
export { GlobalStateProvider } from "./global-state.provider";
|
||||
export { UserState } from "./user-state";
|
||||
export { UserStateProvider } from "./user-state.provider";
|
||||
|
||||
export * from "./key-definitions";
|
||||
|
||||
18
libs/common/src/platform/state/key-definitions.ts
Normal file
18
libs/common/src/platform/state/key-definitions.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { AccountInfo } from "../../auth/abstractions/account.service";
|
||||
import { AccountsDeserializer } from "../../auth/services/account.service";
|
||||
import { UserId } from "../../types/guid";
|
||||
|
||||
import { KeyDefinition } from "./key-definition";
|
||||
import { StateDefinition } from "./state-definition";
|
||||
|
||||
const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
|
||||
export const ACCOUNT_ACCOUNTS = new KeyDefinition<Record<UserId, AccountInfo>>(
|
||||
ACCOUNT_MEMORY,
|
||||
"accounts",
|
||||
{
|
||||
deserializer: (obj) => AccountsDeserializer(obj),
|
||||
}
|
||||
);
|
||||
export const ACCOUNT_ACTIVE_ACCOUNT_ID = new KeyDefinition(ACCOUNT_MEMORY, "activeAccountId", {
|
||||
deserializer: (id: UserId) => id,
|
||||
});
|
||||
Reference in New Issue
Block a user