Files
element-web/src/Unread.ts
David Baker f59af3786e Simplified Sliding Sync (#28515)
* Experimental SSS

Working branch to get SSS functional on element-web.

Requires https://github.com/matrix-org/matrix-js-sdk/pull/4400

* Adjust tests to use new behaviour

* Remove well-known proxy URL lookup; always use native

This is actually required for SSS because otherwise it would use
the proxy over native support.

* Linting

* Debug logging

* Control the race condition when swapping between rooms

* Dont' filter by space as synapse doesn't support it

* Remove SS code related to registering lists and managing ranges

- Update the spidering code to spider all the relevant lists.
- Add canonical alias to the required_state to allow room name calcs to work.

Room sort order is busted because we don't yet look at `bump_stamp`.

* User bumpStamp if it is present

* Drop initial room load from 20 per list to 10

* Half the batch size to trickle more quickly

* Prettier

* prettier on tests too

* Remove proxy URL & unused import

* Hopefully fix tests to assert what the behaviour is supposed to be

* Move the singleton to the manager tyo fix import loop

* Very well, code, I will remove you

Why were you there in the first place?

* Strip out more unused stuff

* Fix playwright test

Seems like this lack of order updating unless a room is selected
was just always a bug with both regular and non-sliding sync. I
have no idea how the test passed on develop because it won't run.

* Fix test to do maybe what it was supposed to do... possibly?

* Remove test for old pre-simplified sliding sync behaviour

* Unused import

* Remove sliding sync proxy & test

I was wrong about what this test was asserting, it was suposed
to assert that notification dots aren't shown (because SS didn't
support them somehow I guess) but they are fine in SSS so the test
is just no longer relevant.

* Remove now pointless credentials

* Remove subscription removal as SSS doesn't do that

* Update tests

* add test

* Switch to new labs flag & break if old labs flag is enabled

* Remove unused import & fix test

* Fix other test

* Remove name & description from old labs flag

as they're not displayed anywhere so not useful

* Remove old sliding sync option

by making it not a feature

* Add back unread nindicator test but inverted

and minus the bit about disabling notification which surely would have
defeated the original point anyway?

* Reinstate test for room_subscriptions

...and also make tests actually use sliding sync

* Use UserFriendlyError

* Remove empty constructor

* Remove unrelated changes

* Unused import

* Fix import

* Avoid moving import

---------

Co-authored-by: Kegan Dougal <7190048+kegsay@users.noreply.github.com>
2025-03-18 17:54:32 +00:00

146 lines
5.4 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2015-2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { M_BEACON, type Room, Thread, type MatrixEvent, EventType, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import shouldHideEvent from "./shouldHideEvent";
import { haveRendererForEvent } from "./events/EventTileFactory";
import { RoomNotifState, getRoomNotifsState } from "./RoomNotifs";
/**
* Returns true if this event arriving in a room should affect the room's
* count of unread messages
*
* @param client The Matrix Client instance of the logged-in user
* @param {Object} ev The event
* @returns {boolean} True if the given event should affect the unread message count
*/
export function eventTriggersUnreadCount(client: MatrixClient, ev: MatrixEvent): boolean {
if (ev.getSender() === client.getSafeUserId()) {
return false;
}
switch (ev.getType()) {
case EventType.RoomMember:
case EventType.RoomThirdPartyInvite:
case EventType.CallAnswer:
case EventType.CallHangup:
case EventType.RoomCanonicalAlias:
case EventType.RoomServerAcl:
case M_BEACON.name:
case M_BEACON.altName:
return false;
}
if (ev.isRedacted()) return false;
return haveRendererForEvent(ev, client, false /* hidden messages should never trigger unread counts anyways */);
}
export function doesRoomHaveUnreadMessages(room: Room, includeThreads: boolean): boolean {
const toCheck: Array<Room | Thread> = [room];
if (includeThreads) {
toCheck.push(...room.getThreads());
}
for (const withTimeline of toCheck) {
if (doesTimelineHaveUnreadMessages(room, withTimeline.timeline)) {
// We found an unread, so the room is unread
return true;
}
}
// If we got here then no timelines were found with unread messages.
return false;
}
function doesTimelineHaveUnreadMessages(room: Room, timeline: Array<MatrixEvent>): boolean {
// The room is a space, let's ignore it
if (room.isSpaceRoom()) return false;
const myUserId = room.client.getSafeUserId();
const latestImportantEventId = findLatestImportantEvent(room.client, timeline)?.getId();
if (latestImportantEventId) {
return !room.hasUserReadEvent(myUserId, latestImportantEventId);
} else {
// We couldn't find an important event to check - check the unimportant ones.
const earliestUnimportantEventId = timeline.at(0)?.getId();
if (!earliestUnimportantEventId) {
// There are no events in this timeline - it is uninitialised, so we
// consider it read
return false;
} else if (room.hasUserReadEvent(myUserId, earliestUnimportantEventId)) {
// Some of the unimportant events are read, and there are no
// important ones after them, so we've read everything.
return false;
} else {
// We have events. and none of them are read. We must guess that
// the timeline is unread, because there could be older unread
// important events that we don't have loaded.
logger.warn("Falling back to unread room because of no read receipt or counting message found", {
roomId: room.roomId,
earliestUnimportantEventId: earliestUnimportantEventId,
});
return true;
}
}
}
/**
* Returns true if this room has unread threads.
* @param room The room to check
* @returns {boolean} True if the given room has unread threads
*/
export function doesRoomHaveUnreadThreads(room: Room): boolean {
if (getRoomNotifsState(room.client, room.roomId) === RoomNotifState.Mute) {
// No unread for muted rooms, nor their threads
// NB. This logic duplicated in RoomNotifs.determineUnreadState
return false;
}
for (const thread of room.getThreads()) {
if (doesTimelineHaveUnreadMessages(room, thread.timeline)) {
// We found an unread, so the room has an unread thread
return true;
}
}
// If we got here then no threads were found with unread messages.
return false;
}
export function doesRoomOrThreadHaveUnreadMessages(roomOrThread: Room | Thread): boolean {
const room = roomOrThread instanceof Thread ? roomOrThread.room : roomOrThread;
const events = roomOrThread instanceof Thread ? roomOrThread.timeline : room.getLiveTimeline().getEvents();
return doesTimelineHaveUnreadMessages(room, events);
}
/**
* Look backwards through the timeline and find the last event that is
* "important" in the sense of isImportantEvent.
*
* @returns the latest important event, or null if none were found
*/
function findLatestImportantEvent(client: MatrixClient, timeline: Array<MatrixEvent>): MatrixEvent | null {
for (let index = timeline.length - 1; index >= 0; index--) {
const event = timeline[index];
if (isImportantEvent(client, event)) {
return event;
}
}
return null;
}
/**
* Given this event does not have a receipt, is it important enough to make
* this room unread?
*/
function isImportantEvent(client: MatrixClient, event: MatrixEvent): boolean {
return !shouldHideEvent(event) && eventTriggersUnreadCount(client, event);
}