vojo/src/app/utils/notifications.ts

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));
}