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 {createContext, useCallback, useContext, useEffect, useMemo} from 'react'
|
||||||
import {
|
import {
|
||||||
|
type ChatBskyActorDefs,
|
||||||
ChatBskyConvoDefs,
|
ChatBskyConvoDefs,
|
||||||
type ChatBskyConvoListConvos,
|
type ChatBskyConvoListConvos,
|
||||||
moderateProfile,
|
moderateProfile,
|
||||||
@@ -19,7 +20,10 @@ import {useMessagesEventBus} from '#/state/messages/events'
|
|||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {useAgent, useSession} from '#/state/session'
|
||||||
import {parseConvoView} from '#/components/dms/util'
|
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 {useLeftConvos} from './leave-conversation'
|
||||||
|
import {listConvoMembersQueryKey} from './list-convo-members'
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 10
|
const DEFAULT_LIMIT = 10
|
||||||
export const UNREAD_LIMIT = 20
|
export const UNREAD_LIMIT = 20
|
||||||
@@ -138,6 +142,80 @@ export function ListConvosProviderInner({
|
|||||||
events => {
|
events => {
|
||||||
if (events.type !== 'logs') return
|
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) {
|
for (const log of events.logs) {
|
||||||
if (ChatBskyConvoDefs.isLogBeginConvo(log)) {
|
if (ChatBskyConvoDefs.isLogBeginConvo(log)) {
|
||||||
debouncedRefetch()
|
debouncedRefetch()
|
||||||
@@ -431,6 +509,74 @@ export function ListConvosProviderInner({
|
|||||||
rev: log.rev,
|
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)) {
|
} else if (ChatBskyConvoDefs.isLogRemoveReaction(log)) {
|
||||||
queryClient.setQueriesData(
|
queryClient.setQueriesData(
|
||||||
{queryKey: [RQKEY_ROOT]},
|
{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) {
|
function optimisticDelete(chatId: string, old?: ConvoListQueryData) {
|
||||||
if (!old) return old
|
if (!old) return old
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
import {useEffect} from 'react'
|
import {type ChatBskyActorDefs} from '@atproto/api'
|
||||||
import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
|
import {type QueryClient, useQuery} from '@tanstack/react-query'
|
||||||
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||||
import {useMessagesEventBus} from '#/state/messages/events'
|
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {createQueryKey} from '#/state/queries/util'
|
import {createQueryKey} from '#/state/queries/util'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
import * as bsky from '#/types/bsky'
|
|
||||||
|
|
||||||
const RQKEY_ROOT = 'listConvoMembers'
|
const RQKEY_ROOT = 'listConvoMembers'
|
||||||
export const listConvoMembersQueryKey = (convoId: string) =>
|
export const listConvoMembersQueryKey = (convoId: string) =>
|
||||||
createQueryKey(RQKEY_ROOT, {convoId})
|
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
|
const LIMIT = 50
|
||||||
|
|
||||||
export function useListConvoMembersQuery({
|
export function useListConvoMembersQuery({
|
||||||
@@ -24,94 +21,6 @@ export function useListConvoMembersQuery({
|
|||||||
placeholderData?: ChatBskyActorDefs.ProfileViewBasic[]
|
placeholderData?: ChatBskyActorDefs.ProfileViewBasic[]
|
||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
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({
|
return useQuery({
|
||||||
queryKey: listConvoMembersQueryKey(convoId),
|
queryKey: listConvoMembersQueryKey(convoId),
|
||||||
|
|||||||
Reference in New Issue
Block a user