import { MatrixClient } from 'matrix-js-sdk'; import { useMemo, useRef } from 'react'; import { TYPING_TIMEOUT_MS } from '../state/typingMembers'; type TypingStatusUpdater = (typing: boolean) => void; // `disabled` is the caller's opt-out for surfaces where typing // notifications would leak to the wrong audience. The Matrix // `m.typing` EDU is strictly room-scoped (the spec has no `thread_id` // field), so a thread composer that called `mx.sendTyping(roomId, ...)` // would broadcast «X is typing» to every member watching the main // channel chat — even though the user is privately drafting in a // thread drawer. The drawer passes `disabled=true` to keep the // indicator absent rather than misleading. Returns a stable noop // when disabled so call sites keep their stable identity. export const useTypingStatusUpdater = ( mx: MatrixClient, roomId: string, disabled = false ): TypingStatusUpdater => { const statusSentTsRef = useRef(0); const sendTypingStatus: TypingStatusUpdater = useMemo(() => { statusSentTsRef.current = 0; if (disabled) { return () => { /* noop: typing leaks across thread/main boundary by spec */ }; } return (typing) => { if (typing) { if (Date.now() - statusSentTsRef.current < TYPING_TIMEOUT_MS) { return; } mx.sendTyping(roomId, true, TYPING_TIMEOUT_MS); const sentTs = Date.now(); statusSentTsRef.current = sentTs; // Don't believe server will timeout typing status; // Clear typing status after timeout if already not; setTimeout(() => { if (statusSentTsRef.current === sentTs) { mx.sendTyping(roomId, false, TYPING_TIMEOUT_MS); statusSentTsRef.current = 0; } }, TYPING_TIMEOUT_MS); return; } if (Date.now() - statusSentTsRef.current < TYPING_TIMEOUT_MS) { mx.sendTyping(roomId, false, TYPING_TIMEOUT_MS); } statusSentTsRef.current = 0; }; }, [mx, roomId, disabled]); return sendTypingStatus; };