Skip to content

Commit c81d759

Browse files
Germainturt2live
andauthored
Emit events when setting unread notifications (#2748)
Co-authored-by: Travis Ralston <[email protected]>
1 parent 50dd79c commit c81d759

File tree

3 files changed

+141
-16
lines changed

3 files changed

+141
-16
lines changed

spec/unit/notifications.spec.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { Feature, ServerSupport } from "../../src/feature";
1718
import {
1819
EventType,
1920
fixNotificationCountOnDecryption,
@@ -23,6 +24,7 @@ import {
2324
NotificationCountType,
2425
RelationType,
2526
Room,
27+
RoomEvent,
2628
} from "../../src/matrix";
2729
import { IActionsObject } from "../../src/pushprocessor";
2830
import { ReEmitter } from "../../src/ReEmitter";
@@ -56,8 +58,12 @@ describe("fixNotificationCountOnDecryption", () => {
5658
supportsExperimentalThreads: jest.fn().mockReturnValue(true),
5759
});
5860
mockClient.reEmitter = mock(ReEmitter, 'ReEmitter');
61+
mockClient.canSupport = new Map();
62+
Object.keys(Feature).forEach(feature => {
63+
mockClient.canSupport.set(feature as Feature, ServerSupport.Stable);
64+
});
5965

60-
room = new Room(ROOM_ID, mockClient, mockClient.getUserId());
66+
room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "");
6167
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
6268
room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
6369

@@ -93,12 +99,12 @@ describe("fixNotificationCountOnDecryption", () => {
9399
});
94100

95101
it("changes the room count to highlight on decryption", () => {
96-
expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(1);
102+
expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(2);
97103
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(0);
98104

99105
fixNotificationCountOnDecryption(mockClient, event);
100106

101-
expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(1);
107+
expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(2);
102108
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
103109
});
104110

@@ -111,4 +117,18 @@ describe("fixNotificationCountOnDecryption", () => {
111117
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(1);
112118
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(1);
113119
});
120+
121+
it("emits events", () => {
122+
const cb = jest.fn();
123+
room.on(RoomEvent.UnreadNotifications, cb);
124+
125+
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
126+
expect(cb).toHaveBeenLastCalledWith({ highlight: 0, total: 1 });
127+
128+
room.setUnreadNotificationCount(NotificationCountType.Highlight, 5);
129+
expect(cb).toHaveBeenLastCalledWith({ highlight: 5, total: 1 });
130+
131+
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 5);
132+
expect(cb).toHaveBeenLastCalledWith({ highlight: 5 }, "$123");
133+
});
114134
});

spec/unit/room.spec.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,15 +2572,15 @@ describe("Room", function() {
25722572
});
25732573

25742574
it("defaults to undefined", () => {
2575-
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Total)).toBeUndefined();
2576-
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Highlight)).toBeUndefined();
2575+
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Total)).toBe(0);
2576+
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Highlight)).toBe(0);
25772577
});
25782578

25792579
it("lets you set values", () => {
25802580
room.setThreadUnreadNotificationCount("123", NotificationCountType.Total, 1);
25812581

25822582
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Total)).toBe(1);
2583-
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Highlight)).toBeUndefined();
2583+
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Highlight)).toBe(0);
25842584

25852585
room.setThreadUnreadNotificationCount("123", NotificationCountType.Highlight, 10);
25862586

@@ -2592,10 +2592,48 @@ describe("Room", function() {
25922592
room.setThreadUnreadNotificationCount("123", NotificationCountType.Total, 666);
25932593
room.setThreadUnreadNotificationCount("123", NotificationCountType.Highlight, 123);
25942594

2595+
expect(room.getThreadsAggregateNotificationType()).toBe(NotificationCountType.Highlight);
2596+
2597+
room.resetThreadUnreadNotificationCount();
2598+
2599+
expect(room.getThreadsAggregateNotificationType()).toBe(null);
2600+
2601+
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Total)).toBe(0);
2602+
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Highlight)).toBe(0);
2603+
});
2604+
2605+
it("sets the room threads notification type", () => {
2606+
room.setThreadUnreadNotificationCount("123", NotificationCountType.Total, 666);
2607+
expect(room.getThreadsAggregateNotificationType()).toBe(NotificationCountType.Total);
2608+
room.setThreadUnreadNotificationCount("123", NotificationCountType.Highlight, 123);
2609+
expect(room.getThreadsAggregateNotificationType()).toBe(NotificationCountType.Highlight);
2610+
room.setThreadUnreadNotificationCount("123", NotificationCountType.Total, 333);
2611+
expect(room.getThreadsAggregateNotificationType()).toBe(NotificationCountType.Highlight);
2612+
});
2613+
});
2614+
2615+
describe("hasThreadUnreadNotification", () => {
2616+
it('has no notifications by default', () => {
2617+
expect(room.hasThreadUnreadNotification()).toBe(false);
2618+
});
2619+
2620+
it('main timeline notification does not affect this', () => {
2621+
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
2622+
expect(room.hasThreadUnreadNotification()).toBe(false);
2623+
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
2624+
expect(room.hasThreadUnreadNotification()).toBe(false);
2625+
2626+
room.setThreadUnreadNotificationCount("123", NotificationCountType.Total, 1);
2627+
expect(room.hasThreadUnreadNotification()).toBe(true);
2628+
});
2629+
2630+
it('lets you reset', () => {
2631+
room.setThreadUnreadNotificationCount("123", NotificationCountType.Highlight, 1);
2632+
expect(room.hasThreadUnreadNotification()).toBe(true);
2633+
25952634
room.resetThreadUnreadNotificationCount();
25962635

2597-
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Total)).toBeUndefined();
2598-
expect(room.getThreadUnreadNotificationCount("123", NotificationCountType.Highlight)).toBeUndefined();
2636+
expect(room.hasThreadUnreadNotification()).toBe(false);
25992637
});
26002638
});
26012639
});

src/models/room.ts

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import {
5757
ReceiptContent,
5858
synthesizeReceipt,
5959
} from "./read-receipt";
60+
import { Feature, ServerSupport } from "../feature";
6061

6162
// These constants are used as sane defaults when the homeserver doesn't support
6263
// the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be
@@ -96,7 +97,7 @@ export interface IRecommendedVersion {
9697
// price to pay to keep matrix-js-sdk responsive.
9798
const MAX_NUMBER_OF_VISIBILITY_EVENTS_TO_SCAN_THROUGH = 30;
9899

99-
type NotificationCount = Partial<Record<NotificationCountType, number>>;
100+
export type NotificationCount = Partial<Record<NotificationCountType, number>>;
100101

101102
export enum NotificationCountType {
102103
Highlight = "highlight",
@@ -127,6 +128,7 @@ export enum RoomEvent {
127128
OldStateUpdated = "Room.OldStateUpdated",
128129
CurrentStateUpdated = "Room.CurrentStateUpdated",
129130
HistoryImportedWithinTimeline = "Room.historyImportedWithinTimeline",
131+
UnreadNotifications = "Room.UnreadNotifications",
130132
}
131133

132134
type EmittedEvents = RoomEvent
@@ -164,6 +166,10 @@ export type RoomEventHandlerMap = {
164166
markerEvent: MatrixEvent,
165167
room: Room,
166168
) => void;
169+
[RoomEvent.UnreadNotifications]: (
170+
unreadNotifications: NotificationCount,
171+
threadId?: string,
172+
) => void;
167173
[RoomEvent.TimelineRefresh]: (room: Room, eventTimelineSet: EventTimelineSet) => void;
168174
[ThreadEvent.New]: (thread: Thread, toStartOfTimeline: boolean) => void;
169175
} & ThreadHandlerMap
@@ -186,7 +192,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
186192
public readonly reEmitter: TypedReEmitter<EmittedEvents, RoomEventHandlerMap>;
187193
private txnToEvent: Record<string, MatrixEvent> = {}; // Pending in-flight requests { string: MatrixEvent }
188194
private notificationCounts: NotificationCount = {};
189-
private threadNotifications: Map<string, NotificationCount> = new Map();
195+
private readonly threadNotifications = new Map<string, NotificationCount>();
196+
private roomThreadsNotificationType: NotificationCountType | null = null;
190197
private readonly timelineSets: EventTimelineSet[];
191198
public readonly threadsTimelineSets: EventTimelineSet[] = [];
192199
// any filtered timeline sets we're maintaining for this room
@@ -1182,38 +1189,97 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
11821189
* @return {Number} The notification count, or undefined if there is no count
11831190
* for this type.
11841191
*/
1185-
public getUnreadNotificationCount(type = NotificationCountType.Total): number | undefined {
1186-
return this.notificationCounts[type];
1192+
public getUnreadNotificationCount(type = NotificationCountType.Total): number {
1193+
let count = this.notificationCounts[type] ?? 0;
1194+
if (this.client.canSupport.get(Feature.ThreadUnreadNotifications) !== ServerSupport.Unsupported) {
1195+
for (const threadNotification of this.threadNotifications.values()) {
1196+
count += threadNotification[type] ?? 0;
1197+
}
1198+
}
1199+
return count;
11871200
}
11881201

11891202
/**
1203+
* @experimental
11901204
* Get one of the notification counts for a thread
11911205
* @param threadId the root event ID
11921206
* @param type The type of notification count to get. default: 'total'
11931207
* @returns The notification count, or undefined if there is no count
11941208
* for this type.
11951209
*/
1196-
public getThreadUnreadNotificationCount(threadId: string, type = NotificationCountType.Total): number | undefined {
1197-
return this.threadNotifications.get(threadId)?.[type];
1210+
public getThreadUnreadNotificationCount(threadId: string, type = NotificationCountType.Total): number {
1211+
return this.threadNotifications.get(threadId)?.[type] ?? 0;
11981212
}
11991213

12001214
/**
1215+
* @experimental
1216+
* Checks if the current room has unread thread notifications
1217+
* @returns {boolean}
1218+
*/
1219+
public hasThreadUnreadNotification(): boolean {
1220+
for (const notification of this.threadNotifications.values()) {
1221+
if ((notification.highlight ?? 0) > 0 || (notification.total ?? 0) > 0) {
1222+
return true;
1223+
}
1224+
}
1225+
return false;
1226+
}
1227+
1228+
/**
1229+
* @experimental
12011230
* Swet one of the notification count for a thread
12021231
* @param threadId the root event ID
12031232
* @param type The type of notification count to get. default: 'total'
12041233
* @returns {void}
12051234
*/
12061235
public setThreadUnreadNotificationCount(threadId: string, type: NotificationCountType, count: number): void {
1207-
this.threadNotifications.set(threadId, {
1236+
const notification: NotificationCount = {
12081237
highlight: this.threadNotifications.get(threadId)?.highlight,
12091238
total: this.threadNotifications.get(threadId)?.total,
12101239
...{
12111240
[type]: count,
12121241
},
1213-
});
1242+
};
1243+
1244+
this.threadNotifications.set(threadId, notification);
1245+
1246+
// Remember what the global threads notification count type is
1247+
// for all the threads in the room
1248+
if (count > 0) {
1249+
switch (this.roomThreadsNotificationType) {
1250+
case NotificationCountType.Highlight:
1251+
break;
1252+
case NotificationCountType.Total:
1253+
if (type === NotificationCountType.Highlight) {
1254+
this.roomThreadsNotificationType = type;
1255+
}
1256+
break;
1257+
default:
1258+
this.roomThreadsNotificationType = type;
1259+
}
1260+
}
1261+
1262+
this.emit(
1263+
RoomEvent.UnreadNotifications,
1264+
notification,
1265+
threadId,
1266+
);
12141267
}
12151268

1269+
/**
1270+
* @experimental
1271+
* @returns the notification count type for all the threads in the room
1272+
*/
1273+
public getThreadsAggregateNotificationType(): NotificationCountType | null {
1274+
return this.roomThreadsNotificationType;
1275+
}
1276+
1277+
/**
1278+
* @experimental
1279+
* Resets the thread notifications for this room
1280+
*/
12161281
public resetThreadUnreadNotificationCount(): void {
1282+
this.roomThreadsNotificationType = null;
12171283
this.threadNotifications.clear();
12181284
}
12191285

@@ -1224,6 +1290,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
12241290
*/
12251291
public setUnreadNotificationCount(type: NotificationCountType, count: number): void {
12261292
this.notificationCounts[type] = count;
1293+
this.emit(RoomEvent.UnreadNotifications, this.notificationCounts);
12271294
}
12281295

12291296
public setSummary(summary: IRoomSummary): void {

0 commit comments

Comments
 (0)