Handle read receipts on web ui

This commit is contained in:
2025-12-01 18:30:22 +01:00
parent 30e63bfdb4
commit 7356a66e4a
6 changed files with 117 additions and 8 deletions

View File

@@ -5,8 +5,9 @@ import type {
MatrixEventsList,
MessageType,
} from "../api/matrix/MatrixApiEvent";
import type { Room } from "../api/matrix/MatrixApiRoom";
import type { Receipt, Room } from "../api/matrix/MatrixApiRoom";
import type { WsMessage } from "../api/WsApi";
import { timeMs } from "./DateUtils";
export interface MessageReaction {
event_id: string;
@@ -29,15 +30,23 @@ export interface Message {
export class RoomEventsManager {
readonly room: Room;
private events: MatrixEvent[];
private receipts: Receipt[];
messages: Message[];
endToken?: string;
typingUsers: string[];
receiptsEventsMap: Map<string, Receipt[]>;
constructor(room: Room, initialMessages: MatrixEventsList) {
constructor(
room: Room,
initialMessages: MatrixEventsList,
receipts: Receipt[]
) {
this.room = room;
this.events = [];
this.receipts = receipts;
this.messages = [];
this.typingUsers = [];
this.receiptsEventsMap = new Map();
this.processNewEvents(initialMessages);
}
@@ -90,9 +99,30 @@ export class RoomEventsManager {
file: m.data.file,
},
};
} else if (m.type === "ReceiptEvent") {
for (const r of m.receipts) {
const prevReceipt = this.receipts.find(
(needle) => r.user === needle.user
);
// Create new receipt
if (!prevReceipt)
this.receipts.push({
user: r.user,
event_id: r.event,
ts: r.ts ?? timeMs(),
});
// Update receipt
else {
prevReceipt.event_id = r.event;
prevReceipt.ts = r.ts ?? timeMs();
}
}
this.rebuildMessagesList();
return true; // Emphemeral event
} else if (m.type === "TypingEvent") {
this.typingUsers = m.user_ids;
return true;
return true; // Not a real event
} else {
// Ignore event
console.info("Event not supported => ignored");
@@ -117,6 +147,12 @@ export class RoomEventsManager {
// Sorts events list to process oldest events first
this.events.sort((a, b) => a.time - b.time);
// Process receipts (users map)
const receiptsUsersMap = new Map<string, Receipt>();
for (const r of this.receipts) {
receiptsUsersMap.set(r.user, { ...r });
}
// First, process redactions to skip redacted events
let redacted = new Set(
this.events
@@ -144,6 +180,24 @@ export class RoomEventsManager {
continue;
}
// Else it is a new message; update receipts if needed
else {
let userReceipt = receiptsUsersMap.get(evt.sender);
// Create fake receipt if none is available
if (!userReceipt)
receiptsUsersMap.set(evt.sender, {
event_id: evt.id,
ts: evt.time,
user: evt.sender,
});
// If the message is more recent than user receipt, replace the receipt
else if (userReceipt.ts < evt.time) {
userReceipt.event_id = evt.id;
userReceipt.ts = evt.time;
}
}
this.messages.push({
event_id: evt.id,
account: evt.sender,
@@ -175,5 +229,13 @@ export class RoomEventsManager {
});
}
}
// Adapt receipts to be event-indexed
this.receiptsEventsMap.clear();
for (const [_userId, receipt] of receiptsUsersMap) {
if (!this.receiptsEventsMap.has(receipt.event_id))
this.receiptsEventsMap.set(receipt.event_id, [receipt]);
else this.receiptsEventsMap.get(receipt.event_id)!.push(receipt);
}
}
}