mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-23 03:22:50 +02:00
[PM-9978] Add State Logging Options (#10251)
* Add `DebugOptions` to Definitions * Respect Debug Options * Configure DI
This commit is contained in:
parent
beb5a65cda
commit
c91f9146da
@ -482,7 +482,10 @@ export default class MainBackground {
|
|||||||
this.largeObjectMemoryStorageForStateProviders,
|
this.largeObjectMemoryStorageForStateProviders,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
this.globalStateProvider = new DefaultGlobalStateProvider(
|
||||||
|
storageServiceProvider,
|
||||||
|
this.logService,
|
||||||
|
);
|
||||||
|
|
||||||
const stateEventRegistrarService = new StateEventRegistrarService(
|
const stateEventRegistrarService = new StateEventRegistrarService(
|
||||||
this.globalStateProvider,
|
this.globalStateProvider,
|
||||||
@ -505,6 +508,7 @@ export default class MainBackground {
|
|||||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
|
this.logService,
|
||||||
);
|
);
|
||||||
this.accountService = new AccountServiceImplementation(
|
this.accountService = new AccountServiceImplementation(
|
||||||
this.messagingService,
|
this.messagingService,
|
||||||
|
@ -291,7 +291,10 @@ export class ServiceContainer {
|
|||||||
this.memoryStorageForStateProviders,
|
this.memoryStorageForStateProviders,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
this.globalStateProvider = new DefaultGlobalStateProvider(
|
||||||
|
storageServiceProvider,
|
||||||
|
this.logService,
|
||||||
|
);
|
||||||
|
|
||||||
const stateEventRegistrarService = new StateEventRegistrarService(
|
const stateEventRegistrarService = new StateEventRegistrarService(
|
||||||
this.globalStateProvider,
|
this.globalStateProvider,
|
||||||
@ -308,6 +311,7 @@ export class ServiceContainer {
|
|||||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
|
this.logService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.messagingService = MessageSender.EMPTY;
|
this.messagingService = MessageSender.EMPTY;
|
||||||
|
@ -109,7 +109,10 @@ export class Main {
|
|||||||
this.storageService,
|
this.storageService,
|
||||||
this.memoryStorageForStateProviders,
|
this.memoryStorageForStateProviders,
|
||||||
);
|
);
|
||||||
const globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
const globalStateProvider = new DefaultGlobalStateProvider(
|
||||||
|
storageServiceProvider,
|
||||||
|
this.logService,
|
||||||
|
);
|
||||||
|
|
||||||
this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);
|
this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);
|
||||||
|
|
||||||
@ -130,6 +133,7 @@ export class Main {
|
|||||||
const singleUserStateProvider = new DefaultSingleUserStateProvider(
|
const singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
|
this.logService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeUserStateProvider = new DefaultActiveUserStateProvider(
|
const activeUserStateProvider = new DefaultActiveUserStateProvider(
|
||||||
|
@ -1119,7 +1119,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: GlobalStateProvider,
|
provide: GlobalStateProvider,
|
||||||
useClass: DefaultGlobalStateProvider,
|
useClass: DefaultGlobalStateProvider,
|
||||||
deps: [StorageServiceProvider],
|
deps: [StorageServiceProvider, LogService],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: ActiveUserStateProvider,
|
provide: ActiveUserStateProvider,
|
||||||
@ -1129,7 +1129,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: SingleUserStateProvider,
|
provide: SingleUserStateProvider,
|
||||||
useClass: DefaultSingleUserStateProvider,
|
useClass: DefaultSingleUserStateProvider,
|
||||||
deps: [StorageServiceProvider, StateEventRegistrarService],
|
deps: [StorageServiceProvider, StateEventRegistrarService, LogService],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DerivedStateProvider,
|
provide: DerivedStateProvider,
|
||||||
|
@ -10,6 +10,7 @@ import { awaitAsync, trackEmissions } from "../../../../spec";
|
|||||||
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
||||||
import { AccountInfo } from "../../../auth/abstractions/account.service";
|
import { AccountInfo } from "../../../auth/abstractions/account.service";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
@ -45,6 +46,7 @@ describe("DefaultActiveUserState", () => {
|
|||||||
let diskStorageService: FakeStorageService;
|
let diskStorageService: FakeStorageService;
|
||||||
const storageServiceProvider = mock<StorageServiceProvider>();
|
const storageServiceProvider = mock<StorageServiceProvider>();
|
||||||
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
||||||
|
const logService = mock<LogService>();
|
||||||
let activeAccountSubject: BehaviorSubject<{ id: UserId } & AccountInfo>;
|
let activeAccountSubject: BehaviorSubject<{ id: UserId } & AccountInfo>;
|
||||||
|
|
||||||
let singleUserStateProvider: DefaultSingleUserStateProvider;
|
let singleUserStateProvider: DefaultSingleUserStateProvider;
|
||||||
@ -58,6 +60,7 @@ describe("DefaultActiveUserState", () => {
|
|||||||
singleUserStateProvider = new DefaultSingleUserStateProvider(
|
singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
|
logService,
|
||||||
);
|
);
|
||||||
|
|
||||||
activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(undefined);
|
activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(undefined);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
import { GlobalState } from "../global-state";
|
import { GlobalState } from "../global-state";
|
||||||
import { GlobalStateProvider } from "../global-state.provider";
|
import { GlobalStateProvider } from "../global-state.provider";
|
||||||
@ -8,7 +9,10 @@ import { DefaultGlobalState } from "./default-global-state";
|
|||||||
export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
||||||
private globalStateCache: Record<string, GlobalState<unknown>> = {};
|
private globalStateCache: Record<string, GlobalState<unknown>> = {};
|
||||||
|
|
||||||
constructor(private storageServiceProvider: StorageServiceProvider) {}
|
constructor(
|
||||||
|
private storageServiceProvider: StorageServiceProvider,
|
||||||
|
private readonly logService: LogService,
|
||||||
|
) {}
|
||||||
|
|
||||||
get<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
get<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
||||||
const [location, storageService] = this.storageServiceProvider.get(
|
const [location, storageService] = this.storageServiceProvider.get(
|
||||||
@ -23,7 +27,11 @@ export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
|||||||
return existingGlobalState as DefaultGlobalState<T>;
|
return existingGlobalState as DefaultGlobalState<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newGlobalState = new DefaultGlobalState<T>(keyDefinition, storageService);
|
const newGlobalState = new DefaultGlobalState<T>(
|
||||||
|
keyDefinition,
|
||||||
|
storageService,
|
||||||
|
this.logService,
|
||||||
|
);
|
||||||
|
|
||||||
this.globalStateCache[cacheKey] = newGlobalState;
|
this.globalStateCache[cacheKey] = newGlobalState;
|
||||||
return newGlobalState;
|
return newGlobalState;
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
* @jest-environment ../shared/test.environment.ts
|
* @jest-environment ../shared/test.environment.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { mock } from "jest-mock-extended";
|
||||||
import { firstValueFrom, of } from "rxjs";
|
import { firstValueFrom, of } from "rxjs";
|
||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
import { trackEmissions, awaitAsync } from "../../../../spec";
|
import { trackEmissions, awaitAsync } from "../../../../spec";
|
||||||
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import { KeyDefinition, globalKeyBuilder } from "../key-definition";
|
import { KeyDefinition, globalKeyBuilder } from "../key-definition";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
|
|
||||||
@ -38,11 +40,12 @@ const globalKey = globalKeyBuilder(testKeyDefinition);
|
|||||||
describe("DefaultGlobalState", () => {
|
describe("DefaultGlobalState", () => {
|
||||||
let diskStorageService: FakeStorageService;
|
let diskStorageService: FakeStorageService;
|
||||||
let globalState: DefaultGlobalState<TestState>;
|
let globalState: DefaultGlobalState<TestState>;
|
||||||
|
const logService = mock<LogService>();
|
||||||
const newData = { date: new Date() };
|
const newData = { date: new Date() };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
diskStorageService = new FakeStorageService();
|
diskStorageService = new FakeStorageService();
|
||||||
globalState = new DefaultGlobalState(testKeyDefinition, diskStorageService);
|
globalState = new DefaultGlobalState(testKeyDefinition, diskStorageService, logService);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import {
|
import {
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
@ -14,7 +15,8 @@ export class DefaultGlobalState<T>
|
|||||||
constructor(
|
constructor(
|
||||||
keyDefinition: KeyDefinition<T>,
|
keyDefinition: KeyDefinition<T>,
|
||||||
chosenLocation: AbstractStorageService & ObservableStorageService,
|
chosenLocation: AbstractStorageService & ObservableStorageService,
|
||||||
|
logService: LogService,
|
||||||
) {
|
) {
|
||||||
super(globalKeyBuilder(keyDefinition), chosenLocation, keyDefinition);
|
super(globalKeyBuilder(keyDefinition), chosenLocation, keyDefinition, logService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
import { UserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
@ -13,6 +14,7 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly storageServiceProvider: StorageServiceProvider,
|
private readonly storageServiceProvider: StorageServiceProvider,
|
||||||
private readonly stateEventRegistrarService: StateEventRegistrarService,
|
private readonly stateEventRegistrarService: StateEventRegistrarService,
|
||||||
|
private readonly logService: LogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get<T>(userId: UserId, keyDefinition: UserKeyDefinition<T>): SingleUserState<T> {
|
get<T>(userId: UserId, keyDefinition: UserKeyDefinition<T>): SingleUserState<T> {
|
||||||
@ -33,6 +35,7 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
|||||||
keyDefinition,
|
keyDefinition,
|
||||||
storageService,
|
storageService,
|
||||||
this.stateEventRegistrarService,
|
this.stateEventRegistrarService,
|
||||||
|
this.logService,
|
||||||
);
|
);
|
||||||
this.cache[cacheKey] = newUserState;
|
this.cache[cacheKey] = newUserState;
|
||||||
return newUserState;
|
return newUserState;
|
||||||
|
@ -10,6 +10,7 @@ import { Jsonify } from "type-fest";
|
|||||||
import { trackEmissions, awaitAsync } from "../../../../spec";
|
import { trackEmissions, awaitAsync } from "../../../../spec";
|
||||||
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import { Utils } from "../../misc/utils";
|
import { Utils } from "../../misc/utils";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
@ -45,6 +46,7 @@ describe("DefaultSingleUserState", () => {
|
|||||||
let diskStorageService: FakeStorageService;
|
let diskStorageService: FakeStorageService;
|
||||||
let userState: DefaultSingleUserState<TestState>;
|
let userState: DefaultSingleUserState<TestState>;
|
||||||
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
||||||
|
const logService = mock<LogService>();
|
||||||
const newData = { date: new Date() };
|
const newData = { date: new Date() };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -54,6 +56,7 @@ describe("DefaultSingleUserState", () => {
|
|||||||
testKeyDefinition,
|
testKeyDefinition,
|
||||||
diskStorageService,
|
diskStorageService,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
|
logService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -108,15 +111,23 @@ describe("DefaultSingleUserState", () => {
|
|||||||
cleanupDelayMs: 0,
|
cleanupDelayMs: 0,
|
||||||
deserializer: TestState.fromJSON,
|
deserializer: TestState.fromJSON,
|
||||||
clearOn: [],
|
clearOn: [],
|
||||||
|
debug: {
|
||||||
|
enableRetrievalLogging: true,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
diskStorageService,
|
diskStorageService,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
|
logService,
|
||||||
);
|
);
|
||||||
|
|
||||||
await firstValueFrom(state.state$);
|
await firstValueFrom(state.state$);
|
||||||
await firstValueFrom(state.state$);
|
await firstValueFrom(state.state$);
|
||||||
|
|
||||||
expect(diskStorageService.mock.get).toHaveBeenCalledTimes(2);
|
expect(diskStorageService.mock.get).toHaveBeenCalledTimes(2);
|
||||||
|
expect(logService.info).toHaveBeenCalledTimes(2);
|
||||||
|
expect(logService.info).toHaveBeenCalledWith(
|
||||||
|
`Retrieving 'user_${userId}_fake_test' from storage, value is null`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -324,6 +335,57 @@ describe("DefaultSingleUserState", () => {
|
|||||||
expect(stateEventRegistrarService.registerEvents).not.toHaveBeenCalled();
|
expect(stateEventRegistrarService.registerEvents).not.toHaveBeenCalled();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const logCases: { startingValue: TestState; updateValue: TestState; phrase: string }[] = [
|
||||||
|
{
|
||||||
|
startingValue: null,
|
||||||
|
updateValue: null,
|
||||||
|
phrase: "null to null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startingValue: null,
|
||||||
|
updateValue: new TestState(),
|
||||||
|
phrase: "null to non-null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startingValue: new TestState(),
|
||||||
|
updateValue: null,
|
||||||
|
phrase: "non-null to null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startingValue: new TestState(),
|
||||||
|
updateValue: new TestState(),
|
||||||
|
phrase: "non-null to non-null",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(logCases)(
|
||||||
|
"should log meta info about the update",
|
||||||
|
async ({ startingValue, updateValue, phrase }) => {
|
||||||
|
diskStorageService.internalUpdateStore({
|
||||||
|
[`user_${userId}_fake_fake`]: startingValue,
|
||||||
|
});
|
||||||
|
const state = new DefaultSingleUserState(
|
||||||
|
userId,
|
||||||
|
new UserKeyDefinition<TestState>(testStateDefinition, "fake", {
|
||||||
|
deserializer: TestState.fromJSON,
|
||||||
|
clearOn: [],
|
||||||
|
debug: {
|
||||||
|
enableUpdateLogging: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
diskStorageService,
|
||||||
|
stateEventRegistrarService,
|
||||||
|
logService,
|
||||||
|
);
|
||||||
|
|
||||||
|
await state.update(() => updateValue);
|
||||||
|
|
||||||
|
expect(logService.info).toHaveBeenCalledWith(
|
||||||
|
`Updating 'user_${userId}_fake_fake' from ${phrase}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("update races", () => {
|
describe("update races", () => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Observable, combineLatest, of } from "rxjs";
|
import { Observable, combineLatest, of } from "rxjs";
|
||||||
|
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import {
|
import {
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
@ -22,8 +23,9 @@ export class DefaultSingleUserState<T>
|
|||||||
keyDefinition: UserKeyDefinition<T>,
|
keyDefinition: UserKeyDefinition<T>,
|
||||||
chosenLocation: AbstractStorageService & ObservableStorageService,
|
chosenLocation: AbstractStorageService & ObservableStorageService,
|
||||||
private stateEventRegistrarService: StateEventRegistrarService,
|
private stateEventRegistrarService: StateEventRegistrarService,
|
||||||
|
logService: LogService,
|
||||||
) {
|
) {
|
||||||
super(keyDefinition.buildKey(userId), chosenLocation, keyDefinition);
|
super(keyDefinition.buildKey(userId), chosenLocation, keyDefinition, logService);
|
||||||
this.combinedState$ = combineLatest([of(userId), this.state$]);
|
this.combinedState$ = combineLatest([of(userId), this.state$]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { mock } from "jest-mock-extended";
|
|||||||
import { mockAccountServiceWith } from "../../../../spec/fake-account-service";
|
import { mockAccountServiceWith } from "../../../../spec/fake-account-service";
|
||||||
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
import { KeyDefinition } from "../key-definition";
|
import { KeyDefinition } from "../key-definition";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
@ -19,6 +20,7 @@ import { DefaultSingleUserStateProvider } from "./default-single-user-state.prov
|
|||||||
describe("Specific State Providers", () => {
|
describe("Specific State Providers", () => {
|
||||||
const storageServiceProvider = mock<StorageServiceProvider>();
|
const storageServiceProvider = mock<StorageServiceProvider>();
|
||||||
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
||||||
|
const logService = mock<LogService>();
|
||||||
|
|
||||||
let singleSut: DefaultSingleUserStateProvider;
|
let singleSut: DefaultSingleUserStateProvider;
|
||||||
let activeSut: DefaultActiveUserStateProvider;
|
let activeSut: DefaultActiveUserStateProvider;
|
||||||
@ -34,9 +36,10 @@ describe("Specific State Providers", () => {
|
|||||||
singleSut = new DefaultSingleUserStateProvider(
|
singleSut = new DefaultSingleUserStateProvider(
|
||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
|
logService,
|
||||||
);
|
);
|
||||||
activeSut = new DefaultActiveUserStateProvider(mockAccountServiceWith(null), singleSut);
|
activeSut = new DefaultActiveUserStateProvider(mockAccountServiceWith(null), singleSut);
|
||||||
globalSut = new DefaultGlobalStateProvider(storageServiceProvider);
|
globalSut = new DefaultGlobalStateProvider(storageServiceProvider, logService);
|
||||||
});
|
});
|
||||||
|
|
||||||
const fakeDiskStateDefinition = new StateDefinition("fake", "disk");
|
const fakeDiskStateDefinition = new StateDefinition("fake", "disk");
|
||||||
|
@ -7,16 +7,19 @@ import {
|
|||||||
merge,
|
merge,
|
||||||
share,
|
share,
|
||||||
switchMap,
|
switchMap,
|
||||||
|
tap,
|
||||||
timeout,
|
timeout,
|
||||||
timer,
|
timer,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
import { StorageKey } from "../../../types/state";
|
import { StorageKey } from "../../../types/state";
|
||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
import {
|
import {
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "../../abstractions/storage.service";
|
} from "../../abstractions/storage.service";
|
||||||
|
import { DebugOptions } from "../key-definition";
|
||||||
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
||||||
|
|
||||||
import { getStoredValue } from "./util";
|
import { getStoredValue } from "./util";
|
||||||
@ -25,6 +28,7 @@ import { getStoredValue } from "./util";
|
|||||||
type KeyDefinitionRequirements<T> = {
|
type KeyDefinitionRequirements<T> = {
|
||||||
deserializer: (jsonState: Jsonify<T>) => T;
|
deserializer: (jsonState: Jsonify<T>) => T;
|
||||||
cleanupDelayMs: number;
|
cleanupDelayMs: number;
|
||||||
|
debug: Required<DebugOptions>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>> {
|
export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>> {
|
||||||
@ -36,6 +40,7 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
|
|||||||
protected readonly key: StorageKey,
|
protected readonly key: StorageKey,
|
||||||
protected readonly storageService: AbstractStorageService & ObservableStorageService,
|
protected readonly storageService: AbstractStorageService & ObservableStorageService,
|
||||||
protected readonly keyDefinition: KeyDef,
|
protected readonly keyDefinition: KeyDef,
|
||||||
|
protected readonly logService: LogService,
|
||||||
) {
|
) {
|
||||||
const storageUpdate$ = storageService.updates$.pipe(
|
const storageUpdate$ = storageService.updates$.pipe(
|
||||||
filter((storageUpdate) => storageUpdate.key === key),
|
filter((storageUpdate) => storageUpdate.key === key),
|
||||||
@ -53,6 +58,18 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
|
|||||||
storageUpdate$,
|
storageUpdate$,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (keyDefinition.debug.enableRetrievalLogging) {
|
||||||
|
state$ = state$.pipe(
|
||||||
|
tap({
|
||||||
|
next: (v) => {
|
||||||
|
this.logService.info(
|
||||||
|
`Retrieving '${key}' from storage, value is ${v == null ? "null" : "non-null"}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If 0 cleanup is chosen, treat this as absolutely no cache
|
// If 0 cleanup is chosen, treat this as absolutely no cache
|
||||||
if (keyDefinition.cleanupDelayMs !== 0) {
|
if (keyDefinition.cleanupDelayMs !== 0) {
|
||||||
state$ = state$.pipe(
|
state$ = state$.pipe(
|
||||||
@ -104,6 +121,11 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async doStorageSave(newState: T, oldState: T) {
|
protected async doStorageSave(newState: T, 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"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
await this.storageService.save(this.key, newState);
|
await this.storageService.save(this.key, newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Opaque } from "type-fest";
|
import { Opaque } from "type-fest";
|
||||||
|
|
||||||
import { KeyDefinition } from "./key-definition";
|
import { DebugOptions, KeyDefinition } from "./key-definition";
|
||||||
import { StateDefinition } from "./state-definition";
|
import { StateDefinition } from "./state-definition";
|
||||||
|
|
||||||
const fakeStateDefinition = new StateDefinition("fake", "disk");
|
const fakeStateDefinition = new StateDefinition("fake", "disk");
|
||||||
@ -16,6 +16,97 @@ describe("KeyDefinition", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes debug options set to undefined", () => {
|
||||||
|
const keyDefinition = new KeyDefinition(fakeStateDefinition, "fake", {
|
||||||
|
deserializer: (v) => v,
|
||||||
|
debug: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(keyDefinition.debug.enableUpdateLogging).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes no debug options", () => {
|
||||||
|
const keyDefinition = new KeyDefinition(fakeStateDefinition, "fake", {
|
||||||
|
deserializer: (v) => v,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(keyDefinition.debug.enableUpdateLogging).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cases: {
|
||||||
|
debug: DebugOptions | undefined;
|
||||||
|
expectedEnableUpdateLogging: boolean;
|
||||||
|
expectedEnableRetrievalLogging: boolean;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
debug: undefined,
|
||||||
|
expectedEnableUpdateLogging: false,
|
||||||
|
expectedEnableRetrievalLogging: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
debug: {},
|
||||||
|
expectedEnableUpdateLogging: false,
|
||||||
|
expectedEnableRetrievalLogging: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
debug: {
|
||||||
|
enableUpdateLogging: false,
|
||||||
|
},
|
||||||
|
expectedEnableUpdateLogging: false,
|
||||||
|
expectedEnableRetrievalLogging: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
debug: {
|
||||||
|
enableRetrievalLogging: false,
|
||||||
|
},
|
||||||
|
expectedEnableUpdateLogging: false,
|
||||||
|
expectedEnableRetrievalLogging: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
debug: {
|
||||||
|
enableUpdateLogging: true,
|
||||||
|
},
|
||||||
|
expectedEnableUpdateLogging: true,
|
||||||
|
expectedEnableRetrievalLogging: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
debug: {
|
||||||
|
enableRetrievalLogging: true,
|
||||||
|
},
|
||||||
|
expectedEnableUpdateLogging: false,
|
||||||
|
expectedEnableRetrievalLogging: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
debug: {
|
||||||
|
enableRetrievalLogging: false,
|
||||||
|
enableUpdateLogging: false,
|
||||||
|
},
|
||||||
|
expectedEnableUpdateLogging: false,
|
||||||
|
expectedEnableRetrievalLogging: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
debug: {
|
||||||
|
enableRetrievalLogging: true,
|
||||||
|
enableUpdateLogging: true,
|
||||||
|
},
|
||||||
|
expectedEnableUpdateLogging: true,
|
||||||
|
expectedEnableRetrievalLogging: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(cases)(
|
||||||
|
"normalizes debug options to correct values when given $debug",
|
||||||
|
({ debug, expectedEnableUpdateLogging, expectedEnableRetrievalLogging }) => {
|
||||||
|
const keyDefinition = new KeyDefinition(fakeStateDefinition, "fake", {
|
||||||
|
deserializer: (v) => v,
|
||||||
|
debug: debug,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(keyDefinition.debug.enableUpdateLogging).toBe(expectedEnableUpdateLogging);
|
||||||
|
expect(keyDefinition.debug.enableRetrievalLogging).toBe(expectedEnableRetrievalLogging);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("cleanupDelayMs", () => {
|
describe("cleanupDelayMs", () => {
|
||||||
|
@ -5,6 +5,28 @@ import { StorageKey } from "../../types/state";
|
|||||||
import { array, record } from "./deserialization-helpers";
|
import { array, record } from "./deserialization-helpers";
|
||||||
import { StateDefinition } from "./state-definition";
|
import { StateDefinition } from "./state-definition";
|
||||||
|
|
||||||
|
export type DebugOptions = {
|
||||||
|
/**
|
||||||
|
* When true, logs will be written that look like the following:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* "Updating 'global_myState_myKey' from null to non-null"
|
||||||
|
* "Updating 'user_32265eda-62ff-4797-9ead-22214772f888_myState_myKey' from non-null to null."
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* It does not include the value of the data, only whether it is null or non-null.
|
||||||
|
*/
|
||||||
|
enableUpdateLogging?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, logs will be written that look like the following everytime a value is retrieved from storage.
|
||||||
|
*
|
||||||
|
* "Retrieving 'global_myState_myKey' from storage, value is null."
|
||||||
|
* "Retrieving 'user_32265eda-62ff-4797-9ead-22214772f888_myState_myKey' from storage, value is non-null."
|
||||||
|
*/
|
||||||
|
enableRetrievalLogging?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of options for customizing the behavior of a {@link KeyDefinition}
|
* A set of options for customizing the behavior of a {@link KeyDefinition}
|
||||||
*/
|
*/
|
||||||
@ -24,6 +46,11 @@ export type KeyDefinitionOptions<T> = {
|
|||||||
* Defaults to 1000ms.
|
* Defaults to 1000ms.
|
||||||
*/
|
*/
|
||||||
readonly cleanupDelayMs?: number;
|
readonly cleanupDelayMs?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for configuring the debugging behavior, see individual options for more info.
|
||||||
|
*/
|
||||||
|
readonly debug?: DebugOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,6 +59,8 @@ export type KeyDefinitionOptions<T> = {
|
|||||||
* sub-divides that domain into specific keys.
|
* sub-divides that domain into specific keys.
|
||||||
*/
|
*/
|
||||||
export class KeyDefinition<T> {
|
export class KeyDefinition<T> {
|
||||||
|
readonly debug: Required<DebugOptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of a KeyDefinition
|
* Creates a new instance of a KeyDefinition
|
||||||
* @param stateDefinition The state definition for which this key belongs to.
|
* @param stateDefinition The state definition for which this key belongs to.
|
||||||
@ -55,6 +84,13 @@ export class KeyDefinition<T> {
|
|||||||
`'cleanupDelayMs' must be greater than or equal to 0. Value of ${options.cleanupDelayMs} passed to key ${this.errorKeyName} `,
|
`'cleanupDelayMs' must be greater than or equal to 0. Value of ${options.cleanupDelayMs} passed to key ${this.errorKeyName} `,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize optional debug options
|
||||||
|
const { enableUpdateLogging = false, enableRetrievalLogging = false } = options.debug ?? {};
|
||||||
|
this.debug = {
|
||||||
|
enableUpdateLogging,
|
||||||
|
enableRetrievalLogging,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,7 +3,7 @@ import { StorageKey } from "../../types/state";
|
|||||||
import { Utils } from "../misc/utils";
|
import { Utils } from "../misc/utils";
|
||||||
|
|
||||||
import { array, record } from "./deserialization-helpers";
|
import { array, record } from "./deserialization-helpers";
|
||||||
import { KeyDefinitionOptions } from "./key-definition";
|
import { DebugOptions, KeyDefinitionOptions } from "./key-definition";
|
||||||
import { StateDefinition } from "./state-definition";
|
import { StateDefinition } from "./state-definition";
|
||||||
|
|
||||||
export type ClearEvent = "lock" | "logout";
|
export type ClearEvent = "lock" | "logout";
|
||||||
@ -21,6 +21,11 @@ export class UserKeyDefinition<T> {
|
|||||||
*/
|
*/
|
||||||
readonly clearOn: ClearEvent[];
|
readonly clearOn: ClearEvent[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalized options used for debugging purposes.
|
||||||
|
*/
|
||||||
|
readonly debug: Required<DebugOptions>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly stateDefinition: StateDefinition,
|
readonly stateDefinition: StateDefinition,
|
||||||
readonly key: string,
|
readonly key: string,
|
||||||
@ -38,6 +43,13 @@ export class UserKeyDefinition<T> {
|
|||||||
|
|
||||||
// Filter out repeat values
|
// Filter out repeat values
|
||||||
this.clearOn = Array.from(new Set(options.clearOn));
|
this.clearOn = Array.from(new Set(options.clearOn));
|
||||||
|
|
||||||
|
// Normalize optional debug options
|
||||||
|
const { enableUpdateLogging = false, enableRetrievalLogging = false } = options.debug ?? {};
|
||||||
|
this.debug = {
|
||||||
|
enableUpdateLogging,
|
||||||
|
enableRetrievalLogging,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user