diff --git a/bots/api/EVENTS.md b/bots/api/EVENTS.md index 947c60586a..794670c436 100644 --- a/bots/api/EVENTS.md +++ b/bots/api/EVENTS.md @@ -61,7 +61,6 @@ This file is generated automatically. - [ContactConnecting](#contactconnecting) - [BusinessLinkConnecting](#businesslinkconnecting) - [JoinedGroupMemberConnecting](#joinedgroupmemberconnecting) -- [SentGroupInvitation](#sentgroupinvitation) - [GroupLinkConnecting](#grouplinkconnecting) [Network connection events](#network-connection-events) @@ -701,20 +700,6 @@ Group member is announced to the group and will be connecting to bot. --- -### SentGroupInvitation - -Sent when another user joins group via bot's link. - -**Record type**: -- type: "sentGroupInvitation" -- user: [User](./TYPES.md#user) -- groupInfo: [GroupInfo](./TYPES.md#groupinfo) -- contact: [Contact](./TYPES.md#contact) -- member: [GroupMember](./TYPES.md#groupmember) - ---- - - ### GroupLinkConnecting Sent when bot joins group via another user link. diff --git a/bots/src/API/Docs/Events.hs b/bots/src/API/Docs/Events.hs index f0c9352efd..1b04ece1a7 100644 --- a/bots/src/API/Docs/Events.hs +++ b/bots/src/API/Docs/Events.hs @@ -133,7 +133,6 @@ chatEventsDocsData = ("CEvtContactConnecting", "Contact confirmed connection.\n\nSent when contact started connecting via bot's 1-time invitation link or when bot connects to another SimpleX address."), -- CONF ("CEvtBusinessLinkConnecting", "Contact confirmed connection.\n\nSent when bot connects to another business address."), -- CONF ("CEvtJoinedGroupMemberConnecting", "Group member is announced to the group and will be connecting to bot."), -- MSG - ("CEvtSentGroupInvitation", "Sent when another user joins group via bot's link."), -- INV ("CEvtGroupLinkConnecting", "Sent when bot joins group via another user link.") -- CONF ], [] diff --git a/packages/simplex-chat-client/types/typescript/src/events.ts b/packages/simplex-chat-client/types/typescript/src/events.ts index cc19305913..a3de141e97 100644 --- a/packages/simplex-chat-client/types/typescript/src/events.ts +++ b/packages/simplex-chat-client/types/typescript/src/events.ts @@ -46,7 +46,6 @@ export type ChatEvent = | CEvt.ContactConnecting | CEvt.BusinessLinkConnecting | CEvt.JoinedGroupMemberConnecting - | CEvt.SentGroupInvitation | CEvt.GroupLinkConnecting | CEvt.HostConnected | CEvt.HostDisconnected @@ -99,7 +98,6 @@ export namespace CEvt { | "contactConnecting" | "businessLinkConnecting" | "joinedGroupMemberConnecting" - | "sentGroupInvitation" | "groupLinkConnecting" | "hostConnected" | "hostDisconnected" @@ -430,14 +428,6 @@ export namespace CEvt { member: T.GroupMember } - export interface SentGroupInvitation extends Interface { - type: "sentGroupInvitation" - user: T.User - groupInfo: T.GroupInfo - contact: T.Contact - member: T.GroupMember - } - export interface GroupLinkConnecting extends Interface { type: "groupLinkConnecting" user: T.User diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_events.py b/packages/simplex-chat-python/src/simplex_chat/types/_events.py index 7b7c724c92..3b293eb608 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_events.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_events.py @@ -281,13 +281,6 @@ class JoinedGroupMemberConnecting(TypedDict): hostMember: "T.GroupMember" member: "T.GroupMember" -class SentGroupInvitation(TypedDict): - type: Literal["sentGroupInvitation"] - user: "T.User" - groupInfo: "T.GroupInfo" - contact: "T.Contact" - member: "T.GroupMember" - class GroupLinkConnecting(TypedDict): type: Literal["groupLinkConnecting"] user: "T.User" @@ -367,7 +360,6 @@ ChatEvent = ( | ContactConnecting | BusinessLinkConnecting | JoinedGroupMemberConnecting - | SentGroupInvitation | GroupLinkConnecting | HostConnected | HostDisconnected @@ -377,7 +369,7 @@ ChatEvent = ( | ChatErrors ) -ChatEvent_Tag = Literal["contactConnected", "contactUpdated", "contactDeletedByContact", "receivedContactRequest", "newMemberContactReceivedInv", "contactSndReady", "newChatItems", "chatItemReaction", "chatItemsDeleted", "chatItemUpdated", "groupChatItemsDeleted", "chatItemsStatusesUpdated", "receivedGroupInvitation", "userJoinedGroup", "groupUpdated", "joinedGroupMember", "memberRole", "deletedMember", "leftMember", "deletedMemberUser", "groupDeleted", "connectedToGroupMember", "memberAcceptedByOther", "memberBlockedForAll", "groupMemberUpdated", "groupLinkDataUpdated", "groupRelayUpdated", "rcvFileDescrReady", "rcvFileComplete", "sndFileCompleteXFTP", "rcvFileStart", "rcvFileSndCancelled", "rcvFileAccepted", "rcvFileError", "rcvFileWarning", "sndFileError", "sndFileWarning", "acceptingContactRequest", "acceptingBusinessRequest", "contactConnecting", "businessLinkConnecting", "joinedGroupMemberConnecting", "sentGroupInvitation", "groupLinkConnecting", "hostConnected", "hostDisconnected", "subscriptionStatus", "messageError", "chatError", "chatErrors"] +ChatEvent_Tag = Literal["contactConnected", "contactUpdated", "contactDeletedByContact", "receivedContactRequest", "newMemberContactReceivedInv", "contactSndReady", "newChatItems", "chatItemReaction", "chatItemsDeleted", "chatItemUpdated", "groupChatItemsDeleted", "chatItemsStatusesUpdated", "receivedGroupInvitation", "userJoinedGroup", "groupUpdated", "joinedGroupMember", "memberRole", "deletedMember", "leftMember", "deletedMemberUser", "groupDeleted", "connectedToGroupMember", "memberAcceptedByOther", "memberBlockedForAll", "groupMemberUpdated", "groupLinkDataUpdated", "groupRelayUpdated", "rcvFileDescrReady", "rcvFileComplete", "sndFileCompleteXFTP", "rcvFileStart", "rcvFileSndCancelled", "rcvFileAccepted", "rcvFileError", "rcvFileWarning", "sndFileError", "sndFileWarning", "acceptingContactRequest", "acceptingBusinessRequest", "contactConnecting", "businessLinkConnecting", "joinedGroupMemberConnecting", "groupLinkConnecting", "hostConnected", "hostDisconnected", "subscriptionStatus", "messageError", "chatError", "chatErrors"] class OnEventDecorator(Protocol): @@ -640,12 +632,6 @@ class OnEventDecorator(Protocol): Callable[["JoinedGroupMemberConnecting"], Awaitable[None]], ]: ... - @overload - def __call__(self, event: Literal["sentGroupInvitation"], /) -> Callable[ - [Callable[["SentGroupInvitation"], Awaitable[None]]], - Callable[["SentGroupInvitation"], Awaitable[None]], - ]: ... - @overload def __call__(self, event: Literal["groupLinkConnecting"], /) -> Callable[ [Callable[["GroupLinkConnecting"], Awaitable[None]]], diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 6a80f51a1e..528c30dfdf 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -937,7 +937,6 @@ data ChatEvent | CEvtUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact} -- there is the same command response | CEvtGroupLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember} | CEvtBusinessLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, fromContact :: Contact} - | CEvtSentGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember} -- there is the same command response | CEvtContactUpdated {user :: User, fromContact :: Contact, toContact :: Contact} | CEvtGroupMemberUpdated {user :: User, groupInfo :: GroupInfo, fromMember :: GroupMember, toMember :: GroupMember} | CEvtContactDeletedByContact {user :: User, contact :: Contact} diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 2226f80fab..61af4e98ee 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1667,8 +1667,8 @@ processChatCommand cxt nm = \case pure $ CRChatRelayTestResult user (Just relayProfile) (Just $ RelayTestFailure step e) lift (withAgent' $ \a -> connRequestPQSupport a PQSupportOff cReq) >>= \case Nothing -> failWithProfile RTSConnect (ChatError $ CERelayTestError "invalid connection request") - Just (agentV, _) -> do - let chatV = agentToChatVersion agentV + Just _ -> do + let chatV = initialChatVersion subMode <- chatReadVar subscriptionMode connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff conn@Connection {connId = testCId} <- withFastStore $ \db -> @@ -3283,7 +3283,7 @@ processChatCommand cxt nm = \case unless (groupFeatureUserAllowed SGFDirectMessages g) $ throwCmdError "direct messages not allowed" case memberConn m of Just mConn@Connection {peerChatVRange} -> do - unless (maxVersion peerChatVRange >= groupDirectInvVersion) $ throwChatError CEPeerChatVRangeIncompatible + unless (maxVersion peerChatVRange >= initialChatVersion) $ throwChatError CEPeerChatVRangeIncompatible when (isJust $ memberContactId m) $ throwCmdError "member contact already exists" subMode <- chatReadVar subscriptionMode -- TODO PQ should negotitate contact connection with PQSupportOn? @@ -3727,15 +3727,15 @@ processChatCommand cxt nm = \case lift (withAgent' $ \a -> connRequestPQSupport a PQSupportOn cReq) >>= \case Nothing -> throwChatError CEInvalidConnReq -- TODO PQ the error above should be CEIncompatibleConnReqVersion, also the same API should be called in Plan - Just (agentV, pqSup') -> do - let chatV = agentToChatVersion agentV + Just (_, pqSup') -> do + let chatV = initialChatVersion withFastStore' (\db -> getConnectionEntityByConnReq db cxt user cReqs) >>= \case Nothing -> joinNewConn chatV Just (RcvDirectMsgConnection conn@Connection {connStatus, contactConnInitiated, customUserProfileId} _ct_) | connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV -- own connection link | connStatus == ConnPrepared -> do -- retrying join after error localIncognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId - joinPreparedConn conn (fromLocalProfile <$> localIncognitoProfile) chatV + joinPreparedConn conn (fromLocalProfile <$> localIncognitoProfile) Just ent -> throwCmdError $ "connection is not RcvDirectMsgConnection: " <> show (connEntityInfo ent) where joinNewConn chatV = do @@ -3744,10 +3744,10 @@ processChatCommand cxt nm = \case connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' let ccLink = CCLink cReq $ serverShortLink <$> sLnk_ conn <- withFastStore' $ \db -> createDirectConnection' db userId connId ccLink contactId_ ConnPrepared incognitoProfile subMode chatV pqSup' - joinPreparedConn conn incognitoProfile chatV - joinPreparedConn conn incognitoProfile chatV = do + joinPreparedConn conn incognitoProfile + joinPreparedConn conn incognitoProfile = do profileToSend <- presentUserBadge user incognitoProfile $ userProfileDirect user incognitoProfile Nothing True - dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend + dm <- encodeConnInfoPQ pqSup' $ XInfo profileToSend sqSecured <- withAgent $ \a -> joinConnection a nm (aUserId user) (aConnId conn) True cReq dm pqSup' subMode let newStatus = if sqSecured then ConnSndReady else ConnJoined conn' <- withFastStore' $ \db -> updateConnectionStatusFromTo db conn ConnPrepared newStatus @@ -3890,14 +3890,14 @@ processChatCommand cxt nm = \case -- 2) toggle enabled, address doesn't support PQ - PQSupportOn but without compression, with version range indicating support lift (withAgent' $ \a -> connRequestPQSupport a pqSup cReq) >>= \case Nothing -> throwChatError CEInvalidConnReq - Just (agentV, _) -> do - let chatV = agentToChatVersion agentV + Just _ -> do + let chatV = initialChatVersion connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup pure (connId, chatV) mkXContactId :: Maybe XContactId -> CM XContactId mkXContactId = maybe (XContactId <$> drgRandomBytes 16) pure joinContact :: User -> Connection -> ConnReqContact -> Maybe Profile -> XContactId -> Maybe SharedMsgId -> Maybe (SharedMsgId, MsgContent) -> Maybe (Maybe GroupInfo) -> Maybe MemberId -> PQSupport -> CM Connection - joinContact user conn@Connection {connChatVersion = chatV} cReq incognitoProfile xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ pqSup = do + joinContact user conn cReq incognitoProfile xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ pqSup = do -- gInfo_ is Maybe (Maybe GroupInfo), where Just Nothing means "some unknown group", e.g. when joining via link without profile profileToSend <- presentUserBadge user incognitoProfile $ case gInfo_ of @@ -3907,7 +3907,7 @@ processChatCommand cxt nm = \case Just (Just gInfo) | useRelays' gInfo -> case relayMemberId_ of Just relayMemberId -> encodeXMemberConnInfo gInfo relayMemberId profileToSend Nothing -> throwChatError $ CEInternalError "relay group join without target relay memberId" - _ -> encodeConnInfoPQ pqSup chatV $ XContact profileToSend (Just xContactId) welcomeSharedMsgId msg_ + _ -> encodeConnInfoPQ pqSup $ XContact profileToSend (Just xContactId) welcomeSharedMsgId msg_ subMode <- chatReadVar subscriptionMode void $ withAgent $ \a -> joinConnection a nm (aUserId user) (aConnId conn) True cReq dm pqSup subMode withFastStore' $ \db -> updateConnectionStatusFromTo db conn ConnPrepared ConnJoined @@ -4215,8 +4215,8 @@ processChatCommand cxt nm = \case (FixedLinkData {linkConnReq = cReq}, _cData) <- getShortLinkConnReq nm user address lift (withAgent' $ \a -> connRequestPQSupport a PQSupportOff cReq) >>= \case Nothing -> throwChatError CEInvalidConnReq - Just (agentV, _) -> do - let chatV = agentToChatVersion agentV + Just _ -> do + let chatV = initialChatVersion gVar <- asks random subMode <- chatReadVar subscriptionMode connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index bf6dde842c..37c3b7ae7d 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -40,7 +40,7 @@ import Data.Foldable (foldr') import Data.Functor (($>)) import Data.Functor.Identity import Data.Int (Int64) -import Data.List (find, foldl', mapAccumL, partition) +import Data.List (foldl', mapAccumL, partition) import Data.List.NonEmpty (NonEmpty (..), (<|)) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) @@ -96,7 +96,7 @@ import Simplex.Messaging.Compression (compressionLevel, limitDecompress') import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF -import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..), pattern IKPQOff, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn) +import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..), pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn) import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding (smpEncode) import Simplex.Messaging.Encoding.String @@ -918,7 +918,7 @@ acceptContactRequest nm user@User {userId} UserContactRequest {agentInvitationId incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId pure (ct, conn, ExistingIncognito <$> incognitoProfile) profileToSend <- presentUserBadge user incognitoProfile $ userProfileDirect user (fromIncognitoProfile <$> incognitoProfile) (Just ct) True - dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend + dm <- encodeConnInfoPQ pqSup' $ XInfo profileToSend (ct,conn,) <$> withAgent (\a -> acceptContact a nm (aUserId user) (aConnId conn) True invId dm pqSup' subMode) acceptContactRequestAsync :: User -> Int64 -> Contact -> UserContactRequest -> Maybe IncognitoProfile -> CM Contact @@ -939,7 +939,7 @@ acceptContactRequestAsync Connection {connId} <- liftIO $ createAcceptedContactConn db user (Just uclId) contactId acId chatV cReqChatVRange cReqPQSup incognitoProfile subMode currentTs liftIO $ setCommandConnId db user cmdId connId getContact db cxt user contactId - agentAcceptContactAsync cmdId acId True cReqInvId (XInfo profileToSend) cReqPQSup chatV subMode + agentAcceptContactAsync cmdId acId True cReqInvId (XInfo profileToSend) cReqPQSup subMode pure ct' acceptGroupJoinRequestAsync :: User -> Int64 -> GroupInfo -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> Maybe MemberId -> Maybe SharedMsgId -> GroupAcceptance -> GroupMemberRole -> Maybe IncognitoProfile -> Maybe MemberKey -> Maybe GroupMember -> CM GroupMember @@ -992,7 +992,7 @@ acceptGroupJoinRequestAsync m <- withStore $ \db -> do liftIO $ createJoiningMemberConnection db user uclId (cmdId, acId) chatV cReqChatVRange groupMemberId subMode getGroupMemberById db cxt user groupMemberId - agentAcceptContactAsync cmdId acId True cReqInvId msg PQSupportOff chatV subMode + agentAcceptContactAsync cmdId acId True cReqInvId msg PQSupportOff subMode pure m acceptGroupJoinSendRejectAsync :: User -> Int64 -> GroupInfo -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> GroupRejectionReason -> CM GroupMember @@ -1024,7 +1024,7 @@ acceptGroupJoinSendRejectAsync m <- withStore $ \db -> do liftIO $ createJoiningMemberConnection db user uclId (cmdId, acId) chatV cReqChatVRange groupMemberId subMode getGroupMemberById db cxt user groupMemberId - agentAcceptContactAsync cmdId acId False cReqInvId msg PQSupportOff chatV subMode + agentAcceptContactAsync cmdId acId False cReqInvId msg PQSupportOff subMode pure m acceptBusinessJoinRequestAsync :: User -> Int64 -> GroupInfo -> GroupMember -> UserContactRequest -> CM (GroupInfo, GroupMember) @@ -1058,7 +1058,7 @@ acceptBusinessJoinRequestAsync withStore' $ \db -> do forM_ xContactId $ \xcId -> setBusinessChatAcceptedXContactId db gInfo xcId createJoiningMemberConnection db user uclId (cmdId, acId) chatV cReqChatVRange groupMemberId subMode - agentAcceptContactAsync cmdId acId True cReqInvId msg PQSupportOff chatV subMode + agentAcceptContactAsync cmdId acId True cReqInvId msg PQSupportOff subMode let cd = CDGroupSnd gInfo Nothing -- TODO [short links] move to profileContactRequest? createInternalChatItem user cd (CISndGroupE2EEInfo $ e2eInfoGroup gInfo) Nothing @@ -1087,7 +1087,7 @@ acceptRelayJoinRequestAsync gInfo' <- liftIO $ updateRelayOwnStatusFromTo db gInfo RSInvited RSAccepted ownerMember' <- getGroupMemberById db cxt user groupMemberId pure (gInfo', ownerMember') - agentAcceptContactAsync cmdId acId True cReqInvId msg PQSupportOff chatV subMode + agentAcceptContactAsync cmdId acId True cReqInvId msg PQSupportOff subMode pure r rejectRelayInvitationAsync @@ -1111,7 +1111,7 @@ rejectRelayInvitationAsync user uclId cxt groupRelayInv invId reqChatVRange init (cmdId, acId) <- prepareAgentAccept user False invId PQSupportOff withStore' $ \db -> createJoiningMemberConnection db user uclId (cmdId, acId) chatV reqChatVRange groupMemberId subMode - agentAcceptContactAsync cmdId acId False invId msg PQSupportOff chatV subMode + agentAcceptContactAsync cmdId acId False invId msg PQSupportOff subMode businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile businessGroupProfile Profile {displayName, fullName, shortDescr, description, image} groupPreferences = @@ -1155,7 +1155,7 @@ shouldIntroduce m vec mem = introduceMember :: User -> GroupInfo -> GroupMember -> [GroupMember] -> Maybe MsgScope -> CM () introduceMember _ _ GroupMember {activeConn = Nothing} _ _ = throwChatError $ CEInternalError "member connection not active" -introduceMember user gInfo@GroupInfo {groupId} toMember@GroupMember {activeConn = Just conn} introduceToMembers msgScope = do +introduceMember user gInfo toMember@GroupMember {activeConn = Just conn} introduceToMembers msgScope = do void . sendGroupMessage' user gInfo introduceToMembers $ XGrpMemNew (memberInfo gInfo toMember) msgScope sendIntroductions introduceToMembers where @@ -1163,13 +1163,9 @@ introduceMember user gInfo@GroupInfo {groupId} toMember@GroupMember {activeConn updateToMemberVector reMembers updateReMembersVectors reMembers shuffledReMembers <- liftIO $ shuffleMembers reMembers - if toMember `supportsVersion` batchSendVersion - then do - let events = map (memberIntroEvt gInfo) shuffledReMembers - forM_ (L.nonEmpty events) $ \events' -> - sendGroupMemberMessages user gInfo conn events' - else forM_ shuffledReMembers $ \reMember -> - void $ sendDirectMemberMessage conn (memberIntroEvt gInfo reMember) groupId + let events = map (memberIntroEvt gInfo) shuffledReMembers + forM_ (L.nonEmpty events) $ \events' -> + sendGroupMemberMessages user gInfo conn events' updateToMemberVector :: [GroupMember] -> CM () updateToMemberVector reMembers = do let relations = map (\GroupMember {indexInGroup} -> (indexInGroup, (IDReferencedIntroduced, MRIntroduced))) reMembers @@ -1315,20 +1311,19 @@ buildGroupRoster mods = take maxGroupRosterSize $ mapMaybe rosterMember mods sendHistory :: User -> GroupInfo -> GroupMember -> CM () sendHistory _ _ GroupMember {activeConn = Nothing} = throwChatError $ CEInternalError "member connection not active" -sendHistory user gInfo@GroupInfo {membership} m@GroupMember {activeConn = Just conn} = - when (m `supportsVersion` batchSendVersion) $ do - (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo m 100) - (errs', fwdMsgsByItem) <- partitionEithers <$> mapM (tryAllErrors . itemForwardMsgs) items - let errors = map ChatErrorStore errs <> errs' - unless (null errors) $ toView $ CEvtChatErrors errors - -- signed items keep the author's original bytes/signature, unsigned are re-encoded; the welcome message - -- (regular groups only; never channels) is an authored element -- all batch together in order. - let fwdEls = map (uncurry encodeFwdElement) (concat fwdMsgsByItem) - welcomeEl <- welcomeElement - let (batches, dropped) = batchElements maxEncodedMsgLength (fwdEls <> maybe [] (: []) welcomeEl) - when (dropped > 0) $ toView $ CEvtChatErrors [ChatError $ CEInternalError ("sendHistory: dropped " <> show dropped <> " oversized history messages")] - forM_ batches $ \body -> - void $ withAgent $ \a -> sendMessages a [(aConnId conn, PQEncOff, MsgFlags False, VRValue Nothing body)] +sendHistory user gInfo@GroupInfo {membership} m@GroupMember {activeConn = Just conn} = do + (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo m 100) + (errs', fwdMsgsByItem) <- partitionEithers <$> mapM (tryAllErrors . itemForwardMsgs) items + let errors = map ChatErrorStore errs <> errs' + unless (null errors) $ toView $ CEvtChatErrors errors + -- signed items keep the author's original bytes/signature, unsigned are re-encoded; the welcome message + -- (regular groups only; never channels) is an authored element -- all batch together in order. + let fwdEls = map (uncurry encodeFwdElement) (concat fwdMsgsByItem) + welcomeEl <- welcomeElement + let (batches, dropped) = batchElements maxEncodedMsgLength (fwdEls <> maybe [] (: []) welcomeEl) + when (dropped > 0) $ toView $ CEvtChatErrors [ChatError $ CEInternalError ("sendHistory: dropped " <> show dropped <> " oversized history messages")] + forM_ batches $ \body -> + void $ withAgent $ \a -> sendMessages a [(aConnId conn, PQEncOff, MsgFlags False, VRValue Nothing body)] where welcomeElement :: CM (Maybe ByteString) welcomeElement = case descrEvent_ of @@ -1347,10 +1342,9 @@ sendHistory user gInfo@GroupInfo {membership} m@GroupMember {activeConn = Just c -- in channels sendHistory runs on the relay, which cannot author XMsgNew (GRRelay < GRObserver); -- the welcome message reaches new members via the channel link data instead | useRelays' gInfo = Nothing - | m `supportsVersion` groupHistoryIncludeWelcomeVersion = do + | otherwise = do let GroupInfo {groupProfile = GroupProfile {description}} = gInfo fmap (\descr -> XMsgNew $ mcSimple (MCText descr)) description - | otherwise = Nothing itemForwardMsgs :: (CChatItem 'CTGroup, (Maybe SignedMsg, Maybe GroupMemberId)) -> CM [(GrpMsgForward, VerifiedMsg 'Json)] itemForwardMsgs (cci, (signedMsg_, signedByGMId_)) = case cci of (CChatItem SMDRcv ci@ChatItem {content = CIRcvMsgContent mc, file}) @@ -1714,7 +1708,7 @@ updatePeerChatVRange conn@Connection {connId, connChatVersion = v, peerChatVRang pure conn {connChatVersion = v', peerChatVRange = msgVRange} else pure conn -- TODO v6.0 remove/review: for contacts only version upgrade should trigger enabling PQ support/encryption - if connType == ConnContact && v' >= pqEncryptionCompressionVersion && (pqSupport /= PQSupportOn || pqEncryption /= PQEncOn) + if connType == ConnContact && (pqSupport /= PQSupportOn || pqEncryption /= PQEncOn) then do withStore' $ \db -> updateConnSupportPQ db connId PQSupportOn PQEncOn pure conn' {pqSupport = PQSupportOn, pqEncryption = PQEncOn} @@ -2118,14 +2112,6 @@ deleteSupportChatIfExists db user gInfo m = do sendDirectContactMessages :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM [Either ChatError SndMessage] sendDirectContactMessages user ct events = do - Connection {connChatVersion = v} <- liftEither $ contactSendConn_ ct - if v >= batchSend2Version - then sendDirectContactMessages' user ct events - else forM (L.toList events) $ \evt -> - (Right . fst <$> sendDirectContactMessage user ct evt) `catchAllErrors` \e -> pure (Left e) - -sendDirectContactMessages' :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM [Either ChatError SndMessage] -sendDirectContactMessages' user ct events = do conn@Connection {connId} <- liftEither $ contactSendConn_ ct let idsEvts = L.map (ConnectionId connId,Nothing,) events msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events} @@ -2261,17 +2247,15 @@ batchSndMessagesJSON :: BatchMode -> NonEmpty (Either ChatError SndMessage) -> [ batchSndMessagesJSON mode = batchMessages mode maxEncodedMsgLength . L.toList encodeConnInfo :: MsgEncodingI e => ChatMsgEvent e -> CM ByteString -encodeConnInfo chatMsgEvent = do - cxt <- chatStoreCxt - encodeConnInfoPQ PQSupportOff (maxVersion (vr cxt)) chatMsgEvent +encodeConnInfo = encodeConnInfoPQ PQSupportOff -encodeConnInfoPQ :: MsgEncodingI e => PQSupport -> VersionChat -> ChatMsgEvent e -> CM ByteString -encodeConnInfoPQ pqSup v chatMsgEvent = do +encodeConnInfoPQ :: MsgEncodingI e => PQSupport -> ChatMsgEvent e -> CM ByteString +encodeConnInfoPQ pqSup chatMsgEvent = do cxt <- chatStoreCxt let info = ChatMessage {chatVRange = vr cxt, msgId = Nothing, chatMsgEvent} case encodeChatMessage maxEncodedInfoLength info of ECMEncoded connInfo -> case pqSup of - PQSupportOn | v >= pqEncryptionCompressionVersion && B.length connInfo > maxCompressedInfoLength -> do + PQSupportOn | B.length connInfo > maxCompressedInfoLength -> do let connInfo' = compressedBatchMsgBody_ connInfo when (B.length connInfo' > maxCompressedInfoLength) $ throwChatError $ CEException "large compressed info" pure connInfo' @@ -2326,7 +2310,7 @@ deliverMessagesB msgReqs = do lift . withStoreBatch $ \db -> L.map (bindRight $ createDelivery db) sent where connSupportsPQ = \case - Right (Connection {pqSupport = PQSupportOn, connChatVersion = v}, _, _) -> v >= pqEncryptionCompressionVersion + Right (Connection {pqSupport = PQSupportOn}, _, _) -> True _ -> False compressBodies = forME msgReqs $ \(conn, msgFlags, (mbr, msgIds)) -> runExceptT $ do @@ -2477,10 +2461,9 @@ sendGroupProfileUpdate user gInfo scope asGroup members = (Nothing, Just _) -> True _ -> False sendProfileUpdate = do - let members' = filter (`supportsVersion` memberProfileUpdateVersion) members -- shouldSendProfileUpdate excludes incognito membership, so the badge is presented profileUpdate <- presentUserBadge user Nothing $ redactedMemberProfile gInfo (membership gInfo) $ fromLocalProfile p - void $ sendGroupMessage' user gInfo members' $ XInfo profileUpdate + void $ sendGroupMessage' user gInfo members $ XInfo profileUpdate currentTs <- liftIO getCurrentTime withStore' $ \db -> updateUserMemberProfileSentAt db user gInfo currentTs @@ -2499,12 +2482,12 @@ sendGroupSignedMessages_ gInfo@GroupInfo {groupId} recipientMembers signedEvents sndMsgs_ <- lift $ createSndMessages idsEvts recipientMembers' <- liftIO $ shuffleMembers recipientMembers let msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events} - (toSendSeparate, toSendBatched, toPending, forwarded, _, dups) = - foldr' (addMember recipientMembers') ([], [], [], [], S.empty, 0 :: Int) recipientMembers' + (toSend, toPending, forwarded, _, dups) = + foldr' (addMember recipientMembers') ([], [], [], S.empty, 0 :: Int) recipientMembers' when (dups /= 0) $ logError $ "sendGroupMessages_: " <> tshow dups <> " duplicate members" -- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here -- Deliver to toSend members - let (sendToMemIds, msgReqs) = prepareMsgReqs msgFlags sndMsgs_ toSendSeparate toSendBatched + let (sendToMemIds, msgReqs) = prepareMsgReqs msgFlags sndMsgs_ toSend delivered <- maybe (pure []) (fmap L.toList . deliverMessagesB) $ L.nonEmpty msgReqs when (length delivered /= length sendToMemIds) $ logError "sendGroupMessages_: sendToMemIds and delivered length mismatch" -- Save as pending for toPending members @@ -2524,29 +2507,24 @@ sendGroupSignedMessages_ gInfo@GroupInfo {groupId} recipientMembers signedEvents liftM2 (<>) (shuffle adminMs) (shuffle otherMs) where isAdmin GroupMember {memberRole} = memberRole >= GRAdmin - addMember members m acc@(toSendSeparate, toSendBatched, pending, forwarded, !mIds, !dups) = + addMember members m acc@(toSend, pending, forwarded, !mIds, !dups) = case memberSendAction gInfo events members m of Just a - | mId `S.member` mIds -> (toSendSeparate, toSendBatched, pending, forwarded, mIds, dups + 1) + | mId `S.member` mIds -> (toSend, pending, forwarded, mIds, dups + 1) | otherwise -> case a of - MSASend conn -> ((m, conn) : toSendSeparate, toSendBatched, pending, forwarded, mIds', dups) - MSASendBatched conn -> (toSendSeparate, (m, conn) : toSendBatched, pending, forwarded, mIds', dups) - MSAPending -> (toSendSeparate, toSendBatched, m : pending, forwarded, mIds', dups) - MSAForwarded -> (toSendSeparate, toSendBatched, pending, m : forwarded, mIds', dups) + MSASend conn -> ((m, conn) : toSend, pending, forwarded, mIds', dups) + MSAPending -> (toSend, m : pending, forwarded, mIds', dups) + MSAForwarded -> (toSend, pending, m : forwarded, mIds', dups) Nothing -> acc where mId = groupMemberId' m mIds' = S.insert mId mIds - prepareMsgReqs :: MsgFlags -> NonEmpty (Either ChatError SndMessage) -> [(GroupMember, Connection)] -> [(GroupMember, Connection)] -> ([GroupMemberId], [Either ChatError ChatMsgReq]) - prepareMsgReqs msgFlags msgs toSendSeparate toSendBatched = do + prepareMsgReqs :: MsgFlags -> NonEmpty (Either ChatError SndMessage) -> [(GroupMember, Connection)] -> ([GroupMemberId], [Either ChatError ChatMsgReq]) + prepareMsgReqs msgFlags msgs toSend = do let mode = if useRelays' gInfo then BMBinary else BMJson batched_ = batchSndMessagesJSON mode msgs case L.nonEmpty batched_ of - Just batched' -> do - let lenMsgs = length msgs - (memsSep, mreqsSep) = foldMembers lenMsgs sndMessageMBR msgs toSendSeparate - (memsBtch, mreqsBtch) = foldMembers (length batched' + lenMsgs) msgBatchMBR batched' toSendBatched - (memsSep <> memsBtch, mreqsSep <> mreqsBtch) + Just batched' -> foldMembers (length batched' + length msgs) msgBatchMBR batched' toSend Nothing -> ([], []) where foldMembers :: forall a. Int -> (Maybe Int -> Int -> a -> (ValueOrRef MsgBody, [MessageId])) -> NonEmpty (Either ChatError a) -> [(GroupMember, Connection)] -> ([GroupMemberId], [Either ChatError ChatMsgReq]) @@ -2561,8 +2539,6 @@ sendGroupSignedMessages_ gInfo@GroupInfo {groupId} recipientMembers signedEvents addBody mb (i, (memIds, reqs)) = let req = (conn,msgFlags,) . mkMb memIdx_ i <$> mb in (i - 1, (groupMemberId : memIds, req : reqs)) - sndMessageMBR :: Maybe Int -> Int -> SndMessage -> (ValueOrRef MsgBody, [MessageId]) - sndMessageMBR memIdx_ i SndMessage {msgId, msgBody} = (vrValue_ memIdx_ i msgBody, [msgId]) msgBatchMBR :: Maybe Int -> Int -> MsgBatch -> (ValueOrRef MsgBody, [MessageId]) msgBatchMBR memIdx_ i (MsgBatch batchBody sndMsgs) = (vrValue_ memIdx_ i batchBody, map (\SndMessage {msgId} -> msgId) sndMsgs) vrValue_ memIdx_ i v = case memIdx_ of @@ -2583,31 +2559,26 @@ sendGroupSignedMessages_ gInfo@GroupInfo {groupId} recipientMembers signedEvents createPendingMsg db (groupMemberId, msgId) = createPendingGroupMessage db groupMemberId msgId $> Right () -data MemberSendAction = MSASend Connection | MSASendBatched Connection | MSAPending | MSAForwarded +data MemberSendAction = MSASend Connection | MSAPending | MSAForwarded memberSendAction :: GroupInfo -> NonEmpty (ChatMsgEvent e) -> [GroupMember] -> GroupMember -> Maybe MemberSendAction -memberSendAction gInfo@GroupInfo {membership} events members m@GroupMember {memberRole, memberStatus} +memberSendAction gInfo@GroupInfo {membership} events members m@GroupMember {memberStatus} -- groups with relays require newer version - we don't need to check member version for batching and forwarding support | useRelays' gInfo = if -- if user is chat relay, send to all non chat relay members - | isRelay membership && not (isRelay m) -> MSASendBatched . snd <$> readyMemberConn m + | isRelay membership && not (isRelay m) -> MSASend . snd <$> readyMemberConn m -- if user is not chat relay, send only to chat relays - | not (isRelay membership) && isRelay m -> MSASendBatched . snd <$> readyMemberConn m + | not (isRelay membership) && isRelay m -> MSASend . snd <$> readyMemberConn m | otherwise -> Nothing -- TODO [relays] MSAForwarded to create GSSForwarded snd statuses? | otherwise = case memberConn m of Nothing -> pendingOrForwarded Just conn@Connection {connStatus} | connDisabled conn || connStatus == ConnDeleted || isConnFailed connStatus || memberStatus == GSMemRejected -> Nothing | connInactive conn -> Just MSAPending - | connStatus == ConnSndReady || connStatus == ConnReady -> sendBatchedOrSeparate conn + | connStatus == ConnSndReady || connStatus == ConnReady -> Just (MSASend conn) | otherwise -> pendingOrForwarded where - sendBatchedOrSeparate conn - -- admin doesn't support batch forwarding - send messages separately so that admin can forward one by one - | memberRole >= GRAdmin && not (m `supportsVersion` batchSend2Version) = Just (MSASend conn) - -- either member is not admin, or admin supports batched forwarding - | otherwise = Just (MSASendBatched conn) pendingOrForwarded = case memberCategory m of GCUserMember -> Nothing -- shouldn't happen GCInviteeMember -> Just MSAPending @@ -2616,18 +2587,14 @@ memberSendAction gInfo@GroupInfo {membership} events members m@GroupMember {memb GCPostMember -> forwardSupportedOrPending (invitedByGroupMemberId m) where forwardSupportedOrPending invitingMemberId_ - | membersSupport && all isForwardedGroupMsg events = Just MSAForwarded + | hasInvitingMember && all isForwardedGroupMsg events = Just MSAForwarded | any isXGrpMsgForward events = Nothing | otherwise = Just MSAPending where - membersSupport = - m `supportsVersion` groupForwardVersion && invitingMemberSupportsForward - invitingMemberSupportsForward = case invitingMemberId_ of + hasInvitingMember = case invitingMemberId_ of Just invMemberId -> -- can be optimized for large groups by replacing [GroupMember] with Map GroupMemberId GroupMember - case find (\m' -> groupMemberId' m' == invMemberId) members of - Just invitingMember -> invitingMember `supportsVersion` groupForwardVersion - Nothing -> False + any (\m' -> groupMemberId' m' == invMemberId) members Nothing -> False isXGrpMsgForward event = case event of XGrpMsgForward {} -> True @@ -2652,7 +2619,6 @@ sendGroupMemberMessage gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId} c messageMember :: SndMessage -> CM () messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction gInfo (chatMsgEvent :| []) [m] m) $ \case MSASend conn -> void $ deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId - MSASendBatched conn -> void $ deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId MSAPending -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId MSAForwarded -> pure () @@ -2874,9 +2840,9 @@ joinAgentConnectionAsync cmdId updateConn connId enableNtfs cReqUri cInfo subMod withAgent $ \a -> joinConnectionAsync a (aCorrId cmdId) updateConn connId enableNtfs cReqUri cInfo PQSupportOff subMode allowAgentConnectionAsync :: MsgEncodingI e => User -> Connection -> ConfirmationId -> ChatMsgEvent e -> CM () -allowAgentConnectionAsync user conn@Connection {connId, pqSupport, connChatVersion} confId msg = do +allowAgentConnectionAsync user conn@Connection {connId, pqSupport} confId msg = do cmdId <- withStore' $ \db -> createCommand db user (Just connId) CFAllowConn - dm <- encodeConnInfoPQ pqSupport connChatVersion msg + dm <- encodeConnInfoPQ pqSupport msg withAgent $ \a -> allowConnectionAsync a (aCorrId cmdId) (aConnId conn) confId dm withStore' $ \db -> updateConnectionStatus db conn ConnAccepted @@ -2886,9 +2852,9 @@ prepareAgentAccept user enableNtfs invId pqSup = do connId <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) enableNtfs invId pqSup pure (cmdId, connId) -agentAcceptContactAsync :: MsgEncodingI e => CommandId -> ConnId -> Bool -> InvitationId -> ChatMsgEvent e -> PQSupport -> VersionChat -> SubscriptionMode -> CM () -agentAcceptContactAsync cmdId connId enableNtfs invId msg pqSup chatV subMode = do - dm <- encodeConnInfoPQ pqSup chatV msg +agentAcceptContactAsync :: MsgEncodingI e => CommandId -> ConnId -> Bool -> InvitationId -> ChatMsgEvent e -> PQSupport -> SubscriptionMode -> CM () +agentAcceptContactAsync cmdId connId enableNtfs invId msg pqSup subMode = do + dm <- encodeConnInfoPQ pqSup msg withAgent $ \a -> acceptContactAsync a (aCorrId cmdId) connId enableNtfs invId dm pqSup subMode deleteAgentConnectionAsync :: ConnId -> CM () diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index a65bed75a0..7b37fe8a68 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -439,7 +439,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = when (pq /= pq') $ messageWarning "processINFOpqSupport: unexpected pqSupport change" processDirectMessage :: AEvent e -> ConnectionEntity -> Connection -> Maybe Contact -> CM () - processDirectMessage agentMsg connEntity conn@Connection {connId, connChatVersion, peerChatVRange, viaUserContactLink, customUserProfileId, connectionCode} = \case + processDirectMessage agentMsg connEntity conn@Connection {connId, viaUserContactLink, customUserProfileId, connectionCode} = \case Nothing -> case agentMsg of CONF confId pqSupport _ connInfo -> do chatRelayTests_ <- asks chatRelayTests @@ -642,19 +642,6 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = when (contactConnInitiated conn') $ do probeMatchingMembers ct' (contactConnIncognito ct') withStore' $ \db -> resetContactConnInitiated db user conn' - forM_ viaUserContactLink $ \userContactLinkId -> do - (ucl, gli_) <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId - -- let UserContactLink {addressSettings = AddressSettings {autoReply}} = ucl - when (connChatVersion < batchSend2Version) $ forM_ (autoReply $ addressSettings ucl) $ \mc -> sendAutoReply ct' mc Nothing -- old versions only - -- TODO REMOVE LEGACY vvv - forM_ gli_ $ \GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do - groupInfo <- withStore $ \db -> getGroupInfo db cxt user groupId - subMode <- chatReadVar subscriptionMode - groupConnIds@(cmdId, grpConnId) <- prepareAgentCreation user CFCreateConnGrpInv True SCMInvitation - gVar <- asks random - withStore $ \db -> createNewContactMemberAsync db gVar user groupInfo ct' gLinkMemRole groupConnIds connChatVersion peerChatVRange subMode - withAgent $ \a -> createConnectionAsync a (aCorrId cmdId) grpConnId True SCMInvitation CR.IKPQOff subMode - -- TODO REMOVE LEGACY ^^^ SENT msgId proxy -> do void $ continueSending connEntity conn sentMsgDeliveryEvent conn msgId @@ -705,7 +692,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> when (directOrUsed ct && sqSecured) $ do toView $ CEvtContactSndReady user ct - when (connChatVersion >= batchSend2Version) $ forM_ viaUserContactLink $ \userContactLinkId -> do + forM_ viaUserContactLink $ \userContactLinkId -> do (ucl, _) <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId forM_ (autoReply $ addressSettings ucl) $ \mc -> do connReq_ <- pure (contactRequestId' ct) $>>= \connReqId -> withStore' (\db -> getContactRequest' db user connReqId) @@ -740,53 +727,18 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM () - processGroupMessage agentMsg connEntity conn@Connection {connId, connChatVersion, customUserProfileId, connectionCode} gInfo@GroupInfo {groupId, groupProfile, membership, chatSettings} m = case agentMsg of + processGroupMessage agentMsg connEntity conn@Connection {connId, customUserProfileId, connectionCode} gInfo@GroupInfo {groupId, groupProfile, membership, chatSettings} m = case agentMsg of INV (ACR _ cReq) -> withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} -> case cReq of groupConnReq@(CRInvitationUri _ _) -> case cmdFunction of -- [async agent commands] XGrpMemIntro continuation on receiving INV - CFCreateConnGrpMemInv - | maxVersion (peerChatVRange conn) >= groupDirectInvVersion -> sendWithoutDirectCReq - | otherwise -> messageError "processGroupMessage INV: member chat version range incompatible" - where - sendWithoutDirectCReq = do - let GroupMember {groupMemberId, memberId} = m - hostConnId <- withStore $ \db -> do - liftIO $ setConnConnReqInv db user connId cReq - getHostConnId db user groupId - sendXGrpMemInv hostConnId Nothing XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} - -- TODO REMOVE LEGACY vvv - -- [async agent commands] group link auto-accept continuation on receiving INV - CFCreateConnGrpInv -> do - (ct, groupLinkId) <- withStore $ \db -> do - ct <- getContactViaMember db cxt user m - liftIO $ setNewContactMemberConnRequest db user m cReq - liftIO $ (ct,) <$> getGroupLinkId db user gInfo - if memberRole' membership >= GRAdmin - then do - sendGrpInvitation ct m groupLinkId - toView $ CEvtSentGroupInvitation user gInfo ct m - else messageError "processGroupMessage: group link host no longer has admin role" - where - sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> CM () - sendGrpInvitation ct GroupMember {memberId, memberRole = memRole} groupLinkId = do - let currentMemCount = fromIntegral $ currentMembers $ groupSummary gInfo - GroupMember {memberRole = userRole, memberId = userMemberId} = membership - groupInv = - GroupInvitation - { fromMember = MemberIdRole userMemberId userRole, - invitedMember = MemberIdRole memberId memRole, - connRequest = cReq, - groupProfile, - business = Nothing, - groupLinkId = groupLinkId, - groupSize = Just currentMemCount - } - (_msg, _) <- sendDirectContactMessage user ct $ XGrpInv groupInv - -- we could link chat item with sent group invitation message (_msg) - createInternalChatItem user (CDGroupRcv gInfo Nothing m) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing - -- TODO REMOVE LEGACY ^^^ + CFCreateConnGrpMemInv -> do + let GroupMember {groupMemberId, memberId} = m + hostConnId <- withStore $ \db -> do + liftIO $ setConnConnReqInv db user connId cReq + getHostConnId db user groupId + sendXGrpMemInv hostConnId Nothing XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} _ -> throwChatError $ CECommandError "unexpected cmdFunction" CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type" CONF confId _pqSupport _ connInfo -> do @@ -955,7 +907,6 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = toView $ CEvtJoinedGroupMember user gInfo'' m' {memberStatus = mStatus} let Connection {viaUserContactLink} = conn when (isJust viaUserContactLink && isNothing (memberContactId m')) $ sendXGrpLinkMem gInfo'' - when (connChatVersion < batchSend2Version) $ getAutoReplyMsg >>= mapM_ (\mc -> sendGroupAutoReply mc Nothing) if useRelays' gInfo'' then do introduceInChannel cxt user gInfo'' m' @@ -1223,7 +1174,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = JOINED sqSecured -> -- [async agent commands] continuation on receiving JOINED when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> - when (sqSecured && connChatVersion >= batchSend2Version) $ do + when (sqSecured) $ do mc_ <- getAutoReplyMsg forM_ mc_ $ \mc -> do connReq_ <- withStore' $ \db -> getBusinessContactRequest db user groupId @@ -1244,8 +1195,8 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = let pqSup = PQSupportOff lift (withAgent' $ \a -> connRequestPQSupport a pqSup cReq) >>= \case Nothing -> throwChatError CEInvalidConnReq - Just (agentV, _) -> do - let chatV = agentToChatVersion agentV + Just _ -> do + let chatV = initialChatVersion groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli cReqHash = contactCReqHash $ CRContactUri crData {crScheme = SSSimplex} -- Update connection with data derived from cReq, now available after getConnShortLinkAsync @@ -1618,15 +1569,12 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = | otherwise -> do acceptMember_ <- asks $ acceptMember . chatHooks . config maybe (pure $ Right (GAAccepted, gLinkMemRole)) (\am -> liftIO $ am gInfo gli p) acceptMember_ >>= \case - Right (acceptance, useRole) - | v < groupFastLinkJoinVersion -> - messageError "processContactConnMessage: chat version range incompatible for accepting group join request" - | otherwise -> do - let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo - mem <- acceptGroupJoinRequestAsync user uclId gInfo invId chatVRange p xContactId_ Nothing welcomeMsgId_ acceptance useRole profileMode Nothing Nothing - (gInfo', mem', scopeInfo) <- mkGroupChatScope gInfo mem - createInternalChatItem user (CDGroupRcv gInfo' scopeInfo mem') (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing - toView $ CEvtAcceptingGroupJoinRequestMember user gInfo' mem' + Right (acceptance, useRole) -> do + let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo + mem <- acceptGroupJoinRequestAsync user uclId gInfo invId chatVRange p xContactId_ Nothing welcomeMsgId_ acceptance useRole profileMode Nothing Nothing + (gInfo', mem', scopeInfo) <- mkGroupChatScope gInfo mem + createInternalChatItem user (CDGroupRcv gInfo' scopeInfo mem') (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing + toView $ CEvtAcceptingGroupJoinRequestMember user gInfo' mem' Left rjctReason | v < groupJoinRejectVersion -> messageWarning $ "processContactConnMessage (group " <> groupName' gInfo <> "): joining of " <> displayName <> " is blocked" @@ -1657,7 +1605,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = withStore $ \db -> do Connection {connId = testCId} <- createRelayTestConnection db cxt user acId ConnAccepted chatV subMode liftIO $ setCommandConnId db user cmdId testCId - agentAcceptContactAsync cmdId acId True invId msg PQSupportOff chatV subMode + agentAcceptContactAsync cmdId acId True invId msg PQSupportOff subMode | otherwise = messageError "relay test sent to non-relay link" where User {userChatRelay} = user @@ -2822,10 +2770,8 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = maybeCreateGroupDescrLocal :: GroupInfo -> GroupMember -> CM () maybeCreateGroupDescrLocal gInfo@GroupInfo {groupProfile = GroupProfile {description}} m = - unless expectHistory $ forM_ description $ \descr -> + unless (groupFeatureAllowed SGFHistory gInfo) $ forM_ description $ \descr -> createInternalChatItem user (CDGroupRcv gInfo Nothing m) (CIRcvMsgContent $ MCText descr) Nothing - where - expectHistory = groupFeatureAllowed SGFHistory gInfo && m `supportsVersion` groupHistoryIncludeWelcomeVersion processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Maybe (RcvMessage, UTCTime) -> CM GroupMember processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' msgTs_ @@ -3223,16 +3169,14 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole c) case memChatVRange of Nothing -> messageError "x.grp.mem.intro: member chat version range incompatible" - Just (ChatVersionRange mcvr) - | maxVersion mcvr >= groupDirectInvVersion -> do - subMode <- chatReadVar subscriptionMode - groupConnIds@(cmdId, connId) <- prepareAgentCreation user CFCreateConnGrpMemInv (chatHasNtfs chatSettings) SCMInvitation - let chatV = maybe (minVersion (vr cxt)) (\peerVR -> vr cxt `peerConnChatVersion` fromChatVRange peerVR) memChatVRange - void $ withStore $ \db -> do - reMember <- createIntroReMember db cxt user gInfo memInfo memRestrictions - createIntroReMemberConn db user m reMember chatV memInfo groupConnIds subMode - withAgent $ \a -> createConnectionAsync a (aCorrId cmdId) connId (chatHasNtfs chatSettings) SCMInvitation CR.IKPQOff subMode - | otherwise -> messageError "x.grp.mem.intro: member chat version range incompatible" + Just _ -> do + subMode <- chatReadVar subscriptionMode + groupConnIds@(cmdId, connId) <- prepareAgentCreation user CFCreateConnGrpMemInv (chatHasNtfs chatSettings) SCMInvitation + let chatV = maybe (minVersion (vr cxt)) (\peerVR -> vr cxt `peerConnChatVersion` fromChatVRange peerVR) memChatVRange + void $ withStore $ \db -> do + reMember <- createIntroReMember db cxt user gInfo memInfo memRestrictions + createIntroReMemberConn db user m reMember chatV memInfo groupConnIds subMode + withAgent $ \a -> createConnectionAsync a (aCorrId cmdId) connId (chatHasNtfs chatSettings) SCMInvitation CR.IKPQOff subMode _ -> messageError "x.grp.mem.intro can be only sent by host member" sendXGrpMemInv :: Int64 -> Maybe ConnReqInvitation -> XGrpMemIntroCont -> CM () diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 19ba3d022d..8d9c8ecfcc 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -56,7 +56,6 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import qualified Simplex.FileTransfer.Description as FD -import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion) import Simplex.Messaging.Agent.Store.DB (blobFieldDecoder, fromTextField_) import Simplex.Messaging.Compression (Compressed, compress1, decompress1, decompressedSize) import qualified Simplex.Messaging.Crypto as C @@ -98,38 +97,6 @@ supportedChatVRange :: VersionRangeChat supportedChatVRange = mkVersionRange initialChatVersion currentChatVersion {-# INLINE supportedChatVRange #-} --- version range that supports skipping establishing direct connections in a group and establishing direct connection via x.grp.direct.inv -groupDirectInvVersion :: VersionChat -groupDirectInvVersion = VersionChat 2 - --- version range that supports joining group via group link without creating direct contact -groupFastLinkJoinVersion :: VersionChat -groupFastLinkJoinVersion = VersionChat 3 - --- version range that supports group forwarding -groupForwardVersion :: VersionChat -groupForwardVersion = VersionChat 4 - --- version range that supports batch sending in groups -batchSendVersion :: VersionChat -batchSendVersion = VersionChat 5 - --- version range that supports sending group welcome message in group history -groupHistoryIncludeWelcomeVersion :: VersionChat -groupHistoryIncludeWelcomeVersion = VersionChat 6 - --- version range that supports sending member profile updates to groups -memberProfileUpdateVersion :: VersionChat -memberProfileUpdateVersion = VersionChat 7 - --- version range that supports compressing messages and PQ e2e encryption -pqEncryptionCompressionVersion :: VersionChat -pqEncryptionCompressionVersion = VersionChat 8 - --- version range that supports batch sending in direct connections, and forwarding batched messages in groups -batchSend2Version :: VersionChat -batchSend2Version = VersionChat 9 - -- supports differentiating business chats when joining contact addresses businessChatsVersion :: VersionChat businessChatsVersion = VersionChat 10 @@ -167,11 +134,6 @@ relayWebCapVersion = VersionChat 18 groupRosterVersion :: VersionChat groupRosterVersion = VersionChat 19 -agentToChatVersion :: VersionSMPA -> VersionChat -agentToChatVersion v - | v < pqdrSMPAgentVersion = initialChatVersion - | otherwise = pqEncryptionCompressionVersion - data ConnectionEntity = RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact} | RcvGroupMsgConnection {entityConnection :: Connection, groupInfo :: GroupInfo, groupMember :: GroupMember} diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index d607541bda..620eb0cc7c 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -27,7 +27,6 @@ module Simplex.Chat.Store.Groups getGroupLinkConnection, deleteGroupLink, getGroupLink, - getGroupLinkId, setGroupLinkMemberRole, setGroupLinkShortLink, createNewGroup, @@ -129,13 +128,11 @@ module Simplex.Chat.Store.Groups getRelayServedGroups, getRelayPublishableGroups, getRelayInactiveGroups, - createNewContactMemberAsync, createJoiningMember, getMemberJoinRequest, createJoiningMemberConnection, createBusinessRequestGroup, getContactViaMember, - setNewContactMemberConnRequest, getMemberInvitation, createMemberConnection, createMemberConnectionAsync, @@ -354,11 +351,6 @@ getGroupLink db User {userId} gInfo@GroupInfo {groupId} = acceptMemberRole = fromMaybe GRMember mRole_ } -getGroupLinkId :: DB.Connection -> User -> GroupInfo -> IO (Maybe GroupLinkId) -getGroupLinkId db User {userId} GroupInfo {groupId} = - fmap join . maybeFirstRow fromOnly $ - DB.query db "SELECT group_link_id FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId) - setGroupLinkMemberRole :: DB.Connection -> User -> GroupLink -> GroupMemberRole -> IO GroupLink setGroupLinkMemberRole db User {userId} gLnk@GroupLink{userContactLinkId} memberRole = do DB.execute db "UPDATE user_contact_links SET group_link_member_role = ? WHERE user_id = ? AND user_contact_link_id = ?" (memberRole, userId, userContactLinkId) @@ -2038,33 +2030,6 @@ getRelayInactiveGroups db cxt User {userId, userContactId} ttl = do ) (userId, userContactId, RSInactive, cutoffTs) -createNewContactMemberAsync :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> Contact -> GroupMemberRole -> (CommandId, ConnId) -> VersionChat -> VersionRangeChat -> SubscriptionMode -> ExceptT StoreError IO () -createNewContactMemberAsync db gVar user@User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile} memberRole (cmdId, agentConnId) chatV peerChatVRange subMode = - createWithRandomId' db gVar $ \memId -> runExceptT $ do - createdAt <- liftIO getCurrentTime - insertMember_ (MemberId memId) createdAt - groupMemberId <- liftIO $ insertedRowId db - Connection {connId} <- liftIO $ createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange Nothing 0 createdAt subMode - liftIO $ setCommandConnId db user cmdId connId - where - VersionRange minV maxV = peerChatVRange - insertMember_ memberId createdAt = do - indexInGroup <- getUpdateNextIndexInGroup_ db groupId - liftIO $ - DB.execute - db - [sql| - INSERT INTO group_members - ( group_id, index_in_group, member_id, member_role, member_category, member_status, member_relations_vector, invited_by, invited_by_group_member_id, - user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at, - peer_chat_min_version, peer_chat_max_version) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) - |] - ( (groupId, indexInGroup, memberId, memberRole, GCInviteeMember, GSMemInvited, Binary B.empty, fromInvitedBy userContactId IBUser, groupMemberId' membership) - :. (userId, localDisplayName, contactId, localProfileId profile, createdAt, createdAt) - :. (minV, maxV) - ) - createJoiningMember :: DB.Connection -> StoreCxt -> TVar ChaChaDRG -> User -> GroupInfo -> VersionRangeChat -> Profile -> Maybe XContactId -> Maybe MemberId -> Maybe SharedMsgId -> GroupMemberRole -> GroupMemberStatus -> Maybe MemberKey -> ExceptT StoreError IO (GroupMemberId, MemberId) createJoiningMember db @@ -2224,11 +2189,6 @@ getContactViaMember db cxt user@User {userId} GroupMember {groupMemberId} = do (userId, groupMemberId) getContact db cxt user contactId -setNewContactMemberConnRequest :: DB.Connection -> User -> GroupMember -> ConnReqInvitation -> IO () -setNewContactMemberConnRequest db User {userId} GroupMember {groupMemberId} connRequest = do - currentTs <- getCurrentTime - DB.execute db "UPDATE group_members SET sent_inv_queue_info = ?, updated_at = ? WHERE user_id = ? AND group_member_id = ?" (connRequest, currentTs, userId, groupMemberId) - getMemberInvitation :: DB.Connection -> User -> Int64 -> IO (Maybe ConnReqInvitation) getMemberInvitation db User {userId} groupMemberId = fmap join . maybeFirstRow fromOnly $ diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 14e2299514..72903e7922 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -4767,13 +4767,6 @@ Query: Plan: -Query: - INSERT INTO chat_item_moderations - (group_id, moderator_member_id, item_member_id, shared_msg_id, created_by_msg_id, moderated_at) - VALUES (?,?,?,?,?,?) - -Plan: - Query: INSERT INTO chat_item_versions (chat_item_id, msg_content, item_version_ts) VALUES (?,?,?) @@ -6477,10 +6470,6 @@ Query: DELETE FROM chat_item_mentions WHERE chat_item_id = ? Plan: SEARCH chat_item_mentions USING COVERING INDEX idx_chat_item_mentions_chat_item_id (chat_item_id=?) -Query: DELETE FROM chat_item_moderations WHERE group_id = ? AND item_member_id = ? AND shared_msg_id = ? -Plan: -SEARCH chat_item_moderations USING COVERING INDEX idx_chat_item_moderations_group (group_id=? AND item_member_id=? AND shared_msg_id=?) - Query: DELETE FROM chat_item_reactions WHERE contact_id = ? Plan: SEARCH chat_item_reactions USING COVERING INDEX idx_chat_item_reactions_contact_id (contact_id=?) @@ -7170,6 +7159,10 @@ Plan: SCAN m USING COVERING INDEX idx_group_members_user_id_local_display_name SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) +Query: SELECT chat_item_id FROM chat_items WHERE group_scope_tag = 'member_support' ORDER BY chat_item_id DESC LIMIT 1 +Plan: +SCAN chat_items + Query: SELECT chat_item_id FROM chat_items WHERE item_text LIKE '%' || ? || '%' ORDER BY chat_item_id DESC LIMIT 1 Plan: SCAN chat_items diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index dce1a2a9c9..63a3315d6d 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -2071,7 +2071,7 @@ instance TextEncoding CommandStatus where data CommandFunction = CFCreateConnGrpMemInv - | CFCreateConnGrpInv + | CFCreateConnGrpInv -- deprecated | CFCreateConnFileInvDirect -- deprecated | CFCreateConnFileInvGroup -- deprecated | CFJoinConn @@ -2204,7 +2204,7 @@ peerConnChatVersion _local@(VersionRange lmin lmax) _peer@(VersionRange rmin rma | otherwise = rmax initialChatVersion :: VersionChat -initialChatVersion = VersionChat 1 +initialChatVersion = VersionChat 9 chatInitialVRange :: VersionRangeChat chatInitialVRange = versionToRange initialChatVersion diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 3cf7554cf5..bdc708b069 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -445,7 +445,6 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} CEvtGroupChatItemsDeleted u g ciIds byUser member_ -> ttyUser u $ viewGroupChatItemsDeleted g ciIds byUser member_ CEvtChatItemDeletedNotFound u Contact {localDisplayName = c} _ -> ttyUser u [ttyFrom $ c <> "> [deleted - original message not found]"] CEvtUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."] - CEvtSentGroupInvitation u g c _ -> ttyUser u $ viewSentGroupInvitation g c CEvtContactDeletedByContact u c -> ttyUser u [ttyFullContact c <> " deleted contact with you"] CEvtAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c CEvtAcceptingBusinessRequest u g -> ttyUser u $ viewAcceptingBusinessRequest g diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 77d91bf522..2e859dd68c 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -37,7 +37,6 @@ import Simplex.Chat.Library.Commands import Simplex.Chat.Operators import Simplex.Chat.Options import Simplex.Chat.Options.DB -import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion) import Simplex.Chat.Store import Simplex.Chat.Store.Profiles import Simplex.Chat.Terminal @@ -51,7 +50,7 @@ import Simplex.FileTransfer.Server.Store import Simplex.FileTransfer.Transport (alpnSupportedXFTPhandshakes, supportedFileServerVRange) import Simplex.Messaging.Agent (disposeAgentClient) import Simplex.Messaging.Agent.Env.SQLite -import Simplex.Messaging.Agent.Protocol (currentSMPAgentVersion, duplexHandshakeSMPAgentVersion, pqdrSMPAgentVersion, supportedSMPAgentVRange) +import Simplex.Messaging.Agent.Protocol (duplexHandshakeSMPAgentVersion, pqdrSMPAgentVersion, supportedSMPAgentVRange) import Simplex.Messaging.Agent.RetryInterval import Simplex.Messaging.Agent.Store.Entity (SDBStored (..)) import Simplex.Messaging.Agent.Store.Interface (closeDBStore) @@ -232,21 +231,12 @@ testAgentCfgVPrev = smpCfg = (smpCfg testAgentCfg) {serverVRange = prevRange $ serverVRange $ smpCfg testAgentCfg} } -testAgentCfgVNext :: AgentConfig -testAgentCfgVNext = - testAgentCfg - { smpClientVRange = nextRange $ smpClientVRange testAgentCfg, - smpAgentVRange = mkVersionRange duplexHandshakeSMPAgentVersion $ max pqdrSMPAgentVersion currentSMPAgentVersion, - e2eEncryptVRange = mkVersionRange CR.kdfX3DHE2EEncryptVersion $ max CR.pqRatchetE2EEncryptVersion CR.currentE2EEncryptVersion, - smpCfg = (smpCfg testAgentCfg) {serverVRange = nextRange $ serverVRange $ smpCfg testAgentCfg} - } - testAgentCfgV1 :: AgentConfig testAgentCfgV1 = testAgentCfg { smpClientVRange = v1Range, - smpAgentVRange = versionToRange duplexHandshakeSMPAgentVersion, - e2eEncryptVRange = versionToRange CR.kdfX3DHE2EEncryptVersion, + smpAgentVRange = mkVersionRange duplexHandshakeSMPAgentVersion pqdrSMPAgentVersion, + e2eEncryptVRange = mkVersionRange CR.kdfX3DHE2EEncryptVersion CR.pqRatchetE2EEncryptVersion, smpCfg = (smpCfg testAgentCfg) {serverVRange = versionToRange minClientSMPRelayVersion} } @@ -257,17 +247,10 @@ testCfgVPrev = agentConfig = testAgentCfgVPrev } -testCfgVNext :: ChatConfig -testCfgVNext = - testCfg - { chatVRange = mkVersionRange initialChatVersion $ max pqEncryptionCompressionVersion currentChatVersion, - agentConfig = testAgentCfgVNext - } - testCfgV1 :: ChatConfig testCfgV1 = testCfg - { chatVRange = v1Range, + { chatVRange = chatInitialVRange, agentConfig = testAgentCfgV1 } diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index e34f071fd0..f37587164b 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -3323,17 +3323,17 @@ testUpdatePeerChatVRange ps = cfg11 = testCfg {chatVRange = vr11} :: ChatConfig vr11 :: VersionRangeChat -vr11 = mkVersionRange (VersionChat 1) (VersionChat 1) +vr11 = mkVersionRange (VersionChat 9) (VersionChat 9) contactInfoChatVRange :: TestCC -> VersionRangeChat -> IO () -contactInfoChatVRange cc (VersionRange minVer maxVer) = do +contactInfoChatVRange cc vr = do cc <## "contact ID: 2" cc <## "receiving messages via: localhost" cc <## "sending messages via: localhost" cc <## "you've shared main profile with this contact" cc <## "connection not verified, use /code command to see security code" cc <## "quantum resistant end-to-end encryption" - cc <## ("peer chat protocol version range: (" <> show minVer <> ", " <> show maxVer <> ")") + cc <## ("peer chat protocol version range: " <> vRangeStr vr) testLinkContentFilter :: HasCallStack => TestParams -> IO () testLinkContentFilter = diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 37827ab2ad..684655c946 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -97,8 +97,8 @@ chatGroupTests = do it "moderate own message (should process as deletion)" testGroupModerateOwn it "moderate multiple messages" testGroupModerateMultiple it "moderate message of another group member (full delete)" testGroupModerateFullDelete - it "moderate message that arrives after the event of moderation" testGroupDelayedModeration - it "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete + xit "moderate message that arrives after the event of moderation" testGroupDelayedModeration + xit "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete it "remove member with messages (full deletion is enabled)" testDeleteMemberWithMessages it "remove member with messages mark deleted" testDeleteMemberMarkMessagesDeleted it "remove member - delete messages of left/removed members" testDeleteMemberMessagesLeftRemoved @@ -2990,7 +2990,7 @@ testGroupLinkMemberRole = bob <## "#team: you joined the group" ] - threadDelay 100000 + threadDelay 250000 alice ##> "/ms team" alice diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 4b82779b0e..c9f3864a82 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -26,7 +26,7 @@ import qualified Data.Map.Strict as M import Simplex.Chat.Badges (BadgeCredential, BadgeInfo (..), BadgePurchase (..), BadgeRequest (..), BadgeType (..), generateMasterKey, issueBadge, verifyPayment) import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatHooks (..), defaultChatHooks, mkStoreCxt) import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..)) -import Simplex.Chat.Protocol (LinkOwnerSig, MsgChatLink (..), MsgContent (..), currentChatVersion) +import Simplex.Chat.Protocol (LinkOwnerSig, MsgChatLink (..), MsgContent (..)) import Simplex.Chat.Store.Shared (createContact) import Simplex.Chat.Types (ConnStatus (..), Profile (..), GroupRejectionReason (..), profileFromName) import qualified Simplex.Messaging.Crypto as C @@ -3040,7 +3040,7 @@ testSetUITheme = a <## "you've shared main profile with this contact" a <## "connection not verified, use /code command to see security code" a <## "quantum resistant end-to-end encryption" - a <## ("peer chat protocol version range: (Version 1, " <> show currentChatVersion <> ")") + a <## currentChatVRangeInfo groupInfo a = do a <## "group ID: 1" a <## "current members: 1" diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index df2bff8ab6..805c3bd155 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -127,9 +127,9 @@ versionTestMatrix2 runTest = do it "prev" $ runTestCfg2 testCfgVPrev testCfgVPrev (runTest False True) it "prev to curr" $ runTestCfg2 testCfg testCfgVPrev (runTest False True) it "curr to prev" $ runTestCfg2 testCfgVPrev testCfg (runTest False True) - it "old (1st supported)" $ testChatCfg2 testCfgV1 aliceProfile bobProfile (runTest False False) - it "old to curr" $ runTestCfg2 testCfg testCfgV1 (runTest False True) - it "curr to old" $ runTestCfg2 testCfgV1 testCfg (runTest False False) + it "old (1st supported)" $ testChatCfg2 testCfgV1 aliceProfile bobProfile (runTest True False) + it "old to curr" $ runTestCfg2 testCfg testCfgV1 (runTest True True) + it "curr to old" $ runTestCfg2 testCfgV1 testCfg (runTest True False) versionTestMatrix3 :: (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> SpecWith TestParams versionTestMatrix3 runTest = do diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 798ab3c4d7..d64ab82d23 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -135,64 +135,64 @@ shortLinkDataTests = describe "Short link data encoding/decoding" $ do decodeChatMessageTest :: Spec decodeChatMessageTest = describe "Chat message encoding/decoding" $ do it "x.msg.new simple text" $ - "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" #==# XMsgNew (mcSimple (MCText "hello")) it "x.msg.new simple text - timed message TTL" $ - "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"ttl\":3600}}" + "{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"ttl\":3600}}" #==# XMsgNew ((mcSimple (MCText "hello")) {ttl = Just 3600}) it "x.msg.new simple text - live message" $ - "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"live\":true}}" + "{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"live\":true}}" #==# XMsgNew ((mcSimple (MCText "hello")) {live = Just True}) it "x.msg.new simple link" $ - "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"https://simplex.chat\",\"type\":\"link\",\"preview\":{\"description\":\"SimpleX Chat\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA\",\"title\":\"SimpleX Chat\",\"uri\":\"https://simplex.chat\"}}}}" + "{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"https://simplex.chat\",\"type\":\"link\",\"preview\":{\"description\":\"SimpleX Chat\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA\",\"title\":\"SimpleX Chat\",\"uri\":\"https://simplex.chat\"}}}}" #==# XMsgNew (mcSimple (MCLink "https://simplex.chat" $ LinkPreview {uri = "https://simplex.chat", title = "SimpleX Chat", description = "SimpleX Chat", image = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA", content = Nothing})) it "x.msg.new simple image" $ - "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}}" + "{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}}" #==# XMsgNew (mcSimple (MCImage "" $ ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=")) it "x.msg.new simple image with text" $ - "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"here's an image\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}}" + "{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"here's an image\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}}" #==# XMsgNew (mcSimple (MCImage "here's an image" $ ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=")) it "x.msg.new chat message" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (mcSimple (MCText "hello"))) it "x.msg.new chat message with chat version range" $ - "{\"v\":\"1-19\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"9-19\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" ##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (mcSimple (MCText "hello"))) it "x.msg.new quote" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (mcQuote quotedMsg (MCText "hello to you too"))) it "x.msg.new quote - timed message TTL" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}},\"ttl\":3600}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}},\"ttl\":3600}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew ((mcQuote quotedMsg (MCText "hello to you too")) {ttl = Just 3600})) it "x.msg.new quote - live message" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}},\"live\":true}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}},\"live\":true}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew ((mcQuote quotedMsg (MCText "hello to you too")) {live = Just True})) it "x.msg.new forward" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ mcForward (MCText "hello")) it "x.msg.new forward - timed message TTL" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"ttl\":3600}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"ttl\":3600}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ (mcForward (MCText "hello")) {ttl = Just 3600}) it "x.msg.new forward - live message" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"live\":true}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"live\":true}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ (mcForward (MCText "hello")) {live = Just True}) it "x.msg.new simple text with file" $ - "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" + "{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" #==# XMsgNew ((mcSimple (MCText "hello")) {file = Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing}}) it "x.msg.new simple file with file" $ - "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"type\":\"file\"},\"file\":{\"fileSize\":12345,\"fileName\":\"file.txt\"}}}" + "{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"type\":\"file\"},\"file\":{\"fileSize\":12345,\"fileName\":\"file.txt\"}}}" #==# XMsgNew ((mcSimple (MCFile "")) {file = Just FileInvitation {fileName = "file.txt", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing}}) it "x.msg.new quote with file" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") @@ -201,134 +201,134 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do {file = Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing}} ) it "x.msg.new report" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"reason\":\"spam\",\"type\":\"report\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"reason\":\"spam\",\"type\":\"report\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (mcQuote quotedMsg (MCReport "" RRSpam))) it "x.msg.new forward with file" $ - "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"},\"forward\":true}}" + "{\"v\":\"9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"},\"forward\":true}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ (mcForward (MCText "hello")) {file = Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing}}) it "x.msg.update" $ - "{\"v\":\"1\",\"event\":\"x.msg.update\",\"params\":{\"msgId\":\"AQIDBA==\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"9\",\"event\":\"x.msg.update\",\"params\":{\"msgId\":\"AQIDBA==\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" #==# XMsgUpdate (SharedMsgId "\1\2\3\4") (MCText "hello") [] Nothing Nothing Nothing Nothing it "x.msg.del" $ - "{\"v\":\"1\",\"event\":\"x.msg.del\",\"params\":{\"msgId\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.msg.del\",\"params\":{\"msgId\":\"AQIDBA==\"}}" #==# XMsgDel (SharedMsgId "\1\2\3\4") Nothing Nothing False it "x.msg.deleted" $ - "{\"v\":\"1\",\"event\":\"x.msg.deleted\",\"params\":{}}" + "{\"v\":\"9\",\"event\":\"x.msg.deleted\",\"params\":{}}" #==# XMsgDeleted it "x.file" $ - "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" + "{\"v\":\"9\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" #==# XFile FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Just testConnReq, fileInline = Nothing, fileDescr = Nothing} it "x.file without file invitation" $ - "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" + "{\"v\":\"9\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" #==# XFile FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing} it "x.file.acpt" $ - "{\"v\":\"1\",\"event\":\"x.file.acpt\",\"params\":{\"fileName\":\"photo.jpg\"}}" + "{\"v\":\"9\",\"event\":\"x.file.acpt\",\"params\":{\"fileName\":\"photo.jpg\"}}" #==# XFileAcpt "photo.jpg" it "x.file.acpt.inv" $ - "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" + "{\"v\":\"9\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" #==# XFileAcptInv (SharedMsgId "\1\2\3\4") (Just testConnReq) "photo.jpg" it "x.file.acpt.inv" $ - "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\"}}" + "{\"v\":\"9\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\"}}" #==# XFileAcptInv (SharedMsgId "\1\2\3\4") Nothing "photo.jpg" it "x.file.cancel" $ - "{\"v\":\"1\",\"event\":\"x.file.cancel\",\"params\":{\"msgId\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.file.cancel\",\"params\":{\"msgId\":\"AQIDBA==\"}}" #==# XFileCancel (SharedMsgId "\1\2\3\4") it "x.info" $ - "{\"v\":\"1\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" + "{\"v\":\"9\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" #==# XInfo testProfile it "x.info with empty full name" $ - "{\"v\":\"1\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" + "{\"v\":\"9\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" #==# XInfo Profile {displayName = "alice", fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = testChatPreferences, badge = Nothing, contactDomain = Nothing} it "x.contact with xContactId" $ - "{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"contactReqId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" + "{\"v\":\"9\",\"event\":\"x.contact\",\"params\":{\"contactReqId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" #==# XContact testProfile (Just $ XContactId "\1\2\3\4") Nothing Nothing it "x.contact without XContactId" $ - "{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" + "{\"v\":\"9\",\"event\":\"x.contact\",\"params\":{\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" #==# XContact testProfile Nothing Nothing Nothing it "x.contact with content null" $ - "{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"content\":null,\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" + "{\"v\":\"9\",\"event\":\"x.contact\",\"params\":{\"content\":null,\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" ==# XContact testProfile Nothing Nothing Nothing it "x.contact with content" $ - "{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"msgId\":\"AQIDBA==\",\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" + "{\"v\":\"9\",\"event\":\"x.contact\",\"params\":{\"msgId\":\"AQIDBA==\",\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" ==# XContact testProfile Nothing Nothing (Just (SharedMsgId "\1\2\3\4", MCText {text = "hello"})) it "x.grp.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Nothing, groupSize = Nothing} it "x.grp.inv with group link id" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" + "{\"v\":\"9\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} it "x.grp.acpt without incognito profile" $ - "{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}" #==# XGrpAcpt (MemberId "\1\2\3\4") it "x.grp.mem.new" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile, memberKey = Nothing} Nothing it "x.grp.mem.new with member chat version range" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-19\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"9-19\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile, memberKey = Nothing} Nothing it "x.grp.mem.intro" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile, memberKey = Nothing} Nothing it "x.grp.mem.intro with member chat version range" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-19\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"9-19\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile, memberKey = Nothing} Nothing it "x.grp.mem.intro with member restrictions" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile, memberKey = Nothing} (Just MemberRestrictions {restriction = MRSBlocked}) it "x.grp.mem.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" #==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.inv w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" #==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.fwd" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile, memberKey = Nothing} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-19\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"9-19\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile, memberKey = Nothing} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.info" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" #==# XGrpMemInfo (MemberId "\1\2\3\4") testProfile it "x.grp.mem.con" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.con\",\"params\":{\"memberId\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.con\",\"params\":{\"memberId\":\"AQIDBA==\"}}" #==# XGrpMemCon (MemberId "\1\2\3\4") it "x.grp.mem.con.all" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.con.all\",\"params\":{\"memberId\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.con.all\",\"params\":{\"memberId\":\"AQIDBA==\"}}" #==# XGrpMemConAll (MemberId "\1\2\3\4") it "x.grp.mem.del" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.del\",\"params\":{\"memberId\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.grp.mem.del\",\"params\":{\"memberId\":\"AQIDBA==\"}}" #==# XGrpMemDel (MemberId "\1\2\3\4") False Nothing it "x.grp.leave" $ - "{\"v\":\"1\",\"event\":\"x.grp.leave\",\"params\":{}}" + "{\"v\":\"9\",\"event\":\"x.grp.leave\",\"params\":{}}" ==# XGrpLeave it "x.grp.del" $ - "{\"v\":\"1\",\"event\":\"x.grp.del\",\"params\":{}}" + "{\"v\":\"9\",\"event\":\"x.grp.del\",\"params\":{}}" ==# XGrpDel it "x.grp.direct.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"9\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" #==# XGrpDirectInv testConnReq (Just $ MCText "hello") Nothing it "x.grp.direct.inv without content" $ - "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" + "{\"v\":\"9\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" #==# XGrpDirectInv testConnReq Nothing Nothing -- it "x.grp.msg.forward" - -- $ "{\"v\":\"1\",\"event\":\"x.grp.msg.forward\",\"params\":{\"msgForward\":{\"memberId\":\"AQIDBA==\",\"msg\":\"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}\",\"msgTs\":\"1970-01-01T00:00:01.000000001Z\"}}}" + -- $ "{\"v\":\"9\",\"event\":\"x.grp.msg.forward\",\"params\":{\"msgForward\":{\"memberId\":\"AQIDBA==\",\"msg\":\"{\"v\":\"9\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}\",\"msgTs\":\"1970-01-01T00:00:01.000000001Z\"}}}" -- #==# XGrpMsgForward -- (MemberId "\1\2\3\4") -- (ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (mcSimple (MCText "hello")))) -- (systemToUTCTime $ MkSystemTime 1 1) it "x.info.probe" $ - "{\"v\":\"1\",\"event\":\"x.info.probe\",\"params\":{\"probe\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.info.probe\",\"params\":{\"probe\":\"AQIDBA==\"}}" #==# XInfoProbe (Probe "\1\2\3\4") it "x.info.probe.check" $ - "{\"v\":\"1\",\"event\":\"x.info.probe.check\",\"params\":{\"probeHash\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.info.probe.check\",\"params\":{\"probeHash\":\"AQIDBA==\"}}" #==# XInfoProbeCheck (ProbeHash "\1\2\3\4") it "x.info.probe.ok" $ - "{\"v\":\"1\",\"event\":\"x.info.probe.ok\",\"params\":{\"probe\":\"AQIDBA==\"}}" + "{\"v\":\"9\",\"event\":\"x.info.probe.ok\",\"params\":{\"probe\":\"AQIDBA==\"}}" #==# XInfoProbeOk (Probe "\1\2\3\4") it "x.ok" $ - "{\"v\":\"1\",\"event\":\"x.ok\",\"params\":{}}" + "{\"v\":\"9\",\"event\":\"x.ok\",\"params\":{}}" ==# XOk