1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 10:13:31 +00:00

[PM-6654] new app id service for angular (#8229)

* Improve state documentation

* Add namespace for application id

* Spec out behavior of app id service

* Use state providers for app ids

* Migrate app Id

* Add reactive interface
This commit is contained in:
Matt Gibson
2024-03-12 10:12:40 -05:00
committed by GitHub
parent 9e8f20a873
commit 5feb9af051
11 changed files with 414 additions and 30 deletions

View File

@@ -0,0 +1,101 @@
import { FakeGlobalStateProvider } from "../../../spec";
import { Utils } from "../misc/utils";
import { ANONYMOUS_APP_ID_KEY, APP_ID_KEY, AppIdService } from "./app-id.service";
describe("AppIdService", () => {
const globalStateProvider = new FakeGlobalStateProvider();
const appIdState = globalStateProvider.getFake(APP_ID_KEY);
const anonymousAppIdState = globalStateProvider.getFake(ANONYMOUS_APP_ID_KEY);
let sut: AppIdService;
beforeEach(() => {
sut = new AppIdService(globalStateProvider);
});
afterEach(() => {
jest.resetAllMocks();
});
describe("getAppId", () => {
it("returns the existing appId when it exists", async () => {
appIdState.stateSubject.next("existingAppId");
const appId = await sut.getAppId();
expect(appId).toBe("existingAppId");
});
it.each([null, undefined])(
"uses the util function to create a new id when it AppId does not exist",
async (value) => {
appIdState.stateSubject.next(value);
const spy = jest.spyOn(Utils, "newGuid");
await sut.getAppId();
expect(spy).toHaveBeenCalledTimes(1);
},
);
it.each([null, undefined])("returns a new appId when it does not exist", async (value) => {
appIdState.stateSubject.next(value);
const appId = await sut.getAppId();
expect(appId).toMatch(Utils.guidRegex);
});
it.each([null, undefined])(
"stores the new guid when it an existing one is not found",
async (value) => {
appIdState.stateSubject.next(value);
const appId = await sut.getAppId();
expect(appIdState.nextMock).toHaveBeenCalledWith(appId);
},
);
});
describe("getAnonymousAppId", () => {
it("returns the existing appId when it exists", async () => {
anonymousAppIdState.stateSubject.next("existingAppId");
const appId = await sut.getAnonymousAppId();
expect(appId).toBe("existingAppId");
});
it.each([null, undefined])(
"uses the util function to create a new id when it AppId does not exist",
async (value) => {
anonymousAppIdState.stateSubject.next(value);
const spy = jest.spyOn(Utils, "newGuid");
await sut.getAnonymousAppId();
expect(spy).toHaveBeenCalledTimes(1);
},
);
it.each([null, undefined])("returns a new appId when it does not exist", async (value) => {
anonymousAppIdState.stateSubject.next(value);
const appId = await sut.getAnonymousAppId();
expect(appId).toMatch(Utils.guidRegex);
});
it.each([null, undefined])(
"stores the new guid when it an existing one is not found",
async (value) => {
anonymousAppIdState.stateSubject.next(value);
const appId = await sut.getAnonymousAppId();
expect(anonymousAppIdState.nextMock).toHaveBeenCalledWith(appId);
},
);
});
});

View File

@@ -1,31 +1,46 @@
import { Observable, filter, firstValueFrom, tap } from "rxjs";
import { AppIdService as AppIdServiceAbstraction } from "../abstractions/app-id.service";
import { AbstractStorageService } from "../abstractions/storage.service";
import { HtmlStorageLocation } from "../enums";
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,
});
export const ANONYMOUS_APP_ID_KEY = new KeyDefinition(APPLICATION_ID_DISK, "anonymousAppId", {
deserializer: (value: string) => value,
});
export class AppIdService implements AppIdServiceAbstraction {
constructor(private storageService: AbstractStorageService) {}
appId$: Observable<string>;
anonymousAppId$: Observable<string>;
getAppId(): Promise<string> {
return this.makeAndGetAppId("appId");
constructor(globalStateProvider: GlobalStateProvider) {
const appIdState = globalStateProvider.get(APP_ID_KEY);
const anonymousAppIdState = globalStateProvider.get(ANONYMOUS_APP_ID_KEY);
this.appId$ = appIdState.state$.pipe(
tap(async (appId) => {
if (!appId) {
await appIdState.update(() => Utils.newGuid());
}
}),
filter((appId) => !!appId),
);
this.anonymousAppId$ = anonymousAppIdState.state$.pipe(
tap(async (appId) => {
if (!appId) {
await anonymousAppIdState.update(() => Utils.newGuid());
}
}),
filter((appId) => !!appId),
);
}
getAnonymousAppId(): Promise<string> {
return this.makeAndGetAppId("anonymousAppId");
async getAppId(): Promise<string> {
return await firstValueFrom(this.appId$);
}
private async makeAndGetAppId(key: string) {
const existingId = await this.storageService.get<string>(key, {
htmlStorageLocation: HtmlStorageLocation.Local,
});
if (existingId != null) {
return existingId;
}
const guid = Utils.newGuid();
await this.storageService.save(key, guid, {
htmlStorageLocation: HtmlStorageLocation.Local,
});
return guid;
async getAnonymousAppId(): Promise<string> {
return await firstValueFrom(this.anonymousAppId$);
}
}