49 lines
2.1 KiB
TypeScript
49 lines
2.1 KiB
TypeScript
import { MatrixClient, MatrixEvent, ReceiptType, Thread } from 'matrix-js-sdk';
|
|
|
|
// Walks a timeline backwards and returns the latest event that is
|
|
// fully sent to the homeserver (not local-echo / pending). Shared by
|
|
// the main-room and thread receipt senders so the «which event is
|
|
// the read marker pointed at» semantic stays consistent.
|
|
//
|
|
// Note: deliberately does NOT skip on `evt.getId() === readEventId`.
|
|
// SDK's `addLocalEchoReceipt` updates the local read-up-to marker
|
|
// synchronously on every `sendReadReceipt` call (read-receipt.js:329-332),
|
|
// so a readEventId-match short-circuit would bail on the second
|
|
// invocation even when the server failed to process the first one
|
|
// (network failure, 5xx, MSC3773-less server) — leaving the unread
|
|
// counters stuck. The server is idempotent on duplicate receipts, so
|
|
// re-sending costs at most one round-trip; callers gate on
|
|
// `document.hasFocus()` to avoid spam from backgrounded tabs.
|
|
const pickLatestNonPending = (events: MatrixEvent[]): MatrixEvent | null => {
|
|
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
const evt = events[i];
|
|
if (evt && !evt.isSending()) {
|
|
return evt;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const receiptType = (privateReceipt: boolean): ReceiptType =>
|
|
privateReceipt ? ReceiptType.ReadPrivate : ReceiptType.Read;
|
|
|
|
export async function markAsRead(mx: MatrixClient, roomId: string, privateReceipt: boolean) {
|
|
const room = mx.getRoom(roomId);
|
|
if (!room) return;
|
|
const latestEvent = pickLatestNonPending(room.getLiveTimeline().getEvents());
|
|
if (!latestEvent) return;
|
|
await mx.sendReadReceipt(latestEvent, receiptType(privateReceipt));
|
|
}
|
|
|
|
// Thread-scoped variant. SDK auto-attaches `thread_id: <rootId>` via
|
|
// `threadIdForReceipt(event)` (matrix-js-sdk client.js:2655).
|
|
export async function markAsThreadRead(
|
|
mx: MatrixClient,
|
|
thread: Thread,
|
|
privateReceipt: boolean
|
|
) {
|
|
if (!mx.getUserId()) return;
|
|
const latestEvent = pickLatestNonPending(thread.liveTimeline.getEvents());
|
|
if (!latestEvent) return;
|
|
await mx.sendReadReceipt(latestEvent, receiptType(privateReceipt));
|
|
}
|