From 85f2ff39a7e197e5b0326bc2938cdf46c82ac13c Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 3 Aug 2026 15:45:55 +0300 Subject: [PATCH] classify migrated chat convo errors with matchXrpcError The lex client throws XrpcResponseError, which is not an XRPCError subclass, so the old per-method error classes generated by @atproto/api no longer match. Those instanceof checks compile fine but silently stop firing, degrading every typed chat failure to the generic fallback message. Narrow against the method schema instead. Co-Authored-By: Claude Fable 5 --- src/components/dms/LeaveConvoPrompt.tsx | 18 +++++--- src/components/dms/dialogs/NewChatDialog.tsx | 46 +++++++++---------- .../dms/dialogs/ShareViaChatDialog.tsx | 46 +++++++++---------- src/components/moderation/BlockDialog.tsx | 23 +++++----- .../Messages/ConversationSettings/index.tsx | 6 ++- .../Messages/components/ChatLocked.tsx | 8 +++- 6 files changed, 75 insertions(+), 72 deletions(-) diff --git a/src/components/dms/LeaveConvoPrompt.tsx b/src/components/dms/LeaveConvoPrompt.tsx index 5e9943e53f..a69dfcf845 100644 --- a/src/components/dms/LeaveConvoPrompt.tsx +++ b/src/components/dms/LeaveConvoPrompt.tsx @@ -1,14 +1,15 @@ -import {ChatBskyConvoLeaveConvo} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' import {type NavigationProp} from '#/lib/routes/types' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import {type DialogOuterProps} from '#/components/Dialog' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {IS_NATIVE} from '#/env' +import {chat} from '#/lexicons' export function LeaveConvoPrompt({ control, @@ -36,12 +37,15 @@ export function LeaveConvoPrompt({ let errorMessage = l`Could not leave chat` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if (error instanceof ChatBskyConvoLeaveConvo.InvalidConvoError) { - errorMessage = l`Conversation not found.` - } else if ( - error instanceof ChatBskyConvoLeaveConvo.OwnerCannotLeaveError - ) { - errorMessage = l`Owner must lock the group before leaving.` + } else { + switch (matchXrpcError(error, chat.bsky.convo.leaveConvo)) { + case 'InvalidConvo': + errorMessage = l`Conversation not found.` + break + case 'OwnerCannotLeave': + errorMessage = l`Owner must lock the group before leaving.` + break + } } Toast.show(errorMessage, {type: 'error'}) }, diff --git a/src/components/dms/dialogs/NewChatDialog.tsx b/src/components/dms/dialogs/NewChatDialog.tsx index fd9eaa8880..4cca14bd0a 100644 --- a/src/components/dms/dialogs/NewChatDialog.tsx +++ b/src/components/dms/dialogs/NewChatDialog.tsx @@ -1,12 +1,10 @@ import {useCallback} from 'react' -import { - ChatBskyConvoGetConvoForMembers, - ChatBskyGroupCreateGroup, -} from '@atproto/api' +import {ChatBskyGroupCreateGroup} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat' import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' @@ -19,6 +17,7 @@ import {InitiateChatFlow} from '#/components/dms/InitiateChatFlow' import {MessagePlus_Stroke2_Corner0_Rounded as NewChatIcon} from '#/components/icons/Message' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {chat} from '#/lexicons' export function NewChat({ control, @@ -54,27 +53,24 @@ export function NewChat({ let errorMessage = l`An issue occurred starting the chat, please try again.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.AccountSuspendedError - ) { - errorMessage = l`Suspended accounts cannot participate in chat.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.BlockedActorError - ) { - errorMessage = l`This user has blocked you and cannot be messaged.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.MessagesDisabledError - ) { - errorMessage = l`This user has disabled chat and cannot be messaged.` - } else if ( - error instanceof - ChatBskyConvoGetConvoForMembers.NotFollowedBySenderError - ) { - errorMessage = l`Chat recipient is not followed by the sender.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.RecipientNotFoundError - ) { - errorMessage = l`Unable to find the selected recipient.` + } else { + switch (matchXrpcError(error, chat.bsky.convo.getConvoForMembers)) { + case 'AccountSuspended': + errorMessage = l`Suspended accounts cannot participate in chat.` + break + case 'BlockedActor': + errorMessage = l`This user has blocked you and cannot be messaged.` + break + case 'MessagesDisabled': + errorMessage = l`This user has disabled chat and cannot be messaged.` + break + case 'NotFollowedBySender': + errorMessage = l`Chat recipient is not followed by the sender.` + break + case 'RecipientNotFound': + errorMessage = l`Unable to find the selected recipient.` + break + } } Toast.show(errorMessage, { type: 'error', diff --git a/src/components/dms/dialogs/ShareViaChatDialog.tsx b/src/components/dms/dialogs/ShareViaChatDialog.tsx index 0463cc694d..6dfb6145f7 100644 --- a/src/components/dms/dialogs/ShareViaChatDialog.tsx +++ b/src/components/dms/dialogs/ShareViaChatDialog.tsx @@ -1,11 +1,9 @@ import {useCallback, useState} from 'react' -import { - ChatBskyConvoGetConvoForMembers, - ChatBskyGroupCreateGroup, -} from '@atproto/api' +import {ChatBskyGroupCreateGroup} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat' import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' @@ -14,6 +12,7 @@ import {SearchablePeopleList} from '#/components/dialogs/SearchablePeopleList' import {InitiateChatFlow} from '#/components/dms/InitiateChatFlow' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {chat} from '#/lexicons' export function SendViaChatDialog({ control, @@ -69,27 +68,24 @@ function SendViaChatDialogInner({ let errorMessage = l`An issue occurred starting the chat, please try again.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.AccountSuspendedError - ) { - errorMessage = l`Suspended accounts cannot participate in chat.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.BlockedActorError - ) { - errorMessage = l`This user has blocked you and cannot be messaged.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.MessagesDisabledError - ) { - errorMessage = l`This user has disabled chat and cannot be messaged.` - } else if ( - error instanceof - ChatBskyConvoGetConvoForMembers.NotFollowedBySenderError - ) { - errorMessage = l`Chat recipient is not followed by the sender.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.RecipientNotFoundError - ) { - errorMessage = l`Unable to find the selected recipient.` + } else { + switch (matchXrpcError(error, chat.bsky.convo.getConvoForMembers)) { + case 'AccountSuspended': + errorMessage = l`Suspended accounts cannot participate in chat.` + break + case 'BlockedActor': + errorMessage = l`This user has blocked you and cannot be messaged.` + break + case 'MessagesDisabled': + errorMessage = l`This user has disabled chat and cannot be messaged.` + break + case 'NotFollowedBySender': + errorMessage = l`Chat recipient is not followed by the sender.` + break + case 'RecipientNotFound': + errorMessage = l`Unable to find the selected recipient.` + break + } } Toast.show(errorMessage, { type: 'error', diff --git a/src/components/moderation/BlockDialog.tsx b/src/components/moderation/BlockDialog.tsx index a575cd7433..5ec9e4d9e4 100644 --- a/src/components/moderation/BlockDialog.tsx +++ b/src/components/moderation/BlockDialog.tsx @@ -1,14 +1,11 @@ import {useState} from 'react' import {View} from 'react-native' -import { - type ChatBskyConvoDefs, - ChatBskyConvoLeaveConvo, - ChatBskyGroupRemoveMembers, -} from '@atproto/api' +import {type ChatBskyConvoDefs, ChatBskyGroupRemoveMembers} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {type Shadow} from '#/state/cache/types' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' @@ -27,6 +24,7 @@ import {parseConvoView} from '#/components/dms/util' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {chat} from '#/lexicons' import {type AnyProfileView} from '#/types/bsky/profile' type Item = ChatBskyConvoDefs.ConvoView @@ -282,12 +280,15 @@ function MutualGroupChat({ let errorMessage = l`Could not leave chat.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if (error instanceof ChatBskyConvoLeaveConvo.InvalidConvoError) { - errorMessage = l`Chat not found.` - } else if ( - error instanceof ChatBskyConvoLeaveConvo.OwnerCannotLeaveError - ) { - errorMessage = l`Chat owners cannot leave a group chat.` + } else { + switch (matchXrpcError(error, chat.bsky.convo.leaveConvo)) { + case 'InvalidConvo': + errorMessage = l`Chat not found.` + break + case 'OwnerCannotLeave': + errorMessage = l`Chat owners cannot leave a group chat.` + break + } } Toast.show(errorMessage, {type: 'error'}) }, diff --git a/src/screens/Messages/ConversationSettings/index.tsx b/src/screens/Messages/ConversationSettings/index.tsx index 5576f28674..5f76a3f565 100644 --- a/src/screens/Messages/ConversationSettings/index.tsx +++ b/src/screens/Messages/ConversationSettings/index.tsx @@ -3,7 +3,6 @@ import {Pressable, View} from 'react-native' import { ChatBskyActorDefs, ChatBskyConvoDefs, - ChatBskyConvoUnlockConvo, type ModerationOpts, } from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' @@ -19,6 +18,7 @@ import { type NativeStackScreenProps, type NavigationProp, } from '#/lib/routes/types' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useConvoQuery} from '#/state/queries/messages/conversation' @@ -59,6 +59,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {chat} from '#/lexicons' import * as bsky from '#/types/bsky' import {InviteLinkDialog} from '../components/InviteLinkDialog' import {AddMembersLink} from './AddMembersLink' @@ -432,7 +433,8 @@ function SettingsHeader({ logger.error('Failed to lock group chat', {message: e}) Toast.show(l`Failed to lock group chat`, {type: 'error'}) } else if ( - e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError + matchXrpcError(e, chat.bsky.convo.unlockConvo) === + 'ConvoLockedByModeration' ) { Toast.show(l`This chat is locked by a moderation action`, { type: 'error', diff --git a/src/screens/Messages/components/ChatLocked.tsx b/src/screens/Messages/components/ChatLocked.tsx index 8718667e9c..c9dcd93c3d 100644 --- a/src/screens/Messages/components/ChatLocked.tsx +++ b/src/screens/Messages/components/ChatLocked.tsx @@ -1,10 +1,10 @@ import {Pressable} from 'react-native' -import {ChatBskyConvoUnlockConvo} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {HITSLOP_10} from '#/lib/constants' import {type NavigationProp} from '#/lib/routes/types' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import {useLockConvo} from '#/state/queries/messages/lock-conversation' @@ -15,6 +15,7 @@ import {Lock_Stroke2_Corner0_Rounded as LockIcon} from '#/components/icons/Lock' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {chat} from '#/lexicons' import {LeaveChatPrompt} from '../ConversationSettings/prompts' import {ChatFooter} from './ChatFooter' @@ -41,7 +42,10 @@ export function ChatLocked({ Toast.show(l({message: 'Group chat unlocked', context: 'toast'})) }, onError: e => { - if (e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError) { + if ( + matchXrpcError(e, chat.bsky.convo.unlockConvo) === + 'ConvoLockedByModeration' + ) { Toast.show(l`This chat is locked by a moderation action`, { type: 'error', })