Skip to content

Commit d5116d1

Browse files
author
Germain Souquet
committed
MSC3773 POC
1 parent 3ae974e commit d5116d1

File tree

4 files changed

+77
-10
lines changed

4 files changed

+77
-10
lines changed

src/filter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export interface IRoomEventFilter extends IFilterComponent {
5757
types?: Array<EventType | string>;
5858
related_by_senders?: Array<RelationType | string>;
5959
related_by_rel_types?: string[];
60+
unread_thread_notifications?: boolean;
6061

6162
// Unstable values
6263
"io.element.relation_senders"?: Array<RelationType | string>;

src/models/room.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ export type RoomEventHandlerMap = {
207207
[ThreadEvent.New]: (thread: Thread, toStartOfTimeline: boolean) => void;
208208
} & ThreadHandlerMap & MatrixEventHandlerMap;
209209

210+
type NotificationCount = Partial<Record<NotificationCountType, number>>;
211+
210212
export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap> {
211213
public readonly reEmitter: TypedReEmitter<EmittedEvents, RoomEventHandlerMap>;
212214
private txnToEvent: Record<string, MatrixEvent> = {}; // Pending in-flight requests { string: MatrixEvent }
@@ -216,7 +218,8 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
216218
// a pre-cached list for this purpose.
217219
private receipts: Receipts = {}; // { receipt_type: { user_id: IReceipt } }
218220
private receiptCacheByEventId: ReceiptCache = {}; // { event_id: ICachedReceipt[] }
219-
private notificationCounts: Partial<Record<NotificationCountType, number>> = {};
221+
private notificationCounts: NotificationCount = {};
222+
private threadNotifications: Record<string, NotificationCount> = {};
220223
private readonly timelineSets: EventTimelineSet[];
221224
public readonly threadsTimelineSets: EventTimelineSet[] = [];
222225
// any filtered timeline sets we're maintaining for this room
@@ -1177,14 +1180,30 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
11771180
return event;
11781181
}
11791182

1183+
/**
1184+
* Get sum of threads and roon notification counts
1185+
* @param {String} type The type of notification count to get. default: 'total'
1186+
* @return {Number} The notification count.
1187+
*/
1188+
public getTotalUnreadNotificationCount(type = NotificationCountType.Total): number {
1189+
return (this.getUnreadNotificationCount(type) ?? 0) +
1190+
this.getTotalThreadsUnreadNotificationCount(type);
1191+
}
1192+
1193+
public getTotalThreadsUnreadNotificationCount(type = NotificationCountType.Total): number {
1194+
return Object.keys(this.threadNotifications).reduce((total: number, threadId: string) => {
1195+
return total + (this.getThreadUnreadNotificationCount(threadId, type) ?? 0);
1196+
}, 0);
1197+
}
1198+
11801199
/**
11811200
* Get one of the notification counts for this room
11821201
* @param {String} type The type of notification count to get. default: 'total'
11831202
* @return {Number} The notification count, or undefined if there is no count
11841203
* for this type.
11851204
*/
11861205
public getUnreadNotificationCount(type = NotificationCountType.Total): number | undefined {
1187-
return this.notificationCounts[type];
1206+
return this.notificationCounts[type] ?? 0;
11881207
}
11891208

11901209
/**
@@ -1196,6 +1215,17 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
11961215
this.notificationCounts[type] = count;
11971216
}
11981217

1218+
public getThreadUnreadNotificationCount(threadId: string, type = NotificationCountType.Total): number | undefined {
1219+
return this.threadNotifications[threadId]?.[type];
1220+
}
1221+
1222+
public setThreadUnreadNotificationCount(threadId: string, type: NotificationCountType, count: number): void {
1223+
const notificationCount = this.threadNotifications[threadId];
1224+
if (notificationCount) {
1225+
notificationCount[type] = count;
1226+
}
1227+
}
1228+
11991229
public setSummary(summary: IRoomSummary): void {
12001230
const heroes = summary["m.heroes"];
12011231
const joinedCount = summary["m.joined_member_count"];

src/sync-accumulator.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ export interface IJoinedRoom {
7676
ephemeral: IEphemeral;
7777
account_data: IAccountData;
7878
unread_notifications: IUnreadNotificationCounts;
79+
unread_thread_notifications?: {
80+
[threadId: string]: IUnreadNotificationCounts;
81+
};
7982
}
8083

8184
export interface IStrippedState {
@@ -154,6 +157,9 @@ interface IRoom {
154157
_summary: Partial<IRoomSummary>;
155158
_accountData: { [eventType: string]: IMinimalEvent };
156159
_unreadNotifications: Partial<IUnreadNotificationCounts>;
160+
_unreadThreadNotifications?: {
161+
[threadId: string]: Partial<IUnreadNotificationCounts>;
162+
};
157163
_readReceipts: {
158164
[userId: string]: {
159165
data: IMinimalEvent;
@@ -358,12 +364,13 @@ export class SyncAccumulator {
358364
// Create truly empty objects so event types of 'hasOwnProperty' and co
359365
// don't cause this code to break.
360366
this.joinRooms[roomId] = {
361-
_currentState: Object.create(null),
362-
_timeline: [],
363-
_accountData: Object.create(null),
364-
_unreadNotifications: {},
365-
_summary: {},
366-
_readReceipts: {},
367+
"_currentState": Object.create(null),
368+
"_timeline": [],
369+
"_accountData": Object.create(null),
370+
"_unreadNotifications": {},
371+
"_unreadThreadNotifications": {},
372+
"_summary": {},
373+
"_readReceipts": {},
367374
};
368375
}
369376
const currentData = this.joinRooms[roomId];
@@ -379,6 +386,9 @@ export class SyncAccumulator {
379386
if (data.unread_notifications) {
380387
currentData._unreadNotifications = data.unread_notifications;
381388
}
389+
if (data.unread_thread_notifications) {
390+
currentData._unreadThreadNotifications = data.unread_thread_notifications;
391+
}
382392
if (data.summary) {
383393
const HEROES_KEY = "m.heroes";
384394
const INVITED_COUNT_KEY = "m.invited_member_count";
@@ -537,6 +547,7 @@ export class SyncAccumulator {
537547
prev_batch: null,
538548
},
539549
unread_notifications: roomData._unreadNotifications,
550+
unread_thread_notifications: roomData._unreadThreadNotifications,
540551
summary: roomData._summary as IRoomSummary,
541552
};
542553
// Add account data

src/sync.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ interface ISyncParams {
138138
// eslint-disable-next-line camelcase
139139
set_presence?: SetPresence;
140140
_cacheBuster?: string | number; // not part of the API itself
141+
unread_thread_notifications?: boolean;
142+
"org.matrix.msc3773.unread_thread_notifications"?: boolean;
141143
}
142144

143145
type WrappedRoom<T> = T & {
@@ -996,8 +998,10 @@ export class SyncApi {
996998
}
997999

9981000
const qps: ISyncParams = {
999-
filter: filterId,
1000-
timeout: pollTimeout,
1001+
"filter": filterId,
1002+
"timeout": pollTimeout,
1003+
"unread_thread_notifications": true,
1004+
"org.matrix.msc3773.unread_thread_notifications": true,
10011005
};
10021006

10031007
if (this.opts.disablePresence) {
@@ -1299,6 +1303,27 @@ export class SyncApi {
12991303
}
13001304
}
13011305

1306+
const unreadThreadNotifications = joinObj.unread_thread_notifications;
1307+
if (unreadThreadNotifications) {
1308+
Object.entries(unreadThreadNotifications).forEach(([threadId, unreadNotification]) => {
1309+
room.setThreadUnreadNotificationCount(
1310+
threadId,
1311+
NotificationCountType.Total,
1312+
unreadNotification.notification_count,
1313+
);
1314+
1315+
const hasUnreadNotification =
1316+
room.getThreadUnreadNotificationCount(threadId, NotificationCountType.Highlight) <= 0;
1317+
if (!encrypted || (encrypted && hasUnreadNotification)) {
1318+
room.setThreadUnreadNotificationCount(
1319+
threadId,
1320+
NotificationCountType.Highlight,
1321+
unreadNotification.highlight_count,
1322+
);
1323+
}
1324+
});
1325+
}
1326+
13021327
joinObj.timeline = joinObj.timeline || {} as ITimeline;
13031328

13041329
if (joinObj.isBrandNewRoom) {

0 commit comments

Comments
 (0)