mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
Add State Provider Framework (#6640)
* Add StateDefinition
Add a class for encapsulation information about state
this will often be for a domain but creations of this will
exist outside of a specific domain, hence just the name State.
* Add KeyDefinition
This adds a type that extends state definition into another sub-key
and forces creators to define the data that will be stored and how
to read the data that they expect to be stored.
* Add key-builders helper functions
Adds to function to help building keys for both keys scoped
to a specific user and for keys scoped to global storage.
Co-authored-by: Matt Gibson <MGibson1@users.noreply.github.com>
* Add updates$ stream to existing storageServices
Original commit by Matt: 823d9546fe
Co-authored-by: Matt Gibson <MGibson1@users.noreply.github.com>
* Add fromChromeEvent helper
Create a helper that creats an Observable from a chrome event
and removes the listener when the subscription is completed.
* Implement `updates$` property for chrome storage
Use fromChromeEvent to create an observable from chrome
event and map that into our expected shape.
* Add GlobalState Abstractions
* Add UserState Abstractions
* Add Default Implementations of User/Global state
Co-authored-by: Matt Gibson <MGibson1@users.noreply.github.com>
* Add Barrel File for state
Co-authored-by: Matt Gibson <MGibson1@users.noreply.github.com>
* Fix ChromeStorageServices
* Rework fromChromeEvent
Rework fromChromeEvent so we have to lie to TS less and
remove unneeded generics. I did this by caring less about
the function and more about the parameters only.
Co-authored-by: Matt Gibson <MGibson1@users.noreply.github.com>
* Fix UserStateProvider Test
* Add Inner Mock & Assert Calls
* Update Tests to use new keys
Use different key format
* Prefer returns over mutations in update
* Update Tests
* Address PR Feedback
* Be stricter with userId parameter
* Add Better Way To Determine if it was a remove
* Fix Web & Browser Storage Services
* Fix Desktop & CLI Storage Services
* Fix Test Storage Service
* Use createKey Helper
* Prefer implement to extending
* Determine storage location in providers
* Export default providers publicly
* Fix user state tests
* Name tests
* Fix CLI
* Prefer Implement In Chrome Storage
* Remove Secure Storage Option
Also throw an exception for subscribes to the secure storage observable.
* Update apps/browser/src/platform/browser/from-chrome-event.ts
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
* Enforce state module barrel file
* Fix Linting Error
* Allow state module import from other modules
* Globally Unregister fromChromeEvent Listeners
Changed fromChromeEvent to add its listeners through the BrowserApi, so that
they will be unregistered when safari closes.
* Test default global state
* Use Proper Casing in Parameter
* Address Feedback
* Update libs/common/src/platform/state/key-definition.ts
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
* Add `buildCacheKey` Method
* Fix lint errors
* Add Comment
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
* Use Generic in callback parameter
* Refactor Out DerivedStateDefinition
* Persist Listener Return Type
* Add Ticket Link
---------
Co-authored-by: Matt Gibson <MGibson1@users.noreply.github.com>
Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
102
libs/common/src/platform/state/key-definition.ts
Normal file
102
libs/common/src/platform/state/key-definition.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Jsonify, Opaque } from "type-fest";
|
||||
|
||||
import { UserId } from "../../types/guid";
|
||||
import { Utils } from "../misc/utils";
|
||||
|
||||
import { StateDefinition } from "./state-definition";
|
||||
|
||||
/**
|
||||
* KeyDefinitions describe the precise location to store data for a given piece of state.
|
||||
* The StateDefinition is used to describe the domain of the state, and the KeyDefinition
|
||||
* sub-divides that domain into specific keys.
|
||||
*/
|
||||
export class KeyDefinition<T> {
|
||||
/**
|
||||
* Creates a new instance of a KeyDefinition
|
||||
* @param stateDefinition The state definition for which this key belongs to.
|
||||
* @param key The name of the key, this should be unique per domain
|
||||
* @param deserializer A function to use to safely convert your type from json to your expected type.
|
||||
*/
|
||||
constructor(
|
||||
readonly stateDefinition: StateDefinition,
|
||||
readonly key: string,
|
||||
readonly deserializer: (jsonValue: Jsonify<T>) => T
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a {@link KeyDefinition} for state that is an array.
|
||||
* @param stateDefinition The state definition to be added to the KeyDefinition
|
||||
* @param key The key to be added to the KeyDefinition
|
||||
* @param deserializer The deserializer for the element of the array in your state.
|
||||
* @returns A {@link KeyDefinition} that contains a serializer that will run the provided deserializer for each
|
||||
* element of an array **unless that array is null in which case it will return an empty list.**
|
||||
*/
|
||||
static array<T>(
|
||||
stateDefinition: StateDefinition,
|
||||
key: string,
|
||||
deserializer: (jsonValue: Jsonify<T>) => T
|
||||
) {
|
||||
return new KeyDefinition<T[]>(stateDefinition, key, (jsonValue) => {
|
||||
return jsonValue?.map((v) => deserializer(v)) ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link KeyDefinition} for state that is a record.
|
||||
* @param stateDefinition The state definition to be added to the KeyDefinition
|
||||
* @param key The key to be added to the KeyDefinition
|
||||
* @param deserializer The deserializer for the value part of a record.
|
||||
* @returns A {@link KeyDefinition} that contains a serializer that will run the provided deserializer for each
|
||||
* value in a record and returns every key as a string **unless that record is null in which case it will return an record.**
|
||||
*/
|
||||
static record<T>(
|
||||
stateDefinition: StateDefinition,
|
||||
key: string,
|
||||
deserializer: (jsonValue: Jsonify<T>) => T
|
||||
) {
|
||||
return new KeyDefinition<Record<string, T>>(stateDefinition, key, (jsonValue) => {
|
||||
const output: Record<string, T> = {};
|
||||
|
||||
if (jsonValue == null) {
|
||||
return output;
|
||||
}
|
||||
|
||||
for (const key in jsonValue) {
|
||||
output[key] = deserializer((jsonValue as Record<string, Jsonify<T>>)[key]);
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
buildCacheKey(): string {
|
||||
return `${this.stateDefinition.storageLocation}_${this.stateDefinition.name}_${this.key}`;
|
||||
}
|
||||
}
|
||||
|
||||
export type StorageKey = Opaque<string, "StorageKey">;
|
||||
|
||||
/**
|
||||
* Creates a {@link StorageKey} that points to the data at the given key definition for the specified user.
|
||||
* @param userId The userId of the user you want the key to be for.
|
||||
* @param keyDefinition The key definition of which data the key should point to.
|
||||
* @returns A key that is ready to be used in a storage service to get data.
|
||||
*/
|
||||
export function userKeyBuilder(userId: UserId, keyDefinition: KeyDefinition<unknown>): StorageKey {
|
||||
if (!Utils.isGuid(userId)) {
|
||||
throw new Error("You cannot build a user key without a valid UserId");
|
||||
}
|
||||
return `user_${userId}_${keyDefinition.stateDefinition.name}_${keyDefinition.key}` as StorageKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link StorageKey}
|
||||
* @param keyDefinition The key definition of which data the key should point to.
|
||||
* @returns A key that is ready to be used in a storage service to get data.
|
||||
*/
|
||||
export function globalKeyBuilder(keyDefinition: KeyDefinition<unknown>): StorageKey {
|
||||
return `global_${keyDefinition.stateDefinition.name}_${keyDefinition.key}` as StorageKey;
|
||||
}
|
||||
Reference in New Issue
Block a user