-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PM-6404] Initial Clear Events Code (#8029)
* Add New KeyDefinitionOption * Add New Services * Add WebStorageServiceProvider Tests * Update Error Message * Add `UserKeyDefinition` * Fix Deserialization Helpers * Fix KeyDefinition * Add `UserKeyDefinition` * Fix Deserialization Helpers * Fix KeyDefinition * Move `ClearEvent` * Cleanup * Fix Imports * Remove `updateMock` * Call Super in Web Implementation * Use Better Type to Avoid Casting * Better Error Docs * Move StorageKey Creation to Function * Throw Aggregated Error for Failures
- Loading branch information
1 parent
929b5eb
commit 87c75e5
Showing
14 changed files
with
553 additions
and
10 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
...rowser/src/platform/background/service-factories/state-event-registrar-service.factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// eslint-disable-next-line import/no-restricted-paths | ||
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; | ||
|
||
import { CachedServices, FactoryOptions, factory } from "./factory-options"; | ||
import { | ||
GlobalStateProviderInitOptions, | ||
globalStateProviderFactory, | ||
} from "./global-state-provider.factory"; | ||
import { | ||
StorageServiceProviderInitOptions, | ||
storageServiceProviderFactory, | ||
} from "./storage-service-provider.factory"; | ||
|
||
type StateEventRegistrarServiceFactoryOptions = FactoryOptions; | ||
|
||
export type StateEventRegistrarServiceInitOptions = StateEventRegistrarServiceFactoryOptions & | ||
GlobalStateProviderInitOptions & | ||
StorageServiceProviderInitOptions; | ||
|
||
export async function stateEventRegistrarServiceFactory( | ||
cache: { | ||
stateEventRegistrarService?: StateEventRegistrarService; | ||
} & CachedServices, | ||
opts: StateEventRegistrarServiceInitOptions, | ||
): Promise<StateEventRegistrarService> { | ||
return factory( | ||
cache, | ||
"stateEventRegistrarService", | ||
opts, | ||
async () => | ||
new StateEventRegistrarService( | ||
await globalStateProviderFactory(cache, opts), | ||
await storageServiceProviderFactory(cache, opts), | ||
), | ||
); | ||
} |
33 changes: 33 additions & 0 deletions
33
apps/browser/src/platform/background/service-factories/storage-service-provider.factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; | ||
|
||
import { CachedServices, FactoryOptions, factory } from "./factory-options"; | ||
import { | ||
DiskStorageServiceInitOptions, | ||
MemoryStorageServiceInitOptions, | ||
observableDiskStorageServiceFactory, | ||
observableMemoryStorageServiceFactory, | ||
} from "./storage-service.factory"; | ||
|
||
type StorageServiceProviderFactoryOptions = FactoryOptions; | ||
|
||
export type StorageServiceProviderInitOptions = StorageServiceProviderFactoryOptions & | ||
MemoryStorageServiceInitOptions & | ||
DiskStorageServiceInitOptions; | ||
|
||
export async function storageServiceProviderFactory( | ||
cache: { | ||
storageServiceProvider?: StorageServiceProvider; | ||
} & CachedServices, | ||
opts: StorageServiceProviderInitOptions, | ||
): Promise<StorageServiceProvider> { | ||
return factory( | ||
cache, | ||
"storageServiceProvider", | ||
opts, | ||
async () => | ||
new StorageServiceProvider( | ||
await observableDiskStorageServiceFactory(cache, opts), | ||
await observableMemoryStorageServiceFactory(cache, opts), | ||
), | ||
); | ||
} |
63 changes: 63 additions & 0 deletions
63
apps/web/src/app/platform/web-storage-service.provider.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { mock } from "jest-mock-extended"; | ||
|
||
import { | ||
AbstractStorageService, | ||
ObservableStorageService, | ||
} from "@bitwarden/common/platform/abstractions/storage.service"; | ||
import { PossibleLocation } from "@bitwarden/common/platform/services/storage-service.provider"; | ||
import { | ||
ClientLocations, | ||
StorageLocation, | ||
// eslint-disable-next-line import/no-restricted-paths | ||
} from "@bitwarden/common/platform/state/state-definition"; | ||
|
||
import { WebStorageServiceProvider } from "./web-storage-service.provider"; | ||
|
||
describe("WebStorageServiceProvider", () => { | ||
const mockDiskStorage = mock<AbstractStorageService & ObservableStorageService>(); | ||
const mockMemoryStorage = mock<AbstractStorageService & ObservableStorageService>(); | ||
const mockDiskLocalStorage = mock<AbstractStorageService & ObservableStorageService>(); | ||
|
||
const sut = new WebStorageServiceProvider( | ||
mockDiskStorage, | ||
mockMemoryStorage, | ||
mockDiskLocalStorage, | ||
); | ||
|
||
describe("get", () => { | ||
const getTests = [ | ||
{ | ||
input: { default: "disk", overrides: {} }, | ||
expected: "disk", | ||
}, | ||
{ | ||
input: { default: "memory", overrides: {} }, | ||
expected: "memory", | ||
}, | ||
{ | ||
input: { default: "disk", overrides: { web: "disk-local" } }, | ||
expected: "disk-local", | ||
}, | ||
{ | ||
input: { default: "disk", overrides: { web: "memory" } }, | ||
expected: "memory", | ||
}, | ||
{ | ||
input: { default: "memory", overrides: { web: "disk" } }, | ||
expected: "disk", | ||
}, | ||
] satisfies { | ||
input: { default: StorageLocation; overrides: Partial<ClientLocations> }; | ||
expected: PossibleLocation; | ||
}[]; | ||
|
||
it.each(getTests)("computes properly based on %s", ({ input, expected: expectedLocation }) => { | ||
const [actualLocation] = sut.get(input.default, input.overrides); | ||
expect(actualLocation).toStrictEqual(expectedLocation); | ||
}); | ||
|
||
it("throws on unsupported option", () => { | ||
expect(() => sut.get("blah" as any, {})).toThrow(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { | ||
AbstractStorageService, | ||
ObservableStorageService, | ||
} from "@bitwarden/common/platform/abstractions/storage.service"; | ||
import { | ||
PossibleLocation, | ||
StorageServiceProvider, | ||
} from "@bitwarden/common/platform/services/storage-service.provider"; | ||
import { | ||
ClientLocations, | ||
// eslint-disable-next-line import/no-restricted-paths | ||
} from "@bitwarden/common/platform/state/state-definition"; | ||
|
||
export class WebStorageServiceProvider extends StorageServiceProvider { | ||
constructor( | ||
diskStorageService: AbstractStorageService & ObservableStorageService, | ||
memoryStorageService: AbstractStorageService & ObservableStorageService, | ||
private readonly diskLocalStorageService: AbstractStorageService & ObservableStorageService, | ||
) { | ||
super(diskStorageService, memoryStorageService); | ||
} | ||
|
||
override get( | ||
defaultLocation: PossibleLocation, | ||
overrides: Partial<ClientLocations>, | ||
): [location: PossibleLocation, service: AbstractStorageService & ObservableStorageService] { | ||
const location = overrides["web"] ?? defaultLocation; | ||
switch (location) { | ||
case "disk-local": | ||
return ["disk-local", this.diskLocalStorageService]; | ||
default: | ||
// Pass in computed location to super because they could have | ||
// overriden default "disk" with web "memory". | ||
return super.get(location, overrides); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
libs/common/src/platform/services/storage-service.provider.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { mock } from "jest-mock-extended"; | ||
|
||
import { AbstractStorageService, ObservableStorageService } from "../abstractions/storage.service"; | ||
|
||
import { StorageServiceProvider } from "./storage-service.provider"; | ||
|
||
describe("StorageServiceProvider", () => { | ||
const mockDiskStorage = mock<AbstractStorageService & ObservableStorageService>(); | ||
const mockMemoryStorage = mock<AbstractStorageService & ObservableStorageService>(); | ||
|
||
const sut = new StorageServiceProvider(mockDiskStorage, mockMemoryStorage); | ||
|
||
describe("get", () => { | ||
it("gets disk service when default location is disk", () => { | ||
const [computedLocation, computedService] = sut.get("disk", {}); | ||
|
||
expect(computedLocation).toBe("disk"); | ||
expect(computedService).toStrictEqual(mockDiskStorage); | ||
}); | ||
|
||
it("gets memory service when default location is memory", () => { | ||
const [computedLocation, computedService] = sut.get("memory", {}); | ||
|
||
expect(computedLocation).toBe("memory"); | ||
expect(computedService).toStrictEqual(mockMemoryStorage); | ||
}); | ||
}); | ||
}); |
39 changes: 39 additions & 0 deletions
39
libs/common/src/platform/services/storage-service.provider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { AbstractStorageService, ObservableStorageService } from "../abstractions/storage.service"; | ||
// eslint-disable-next-line import/no-restricted-paths | ||
import { ClientLocations, StorageLocation } from "../state/state-definition"; | ||
|
||
export type PossibleLocation = StorageLocation | ClientLocations[keyof ClientLocations]; | ||
|
||
/** | ||
* A provider for getting client specific computed storage locations and services. | ||
*/ | ||
export class StorageServiceProvider { | ||
constructor( | ||
protected readonly diskStorageService: AbstractStorageService & ObservableStorageService, | ||
protected readonly memoryStorageService: AbstractStorageService & ObservableStorageService, | ||
) {} | ||
|
||
/** | ||
* Computes the location and corresponding service for a given client. | ||
* | ||
* **NOTE** The default implementation does not respect client overrides and if clients | ||
* have special overrides they are responsible for implementing this service. | ||
* @param defaultLocation The default location to use if no client specific override is preferred. | ||
* @param overrides Client specific overrides | ||
* @returns The computed storage location and corresponding storage service to use to get/store state. | ||
* @throws If there is no configured storage service for the given inputs. | ||
*/ | ||
get( | ||
defaultLocation: PossibleLocation, | ||
overrides: Partial<ClientLocations>, | ||
): [location: PossibleLocation, service: AbstractStorageService & ObservableStorageService] { | ||
switch (defaultLocation) { | ||
case "disk": | ||
return [defaultLocation, this.diskStorageService]; | ||
case "memory": | ||
return [defaultLocation, this.memoryStorageService]; | ||
default: | ||
throw new Error(`Unexpected location: ${defaultLocation}`); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
libs/common/src/platform/state/state-event-registrar.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { mock } from "jest-mock-extended"; | ||
|
||
import { FakeGlobalStateProvider } from "../../../spec"; | ||
import { AbstractStorageService, ObservableStorageService } from "../abstractions/storage.service"; | ||
import { StorageServiceProvider } from "../services/storage-service.provider"; | ||
|
||
import { StateDefinition } from "./state-definition"; | ||
import { STATE_LOCK_EVENT, StateEventRegistrarService } from "./state-event-registrar.service"; | ||
import { UserKeyDefinition } from "./user-key-definition"; | ||
|
||
describe("StateEventRegistrarService", () => { | ||
const globalStateProvider = new FakeGlobalStateProvider(); | ||
const lockState = globalStateProvider.getFake(STATE_LOCK_EVENT); | ||
const storageServiceProvider = mock<StorageServiceProvider>(); | ||
|
||
const sut = new StateEventRegistrarService(globalStateProvider, storageServiceProvider); | ||
|
||
describe("registerEvents", () => { | ||
const fakeKeyDefinition = new UserKeyDefinition<boolean>( | ||
new StateDefinition("fakeState", "disk"), | ||
"fakeKey", | ||
{ | ||
deserializer: (s) => s, | ||
clearOn: ["lock"], | ||
}, | ||
); | ||
|
||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it("adds event on null storage", async () => { | ||
storageServiceProvider.get.mockReturnValue([ | ||
"disk", | ||
mock<AbstractStorageService & ObservableStorageService>(), | ||
]); | ||
|
||
await sut.registerEvents(fakeKeyDefinition); | ||
|
||
expect(lockState.nextMock).toHaveBeenCalledWith([ | ||
{ | ||
key: "fakeKey", | ||
location: "disk", | ||
state: "fakeState", | ||
}, | ||
]); | ||
}); | ||
|
||
it("adds event on empty array in storage", async () => { | ||
lockState.stateSubject.next([]); | ||
storageServiceProvider.get.mockReturnValue([ | ||
"disk", | ||
mock<AbstractStorageService & ObservableStorageService>(), | ||
]); | ||
|
||
await sut.registerEvents(fakeKeyDefinition); | ||
|
||
expect(lockState.nextMock).toHaveBeenCalledWith([ | ||
{ | ||
key: "fakeKey", | ||
location: "disk", | ||
state: "fakeState", | ||
}, | ||
]); | ||
}); | ||
|
||
it("doesn't add a duplicate", async () => { | ||
lockState.stateSubject.next([ | ||
{ | ||
key: "fakeKey", | ||
location: "disk", | ||
state: "fakeState", | ||
}, | ||
]); | ||
storageServiceProvider.get.mockReturnValue([ | ||
"disk", | ||
mock<AbstractStorageService & ObservableStorageService>(), | ||
]); | ||
|
||
await sut.registerEvents(fakeKeyDefinition); | ||
|
||
expect(lockState.nextMock).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.