61 lines
1.9 KiB
TypeScript
61 lines
1.9 KiB
TypeScript
import { listPeerReadsForMessages, markMessagesRead } from '@chat-app/shared/chat';
|
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
|
import { supabase } from './supabase';
|
|
|
|
// Tracks which of our own messages the peer has read. For groups this would
|
|
// return a per-message Map<userId, readAt>; M1 is DM-focused so we just return
|
|
// a Set of message ids read by the single peer.
|
|
export function useMessageReads(
|
|
messageIds: string[],
|
|
peerUserId: string | undefined,
|
|
): { peerReadSet: Set<string>; refresh: () => Promise<void> } {
|
|
const idsKey = useMemo(() => messageIds.join(','), [messageIds]);
|
|
const [peerReadSet, setPeerReadSet] = useState<Set<string>>(new Set());
|
|
|
|
const refresh = useCallback(async () => {
|
|
if (!peerUserId || messageIds.length === 0) {
|
|
setPeerReadSet(new Set());
|
|
return;
|
|
}
|
|
try {
|
|
const s = await listPeerReadsForMessages(supabase, messageIds, peerUserId);
|
|
setPeerReadSet(s);
|
|
} catch (err: unknown) {
|
|
console.error('listPeerReadsForMessages failed', err);
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [peerUserId, idsKey]);
|
|
|
|
useEffect(() => {
|
|
void refresh();
|
|
if (!peerUserId) return;
|
|
const channel = supabase
|
|
.channel('reads:' + peerUserId)
|
|
.on(
|
|
'postgres_changes',
|
|
{
|
|
event: 'INSERT',
|
|
schema: 'public',
|
|
table: 'message_reads',
|
|
filter: 'user_id=eq.' + peerUserId,
|
|
},
|
|
() => {
|
|
void refresh();
|
|
},
|
|
)
|
|
.subscribe();
|
|
return () => {
|
|
void supabase.removeChannel(channel);
|
|
};
|
|
}, [peerUserId, refresh]);
|
|
|
|
return { peerReadSet, refresh };
|
|
}
|
|
|
|
// Mark a batch of messages as read. Caller uses this when they arrive while
|
|
// the conversation is actively being viewed.
|
|
export async function markRead(messageIds: string[]): Promise<void> {
|
|
await markMessagesRead(supabase, messageIds);
|
|
}
|