Colocate all chat log event handlers (#10632)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {createContext, useCallback, useContext, useEffect, useMemo} from 'react'
|
||||
import {
|
||||
type ChatBskyActorDefs,
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyConvoListConvos,
|
||||
moderateProfile,
|
||||
@@ -19,7 +20,10 @@ import {useMessagesEventBus} from '#/state/messages/events'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {parseConvoView} from '#/components/dms/util'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {RQKEY as CONVO_KEY} from './conversation'
|
||||
import {useLeftConvos} from './leave-conversation'
|
||||
import {listConvoMembersQueryKey} from './list-convo-members'
|
||||
|
||||
const DEFAULT_LIMIT = 10
|
||||
export const UNREAD_LIMIT = 20
|
||||
@@ -138,6 +142,80 @@ export function ListConvosProviderInner({
|
||||
events => {
|
||||
if (events.type !== 'logs') return
|
||||
|
||||
function mutateMembers(
|
||||
convoId: string,
|
||||
fn: (
|
||||
members: ChatBskyActorDefs.ProfileViewBasic[],
|
||||
) => ChatBskyActorDefs.ProfileViewBasic[],
|
||||
) {
|
||||
queryClient.setQueryData<ChatBskyActorDefs.ProfileViewBasic[]>(
|
||||
listConvoMembersQueryKey(convoId),
|
||||
old => {
|
||||
if (!old) return // query doesn't exist yet, skip
|
||||
return fn(old)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
function mutateConvoView(
|
||||
convoId: string,
|
||||
fn: (
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
) => ChatBskyConvoDefs.ConvoView,
|
||||
) {
|
||||
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
old => (old ? fn(old) : old),
|
||||
)
|
||||
queryClient.setQueriesData<ConvoListQueryData>(
|
||||
{queryKey: [RQKEY_ROOT]},
|
||||
old => optimisticUpdate(convoId, old, fn),
|
||||
)
|
||||
}
|
||||
|
||||
function handleMemberAdded(
|
||||
convoId: string,
|
||||
did: string,
|
||||
relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[],
|
||||
rev: string,
|
||||
) {
|
||||
const newMember = relatedProfiles.find(r => r.did === did)
|
||||
if (!newMember) return
|
||||
// If the optimistic add already added them, skip the
|
||||
// memberCount bump to avoid double-counting.
|
||||
const alreadyKnownMember =
|
||||
queryClient
|
||||
.getQueryData<
|
||||
ChatBskyActorDefs.ProfileViewBasic[]
|
||||
>(listConvoMembersQueryKey(convoId))
|
||||
?.some(m => m.did === did) ?? false
|
||||
mutateMembers(convoId, list =>
|
||||
list.some(m => m.did === did) ? list : list.concat(newMember),
|
||||
)
|
||||
mutateConvoView(convoId, convo =>
|
||||
addMemberToConvoView(convo, newMember, rev, alreadyKnownMember),
|
||||
)
|
||||
}
|
||||
|
||||
function handleMemberRemoved(
|
||||
convoId: string,
|
||||
did: string,
|
||||
rev: string,
|
||||
) {
|
||||
// If the optimistic remove already dropped them from the full
|
||||
// list, skip the memberCount decrement to avoid double-counting.
|
||||
const alreadyRemovedMember =
|
||||
queryClient
|
||||
.getQueryData<
|
||||
ChatBskyActorDefs.ProfileViewBasic[]
|
||||
>(listConvoMembersQueryKey(convoId))
|
||||
?.some(m => m.did === did) === false
|
||||
mutateMembers(convoId, list => list.filter(m => m.did !== did))
|
||||
mutateConvoView(convoId, convo =>
|
||||
removeMemberFromConvoView(convo, did, rev, alreadyRemovedMember),
|
||||
)
|
||||
}
|
||||
|
||||
for (const log of events.logs) {
|
||||
if (ChatBskyConvoDefs.isLogBeginConvo(log)) {
|
||||
debouncedRefetch()
|
||||
@@ -431,6 +509,74 @@ export function ListConvosProviderInner({
|
||||
rev: log.rev,
|
||||
})),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogAddMember(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataAddMember>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataAddMember,
|
||||
)
|
||||
) {
|
||||
handleMemberAdded(
|
||||
log.convoId,
|
||||
data.member.did,
|
||||
log.relatedProfiles,
|
||||
log.rev,
|
||||
)
|
||||
}
|
||||
// Refetch so the server can refresh the curated members list.
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogRemoveMember(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataRemoveMember>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataRemoveMember,
|
||||
)
|
||||
) {
|
||||
handleMemberRemoved(log.convoId, data.member.did, log.rev)
|
||||
}
|
||||
// Refetch so the server can refill the curated members list.
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogMemberJoin(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataMemberJoin>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataMemberJoin,
|
||||
)
|
||||
) {
|
||||
handleMemberAdded(
|
||||
log.convoId,
|
||||
data.member.did,
|
||||
log.relatedProfiles,
|
||||
log.rev,
|
||||
)
|
||||
}
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogMemberLeave(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataMemberLeave>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataMemberLeave,
|
||||
)
|
||||
) {
|
||||
handleMemberRemoved(log.convoId, data.member.did, log.rev)
|
||||
}
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogRemoveReaction(log)) {
|
||||
queryClient.setQueriesData(
|
||||
{queryKey: [RQKEY_ROOT]},
|
||||
@@ -616,6 +762,53 @@ function optimisticUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
function removeMemberFromConvoView(
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
did: string,
|
||||
rev: string,
|
||||
alreadyRemovedMember: boolean,
|
||||
): ChatBskyConvoDefs.ConvoView {
|
||||
// Member add/remove/join/leave events are only meaningful for group convos.
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) return convo
|
||||
const nextMembers = convo.members.filter(m => m.did !== did)
|
||||
return {
|
||||
...convo,
|
||||
rev,
|
||||
members: nextMembers,
|
||||
kind: {
|
||||
...convo.kind,
|
||||
memberCount: alreadyRemovedMember
|
||||
? convo.kind.memberCount
|
||||
: Math.max(0, convo.kind.memberCount - 1),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function addMemberToConvoView(
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
member: ChatBskyActorDefs.ProfileViewBasic,
|
||||
rev: string,
|
||||
alreadyKnownMember: boolean,
|
||||
): ChatBskyConvoDefs.ConvoView {
|
||||
// Member add/remove/join/leave events are only meaningful for group convos.
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) return convo
|
||||
const alreadyInCuratedList = convo.members.some(m => m.did === member.did)
|
||||
const nextMembers = alreadyInCuratedList
|
||||
? convo.members
|
||||
: convo.members.concat(member)
|
||||
return {
|
||||
...convo,
|
||||
rev,
|
||||
members: nextMembers,
|
||||
kind: {
|
||||
...convo.kind,
|
||||
memberCount: alreadyKnownMember
|
||||
? convo.kind.memberCount
|
||||
: convo.kind.memberCount + 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function optimisticDelete(chatId: string, old?: ConvoListQueryData) {
|
||||
if (!old) return old
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import {useEffect} from 'react'
|
||||
import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
import {type ChatBskyActorDefs} from '@atproto/api'
|
||||
import {type QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useMessagesEventBus} from '#/state/messages/events'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
const RQKEY_ROOT = 'listConvoMembers'
|
||||
export const listConvoMembersQueryKey = (convoId: string) =>
|
||||
createQueryKey(RQKEY_ROOT, {convoId})
|
||||
|
||||
// group chat size is 50, so should fetch the whole list in one go
|
||||
// Group chat size is at least 50, so should fetch the whole list in one go
|
||||
const LIMIT = 50
|
||||
|
||||
export function useListConvoMembersQuery({
|
||||
@@ -24,94 +21,6 @@ export function useListConvoMembersQuery({
|
||||
placeholderData?: ChatBskyActorDefs.ProfileViewBasic[]
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
const messagesBus = useMessagesEventBus()
|
||||
|
||||
useEffect(() => {
|
||||
const unsub = messagesBus.on(
|
||||
ev => {
|
||||
if (ev.type !== 'logs') return
|
||||
|
||||
function mutateList(
|
||||
fn: (
|
||||
update: ChatBskyActorDefs.ProfileViewBasic[],
|
||||
) => ChatBskyActorDefs.ProfileViewBasic[],
|
||||
) {
|
||||
queryClient.setQueryData<ChatBskyActorDefs.ProfileViewBasic[]>(
|
||||
listConvoMembersQueryKey(convoId),
|
||||
old => {
|
||||
if (!old) return // query doesn't exist yet, skip
|
||||
return fn(old)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for (const log of ev.logs) {
|
||||
if (ChatBskyConvoDefs.isLogAddMember(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataAddMember>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataAddMember,
|
||||
)
|
||||
) {
|
||||
const newMember = log.relatedProfiles.find(
|
||||
r => r.did === data.member.did,
|
||||
)
|
||||
if (newMember) {
|
||||
mutateList(list =>
|
||||
list.some(m => m.did === newMember.did)
|
||||
? list
|
||||
: list.concat(newMember),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (ChatBskyConvoDefs.isLogRemoveMember(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataRemoveMember>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataRemoveMember,
|
||||
)
|
||||
) {
|
||||
mutateList(list => list.filter(m => m.did !== data.member.did))
|
||||
}
|
||||
} else if (ChatBskyConvoDefs.isLogMemberJoin(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataMemberJoin>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataMemberJoin,
|
||||
)
|
||||
) {
|
||||
const newMember = log.relatedProfiles.find(
|
||||
r => r.did === data.member.did,
|
||||
)
|
||||
if (newMember) {
|
||||
mutateList(list =>
|
||||
list.some(m => m.did === newMember.did)
|
||||
? list
|
||||
: list.concat(newMember),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (ChatBskyConvoDefs.isLogMemberLeave(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataMemberLeave>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataMemberLeave,
|
||||
)
|
||||
) {
|
||||
mutateList(list => list.filter(m => m.did !== data.member.did))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{convoId},
|
||||
)
|
||||
return () => unsub()
|
||||
}, [convoId, messagesBus, queryClient])
|
||||
|
||||
return useQuery({
|
||||
queryKey: listConvoMembersQueryKey(convoId),
|
||||
|
||||
Reference in New Issue
Block a user