Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Ensure we don't overinflate the total notification count" #3639

Merged
merged 1 commit into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions spec/unit/event-timeline-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,8 @@ describe("EventTimelineSet", () => {
messageEventIsDecryptionFailureSpy.mockReturnValue(true);
replyEventIsDecryptionFailureSpy.mockReturnValue(true);

messageEvent.emit(
MatrixEventEvent.Decrypted,
messageEvent,
undefined,
messageEvent.getPushDetails(),
);
replyEvent.emit(MatrixEventEvent.Decrypted, replyEvent, undefined, replyEvent.getPushDetails());
messageEvent.emit(MatrixEventEvent.Decrypted, messageEvent);
replyEvent.emit(MatrixEventEvent.Decrypted, replyEvent);

// simulate decryption
messageEventIsDecryptionFailureSpy.mockReturnValue(false);
Expand All @@ -318,13 +313,8 @@ describe("EventTimelineSet", () => {
messageEventShouldAttemptDecryptionSpy.mockReturnValue(false);
replyEventShouldAttemptDecryptionSpy.mockReturnValue(false);

messageEvent.emit(
MatrixEventEvent.Decrypted,
messageEvent,
undefined,
messageEvent.getPushDetails(),
);
replyEvent.emit(MatrixEventEvent.Decrypted, replyEvent, undefined, replyEvent.getPushDetails());
messageEvent.emit(MatrixEventEvent.Decrypted, messageEvent);
replyEvent.emit(MatrixEventEvent.Decrypted, replyEvent);
});

itShouldReturnTheRelatedEvents();
Expand Down
31 changes: 10 additions & 21 deletions spec/unit/notifications.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe("fixNotificationCountOnDecryption", () => {
mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(),
isInitialSyncComplete: jest.fn().mockReturnValue(false),
getPushActionsForEvent: jest.fn((ev) => event.getPushActions() ?? mkPushAction(true, true)),
getPushActionsForEvent: jest.fn().mockReturnValue(mkPushAction(true, true)),
getRoom: jest.fn().mockImplementation(() => room),
decryptEventIfNeeded: jest.fn().mockResolvedValue(void 0),
supportsThreads: jest.fn().mockReturnValue(true),
Expand Down Expand Up @@ -125,15 +125,15 @@ describe("fixNotificationCountOnDecryption", () => {
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 1);
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);

event.getPushActions = jest.fn().mockReturnValue(mkPushAction(true, true));
threadEvent.getPushActions = jest.fn().mockReturnValue(mkPushAction(true, true));
event.getPushActions = jest.fn().mockReturnValue(mkPushAction(false, false));
threadEvent.getPushActions = jest.fn().mockReturnValue(mkPushAction(false, false));
});

it("changes the room count to highlight on decryption", () => {
expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(2);
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(0);

fixNotificationCountOnDecryption(mockClient, event, {});
fixNotificationCountOnDecryption(mockClient, event);

expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(3);
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
Expand All @@ -143,7 +143,7 @@ describe("fixNotificationCountOnDecryption", () => {
room.setUnreadNotificationCount(NotificationCountType.Total, 0);
room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);

fixNotificationCountOnDecryption(mockClient, event, {});
fixNotificationCountOnDecryption(mockClient, event);

expect(room.getRoomUnreadNotificationCount(NotificationCountType.Total)).toBe(1);
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
Expand All @@ -153,7 +153,7 @@ describe("fixNotificationCountOnDecryption", () => {
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(1);
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(0);

fixNotificationCountOnDecryption(mockClient, threadEvent, {});
fixNotificationCountOnDecryption(mockClient, threadEvent);

expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(2);
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(1);
Expand All @@ -163,7 +163,7 @@ describe("fixNotificationCountOnDecryption", () => {
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);

fixNotificationCountOnDecryption(mockClient, event, {});
fixNotificationCountOnDecryption(mockClient, event);

expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(0);
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(0);
Expand All @@ -187,7 +187,7 @@ describe("fixNotificationCountOnDecryption", () => {
event: true,
});

fixNotificationCountOnDecryption(mockClient, unknownThreadEvent, {});
fixNotificationCountOnDecryption(mockClient, unknownThreadEvent);

expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(0);
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(0);
Expand All @@ -201,7 +201,7 @@ describe("fixNotificationCountOnDecryption", () => {
event.getPushActions = jest.fn().mockReturnValue(mkPushAction(true, false));
mockClient.getPushActionsForEvent = jest.fn().mockReturnValue(mkPushAction(false, false));

fixNotificationCountOnDecryption(mockClient, event, {});
fixNotificationCountOnDecryption(mockClient, event);
expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(0);
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(0);
});
Expand All @@ -213,7 +213,7 @@ describe("fixNotificationCountOnDecryption", () => {
threadEvent.getPushActions = jest.fn().mockReturnValue(mkPushAction(true, false));
mockClient.getPushActionsForEvent = jest.fn().mockReturnValue(mkPushAction(false, false));

fixNotificationCountOnDecryption(mockClient, event, {});
fixNotificationCountOnDecryption(mockClient, event);
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(0);
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(0);
});
Expand All @@ -231,15 +231,4 @@ describe("fixNotificationCountOnDecryption", () => {
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 5);
expect(cb).toHaveBeenLastCalledWith({ highlight: 5 }, "$123");
});

it("should not increment notification count where event was already contributing notification", () => {
expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(2);
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(0);

event.getPushActions = jest.fn().mockReturnValue(mkPushAction(true, false));
fixNotificationCountOnDecryption(mockClient, event, { actions: { notify: true, tweaks: {} } });

expect(room.getUnreadNotificationCount(NotificationCountType.Total)).toBe(2);
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(0);
});
});
7 changes: 1 addition & 6 deletions spec/unit/room-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1037,12 +1037,7 @@ describe("RoomState", function () {

// this event is a message after decryption
decryptingRelatedEvent.event.type = EventType.RoomMessage;
decryptingRelatedEvent.emit(
MatrixEventEvent.Decrypted,
decryptingRelatedEvent,
undefined,
decryptingRelatedEvent.getPushDetails(),
);
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);

expect(addLocationsSpy).not.toHaveBeenCalled();
});
Expand Down
4 changes: 2 additions & 2 deletions spec/unit/room.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3428,13 +3428,13 @@ describe("Room", function () {
expect(room.polls.get(pollStartEventId)).toBeUndefined();

// now emit a Decrypted event but keep the decryption failure
pollStartEvent.emit(MatrixEventEvent.Decrypted, pollStartEvent, undefined, pollStartEvent.getPushDetails());
pollStartEvent.emit(MatrixEventEvent.Decrypted, pollStartEvent);
// still do not expect a poll to show up for the room
expect(room.polls.get(pollStartEventId)).toBeUndefined();

// clear decryption failure and emit a Decrypted event again
isDecryptionFailureSpy.mockRestore();
pollStartEvent.emit(MatrixEventEvent.Decrypted, pollStartEvent, undefined, pollStartEvent.getPushDetails());
pollStartEvent.emit(MatrixEventEvent.Decrypted, pollStartEvent);

// the poll should now show up in the room's polls
const poll = room.polls.get(pollStartEventId);
Expand Down
73 changes: 39 additions & 34 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1371,8 +1371,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// actions for themselves, so we have to kinda help them out when they are encrypted.
// We do this so that push rules are correctly executed on events in their decrypted
// state, such as highlights when the user's name is mentioned.
this.on(MatrixEventEvent.Decrypted, (event, _, pushDetails) => {
fixNotificationCountOnDecryption(this, event, pushDetails);
this.on(MatrixEventEvent.Decrypted, (event) => {
fixNotificationCountOnDecryption(this, event);
});

// Like above, we have to listen for read receipts from ourselves in order to
Expand Down Expand Up @@ -9851,23 +9851,26 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Servers do not have enough knowledge about encrypted events to calculate an
* accurate notification_count
*/
export function fixNotificationCountOnDecryption(
cli: MatrixClient,
event: MatrixEvent,
pushDetails: PushDetails,
): void {
export function fixNotificationCountOnDecryption(cli: MatrixClient, event: MatrixEvent): void {
const ourUserId = cli.getUserId();
const eventId = event.getId();

const room = cli.getRoom(event.getRoomId());
if (!room || !ourUserId || !eventId) return;

// We cannot call event.getPushActions here as the decryption loop just nulled them for re-calculation
const oldActions = pushDetails.actions;
const oldActions = event.getPushActions();
const actions = cli.getPushActionsForEvent(event, true);

const isThreadEvent = !!event.threadRootId && !event.isThreadRoot;

const currentHighlightCount = room.getUnreadCountForEventContext(NotificationCountType.Highlight, event);

// Ensure the unread counts are kept up to date if the event is encrypted
// We also want to make sure that the notification count goes up if we already
// have encrypted events to avoid other code from resetting 'highlight' to zero.
const oldHighlight = !!oldActions?.tweaks?.highlight;
const newHighlight = !!actions?.tweaks?.highlight;

let hasReadEvent;
if (isThreadEvent) {
const thread = room.getThread(event.threadRootId);
Expand All @@ -9889,35 +9892,37 @@ export function fixNotificationCountOnDecryption(
return;
}

// Ensure the unread counts are kept up to date if the event is encrypted
// We also want to make sure that the notification count goes up if we already
// have encrypted events to avoid other code from resetting 'highlight' to zero.
// TODO: Handle mentions received while the client is offline
// See also https://github.com/vector-im/element-web/issues/9069
for (const type of [NotificationCountType.Highlight, NotificationCountType.Total]) {
let count = room.getUnreadCountForEventContext(type, event);

let oldValue: boolean;
let newValue: boolean;
if (type === NotificationCountType.Total) {
// Total count is used to typically increment a room notification counter, but not loudly highlight it.
// `notify` is used in practice for incrementing the total count
oldValue = !!oldActions?.notify;
newValue = !!actions?.notify;
if (oldHighlight !== newHighlight || currentHighlightCount > 0) {
// TODO: Handle mentions received while the client is offline
// See also https://github.com/vector-im/element-web/issues/9069
let newCount = currentHighlightCount;
if (newHighlight && !oldHighlight) newCount++;
if (!newHighlight && oldHighlight) newCount--;

if (isThreadEvent) {
room.setThreadUnreadNotificationCount(event.threadRootId, NotificationCountType.Highlight, newCount);
} else {
oldValue = !!oldActions?.tweaks?.highlight;
newValue = !!actions?.tweaks?.highlight;
room.setUnreadNotificationCount(NotificationCountType.Highlight, newCount);
}
}

if (oldValue !== newValue || count > 0) {
if (newValue && !oldValue) count++;
if (!newValue && oldValue) count--;
// Total count is used to typically increment a room notification counter, but not loudly highlight it.
const currentTotalCount = room.getUnreadCountForEventContext(NotificationCountType.Total, event);

if (isThreadEvent) {
room.setThreadUnreadNotificationCount(event.threadRootId, type, count);
} else {
room.setUnreadNotificationCount(type, count);
}
// `notify` is used in practice for incrementing the total count
const newNotify = !!actions?.notify;

// The room total count is NEVER incremented by the server for encrypted rooms. We basically ignore
// the server here as it's always going to tell us to increment for encrypted events.
if (newNotify) {
if (isThreadEvent) {
room.setThreadUnreadNotificationCount(
event.threadRootId,
NotificationCountType.Total,
currentTotalCount + 1,
);
} else {
room.setUnreadNotificationCount(NotificationCountType.Total, currentTotalCount + 1);
}
}
}
6 changes: 2 additions & 4 deletions src/models/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export type MatrixEventHandlerMap = {
* @param event - The matrix event which has been decrypted
* @param err - The error that occurred during decryption, or `undefined` if no error occurred.
*/
[MatrixEventEvent.Decrypted]: (event: MatrixEvent, err: Error | undefined, pushDetails: PushDetails) => void;
[MatrixEventEvent.Decrypted]: (event: MatrixEvent, err?: Error) => void;
[MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void;
[MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void;
[MatrixEventEvent.LocalEventIdReplaced]: (event: MatrixEvent) => void;
Expand Down Expand Up @@ -916,8 +916,6 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
this.retryDecryption = false;
this.setClearData(res);

const pushDetails = this.getPushDetails();

// Before we emit the event, clear the push actions so that they can be recalculated
// by relevant code. We do this because the clear event has now changed, making it
// so that existing rules can be re-run over the applicable properties. Stuff like
Expand All @@ -927,7 +925,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
this.setPushDetails();

if (options.emit !== false) {
this.emit(MatrixEventEvent.Decrypted, this, err, pushDetails);
this.emit(MatrixEventEvent.Decrypted, this, err);
}

return;
Expand Down
Loading