mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-3530] browser extension view cache (#10437)
Introduces a way to store temporary component state, for the purposes of persisting views between extension popup open and close.
This commit is contained in:
83
libs/angular/src/platform/abstractions/view-cache.service.ts
Normal file
83
libs/angular/src/platform/abstractions/view-cache.service.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Injector, WritableSignal } from "@angular/core";
|
||||
import type { FormGroup } from "@angular/forms";
|
||||
import type { Jsonify, JsonValue } from "type-fest";
|
||||
|
||||
type Deserializer<T> = {
|
||||
/**
|
||||
* A function to use to safely convert your type from json to your expected type.
|
||||
*
|
||||
* @param jsonValue The JSON object representation of your state.
|
||||
* @returns The fully typed version of your state.
|
||||
*/
|
||||
readonly deserializer?: (jsonValue: Jsonify<T>) => T;
|
||||
};
|
||||
|
||||
type BaseCacheOptions<T> = {
|
||||
/** A unique key for saving the cached value to state */
|
||||
key: string;
|
||||
|
||||
/** An optional injector. Required if the method is called outside of an injection context. */
|
||||
injector?: Injector;
|
||||
} & (T extends JsonValue ? Deserializer<T> : Required<Deserializer<T>>);
|
||||
|
||||
export type SignalCacheOptions<T> = BaseCacheOptions<T> & {
|
||||
/** The initial value for the signal. */
|
||||
initialValue: T;
|
||||
};
|
||||
|
||||
/** Extract the value type from a FormGroup */
|
||||
type FormValue<TFormGroup extends FormGroup> = TFormGroup["value"];
|
||||
|
||||
export type FormCacheOptions<TFormGroup extends FormGroup> = BaseCacheOptions<
|
||||
FormValue<TFormGroup>
|
||||
> & {
|
||||
control: TFormGroup;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache for temporary component state
|
||||
*
|
||||
* #### Implementations
|
||||
* - browser extension popup: used to persist UI between popup open and close
|
||||
* - all other clients: noop
|
||||
*/
|
||||
export abstract class ViewCacheService {
|
||||
/**
|
||||
* Create a signal from a previously cached value. Whenever the signal is updated, the new value is saved to the cache.
|
||||
*
|
||||
* Non browser extension implementations are noop and return a normal signal.
|
||||
*
|
||||
* @returns the created signal
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const mySignal = this.viewCacheService.signal({
|
||||
* key: "popup-search-text"
|
||||
* initialValue: ""
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
abstract signal<T>(options: SignalCacheOptions<T>): WritableSignal<T>;
|
||||
|
||||
/**
|
||||
* - Initialize a form from a cached value
|
||||
* - Save form value to cache when it changes
|
||||
* - The form is marked dirty if the restored value is not `undefined`.
|
||||
*
|
||||
* Non browser extension implementations are noop and return the original form group.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* this.loginDetailsForm = this.viewCacheService.formGroup({
|
||||
* key: "vault-login-details-form",
|
||||
* control: this.formBuilder.group({
|
||||
* username: [""],
|
||||
* email: [""],
|
||||
* })
|
||||
* });
|
||||
* ```
|
||||
**/
|
||||
abstract formGroup<TFormGroup extends FormGroup>(
|
||||
options: FormCacheOptions<TFormGroup>,
|
||||
): TFormGroup;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable, signal, WritableSignal } from "@angular/core";
|
||||
import type { FormGroup } from "@angular/forms";
|
||||
|
||||
import {
|
||||
FormCacheOptions,
|
||||
SignalCacheOptions,
|
||||
ViewCacheService,
|
||||
} from "../abstractions/view-cache.service";
|
||||
|
||||
/**
|
||||
* The functionality of the {@link ViewCacheService} is only needed in the browser extension popup,
|
||||
* yet is provided to all clients to make sharing components easier.
|
||||
*
|
||||
* Non-extension clients use this noop implementation.
|
||||
* */
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class NoopViewCacheService implements ViewCacheService {
|
||||
/**
|
||||
* Return a normal signal.
|
||||
*/
|
||||
signal<T>(options: SignalCacheOptions<T>): WritableSignal<T> {
|
||||
return signal(options.initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the original form group.
|
||||
**/
|
||||
formGroup<TFormGroup extends FormGroup>(options: FormCacheOptions<TFormGroup>): TFormGroup {
|
||||
return options.control;
|
||||
}
|
||||
}
|
||||
@@ -268,8 +268,10 @@ import {
|
||||
} from "@bitwarden/vault-export-core";
|
||||
|
||||
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
|
||||
import { ViewCacheService } from "../platform/abstractions/view-cache.service";
|
||||
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
|
||||
import { LoggingErrorHandler } from "../platform/services/logging-error-handler";
|
||||
import { NoopViewCacheService } from "../platform/services/noop-view-cache.service";
|
||||
import { AngularThemingService } from "../platform/services/theming/angular-theming.service";
|
||||
import { AbstractThemingService } from "../platform/services/theming/theming.service.abstraction";
|
||||
import { safeProvider, SafeProvider } from "../platform/utils/safe-provider";
|
||||
@@ -1290,6 +1292,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: DefaultRegistrationFinishService,
|
||||
deps: [CryptoServiceAbstraction, AccountApiServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ViewCacheService,
|
||||
useExisting: NoopViewCacheService,
|
||||
deps: [],
|
||||
}),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
Reference in New Issue
Block a user