1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

[PM-14445] TS strict for Key Management Biometrics (#13039)

* PM-14445: TS strict for Key Management Biometrics

* formatting

* callbacks not null expectations

* state nullability expectations updates

* unit tests fix

* secure channel naming, explicit null check on messageId

* revert null for getUser, getGlobal in state.provider.ts

* revert null for getUser, getGlobal in state.provider.ts
This commit is contained in:
Maciej Zieniuk
2025-02-10 13:31:19 +01:00
committed by GitHub
parent 40e8c88d77
commit 7e2e604439
23 changed files with 307 additions and 204 deletions

View File

@@ -18,13 +18,13 @@ export interface GlobalState<T> {
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
*/
update: <TCombine>(
configureState: (state: T, dependency: TCombine) => T,
configureState: (state: T | null, dependency: TCombine) => T | null,
options?: StateUpdateOptions<T, TCombine>,
) => Promise<T>;
) => Promise<T | null>;
/**
* An observable stream of this state, the first emission of this will be the current state on disk
* and subsequent updates will be from an update to that state.
*/
state$: Observable<T>;
state$: Observable<T | null>;
}

View File

@@ -16,7 +16,7 @@ export class DefaultSingleUserState<T>
extends StateBase<T, UserKeyDefinition<T>>
implements SingleUserState<T>
{
readonly combinedState$: Observable<CombinedState<T>>;
readonly combinedState$: Observable<CombinedState<T | null>>;
constructor(
readonly userId: UserId,

View File

@@ -54,9 +54,9 @@ export class DefaultStateProvider implements StateProvider {
async setUserState<T>(
userKeyDefinition: UserKeyDefinition<T>,
value: T,
value: T | null,
userId?: UserId,
): Promise<[UserId, T]> {
): Promise<[UserId, T | null]> {
if (userId) {
return [userId, await this.getUser<T>(userId, userKeyDefinition).update(() => value)];
} else {

View File

@@ -1,12 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import {
Observable,
ReplaySubject,
defer,
filter,
firstValueFrom,
merge,
Observable,
ReplaySubject,
share,
switchMap,
tap,
@@ -22,7 +22,7 @@ import {
ObservableStorageService,
} from "../../abstractions/storage.service";
import { DebugOptions } from "../key-definition";
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
import { populateOptionsWithDefault, StateUpdateOptions } from "../state-update-options";
import { getStoredValue } from "./util";
@@ -36,7 +36,7 @@ type KeyDefinitionRequirements<T> = {
export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>> {
private updatePromise: Promise<T>;
readonly state$: Observable<T>;
readonly state$: Observable<T | null>;
constructor(
protected readonly key: StorageKey,
@@ -86,9 +86,9 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
}
async update<TCombine>(
configureState: (state: T, dependency: TCombine) => T,
configureState: (state: T | null, dependency: TCombine) => T | null,
options: StateUpdateOptions<T, TCombine> = {},
): Promise<T> {
): Promise<T | null> {
options = populateOptionsWithDefault(options);
if (this.updatePromise != null) {
await this.updatePromise;
@@ -96,17 +96,16 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
try {
this.updatePromise = this.internalUpdate(configureState, options);
const newState = await this.updatePromise;
return newState;
return await this.updatePromise;
} finally {
this.updatePromise = null;
}
}
private async internalUpdate<TCombine>(
configureState: (state: T, dependency: TCombine) => T,
configureState: (state: T | null, dependency: TCombine) => T | null,
options: StateUpdateOptions<T, TCombine>,
): Promise<T> {
): Promise<T | null> {
const currentState = await this.getStateForUpdate();
const combinedDependencies =
options.combineLatestWith != null
@@ -122,7 +121,7 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
return newState;
}
protected async doStorageSave(newState: T, oldState: T) {
protected async doStorageSave(newState: T | null, oldState: T) {
if (this.keyDefinition.debug.enableUpdateLogging) {
this.logService.info(
`Updating '${this.key}' from ${oldState == null ? "null" : "non-null"} to ${newState == null ? "null" : "non-null"}`,

View File

@@ -60,9 +60,9 @@ export abstract class StateProvider {
*/
abstract setUserState<T>(
keyDefinition: UserKeyDefinition<T>,
value: T,
value: T | null,
userId?: UserId,
): Promise<[UserId, T]>;
): Promise<[UserId, T | null]>;
/** @see{@link ActiveUserStateProvider.get} */
abstract getActive<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T>;

View File

@@ -12,7 +12,7 @@ export interface UserState<T> {
readonly state$: Observable<T | null>;
/** Emits a stream of tuples, with the first element being a user id and the second element being the data for that user. */
readonly combinedState$: Observable<CombinedState<T>>;
readonly combinedState$: Observable<CombinedState<T | null>>;
}
export const activeMarker: unique symbol = Symbol("active");
@@ -38,9 +38,9 @@ export interface ActiveUserState<T> extends UserState<T> {
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
*/
readonly update: <TCombine>(
configureState: (state: T, dependencies: TCombine) => T,
configureState: (state: T | null, dependencies: TCombine) => T | null,
options?: StateUpdateOptions<T, TCombine>,
) => Promise<[UserId, T]>;
) => Promise<[UserId, T | null]>;
}
export interface SingleUserState<T> extends UserState<T> {
@@ -58,7 +58,7 @@ export interface SingleUserState<T> extends UserState<T> {
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
*/
readonly update: <TCombine>(
configureState: (state: T, dependencies: TCombine) => T,
configureState: (state: T | null, dependencies: TCombine) => T | null,
options?: StateUpdateOptions<T, TCombine>,
) => Promise<T>;
) => Promise<T | null>;
}

View File

@@ -32,7 +32,11 @@ export class DefaultThemeStateService implements ThemeStateService {
map(([theme, isExtensionRefresh]) => {
// The extension refresh should not allow for Nord or SolarizedDark
// Default the user to their system theme
if (isExtensionRefresh && [ThemeType.Nord, ThemeType.SolarizedDark].includes(theme)) {
if (
isExtensionRefresh &&
theme != null &&
[ThemeType.Nord, ThemeType.SolarizedDark].includes(theme)
) {
return ThemeType.System;
}