core, ui: chat item to show message error (#6785)

* core: chat item to show message error

* ui: chat item for removed messages

* remove local maven repo

* command to test dropped messages

* update nix config

* show parse errors

* error texts, simplexmq

* alert messages

* simplexmq, alert

* better parsing

* better parsing

* simplify

* correct message

* remove test api

* do not check size twice, bot types

* send error in relays

* do not create error item in relays

* diff

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
Evgeny
2026-04-11 19:40:33 +01:00
committed by GitHub
co-authored by Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
parent 1428ad75f9
commit 6f21826579
17 changed files with 196 additions and 7 deletions
+16 -2
View File
@@ -493,7 +493,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
Left e -> do
atomically $ modifyTVar' tags ("error" :)
logInfo $ "contact msg=error " <> eInfo <> " " <> tshow e
eToView (ChatError . CEException $ "error parsing chat message: " <> e)
createInternalChatItem user (CDDirectRcv ct') (CIRcvMsgError $ RMEParseError $ T.pack e) Nothing
`catchAllErrors` \_ -> pure ()
withRcpt <- checkSendRcpt ct' $ rights aChatMsgs -- not crucial to use ct'' from processEvent
pure (withRcpt, False)
where
@@ -687,6 +688,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- error cannot be AUTH error here
updateDirectItemsStatusMsgs ct conn (L.toList msgIds) (CISSndError $ agentSndError err)
eToView $ ChatErrorAgent err (AgentConnId agentConnId) (Just connEntity)
ERR (AGENT (A_DUPLICATE (Just DroppedMsg {brokerTs, attempts}))) ->
createInternalChatItem user (CDDirectRcv ct) (CIRcvMsgError $ RMEDropped attempts) (Just brokerTs)
ERR err -> do
eToView $ ChatErrorAgent err (AgentConnId agentConnId) (Just connEntity)
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
@@ -962,7 +965,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
Left e -> do
atomically $ modifyTVar' tags ("error" :)
logInfo $ "group msg=error " <> eInfo <> " " <> tshow e
eToView (ChatError . CEException $ "error parsing chat message: " <> e)
if isRelay membership
then
eToView (ChatError . CEException $ "error parsing chat message: " <> e)
else
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m') (CIRcvMsgError $ RMEParseError $ T.pack e) Nothing
`catchAllErrors` \_ -> pure ()
pure newDeliveryTasks
processEvent :: forall e. MsgEncodingI e => GroupInfo -> GroupMember -> VerifiedMsg e -> CM (Maybe NewMessageDeliveryTask)
processEvent gInfo' m' verifiedMsg = do
@@ -1178,6 +1186,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
withStore' $ \db -> forM_ msgIds $ \msgId ->
updateGroupItemsErrorStatus db msgId (groupMemberId' m) newStatus `catchAll_` pure ()
eToView $ ChatErrorAgent err (AgentConnId agentConnId) (Just connEntity)
ERR err@(AGENT (A_DUPLICATE (Just DroppedMsg {brokerTs, attempts})))
| isRelay membership ->
eToView $ ChatErrorAgent err (AgentConnId agentConnId) (Just connEntity)
| otherwise -> do
(gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m') (CIRcvMsgError $ RMEDropped attempts) (Just brokerTs)
ERR err -> do
eToView $ ChatErrorAgent err (AgentConnId agentConnId) (Just connEntity)
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
+22
View File
@@ -145,6 +145,7 @@ data CIContent (d :: MsgDirection) where
CIRcvCall :: CICallStatus -> Int -> CIContent 'MDRcv
CIRcvIntegrityError :: MsgErrorType -> CIContent 'MDRcv
CIRcvDecryptionError :: MsgDecryptError -> Word32 -> CIContent 'MDRcv
CIRcvMsgError :: RcvMsgError -> CIContent 'MDRcv
CIRcvGroupInvitation :: CIGroupInvitation -> GroupMemberRole -> CIContent 'MDRcv
CISndGroupInvitation :: CIGroupInvitation -> GroupMemberRole -> CIContent 'MDSnd
CIRcvDirectEvent :: RcvDirectEvent -> CIContent 'MDRcv
@@ -196,6 +197,11 @@ data MsgDecryptError
| MDERatchetSync
deriving (Eq, Show)
data RcvMsgError
= RMEDropped {attempts :: Int}
| RMEParseError {parseError :: Text}
deriving (Eq, Show)
ciRequiresAttention :: forall d. MsgDirectionI d => CIContent d -> Bool
ciRequiresAttention content = case msgDirection @d of
SMDSnd -> True
@@ -205,6 +211,7 @@ ciRequiresAttention content = case msgDirection @d of
CIRcvCall {} -> True
CIRcvIntegrityError _ -> True
CIRcvDecryptionError {} -> True
CIRcvMsgError _ -> False
CIRcvGroupInvitation {} -> True
CIRcvDirectEvent rde -> case rde of
RDEContactDeleted -> False
@@ -275,6 +282,7 @@ ciContentToText = \case
CIRcvCall status duration -> "incoming call: " <> ciCallInfoText status duration
CIRcvIntegrityError err -> msgIntegrityError err
CIRcvDecryptionError err n -> msgDecryptErrorText err n
CIRcvMsgError err -> rcvMsgErrorText err
CIRcvGroupInvitation groupInvitation memberRole -> "received " <> ciGroupInvitationToText groupInvitation memberRole
CISndGroupInvitation groupInvitation memberRole -> "sent " <> ciGroupInvitationToText groupInvitation memberRole
CIRcvDirectEvent event -> rcvDirectEventToText event
@@ -421,6 +429,11 @@ msgIntegrityError = \case
MsgBadHash -> "incorrect message hash"
MsgDuplicate -> "duplicate message ID"
rcvMsgErrorText :: RcvMsgError -> Text
rcvMsgErrorText = \case
RMEDropped {attempts} -> "message removed after " <> tshow attempts <> " attempts"
RMEParseError {parseError} -> "message error: " <> parseError
msgDecryptErrorText :: MsgDecryptError -> Word32 -> Text
msgDecryptErrorText err n =
"decryption error, possibly due to the device change"
@@ -457,6 +470,7 @@ data JSONCIContent
| JCIRcvCall {status :: CICallStatus, duration :: Int}
| JCIRcvIntegrityError {msgError :: MsgErrorType}
| JCIRcvDecryptionError {msgDecryptError :: MsgDecryptError, msgCount :: Word32}
| JCIRcvMsgError {rcvMsgError :: RcvMsgError}
| JCIRcvGroupInvitation {groupInvitation :: CIGroupInvitation, memberRole :: GroupMemberRole}
| JCISndGroupInvitation {groupInvitation :: CIGroupInvitation, memberRole :: GroupMemberRole}
| JCIRcvDirectEvent {rcvDirectEvent :: RcvDirectEvent}
@@ -492,6 +506,7 @@ jsonCIContent = \case
CIRcvCall status duration -> JCIRcvCall {status, duration}
CIRcvIntegrityError err -> JCIRcvIntegrityError err
CIRcvDecryptionError err n -> JCIRcvDecryptionError err n
CIRcvMsgError err -> JCIRcvMsgError err
CIRcvGroupInvitation groupInvitation memberRole -> JCIRcvGroupInvitation {groupInvitation, memberRole}
CISndGroupInvitation groupInvitation memberRole -> JCISndGroupInvitation {groupInvitation, memberRole}
CIRcvDirectEvent rcvDirectEvent -> JCIRcvDirectEvent {rcvDirectEvent}
@@ -527,6 +542,7 @@ aciContentJSON = \case
JCIRcvCall {status, duration} -> ACIContent SMDRcv $ CIRcvCall status duration
JCIRcvIntegrityError err -> ACIContent SMDRcv $ CIRcvIntegrityError err
JCIRcvDecryptionError err n -> ACIContent SMDRcv $ CIRcvDecryptionError err n
JCIRcvMsgError err -> ACIContent SMDRcv $ CIRcvMsgError err
JCIRcvGroupInvitation {groupInvitation, memberRole} -> ACIContent SMDRcv $ CIRcvGroupInvitation groupInvitation memberRole
JCISndGroupInvitation {groupInvitation, memberRole} -> ACIContent SMDSnd $ CISndGroupInvitation groupInvitation memberRole
JCIRcvDirectEvent {rcvDirectEvent} -> ACIContent SMDRcv $ CIRcvDirectEvent rcvDirectEvent
@@ -563,6 +579,7 @@ data DBJSONCIContent
| DBJCIRcvCall {status :: CICallStatus, duration :: Int}
| DBJCIRcvIntegrityError {msgError :: DBMsgErrorType}
| DBJCIRcvDecryptionError {msgDecryptError :: MsgDecryptError, msgCount :: Word32}
| DBJCIRcvMsgError {rcvMsgError :: RcvMsgError}
| DBJCIRcvGroupInvitation {groupInvitation :: CIGroupInvitation, memberRole :: GroupMemberRole}
| DBJCISndGroupInvitation {groupInvitation :: CIGroupInvitation, memberRole :: GroupMemberRole}
| DBJCIRcvDirectEvent {rcvDirectEvent :: DBRcvDirectEvent}
@@ -598,6 +615,7 @@ dbJsonCIContent = \case
CIRcvCall status duration -> DBJCIRcvCall {status, duration}
CIRcvIntegrityError err -> DBJCIRcvIntegrityError $ DBME err
CIRcvDecryptionError err n -> DBJCIRcvDecryptionError err n
CIRcvMsgError err -> DBJCIRcvMsgError err
CIRcvGroupInvitation groupInvitation memberRole -> DBJCIRcvGroupInvitation {groupInvitation, memberRole}
CISndGroupInvitation groupInvitation memberRole -> DBJCISndGroupInvitation {groupInvitation, memberRole}
CIRcvDirectEvent rde -> DBJCIRcvDirectEvent $ RDE rde
@@ -633,6 +651,7 @@ aciContentDBJSON = \case
DBJCIRcvCall {status, duration} -> ACIContent SMDRcv $ CIRcvCall status duration
DBJCIRcvIntegrityError (DBME err) -> ACIContent SMDRcv $ CIRcvIntegrityError err
DBJCIRcvDecryptionError err n -> ACIContent SMDRcv $ CIRcvDecryptionError err n
DBJCIRcvMsgError err -> ACIContent SMDRcv $ CIRcvMsgError err
DBJCIRcvGroupInvitation {groupInvitation, memberRole} -> ACIContent SMDRcv $ CIRcvGroupInvitation groupInvitation memberRole
DBJCISndGroupInvitation {groupInvitation, memberRole} -> ACIContent SMDSnd $ CISndGroupInvitation groupInvitation memberRole
DBJCIRcvDirectEvent (RDE rde) -> ACIContent SMDRcv $ CIRcvDirectEvent rde
@@ -693,6 +712,8 @@ $(JQ.deriveJSON defaultJSON ''E2EInfo)
$(JQ.deriveJSON (enumJSON $ dropPrefix "MDE") ''MsgDecryptError)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RME") ''RcvMsgError)
$(JQ.deriveJSON (enumJSON $ dropPrefix "CIGIS") ''CIGroupInvitationStatus)
$(JQ.deriveJSON defaultJSON ''CIGroupInvitation)
@@ -751,6 +772,7 @@ toCIContentTag ciContent = case ciContent of
CIRcvCall {} -> "rcvCall"
CIRcvIntegrityError _ -> "rcvIntegrityError"
CIRcvDecryptionError {} -> "rcvDecryptionError"
CIRcvMsgError _ -> "rcvMsgError"
CIRcvGroupInvitation {} -> "rcvGroupInvitation"
CISndGroupInvitation {} -> "sndGroupInvitation"
CIRcvDirectEvent _ -> "rcvDirectEvent"
+6 -2
View File
@@ -56,7 +56,7 @@ import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared
import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion)
import Simplex.Messaging.Agent.Store.DB (blobFieldDecoder, fromTextField_)
import Simplex.Messaging.Compression (Compressed, compress1, decompress1)
import Simplex.Messaging.Compression (Compressed, compress1, decompress1, decompressedSize)
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String
@@ -799,7 +799,11 @@ parseChatMessages msg = case B.head msg of
decodeCompressed :: ByteString -> [Either String AParsedMsg]
decodeCompressed s = case smpDecode s of
Left e -> [Left e]
Right (compressed :: L.NonEmpty Compressed) -> concatMap (either (\e -> [Left e]) parseUncompressed' . decompress1 maxDecompressedMsgLength) compressed
Right (compressed :: L.NonEmpty Compressed) -> case traverse decompressedSize compressed of
Nothing -> [Left "compressed size not specified"]
Just sizes
| sum sizes > maxDecompressedMsgLength -> [Left "decompressed size exceeds limit"]
| otherwise -> concatMap (either (\e -> [Left e]) parseUncompressed' . decompress1) compressed
parseUncompressed' "" = [Left "empty string"]
parseUncompressed' s = parseUncompressed (B.head s) s
-- Binary batch format: '=' <count:1> (<len:2> <body>)*
+7 -1
View File
@@ -671,6 +671,7 @@ viewChatItem chat ci@ChatItem {chatDir, meta = meta@CIMeta {itemForwarded, forwa
CIDirectRcv -> case content of
CIRcvMsgContent mc -> withRcvFile from $ rcvMsg from context mc
CIRcvIntegrityError err -> viewRcvIntegrityError from err ts tz meta
CIRcvMsgError err -> viewRcvMsgError from err ts tz meta
CIRcvGroupEvent {} -> showRcvItemProhibited from
_ -> showRcvItem from
where
@@ -694,6 +695,7 @@ viewChatItem chat ci@ChatItem {chatDir, meta = meta@CIMeta {itemForwarded, forwa
rcvGroupItem m_ = case content of
CIRcvMsgContent mc -> withRcvFile from $ rcvMsg from context mc
CIRcvIntegrityError err -> viewRcvIntegrityError from err ts tz meta
CIRcvMsgError err -> viewRcvMsgError from err ts tz meta
CIRcvGroupInvitation {} | isJust m_ -> showRcvItemProhibited from
CIRcvModerated {} -> receivedWithTime_ ts tz (ttyFromGroup g scopeInfo m_) context meta [plainContent content] False
CIRcvBlocked {} -> receivedWithTime_ ts tz (ttyFromGroup g scopeInfo m_) context meta [plainContent content] False
@@ -715,6 +717,7 @@ viewChatItem chat ci@ChatItem {chatDir, meta = meta@CIMeta {itemForwarded, forwa
CILocalRcv -> case content of
CIRcvMsgContent mc -> withLocalFile from $ rcvMsg from context mc
CIRcvIntegrityError err -> viewRcvIntegrityError from err ts tz meta
CIRcvMsgError err -> viewRcvMsgError from err ts tz meta
CIRcvGroupEvent {} -> showRcvItemProhibited from
_ -> showRcvItem from
where
@@ -991,6 +994,9 @@ viewRcvIntegrityError from msgErr ts tz meta = receivedWithTime_ ts tz from [] m
viewMsgIntegrityError :: MsgErrorType -> [StyledString]
viewMsgIntegrityError err = [ttyError $ msgIntegrityError err]
viewRcvMsgError :: StyledString -> RcvMsgError -> CurrentTime -> TimeZone -> CIMeta c 'MDRcv -> [StyledString]
viewRcvMsgError from rcvErr ts tz meta = receivedWithTime_ ts tz from [] meta [ttyError $ rcvMsgErrorText rcvErr] False
viewInvalidConnReq :: [StyledString]
viewInvalidConnReq =
[ "",
@@ -2656,7 +2662,7 @@ viewChatError isCmd logLevel testView = \case
BRContent -> "content violates conditions of use"
BROKER _ (NETWORK _) | not isCmd -> []
BROKER _ TIMEOUT | not isCmd -> []
AGENT A_DUPLICATE -> [withConnEntity <> "error: AGENT A_DUPLICATE" | logLevel == CLLDebug || isCmd]
AGENT A_DUPLICATE {} -> [withConnEntity <> "error: AGENT A_DUPLICATE" | logLevel == CLLDebug || isCmd]
AGENT (A_PROHIBITED e) -> [withConnEntity <> "error: AGENT A_PROHIBITED, " <> plain e | logLevel <= CLLWarning || isCmd]
CONN NOT_FOUND _ -> [withConnEntity <> "error: CONN NOT_FOUND" | logLevel <= CLLWarning || isCmd]
CRITICAL restart e -> [plain $ "critical error: " <> e] <> ["please restart the app" | restart]