1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-21 16:18:28 +01:00

[PM-14219] Add service for new device verification notice (#11988)

* added service and spec file for new device verification notice
This commit is contained in:
Jason Ng 2024-11-19 10:04:40 -05:00 committed by GitHub
parent 140a514be3
commit 21855595c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 154 additions and 0 deletions

View File

@ -173,3 +173,7 @@ export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerRepro
});
export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk");
export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk");
export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition(
"newDeviceVerificationNotice",
"disk",
);

View File

@ -0,0 +1,85 @@
import { firstValueFrom } from "rxjs";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import {
FakeAccountService,
FakeSingleUserState,
FakeStateProvider,
mockAccountServiceWith,
} from "../../../common/spec";
import {
NewDeviceVerificationNoticeService,
NewDeviceVerificationNotice,
NEW_DEVICE_VERIFICATION_NOTICE_KEY,
} from "./new-device-verification-notice.service";
describe("New Device Verification Notice", () => {
const sut = NEW_DEVICE_VERIFICATION_NOTICE_KEY;
const userId = Utils.newGuid() as UserId;
let newDeviceVerificationService: NewDeviceVerificationNoticeService;
let mockNoticeState: FakeSingleUserState<NewDeviceVerificationNotice>;
let stateProvider: FakeStateProvider;
let accountService: FakeAccountService;
beforeEach(() => {
accountService = mockAccountServiceWith(userId);
stateProvider = new FakeStateProvider(accountService);
mockNoticeState = stateProvider.singleUser.getFake(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
newDeviceVerificationService = new NewDeviceVerificationNoticeService(stateProvider);
});
it("should deserialize newDeviceVerificationNotice values", async () => {
const currentDate = new Date();
const inputObj = {
last_dismissal: currentDate,
permanent_dismissal: false,
};
const expectedFolderData = {
last_dismissal: currentDate.toJSON(),
permanent_dismissal: false,
};
const result = sut.deserializer(JSON.parse(JSON.stringify(inputObj)));
expect(result).toEqual(expectedFolderData);
});
describe("notice$", () => {
it("emits new device verification notice state", async () => {
const currentDate = new Date();
const data = {
last_dismissal: currentDate,
permanent_dismissal: false,
};
await stateProvider.setUserState(NEW_DEVICE_VERIFICATION_NOTICE_KEY, data, userId);
const result = await firstValueFrom(newDeviceVerificationService.noticeState$(userId));
expect(result).toBe(data);
});
});
describe("update notice state", () => {
it("should update the date with a new value", async () => {
const currentDate = new Date();
const oldDate = new Date("11-11-2011");
const oldState = {
last_dismissal: oldDate,
permanent_dismissal: false,
};
const newState = {
last_dismissal: currentDate,
permanent_dismissal: true,
};
mockNoticeState.nextState(oldState);
await newDeviceVerificationService.updateNewDeviceVerificationNoticeState(userId, newState);
const result = await firstValueFrom(newDeviceVerificationService.noticeState$(userId));
expect(result).toEqual(newState);
});
});
});

View File

@ -0,0 +1,65 @@
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { Jsonify } from "type-fest";
import {
StateProvider,
UserKeyDefinition,
NEW_DEVICE_VERIFICATION_NOTICE,
SingleUserState,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
// This service checks when to show New Device Verification Notice to Users
// It will be a two phase approach and the values below will work with two different feature flags
// If a user dismisses the notice, use "last_dismissal" to wait 7 days before re-prompting
// permanent_dismissal will be checked if the user should never see the notice again
export class NewDeviceVerificationNotice {
last_dismissal: Date;
permanent_dismissal: boolean;
constructor(obj: Partial<NewDeviceVerificationNotice>) {
if (obj == null) {
return;
}
this.last_dismissal = obj.last_dismissal || null;
this.permanent_dismissal = obj.permanent_dismissal || null;
}
static fromJSON(obj: Jsonify<NewDeviceVerificationNotice>) {
return Object.assign(new NewDeviceVerificationNotice({}), obj);
}
}
export const NEW_DEVICE_VERIFICATION_NOTICE_KEY =
new UserKeyDefinition<NewDeviceVerificationNotice>(
NEW_DEVICE_VERIFICATION_NOTICE,
"noticeState",
{
deserializer: (obj: Jsonify<NewDeviceVerificationNotice>) =>
NewDeviceVerificationNotice.fromJSON(obj),
clearOn: [],
},
);
@Injectable()
export class NewDeviceVerificationNoticeService {
constructor(private stateProvider: StateProvider) {}
private noticeState(userId: UserId): SingleUserState<NewDeviceVerificationNotice> {
return this.stateProvider.getUser(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
}
noticeState$(userId: UserId): Observable<NewDeviceVerificationNotice> {
return this.noticeState(userId).state$;
}
async updateNewDeviceVerificationNoticeState(
userId: UserId,
newState: NewDeviceVerificationNotice,
): Promise<void> {
await this.noticeState(userId).update(() => {
return { ...newState };
});
}
}