mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-28 07:34:16 +00:00
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:
co-authored by
Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
parent
1428ad75f9
commit
6f21826579
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+15
@@ -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()
|
||||
|
||||
+4
@@ -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,
|
||||
|
||||
+14
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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,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";
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>)*
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user