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

Commit c795ada

Browse files
author
Kerry
authored
Device manager - eagerly create m.local_notification_settings events (#9353)
* eagerly save m.local_notification_settings events * unskip test * create local notification settings after first non-cached sync
1 parent cf029c5 commit c795ada

File tree

4 files changed

+130
-8
lines changed

4 files changed

+130
-8
lines changed

src/Notifier.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
2626
import {
2727
PermissionChanged as PermissionChangedEvent,
2828
} from "@matrix-org/analytics-events/types/typescript/PermissionChanged";
29+
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
2930

3031
import { MatrixClientPeg } from './MatrixClientPeg';
3132
import { PosthogAnalytics } from "./PosthogAnalytics";
@@ -50,6 +51,7 @@ import { localNotificationsAreSilenced } from "./utils/notifications";
5051
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
5152
import ToastStore from "./stores/ToastStore";
5253
import { ElementCall } from "./models/Call";
54+
import { createLocalNotificationSettingsIfNeeded } from './utils/notifications';
5355

5456
/*
5557
* Dispatches:
@@ -351,12 +353,20 @@ export const Notifier = {
351353
return this.toolbarHidden;
352354
},
353355

354-
onSyncStateChange: function(state: string) {
355-
if (state === "SYNCING") {
356+
onSyncStateChange: function(state: SyncState, prevState?: SyncState, data?: ISyncStateData) {
357+
if (state === SyncState.Syncing) {
356358
this.isSyncing = true;
357-
} else if (state === "STOPPED" || state === "ERROR") {
359+
} else if (state === SyncState.Stopped || state === SyncState.Error) {
358360
this.isSyncing = false;
359361
}
362+
363+
// wait for first non-cached sync to complete
364+
if (
365+
![SyncState.Stopped, SyncState.Error].includes(state) &&
366+
!data?.fromCache
367+
) {
368+
createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get());
369+
}
360370
},
361371

362372
onEvent: function(ev: MatrixEvent) {

src/utils/notifications.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,40 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { MatrixClient } from "matrix-js-sdk/src/client";
1718
import { LOCAL_NOTIFICATION_SETTINGS_PREFIX } from "matrix-js-sdk/src/@types/event";
1819
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
19-
import { MatrixClient } from "matrix-js-sdk/src/client";
20+
21+
import SettingsStore from "../settings/SettingsStore";
22+
23+
export const deviceNotificationSettingsKeys = [
24+
"notificationsEnabled",
25+
"notificationBodyEnabled",
26+
"audioNotificationsEnabled",
27+
];
2028

2129
export function getLocalNotificationAccountDataEventType(deviceId: string): string {
2230
return `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`;
2331
}
2432

33+
export async function createLocalNotificationSettingsIfNeeded(cli: MatrixClient): Promise<void> {
34+
const eventType = getLocalNotificationAccountDataEventType(cli.deviceId);
35+
const event = cli.getAccountData(eventType);
36+
// New sessions will create an account data event to signify they support
37+
// remote toggling of push notifications on this device. Default `is_silenced=true`
38+
// For backwards compat purposes, older sessions will need to check settings value
39+
// to determine what the state of `is_silenced`
40+
if (!event) {
41+
// If any of the above is true, we fall in the "backwards compat" case,
42+
// and `is_silenced` will be set to `false`
43+
const isSilenced = !deviceNotificationSettingsKeys.some(key => SettingsStore.getValue(key));
44+
45+
await cli.setAccountData(eventType, {
46+
is_silenced: isSilenced,
47+
});
48+
}
49+
}
50+
2551
export function localNotificationsAreSilenced(cli: MatrixClient): boolean {
2652
const eventType = getLocalNotificationAccountDataEventType(cli.deviceId);
2753
const event = cli.getAccountData(eventType);

test/Notifier-test.ts

+56-4
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,30 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { MockedObject } from "jest-mock";
18-
import { MatrixClient } from "matrix-js-sdk/src/client";
17+
import { mocked, MockedObject } from "jest-mock";
18+
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
1919
import { Room } from "matrix-js-sdk/src/models/room";
2020
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
21+
import { SyncState } from "matrix-js-sdk/src/sync";
2122

2223
import BasePlatform from "../src/BasePlatform";
2324
import { ElementCall } from "../src/models/Call";
2425
import Notifier from "../src/Notifier";
2526
import SettingsStore from "../src/settings/SettingsStore";
2627
import ToastStore from "../src/stores/ToastStore";
27-
import { getLocalNotificationAccountDataEventType } from "../src/utils/notifications";
28+
import {
29+
createLocalNotificationSettingsIfNeeded,
30+
getLocalNotificationAccountDataEventType,
31+
} from "../src/utils/notifications";
2832
import { getMockClientWithEventEmitter, mkEvent, mkRoom, mockPlatformPeg } from "./test-utils";
2933
import { IncomingCallToast } from "../src/toasts/IncomingCallToast";
3034

35+
jest.mock("../src/utils/notifications", () => ({
36+
// @ts-ignore
37+
...jest.requireActual("../src/utils/notifications"),
38+
createLocalNotificationSettingsIfNeeded: jest.fn(),
39+
}));
40+
3141
describe("Notifier", () => {
3242
const roomId = "!room1:server";
3343
const testEvent = mkEvent({
@@ -111,7 +121,7 @@ describe("Notifier", () => {
111121
tweaks: {},
112122
});
113123

114-
Notifier.onSyncStateChange("SYNCING");
124+
Notifier.onSyncStateChange(SyncState.Syncing);
115125
});
116126

117127
afterEach(() => {
@@ -169,4 +179,46 @@ describe("Notifier", () => {
169179
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
170180
});
171181
});
182+
183+
describe('local notification settings', () => {
184+
const createLocalNotificationSettingsIfNeededMock = mocked(createLocalNotificationSettingsIfNeeded);
185+
let hasStartedNotiferBefore = false;
186+
beforeEach(() => {
187+
// notifier defines some listener functions in start
188+
// and references them in stop
189+
// so blows up if stopped before it was started
190+
if (hasStartedNotiferBefore) {
191+
Notifier.stop();
192+
}
193+
Notifier.start();
194+
hasStartedNotiferBefore = true;
195+
createLocalNotificationSettingsIfNeededMock.mockClear();
196+
});
197+
198+
afterAll(() => {
199+
Notifier.stop();
200+
});
201+
202+
it('does not create local notifications event after a sync error', () => {
203+
mockClient.emit(ClientEvent.Sync, SyncState.Error, SyncState.Syncing);
204+
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
205+
});
206+
207+
it('does not create local notifications event after sync stops', () => {
208+
mockClient.emit(ClientEvent.Sync, SyncState.Stopped, SyncState.Syncing);
209+
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
210+
});
211+
212+
it('does not create local notifications event after a cached sync', () => {
213+
mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing, {
214+
fromCache: true,
215+
});
216+
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
217+
});
218+
219+
it('creates local notifications event after a non-cached sync', () => {
220+
mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing, {});
221+
expect(createLocalNotificationSettingsIfNeededMock).toHaveBeenCalled();
222+
});
223+
});
172224
});

test/utils/notifications-test.ts

+34
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { mocked } from "jest-mock";
2020
import {
2121
localNotificationsAreSilenced,
2222
getLocalNotificationAccountDataEventType,
23+
createLocalNotificationSettingsIfNeeded,
24+
deviceNotificationSettingsKeys,
2325
} from "../../src/utils/notifications";
2426
import SettingsStore from "../../src/settings/SettingsStore";
2527
import { getMockClientWithEventEmitter } from "../test-utils/client";
@@ -46,6 +48,38 @@ describe('notifications', () => {
4648
mocked(SettingsStore).getValue.mockReturnValue(false);
4749
});
4850

51+
describe('createLocalNotification', () => {
52+
it('creates account data event', async () => {
53+
await createLocalNotificationSettingsIfNeeded(mockClient);
54+
const event = mockClient.getAccountData(accountDataEventKey);
55+
expect(event?.getContent().is_silenced).toBe(true);
56+
});
57+
58+
it.each(deviceNotificationSettingsKeys)(
59+
'unsilenced for existing sessions when %s setting is truthy',
60+
async (settingKey) => {
61+
mocked(SettingsStore)
62+
.getValue
63+
.mockImplementation((key) => {
64+
return key === settingKey;
65+
});
66+
67+
await createLocalNotificationSettingsIfNeeded(mockClient);
68+
const event = mockClient.getAccountData(accountDataEventKey);
69+
expect(event?.getContent().is_silenced).toBe(false);
70+
});
71+
72+
it("does not override an existing account event data", async () => {
73+
mockClient.setAccountData(accountDataEventKey, {
74+
is_silenced: false,
75+
});
76+
77+
await createLocalNotificationSettingsIfNeeded(mockClient);
78+
const event = mockClient.getAccountData(accountDataEventKey);
79+
expect(event?.getContent().is_silenced).toBe(false);
80+
});
81+
});
82+
4983
describe('localNotificationsAreSilenced', () => {
5084
it('defaults to true when no setting exists', () => {
5185
expect(localNotificationsAreSilenced(mockClient)).toBeTruthy();

0 commit comments

Comments
 (0)