mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +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:
@@ -143,6 +143,8 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement
|
|||||||
import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state";
|
import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state";
|
||||||
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||||
/* eslint-enable import/no-restricted-paths */
|
/* eslint-enable import/no-restricted-paths */
|
||||||
|
import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service";
|
||||||
|
import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service";
|
||||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
||||||
import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal";
|
import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal";
|
||||||
@@ -239,6 +241,7 @@ import { ForegroundTaskSchedulerService } from "../platform/services/task-schedu
|
|||||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
||||||
import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider";
|
import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider";
|
||||||
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
|
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
|
||||||
|
import { OffscreenStorageService } from "../platform/storage/offscreen-storage.service";
|
||||||
import { ForegroundSyncService } from "../platform/sync/foreground-sync.service";
|
import { ForegroundSyncService } from "../platform/sync/foreground-sync.service";
|
||||||
import { SyncServiceListener } from "../platform/sync/sync-service.listener";
|
import { SyncServiceListener } from "../platform/sync/sync-service.listener";
|
||||||
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
||||||
@@ -485,10 +488,15 @@ export default class MainBackground {
|
|||||||
? mv3MemoryStorageCreator() // mv3 stores to local-backed session storage
|
? mv3MemoryStorageCreator() // mv3 stores to local-backed session storage
|
||||||
: this.memoryStorageForStateProviders; // mv2 stores to the same location
|
: this.memoryStorageForStateProviders; // mv2 stores to the same location
|
||||||
|
|
||||||
|
const localStorageStorageService = BrowserApi.isManifestVersion(3)
|
||||||
|
? new OffscreenStorageService(this.offscreenDocumentService)
|
||||||
|
: new WindowStorageService(self.localStorage);
|
||||||
|
|
||||||
const storageServiceProvider = new BrowserStorageServiceProvider(
|
const storageServiceProvider = new BrowserStorageServiceProvider(
|
||||||
this.storageService,
|
this.storageService,
|
||||||
this.memoryStorageForStateProviders,
|
this.memoryStorageForStateProviders,
|
||||||
this.largeObjectMemoryStorageForStateProviders,
|
this.largeObjectMemoryStorageForStateProviders,
|
||||||
|
new PrimarySecondaryStorageService(this.storageService, localStorageStorageService),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.globalStateProvider = new DefaultGlobalStateProvider(
|
this.globalStateProvider = new DefaultGlobalStateProvider(
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ class OffscreenDocument implements OffscreenDocumentInterface {
|
|||||||
private readonly extensionMessageHandlers: OffscreenDocumentExtensionMessageHandlers = {
|
private readonly extensionMessageHandlers: OffscreenDocumentExtensionMessageHandlers = {
|
||||||
offscreenCopyToClipboard: ({ message }) => this.handleOffscreenCopyToClipboard(message),
|
offscreenCopyToClipboard: ({ message }) => this.handleOffscreenCopyToClipboard(message),
|
||||||
offscreenReadFromClipboard: () => this.handleOffscreenReadFromClipboard(),
|
offscreenReadFromClipboard: () => this.handleOffscreenReadFromClipboard(),
|
||||||
|
localStorageGet: ({ message }) => this.handleLocalStorageGet(message.key),
|
||||||
|
localStorageSave: ({ message }) => this.handleLocalStorageSave(message.key, message.value),
|
||||||
|
localStorageRemove: ({ message }) => this.handleLocalStorageRemove(message.key),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +42,18 @@ class OffscreenDocument implements OffscreenDocumentInterface {
|
|||||||
return await BrowserClipboardService.read(self);
|
return await BrowserClipboardService.read(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleLocalStorageGet(key: string) {
|
||||||
|
return self.localStorage.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleLocalStorageSave(key: string, value: string) {
|
||||||
|
self.localStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleLocalStorageRemove(key: string) {
|
||||||
|
self.localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the listener for extension messages.
|
* Sets up the listener for extension messages.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export class BrowserStorageServiceProvider extends StorageServiceProvider {
|
|||||||
diskStorageService: AbstractStorageService & ObservableStorageService,
|
diskStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
limitedMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
limitedMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
private largeObjectMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
private largeObjectMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
|
private readonly diskBackupLocalStorage: AbstractStorageService & ObservableStorageService,
|
||||||
) {
|
) {
|
||||||
super(diskStorageService, limitedMemoryStorageService);
|
super(diskStorageService, limitedMemoryStorageService);
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,8 @@ export class BrowserStorageServiceProvider extends StorageServiceProvider {
|
|||||||
switch (location) {
|
switch (location) {
|
||||||
case "memory-large-object":
|
case "memory-large-object":
|
||||||
return ["memory-large-object", this.largeObjectMemoryStorageService];
|
return ["memory-large-object", this.largeObjectMemoryStorageService];
|
||||||
|
case "disk-backup-local-storage":
|
||||||
|
return ["disk-backup-local-storage", this.diskBackupLocalStorage];
|
||||||
default:
|
default:
|
||||||
// Pass in computed location to super because they could have
|
// Pass in computed location to super because they could have
|
||||||
// override default "disk" with web "memory".
|
// override default "disk" with web "memory".
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||||
|
|
||||||
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
|
import { OffscreenDocumentService } from "../offscreen-document/abstractions/offscreen-document";
|
||||||
|
|
||||||
|
export class OffscreenStorageService implements AbstractStorageService {
|
||||||
|
constructor(private readonly offscreenDocumentService: OffscreenDocumentService) {}
|
||||||
|
|
||||||
|
get valuesRequireDeserialization(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||||
|
return await this.offscreenDocumentService.withDocument<T>(
|
||||||
|
[chrome.offscreen.Reason.LOCAL_STORAGE],
|
||||||
|
"backup storage of user data",
|
||||||
|
async () => {
|
||||||
|
const response = await BrowserApi.sendMessageWithResponse<string>("localStorageGet", {
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
if (response != null) {
|
||||||
|
return JSON.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
async has(key: string, options?: StorageOptions): Promise<boolean> {
|
||||||
|
return (await this.get(key, options)) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async save<T>(key: string, obj: T, options?: StorageOptions): Promise<void> {
|
||||||
|
await this.offscreenDocumentService.withDocument(
|
||||||
|
[chrome.offscreen.Reason.LOCAL_STORAGE],
|
||||||
|
"backup storage of user data",
|
||||||
|
async () =>
|
||||||
|
await BrowserApi.sendMessageWithResponse<void>("localStorageSave", {
|
||||||
|
key,
|
||||||
|
value: JSON.stringify(obj),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
async remove(key: string, options?: StorageOptions): Promise<void> {
|
||||||
|
await this.offscreenDocumentService.withDocument(
|
||||||
|
[chrome.offscreen.Reason.LOCAL_STORAGE],
|
||||||
|
"backup storage of user data",
|
||||||
|
async () =>
|
||||||
|
await BrowserApi.sendMessageWithResponse<void>("localStorageRemove", {
|
||||||
|
key,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,8 @@ import {
|
|||||||
} from "@bitwarden/common/platform/state";
|
} from "@bitwarden/common/platform/state";
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- Used for dependency injection
|
// eslint-disable-next-line import/no-restricted-paths -- Used for dependency injection
|
||||||
import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state";
|
import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state";
|
||||||
|
import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service";
|
||||||
|
import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service";
|
||||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||||
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
|
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -122,6 +124,10 @@ const OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE = new SafeInjectionToken<
|
|||||||
AbstractStorageService & ObservableStorageService
|
AbstractStorageService & ObservableStorageService
|
||||||
>("OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE");
|
>("OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE");
|
||||||
|
|
||||||
|
const DISK_BACKUP_LOCAL_STORAGE = new SafeInjectionToken<
|
||||||
|
AbstractStorageService & ObservableStorageService
|
||||||
|
>("DISK_BACKUP_LOCAL_STORAGE");
|
||||||
|
|
||||||
const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired();
|
const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired();
|
||||||
const mainBackground: MainBackground = needsBackgroundInit
|
const mainBackground: MainBackground = needsBackgroundInit
|
||||||
? createLocalBgService()
|
? createLocalBgService()
|
||||||
@@ -496,6 +502,12 @@ const safeProviders: SafeProvider[] = [
|
|||||||
},
|
},
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: DISK_BACKUP_LOCAL_STORAGE,
|
||||||
|
useFactory: (diskStorage: AbstractStorageService & ObservableStorageService) =>
|
||||||
|
new PrimarySecondaryStorageService(diskStorage, new WindowStorageService(self.localStorage)),
|
||||||
|
deps: [OBSERVABLE_DISK_STORAGE],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: StorageServiceProvider,
|
provide: StorageServiceProvider,
|
||||||
useClass: BrowserStorageServiceProvider,
|
useClass: BrowserStorageServiceProvider,
|
||||||
@@ -503,6 +515,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
OBSERVABLE_DISK_STORAGE,
|
OBSERVABLE_DISK_STORAGE,
|
||||||
OBSERVABLE_MEMORY_STORAGE,
|
OBSERVABLE_MEMORY_STORAGE,
|
||||||
OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
||||||
|
DISK_BACKUP_LOCAL_STORAGE,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import { StorageServiceProvider } from "@bitwarden/common/platform/services/stor
|
|||||||
import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state";
|
import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state";
|
||||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||||
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
|
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
|
||||||
|
import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service";
|
||||||
import {
|
import {
|
||||||
DefaultThemeStateService,
|
DefaultThemeStateService,
|
||||||
ThemeStateService,
|
ThemeStateService,
|
||||||
@@ -63,7 +64,6 @@ import { I18nService } from "../core/i18n.service";
|
|||||||
import { WebEnvironmentService } from "../platform/web-environment.service";
|
import { WebEnvironmentService } from "../platform/web-environment.service";
|
||||||
import { WebMigrationRunner } from "../platform/web-migration-runner";
|
import { WebMigrationRunner } from "../platform/web-migration-runner";
|
||||||
import { WebStorageServiceProvider } from "../platform/web-storage-service.provider";
|
import { WebStorageServiceProvider } from "../platform/web-storage-service.provider";
|
||||||
import { WindowStorageService } from "../platform/window-storage.service";
|
|
||||||
import { CollectionAdminService } from "../vault/core/collection-admin.service";
|
import { CollectionAdminService } from "../vault/core/collection-admin.service";
|
||||||
|
|
||||||
import { EventService } from "./event.service";
|
import { EventService } from "./event.service";
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { MockProxy, mock } from "jest-mock-extended";
|
|||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
|
import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service";
|
||||||
import { MigrationBuilder } from "@bitwarden/common/state-migrations/migration-builder";
|
import { MigrationBuilder } from "@bitwarden/common/state-migrations/migration-builder";
|
||||||
import { MigrationHelper } from "@bitwarden/common/state-migrations/migration-helper";
|
import { MigrationHelper } from "@bitwarden/common/state-migrations/migration-helper";
|
||||||
|
|
||||||
import { WebMigrationRunner } from "./web-migration-runner";
|
import { WebMigrationRunner } from "./web-migration-runner";
|
||||||
import { WindowStorageService } from "./window-storage.service";
|
|
||||||
|
|
||||||
describe("WebMigrationRunner", () => {
|
describe("WebMigrationRunner", () => {
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/
|
|||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
|
import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service";
|
||||||
import { MigrationHelper } from "@bitwarden/common/state-migrations/migration-helper";
|
import { MigrationHelper } from "@bitwarden/common/state-migrations/migration-helper";
|
||||||
|
|
||||||
import { WindowStorageService } from "./window-storage.service";
|
|
||||||
|
|
||||||
export class WebMigrationRunner extends MigrationRunner {
|
export class WebMigrationRunner extends MigrationRunner {
|
||||||
constructor(
|
constructor(
|
||||||
diskStorage: AbstractStorageService,
|
diskStorage: AbstractStorageService,
|
||||||
|
|||||||
@@ -25,9 +25,12 @@ export type ClientLocations = {
|
|||||||
/**
|
/**
|
||||||
* Overriding storage location for browser clients.
|
* 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.
|
* 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 AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" });
|
||||||
export const DEVICE_TRUST_DISK_LOCAL = new StateDefinition("deviceTrust", "disk", {
|
export const DEVICE_TRUST_DISK_LOCAL = new StateDefinition("deviceTrust", "disk", {
|
||||||
web: "disk-local",
|
web: "disk-local",
|
||||||
|
browser: "disk-backup-local-storage",
|
||||||
});
|
});
|
||||||
export const KDF_CONFIG_DISK = new StateDefinition("kdfConfig", "disk");
|
export const KDF_CONFIG_DISK = new StateDefinition("kdfConfig", "disk");
|
||||||
export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "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),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
StorageUpdate,
|
StorageUpdate,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "../abstractions/storage.service";
|
||||||
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
import { StorageOptions } from "../models/domain/storage-options";
|
||||||
|
|
||||||
export class WindowStorageService implements AbstractStorageService, ObservableStorageService {
|
export class WindowStorageService implements AbstractStorageService, ObservableStorageService {
|
||||||
private readonly updatesSubject = new Subject<StorageUpdate>();
|
private readonly updatesSubject = new Subject<StorageUpdate>();
|
||||||
Reference in New Issue
Block a user