Skip to content

Commit 2185559

Browse files
authored
[PM-14219] Add service for new device verification notice (#11988)
* added service and spec file for new device verification notice
1 parent 140a514 commit 2185559

File tree

3 files changed

+154
-0
lines changed

3 files changed

+154
-0
lines changed

libs/common/src/platform/state/state-definitions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,7 @@ export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerRepro
173173
});
174174
export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk");
175175
export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk");
176+
export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition(
177+
"newDeviceVerificationNotice",
178+
"disk",
179+
);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { firstValueFrom } from "rxjs";
2+
3+
import { Utils } from "@bitwarden/common/platform/misc/utils";
4+
import { UserId } from "@bitwarden/common/types/guid";
5+
6+
import {
7+
FakeAccountService,
8+
FakeSingleUserState,
9+
FakeStateProvider,
10+
mockAccountServiceWith,
11+
} from "../../../common/spec";
12+
13+
import {
14+
NewDeviceVerificationNoticeService,
15+
NewDeviceVerificationNotice,
16+
NEW_DEVICE_VERIFICATION_NOTICE_KEY,
17+
} from "./new-device-verification-notice.service";
18+
19+
describe("New Device Verification Notice", () => {
20+
const sut = NEW_DEVICE_VERIFICATION_NOTICE_KEY;
21+
const userId = Utils.newGuid() as UserId;
22+
let newDeviceVerificationService: NewDeviceVerificationNoticeService;
23+
let mockNoticeState: FakeSingleUserState<NewDeviceVerificationNotice>;
24+
let stateProvider: FakeStateProvider;
25+
let accountService: FakeAccountService;
26+
27+
beforeEach(() => {
28+
accountService = mockAccountServiceWith(userId);
29+
stateProvider = new FakeStateProvider(accountService);
30+
mockNoticeState = stateProvider.singleUser.getFake(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
31+
newDeviceVerificationService = new NewDeviceVerificationNoticeService(stateProvider);
32+
});
33+
34+
it("should deserialize newDeviceVerificationNotice values", async () => {
35+
const currentDate = new Date();
36+
const inputObj = {
37+
last_dismissal: currentDate,
38+
permanent_dismissal: false,
39+
};
40+
41+
const expectedFolderData = {
42+
last_dismissal: currentDate.toJSON(),
43+
permanent_dismissal: false,
44+
};
45+
46+
const result = sut.deserializer(JSON.parse(JSON.stringify(inputObj)));
47+
48+
expect(result).toEqual(expectedFolderData);
49+
});
50+
51+
describe("notice$", () => {
52+
it("emits new device verification notice state", async () => {
53+
const currentDate = new Date();
54+
const data = {
55+
last_dismissal: currentDate,
56+
permanent_dismissal: false,
57+
};
58+
await stateProvider.setUserState(NEW_DEVICE_VERIFICATION_NOTICE_KEY, data, userId);
59+
60+
const result = await firstValueFrom(newDeviceVerificationService.noticeState$(userId));
61+
62+
expect(result).toBe(data);
63+
});
64+
});
65+
66+
describe("update notice state", () => {
67+
it("should update the date with a new value", async () => {
68+
const currentDate = new Date();
69+
const oldDate = new Date("11-11-2011");
70+
const oldState = {
71+
last_dismissal: oldDate,
72+
permanent_dismissal: false,
73+
};
74+
const newState = {
75+
last_dismissal: currentDate,
76+
permanent_dismissal: true,
77+
};
78+
mockNoticeState.nextState(oldState);
79+
await newDeviceVerificationService.updateNewDeviceVerificationNoticeState(userId, newState);
80+
81+
const result = await firstValueFrom(newDeviceVerificationService.noticeState$(userId));
82+
expect(result).toEqual(newState);
83+
});
84+
});
85+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Injectable } from "@angular/core";
2+
import { Observable } from "rxjs";
3+
import { Jsonify } from "type-fest";
4+
5+
import {
6+
StateProvider,
7+
UserKeyDefinition,
8+
NEW_DEVICE_VERIFICATION_NOTICE,
9+
SingleUserState,
10+
} from "@bitwarden/common/platform/state";
11+
import { UserId } from "@bitwarden/common/types/guid";
12+
13+
// This service checks when to show New Device Verification Notice to Users
14+
// It will be a two phase approach and the values below will work with two different feature flags
15+
// If a user dismisses the notice, use "last_dismissal" to wait 7 days before re-prompting
16+
// permanent_dismissal will be checked if the user should never see the notice again
17+
export class NewDeviceVerificationNotice {
18+
last_dismissal: Date;
19+
permanent_dismissal: boolean;
20+
21+
constructor(obj: Partial<NewDeviceVerificationNotice>) {
22+
if (obj == null) {
23+
return;
24+
}
25+
this.last_dismissal = obj.last_dismissal || null;
26+
this.permanent_dismissal = obj.permanent_dismissal || null;
27+
}
28+
29+
static fromJSON(obj: Jsonify<NewDeviceVerificationNotice>) {
30+
return Object.assign(new NewDeviceVerificationNotice({}), obj);
31+
}
32+
}
33+
34+
export const NEW_DEVICE_VERIFICATION_NOTICE_KEY =
35+
new UserKeyDefinition<NewDeviceVerificationNotice>(
36+
NEW_DEVICE_VERIFICATION_NOTICE,
37+
"noticeState",
38+
{
39+
deserializer: (obj: Jsonify<NewDeviceVerificationNotice>) =>
40+
NewDeviceVerificationNotice.fromJSON(obj),
41+
clearOn: [],
42+
},
43+
);
44+
45+
@Injectable()
46+
export class NewDeviceVerificationNoticeService {
47+
constructor(private stateProvider: StateProvider) {}
48+
49+
private noticeState(userId: UserId): SingleUserState<NewDeviceVerificationNotice> {
50+
return this.stateProvider.getUser(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
51+
}
52+
53+
noticeState$(userId: UserId): Observable<NewDeviceVerificationNotice> {
54+
return this.noticeState(userId).state$;
55+
}
56+
57+
async updateNewDeviceVerificationNoticeState(
58+
userId: UserId,
59+
newState: NewDeviceVerificationNotice,
60+
): Promise<void> {
61+
await this.noticeState(userId).update(() => {
62+
return { ...newState };
63+
});
64+
}
65+
}

0 commit comments

Comments
 (0)