Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 82ad8d5

Browse files
author
Kerry
authored
Snooze the bulk unverified sessions reminder on dismiss (#9706)
* test bulk unverified sessions toast behaviour * unverified sessions toast text tweak * only show bulk unverified sessions toast when current device is verified * add Setting for BulkUnverifiedSessionsReminder * add build config for BulkUnverifiedSessionsReminder * add more assertions for show/hide toast, fix strict errors * fix strict error * add util methods for snoozing in local storage * rename nag to reminder * set and read snooze for toast * test snooze * remove debug * strict fix * remove unused code
1 parent 75c2c1a commit 82ad8d5

File tree

5 files changed

+167
-0
lines changed

5 files changed

+167
-0
lines changed

src/DeviceListener.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
} from "./utils/device/clientInformation";
4949
import SettingsStore, { CallbackFn } from "./settings/SettingsStore";
5050
import { UIFeature } from "./settings/UIFeature";
51+
import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder";
5152

5253
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
5354

@@ -335,12 +336,15 @@ export default class DeviceListener {
335336
logger.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(','));
336337
logger.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(','));
337338

339+
const isBulkUnverifiedSessionsReminderSnoozed = isBulkUnverifiedDeviceReminderSnoozed();
340+
338341
// Display or hide the batch toast for old unverified sessions
339342
// don't show the toast if the current device is unverified
340343
if (
341344
oldUnverifiedDeviceIds.size > 0
342345
&& isCurrentDeviceTrusted
343346
&& this.enableBulkUnverifiedSessionsReminder
347+
&& !isBulkUnverifiedSessionsReminderSnoozed
344348
) {
345349
showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds);
346350
} else {

src/toasts/BulkUnverifiedSessionsToast.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import DeviceListener from '../DeviceListener';
2020
import GenericToast from "../components/views/toasts/GenericToast";
2121
import ToastStore from "../stores/ToastStore";
2222
import { Action } from "../dispatcher/actions";
23+
import { snoozeBulkUnverifiedDeviceReminder } from '../utils/device/snoozeBulkUnverifiedDeviceReminder';
2324

2425
const TOAST_KEY = "reviewsessions";
2526

@@ -34,6 +35,7 @@ export const showToast = (deviceIds: Set<string>) => {
3435

3536
const onReject = () => {
3637
DeviceListener.sharedInstance().dismissUnverifiedSessions(deviceIds);
38+
snoozeBulkUnverifiedDeviceReminder();
3739
};
3840

3941
ToastStore.sharedInstance().addOrReplaceToast({
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { logger } from "matrix-js-sdk/src/logger";
18+
19+
const SNOOZE_KEY = 'mx_snooze_bulk_unverified_device_nag';
20+
// one week
21+
const snoozePeriod = 1000 * 60 * 60 * 24 * 7;
22+
export const snoozeBulkUnverifiedDeviceReminder = () => {
23+
try {
24+
localStorage.setItem(SNOOZE_KEY, String(Date.now()));
25+
} catch (error) {
26+
logger.error('Failed to persist bulk unverified device nag snooze', error);
27+
}
28+
};
29+
30+
export const isBulkUnverifiedDeviceReminderSnoozed = () => {
31+
try {
32+
const snoozedTimestamp = localStorage.getItem(SNOOZE_KEY);
33+
34+
const parsedTimestamp = Number.parseInt(snoozedTimestamp || '', 10);
35+
36+
return Number.isInteger(parsedTimestamp) && (parsedTimestamp + snoozePeriod) > Date.now();
37+
} catch (error) {
38+
return false;
39+
}
40+
};

test/DeviceListener-test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import SettingsStore from "../src/settings/SettingsStore";
3535
import { SettingLevel } from "../src/settings/SettingLevel";
3636
import { getMockClientWithEventEmitter, mockPlatformPeg } from "./test-utils";
3737
import { UIFeature } from "../src/settings/UIFeature";
38+
import { isBulkUnverifiedDeviceReminderSnoozed } from "../src/utils/device/snoozeBulkUnverifiedDeviceReminder";
3839

3940
// don't litter test console with logs
4041
jest.mock("matrix-js-sdk/src/logger");
@@ -48,6 +49,10 @@ jest.mock("../src/SecurityManager", () => ({
4849
isSecretStorageBeingAccessed: jest.fn(), accessSecretStorage: jest.fn(),
4950
}));
5051

52+
jest.mock("../src/utils/device/snoozeBulkUnverifiedDeviceReminder", () => ({
53+
isBulkUnverifiedDeviceReminderSnoozed: jest.fn(),
54+
}));
55+
5156
const userId = '@user:server';
5257
const deviceId = 'my-device-id';
5358
const mockDispatcher = mocked(dis);
@@ -95,6 +100,7 @@ describe('DeviceListener', () => {
95100
});
96101
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient);
97102
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
103+
mocked(isBulkUnverifiedDeviceReminderSnoozed).mockClear().mockReturnValue(false);
98104
});
99105

100106
const createAndStart = async (): Promise<DeviceListener> => {
@@ -451,6 +457,23 @@ describe('DeviceListener', () => {
451457
expect(BulkUnverifiedSessionsToast.showToast).not.toHaveBeenCalled();
452458
});
453459

460+
it('hides toast when reminder is snoozed', async () => {
461+
mocked(isBulkUnverifiedDeviceReminderSnoozed).mockReturnValue(true);
462+
// currentDevice, device2 are verified, device3 is unverified
463+
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
464+
switch (deviceId) {
465+
case currentDevice.deviceId:
466+
case device2.deviceId:
467+
return deviceTrustVerified;
468+
default:
469+
return deviceTrustUnverified;
470+
}
471+
});
472+
await createAndStart();
473+
expect(BulkUnverifiedSessionsToast.showToast).not.toHaveBeenCalled();
474+
expect(BulkUnverifiedSessionsToast.hideToast).toHaveBeenCalled();
475+
});
476+
454477
it('shows toast with unverified devices at app start', async () => {
455478
// currentDevice, device2 are verified, device3 is unverified
456479
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { logger } from "matrix-js-sdk/src/logger";
18+
19+
import {
20+
isBulkUnverifiedDeviceReminderSnoozed,
21+
snoozeBulkUnverifiedDeviceReminder,
22+
} from "../../../src/utils/device/snoozeBulkUnverifiedDeviceReminder";
23+
24+
const SNOOZE_KEY = 'mx_snooze_bulk_unverified_device_nag';
25+
26+
describe('snooze bulk unverified device nag', () => {
27+
const localStorageSetSpy = jest.spyOn(localStorage.__proto__, 'setItem');
28+
const localStorageGetSpy = jest.spyOn(localStorage.__proto__, 'getItem');
29+
const localStorageRemoveSpy = jest.spyOn(localStorage.__proto__, 'removeItem');
30+
31+
// 14.03.2022 16:15
32+
const now = 1647270879403;
33+
34+
beforeEach(() => {
35+
localStorageSetSpy.mockClear().mockImplementation(() => {});
36+
localStorageGetSpy.mockClear().mockReturnValue(null);
37+
localStorageRemoveSpy.mockClear().mockImplementation(() => {});
38+
39+
jest.spyOn(Date, 'now').mockReturnValue(now);
40+
});
41+
42+
afterAll(() => {
43+
jest.restoreAllMocks();
44+
});
45+
46+
describe('snoozeBulkUnverifiedDeviceReminder()', () => {
47+
it('sets the current time in local storage', () => {
48+
snoozeBulkUnverifiedDeviceReminder();
49+
50+
expect(localStorageSetSpy).toHaveBeenCalledWith(SNOOZE_KEY, now.toString());
51+
});
52+
53+
it('catches an error from localstorage', () => {
54+
const loggerErrorSpy = jest.spyOn(logger, 'error');
55+
localStorageSetSpy.mockImplementation(() => { throw new Error('oups'); });
56+
snoozeBulkUnverifiedDeviceReminder();
57+
expect(loggerErrorSpy).toHaveBeenCalled();
58+
});
59+
});
60+
61+
describe('isBulkUnverifiedDeviceReminderSnoozed()', () => {
62+
it('returns false when there is no snooze in storage', () => {
63+
const result = isBulkUnverifiedDeviceReminderSnoozed();
64+
expect(localStorageGetSpy).toHaveBeenCalledWith(SNOOZE_KEY);
65+
expect(result).toBe(false);
66+
});
67+
68+
it('catches an error from localstorage and returns false', () => {
69+
const loggerErrorSpy = jest.spyOn(logger, 'error');
70+
localStorageGetSpy.mockImplementation(() => { throw new Error('oups'); });
71+
const result = isBulkUnverifiedDeviceReminderSnoozed();
72+
expect(result).toBe(false);
73+
expect(loggerErrorSpy).toHaveBeenCalled();
74+
});
75+
76+
it('returns false when snooze timestamp in storage is not a number', () => {
77+
localStorageGetSpy.mockReturnValue('test');
78+
const result = isBulkUnverifiedDeviceReminderSnoozed();
79+
expect(result).toBe(false);
80+
});
81+
82+
it('returns false when snooze timestamp in storage is over a week ago', () => {
83+
const msDay = 1000 * 60 * 60 * 24;
84+
// snoozed 8 days ago
85+
localStorageGetSpy.mockReturnValue(now - (msDay * 8));
86+
const result = isBulkUnverifiedDeviceReminderSnoozed();
87+
expect(result).toBe(false);
88+
});
89+
90+
it('returns true when snooze timestamp in storage is less than a week ago', () => {
91+
const msDay = 1000 * 60 * 60 * 24;
92+
// snoozed 8 days ago
93+
localStorageGetSpy.mockReturnValue(now - (msDay * 6));
94+
const result = isBulkUnverifiedDeviceReminderSnoozed();
95+
expect(result).toBe(true);
96+
});
97+
});
98+
});

0 commit comments

Comments
 (0)