mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00: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:
@@ -173,3 +173,7 @@ export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerRepro
|
|||||||
});
|
});
|
||||||
export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk");
|
export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk");
|
||||||
export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk");
|
export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk");
|
||||||
|
export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition(
|
||||||
|
"newDeviceVerificationNotice",
|
||||||
|
"disk",
|
||||||
|
);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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 };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user