mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-10754] Store DeviceKey In Backup Storage Location (#10469)
* Implement Backup Storage Location For Browser Disk * Remove Testing Change * Remove Comment * Add Comment For `disk-backup-local-storage` * Require Matching `valuesRequireDeserialization` values
This commit is contained in:
@@ -25,9 +25,12 @@ export type ClientLocations = {
|
||||
/**
|
||||
* Overriding storage location for browser clients.
|
||||
*
|
||||
* "memory-large-object" is used to store non-countable objects in memory. This exists due to limited persistent memory available to browser extensions.
|
||||
* `"memory-large-object"` is used to store non-countable objects in memory. This exists due to limited persistent memory available to browser extensions.
|
||||
*
|
||||
* `"disk-backup-local-storage"` is used to store object in both disk and in `localStorage`. Data is stored in both locations but is only retrieved
|
||||
* from `localStorage` when a null-ish value is retrieved from disk first.
|
||||
*/
|
||||
browser: StorageLocation | "memory-large-object";
|
||||
browser: StorageLocation | "memory-large-object" | "disk-backup-local-storage";
|
||||
/**
|
||||
* Overriding storage location for desktop clients.
|
||||
*/
|
||||
|
||||
@@ -46,6 +46,7 @@ export const AUTH_REQUEST_DISK_LOCAL = new StateDefinition("authRequestLocal", "
|
||||
export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" });
|
||||
export const DEVICE_TRUST_DISK_LOCAL = new StateDefinition("deviceTrust", "disk", {
|
||||
web: "disk-local",
|
||||
browser: "disk-backup-local-storage",
|
||||
});
|
||||
export const KDF_CONFIG_DISK = new StateDefinition("kdfConfig", "disk");
|
||||
export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk");
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { AbstractStorageService, ObservableStorageService } from "../abstractions/storage.service";
|
||||
import { StorageOptions } from "../models/domain/storage-options";
|
||||
|
||||
export class PrimarySecondaryStorageService
|
||||
implements AbstractStorageService, ObservableStorageService
|
||||
{
|
||||
// Only follow the primary storage service as updates should all be done to both
|
||||
updates$ = this.primaryStorageService.updates$;
|
||||
|
||||
constructor(
|
||||
private readonly primaryStorageService: AbstractStorageService & ObservableStorageService,
|
||||
// Secondary service doesn't need to be observable as the only `updates$` are listened to from the primary store
|
||||
private readonly secondaryStorageService: AbstractStorageService,
|
||||
) {
|
||||
if (
|
||||
primaryStorageService.valuesRequireDeserialization !==
|
||||
secondaryStorageService.valuesRequireDeserialization
|
||||
) {
|
||||
throw new Error(
|
||||
"Differing values for valuesRequireDeserialization between storage services is not supported.",
|
||||
);
|
||||
}
|
||||
}
|
||||
get valuesRequireDeserialization(): boolean {
|
||||
return this.primaryStorageService.valuesRequireDeserialization;
|
||||
}
|
||||
|
||||
async get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
const primaryValue = await this.primaryStorageService.get<T>(key, options);
|
||||
|
||||
// If it's null-ish try the secondary location for its value
|
||||
if (primaryValue == null) {
|
||||
return await this.secondaryStorageService.get<T>(key, options);
|
||||
}
|
||||
|
||||
return primaryValue;
|
||||
}
|
||||
|
||||
async has(key: string, options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.primaryStorageService.has(key, options)) ||
|
||||
(await this.secondaryStorageService.has(key, options))
|
||||
);
|
||||
}
|
||||
|
||||
async save<T>(key: string, obj: T, options?: StorageOptions): Promise<void> {
|
||||
await Promise.allSettled([
|
||||
this.primaryStorageService.save(key, obj, options),
|
||||
this.secondaryStorageService.save(key, obj, options),
|
||||
]);
|
||||
}
|
||||
|
||||
async remove(key: string, options?: StorageOptions): Promise<void> {
|
||||
await Promise.allSettled([
|
||||
this.primaryStorageService.remove(key, options),
|
||||
this.secondaryStorageService.remove(key, options),
|
||||
]);
|
||||
}
|
||||
}
|
||||
57
libs/common/src/platform/storage/window-storage.service.ts
Normal file
57
libs/common/src/platform/storage/window-storage.service.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Observable, Subject } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
StorageUpdate,
|
||||
} from "../abstractions/storage.service";
|
||||
import { StorageOptions } from "../models/domain/storage-options";
|
||||
|
||||
export class WindowStorageService implements AbstractStorageService, ObservableStorageService {
|
||||
private readonly updatesSubject = new Subject<StorageUpdate>();
|
||||
|
||||
updates$: Observable<StorageUpdate>;
|
||||
constructor(private readonly storage: Storage) {
|
||||
this.updates$ = this.updatesSubject.asObservable();
|
||||
}
|
||||
|
||||
get valuesRequireDeserialization(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
const jsonValue = this.storage.getItem(key);
|
||||
if (jsonValue != null) {
|
||||
return Promise.resolve(JSON.parse(jsonValue) as T);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
async has(key: string, options?: StorageOptions): Promise<boolean> {
|
||||
return (await this.get(key, options)) != null;
|
||||
}
|
||||
|
||||
save<T>(key: string, obj: T, options?: StorageOptions): Promise<void> {
|
||||
if (obj == null) {
|
||||
return this.remove(key, options);
|
||||
}
|
||||
|
||||
if (obj instanceof Set) {
|
||||
obj = Array.from(obj) as T;
|
||||
}
|
||||
|
||||
this.storage.setItem(key, JSON.stringify(obj));
|
||||
this.updatesSubject.next({ key, updateType: "save" });
|
||||
}
|
||||
|
||||
remove(key: string, options?: StorageOptions): Promise<void> {
|
||||
this.storage.removeItem(key);
|
||||
this.updatesSubject.next({ key, updateType: "remove" });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getKeys(): string[] {
|
||||
return Object.keys(this.storage);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user