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
@@ -77,6 +77,24 @@ struct CIMsgError: View {
}
}
struct RcvMsgErrorItemView: View {
@ObservedObject var chat: Chat
var rcvMsgError: RcvMsgError
var chatItem: ChatItem
var body: some View {
CIMsgError(chat: chat, chatItem: chatItem) {
AlertManager.shared.showAlertMsg(
title: "Message error",
message: switch rcvMsgError {
case let .dropped(attempts): "The app removed this message after \(attempts) attempts to receive it."
case let .parseError(parseError): "\(parseError)"
}
)
}
}
}
struct IntegrityErrorItemView_Previews: PreviewProvider {
static var previews: some View {
IntegrityErrorItemView(chat: Chat.sampleData, msgError: .msgBadHash, chatItem: ChatItem.getIntegrityErrorSample())
@@ -145,6 +145,7 @@ struct ChatItemContentView<Content: View>: View {
} else {
ZStack {}
}
case let .rcvMsgError(rcvMsgError): RcvMsgErrorItemView(chat: chat, rcvMsgError: rcvMsgError, chatItem: chatItem)
case let .rcvDecryptionError(msgDecryptError, msgCount): CIRcvDecryptionError(chat: chat, msgDecryptError: msgDecryptError, msgCount: msgCount, chatItem: chatItem)
case let .rcvGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
case let .sndGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
@@ -37,6 +37,7 @@ struct ChatItemClipped: ViewModifier {
.rcvMsgContent,
.rcvDecryptionError,
.rcvIntegrityError,
.rcvMsgError,
.invalidJSON:
let tail = if let mc = ci.content.msgContent, mc.isImageOrVideo && mc.text.isEmpty {
false
+18
View File
@@ -3271,6 +3271,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
case .rcvCall: return false // notification is shown on .callInvitation instead
case .rcvIntegrityError: return false
case .rcvDecryptionError: return false
case .rcvMsgError: return false
case .rcvGroupInvitation: return true
case .sndGroupInvitation: return false
case .rcvDirectEvent(rcvDirectEvent: let rcvDirectEvent):
@@ -4028,6 +4029,7 @@ public enum CIContent: Decodable, ItemContent, Hashable {
case rcvCall(status: CICallStatus, duration: Int)
case rcvIntegrityError(msgError: MsgErrorType)
case rcvDecryptionError(msgDecryptError: MsgDecryptError, msgCount: UInt32)
case rcvMsgError(rcvMsgError: RcvMsgError)
case rcvGroupInvitation(groupInvitation: CIGroupInvitation, memberRole: GroupMemberRole)
case sndGroupInvitation(groupInvitation: CIGroupInvitation, memberRole: GroupMemberRole)
case rcvDirectEvent(rcvDirectEvent: RcvDirectEvent)
@@ -4065,6 +4067,7 @@ public enum CIContent: Decodable, ItemContent, Hashable {
case let .rcvCall(status, duration): return status.text(duration)
case let .rcvIntegrityError(msgError): return msgError.text
case let .rcvDecryptionError(msgDecryptError, _): return msgDecryptError.text
case let .rcvMsgError(rcvMsgError): return rcvMsgError.text
case let .rcvGroupInvitation(groupInvitation, _): return groupInvitation.text
case let .sndGroupInvitation(groupInvitation, _): return groupInvitation.text
case let .rcvDirectEvent(rcvDirectEvent): return rcvDirectEvent.text
@@ -4156,6 +4159,7 @@ public enum CIContent: Decodable, ItemContent, Hashable {
case .rcvCall: return true
case .rcvIntegrityError: return true
case .rcvDecryptionError: return true
case .rcvMsgError: return true
case .rcvGroupInvitation: return true
case .rcvModerated: return true
case .rcvBlocked: return true
@@ -5082,6 +5086,20 @@ public enum MsgErrorType: Decodable, Hashable {
}
}
public enum RcvMsgError: Decodable, Hashable {
case dropped(attempts: Int)
case parseError(parseError: String)
var text: String {
switch self {
case let .dropped(attempts):
String.localizedStringWithFormat(NSLocalizedString("removed (%d attempts)", comment: "receive error chat item"), attempts)
case let .parseError(parseError):
String.localizedStringWithFormat(NSLocalizedString("error: %@", comment: "receive error chat item"), parseError)
}
}
}
public struct CIGroupInvitation: Decodable, Hashable {
public var groupId: Int64
public var groupMemberId: Int64
@@ -3040,6 +3040,7 @@ data class ChatItem (
is CIContent.RcvCall -> false // notification is shown on CallInvitation instead
is CIContent.RcvIntegrityError -> false
is CIContent.RcvDecryptionError -> false
is CIContent.RcvMsgErrorContent -> false
is CIContent.RcvGroupInvitation -> true
is CIContent.SndGroupInvitation -> false
is CIContent.RcvDirectEventContent -> when (content.rcvDirectEvent) {
@@ -3734,6 +3735,7 @@ sealed class CIContent: ItemContent {
@Serializable @SerialName("rcvCall") class RcvCall(val status: CICallStatus, val duration: Int): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvIntegrityError") class RcvIntegrityError(val msgError: MsgErrorType): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvDecryptionError") class RcvDecryptionError(val msgDecryptError: MsgDecryptError, val msgCount: UInt): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvMsgError") class RcvMsgErrorContent(val rcvMsgError: RcvMsgError): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvGroupInvitation") class RcvGroupInvitation(val groupInvitation: CIGroupInvitation, val memberRole: GroupMemberRole): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("sndGroupInvitation") class SndGroupInvitation(val groupInvitation: CIGroupInvitation, val memberRole: GroupMemberRole): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvDirectEvent") class RcvDirectEventContent(val rcvDirectEvent: RcvDirectEvent): CIContent() { override val msgContent: MsgContent? get() = null }
@@ -3770,6 +3772,7 @@ sealed class CIContent: ItemContent {
is RcvCall -> status.text(duration)
is RcvIntegrityError -> msgError.text
is RcvDecryptionError -> msgDecryptError.text
is RcvMsgErrorContent -> rcvMsgError.text
is RcvGroupInvitation -> groupInvitation.text
is SndGroupInvitation -> groupInvitation.text
is RcvDirectEventContent -> rcvDirectEvent.text
@@ -3810,6 +3813,7 @@ sealed class CIContent: ItemContent {
is RcvCall -> true
is RcvIntegrityError -> true
is RcvDecryptionError -> true
is RcvMsgErrorContent -> true
is RcvGroupInvitation -> true
is RcvModerated -> true
is RcvBlocked -> true
@@ -4721,6 +4725,17 @@ sealed class MsgErrorType() {
}
}
@Serializable
sealed class RcvMsgError() {
@Serializable @SerialName("dropped") class Dropped(val attempts: Int): RcvMsgError()
@Serializable @SerialName("parseError") class ParseError(val parseError: String): RcvMsgError()
val text: String get() = when (this) {
is Dropped -> String.format(generalGetString(MR.strings.rcv_msg_error_dropped), attempts)
is ParseError -> String.format(generalGetString(MR.strings.rcv_msg_error_parse), parseError)
}
}
@Serializable
sealed class RcvDirectEvent() {
@Serializable @SerialName("contactDeleted") class ContactDeleted(): RcvDirectEvent()
@@ -714,6 +714,9 @@ fun ChatItemView(
} else {
Box(Modifier.size(0.dp)) {}
}
is CIContent.RcvMsgErrorContent -> {
RcvMsgErrorItemView(c.rcvMsgError, cItem, showTimestamp, cInfo.timedMessagesTTL)
}
is CIContent.RcvDecryptionError -> {
CIRcvDecryptionError(c.msgDecryptError, c.msgCount, cInfo, cItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember)
DeleteItemMenu()
@@ -1310,6 +1313,7 @@ fun shapeStyleWithTail(chatItem: ChatItem? = null, tailEnabled: Boolean, tailVis
is CIContent.SndMsgContent,
is CIContent.RcvMsgContent,
is CIContent.RcvDecryptionError,
is CIContent.RcvMsgErrorContent,
is CIContent.SndDeleted,
is CIContent.RcvDeleted,
is CIContent.RcvIntegrityError,
@@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.ChatItem
import chat.simplex.common.model.MsgErrorType
import chat.simplex.common.model.RcvMsgError
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.AlertManager
import chat.simplex.common.views.helpers.generalGetString
@@ -73,6 +74,19 @@ fun CIMsgError(ci: ChatItem, showTimestamp: Boolean, timedMessagesTTL: Int?, onC
}
}
@Composable
fun RcvMsgErrorItemView(rcvMsgError: RcvMsgError, ci: ChatItem, showTimestamp: Boolean, timedMessagesTTL: Int?) {
CIMsgError(ci, showTimestamp, timedMessagesTTL) {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.alert_title_msg_error),
text = when (rcvMsgError) {
is RcvMsgError.Dropped -> String.format(generalGetString(MR.strings.alert_text_msg_reception_error), rcvMsgError.attempts)
is RcvMsgError.ParseError -> rcvMsgError.parseError
}
)
}
}
@Preview/*(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
@@ -1377,6 +1377,10 @@
<string name="integrity_msg_bad_hash">bad message hash</string>
<string name="integrity_msg_bad_id">bad message ID</string>
<string name="integrity_msg_duplicate">duplicate message</string>
<string name="rcv_msg_error_dropped">dropped (%1$d attempts)</string>
<string name="rcv_msg_error_parse">error: %s</string>
<string name="alert_title_msg_error">Message error</string>
<string name="alert_text_msg_reception_error">The app removed this message after %1$d attempts to receive it.</string>
<string name="alert_title_skipped_messages">Skipped messages</string>
<string name="alert_text_skipped_messages_it_can_happen_when">It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised.</string>
<string name="alert_title_msg_bad_hash">Bad message hash</string>
+31
View File
@@ -70,6 +70,7 @@ This file is generated automatically.
- [CreatedConnLink](#createdconnlink)
- [CryptoFile](#cryptofile)
- [CryptoFileArgs](#cryptofileargs)
- [DroppedMsg](#droppedmsg)
- [E2EInfo](#e2einfo)
- [ErrorType](#errortype)
- [FeatureAllowed](#featureallowed)
@@ -149,6 +150,7 @@ This file is generated automatically.
- [RcvFileStatus](#rcvfilestatus)
- [RcvFileTransfer](#rcvfiletransfer)
- [RcvGroupEvent](#rcvgroupevent)
- [RcvMsgError](#rcvmsgerror)
- [RelayProfile](#relayprofile)
- [RelayStatus](#relaystatus)
- [ReportReason](#reportreason)
@@ -454,6 +456,10 @@ RcvDecryptionError:
- msgDecryptError: [MsgDecryptError](#msgdecrypterror)
- msgCount: word32
RcvMsgError:
- type: "rcvMsgError"
- rcvMsgError: [RcvMsgError](#rcvmsgerror)
RcvGroupInvitation:
- type: "rcvGroupInvitation"
- groupInvitation: [CIGroupInvitation](#cigroupinvitation)
@@ -1813,6 +1819,15 @@ connFullLink + ((' ' + connShortLink) if connShortLink is not None else '') # Py
- fileNonce: string
---
## DroppedMsg
**Record type**:
- brokerTs: UTCTime
- attempts: int
---
## E2EInfo
@@ -3192,6 +3207,21 @@ MsgBadSignature:
- type: "msgBadSignature"
---
## RcvMsgError
**Discriminated union type**:
Dropped:
- type: "dropped"
- attempts: int
ParseError:
- type: "parseError"
- parseError: string
---
## RelayProfile
@@ -3261,6 +3291,7 @@ A_CRYPTO:
A_DUPLICATE:
- type: "A_DUPLICATE"
- droppedMsg_: [DroppedMsg](#droppedmsg)?
A_QUEUE:
- type: "A_QUEUE"
+4
View File
@@ -253,6 +253,7 @@ chatTypesDocsData =
(sti @ContactUserPreferences, STRecord, "", [], "", ""),
(sti @CryptoFile, STRecord, "", [], "", ""),
(sti @CryptoFileArgs, STRecord, "", [], "", ""),
(sti @DroppedMsg, STRecord, "", [], "", ""),
(sti @E2EInfo, STRecord, "", [], "", ""),
(sti @ErrorType, STUnion, "", [], "", ""),
(sti @FeatureAllowed, STEnum, "FA", [], "", ""),
@@ -332,6 +333,7 @@ chatTypesDocsData =
(sti @RcvFileStatus, STUnion, "RFS", [], "", ""),
(sti @RcvFileTransfer, STRecord, "", [], "", ""),
(sti @RcvGroupEvent, STUnion, "RGE", [], "", ""),
(sti @RcvMsgError, STUnion, "RME", [], "", ""),
(sti @RelayProfile, STRecord, "", [], "", ""),
(sti @RelayStatus, STEnum, "RS", [], "", ""),
(sti @ReportReason, STEnum' (dropPfxSfx "RR" ""), "", ["RRUnknown"], "", ""),
@@ -452,6 +454,7 @@ deriving instance Generic ContactStatus
deriving instance Generic ContactUserPreferences
deriving instance Generic CryptoFile
deriving instance Generic CryptoFileArgs
deriving instance Generic DroppedMsg
deriving instance Generic E2EInfo
deriving instance Generic ErrorType
deriving instance Generic FeatureAllowed
@@ -537,6 +540,7 @@ deriving instance Generic RcvFileDescr
deriving instance Generic RcvFileStatus
deriving instance Generic RcvFileTransfer
deriving instance Generic RcvGroupEvent
deriving instance Generic RcvMsgError
deriving instance Generic RelayProfile
deriving instance Generic RelayStatus
deriving instance Generic ReportReason
+1 -1
View File
@@ -21,7 +21,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: 97802a30fce1dfeea90f0b465e21fc8eca937abb
tag: 0933cbcb9ce67e055f5230a8d5e07f5cb41f887d
source-repository-package
type: git
@@ -278,6 +278,7 @@ export type CIContent =
| CIContent.RcvCall
| CIContent.RcvIntegrityError
| CIContent.RcvDecryptionError
| CIContent.RcvMsgError
| CIContent.RcvGroupInvitation
| CIContent.SndGroupInvitation
| CIContent.RcvDirectEvent
@@ -312,6 +313,7 @@ export namespace CIContent {
| "rcvCall"
| "rcvIntegrityError"
| "rcvDecryptionError"
| "rcvMsgError"
| "rcvGroupInvitation"
| "sndGroupInvitation"
| "rcvDirectEvent"
@@ -383,6 +385,11 @@ export namespace CIContent {
msgCount: number // word32
}
export interface RcvMsgError extends Interface {
type: "rcvMsgError"
rcvMsgError: RcvMsgError
}
export interface RcvGroupInvitation extends Interface {
type: "rcvGroupInvitation"
groupInvitation: CIGroupInvitation
@@ -2075,6 +2082,11 @@ export interface CryptoFileArgs {
fileNonce: string
}
export interface DroppedMsg {
brokerTs: string // ISO-8601 timestamp
attempts: number // int
}
export interface E2EInfo {
pqEnabled?: boolean
}
@@ -3606,6 +3618,26 @@ export namespace RcvGroupEvent {
}
}
export type RcvMsgError = RcvMsgError.Dropped | RcvMsgError.ParseError
export namespace RcvMsgError {
export type Tag = "dropped" | "parseError"
interface Interface {
type: Tag
}
export interface Dropped extends Interface {
type: "dropped"
attempts: number // int
}
export interface ParseError extends Interface {
type: "parseError"
parseError: string
}
}
export interface RelayProfile {
displayName: string
fullName: string
@@ -3681,6 +3713,7 @@ export namespace SMPAgentError {
export interface A_DUPLICATE extends Interface {
type: "A_DUPLICATE"
droppedMsg_?: DroppedMsg
}
export interface A_QUEUE extends Interface {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."97802a30fce1dfeea90f0b465e21fc8eca937abb" = "1pgrfzir7www4f2h1byvzlihs6vird85m1mhwiinl0xaqsycha1m";
"https://github.com/simplex-chat/simplexmq.git"."0933cbcb9ce67e055f5230a8d5e07f5cb41f887d" = "0mnqcfvv1hshknl5mv6b05sh6nylzsy98xmfgyczfnryqd88r2jc";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
+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]