Files
cinny/src/app/state/hooks/useAutoMarkVerificationRead.ts
T

59 lines
2.5 KiB
TypeScript
Raw Normal View History

import { useEffect, useRef } from 'react';
import { MatrixClient, MatrixEvent, MatrixEventEvent, Room } from 'matrix-js-sdk';
import { roomHaveNotification, unreadIsOnlyVerification } from '../../utils/room';
import { markAsRead } from '../../utils/notifications';
/**
* A COMPLETED in-room device-verification request is a plain `m.room.message`
* that permanently keeps a DM's server/SDK notification count > 0 (it matches the
* default DM push rule and there's no recency gate), so the DM re-lights as unread
* on every fresh sync. `getUnreadInfo`'s suppression hides the dot, but the raw
* SDK count stays "dirty" (desktop badge, other consumers) and the SDK re-inflates
* it on every decrypt. The only durable, SDK-supported fix is a read receipt that
* covers the request event.
*
* This hook sends that receipt — but ONLY when a room's ENTIRE unread span is
* verification-flow events (`unreadIsOnlyVerification`), so it can never mark a
* real unread message read. It fires at most once per room per session, after the
* tail decrypts (the count is only attributable to the request post-decryption).
* `markAsRead` honours the user's private-read-receipt setting.
*/
export const useAutoMarkVerificationRead = (mx: MatrixClient): void => {
const doneRef = useRef<Set<string>>(new Set());
useEffect(() => {
const done = doneRef.current;
const maybeMark = (room: Room) => {
const { roomId } = room;
if (done.has(roomId)) return;
if (room.getMyMembership() !== 'join') return;
// Only touch rooms the SDK actually counts as notifying...
if (!roomHaveNotification(room)) return;
// ...and only when the whole unread span is a completed verification.
if (!unreadIsOnlyVerification(room, mx.getUserId())) return;
done.add(roomId);
markAsRead(mx, roomId, false).catch(() => {
// Let a later decrypt/sweep retry on transient failure.
done.delete(roomId);
});
};
// Sweep once on mount (after initial sync some verification tails are already
// decrypted), then re-check whenever an event decrypts — the count only
// becomes attributable to the verification request once it's decrypted.
mx.getRooms().forEach(maybeMark);
const onDecrypted = (event: MatrixEvent) => {
const roomId = event.getRoomId();
const room = roomId ? mx.getRoom(roomId) : null;
if (room) maybeMark(room);
};
mx.on(MatrixEventEvent.Decrypted, onDecrypted);
return () => {
mx.removeListener(MatrixEventEvent.Decrypted, onDecrypted);
};
}, [mx]);
};