[SDK] Migrate group chat and join links to the chat client (#11358)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-08-13 22:26:16 +03:00
committed by GitHub
parent 0e8e0b2ab4
commit ea4f70fb1e
21 changed files with 263 additions and 259 deletions
+21 -24
View File
@@ -1,5 +1,4 @@
import {useCallback} from 'react'
import {ChatBskyGroupCreateGroup} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
@@ -88,29 +87,27 @@ export function NewChat({
let errorMessage = l`An issue occurred starting the group chat, please try again.`
if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.`
} else if (
error instanceof ChatBskyGroupCreateGroup.AccountSuspendedError
) {
errorMessage = l`Suspended accounts cannot participate in a group chat.`
} else if (error instanceof ChatBskyGroupCreateGroup.BlockedActorError) {
errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.`
} else if (
error instanceof
ChatBskyGroupCreateGroup.NewAccountCannotCreateGroupError
) {
errorMessage = l`You cannot create a group chat yet.`
} else if (
error instanceof ChatBskyGroupCreateGroup.NotFollowedBySenderError
) {
errorMessage = l`A selected recipient is not followed by the sender.`
} else if (
error instanceof ChatBskyGroupCreateGroup.RecipientNotFoundError
) {
errorMessage = l`Unable to find a selected recipient.`
} else if (
error instanceof ChatBskyGroupCreateGroup.UserForbidsGroupsError
) {
errorMessage = l`One of the selected recipients does not allow group chats.`
} else {
switch (matchXrpcError(error, chat.bsky.group.createGroup)) {
case 'AccountSuspended':
errorMessage = l`Suspended accounts cannot participate in a group chat.`
break
case 'BlockedActor':
errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.`
break
case 'NewAccountCannotCreateGroup':
errorMessage = l`You cannot create a group chat yet.`
break
case 'NotFollowedBySender':
errorMessage = l`A selected recipient is not followed by the sender.`
break
case 'RecipientNotFound':
errorMessage = l`Unable to find a selected recipient.`
break
case 'UserForbidsGroups':
errorMessage = l`One of the selected recipients does not allow group chats.`
break
}
}
Toast.show(errorMessage, {
type: 'error',
@@ -1,5 +1,4 @@
import {useCallback, useState} from 'react'
import {ChatBskyGroupCreateGroup} from '@atproto/api'
import {useLingui} from '@lingui/react/macro'
import {isNetworkError} from '#/lib/strings/errors'
@@ -104,29 +103,27 @@ function SendViaChatDialogInner({
let errorMessage = l`An issue occurred starting the group chat, please try again.`
if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.`
} else if (
error instanceof ChatBskyGroupCreateGroup.AccountSuspendedError
) {
errorMessage = l`Suspended accounts cannot participate in a group chat.`
} else if (error instanceof ChatBskyGroupCreateGroup.BlockedActorError) {
errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.`
} else if (
error instanceof
ChatBskyGroupCreateGroup.NewAccountCannotCreateGroupError
) {
errorMessage = l`You cannot create a group chat yet.`
} else if (
error instanceof ChatBskyGroupCreateGroup.NotFollowedBySenderError
) {
errorMessage = l`A selected recipient is not followed by the sender.`
} else if (
error instanceof ChatBskyGroupCreateGroup.RecipientNotFoundError
) {
errorMessage = l`Unable to find a selected recipient.`
} else if (
error instanceof ChatBskyGroupCreateGroup.UserForbidsGroupsError
) {
errorMessage = l`One of the selected recipients does not allow group chats.`
} else {
switch (matchXrpcError(error, chat.bsky.group.createGroup)) {
case 'AccountSuspended':
errorMessage = l`Suspended accounts cannot participate in a group chat.`
break
case 'BlockedActor':
errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.`
break
case 'NewAccountCannotCreateGroup':
errorMessage = l`You cannot create a group chat yet.`
break
case 'NotFollowedBySender':
errorMessage = l`A selected recipient is not followed by the sender.`
break
case 'RecipientNotFound':
errorMessage = l`Unable to find a selected recipient.`
break
case 'UserForbidsGroups':
errorMessage = l`One of the selected recipients does not allow group chats.`
break
}
}
Toast.show(errorMessage, {
type: 'error',
+35 -34
View File
@@ -1,11 +1,6 @@
import {useEffect} from 'react'
import {View} from 'react-native'
import {
ChatBskyGroupDefs,
ChatBskyGroupRequestJoin,
ChatBskyGroupWithdrawJoinRequest,
moderateProfile,
} from '@atproto/api'
import {ChatBskyGroupDefs, moderateProfile} from '@atproto/api'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
@@ -16,6 +11,7 @@ import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types'
import {isNetworkError} from '#/lib/strings/errors'
import {sanitizeHandle} from '#/lib/strings/handles'
import {matchXrpcError} from '#/lib/xrpc-error'
import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {
@@ -49,6 +45,7 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {chat} from '#/lexicons'
import {ProfileBadges} from '../ProfileBadges'
export function GroupChatJoinDialog() {
@@ -147,33 +144,37 @@ function GroupChatJoinDialogContent({code}: {code?: string}) {
let errorMessage = l`Failed to join the group chat. Please try again.`
if (isNetworkError(error)) {
errorMessage = l`There was a problem with your internet connection, please try again`
} else if (error instanceof ChatBskyGroupRequestJoin.ConvoLockedError) {
errorMessage = l`This conversation is locked.`
} else if (
error instanceof ChatBskyGroupRequestJoin.FollowRequiredError
) {
errorMessage = l`Only followers can join this group chat.`
} else if (error instanceof ChatBskyGroupRequestJoin.InvalidCodeError) {
errorMessage = l`Invalid group chat code.`
} else if (
error instanceof ChatBskyGroupRequestJoin.LinkDisabledError
) {
errorMessage = l`This invite link has been disabled.`
} else if (
error instanceof ChatBskyGroupRequestJoin.MemberLimitReachedError
) {
errorMessage = l`The member limit has been reached.`
const preview = data?.joinLinkPreviews[0]
if (
ChatBskyGroupDefs.isJoinLinkPreviewView(preview) &&
preview.convo?.id
) {
ax.metric('groupchat:join:memberLimitReached', {
convoId: preview.convo.id,
})
} else {
switch (matchXrpcError(error, chat.bsky.group.requestJoin)) {
case 'ConvoLocked':
errorMessage = l`This conversation is locked.`
break
case 'FollowRequired':
errorMessage = l`Only followers can join this group chat.`
break
case 'InvalidCode':
errorMessage = l`Invalid group chat code.`
break
case 'LinkDisabled':
errorMessage = l`This invite link has been disabled.`
break
case 'MemberLimitReached': {
errorMessage = l`The member limit has been reached.`
const preview = data?.joinLinkPreviews[0]
if (
ChatBskyGroupDefs.isJoinLinkPreviewView(preview) &&
preview.convo?.id
) {
ax.metric('groupchat:join:memberLimitReached', {
convoId: preview.convo.id,
})
}
break
}
case 'UserKicked':
errorMessage = l`You have been previously removed from this group and cant join it using this link.`
break
}
} else if (error instanceof ChatBskyGroupRequestJoin.UserKickedError) {
errorMessage = l`You have been previously removed from this group and cant join it using this link.`
}
Toast.show(errorMessage)
},
@@ -194,8 +195,8 @@ function GroupChatJoinDialogContent({code}: {code?: string}) {
if (isNetworkError(error)) {
errorMessage = l`There was a problem with your internet connection, please try again`
} else if (
error instanceof
ChatBskyGroupWithdrawJoinRequest.InvalidJoinRequestError
matchXrpcError(error, chat.bsky.group.withdrawJoinRequest) ===
'InvalidJoinRequest'
) {
errorMessage = l`Invalid rescind request.`
}
+10 -9
View File
@@ -1,6 +1,6 @@
import {useState} from 'react'
import {View} from 'react-native'
import {type ChatBskyConvoDefs, ChatBskyGroupRemoveMembers} from '@atproto/api'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'
@@ -309,14 +309,15 @@ function MutualGroupChat({
let errorMessage = l`Could not remove member.`
if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.`
} else if (
error instanceof ChatBskyGroupRemoveMembers.InvalidConvoError
) {
errorMessage = l`Chat not found.`
} else if (
error instanceof ChatBskyGroupRemoveMembers.InsufficientRoleError
) {
errorMessage = l`You must be a chat owner to remove a member.`
} else {
switch (matchXrpcError(error, chat.bsky.group.removeMembers)) {
case 'InvalidConvo':
errorMessage = l`Chat not found.`
break
case 'InsufficientRole':
errorMessage = l`You must be a chat owner to remove a member.`
break
}
}
Toast.show(errorMessage, {type: 'error'})
},
+24 -26
View File
@@ -1,10 +1,6 @@
import {useState} from 'react'
import {View} from 'react-native'
import {
ChatBskyGroupApproveJoinRequest,
type ChatBskyGroupListJoinRequests,
ChatBskyGroupRejectJoinRequest,
} from '@atproto/api'
import {type ChatBskyGroupListJoinRequests} from '@atproto/api'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {type InfiniteData, useQueryClient} from '@tanstack/react-query'
@@ -16,6 +12,7 @@ import {
type NativeStackScreenProps,
type NavigationProp,
} from '#/lib/routes/types'
import {matchXrpcError} from '#/lib/xrpc-error'
import {logger} from '#/logger'
import {ConvoProvider, useConvo} from '#/state/messages/convo'
import {ConvoStatus} from '#/state/messages/convo/types'
@@ -43,6 +40,7 @@ import * as ProfileCard from '#/components/ProfileCard'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {chat} from '#/lexicons'
import type * as bsky from '#/types/bsky'
import {InviteLinkDialog} from './components/InviteLinkDialog'
@@ -190,19 +188,18 @@ function JoinRequestsList({
let errorMessage = l`Failed to accept join request`
if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.`
} else if (
error instanceof ChatBskyGroupApproveJoinRequest.InvalidConvoError
) {
errorMessage = l`Conversation not found.`
} else if (
error instanceof ChatBskyGroupApproveJoinRequest.InsufficientRoleError
) {
errorMessage = l`Only admins can accept join requests.`
} else if (
error instanceof
ChatBskyGroupApproveJoinRequest.MemberLimitReachedError
) {
errorMessage = l`The member limit has been reached.`
} else {
switch (matchXrpcError(error, chat.bsky.group.approveJoinRequest)) {
case 'InvalidConvo':
errorMessage = l`Conversation not found.`
break
case 'InsufficientRole':
errorMessage = l`Only admins can accept join requests.`
break
case 'MemberLimitReached':
errorMessage = l`The member limit has been reached.`
break
}
}
Toast.show(errorMessage, {type: 'error'})
},
@@ -223,14 +220,15 @@ function JoinRequestsList({
let errorMessage = l`Failed to reject join request`
if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.`
} else if (
error instanceof ChatBskyGroupRejectJoinRequest.InvalidConvoError
) {
errorMessage = l`Conversation not found.`
} else if (
error instanceof ChatBskyGroupRejectJoinRequest.InsufficientRoleError
) {
errorMessage = l`Only admins can reject join requests.`
} else {
switch (matchXrpcError(error, chat.bsky.group.rejectJoinRequest)) {
case 'InvalidConvo':
errorMessage = l`Conversation not found.`
break
case 'InsufficientRole':
errorMessage = l`Only admins can reject join requests.`
break
}
}
Toast.show(errorMessage, {type: 'error'})
},
@@ -1,11 +1,9 @@
import {View} from 'react-native'
import {
type ChatBskyGroupDefs,
ChatBskyGroupWithdrawJoinRequest,
} from '@atproto/api'
import {type ChatBskyGroupDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {isNetworkError} from '#/lib/strings/errors'
import {matchXrpcError} from '#/lib/xrpc-error'
import {useWithdrawJoinGroupChatRequest} from '#/state/queries/messages/withdraw-join-group-chat'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {atoms as a, useTheme, web} from '#/alf'
@@ -14,6 +12,7 @@ import {createStaticClick, Link} from '#/components/Link'
import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {chat} from '#/lexicons'
export function OutgoingRequestListItem({
convo: convoView,
@@ -35,8 +34,8 @@ export function OutgoingRequestListItem({
if (isNetworkError(error)) {
errorMessage = l`There was a problem with your internet connection, please try again`
} else if (
error instanceof
ChatBskyGroupWithdrawJoinRequest.InvalidJoinRequestError
matchXrpcError(error, chat.bsky.group.withdrawJoinRequest) ===
'InvalidJoinRequest'
) {
errorMessage = l`Invalid rescind request.`
}
+38 -20
View File
@@ -1,17 +1,19 @@
import {useCallback} from 'react'
import {
type $Typed,
AtpAgent,
ChatBskyGroupDefs,
type ChatBskyGroupGetJoinLinkPreviews,
} from '@atproto/api'
import {type Client} from '@atproto/lex'
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
import {CHAT_SERVICE, DM_SERVICE_HEADERS} from '#/lib/constants'
import {CHAT_SERVICE} from '#/lib/constants'
import {createLexClient} from '#/lib/lexClient'
import {logger} from '#/logger'
import {STALE} from '#/state/queries/index'
import {createQueryKey, type StructuredQueryKey} from '#/state/queries/util'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
/**
* The three preview shapes we currently support. Excludes the `{$type: string}`
@@ -154,23 +156,39 @@ export function invalidateJoinLinkPreviewsForConvo(
})
}
let publicChatClient: Client | undefined
/**
* The unauthenticated {@link Client} for the chat service, used to resolve join
* links before the viewer has a session.
*
* It talks to {@link CHAT_SERVICE} directly rather than proxying through a PDS,
* so no `atproto-proxy` header is involved. A single module-level instance,
* because there is no session to scope it to and reconstructing it per call
* would be waste.
*
* Unlike the public appview client this does NOT wrap `networkAwareFetch`,
* matching the plain-fetch behavior of the ad-hoc agent it replaces: a
* logged-out link resolution failing says nothing useful about the session's
* reachability, so it should not move the app-wide network signal.
*/
function getPublicChatClient(): Client {
return (publicChatClient ??= createLexClient({service: CHAT_SERVICE}))
}
async function fetchJoinLinkPreviews({
agent,
client,
codes,
hasSession,
}: {
agent: AtpAgent
client: Client
codes: string[]
hasSession: boolean
}) {
const previewAgent = new AtpAgent({service: CHAT_SERVICE})
const res = hasSession
? await agent.chat.bsky.group.getJoinLinkPreviews(
{codes},
{headers: DM_SERVICE_HEADERS},
)
: await previewAgent.chat.bsky.group.getJoinLinkPreviews({codes})
return res.data
return await (hasSession ? client : getPublicChatClient()).call(
chat.bsky.group.getJoinLinkPreviews,
{codes},
)
}
export function useJoinLinkPreviewsQuery({
@@ -188,14 +206,14 @@ export function useJoinLinkPreviewsQuery({
*/
initialData?: ChatBskyGroupGetJoinLinkPreviews.OutputSchema
}) {
const agent = useAgent()
const client = useChatClient()
return useQuery({
queryKey: createJoinLinkPreviewQueryKey({codes: codes ?? [], hasSession}),
queryFn: async () => {
if (!codes) throw new Error('No invite code')
try {
return await fetchJoinLinkPreviews({agent, codes, hasSession})
return await fetchJoinLinkPreviews({client, codes, hasSession})
} catch (error) {
logger.error('Failed to fetch join link preview', {safeMessage: error})
throw error
@@ -208,13 +226,13 @@ export function useJoinLinkPreviewsQuery({
}
export function usePrefetchJoinLinkPreviews() {
const agent = useAgent()
const client = useChatClient()
const queryClient = useQueryClient()
return ({codes, hasSession}: {codes: string[]; hasSession: boolean}) => {
return queryClient.prefetchQuery({
queryKey: createJoinLinkPreviewQueryKey({codes, hasSession}),
queryFn: () => fetchJoinLinkPreviews({agent, codes, hasSession}),
queryFn: () => fetchJoinLinkPreviews({client, codes, hasSession}),
staleTime: STALE.SECONDS.FIFTEEN,
})
}
@@ -226,7 +244,7 @@ export function usePrefetchJoinLinkPreviews() {
* Returns undefined if the preview can't be resolved.
*/
export function useGetJoinLinkPreview() {
const agent = useAgent()
const client = useChatClient()
const queryClient = useQueryClient()
return useCallback(
@@ -241,7 +259,7 @@ export function useGetJoinLinkPreview() {
const data = await queryClient.fetchQuery({
queryKey: createJoinLinkPreviewQueryKey({codes: [code], hasSession}),
queryFn: () =>
fetchJoinLinkPreviews({agent, codes: [code], hasSession}),
fetchJoinLinkPreviews({client, codes: [code], hasSession}),
staleTime: STALE.SECONDS.FIFTEEN,
})
const found = data.joinLinkPreviews[0]
@@ -251,6 +269,6 @@ export function useGetJoinLinkPreview() {
return undefined
}
},
[agent, queryClient],
[client, queryClient],
)
}
@@ -4,16 +4,17 @@ import {
type ChatBskyConvoListConvos,
type ChatBskyGroupAddMembers,
} from '@atproto/api'
import {type DidString} from '@atproto/syntax'
import {
type InfiniteData,
useMutation,
useQueryClient,
} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useProfileQuery} from '#/state/queries/profile'
import {useAgent, useSession} from '#/state/session'
import {useChatClient, useSession} from '#/state/session'
import {chat} from '#/lexicons'
import type * as bsky from '#/types/bsky'
import {RQKEY as CONVO_KEY} from './conversation'
import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations'
@@ -30,7 +31,7 @@ export function useAddGroupMembers(
},
) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
const {currentAccount} = useSession()
const {data: myProfile} = useProfileQuery({did: currentAccount?.did})
@@ -42,11 +43,11 @@ export function useAddGroupMembers(
profiles: bsky.profile.AnyProfileView[]
}) => {
if (!convoId) throw new Error('No convoId provided')
const {data} = await agent.chat.bsky.group.addMembers(
{convoId, members},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
return data
return await client.call(chat.bsky.group.addMembers, {
convoId,
// callers pass already-resolved actor dids
members: members as DidString[],
})
},
onMutate: ({profiles}) => {
if (!convoId) return
@@ -1,9 +1,10 @@
import {type ChatBskyGroupCreateGroup} from '@atproto/api'
import {type DidString} from '@atproto/syntax'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {precacheConvoQuery} from './conversation'
export function useCreateGroupChat({
@@ -14,16 +15,15 @@ export function useCreateGroupChat({
onError?: (error: Error) => void
}) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async ({name, members}: {name: string; members: string[]}) => {
const {data} = await agent.chat.bsky.group.createGroup(
{name, members},
{headers: DM_SERVICE_HEADERS},
)
return data
return await client.call(chat.bsky.group.createGroup, {
name,
// callers pass already-resolved actor dids
members: members as DidString[],
})
},
onSuccess: data => {
precacheConvoQuery(queryClient, data.convo)
@@ -5,9 +5,9 @@ import {
} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {
rollbackConvoOptimistic,
updateConvoOptimistic,
@@ -24,7 +24,7 @@ export function useCreateJoinLink(
},
) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async ({
@@ -35,11 +35,11 @@ export function useCreateJoinLink(
requireApproval: boolean
}) => {
if (!convoId) throw new Error('No convoId provided')
const {data} = await agent.chat.bsky.group.createJoinLink(
{convoId, joinRule, requireApproval},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
return data
return await client.call(chat.bsky.group.createJoinLink, {
convoId,
joinRule,
requireApproval,
})
},
onMutate: ({joinRule, requireApproval}) => {
if (!convoId) return
@@ -4,10 +4,10 @@ import {
} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {invalidateJoinLinkPreviewsForCode} from '#/state/queries/join-links'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {
rollbackConvoOptimistic,
updateConvoOptimistic,
@@ -24,16 +24,12 @@ export function useDisableJoinLink(
},
) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async () => {
if (!convoId) throw new Error('No convoId provided')
const {data} = await agent.chat.bsky.group.disableJoinLink(
{convoId},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
return data
return await client.call(chat.bsky.group.disableJoinLink, {convoId})
},
onMutate: () => {
if (!convoId) return
@@ -1,9 +1,9 @@
import {ChatBskyConvoDefs, type ChatBskyGroupEditGroup} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {
rollbackConvoOptimistic,
updateConvoOptimistic,
@@ -20,16 +20,15 @@ export function useEditGroupChatName(
},
) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async ({name: groupName}: {name: string}) => {
if (!convoId) throw new Error('No convoId provided')
const {data} = await agent.chat.bsky.group.editGroup(
{convoId, name: groupName},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
return data
return await client.call(chat.bsky.group.editGroup, {
convoId,
name: groupName,
})
},
onMutate: ({name: groupName}) => {
if (!convoId) return
+8 -8
View File
@@ -5,9 +5,9 @@ import {
} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {
rollbackConvoOptimistic,
updateConvoOptimistic,
@@ -24,7 +24,7 @@ export function useEditJoinLink(
},
) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async ({
@@ -35,11 +35,11 @@ export function useEditJoinLink(
requireApproval: boolean
}) => {
if (!convoId) throw new Error('No convoId provided')
const {data} = await agent.chat.bsky.group.editJoinLink(
{convoId, joinRule, requireApproval},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
return data
return await client.call(chat.bsky.group.editJoinLink, {
convoId,
joinRule,
requireApproval,
})
},
onMutate: ({joinRule, requireApproval}) => {
if (!convoId) return
@@ -1,10 +1,10 @@
import {ChatBskyConvoDefs, type ChatBskyGroupEnableJoinLink} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {invalidateJoinLinkPreviewsForCode} from '#/state/queries/join-links'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {
rollbackConvoOptimistic,
updateConvoOptimistic,
@@ -21,16 +21,12 @@ export function useEnableJoinLink(
},
) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async () => {
if (!convoId) throw new Error('No convoId provided')
const {data} = await agent.chat.bsky.group.enableJoinLink(
{convoId},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
return data
return await client.call(chat.bsky.group.enableJoinLink, {convoId})
},
onMutate: () => {
if (!convoId) return
+20 -12
View File
@@ -4,15 +4,16 @@ import {
type ChatBskyGroupListJoinRequests,
type ChatBskyGroupRejectJoinRequest,
} from '@atproto/api'
import {type DidString} from '@atproto/syntax'
import {
type InfiniteData,
useMutation,
useQueryClient,
} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {listConvoMembersQueryKey} from './list-convo-members'
import {createListJoinRequestsQueryKey} from './list-join-requests'
@@ -34,21 +35,28 @@ export function useJoinRequestMutation<A extends JoinRequestAction>(
},
) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async ({member}: {member: string}) => {
if (!convoId) throw new Error('No convoId provided')
const {data} =
// callers pass an already-resolved actor did
const memberDid = member as DidString
const data =
action === 'approve'
? await agent.chat.bsky.group.approveJoinRequest(
{convoId, member},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
: await agent.chat.bsky.group.rejectJoinRequest(
{convoId, member},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
? await client.call(chat.bsky.group.approveJoinRequest, {
convoId,
member: memberDid,
})
: await client.call(chat.bsky.group.rejectJoinRequest, {
convoId,
member: memberDid,
})
/*
* The two branches have different output schemas, so the union cannot be
* narrowed from the `action` value alone - the cast carries the generic's
* mapping through, exactly as the pre-migration code did.
*/
return data as JoinRequestOutput<A>
},
onMutate: ({member}) => {
@@ -2,10 +2,10 @@ import {useEffect} from 'react'
import {ChatBskyConvoDefs} from '@atproto/api'
import {useInfiniteQuery, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {useMessagesEventBus} from '#/state/messages/events'
import {createQueryKey} from '#/state/queries/util'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {STALE} from '..'
export const JOIN_REQUESTS_THRESHOLD = 20
@@ -22,7 +22,7 @@ export function useListJoinRequestsQuery({
convoId: string | undefined
enabled?: boolean
}) {
const agent = useAgent()
const client = useChatClient()
const queryClient = useQueryClient()
const messagesBus = useMessagesEventBus()
const isEnabled = enabled !== false && !!convoId
@@ -54,11 +54,12 @@ export function useListJoinRequestsQuery({
enabled: isEnabled,
queryKey: createListJoinRequestsQueryKey({convoId: convoId ?? ''}),
queryFn: async ({pageParam}) => {
const {data} = await agent.chat.bsky.group.listJoinRequests(
{convoId: convoId!, cursor: pageParam, limit: JOIN_REQUESTS_THRESHOLD},
{headers: DM_SERVICE_HEADERS},
)
return data
return await client.call(chat.bsky.group.listJoinRequests, {
// guarded by `isEnabled`
convoId: convoId!,
cursor: pageParam,
limit: JOIN_REQUESTS_THRESHOLD,
})
},
initialPageParam: undefined as string | undefined,
getNextPageParam: page => page.cursor,
@@ -1,8 +1,9 @@
import {type DidString} from '@atproto/syntax'
import {useInfiniteQuery} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {createQueryKey} from '#/state/queries/util'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
const listMutualGroupsQueryKeyRoot = 'list-mutual-groups'
@@ -18,7 +19,7 @@ export function useListMutualGroupsQuery({
enabled?: boolean
limit?: number
}) {
const agent = useAgent()
const client = useChatClient()
const isEnabled = enabled !== false && !!subject
return useInfiniteQuery({
@@ -27,11 +28,12 @@ export function useListMutualGroupsQuery({
enabled: isEnabled,
queryKey: createListMutualGroupsQueryKey({subject: subject ?? ''}),
queryFn: async ({pageParam}) => {
const {data} = await agent.chat.bsky.group.listMutualGroups(
{subject: subject!, cursor: pageParam, limit},
{headers: DM_SERVICE_HEADERS},
)
return data
return await client.call(chat.bsky.group.listMutualGroups, {
// guarded by `enabled`, and callers pass a resolved actor did
subject: subject as DidString,
cursor: pageParam,
limit,
})
},
initialPageParam: undefined as string | undefined,
getNextPageParam: page => page.cursor,
@@ -1,9 +1,9 @@
import {ChatBskyConvoDefs} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {RQKEY as CONVO_KEY} from './conversation'
import {
type ConvoListQueryData,
@@ -12,15 +12,12 @@ import {
export function useMarkJoinRequestsRead(convoId: string | undefined) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async () => {
if (!convoId) throw new Error('No convoId provided')
await agent.chat.bsky.group.updateJoinRequestsRead(
{convoId},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
await client.call(chat.bsky.group.updateJoinRequestsRead, {convoId})
},
onMutate: () => {
if (!convoId) return
@@ -4,15 +4,16 @@ import {
type ChatBskyConvoListConvos,
type ChatBskyGroupRemoveMembers,
} from '@atproto/api'
import {type DidString} from '@atproto/syntax'
import {
type InfiniteData,
useMutation,
useQueryClient,
} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent} from '#/state/session'
import {useChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import {RQKEY as CONVO_KEY} from './conversation'
import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations'
import {listConvoMembersQueryKey} from './list-convo-members'
@@ -28,16 +29,16 @@ export function useRemoveFromGroupChat(
},
) {
const queryClient = useQueryClient()
const agent = useAgent()
const client = useChatClient()
return useMutation({
mutationFn: async ({members}: {members: string[]}) => {
if (!convoId) throw new Error('No convoId provided')
const {data} = await agent.chat.bsky.group.removeMembers(
{convoId, members},
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
)
return data
return await client.call(chat.bsky.group.removeMembers, {
convoId,
// callers pass already-resolved actor dids
members: members as DidString[],
})
},
onMutate: ({members}) => {
if (!convoId) return
@@ -1,9 +1,9 @@
import {type ChatBskyGroupRequestJoin} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent, useSession} from '#/state/session'
import {useChatClient, useSession} from '#/state/session'
import {chat} from '#/lexicons'
import {RQKEY_ROOT as REQUESTS_RQKEY_ROOT} from './list-conversation-requests'
export function useRequestJoinGroupChat({
@@ -13,7 +13,7 @@ export function useRequestJoinGroupChat({
onSuccess?: (data: ChatBskyGroupRequestJoin.OutputSchema) => void
onError?: (error: Error) => void
} = {}) {
const agent = useAgent()
const client = useChatClient()
const queryClient = useQueryClient()
const {hasSession} = useSession()
@@ -22,11 +22,7 @@ export function useRequestJoinGroupChat({
if (!hasSession) throw new Error('Must be logged in to join')
if (!code) throw new Error('No invite code')
const res = await agent.chat.bsky.group.requestJoin(
{code},
{headers: DM_SERVICE_HEADERS},
)
return res.data
return await client.call(chat.bsky.group.requestJoin, {code})
},
onSuccess: data => {
void queryClient.invalidateQueries({queryKey: [REQUESTS_RQKEY_ROOT]})
@@ -1,9 +1,9 @@
import {type ChatBskyGroupWithdrawJoinRequest} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent, useSession} from '#/state/session'
import {useChatClient, useSession} from '#/state/session'
import {chat} from '#/lexicons'
import {
type ConvoRequestListQueryData,
optimisticDeleteJoinRequest,
@@ -17,7 +17,7 @@ export function useWithdrawJoinGroupChatRequest({
onSuccess?: (data: ChatBskyGroupWithdrawJoinRequest.OutputSchema) => void
onError?: (error: Error) => void
} = {}) {
const agent = useAgent()
const client = useChatClient()
const queryClient = useQueryClient()
const {hasSession} = useSession()
@@ -27,11 +27,7 @@ export function useWithdrawJoinGroupChatRequest({
throw new Error('Must be logged in to withdraw a join request')
if (!convoId) throw new Error('No convoId provided')
const res = await agent.chat.bsky.group.withdrawJoinRequest(
{convoId},
{headers: DM_SERVICE_HEADERS},
)
return res.data
return await client.call(chat.bsky.group.withdrawJoinRequest, {convoId})
},
onSuccess: (data, {convoId}) => {
queryClient.setQueriesData<ConvoRequestListQueryData>(