mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 16:53:34 +00:00
[PM-6404] Add UserKeyDefinition (#8052)
* Add `UserKeyDefinition` * Fix Deserialization Helpers * Fix KeyDefinition * Move `ClearEvent` * Address PR Feedback * Feedback
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
} from "../../abstractions/storage.service";
|
||||
import { KeyDefinition } from "../key-definition";
|
||||
import { StateDefinition } from "../state-definition";
|
||||
import { UserKeyDefinition, isUserKeyDefinition } from "../user-key-definition";
|
||||
import { ActiveUserState } from "../user-state";
|
||||
import { ActiveUserStateProvider } from "../user-state.provider";
|
||||
|
||||
@@ -27,7 +28,10 @@ export class DefaultActiveUserStateProvider implements ActiveUserStateProvider {
|
||||
this.activeUserId$ = this.accountService.activeAccount$.pipe(map((account) => account?.id));
|
||||
}
|
||||
|
||||
get<T>(keyDefinition: KeyDefinition<T>): ActiveUserState<T> {
|
||||
get<T>(keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>): ActiveUserState<T> {
|
||||
if (!isUserKeyDefinition(keyDefinition)) {
|
||||
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
||||
}
|
||||
const cacheKey = this.buildCacheKey(keyDefinition);
|
||||
const existingUserState = this.cache[cacheKey];
|
||||
if (existingUserState != null) {
|
||||
@@ -41,11 +45,11 @@ export class DefaultActiveUserStateProvider implements ActiveUserStateProvider {
|
||||
return newUserState;
|
||||
}
|
||||
|
||||
private buildCacheKey(keyDefinition: KeyDefinition<unknown>) {
|
||||
private buildCacheKey(keyDefinition: UserKeyDefinition<unknown>) {
|
||||
return `${this.getLocationString(keyDefinition)}_${keyDefinition.fullName}`;
|
||||
}
|
||||
|
||||
protected buildActiveUserState<T>(keyDefinition: KeyDefinition<T>): ActiveUserState<T> {
|
||||
protected buildActiveUserState<T>(keyDefinition: UserKeyDefinition<T>): ActiveUserState<T> {
|
||||
return new DefaultActiveUserState<T>(
|
||||
keyDefinition,
|
||||
this.accountService,
|
||||
@@ -53,7 +57,7 @@ export class DefaultActiveUserStateProvider implements ActiveUserStateProvider {
|
||||
);
|
||||
}
|
||||
|
||||
protected getLocationString(keyDefinition: KeyDefinition<unknown>): string {
|
||||
protected getLocationString(keyDefinition: UserKeyDefinition<unknown>): string {
|
||||
return keyDefinition.stateDefinition.defaultStorageLocation;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
||||
import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service";
|
||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { KeyDefinition, userKeyBuilder } from "../key-definition";
|
||||
import { StateDefinition } from "../state-definition";
|
||||
import { UserKeyDefinition } from "../user-key-definition";
|
||||
|
||||
import { DefaultActiveUserState } from "./default-active-user-state";
|
||||
|
||||
@@ -33,9 +33,10 @@ class TestState {
|
||||
|
||||
const testStateDefinition = new StateDefinition("fake", "disk");
|
||||
const cleanupDelayMs = 15;
|
||||
const testKeyDefinition = new KeyDefinition<TestState>(testStateDefinition, "fake", {
|
||||
const testKeyDefinition = new UserKeyDefinition<TestState>(testStateDefinition, "fake", {
|
||||
deserializer: TestState.fromJSON,
|
||||
cleanupDelayMs,
|
||||
clearOn: [],
|
||||
});
|
||||
|
||||
describe("DefaultActiveUserState", () => {
|
||||
@@ -592,7 +593,7 @@ describe("DefaultActiveUserState", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await changeActiveUser("1");
|
||||
userKey = userKeyBuilder(userId, testKeyDefinition);
|
||||
userKey = testKeyDefinition.buildKey(userId);
|
||||
});
|
||||
|
||||
function assertClean() {
|
||||
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "../../abstractions/storage.service";
|
||||
import { KeyDefinition, userKeyBuilder } from "../key-definition";
|
||||
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
||||
import { UserKeyDefinition } from "../user-key-definition";
|
||||
import { ActiveUserState, CombinedState, activeMarker } from "../user-state";
|
||||
|
||||
import { getStoredValue } from "./util";
|
||||
@@ -39,7 +39,7 @@ export class DefaultActiveUserState<T> implements ActiveUserState<T> {
|
||||
state$: Observable<T>;
|
||||
|
||||
constructor(
|
||||
protected keyDefinition: KeyDefinition<T>,
|
||||
protected keyDefinition: UserKeyDefinition<T>,
|
||||
private accountService: AccountService,
|
||||
private chosenStorageLocation: AbstractStorageService & ObservableStorageService,
|
||||
) {
|
||||
@@ -61,7 +61,7 @@ export class DefaultActiveUserState<T> implements ActiveUserState<T> {
|
||||
return FAKE;
|
||||
}
|
||||
|
||||
const fullKey = userKeyBuilder(userId, this.keyDefinition);
|
||||
const fullKey = this.keyDefinition.buildKey(userId);
|
||||
const data = await getStoredValue(
|
||||
fullKey,
|
||||
this.chosenStorageLocation,
|
||||
@@ -80,7 +80,7 @@ export class DefaultActiveUserState<T> implements ActiveUserState<T> {
|
||||
// Null userId is already taken care of through the userChange observable above
|
||||
filter((u) => u != null),
|
||||
// Take the userId and build the fullKey that we can now create
|
||||
map((userId) => [userId, userKeyBuilder(userId, this.keyDefinition)] as const),
|
||||
map((userId) => [userId, this.keyDefinition.buildKey(userId)] as const),
|
||||
),
|
||||
),
|
||||
// Filter to only storage updates that pertain to our key
|
||||
@@ -168,7 +168,7 @@ export class DefaultActiveUserState<T> implements ActiveUserState<T> {
|
||||
if (userId == null) {
|
||||
throw new Error("No active user at this time.");
|
||||
}
|
||||
const fullKey = userKeyBuilder(userId, this.keyDefinition);
|
||||
const fullKey = this.keyDefinition.buildKey(userId);
|
||||
return [
|
||||
userId,
|
||||
fullKey,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "../../abstractions/storage.service";
|
||||
import { KeyDefinition } from "../key-definition";
|
||||
import { StateDefinition } from "../state-definition";
|
||||
import { UserKeyDefinition, isUserKeyDefinition } from "../user-key-definition";
|
||||
import { SingleUserState } from "../user-state";
|
||||
import { SingleUserStateProvider } from "../user-state.provider";
|
||||
|
||||
@@ -19,7 +20,13 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
||||
protected readonly diskStorage: AbstractStorageService & ObservableStorageService,
|
||||
) {}
|
||||
|
||||
get<T>(userId: UserId, keyDefinition: KeyDefinition<T>): SingleUserState<T> {
|
||||
get<T>(
|
||||
userId: UserId,
|
||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
||||
): SingleUserState<T> {
|
||||
if (!isUserKeyDefinition(keyDefinition)) {
|
||||
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
||||
}
|
||||
const cacheKey = this.buildCacheKey(userId, keyDefinition);
|
||||
const existingUserState = this.cache[cacheKey];
|
||||
if (existingUserState != null) {
|
||||
@@ -33,13 +40,13 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
||||
return newUserState;
|
||||
}
|
||||
|
||||
private buildCacheKey(userId: UserId, keyDefinition: KeyDefinition<unknown>) {
|
||||
private buildCacheKey(userId: UserId, keyDefinition: UserKeyDefinition<unknown>) {
|
||||
return `${this.getLocationString(keyDefinition)}_${keyDefinition.fullName}_${userId}`;
|
||||
}
|
||||
|
||||
protected buildSingleUserState<T>(
|
||||
userId: UserId,
|
||||
keyDefinition: KeyDefinition<T>,
|
||||
keyDefinition: UserKeyDefinition<T>,
|
||||
): SingleUserState<T> {
|
||||
return new DefaultSingleUserState<T>(
|
||||
userId,
|
||||
@@ -48,7 +55,7 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
||||
);
|
||||
}
|
||||
|
||||
protected getLocationString(keyDefinition: KeyDefinition<unknown>): string {
|
||||
protected getLocationString(keyDefinition: UserKeyDefinition<unknown>): string {
|
||||
return keyDefinition.stateDefinition.defaultStorageLocation;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import { trackEmissions, awaitAsync } from "../../../../spec";
|
||||
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { KeyDefinition, userKeyBuilder } from "../key-definition";
|
||||
import { StateDefinition } from "../state-definition";
|
||||
import { UserKeyDefinition } from "../user-key-definition";
|
||||
|
||||
import { DefaultSingleUserState } from "./default-single-user-state";
|
||||
|
||||
@@ -31,12 +31,13 @@ class TestState {
|
||||
|
||||
const testStateDefinition = new StateDefinition("fake", "disk");
|
||||
const cleanupDelayMs = 10;
|
||||
const testKeyDefinition = new KeyDefinition<TestState>(testStateDefinition, "fake", {
|
||||
const testKeyDefinition = new UserKeyDefinition<TestState>(testStateDefinition, "fake", {
|
||||
deserializer: TestState.fromJSON,
|
||||
cleanupDelayMs,
|
||||
clearOn: [],
|
||||
});
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const userKey = userKeyBuilder(userId, testKeyDefinition);
|
||||
const userKey = testKeyDefinition.buildKey(userId);
|
||||
|
||||
describe("DefaultSingleUserState", () => {
|
||||
let diskStorageService: FakeStorageService;
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "../../abstractions/storage.service";
|
||||
import { KeyDefinition, userKeyBuilder } from "../key-definition";
|
||||
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
||||
import { UserKeyDefinition } from "../user-key-definition";
|
||||
import { CombinedState, SingleUserState } from "../user-state";
|
||||
|
||||
import { getStoredValue } from "./util";
|
||||
@@ -33,10 +33,10 @@ export class DefaultSingleUserState<T> implements SingleUserState<T> {
|
||||
|
||||
constructor(
|
||||
readonly userId: UserId,
|
||||
private keyDefinition: KeyDefinition<T>,
|
||||
private keyDefinition: UserKeyDefinition<T>,
|
||||
private chosenLocation: AbstractStorageService & ObservableStorageService,
|
||||
) {
|
||||
this.storageKey = userKeyBuilder(this.userId, this.keyDefinition);
|
||||
this.storageKey = this.keyDefinition.buildKey(this.userId);
|
||||
const initialStorageGet$ = defer(() => {
|
||||
return getStoredValue(this.storageKey, this.chosenLocation, this.keyDefinition.deserializer);
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DerivedStateProvider } from "../derived-state.provider";
|
||||
import { GlobalStateProvider } from "../global-state.provider";
|
||||
import { KeyDefinition } from "../key-definition";
|
||||
import { StateProvider } from "../state.provider";
|
||||
import { UserKeyDefinition } from "../user-key-definition";
|
||||
import { ActiveUserStateProvider, SingleUserStateProvider } from "../user-state.provider";
|
||||
|
||||
export class DefaultStateProvider implements StateProvider {
|
||||
@@ -21,7 +22,10 @@ export class DefaultStateProvider implements StateProvider {
|
||||
this.activeUserId$ = this.activeUserStateProvider.activeUserId$;
|
||||
}
|
||||
|
||||
getUserState$<T>(keyDefinition: KeyDefinition<T>, userId?: UserId): Observable<T> {
|
||||
getUserState$<T>(
|
||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
||||
userId?: UserId,
|
||||
): Observable<T> {
|
||||
if (userId) {
|
||||
return this.getUser<T>(userId, keyDefinition).state$;
|
||||
} else {
|
||||
@@ -33,7 +37,7 @@ export class DefaultStateProvider implements StateProvider {
|
||||
}
|
||||
|
||||
async setUserState<T>(
|
||||
keyDefinition: KeyDefinition<T>,
|
||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
||||
value: T,
|
||||
userId?: UserId,
|
||||
): Promise<[UserId, T]> {
|
||||
|
||||
Reference in New Issue
Block a user