1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

Change AppIdService to Use Storage Directly (#10835)

This commit is contained in:
Justin Baur
2024-09-05 14:44:19 -04:00
committed by GitHub
parent f58138a51d
commit 3bbc2cc691
7 changed files with 65 additions and 92 deletions

View File

@@ -370,7 +370,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: AppIdServiceAbstraction,
useClass: AppIdService,
deps: [GlobalStateProvider],
deps: [OBSERVABLE_DISK_STORAGE, LogService],
}),
safeProvider({
provide: AuditServiceAbstraction,

View File

@@ -1,8 +1,4 @@
import { Observable } from "rxjs";
export abstract class AppIdService {
abstract appId$: Observable<string>;
abstract anonymousAppId$: Observable<string>;
abstract getAppId(): Promise<string>;
abstract getAnonymousAppId(): Promise<string>;
}

View File

@@ -1,19 +1,18 @@
import { FakeGlobalState, FakeGlobalStateProvider, ObservableTracker } from "../../../spec";
import { mock } from "jest-mock-extended";
import { FakeStorageService } from "../../../spec";
import { LogService } from "../abstractions/log.service";
import { Utils } from "../misc/utils";
import { ANONYMOUS_APP_ID_KEY, APP_ID_KEY, AppIdService } from "./app-id.service";
describe("AppIdService", () => {
let globalStateProvider: FakeGlobalStateProvider;
let appIdState: FakeGlobalState<string>;
let anonymousAppIdState: FakeGlobalState<string>;
let fakeStorageService: FakeStorageService;
let sut: AppIdService;
beforeEach(() => {
globalStateProvider = new FakeGlobalStateProvider();
appIdState = globalStateProvider.getFake(APP_ID_KEY);
anonymousAppIdState = globalStateProvider.getFake(ANONYMOUS_APP_ID_KEY);
sut = new AppIdService(globalStateProvider);
fakeStorageService = new FakeStorageService();
sut = new AppIdService(fakeStorageService, mock<LogService>());
});
afterEach(() => {
@@ -22,7 +21,7 @@ describe("AppIdService", () => {
describe("getAppId", () => {
it("returns the existing appId when it exists", async () => {
appIdState.stateSubject.next("existingAppId");
fakeStorageService.internalUpdateStore({ [APP_ID_KEY]: "existingAppId" });
const appId = await sut.getAppId();
@@ -30,7 +29,7 @@ describe("AppIdService", () => {
});
it("creates a new appId only once", async () => {
appIdState.stateSubject.next(null);
fakeStorageService.internalUpdateStore({ [APP_ID_KEY]: null });
const appIds: string[] = [];
const promises = [async () => appIds.push(await sut.getAppId())];
@@ -41,7 +40,7 @@ describe("AppIdService", () => {
});
it.each([null, undefined])("returns a new appId when %s", async (value) => {
appIdState.stateSubject.next(value);
fakeStorageService.internalUpdateStore({ [APP_ID_KEY]: value });
const appId = await sut.getAppId();
@@ -49,27 +48,17 @@ describe("AppIdService", () => {
});
it.each([null, undefined])("stores the new guid when %s", async (value) => {
appIdState.stateSubject.next(value);
fakeStorageService.internalUpdateStore({ [APP_ID_KEY]: value });
const appId = await sut.getAppId();
expect(appIdState.nextMock).toHaveBeenCalledWith(appId);
});
it("emits only once when creating a new appId", async () => {
appIdState.stateSubject.next(null);
const tracker = new ObservableTracker(sut.appId$);
const appId = await sut.getAppId();
expect(tracker.emissions).toEqual([appId]);
await expect(tracker.pauseUntilReceived(2, 50)).rejects.toThrow("Timeout exceeded");
expect(fakeStorageService.mock.save).toHaveBeenCalledWith(APP_ID_KEY, appId, undefined);
});
});
describe("getAnonymousAppId", () => {
it("returns the existing appId when it exists", async () => {
anonymousAppIdState.stateSubject.next("existingAppId");
fakeStorageService.internalUpdateStore({ [ANONYMOUS_APP_ID_KEY]: "existingAppId" });
const appId = await sut.getAnonymousAppId();
@@ -77,7 +66,7 @@ describe("AppIdService", () => {
});
it("creates a new anonymousAppId only once", async () => {
anonymousAppIdState.stateSubject.next(null);
fakeStorageService.internalUpdateStore({ [ANONYMOUS_APP_ID_KEY]: null });
const appIds: string[] = [];
const promises = [async () => appIds.push(await sut.getAnonymousAppId())];
@@ -88,7 +77,7 @@ describe("AppIdService", () => {
});
it.each([null, undefined])("returns a new appId when it does not exist", async (value) => {
anonymousAppIdState.stateSubject.next(value);
fakeStorageService.internalUpdateStore({ [ANONYMOUS_APP_ID_KEY]: value });
const appId = await sut.getAnonymousAppId();
@@ -98,22 +87,16 @@ describe("AppIdService", () => {
it.each([null, undefined])(
"stores the new guid when it an existing one is not found",
async (value) => {
anonymousAppIdState.stateSubject.next(value);
fakeStorageService.internalUpdateStore({ [ANONYMOUS_APP_ID_KEY]: value });
const appId = await sut.getAnonymousAppId();
expect(anonymousAppIdState.nextMock).toHaveBeenCalledWith(appId);
expect(fakeStorageService.mock.save).toHaveBeenCalledWith(
ANONYMOUS_APP_ID_KEY,
appId,
undefined,
);
},
);
it("emits only once when creating a new anonymousAppId", async () => {
anonymousAppIdState.stateSubject.next(null);
const tracker = new ObservableTracker(sut.anonymousAppId$);
const appId = await sut.getAnonymousAppId();
expect(tracker.emissions).toEqual([appId]);
await expect(tracker.pauseUntilReceived(2, 50)).rejects.toThrow("Timeout exceeded");
});
});
});

View File

@@ -1,59 +1,46 @@
import { Observable, concatMap, distinctUntilChanged, firstValueFrom, share } from "rxjs";
import { AppIdService as AppIdServiceAbstraction } from "../abstractions/app-id.service";
import { LogService } from "../abstractions/log.service";
import { AbstractStorageService } from "../abstractions/storage.service";
import { Utils } from "../misc/utils";
import { APPLICATION_ID_DISK, GlobalStateProvider, KeyDefinition } from "../state";
export const APP_ID_KEY = new KeyDefinition(APPLICATION_ID_DISK, "appId", {
deserializer: (value: string) => value,
cleanupDelayMs: 0,
debug: {
enableRetrievalLogging: true,
enableUpdateLogging: true,
},
});
export const ANONYMOUS_APP_ID_KEY = new KeyDefinition(APPLICATION_ID_DISK, "anonymousAppId", {
deserializer: (value: string) => value,
});
// export const APP_ID_KEY = new KeyDefinition(APPLICATION_ID_DISK, "appId", {
// deserializer: (value: string) => value,
// cleanupDelayMs: 0,
// debug: {
// enableRetrievalLogging: true,
// enableUpdateLogging: true,
// },
// });
// export const ANONYMOUS_APP_ID_KEY = new KeyDefinition(APPLICATION_ID_DISK, "anonymousAppId", {
// deserializer: (value: string) => value,
// });
export const APP_ID_KEY = "global_applicationId_appId";
export const ANONYMOUS_APP_ID_KEY = "global_applicationId_appId";
export class AppIdService implements AppIdServiceAbstraction {
appId$: Observable<string>;
anonymousAppId$: Observable<string>;
constructor(globalStateProvider: GlobalStateProvider) {
const appIdState = globalStateProvider.get(APP_ID_KEY);
const anonymousAppIdState = globalStateProvider.get(ANONYMOUS_APP_ID_KEY);
this.appId$ = appIdState.state$.pipe(
concatMap(async (appId) => {
if (!appId) {
return await appIdState.update(() => Utils.newGuid(), {
shouldUpdate: (v) => v == null,
});
}
return appId;
}),
distinctUntilChanged(),
share(),
);
this.anonymousAppId$ = anonymousAppIdState.state$.pipe(
concatMap(async (appId) => {
if (!appId) {
return await anonymousAppIdState.update(() => Utils.newGuid(), {
shouldUpdate: (v) => v == null,
});
}
return appId;
}),
distinctUntilChanged(),
share(),
);
}
constructor(
private readonly storageService: AbstractStorageService,
private readonly logService: LogService,
) {}
async getAppId(): Promise<string> {
return await firstValueFrom(this.appId$);
this.logService.info("Retrieving application id");
return await this.getEnsuredValue(APP_ID_KEY);
}
async getAnonymousAppId(): Promise<string> {
return await firstValueFrom(this.anonymousAppId$);
return await this.getEnsuredValue(ANONYMOUS_APP_ID_KEY);
}
private async getEnsuredValue(key: string) {
let value = await this.storageService.get<string | null>(key);
if (value == null) {
value = Utils.newGuid();
await this.storageService.save(key, value);
}
return value;
}
}