Merge pull request #2995 from simplex-chat/chat-version-negotiation
core: communicate connection chat version range; don't create direct connections in group (disabled)
This commit is contained in:
commit
5e8e4c295c
@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
|||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/simplexmq.git
|
location: https://github.com/simplex-chat/simplexmq.git
|
||||||
tag: 980e5c4d1ec15f44290542fd2a5d1c08456f00d1
|
tag: 351f42650c57f310fc1ea858ff9b7178823f1fd4
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
@ -3,24 +3,31 @@ sequenceDiagram
|
|||||||
participant A as Alice
|
participant A as Alice
|
||||||
participant B as Bob
|
participant B as Bob
|
||||||
participant C as Existing<br>contact
|
participant C as Existing<br>contact
|
||||||
|
|
||||||
note over A, B: 1. send and accept group invitation
|
note over A, B: 1. send and accept group invitation
|
||||||
A ->> B: x.grp.inv<br>invite Bob to group<br>(via contact connection)
|
A ->> B: x.grp.inv<br>invite Bob to group<br>(via contact connection)
|
||||||
B ->> A: x.grp.acpt<br>accept invitation<br>(via member connection)
|
B ->> A: x.grp.acpt<br>accept invitation<br>(via member connection)<br>establish group member connection
|
||||||
B ->> A: establish group member connection
|
|
||||||
|
|
||||||
note over M, B: 2. introduce new member Bob to all existing members
|
note over M, B: 2. introduce new member Bob to all existing members
|
||||||
A ->> M: x.grp.mem.new<br>"announce" Bob<br>to existing members<br>(via member connections)
|
A ->> M: x.grp.mem.new<br>"announce" Bob<br>to existing members<br>(via member connections)
|
||||||
A ->> B: x.grp.mem.intro * N<br>"introduce" members<br>(via member connection)
|
loop batched
|
||||||
B ->> A: x.grp.mem.inv * N<br>"invitations" to connect<br>for all members<br>(via member connection)
|
A ->> B: x.grp.mem.intro * N<br>"introduce" members and<br>their chat protocol versions<br>(via member connection)
|
||||||
A ->> M: x.grp.mem.fwd<br>forward "invitations"<br>to all members<br>(via member connections)
|
note over B: prepare group member connections
|
||||||
|
opt chat protocol compatible version < 2
|
||||||
|
note over B: prepare direct connections
|
||||||
|
end
|
||||||
|
B ->> A: x.grp.mem.inv * N<br>"invitations" to connect<br>for all members<br>(via member connection)
|
||||||
|
end
|
||||||
|
A ->> M: x.grp.mem.fwd<br>forward "invitations" and<br>Bob's chat protocol version<br>to all members<br>(via member connections)
|
||||||
|
|
||||||
note over M, B: 3. establish direct and group member connections
|
note over M, B: 3. establish direct and group member connections
|
||||||
M ->> B: establish group member connection
|
M ->> B: establish group member connection
|
||||||
M ->> B: establish direct connection
|
|
||||||
|
|
||||||
note over M, C: 4. deduplicate new contact
|
opt chat protocol compatible version < 2
|
||||||
B ->> M: x.info.probe<br>"probe" is sent to all new members
|
M ->> B: establish direct connection
|
||||||
B ->> C: x.info.probe.check<br>"probe" hash,<br>in case contact and<br>member profiles match
|
note over M, C: 4. deduplicate new contact
|
||||||
C ->> B: x.info.probe.ok<br> original "probe",<br> in case contact and member<br>are the same user
|
B ->> M: x.info.probe<br>"probe" is sent to all new members
|
||||||
note over B: merge existing and new contacts if received and sent probe hashes match
|
B ->> C: x.info.probe.check<br>"probe" hash,<br>in case contact and<br>member profiles match
|
||||||
|
C ->> B: x.info.probe.ok<br> original "probe",<br> in case contact and member<br>are the same user
|
||||||
|
note over B: merge existing and new contacts if received and sent probe hashes match
|
||||||
|
end
|
||||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."980e5c4d1ec15f44290542fd2a5d1c08456f00d1" = "1lqciyy215dvmbhykyp80bwipqmxybv39p6jff6vjgd5r34958nh";
|
"https://github.com/simplex-chat/simplexmq.git"."351f42650c57f310fc1ea858ff9b7178823f1fd4" = "12r13yc0qk9dkii58808862wraqrk66rzmkrgyp6lg1xrazrd0d2";
|
||||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||||
"https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb";
|
"https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb";
|
||||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd";
|
"https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd";
|
||||||
|
@ -109,6 +109,7 @@ library
|
|||||||
Simplex.Chat.Migrations.M20230721_group_snd_item_statuses
|
Simplex.Chat.Migrations.M20230721_group_snd_item_statuses
|
||||||
Simplex.Chat.Migrations.M20230814_indexes
|
Simplex.Chat.Migrations.M20230814_indexes
|
||||||
Simplex.Chat.Migrations.M20230827_file_encryption
|
Simplex.Chat.Migrations.M20230827_file_encryption
|
||||||
|
Simplex.Chat.Migrations.M20230829_connections_chat_vrange
|
||||||
Simplex.Chat.Mobile
|
Simplex.Chat.Mobile
|
||||||
Simplex.Chat.Mobile.File
|
Simplex.Chat.Mobile.File
|
||||||
Simplex.Chat.Mobile.Shared
|
Simplex.Chat.Mobile.Shared
|
||||||
|
@ -94,6 +94,7 @@ import qualified Simplex.Messaging.Protocol as SMP
|
|||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
import Simplex.Messaging.Transport.Client (defaultSocksProxy)
|
import Simplex.Messaging.Transport.Client (defaultSocksProxy)
|
||||||
import Simplex.Messaging.Util
|
import Simplex.Messaging.Util
|
||||||
|
import Simplex.Messaging.Version
|
||||||
import System.Exit (exitFailure, exitSuccess)
|
import System.Exit (exitFailure, exitSuccess)
|
||||||
import System.FilePath (combine, splitExtensions, takeFileName, (</>))
|
import System.FilePath (combine, splitExtensions, takeFileName, (</>))
|
||||||
import System.IO (Handle, IOMode (..), SeekMode (..), hFlush, stdout)
|
import System.IO (Handle, IOMode (..), SeekMode (..), hFlush, stdout)
|
||||||
@ -113,6 +114,7 @@ defaultChatConfig =
|
|||||||
{ tcpPort = undefined, -- agent does not listen to TCP
|
{ tcpPort = undefined, -- agent does not listen to TCP
|
||||||
tbqSize = 1024
|
tbqSize = 1024
|
||||||
},
|
},
|
||||||
|
chatVRange = supportedChatVRange,
|
||||||
confirmMigrations = MCConsole,
|
confirmMigrations = MCConsole,
|
||||||
defaultServers =
|
defaultServers =
|
||||||
DefaultAgentServers
|
DefaultAgentServers
|
||||||
@ -1296,7 +1298,8 @@ processChatCommand = \case
|
|||||||
-- [incognito] generate profile to send
|
-- [incognito] generate profile to send
|
||||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||||
let profileToSend = userProfileToSend user incognitoProfile Nothing
|
let profileToSend = userProfileToSend user incognitoProfile Nothing
|
||||||
connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq . directMessage $ XInfo profileToSend
|
dm <- directMessage $ XInfo profileToSend
|
||||||
|
connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq dm
|
||||||
conn <- withStore' $ \db -> createDirectConnection db user connId cReq ConnJoined $ incognitoProfile $> profileToSend
|
conn <- withStore' $ \db -> createDirectConnection db user connId cReq ConnJoined $ incognitoProfile $> profileToSend
|
||||||
toView $ CRNewContactConnection user conn
|
toView $ CRNewContactConnection user conn
|
||||||
pure $ CRSentConfirmation user
|
pure $ CRSentConfirmation user
|
||||||
@ -1434,11 +1437,16 @@ processChatCommand = \case
|
|||||||
Nothing -> throwChatError $ CEGroupCantResendInvitation gInfo cName
|
Nothing -> throwChatError $ CEGroupCantResendInvitation gInfo cName
|
||||||
| otherwise -> throwChatError $ CEGroupDuplicateMember cName
|
| otherwise -> throwChatError $ CEGroupDuplicateMember cName
|
||||||
APIJoinGroup groupId -> withUser $ \user@User {userId} -> do
|
APIJoinGroup groupId -> withUser $ \user@User {userId} -> do
|
||||||
ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} <- withStore $ \db -> getGroupInvitation db user groupId
|
(invitation, ct) <- withStore $ \db -> do
|
||||||
|
inv@ReceivedGroupInvitation {fromMember} <- getGroupInvitation db user groupId
|
||||||
|
(inv,) <$> getContactViaMember db user fromMember
|
||||||
|
let ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} = invitation
|
||||||
|
Contact {activeConn = Connection {peerChatVRange}} = ct
|
||||||
withChatLock "joinGroup" . procCmd $ do
|
withChatLock "joinGroup" . procCmd $ do
|
||||||
agentConnId <- withAgent $ \a -> joinConnection a (aUserId user) True connRequest . directMessage $ XGrpAcpt (memberId (membership :: GroupMember))
|
dm <- directMessage $ XGrpAcpt (memberId (membership :: GroupMember))
|
||||||
|
agentConnId <- withAgent $ \a -> joinConnection a (aUserId user) True connRequest dm
|
||||||
withStore' $ \db -> do
|
withStore' $ \db -> do
|
||||||
createMemberConnection db userId fromMember agentConnId
|
createMemberConnection db userId fromMember agentConnId peerChatVRange
|
||||||
updateGroupMemberStatus db userId fromMember GSMemAccepted
|
updateGroupMemberStatus db userId fromMember GSMemAccepted
|
||||||
updateGroupMemberStatus db userId membership GSMemAccepted
|
updateGroupMemberStatus db userId membership GSMemAccepted
|
||||||
updateCIGroupInvitationStatus user
|
updateCIGroupInvitationStatus user
|
||||||
@ -1840,7 +1848,8 @@ processChatCommand = \case
|
|||||||
-- [incognito] generate profile to send
|
-- [incognito] generate profile to send
|
||||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||||
let profileToSend = userProfileToSend user incognitoProfile Nothing
|
let profileToSend = userProfileToSend user incognitoProfile Nothing
|
||||||
connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq $ directMessage (XContact profileToSend $ Just xContactId)
|
dm <- directMessage (XContact profileToSend $ Just xContactId)
|
||||||
|
connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq dm
|
||||||
let groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli
|
let groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli
|
||||||
conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId
|
conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId
|
||||||
toView $ CRNewContactConnection user conn
|
toView $ CRNewContactConnection user conn
|
||||||
@ -1857,7 +1866,7 @@ processChatCommand = \case
|
|||||||
xftpCfg <- readTVarIO =<< asks userXFTPFileConfig
|
xftpCfg <- readTVarIO =<< asks userXFTPFileConfig
|
||||||
fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cfArgs
|
fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cfArgs
|
||||||
when (fromInteger fileSize > maxFileSize) $ throwChatError $ CEFileSize f
|
when (fromInteger fileSize > maxFileSize) $ throwChatError $ CEFileSize f
|
||||||
let chunks = -((-fileSize) `div` fileChunkSize)
|
let chunks = - ((- fileSize) `div` fileChunkSize)
|
||||||
fileInline = inlineFileMode mc inlineFiles chunks n
|
fileInline = inlineFileMode mc inlineFiles chunks n
|
||||||
fileMode = case xftpCfg of
|
fileMode = case xftpCfg of
|
||||||
Just cfg
|
Just cfg
|
||||||
@ -2231,7 +2240,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI
|
|||||||
case (xftpRcvFile, fileConnReq) of
|
case (xftpRcvFile, fileConnReq) of
|
||||||
-- direct file protocol
|
-- direct file protocol
|
||||||
(Nothing, Just connReq) -> do
|
(Nothing, Just connReq) -> do
|
||||||
connIds <- joinAgentConnectionAsync user True connReq . directMessage $ XFileAcpt fName
|
connIds <- joinAgentConnectionAsync user True connReq =<< directMessage (XFileAcpt fName)
|
||||||
filePath <- getRcvFilePath fileId filePath_ fName True
|
filePath <- getRcvFilePath fileId filePath_ fName True
|
||||||
withStoreCtx (Just "acceptFileReceive, acceptRcvFileTransfer") $ \db -> acceptRcvFileTransfer db user fileId connIds ConnJoined filePath
|
withStoreCtx (Just "acceptFileReceive, acceptRcvFileTransfer") $ \db -> acceptRcvFileTransfer db user fileId connIds ConnJoined filePath
|
||||||
-- XFTP
|
-- XFTP
|
||||||
@ -2346,17 +2355,18 @@ getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of
|
|||||||
in ifM (doesFileExist f) (tryCombine $ n + 1) (pure f)
|
in ifM (doesFileExist f) (tryCombine $ n + 1) (pure f)
|
||||||
|
|
||||||
acceptContactRequest :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> m Contact
|
acceptContactRequest :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> m Contact
|
||||||
acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId invId, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId} incognitoProfile = do
|
acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId} incognitoProfile = do
|
||||||
let profileToSend = profileToSendOnAccept user incognitoProfile
|
let profileToSend = profileToSendOnAccept user incognitoProfile
|
||||||
acId <- withAgent $ \a -> acceptContact a True invId . directMessage $ XInfo profileToSend
|
dm <- directMessage $ XInfo profileToSend
|
||||||
withStore' $ \db -> createAcceptedContact db user acId cName profileId cp userContactLinkId xContactId incognitoProfile
|
acId <- withAgent $ \a -> acceptContact a True invId dm
|
||||||
|
withStore' $ \db -> createAcceptedContact db user acId cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile
|
||||||
|
|
||||||
acceptContactRequestAsync :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> m Contact
|
acceptContactRequestAsync :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> m Contact
|
||||||
acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvId invId, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile = do
|
acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile = do
|
||||||
let profileToSend = profileToSendOnAccept user incognitoProfile
|
let profileToSend = profileToSendOnAccept user incognitoProfile
|
||||||
(cmdId, acId) <- agentAcceptContactAsync user True invId $ XInfo profileToSend
|
(cmdId, acId) <- agentAcceptContactAsync user True invId $ XInfo profileToSend
|
||||||
withStore' $ \db -> do
|
withStore' $ \db -> do
|
||||||
ct@Contact {activeConn = Connection {connId}} <- createAcceptedContact db user acId cName profileId p userContactLinkId xContactId incognitoProfile
|
ct@Contact {activeConn = Connection {connId}} <- createAcceptedContact db user acId cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile
|
||||||
setCommandConnId db user cmdId connId
|
setCommandConnId db user cmdId connId
|
||||||
pure ct
|
pure ct
|
||||||
|
|
||||||
@ -2556,7 +2566,7 @@ cleanupManager = do
|
|||||||
`catchChatError` (toView . CRChatError (Just user))
|
`catchChatError` (toView . CRChatError (Just user))
|
||||||
cleanupMessages = do
|
cleanupMessages = do
|
||||||
ts <- liftIO getCurrentTime
|
ts <- liftIO getCurrentTime
|
||||||
let cutoffTs = addUTCTime (-(30 * nominalDay)) ts
|
let cutoffTs = addUTCTime (- (30 * nominalDay)) ts
|
||||||
withStoreCtx' (Just "cleanupManager, deleteOldMessages") (`deleteOldMessages` cutoffTs)
|
withStoreCtx' (Just "cleanupManager, deleteOldMessages") (`deleteOldMessages` cutoffTs)
|
||||||
|
|
||||||
startProximateTimedItemThread :: ChatMonad m => User -> (ChatRef, ChatItemId) -> UTCTime -> m ()
|
startProximateTimedItemThread :: ChatMonad m => User -> (ChatRef, ChatItemId) -> UTCTime -> m ()
|
||||||
@ -2840,21 +2850,23 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
processDirectMessage :: ACommand 'Agent e -> ConnectionEntity -> Connection -> Maybe Contact -> m ()
|
processDirectMessage :: ACommand 'Agent e -> ConnectionEntity -> Connection -> Maybe Contact -> m ()
|
||||||
processDirectMessage agentMsg connEntity conn@Connection {connId, viaUserContactLink, groupLinkId, customUserProfileId, connectionCode} = \case
|
processDirectMessage agentMsg connEntity conn@Connection {connId, peerChatVRange, viaUserContactLink, groupLinkId, customUserProfileId, connectionCode} = \case
|
||||||
Nothing -> case agentMsg of
|
Nothing -> case agentMsg of
|
||||||
CONF confId _ connInfo -> do
|
CONF confId _ connInfo -> do
|
||||||
-- [incognito] send saved profile
|
-- [incognito] send saved profile
|
||||||
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
||||||
let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing
|
let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing
|
||||||
saveConnInfo conn connInfo
|
conn' <- saveConnInfo conn connInfo
|
||||||
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||||
allowAgentConnectionAsync user conn confId $ XInfo profileToSend
|
allowAgentConnectionAsync user conn' confId $ XInfo profileToSend
|
||||||
INFO connInfo ->
|
INFO connInfo -> do
|
||||||
saveConnInfo conn connInfo
|
_conn' <- saveConnInfo conn connInfo
|
||||||
|
pure ()
|
||||||
MSG meta _msgFlags msgBody -> do
|
MSG meta _msgFlags msgBody -> do
|
||||||
cmdId <- createAckCmd conn
|
cmdId <- createAckCmd conn
|
||||||
withAckMessage agentConnId cmdId meta $
|
withAckMessage agentConnId cmdId meta $ do
|
||||||
saveRcvMSG conn (ConnectionId connId) meta msgBody cmdId $> False
|
(_conn', _) <- saveRcvMSG conn (ConnectionId connId) meta msgBody cmdId
|
||||||
|
pure False
|
||||||
SENT msgId ->
|
SENT msgId ->
|
||||||
sentMsgDeliveryEvent conn msgId
|
sentMsgDeliveryEvent conn msgId
|
||||||
OK ->
|
OK ->
|
||||||
@ -2879,54 +2891,57 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
setConnConnReqInv db user connId cReq
|
setConnConnReqInv db user connId cReq
|
||||||
getXGrpMemIntroContDirect db user ct
|
getXGrpMemIntroContDirect db user ct
|
||||||
forM_ contData $ \(hostConnId, xGrpMemIntroCont) ->
|
forM_ contData $ \(hostConnId, xGrpMemIntroCont) ->
|
||||||
sendXGrpMemInv hostConnId directConnReq xGrpMemIntroCont
|
sendXGrpMemInv hostConnId (Just directConnReq) xGrpMemIntroCont
|
||||||
CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type"
|
CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type"
|
||||||
MSG msgMeta _msgFlags msgBody -> do
|
MSG msgMeta _msgFlags msgBody -> do
|
||||||
cmdId <- createAckCmd conn
|
cmdId <- createAckCmd conn
|
||||||
withAckMessage agentConnId cmdId msgMeta $ do
|
withAckMessage agentConnId cmdId msgMeta $ do
|
||||||
msg@RcvMessage {chatMsgEvent = ACME _ event} <- saveRcvMSG conn (ConnectionId connId) msgMeta msgBody cmdId
|
(conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveRcvMSG conn (ConnectionId connId) msgMeta msgBody cmdId
|
||||||
assertDirectAllowed user MDRcv ct $ toCMEventTag event
|
let ct' = ct {activeConn = conn'} :: Contact
|
||||||
|
assertDirectAllowed user MDRcv ct' $ toCMEventTag event
|
||||||
updateChatLock "directMessage" event
|
updateChatLock "directMessage" event
|
||||||
case event of
|
case event of
|
||||||
XMsgNew mc -> newContentMessage ct mc msg msgMeta
|
XMsgNew mc -> newContentMessage ct' mc msg msgMeta
|
||||||
XMsgFileDescr sharedMsgId fileDescr -> messageFileDescription ct sharedMsgId fileDescr msgMeta
|
XMsgFileDescr sharedMsgId fileDescr -> messageFileDescription ct' sharedMsgId fileDescr msgMeta
|
||||||
XMsgFileCancel sharedMsgId -> cancelMessageFile ct sharedMsgId msgMeta
|
XMsgFileCancel sharedMsgId -> cancelMessageFile ct' sharedMsgId msgMeta
|
||||||
XMsgUpdate sharedMsgId mContent ttl live -> messageUpdate ct sharedMsgId mContent msg msgMeta ttl live
|
XMsgUpdate sharedMsgId mContent ttl live -> messageUpdate ct' sharedMsgId mContent msg msgMeta ttl live
|
||||||
XMsgDel sharedMsgId _ -> messageDelete ct sharedMsgId msg msgMeta
|
XMsgDel sharedMsgId _ -> messageDelete ct' sharedMsgId msg msgMeta
|
||||||
XMsgReact sharedMsgId _ reaction add -> directMsgReaction ct sharedMsgId reaction add msg msgMeta
|
XMsgReact sharedMsgId _ reaction add -> directMsgReaction ct' sharedMsgId reaction add msg msgMeta
|
||||||
-- TODO discontinue XFile
|
-- TODO discontinue XFile
|
||||||
XFile fInv -> processFileInvitation' ct fInv msg msgMeta
|
XFile fInv -> processFileInvitation' ct' fInv msg msgMeta
|
||||||
XFileCancel sharedMsgId -> xFileCancel ct sharedMsgId msgMeta
|
XFileCancel sharedMsgId -> xFileCancel ct' sharedMsgId msgMeta
|
||||||
XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInv ct sharedMsgId fileConnReq_ fName msgMeta
|
XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInv ct' sharedMsgId fileConnReq_ fName msgMeta
|
||||||
XInfo p -> xInfo ct p
|
XInfo p -> xInfo ct' p
|
||||||
XGrpInv gInv -> processGroupInvitation ct gInv msg msgMeta
|
XGrpInv gInv -> processGroupInvitation ct' gInv msg msgMeta
|
||||||
XInfoProbe probe -> xInfoProbe ct probe
|
XInfoProbe probe -> xInfoProbe ct' probe
|
||||||
XInfoProbeCheck probeHash -> xInfoProbeCheck ct probeHash
|
XInfoProbeCheck probeHash -> xInfoProbeCheck ct' probeHash
|
||||||
XInfoProbeOk probe -> xInfoProbeOk ct probe
|
XInfoProbeOk probe -> xInfoProbeOk ct' probe
|
||||||
XCallInv callId invitation -> xCallInv ct callId invitation msg msgMeta
|
XCallInv callId invitation -> xCallInv ct' callId invitation msg msgMeta
|
||||||
XCallOffer callId offer -> xCallOffer ct callId offer msg msgMeta
|
XCallOffer callId offer -> xCallOffer ct' callId offer msg msgMeta
|
||||||
XCallAnswer callId answer -> xCallAnswer ct callId answer msg msgMeta
|
XCallAnswer callId answer -> xCallAnswer ct' callId answer msg msgMeta
|
||||||
XCallExtra callId extraInfo -> xCallExtra ct callId extraInfo msg msgMeta
|
XCallExtra callId extraInfo -> xCallExtra ct' callId extraInfo msg msgMeta
|
||||||
XCallEnd callId -> xCallEnd ct callId msg msgMeta
|
XCallEnd callId -> xCallEnd ct' callId msg msgMeta
|
||||||
BFileChunk sharedMsgId chunk -> bFileChunk ct sharedMsgId chunk msgMeta
|
BFileChunk sharedMsgId chunk -> bFileChunk ct' sharedMsgId chunk msgMeta
|
||||||
_ -> messageError $ "unsupported message: " <> T.pack (show event)
|
_ -> messageError $ "unsupported message: " <> T.pack (show event)
|
||||||
let Contact {chatSettings = ChatSettings {sendRcpts}} = ct
|
let Contact {chatSettings = ChatSettings {sendRcpts}} = ct'
|
||||||
pure $ fromMaybe (sendRcptsContacts user) sendRcpts && hasDeliveryReceipt (toCMEventTag event)
|
pure $ fromMaybe (sendRcptsContacts user) sendRcpts && hasDeliveryReceipt (toCMEventTag event)
|
||||||
RCVD msgMeta msgRcpt ->
|
RCVD msgMeta msgRcpt ->
|
||||||
withAckMessage' agentConnId conn msgMeta $
|
withAckMessage' agentConnId conn msgMeta $
|
||||||
directMsgReceived ct conn msgMeta msgRcpt
|
directMsgReceived ct conn msgMeta msgRcpt
|
||||||
CONF confId _ connInfo -> do
|
CONF confId _ connInfo -> do
|
||||||
-- confirming direct connection with a member
|
-- confirming direct connection with a member
|
||||||
ChatMessage {chatMsgEvent} <- parseChatMessage conn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo
|
||||||
|
conn' <- updatePeerChatVRange conn chatVRange
|
||||||
case chatMsgEvent of
|
case chatMsgEvent of
|
||||||
XGrpMemInfo _memId _memProfile -> do
|
XGrpMemInfo _memId _memProfile -> do
|
||||||
-- TODO check member ID
|
-- TODO check member ID
|
||||||
-- TODO update member profile
|
-- TODO update member profile
|
||||||
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||||
allowAgentConnectionAsync user conn confId XOk
|
allowAgentConnectionAsync user conn' confId XOk
|
||||||
_ -> messageError "CONF from member must have x.grp.mem.info"
|
_ -> messageError "CONF from member must have x.grp.mem.info"
|
||||||
INFO connInfo -> do
|
INFO connInfo -> do
|
||||||
ChatMessage {chatMsgEvent} <- parseChatMessage conn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo
|
||||||
|
_conn' <- updatePeerChatVRange conn chatVRange
|
||||||
case chatMsgEvent of
|
case chatMsgEvent of
|
||||||
XGrpMemInfo _memId _memProfile -> do
|
XGrpMemInfo _memId _memProfile -> do
|
||||||
-- TODO check member ID
|
-- TODO check member ID
|
||||||
@ -2958,7 +2973,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
forM_ groupId_ $ \groupId -> do
|
forM_ groupId_ $ \groupId -> do
|
||||||
gVar <- asks idsDrg
|
gVar <- asks idsDrg
|
||||||
groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpInv True SCMInvitation
|
groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpInv True SCMInvitation
|
||||||
withStore $ \db -> createNewContactMemberAsync db gVar user groupId ct gLinkMemRole groupConnIds
|
withStore $ \db -> createNewContactMemberAsync db gVar user groupId ct gLinkMemRole groupConnIds peerChatVRange
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
Just (gInfo@GroupInfo {membership}, m@GroupMember {activeConn}) ->
|
Just (gInfo@GroupInfo {membership}, m@GroupMember {activeConn}) ->
|
||||||
when (maybe False ((== ConnReady) . connStatus) activeConn) $ do
|
when (maybe False ((== ConnReady) . connStatus) activeConn) $ do
|
||||||
@ -3025,22 +3040,30 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
case cReq of
|
case cReq of
|
||||||
groupConnReq@(CRInvitationUri _ _) -> case cmdFunction of
|
groupConnReq@(CRInvitationUri _ _) -> case cmdFunction of
|
||||||
-- [async agent commands] XGrpMemIntro continuation on receiving INV
|
-- [async agent commands] XGrpMemIntro continuation on receiving INV
|
||||||
CFCreateConnGrpMemInv -> do
|
CFCreateConnGrpMemInv
|
||||||
contData <- withStore' $ \db -> do
|
| isCompatibleRange (peerChatVRange conn) groupNoDirectVRange -> sendWithDirectCReq -- sendWithoutDirectCReq
|
||||||
setConnConnReqInv db user connId cReq
|
| otherwise -> sendWithDirectCReq
|
||||||
getXGrpMemIntroContGroup db user m
|
where
|
||||||
forM_ contData $ \(hostConnId, directConnReq) -> do
|
sendWithoutDirectCReq = do
|
||||||
let GroupMember {groupMemberId, memberId} = m
|
let GroupMember {groupMemberId, memberId} = m
|
||||||
sendXGrpMemInv hostConnId directConnReq XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq}
|
hostConnId <- withStore $ \db -> do
|
||||||
|
liftIO $ setConnConnReqInv db user connId cReq
|
||||||
|
getHostConnId db user groupId
|
||||||
|
sendXGrpMemInv hostConnId Nothing XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq}
|
||||||
|
sendWithDirectCReq = do
|
||||||
|
let GroupMember {groupMemberId, memberId} = m
|
||||||
|
contData <- withStore' $ \db -> do
|
||||||
|
setConnConnReqInv db user connId cReq
|
||||||
|
getXGrpMemIntroContGroup db user m
|
||||||
|
forM_ contData $ \(hostConnId, directConnReq) ->
|
||||||
|
sendXGrpMemInv hostConnId (Just directConnReq) XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq}
|
||||||
-- [async agent commands] group link auto-accept continuation on receiving INV
|
-- [async agent commands] group link auto-accept continuation on receiving INV
|
||||||
CFCreateConnGrpInv ->
|
CFCreateConnGrpInv -> do
|
||||||
withStore' (\db -> getContactViaMember db user m) >>= \case
|
ct <- withStore $ \db -> getContactViaMember db user m
|
||||||
Nothing -> messageError "implementation error: invitee does not have contact"
|
withStore' $ \db -> setNewContactMemberConnRequest db user m cReq
|
||||||
Just ct -> do
|
groupLinkId <- withStore' $ \db -> getGroupLinkId db user gInfo
|
||||||
withStore' $ \db -> setNewContactMemberConnRequest db user m cReq
|
sendGrpInvitation ct m groupLinkId
|
||||||
groupLinkId <- withStore' $ \db -> getGroupLinkId db user gInfo
|
toView $ CRSentGroupInvitation user gInfo ct m
|
||||||
sendGrpInvitation ct m groupLinkId
|
|
||||||
toView $ CRSentGroupInvitation user gInfo ct m
|
|
||||||
where
|
where
|
||||||
sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> m ()
|
sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> m ()
|
||||||
sendGrpInvitation ct GroupMember {memberId, memberRole = memRole} groupLinkId = do
|
sendGrpInvitation ct GroupMember {memberId, memberRole = memRole} groupLinkId = do
|
||||||
@ -3052,7 +3075,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
_ -> throwChatError $ CECommandError "unexpected cmdFunction"
|
_ -> throwChatError $ CECommandError "unexpected cmdFunction"
|
||||||
CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type"
|
CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type"
|
||||||
CONF confId _ connInfo -> do
|
CONF confId _ connInfo -> do
|
||||||
ChatMessage {chatMsgEvent} <- parseChatMessage conn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo
|
||||||
|
conn' <- updatePeerChatVRange conn chatVRange
|
||||||
case memberCategory m of
|
case memberCategory m of
|
||||||
GCInviteeMember ->
|
GCInviteeMember ->
|
||||||
case chatMsgEvent of
|
case chatMsgEvent of
|
||||||
@ -3060,7 +3084,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
| sameMemberId memId m -> do
|
| sameMemberId memId m -> do
|
||||||
withStore $ \db -> liftIO $ updateGroupMemberStatus db userId m GSMemAccepted
|
withStore $ \db -> liftIO $ updateGroupMemberStatus db userId m GSMemAccepted
|
||||||
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||||
allowAgentConnectionAsync user conn confId XOk
|
allowAgentConnectionAsync user conn' confId XOk
|
||||||
| otherwise -> messageError "x.grp.acpt: memberId is different from expected"
|
| otherwise -> messageError "x.grp.acpt: memberId is different from expected"
|
||||||
_ -> messageError "CONF from invited member must have x.grp.acpt"
|
_ -> messageError "CONF from invited member must have x.grp.acpt"
|
||||||
_ ->
|
_ ->
|
||||||
@ -3069,11 +3093,12 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
| sameMemberId memId m -> do
|
| sameMemberId memId m -> do
|
||||||
-- TODO update member profile
|
-- TODO update member profile
|
||||||
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||||
allowAgentConnectionAsync user conn confId $ XGrpMemInfo (memberId (membership :: GroupMember)) (fromLocalProfile $ memberProfile membership)
|
allowAgentConnectionAsync user conn' confId $ XGrpMemInfo (memberId (membership :: GroupMember)) (fromLocalProfile $ memberProfile membership)
|
||||||
| otherwise -> messageError "x.grp.mem.info: memberId is different from expected"
|
| otherwise -> messageError "x.grp.mem.info: memberId is different from expected"
|
||||||
_ -> messageError "CONF from member must have x.grp.mem.info"
|
_ -> messageError "CONF from member must have x.grp.mem.info"
|
||||||
INFO connInfo -> do
|
INFO connInfo -> do
|
||||||
ChatMessage {chatMsgEvent} <- parseChatMessage conn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo
|
||||||
|
_conn' <- updatePeerChatVRange conn chatVRange
|
||||||
case chatMsgEvent of
|
case chatMsgEvent of
|
||||||
XGrpMemInfo memId _memProfile
|
XGrpMemInfo memId _memProfile
|
||||||
| sameMemberId memId m -> do
|
| sameMemberId memId m -> do
|
||||||
@ -3114,7 +3139,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
processIntro intro `catchChatError` (toView . CRChatError (Just user))
|
processIntro intro `catchChatError` (toView . CRChatError (Just user))
|
||||||
where
|
where
|
||||||
processIntro intro@GroupMemberIntro {introId} = do
|
processIntro intro@GroupMemberIntro {introId} = do
|
||||||
void $ sendDirectMessage conn (XGrpMemIntro . memberInfo $ reMember intro) (GroupId groupId)
|
void $ sendDirectMessage conn (XGrpMemIntro $ memberInfo (reMember intro)) (GroupId groupId)
|
||||||
withStore' $ \db -> updateIntroStatus db introId GMIntroSent
|
withStore' $ \db -> updateIntroStatus db introId GMIntroSent
|
||||||
_ -> do
|
_ -> do
|
||||||
-- TODO send probe and decide whether to use existing contact connection or the new contact connection
|
-- TODO send probe and decide whether to use existing contact connection or the new contact connection
|
||||||
@ -3131,28 +3156,29 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
MSG msgMeta _msgFlags msgBody -> do
|
MSG msgMeta _msgFlags msgBody -> do
|
||||||
cmdId <- createAckCmd conn
|
cmdId <- createAckCmd conn
|
||||||
withAckMessage agentConnId cmdId msgMeta $ do
|
withAckMessage agentConnId cmdId msgMeta $ do
|
||||||
msg@RcvMessage {chatMsgEvent = ACME _ event} <- saveRcvMSG conn (GroupId groupId) msgMeta msgBody cmdId
|
(conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveRcvMSG conn (GroupId groupId) msgMeta msgBody cmdId
|
||||||
|
let m' = m {activeConn = Just conn'} :: GroupMember
|
||||||
updateChatLock "groupMessage" event
|
updateChatLock "groupMessage" event
|
||||||
case event of
|
case event of
|
||||||
XMsgNew mc -> canSend $ newGroupContentMessage gInfo m mc msg msgMeta
|
XMsgNew mc -> canSend m' $ newGroupContentMessage gInfo m' mc msg msgMeta
|
||||||
XMsgFileDescr sharedMsgId fileDescr -> canSend $ groupMessageFileDescription gInfo m sharedMsgId fileDescr msgMeta
|
XMsgFileDescr sharedMsgId fileDescr -> canSend m' $ groupMessageFileDescription gInfo m' sharedMsgId fileDescr msgMeta
|
||||||
XMsgFileCancel sharedMsgId -> cancelGroupMessageFile gInfo m sharedMsgId msgMeta
|
XMsgFileCancel sharedMsgId -> cancelGroupMessageFile gInfo m' sharedMsgId msgMeta
|
||||||
XMsgUpdate sharedMsgId mContent ttl live -> canSend $ groupMessageUpdate gInfo m sharedMsgId mContent msg msgMeta ttl live
|
XMsgUpdate sharedMsgId mContent ttl live -> canSend m' $ groupMessageUpdate gInfo m' sharedMsgId mContent msg msgMeta ttl live
|
||||||
XMsgDel sharedMsgId memberId -> groupMessageDelete gInfo m sharedMsgId memberId msg msgMeta
|
XMsgDel sharedMsgId memberId -> groupMessageDelete gInfo m' sharedMsgId memberId msg msgMeta
|
||||||
XMsgReact sharedMsgId (Just memberId) reaction add -> groupMsgReaction gInfo m sharedMsgId memberId reaction add msg msgMeta
|
XMsgReact sharedMsgId (Just memberId) reaction add -> groupMsgReaction gInfo m' sharedMsgId memberId reaction add msg msgMeta
|
||||||
-- TODO discontinue XFile
|
-- TODO discontinue XFile
|
||||||
XFile fInv -> processGroupFileInvitation' gInfo m fInv msg msgMeta
|
XFile fInv -> processGroupFileInvitation' gInfo m' fInv msg msgMeta
|
||||||
XFileCancel sharedMsgId -> xFileCancelGroup gInfo m sharedMsgId msgMeta
|
XFileCancel sharedMsgId -> xFileCancelGroup gInfo m' sharedMsgId msgMeta
|
||||||
XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInvGroup gInfo m sharedMsgId fileConnReq_ fName msgMeta
|
XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInvGroup gInfo m' sharedMsgId fileConnReq_ fName msgMeta
|
||||||
XGrpMemNew memInfo -> xGrpMemNew gInfo m memInfo msg msgMeta
|
XGrpMemNew memInfo -> xGrpMemNew gInfo m' memInfo msg msgMeta
|
||||||
XGrpMemIntro memInfo -> xGrpMemIntro gInfo m memInfo
|
XGrpMemIntro memInfo -> xGrpMemIntro gInfo m' memInfo
|
||||||
XGrpMemInv memId introInv -> xGrpMemInv gInfo m memId introInv
|
XGrpMemInv memId introInv -> xGrpMemInv gInfo m' memId introInv
|
||||||
XGrpMemFwd memInfo introInv -> xGrpMemFwd gInfo m memInfo introInv
|
XGrpMemFwd memInfo introInv -> xGrpMemFwd gInfo m' memInfo introInv
|
||||||
XGrpMemRole memId memRole -> xGrpMemRole gInfo m memId memRole msg msgMeta
|
XGrpMemRole memId memRole -> xGrpMemRole gInfo m' memId memRole msg msgMeta
|
||||||
XGrpMemDel memId -> xGrpMemDel gInfo m memId msg msgMeta
|
XGrpMemDel memId -> xGrpMemDel gInfo m' memId msg msgMeta
|
||||||
XGrpLeave -> xGrpLeave gInfo m msg msgMeta
|
XGrpLeave -> xGrpLeave gInfo m' msg msgMeta
|
||||||
XGrpDel -> xGrpDel gInfo m msg msgMeta
|
XGrpDel -> xGrpDel gInfo m' msg msgMeta
|
||||||
XGrpInfo p' -> xGrpInfo gInfo m p' msg msgMeta
|
XGrpInfo p' -> xGrpInfo gInfo m' p' msg msgMeta
|
||||||
BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta
|
BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta
|
||||||
_ -> messageError $ "unsupported message: " <> T.pack (show event)
|
_ -> messageError $ "unsupported message: " <> T.pack (show event)
|
||||||
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
|
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
|
||||||
@ -3162,8 +3188,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
&& hasDeliveryReceipt (toCMEventTag event)
|
&& hasDeliveryReceipt (toCMEventTag event)
|
||||||
&& currentMemCount <= smallGroupsRcptsMemLimit
|
&& currentMemCount <= smallGroupsRcptsMemLimit
|
||||||
where
|
where
|
||||||
canSend a
|
canSend mem a
|
||||||
| memberRole (m :: GroupMember) <= GRObserver = messageError "member is not allowed to send messages"
|
| memberRole (mem :: GroupMember) <= GRObserver = messageError "member is not allowed to send messages"
|
||||||
| otherwise = a
|
| otherwise = a
|
||||||
RCVD msgMeta msgRcpt ->
|
RCVD msgMeta msgRcpt ->
|
||||||
withAckMessage' agentConnId conn msgMeta $
|
withAckMessage' agentConnId conn msgMeta $
|
||||||
@ -3248,14 +3274,15 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
-- SMP CONF for SndFileConnection happens for direct file protocol
|
-- SMP CONF for SndFileConnection happens for direct file protocol
|
||||||
-- when recipient of the file "joins" connection created by the sender
|
-- when recipient of the file "joins" connection created by the sender
|
||||||
CONF confId _ connInfo -> do
|
CONF confId _ connInfo -> do
|
||||||
ChatMessage {chatMsgEvent} <- parseChatMessage conn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo
|
||||||
|
conn' <- updatePeerChatVRange conn chatVRange
|
||||||
case chatMsgEvent of
|
case chatMsgEvent of
|
||||||
-- TODO save XFileAcpt message
|
-- TODO save XFileAcpt message
|
||||||
XFileAcpt name
|
XFileAcpt name
|
||||||
| name == fileName -> do
|
| name == fileName -> do
|
||||||
withStore' $ \db -> updateSndFileStatus db ft FSAccepted
|
withStore' $ \db -> updateSndFileStatus db ft FSAccepted
|
||||||
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||||
allowAgentConnectionAsync user conn confId XOk
|
allowAgentConnectionAsync user conn' confId XOk
|
||||||
| otherwise -> messageError "x.file.acpt: fileName is different from expected"
|
| otherwise -> messageError "x.file.acpt: fileName is different from expected"
|
||||||
_ -> messageError "CONF from file connection must have x.file.acpt"
|
_ -> messageError "CONF from file connection must have x.file.acpt"
|
||||||
CON -> do
|
CON -> do
|
||||||
@ -3316,9 +3343,10 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
-- when sender of the file "joins" connection created by the recipient
|
-- when sender of the file "joins" connection created by the recipient
|
||||||
-- (sender doesn't create connections for all group members)
|
-- (sender doesn't create connections for all group members)
|
||||||
CONF confId _ connInfo -> do
|
CONF confId _ connInfo -> do
|
||||||
ChatMessage {chatMsgEvent} <- parseChatMessage conn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo
|
||||||
|
conn' <- updatePeerChatVRange conn chatVRange
|
||||||
case chatMsgEvent of
|
case chatMsgEvent of
|
||||||
XOk -> allowAgentConnectionAsync user conn confId XOk -- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
XOk -> allowAgentConnectionAsync user conn' confId XOk -- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
CON -> startReceivingFile user fileId
|
CON -> startReceivingFile user fileId
|
||||||
MSG meta _ msgBody -> do
|
MSG meta _ msgBody -> do
|
||||||
@ -3377,10 +3405,10 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
processUserContactRequest :: ACommand 'Agent e -> ConnectionEntity -> Connection -> UserContact -> m ()
|
processUserContactRequest :: ACommand 'Agent e -> ConnectionEntity -> Connection -> UserContact -> m ()
|
||||||
processUserContactRequest agentMsg connEntity conn UserContact {userContactLinkId} = case agentMsg of
|
processUserContactRequest agentMsg connEntity conn UserContact {userContactLinkId} = case agentMsg of
|
||||||
REQ invId _ connInfo -> do
|
REQ invId _ connInfo -> do
|
||||||
ChatMessage {chatMsgEvent} <- parseChatMessage conn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo
|
||||||
case chatMsgEvent of
|
case chatMsgEvent of
|
||||||
XContact p xContactId_ -> profileContactRequest invId p xContactId_
|
XContact p xContactId_ -> profileContactRequest invId chatVRange p xContactId_
|
||||||
XInfo p -> profileContactRequest invId p Nothing
|
XInfo p -> profileContactRequest invId chatVRange p Nothing
|
||||||
-- TODO show/log error, other events in contact request
|
-- TODO show/log error, other events in contact request
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
MERR _ err -> do
|
MERR _ err -> do
|
||||||
@ -3392,9 +3420,9 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
-- TODO add debugging output
|
-- TODO add debugging output
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
where
|
where
|
||||||
profileContactRequest :: InvitationId -> Profile -> Maybe XContactId -> m ()
|
profileContactRequest :: InvitationId -> VersionRange -> Profile -> Maybe XContactId -> m ()
|
||||||
profileContactRequest invId p xContactId_ = do
|
profileContactRequest invId chatVRange p xContactId_ = do
|
||||||
withStore (\db -> createOrUpdateContactRequest db user userContactLinkId invId p xContactId_) >>= \case
|
withStore (\db -> createOrUpdateContactRequest db user userContactLinkId invId chatVRange p xContactId_) >>= \case
|
||||||
CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact
|
CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact
|
||||||
CORRequest cReq@UserContactRequest {localDisplayName} -> do
|
CORRequest cReq@UserContactRequest {localDisplayName} -> do
|
||||||
withStore' (\db -> getUserContactLinkById db userId userContactLinkId) >>= \case
|
withStore' (\db -> getUserContactLinkById db userId userContactLinkId) >>= \case
|
||||||
@ -3892,7 +3920,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
then unless cancelled $ case fileConnReq_ of
|
then unless cancelled $ case fileConnReq_ of
|
||||||
-- receiving via a separate connection
|
-- receiving via a separate connection
|
||||||
Just fileConnReq -> do
|
Just fileConnReq -> do
|
||||||
connIds <- joinAgentConnectionAsync user True fileConnReq $ directMessage XOk
|
connIds <- joinAgentConnectionAsync user True fileConnReq =<< directMessage XOk
|
||||||
withStore' $ \db -> createSndDirectFTConnection db user fileId connIds
|
withStore' $ \db -> createSndDirectFTConnection db user fileId connIds
|
||||||
-- receiving inline
|
-- receiving inline
|
||||||
_ -> do
|
_ -> do
|
||||||
@ -3989,7 +4017,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
(Just fileConnReq, _) -> do
|
(Just fileConnReq, _) -> do
|
||||||
-- receiving via a separate connection
|
-- receiving via a separate connection
|
||||||
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||||
connIds <- joinAgentConnectionAsync user True fileConnReq $ directMessage XOk
|
connIds <- joinAgentConnectionAsync user True fileConnReq =<< directMessage XOk
|
||||||
withStore' $ \db -> createSndGroupFileTransferConnection db user fileId connIds m
|
withStore' $ \db -> createSndGroupFileTransferConnection db user fileId connIds m
|
||||||
(_, Just conn) -> do
|
(_, Just conn) -> do
|
||||||
-- receiving inline
|
-- receiving inline
|
||||||
@ -4012,7 +4040,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
|
|
||||||
processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> m ()
|
processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> m ()
|
||||||
processGroupInvitation ct inv msg msgMeta = do
|
processGroupInvitation ct inv msg msgMeta = do
|
||||||
let Contact {localDisplayName = c, activeConn = Connection {customUserProfileId, groupLinkId = groupLinkId'}} = ct
|
let Contact {localDisplayName = c, activeConn = Connection {peerChatVRange, customUserProfileId, groupLinkId = groupLinkId'}} = ct
|
||||||
GroupInvitation {fromMember = (MemberIdRole fromMemId fromRole), invitedMember = (MemberIdRole memId memRole), connRequest, groupLinkId} = inv
|
GroupInvitation {fromMember = (MemberIdRole fromMemId fromRole), invitedMember = (MemberIdRole memId memRole), connRequest, groupLinkId} = inv
|
||||||
checkIntegrityCreateItem (CDDirectRcv ct) msgMeta
|
checkIntegrityCreateItem (CDDirectRcv ct) msgMeta
|
||||||
when (fromRole < GRAdmin || fromRole < memRole) $ throwChatError (CEGroupContactRole c)
|
when (fromRole < GRAdmin || fromRole < memRole) $ throwChatError (CEGroupContactRole c)
|
||||||
@ -4021,9 +4049,9 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
(gInfo@GroupInfo {groupId, localDisplayName, groupProfile, membership = membership@GroupMember {groupMemberId, memberId}}, hostId) <- withStore $ \db -> createGroupInvitation db user ct inv customUserProfileId
|
(gInfo@GroupInfo {groupId, localDisplayName, groupProfile, membership = membership@GroupMember {groupMemberId, memberId}}, hostId) <- withStore $ \db -> createGroupInvitation db user ct inv customUserProfileId
|
||||||
if sameGroupLinkId groupLinkId groupLinkId'
|
if sameGroupLinkId groupLinkId groupLinkId'
|
||||||
then do
|
then do
|
||||||
connIds <- joinAgentConnectionAsync user True connRequest . directMessage $ XGrpAcpt memberId
|
connIds <- joinAgentConnectionAsync user True connRequest =<< directMessage (XGrpAcpt memberId)
|
||||||
withStore' $ \db -> do
|
withStore' $ \db -> do
|
||||||
createMemberConnectionAsync db user hostId connIds
|
createMemberConnectionAsync db user hostId connIds peerChatVRange
|
||||||
updateGroupMemberStatusById db userId hostId GSMemAccepted
|
updateGroupMemberStatusById db userId hostId GSMemAccepted
|
||||||
updateGroupMemberStatus db userId membership GSMemAccepted
|
updateGroupMemberStatus db userId membership GSMemAccepted
|
||||||
toView $ CRUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct)
|
toView $ CRUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct)
|
||||||
@ -4223,18 +4251,20 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
withStore' $ \db -> mergeContactRecords db userId c1 c2
|
withStore' $ \db -> mergeContactRecords db userId c1 c2
|
||||||
toView $ CRContactsMerged user c1 c2
|
toView $ CRContactsMerged user c1 c2
|
||||||
|
|
||||||
saveConnInfo :: Connection -> ConnInfo -> m ()
|
saveConnInfo :: Connection -> ConnInfo -> m Connection
|
||||||
saveConnInfo activeConn connInfo = do
|
saveConnInfo activeConn connInfo = do
|
||||||
ChatMessage {chatMsgEvent} <- parseChatMessage activeConn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage activeConn connInfo
|
||||||
|
conn' <- updatePeerChatVRange activeConn chatVRange
|
||||||
case chatMsgEvent of
|
case chatMsgEvent of
|
||||||
XInfo p -> do
|
XInfo p -> do
|
||||||
ct <- withStore $ \db -> createDirectContact db user activeConn p
|
ct <- withStore $ \db -> createDirectContact db user conn' p
|
||||||
toView $ CRContactConnecting user ct
|
toView $ CRContactConnecting user ct
|
||||||
|
pure conn'
|
||||||
-- TODO show/log error, other events in SMP confirmation
|
-- TODO show/log error, other events in SMP confirmation
|
||||||
_ -> pure ()
|
_ -> pure conn'
|
||||||
|
|
||||||
xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> MsgMeta -> m ()
|
xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> MsgMeta -> m ()
|
||||||
xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole memberProfile) msg msgMeta = do
|
xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ memberProfile) msg msgMeta = do
|
||||||
checkHostRole m memRole
|
checkHostRole m memRole
|
||||||
members <- withStore' $ \db -> getGroupMembers db user gInfo
|
members <- withStore' $ \db -> getGroupMembers db user gInfo
|
||||||
unless (sameMemberId memId $ membership gInfo) $
|
unless (sameMemberId memId $ membership gInfo) $
|
||||||
@ -4247,7 +4277,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
toView $ CRJoinedGroupMemberConnecting user gInfo m newMember
|
toView $ CRJoinedGroupMemberConnecting user gInfo m newMember
|
||||||
|
|
||||||
xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> m ()
|
xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> m ()
|
||||||
xGrpMemIntro gInfo@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ _) = do
|
xGrpMemIntro gInfo@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memberChatVRange _) = do
|
||||||
case memberCategory m of
|
case memberCategory m of
|
||||||
GCHostMember -> do
|
GCHostMember -> do
|
||||||
members <- withStore' $ \db -> getGroupMembers db user gInfo
|
members <- withStore' $ \db -> getGroupMembers db user gInfo
|
||||||
@ -4256,14 +4286,19 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
else do
|
else do
|
||||||
when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole c)
|
when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole c)
|
||||||
-- [async agent commands] commands should be asynchronous, continuation is to send XGrpMemInv - have to remember one has completed and process on second
|
-- [async agent commands] commands should be asynchronous, continuation is to send XGrpMemInv - have to remember one has completed and process on second
|
||||||
groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpMemInv enableNtfs SCMInvitation
|
groupConnIds <- createConn
|
||||||
directConnIds <- createAgentConnectionAsync user CFCreateConnGrpMemInv enableNtfs SCMInvitation
|
directConnIds <- case memberChatVRange of
|
||||||
-- [incognito] direct connection with member has to be established using the same incognito profile [that was known to host and used for group membership]
|
Nothing -> Just <$> createConn
|
||||||
|
Just mcvr
|
||||||
|
| isCompatibleRange (fromChatVRange mcvr) groupNoDirectVRange -> Just <$> createConn -- pure Nothing
|
||||||
|
| otherwise -> Just <$> createConn
|
||||||
let customUserProfileId = if memberIncognito membership then Just (localProfileId $ memberProfile membership) else Nothing
|
let customUserProfileId = if memberIncognito membership then Just (localProfileId $ memberProfile membership) else Nothing
|
||||||
void $ withStore $ \db -> createIntroReMember db user gInfo m memInfo groupConnIds directConnIds customUserProfileId
|
void $ withStore $ \db -> createIntroReMember db user gInfo m memInfo groupConnIds directConnIds customUserProfileId
|
||||||
_ -> messageError "x.grp.mem.intro can be only sent by host member"
|
_ -> messageError "x.grp.mem.intro can be only sent by host member"
|
||||||
|
where
|
||||||
|
createConn = createAgentConnectionAsync user CFCreateConnGrpMemInv enableNtfs SCMInvitation
|
||||||
|
|
||||||
sendXGrpMemInv :: Int64 -> ConnReqInvitation -> XGrpMemIntroCont -> m ()
|
sendXGrpMemInv :: Int64 -> Maybe ConnReqInvitation -> XGrpMemIntroCont -> m ()
|
||||||
sendXGrpMemInv hostConnId directConnReq XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} = do
|
sendXGrpMemInv hostConnId directConnReq XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} = do
|
||||||
hostConn <- withStore $ \db -> getConnectionById db user hostConnId
|
hostConn <- withStore $ \db -> getConnectionById db user hostConnId
|
||||||
let msg = XGrpMemInv memberId IntroInvitation {groupConnReq, directConnReq}
|
let msg = XGrpMemInv memberId IntroInvitation {groupConnReq, directConnReq}
|
||||||
@ -4284,7 +4319,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
_ -> messageError "x.grp.mem.inv can be only sent by invitee member"
|
_ -> messageError "x.grp.mem.inv can be only sent by invitee member"
|
||||||
|
|
||||||
xGrpMemFwd :: GroupInfo -> GroupMember -> MemberInfo -> IntroInvitation -> m ()
|
xGrpMemFwd :: GroupInfo -> GroupMember -> MemberInfo -> IntroInvitation -> m ()
|
||||||
xGrpMemFwd gInfo@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}} m memInfo@(MemberInfo memId memRole _) introInv@IntroInvitation {groupConnReq, directConnReq} = do
|
xGrpMemFwd gInfo@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}} m memInfo@(MemberInfo memId memRole memberChatVRange _) introInv@IntroInvitation {groupConnReq, directConnReq} = do
|
||||||
checkHostRole m memRole
|
checkHostRole m memRole
|
||||||
members <- withStore' $ \db -> getGroupMembers db user gInfo
|
members <- withStore' $ \db -> getGroupMembers db user gInfo
|
||||||
toMember <- case find (sameMemberId memId) members of
|
toMember <- case find (sameMemberId memId) members of
|
||||||
@ -4296,12 +4331,13 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
Just m' -> pure m'
|
Just m' -> pure m'
|
||||||
withStore' $ \db -> saveMemberInvitation db toMember introInv
|
withStore' $ \db -> saveMemberInvitation db toMember introInv
|
||||||
-- [incognito] send membership incognito profile, create direct connection as incognito
|
-- [incognito] send membership incognito profile, create direct connection as incognito
|
||||||
let msg = XGrpMemInfo (memberId (membership :: GroupMember)) (fromLocalProfile $ memberProfile membership)
|
dm <- directMessage $ XGrpMemInfo (memberId (membership :: GroupMember)) (fromLocalProfile $ memberProfile membership)
|
||||||
-- [async agent commands] no continuation needed, but commands should be asynchronous for stability
|
-- [async agent commands] no continuation needed, but commands should be asynchronous for stability
|
||||||
groupConnIds <- joinAgentConnectionAsync user enableNtfs groupConnReq $ directMessage msg
|
groupConnIds <- joinAgentConnectionAsync user enableNtfs groupConnReq dm
|
||||||
directConnIds <- joinAgentConnectionAsync user enableNtfs directConnReq $ directMessage msg
|
directConnIds <- forM directConnReq $ \dcr -> joinAgentConnectionAsync user enableNtfs dcr dm
|
||||||
let customUserProfileId = if memberIncognito membership then Just (localProfileId $ memberProfile membership) else Nothing
|
let customUserProfileId = if memberIncognito membership then Just (localProfileId $ memberProfile membership) else Nothing
|
||||||
withStore' $ \db -> createIntroToMemberContact db user m toMember groupConnIds directConnIds customUserProfileId
|
mcvr = maybe chatInitialVRange fromChatVRange memberChatVRange
|
||||||
|
withStore' $ \db -> createIntroToMemberContact db user m toMember mcvr groupConnIds directConnIds customUserProfileId
|
||||||
|
|
||||||
xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> MsgMeta -> m ()
|
xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> MsgMeta -> m ()
|
||||||
xGrpMemRole gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId memRole msg msgMeta
|
xGrpMemRole gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId memRole msg msgMeta
|
||||||
@ -4441,6 +4477,13 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem)
|
toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem)
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
|
|
||||||
|
updatePeerChatVRange :: ChatMonad m => Connection -> VersionRange -> m Connection
|
||||||
|
updatePeerChatVRange conn@Connection {connId, peerChatVRange} msgChatVRange
|
||||||
|
| msgChatVRange /= peerChatVRange = do
|
||||||
|
withStore' $ \db -> setPeerChatVRange db connId msgChatVRange
|
||||||
|
pure conn {peerChatVRange = msgChatVRange}
|
||||||
|
| otherwise = pure conn
|
||||||
|
|
||||||
parseFileDescription :: (ChatMonad m, FilePartyI p) => Text -> m (ValidFileDescription p)
|
parseFileDescription :: (ChatMonad m, FilePartyI p) => Text -> m (ValidFileDescription p)
|
||||||
parseFileDescription =
|
parseFileDescription =
|
||||||
liftEither . first (ChatError . CEInvalidFileDescription) . (strDecode . encodeUtf8)
|
liftEither . first (ChatError . CEInvalidFileDescription) . (strDecode . encodeUtf8)
|
||||||
@ -4639,12 +4682,15 @@ sendDirectMessage conn chatMsgEvent connOrGroupId = do
|
|||||||
createSndMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> ConnOrGroupId -> m SndMessage
|
createSndMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> ConnOrGroupId -> m SndMessage
|
||||||
createSndMessage chatMsgEvent connOrGroupId = do
|
createSndMessage chatMsgEvent connOrGroupId = do
|
||||||
gVar <- asks idsDrg
|
gVar <- asks idsDrg
|
||||||
|
ChatConfig {chatVRange} <- asks config
|
||||||
withStore $ \db -> createNewSndMessage db gVar connOrGroupId $ \sharedMsgId ->
|
withStore $ \db -> createNewSndMessage db gVar connOrGroupId $ \sharedMsgId ->
|
||||||
let msgBody = strEncode ChatMessage {msgId = Just sharedMsgId, chatMsgEvent}
|
let msgBody = strEncode ChatMessage {chatVRange, msgId = Just sharedMsgId, chatMsgEvent}
|
||||||
in NewMessage {chatMsgEvent, msgBody}
|
in NewMessage {chatMsgEvent, msgBody}
|
||||||
|
|
||||||
directMessage :: MsgEncodingI e => ChatMsgEvent e -> ByteString
|
directMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> m ByteString
|
||||||
directMessage chatMsgEvent = strEncode ChatMessage {msgId = Nothing, chatMsgEvent}
|
directMessage chatMsgEvent = do
|
||||||
|
ChatConfig {chatVRange} <- asks config
|
||||||
|
pure $ strEncode ChatMessage {chatVRange, msgId = Nothing, chatMsgEvent}
|
||||||
|
|
||||||
deliverMessage :: ChatMonad m => Connection -> CMEventTag e -> MsgBody -> MessageId -> m Int64
|
deliverMessage :: ChatMonad m => Connection -> CMEventTag e -> MsgBody -> MessageId -> m Int64
|
||||||
deliverMessage conn@Connection {connId} cmEventTag msgBody msgId = do
|
deliverMessage conn@Connection {connId} cmEventTag msgBody msgId = do
|
||||||
@ -4699,15 +4745,17 @@ sendPendingGroupMessages user GroupMember {groupMemberId, localDisplayName} conn
|
|||||||
_ -> throwChatError $ CEGroupMemberIntroNotFound localDisplayName
|
_ -> throwChatError $ CEGroupMemberIntroNotFound localDisplayName
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
|
|
||||||
saveRcvMSG :: ChatMonad m => Connection -> ConnOrGroupId -> MsgMeta -> MsgBody -> CommandId -> m RcvMessage
|
saveRcvMSG :: ChatMonad m => Connection -> ConnOrGroupId -> MsgMeta -> MsgBody -> CommandId -> m (Connection, RcvMessage)
|
||||||
saveRcvMSG conn@Connection {connId} connOrGroupId agentMsgMeta msgBody agentAckCmdId = do
|
saveRcvMSG conn@Connection {connId} connOrGroupId agentMsgMeta msgBody agentAckCmdId = do
|
||||||
ACMsg _ ChatMessage {msgId = sharedMsgId_, chatMsgEvent} <- parseAChatMessage conn agentMsgMeta msgBody
|
ACMsg _ ChatMessage {chatVRange, msgId = sharedMsgId_, chatMsgEvent} <- parseAChatMessage conn agentMsgMeta msgBody
|
||||||
|
conn' <- updatePeerChatVRange conn chatVRange
|
||||||
let agentMsgId = fst $ recipient agentMsgMeta
|
let agentMsgId = fst $ recipient agentMsgMeta
|
||||||
newMsg = NewMessage {chatMsgEvent, msgBody}
|
newMsg = NewMessage {chatMsgEvent, msgBody}
|
||||||
rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId}
|
rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId}
|
||||||
withStoreCtx'
|
msg <- withStoreCtx'
|
||||||
(Just $ "createNewMessageAndRcvMsgDelivery, rcvMsgDelivery: " <> show rcvMsgDelivery <> ", sharedMsgId_: " <> show sharedMsgId_ <> ", msgDeliveryStatus: MDSRcvAgent")
|
(Just $ "createNewMessageAndRcvMsgDelivery, rcvMsgDelivery: " <> show rcvMsgDelivery <> ", sharedMsgId_: " <> show sharedMsgId_ <> ", msgDeliveryStatus: MDSRcvAgent")
|
||||||
$ \db -> createNewMessageAndRcvMsgDelivery db connOrGroupId newMsg sharedMsgId_ rcvMsgDelivery
|
$ \db -> createNewMessageAndRcvMsgDelivery db connOrGroupId newMsg sharedMsgId_ rcvMsgDelivery
|
||||||
|
pure (conn', msg)
|
||||||
|
|
||||||
saveSndChatItem :: ChatMonad m => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> m (ChatItem c 'MDSnd)
|
saveSndChatItem :: ChatMonad m => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> m (ChatItem c 'MDSnd)
|
||||||
saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothing Nothing Nothing False
|
saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothing Nothing Nothing False
|
||||||
@ -4805,13 +4853,15 @@ joinAgentConnectionAsync user enableNtfs cReqUri cInfo = do
|
|||||||
allowAgentConnectionAsync :: (MsgEncodingI e, ChatMonad m) => User -> Connection -> ConfirmationId -> ChatMsgEvent e -> m ()
|
allowAgentConnectionAsync :: (MsgEncodingI e, ChatMonad m) => User -> Connection -> ConfirmationId -> ChatMsgEvent e -> m ()
|
||||||
allowAgentConnectionAsync user conn@Connection {connId} confId msg = do
|
allowAgentConnectionAsync user conn@Connection {connId} confId msg = do
|
||||||
cmdId <- withStore' $ \db -> createCommand db user (Just connId) CFAllowConn
|
cmdId <- withStore' $ \db -> createCommand db user (Just connId) CFAllowConn
|
||||||
withAgent $ \a -> allowConnectionAsync a (aCorrId cmdId) (aConnId conn) confId $ directMessage msg
|
dm <- directMessage msg
|
||||||
|
withAgent $ \a -> allowConnectionAsync a (aCorrId cmdId) (aConnId conn) confId dm
|
||||||
withStore' $ \db -> updateConnectionStatus db conn ConnAccepted
|
withStore' $ \db -> updateConnectionStatus db conn ConnAccepted
|
||||||
|
|
||||||
agentAcceptContactAsync :: (MsgEncodingI e, ChatMonad m) => User -> Bool -> InvitationId -> ChatMsgEvent e -> m (CommandId, ConnId)
|
agentAcceptContactAsync :: (MsgEncodingI e, ChatMonad m) => User -> Bool -> InvitationId -> ChatMsgEvent e -> m (CommandId, ConnId)
|
||||||
agentAcceptContactAsync user enableNtfs invId msg = do
|
agentAcceptContactAsync user enableNtfs invId msg = do
|
||||||
cmdId <- withStore' $ \db -> createCommand db user Nothing CFAcceptContact
|
cmdId <- withStore' $ \db -> createCommand db user Nothing CFAcceptContact
|
||||||
connId <- withAgent $ \a -> acceptContactAsync a (aCorrId cmdId) enableNtfs invId $ directMessage msg
|
dm <- directMessage msg
|
||||||
|
connId <- withAgent $ \a -> acceptContactAsync a (aCorrId cmdId) enableNtfs invId dm
|
||||||
pure (cmdId, connId)
|
pure (cmdId, connId)
|
||||||
|
|
||||||
deleteAgentConnectionAsync :: ChatMonad m => User -> ConnId -> m ()
|
deleteAgentConnectionAsync :: ChatMonad m => User -> ConnId -> m ()
|
||||||
|
@ -67,6 +67,7 @@ import Simplex.Messaging.TMap (TMap)
|
|||||||
import Simplex.Messaging.Transport (simplexMQVersion)
|
import Simplex.Messaging.Transport (simplexMQVersion)
|
||||||
import Simplex.Messaging.Transport.Client (TransportHost)
|
import Simplex.Messaging.Transport.Client (TransportHost)
|
||||||
import Simplex.Messaging.Util (allFinally, catchAllErrors, tryAllErrors, (<$$>))
|
import Simplex.Messaging.Util (allFinally, catchAllErrors, tryAllErrors, (<$$>))
|
||||||
|
import Simplex.Messaging.Version
|
||||||
import System.IO (Handle)
|
import System.IO (Handle)
|
||||||
import System.Mem.Weak (Weak)
|
import System.Mem.Weak (Weak)
|
||||||
import UnliftIO.STM
|
import UnliftIO.STM
|
||||||
@ -75,7 +76,7 @@ versionNumber :: String
|
|||||||
versionNumber = showVersion SC.version
|
versionNumber = showVersion SC.version
|
||||||
|
|
||||||
versionString :: String -> String
|
versionString :: String -> String
|
||||||
versionString version = "SimpleX Chat v" <> version
|
versionString ver = "SimpleX Chat v" <> ver
|
||||||
|
|
||||||
updateStr :: String
|
updateStr :: String
|
||||||
updateStr = "To update run: curl -o- https://raw.githubusercontent.com/simplex-chat/simplex-chat/master/install.sh | bash"
|
updateStr = "To update run: curl -o- https://raw.githubusercontent.com/simplex-chat/simplex-chat/master/install.sh | bash"
|
||||||
@ -104,6 +105,7 @@ coreVersionInfo simplexmqCommit =
|
|||||||
|
|
||||||
data ChatConfig = ChatConfig
|
data ChatConfig = ChatConfig
|
||||||
{ agentConfig :: AgentConfig,
|
{ agentConfig :: AgentConfig,
|
||||||
|
chatVRange :: VersionRange,
|
||||||
confirmMigrations :: MigrationConfirmation,
|
confirmMigrations :: MigrationConfirmation,
|
||||||
defaultServers :: DefaultAgentServers,
|
defaultServers :: DefaultAgentServers,
|
||||||
tbqSize :: Natural,
|
tbqSize :: Natural,
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
|
||||||
|
module Simplex.Chat.Migrations.M20230829_connections_chat_vrange where
|
||||||
|
|
||||||
|
import Database.SQLite.Simple (Query)
|
||||||
|
import Database.SQLite.Simple.QQ (sql)
|
||||||
|
|
||||||
|
m20230829_connections_chat_vrange :: Query
|
||||||
|
m20230829_connections_chat_vrange =
|
||||||
|
[sql|
|
||||||
|
ALTER TABLE connections ADD COLUMN peer_chat_min_version INTEGER NOT NULL DEFAULT 1;
|
||||||
|
ALTER TABLE connections ADD COLUMN peer_chat_max_version INTEGER NOT NULL DEFAULT 1;
|
||||||
|
|
||||||
|
ALTER TABLE contact_requests ADD COLUMN peer_chat_min_version INTEGER NOT NULL DEFAULT 1;
|
||||||
|
ALTER TABLE contact_requests ADD COLUMN peer_chat_max_version INTEGER NOT NULL DEFAULT 1;
|
||||||
|
|]
|
||||||
|
|
||||||
|
down_m20230829_connections_chat_vrange :: Query
|
||||||
|
down_m20230829_connections_chat_vrange =
|
||||||
|
[sql|
|
||||||
|
ALTER TABLE contact_requests DROP COLUMN peer_chat_max_version;
|
||||||
|
ALTER TABLE contact_requests DROP COLUMN peer_chat_min_version;
|
||||||
|
|
||||||
|
ALTER TABLE connections DROP COLUMN peer_chat_max_version;
|
||||||
|
ALTER TABLE connections DROP COLUMN peer_chat_min_version;
|
||||||
|
|]
|
@ -285,6 +285,8 @@ CREATE TABLE connections(
|
|||||||
security_code TEXT NULL,
|
security_code TEXT NULL,
|
||||||
security_code_verified_at TEXT NULL,
|
security_code_verified_at TEXT NULL,
|
||||||
auth_err_counter INTEGER DEFAULT 0 CHECK(auth_err_counter NOT NULL),
|
auth_err_counter INTEGER DEFAULT 0 CHECK(auth_err_counter NOT NULL),
|
||||||
|
peer_chat_min_version INTEGER NOT NULL DEFAULT 1,
|
||||||
|
peer_chat_max_version INTEGER NOT NULL DEFAULT 1,
|
||||||
FOREIGN KEY(snd_file_id, connection_id)
|
FOREIGN KEY(snd_file_id, connection_id)
|
||||||
REFERENCES snd_files(file_id, connection_id)
|
REFERENCES snd_files(file_id, connection_id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
@ -318,6 +320,8 @@ CREATE TABLE contact_requests(
|
|||||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||||
updated_at TEXT CHECK(updated_at NOT NULL),
|
updated_at TEXT CHECK(updated_at NOT NULL),
|
||||||
xcontact_id BLOB,
|
xcontact_id BLOB,
|
||||||
|
peer_chat_min_version INTEGER NOT NULL DEFAULT 1,
|
||||||
|
peer_chat_max_version INTEGER NOT NULL DEFAULT 1,
|
||||||
FOREIGN KEY(user_id, local_display_name)
|
FOREIGN KEY(user_id, local_display_name)
|
||||||
REFERENCES display_names(user_id, local_display_name)
|
REFERENCES display_names(user_id, local_display_name)
|
||||||
ON UPDATE CASCADE
|
ON UPDATE CASCADE
|
||||||
|
@ -46,6 +46,17 @@ import Simplex.Messaging.Encoding
|
|||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
|
||||||
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
|
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
|
||||||
|
import Simplex.Messaging.Version hiding (version)
|
||||||
|
|
||||||
|
currentChatVersion :: Version
|
||||||
|
currentChatVersion = 2
|
||||||
|
|
||||||
|
supportedChatVRange :: VersionRange
|
||||||
|
supportedChatVRange = mkVersionRange 1 currentChatVersion
|
||||||
|
|
||||||
|
-- version range that supports skipping establishing direct connections in a group
|
||||||
|
groupNoDirectVRange :: VersionRange
|
||||||
|
groupNoDirectVRange = mkVersionRange 2 currentChatVersion
|
||||||
|
|
||||||
data ConnectionEntity
|
data ConnectionEntity
|
||||||
= RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact}
|
= RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact}
|
||||||
@ -102,7 +113,8 @@ data AppMessage (e :: MsgEncoding) where
|
|||||||
|
|
||||||
-- chat message is sent as JSON with these properties
|
-- chat message is sent as JSON with these properties
|
||||||
data AppMessageJson = AppMessageJson
|
data AppMessageJson = AppMessageJson
|
||||||
{ msgId :: Maybe SharedMsgId,
|
{ v :: Maybe ChatVersionRange,
|
||||||
|
msgId :: Maybe SharedMsgId,
|
||||||
event :: Text,
|
event :: Text,
|
||||||
params :: J.Object
|
params :: J.Object
|
||||||
}
|
}
|
||||||
@ -161,7 +173,11 @@ instance ToJSON MsgRef where
|
|||||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
data ChatMessage e = ChatMessage {msgId :: Maybe SharedMsgId, chatMsgEvent :: ChatMsgEvent e}
|
data ChatMessage e = ChatMessage
|
||||||
|
{ chatVRange :: VersionRange,
|
||||||
|
msgId :: Maybe SharedMsgId,
|
||||||
|
chatMsgEvent :: ChatMsgEvent e
|
||||||
|
}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
data AChatMessage = forall e. MsgEncodingI e => ACMsg (SMsgEncoding e) (ChatMessage e)
|
data AChatMessage = forall e. MsgEncodingI e => ACMsg (SMsgEncoding e) (ChatMessage e)
|
||||||
@ -724,17 +740,17 @@ appBinaryToCM :: AppMessageBinary -> Either String (ChatMessage 'Binary)
|
|||||||
appBinaryToCM AppMessageBinary {msgId, tag, body} = do
|
appBinaryToCM AppMessageBinary {msgId, tag, body} = do
|
||||||
eventTag <- strDecode $ B.singleton tag
|
eventTag <- strDecode $ B.singleton tag
|
||||||
chatMsgEvent <- parseAll (msg eventTag) body
|
chatMsgEvent <- parseAll (msg eventTag) body
|
||||||
pure ChatMessage {msgId, chatMsgEvent}
|
pure ChatMessage {chatVRange = chatInitialVRange, msgId, chatMsgEvent}
|
||||||
where
|
where
|
||||||
msg :: CMEventTag 'Binary -> A.Parser (ChatMsgEvent 'Binary)
|
msg :: CMEventTag 'Binary -> A.Parser (ChatMsgEvent 'Binary)
|
||||||
msg = \case
|
msg = \case
|
||||||
BFileChunk_ -> BFileChunk <$> (SharedMsgId <$> smpP) <*> (unIFC <$> smpP)
|
BFileChunk_ -> BFileChunk <$> (SharedMsgId <$> smpP) <*> (unIFC <$> smpP)
|
||||||
|
|
||||||
appJsonToCM :: AppMessageJson -> Either String (ChatMessage 'Json)
|
appJsonToCM :: AppMessageJson -> Either String (ChatMessage 'Json)
|
||||||
appJsonToCM AppMessageJson {msgId, event, params} = do
|
appJsonToCM AppMessageJson {v, msgId, event, params} = do
|
||||||
eventTag <- strDecode $ encodeUtf8 event
|
eventTag <- strDecode $ encodeUtf8 event
|
||||||
chatMsgEvent <- msg eventTag
|
chatMsgEvent <- msg eventTag
|
||||||
pure ChatMessage {msgId, chatMsgEvent}
|
pure ChatMessage {chatVRange = maybe chatInitialVRange fromChatVRange v, msgId, chatMsgEvent}
|
||||||
where
|
where
|
||||||
p :: FromJSON a => J.Key -> Either String a
|
p :: FromJSON a => J.Key -> Either String a
|
||||||
p key = JT.parseEither (.: key) params
|
p key = JT.parseEither (.: key) params
|
||||||
@ -784,11 +800,11 @@ appJsonToCM AppMessageJson {msgId, event, params} = do
|
|||||||
key .=? value = maybe id ((:) . (key .=)) value
|
key .=? value = maybe id ((:) . (key .=)) value
|
||||||
|
|
||||||
chatToAppMessage :: forall e. MsgEncodingI e => ChatMessage e -> AppMessage e
|
chatToAppMessage :: forall e. MsgEncodingI e => ChatMessage e -> AppMessage e
|
||||||
chatToAppMessage ChatMessage {msgId, chatMsgEvent} = case encoding @e of
|
chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @e of
|
||||||
SBinary ->
|
SBinary ->
|
||||||
let (binaryMsgId, body) = toBody chatMsgEvent
|
let (binaryMsgId, body) = toBody chatMsgEvent
|
||||||
in AMBinary AppMessageBinary {msgId = binaryMsgId, tag = B.head $ strEncode tag, body}
|
in AMBinary AppMessageBinary {msgId = binaryMsgId, tag = B.head $ strEncode tag, body}
|
||||||
SJson -> AMJson AppMessageJson {msgId, event = textEncode tag, params = params chatMsgEvent}
|
SJson -> AMJson AppMessageJson {v = Just $ ChatVersionRange chatVRange, msgId, event = textEncode tag, params = params chatMsgEvent}
|
||||||
where
|
where
|
||||||
tag = toCMEventTag chatMsgEvent
|
tag = toCMEventTag chatMsgEvent
|
||||||
o :: [(J.Key, J.Value)] -> J.Object
|
o :: [(J.Key, J.Value)] -> J.Object
|
||||||
@ -804,7 +820,7 @@ chatToAppMessage ChatMessage {msgId, chatMsgEvent} = case encoding @e of
|
|||||||
XMsgUpdate msgId' content ttl live -> o $ ("ttl" .=? ttl) $ ("live" .=? live) ["msgId" .= msgId', "content" .= content]
|
XMsgUpdate msgId' content ttl live -> o $ ("ttl" .=? ttl) $ ("live" .=? live) ["msgId" .= msgId', "content" .= content]
|
||||||
XMsgDel msgId' memberId -> o $ ("memberId" .=? memberId) ["msgId" .= msgId']
|
XMsgDel msgId' memberId -> o $ ("memberId" .=? memberId) ["msgId" .= msgId']
|
||||||
XMsgDeleted -> JM.empty
|
XMsgDeleted -> JM.empty
|
||||||
XMsgReact msgId' memberId reaction add -> o $ ("memberId" .=? memberId) ["msgId" .= msgId', "reaction" .= reaction, "add" .= add]
|
XMsgReact msgId' memberId reaction add -> o $ ("memberId" .=? memberId) ["msgId" .= msgId', "reaction" .= reaction, "add" .= add]
|
||||||
XFile fileInv -> o ["file" .= fileInv]
|
XFile fileInv -> o ["file" .= fileInv]
|
||||||
XFileAcpt fileName -> o ["fileName" .= fileName]
|
XFileAcpt fileName -> o ["fileName" .= fileName]
|
||||||
XFileAcptInv sharedMsgId fileConnReq fileName -> o $ ("fileConnReq" .=? fileConnReq) ["msgId" .= sharedMsgId, "fileName" .= fileName]
|
XFileAcptInv sharedMsgId fileConnReq fileName -> o $ ("fileConnReq" .=? fileConnReq) ["msgId" .= sharedMsgId, "fileName" .= fileName]
|
||||||
|
@ -49,7 +49,8 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do
|
|||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id,
|
SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id,
|
||||||
conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at, auth_err_counter
|
conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at, auth_err_counter,
|
||||||
|
peer_chat_min_version, peer_chat_max_version
|
||||||
FROM connections
|
FROM connections
|
||||||
WHERE user_id = ? AND agent_conn_id = ?
|
WHERE user_id = ? AND agent_conn_id = ?
|
||||||
|]
|
|]
|
||||||
|
@ -75,6 +75,7 @@ import Simplex.Chat.Types.Preferences
|
|||||||
import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId)
|
import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
||||||
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||||
|
import Simplex.Messaging.Version
|
||||||
|
|
||||||
getPendingContactConnection :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO PendingContactConnection
|
getPendingContactConnection :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO PendingContactConnection
|
||||||
getPendingContactConnection db userId connId = do
|
getPendingContactConnection db userId connId = do
|
||||||
@ -143,7 +144,8 @@ getConnReqContactXContactId db user@User {userId} cReqHash = do
|
|||||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||||
-- Connection
|
-- Connection
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM contacts ct
|
FROM contacts ct
|
||||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||||
JOIN connections c ON c.contact_id = ct.contact_id
|
JOIN connections c ON c.contact_id = ct.contact_id
|
||||||
@ -411,8 +413,8 @@ getUserContacts db user@User {userId} = do
|
|||||||
contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 0" (Only userId)
|
contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 0" (Only userId)
|
||||||
rights <$> mapM (runExceptT . getContact db user) contactIds
|
rights <$> mapM (runExceptT . getContact db user) contactIds
|
||||||
|
|
||||||
createOrUpdateContactRequest :: DB.Connection -> User -> Int64 -> InvitationId -> Profile -> Maybe XContactId -> ExceptT StoreError IO ContactOrRequest
|
createOrUpdateContactRequest :: DB.Connection -> User -> Int64 -> InvitationId -> VersionRange -> Profile -> Maybe XContactId -> ExceptT StoreError IO ContactOrRequest
|
||||||
createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profile {displayName, fullName, image, contactLink, preferences} xContactId_ =
|
createOrUpdateContactRequest db user@User {userId} userContactLinkId invId (VersionRange minV maxV) Profile {displayName, fullName, image, contactLink, preferences} xContactId_ =
|
||||||
liftIO (maybeM getContact' xContactId_) >>= \case
|
liftIO (maybeM getContact' xContactId_) >>= \case
|
||||||
Just contact -> pure $ CORContact contact
|
Just contact -> pure $ CORContact contact
|
||||||
Nothing -> CORRequest <$> createOrUpdate_
|
Nothing -> CORRequest <$> createOrUpdate_
|
||||||
@ -441,10 +443,10 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi
|
|||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
INSERT INTO contact_requests
|
INSERT INTO contact_requests
|
||||||
(user_contact_link_id, agent_invitation_id, contact_profile_id, local_display_name, user_id, created_at, updated_at, xcontact_id)
|
(user_contact_link_id, agent_invitation_id, peer_chat_min_version, peer_chat_max_version, contact_profile_id, local_display_name, user_id, created_at, updated_at, xcontact_id)
|
||||||
VALUES (?,?,?,?,?,?,?,?)
|
VALUES (?,?,?,?,?,?,?,?,?,?)
|
||||||
|]
|
|]
|
||||||
(userContactLinkId, invId, profileId, ldn, userId, currentTs, currentTs, xContactId_)
|
(userContactLinkId, invId, minV, maxV, profileId, ldn, userId, currentTs, currentTs, xContactId_)
|
||||||
insertedRowId db
|
insertedRowId db
|
||||||
getContact' :: XContactId -> IO (Maybe Contact)
|
getContact' :: XContactId -> IO (Maybe Contact)
|
||||||
getContact' xContactId =
|
getContact' xContactId =
|
||||||
@ -458,7 +460,8 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi
|
|||||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||||
-- Connection
|
-- Connection
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM contacts ct
|
FROM contacts ct
|
||||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||||
@ -475,7 +478,8 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi
|
|||||||
[sql|
|
[sql|
|
||||||
SELECT
|
SELECT
|
||||||
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
|
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
|
||||||
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at
|
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at,
|
||||||
|
cr.peer_chat_min_version, cr.peer_chat_max_version
|
||||||
FROM contact_requests cr
|
FROM contact_requests cr
|
||||||
JOIN connections c USING (user_contact_link_id)
|
JOIN connections c USING (user_contact_link_id)
|
||||||
JOIN contact_profiles p USING (contact_profile_id)
|
JOIN contact_profiles p USING (contact_profile_id)
|
||||||
@ -489,10 +493,26 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi
|
|||||||
currentTs <- liftIO getCurrentTime
|
currentTs <- liftIO getCurrentTime
|
||||||
updateProfile currentTs
|
updateProfile currentTs
|
||||||
if displayName == oldDisplayName
|
if displayName == oldDisplayName
|
||||||
then Right <$> DB.execute db "UPDATE contact_requests SET agent_invitation_id = ?, updated_at = ? WHERE user_id = ? AND contact_request_id = ?" (invId, currentTs, userId, cReqId)
|
then
|
||||||
|
Right
|
||||||
|
<$> DB.execute
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
UPDATE contact_requests
|
||||||
|
SET agent_invitation_id = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, updated_at = ?
|
||||||
|
WHERE user_id = ? AND contact_request_id = ?
|
||||||
|
|]
|
||||||
|
(invId, minV, maxV, currentTs, userId, cReqId)
|
||||||
else withLocalDisplayName db userId displayName $ \ldn ->
|
else withLocalDisplayName db userId displayName $ \ldn ->
|
||||||
Right <$> do
|
Right <$> do
|
||||||
DB.execute db "UPDATE contact_requests SET agent_invitation_id = ?, local_display_name = ?, updated_at = ? WHERE user_id = ? AND contact_request_id = ?" (invId, ldn, currentTs, userId, cReqId)
|
DB.execute
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
UPDATE contact_requests
|
||||||
|
SET agent_invitation_id = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, local_display_name = ?, updated_at = ?
|
||||||
|
WHERE user_id = ? AND contact_request_id = ?
|
||||||
|
|]
|
||||||
|
(invId, minV, maxV, ldn, currentTs, userId, cReqId)
|
||||||
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (oldLdn, userId)
|
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (oldLdn, userId)
|
||||||
where
|
where
|
||||||
updateProfile currentTs =
|
updateProfile currentTs =
|
||||||
@ -527,7 +547,8 @@ getContactRequest db User {userId} contactRequestId =
|
|||||||
[sql|
|
[sql|
|
||||||
SELECT
|
SELECT
|
||||||
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
|
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
|
||||||
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at
|
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at,
|
||||||
|
cr.peer_chat_min_version, cr.peer_chat_max_version
|
||||||
FROM contact_requests cr
|
FROM contact_requests cr
|
||||||
JOIN connections c USING (user_contact_link_id)
|
JOIN connections c USING (user_contact_link_id)
|
||||||
JOIN contact_profiles p USING (contact_profile_id)
|
JOIN contact_profiles p USING (contact_profile_id)
|
||||||
@ -566,8 +587,8 @@ deleteContactRequest db User {userId} contactRequestId = do
|
|||||||
(userId, userId, contactRequestId)
|
(userId, userId, contactRequestId)
|
||||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
|
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
|
||||||
|
|
||||||
createAcceptedContact :: DB.Connection -> User -> ConnId -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> IO Contact
|
createAcceptedContact :: DB.Connection -> User -> ConnId -> VersionRange -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> IO Contact
|
||||||
createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId localDisplayName profileId profile userContactLinkId xContactId incognitoProfile = do
|
createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile = do
|
||||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
||||||
createdAt <- getCurrentTime
|
createdAt <- getCurrentTime
|
||||||
customUserProfileId <- forM incognitoProfile $ \case
|
customUserProfileId <- forM incognitoProfile $ \case
|
||||||
@ -579,7 +600,7 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}
|
|||||||
"INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id) VALUES (?,?,?,?,?,?,?,?,?)"
|
"INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id) VALUES (?,?,?,?,?,?,?,?,?)"
|
||||||
(userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, createdAt, xContactId)
|
(userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, createdAt, xContactId)
|
||||||
contactId <- insertedRowId db
|
contactId <- insertedRowId db
|
||||||
activeConn <- createConnection_ db userId ConnContact (Just contactId) agentConnId Nothing (Just userContactLinkId) customUserProfileId 0 createdAt
|
activeConn <- createConnection_ db userId ConnContact (Just contactId) agentConnId cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt
|
||||||
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn
|
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn
|
||||||
pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn, viaGroup = Nothing, contactUsed = False, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = createdAt, updatedAt = createdAt, chatTs = Just createdAt}
|
pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn, viaGroup = Nothing, contactUsed = False, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = createdAt, updatedAt = createdAt, chatTs = Just createdAt}
|
||||||
|
|
||||||
@ -603,7 +624,8 @@ getContact_ db user@User {userId} contactId deleted =
|
|||||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||||
-- Connection
|
-- Connection
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM contacts ct
|
FROM contacts ct
|
||||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||||
@ -651,7 +673,8 @@ getContactConnections db userId Contact {contactId} =
|
|||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM connections c
|
FROM connections c
|
||||||
JOIN contacts ct ON ct.contact_id = c.contact_id
|
JOIN contacts ct ON ct.contact_id = c.contact_id
|
||||||
WHERE c.user_id = ? AND ct.user_id = ? AND ct.contact_id = ?
|
WHERE c.user_id = ? AND ct.user_id = ? AND ct.contact_id = ?
|
||||||
@ -667,7 +690,8 @@ getConnectionById db User {userId} connId = ExceptT $ do
|
|||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id,
|
SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id,
|
||||||
conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at, auth_err_counter
|
conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at, auth_err_counter,
|
||||||
|
peer_chat_min_version, peer_chat_max_version
|
||||||
FROM connections
|
FROM connections
|
||||||
WHERE user_id = ? AND connection_id = ?
|
WHERE user_id = ? AND connection_id = ?
|
||||||
|]
|
|]
|
||||||
|
@ -425,7 +425,7 @@ getChatRefByFileId db User {userId} fileId =
|
|||||||
createSndFileConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> IO Connection
|
createSndFileConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> IO Connection
|
||||||
createSndFileConnection_ db userId fileId agentConnId = do
|
createSndFileConnection_ db userId fileId agentConnId = do
|
||||||
currentTs <- getCurrentTime
|
currentTs <- getCurrentTime
|
||||||
createConnection_ db userId ConnSndFile (Just fileId) agentConnId Nothing Nothing Nothing 0 currentTs
|
createConnection_ db userId ConnSndFile (Just fileId) agentConnId chatInitialVRange Nothing Nothing Nothing 0 currentTs
|
||||||
|
|
||||||
updateSndFileStatus :: DB.Connection -> SndFileTransfer -> FileStatus -> IO ()
|
updateSndFileStatus :: DB.Connection -> SndFileTransfer -> FileStatus -> IO ()
|
||||||
updateSndFileStatus db SndFileTransfer {fileId, connId} status = do
|
updateSndFileStatus db SndFileTransfer {fileId, connId} status = do
|
||||||
|
@ -83,6 +83,7 @@ module Simplex.Chat.Store.Groups
|
|||||||
updateGroupSettings,
|
updateGroupSettings,
|
||||||
getXGrpMemIntroContDirect,
|
getXGrpMemIntroContDirect,
|
||||||
getXGrpMemIntroContGroup,
|
getXGrpMemIntroContGroup,
|
||||||
|
getHostConnId,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@ -105,6 +106,7 @@ import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
|||||||
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Util (eitherToMaybe)
|
import Simplex.Messaging.Util (eitherToMaybe)
|
||||||
|
import Simplex.Messaging.Version
|
||||||
import UnliftIO.STM
|
import UnliftIO.STM
|
||||||
|
|
||||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe Bool, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime) :. GroupMemberRow
|
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe Bool, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime) :. GroupMemberRow
|
||||||
@ -142,7 +144,7 @@ createGroupLink db User {userId} groupInfo@GroupInfo {groupId, localDisplayName}
|
|||||||
"INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)"
|
"INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)"
|
||||||
(userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, memberRole, True, currentTs, currentTs)
|
(userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, memberRole, True, currentTs, currentTs)
|
||||||
userContactLinkId <- insertedRowId db
|
userContactLinkId <- insertedRowId db
|
||||||
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId Nothing Nothing Nothing 0 currentTs
|
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId chatInitialVRange Nothing Nothing Nothing 0 currentTs
|
||||||
|
|
||||||
getGroupLinkConnection :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO Connection
|
getGroupLinkConnection :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO Connection
|
||||||
getGroupLinkConnection db User {userId} groupInfo@GroupInfo {groupId} =
|
getGroupLinkConnection db User {userId} groupInfo@GroupInfo {groupId} =
|
||||||
@ -151,7 +153,8 @@ getGroupLinkConnection db User {userId} groupInfo@GroupInfo {groupId} =
|
|||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM connections c
|
FROM connections c
|
||||||
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
||||||
WHERE c.user_id = ? AND uc.user_id = ? AND uc.group_id = ?
|
WHERE c.user_id = ? AND uc.user_id = ? AND uc.group_id = ?
|
||||||
@ -232,7 +235,8 @@ getGroupAndMember db User {userId, userContactId} groupMemberId =
|
|||||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
||||||
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
|
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM group_members m
|
FROM group_members m
|
||||||
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
||||||
JOIN groups g ON g.group_id = m.group_id
|
JOIN groups g ON g.group_id = m.group_id
|
||||||
@ -478,20 +482,21 @@ getUserGroupsWithSummary db user _contactId_ search_ =
|
|||||||
-- the statuses on non-current members should match memberCurrent' function
|
-- the statuses on non-current members should match memberCurrent' function
|
||||||
getGroupSummary :: DB.Connection -> User -> GroupId -> IO GroupSummary
|
getGroupSummary :: DB.Connection -> User -> GroupId -> IO GroupSummary
|
||||||
getGroupSummary db User {userId} groupId = do
|
getGroupSummary db User {userId} groupId = do
|
||||||
currentMembers_ <- maybeFirstRow fromOnly $
|
currentMembers_ <-
|
||||||
DB.query
|
maybeFirstRow fromOnly $
|
||||||
db
|
DB.query
|
||||||
[sql|
|
db
|
||||||
SELECT count (m.group_member_id)
|
[sql|
|
||||||
FROM groups g
|
SELECT count (m.group_member_id)
|
||||||
JOIN group_members m USING (group_id)
|
FROM groups g
|
||||||
WHERE g.user_id = ?
|
JOIN group_members m USING (group_id)
|
||||||
AND g.group_id = ?
|
WHERE g.user_id = ?
|
||||||
AND m.member_status != ?
|
AND g.group_id = ?
|
||||||
AND m.member_status != ?
|
AND m.member_status != ?
|
||||||
AND m.member_status != ?
|
AND m.member_status != ?
|
||||||
|]
|
AND m.member_status != ?
|
||||||
(userId, groupId, GSMemRemoved, GSMemLeft, GSMemInvited)
|
|]
|
||||||
|
(userId, groupId, GSMemRemoved, GSMemLeft, GSMemInvited)
|
||||||
pure GroupSummary {currentMembers = fromMaybe 0 currentMembers_}
|
pure GroupSummary {currentMembers = fromMaybe 0 currentMembers_}
|
||||||
|
|
||||||
getContactGroupPreferences :: DB.Connection -> User -> Contact -> IO [FullGroupPreferences]
|
getContactGroupPreferences :: DB.Connection -> User -> Contact -> IO [FullGroupPreferences]
|
||||||
@ -524,7 +529,8 @@ groupMemberQuery =
|
|||||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
||||||
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
|
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM group_members m
|
FROM group_members m
|
||||||
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
||||||
LEFT JOIN connections c ON c.connection_id = (
|
LEFT JOIN connections c ON c.connection_id = (
|
||||||
@ -609,11 +615,11 @@ getGroupInvitation db user groupId =
|
|||||||
DB.query db "SELECT g.inv_queue_info FROM groups g WHERE g.group_id = ? AND g.user_id = ?" (groupId, userId)
|
DB.query db "SELECT g.inv_queue_info FROM groups g WHERE g.group_id = ? AND g.user_id = ?" (groupId, userId)
|
||||||
|
|
||||||
createNewContactMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupId -> Contact -> GroupMemberRole -> ConnId -> ConnReqInvitation -> ExceptT StoreError IO GroupMember
|
createNewContactMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupId -> Contact -> GroupMemberRole -> ConnId -> ConnReqInvitation -> ExceptT StoreError IO GroupMember
|
||||||
createNewContactMember db gVar User {userId, userContactId} groupId Contact {contactId, localDisplayName, profile} memberRole agentConnId connRequest =
|
createNewContactMember db gVar User {userId, userContactId} groupId Contact {contactId, localDisplayName, profile, activeConn = Connection {peerChatVRange}} memberRole agentConnId connRequest =
|
||||||
createWithRandomId gVar $ \memId -> do
|
createWithRandomId gVar $ \memId -> do
|
||||||
createdAt <- liftIO getCurrentTime
|
createdAt <- liftIO getCurrentTime
|
||||||
member@GroupMember {groupMemberId} <- createMember_ (MemberId memId) createdAt
|
member@GroupMember {groupMemberId} <- createMember_ (MemberId memId) createdAt
|
||||||
void $ createMemberConnection_ db userId groupMemberId agentConnId Nothing 0 createdAt
|
void $ createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange Nothing 0 createdAt
|
||||||
pure member
|
pure member
|
||||||
where
|
where
|
||||||
createMember_ memberId createdAt = do
|
createMember_ memberId createdAt = do
|
||||||
@ -648,13 +654,13 @@ createNewContactMember db gVar User {userId, userContactId} groupId Contact {con
|
|||||||
:. (userId, localDisplayName, contactId, localProfileId profile, connRequest, createdAt, createdAt)
|
:. (userId, localDisplayName, contactId, localProfileId profile, connRequest, createdAt, createdAt)
|
||||||
)
|
)
|
||||||
|
|
||||||
createNewContactMemberAsync :: DB.Connection -> TVar ChaChaDRG -> User -> GroupId -> Contact -> GroupMemberRole -> (CommandId, ConnId) -> ExceptT StoreError IO ()
|
createNewContactMemberAsync :: DB.Connection -> TVar ChaChaDRG -> User -> GroupId -> Contact -> GroupMemberRole -> (CommandId, ConnId) -> VersionRange -> ExceptT StoreError IO ()
|
||||||
createNewContactMemberAsync db gVar user@User {userId, userContactId} groupId Contact {contactId, localDisplayName, profile} memberRole (cmdId, agentConnId) =
|
createNewContactMemberAsync db gVar user@User {userId, userContactId} groupId Contact {contactId, localDisplayName, profile} memberRole (cmdId, agentConnId) peerChatVRange =
|
||||||
createWithRandomId gVar $ \memId -> do
|
createWithRandomId gVar $ \memId -> do
|
||||||
createdAt <- liftIO getCurrentTime
|
createdAt <- liftIO getCurrentTime
|
||||||
insertMember_ (MemberId memId) createdAt
|
insertMember_ (MemberId memId) createdAt
|
||||||
groupMemberId <- liftIO $ insertedRowId db
|
groupMemberId <- liftIO $ insertedRowId db
|
||||||
Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId Nothing 0 createdAt
|
Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange Nothing 0 createdAt
|
||||||
setCommandConnId db user cmdId connId
|
setCommandConnId db user cmdId connId
|
||||||
where
|
where
|
||||||
insertMember_ memberId createdAt =
|
insertMember_ memberId createdAt =
|
||||||
@ -670,30 +676,32 @@ createNewContactMemberAsync db gVar user@User {userId, userContactId} groupId Co
|
|||||||
:. (userId, localDisplayName, contactId, localProfileId profile, createdAt, createdAt)
|
:. (userId, localDisplayName, contactId, localProfileId profile, createdAt, createdAt)
|
||||||
)
|
)
|
||||||
|
|
||||||
getContactViaMember :: DB.Connection -> User -> GroupMember -> IO (Maybe Contact)
|
getContactViaMember :: DB.Connection -> User -> GroupMember -> ExceptT StoreError IO Contact
|
||||||
getContactViaMember db user@User {userId} GroupMember {groupMemberId} =
|
getContactViaMember db user@User {userId} GroupMember {groupMemberId} =
|
||||||
maybeFirstRow (toContact user) $
|
ExceptT $
|
||||||
DB.query
|
firstRow (toContact user) (SEContactNotFoundByMemberId groupMemberId) $
|
||||||
db
|
DB.query
|
||||||
[sql|
|
db
|
||||||
SELECT
|
[sql|
|
||||||
-- Contact
|
SELECT
|
||||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
-- Contact
|
||||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||||
-- Connection
|
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
-- Connection
|
||||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||||
FROM contacts ct
|
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
JOIN contact_profiles cp ON cp.contact_profile_id = ct.contact_profile_id
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
JOIN connections c ON c.connection_id = (
|
FROM contacts ct
|
||||||
SELECT max(cc.connection_id)
|
JOIN contact_profiles cp ON cp.contact_profile_id = ct.contact_profile_id
|
||||||
FROM connections cc
|
JOIN connections c ON c.connection_id = (
|
||||||
where cc.contact_id = ct.contact_id
|
SELECT max(cc.connection_id)
|
||||||
)
|
FROM connections cc
|
||||||
JOIN group_members m ON m.contact_id = ct.contact_id
|
where cc.contact_id = ct.contact_id
|
||||||
WHERE ct.user_id = ? AND m.group_member_id = ? AND ct.deleted = 0
|
)
|
||||||
|]
|
JOIN group_members m ON m.contact_id = ct.contact_id
|
||||||
(userId, groupMemberId)
|
WHERE ct.user_id = ? AND m.group_member_id = ? AND ct.deleted = 0
|
||||||
|
|]
|
||||||
|
(userId, groupMemberId)
|
||||||
|
|
||||||
setNewContactMemberConnRequest :: DB.Connection -> User -> GroupMember -> ConnReqInvitation -> IO ()
|
setNewContactMemberConnRequest :: DB.Connection -> User -> GroupMember -> ConnReqInvitation -> IO ()
|
||||||
setNewContactMemberConnRequest db User {userId} GroupMember {groupMemberId} connRequest = do
|
setNewContactMemberConnRequest db User {userId} GroupMember {groupMemberId} connRequest = do
|
||||||
@ -705,15 +713,15 @@ getMemberInvitation db User {userId} groupMemberId =
|
|||||||
fmap join . maybeFirstRow fromOnly $
|
fmap join . maybeFirstRow fromOnly $
|
||||||
DB.query db "SELECT sent_inv_queue_info FROM group_members WHERE group_member_id = ? AND user_id = ?" (groupMemberId, userId)
|
DB.query db "SELECT sent_inv_queue_info FROM group_members WHERE group_member_id = ? AND user_id = ?" (groupMemberId, userId)
|
||||||
|
|
||||||
createMemberConnection :: DB.Connection -> UserId -> GroupMember -> ConnId -> IO ()
|
createMemberConnection :: DB.Connection -> UserId -> GroupMember -> ConnId -> VersionRange -> IO ()
|
||||||
createMemberConnection db userId GroupMember {groupMemberId} agentConnId = do
|
createMemberConnection db userId GroupMember {groupMemberId} agentConnId peerChatVRange = do
|
||||||
currentTs <- getCurrentTime
|
currentTs <- getCurrentTime
|
||||||
void $ createMemberConnection_ db userId groupMemberId agentConnId Nothing 0 currentTs
|
void $ createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange Nothing 0 currentTs
|
||||||
|
|
||||||
createMemberConnectionAsync :: DB.Connection -> User -> GroupMemberId -> (CommandId, ConnId) -> IO ()
|
createMemberConnectionAsync :: DB.Connection -> User -> GroupMemberId -> (CommandId, ConnId) -> VersionRange -> IO ()
|
||||||
createMemberConnectionAsync db user@User {userId} groupMemberId (cmdId, agentConnId) = do
|
createMemberConnectionAsync db user@User {userId} groupMemberId (cmdId, agentConnId) peerChatVRange = do
|
||||||
currentTs <- getCurrentTime
|
currentTs <- getCurrentTime
|
||||||
Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId Nothing 0 currentTs
|
Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange Nothing 0 currentTs
|
||||||
setCommandConnId db user cmdId connId
|
setCommandConnId db user cmdId connId
|
||||||
|
|
||||||
updateGroupMemberStatus :: DB.Connection -> UserId -> GroupMember -> GroupMemberStatus -> IO ()
|
updateGroupMemberStatus :: DB.Connection -> UserId -> GroupMember -> GroupMemberStatus -> IO ()
|
||||||
@ -733,25 +741,30 @@ updateGroupMemberStatusById db userId groupMemberId memStatus = do
|
|||||||
|
|
||||||
-- | add new member with profile
|
-- | add new member with profile
|
||||||
createNewGroupMember :: DB.Connection -> User -> GroupInfo -> MemberInfo -> GroupMemberCategory -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
|
createNewGroupMember :: DB.Connection -> User -> GroupInfo -> MemberInfo -> GroupMemberCategory -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
|
||||||
createNewGroupMember db user@User {userId} gInfo memInfo@(MemberInfo _ _ Profile {displayName, fullName, image, contactLink, preferences}) memCategory memStatus =
|
createNewGroupMember db user gInfo memInfo memCategory memStatus = do
|
||||||
ExceptT . withLocalDisplayName db userId displayName $ \localDisplayName -> do
|
currentTs <- liftIO getCurrentTime
|
||||||
currentTs <- getCurrentTime
|
(localDisplayName, memProfileId) <- createNewMemberProfile_ db user memInfo currentTs
|
||||||
|
let newMember =
|
||||||
|
NewGroupMember
|
||||||
|
{ memInfo,
|
||||||
|
memCategory,
|
||||||
|
memStatus,
|
||||||
|
memInvitedBy = IBUnknown,
|
||||||
|
localDisplayName,
|
||||||
|
memContactId = Nothing,
|
||||||
|
memProfileId
|
||||||
|
}
|
||||||
|
liftIO $ createNewMember_ db user gInfo newMember currentTs
|
||||||
|
|
||||||
|
createNewMemberProfile_ :: DB.Connection -> User -> MemberInfo -> UTCTime -> ExceptT StoreError IO (Text, ProfileId)
|
||||||
|
createNewMemberProfile_ db User {userId} (MemberInfo _ _ _ Profile {displayName, fullName, image, contactLink, preferences}) createdAt =
|
||||||
|
ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do
|
||||||
DB.execute
|
DB.execute
|
||||||
db
|
db
|
||||||
"INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)"
|
"INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)"
|
||||||
(displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs)
|
(displayName, fullName, image, contactLink, userId, preferences, createdAt, createdAt)
|
||||||
memProfileId <- insertedRowId db
|
profileId <- insertedRowId db
|
||||||
let newMember =
|
pure $ Right (ldn, profileId)
|
||||||
NewGroupMember
|
|
||||||
{ memInfo,
|
|
||||||
memCategory,
|
|
||||||
memStatus,
|
|
||||||
memInvitedBy = IBUnknown,
|
|
||||||
localDisplayName,
|
|
||||||
memContactId = Nothing,
|
|
||||||
memProfileId
|
|
||||||
}
|
|
||||||
Right <$> createNewMember_ db user gInfo newMember currentTs
|
|
||||||
|
|
||||||
createNewMember_ :: DB.Connection -> User -> GroupInfo -> NewGroupMember -> UTCTime -> IO GroupMember
|
createNewMember_ :: DB.Connection -> User -> GroupInfo -> NewGroupMember -> UTCTime -> IO GroupMember
|
||||||
createNewMember_
|
createNewMember_
|
||||||
@ -759,7 +772,7 @@ createNewMember_
|
|||||||
User {userId, userContactId}
|
User {userId, userContactId}
|
||||||
GroupInfo {groupId}
|
GroupInfo {groupId}
|
||||||
NewGroupMember
|
NewGroupMember
|
||||||
{ memInfo = MemberInfo memberId memberRole memberProfile,
|
{ memInfo = MemberInfo memberId memberRole _ memberProfile,
|
||||||
memCategory = memberCategory,
|
memCategory = memberCategory,
|
||||||
memStatus = memberStatus,
|
memStatus = memberStatus,
|
||||||
memInvitedBy = invitedBy,
|
memInvitedBy = invitedBy,
|
||||||
@ -903,43 +916,41 @@ getIntroduction_ db reMember toMember = ExceptT $ do
|
|||||||
where
|
where
|
||||||
toIntro :: [(Int64, Maybe ConnReqInvitation, Maybe ConnReqInvitation, GroupMemberIntroStatus)] -> Either StoreError GroupMemberIntro
|
toIntro :: [(Int64, Maybe ConnReqInvitation, Maybe ConnReqInvitation, GroupMemberIntroStatus)] -> Either StoreError GroupMemberIntro
|
||||||
toIntro [(introId, groupConnReq, directConnReq, introStatus)] =
|
toIntro [(introId, groupConnReq, directConnReq, introStatus)] =
|
||||||
let introInvitation = IntroInvitation <$> groupConnReq <*> directConnReq
|
let introInvitation = IntroInvitation <$> groupConnReq <*> pure directConnReq
|
||||||
in Right GroupMemberIntro {introId, reMember, toMember, introStatus, introInvitation}
|
in Right GroupMemberIntro {introId, reMember, toMember, introStatus, introInvitation}
|
||||||
toIntro _ = Left SEIntroNotFound
|
toIntro _ = Left SEIntroNotFound
|
||||||
|
|
||||||
createIntroReMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> MemberInfo -> (CommandId, ConnId) -> (CommandId, ConnId) -> Maybe ProfileId -> ExceptT StoreError IO GroupMember
|
createIntroReMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> MemberInfo -> (CommandId, ConnId) -> Maybe (CommandId, ConnId) -> Maybe ProfileId -> ExceptT StoreError IO GroupMember
|
||||||
createIntroReMember db user@User {userId} gInfo@GroupInfo {groupId} _host@GroupMember {memberContactId, activeConn} memInfo@(MemberInfo _ _ memberProfile) (groupCmdId, groupAgentConnId) (directCmdId, directAgentConnId) customUserProfileId = do
|
createIntroReMember db user@User {userId} gInfo@GroupInfo {groupId} _host@GroupMember {memberContactId, activeConn} memInfo@(MemberInfo _ _ memberChatVRange memberProfile) (groupCmdId, groupAgentConnId) directConnIds customUserProfileId = do
|
||||||
let cLevel = 1 + maybe 0 (connLevel :: Connection -> Int) activeConn
|
let mcvr = maybe chatInitialVRange fromChatVRange memberChatVRange
|
||||||
|
cLevel = 1 + maybe 0 (connLevel :: Connection -> Int) activeConn
|
||||||
currentTs <- liftIO getCurrentTime
|
currentTs <- liftIO getCurrentTime
|
||||||
Connection {connId = directConnId} <- liftIO $ createConnection_ db userId ConnContact Nothing directAgentConnId memberContactId Nothing customUserProfileId cLevel currentTs
|
newMember <- case directConnIds of
|
||||||
liftIO $ setCommandConnId db user directCmdId directConnId
|
Just (directCmdId, directAgentConnId) -> do
|
||||||
(localDisplayName, contactId, memProfileId) <- createContact_ db userId directConnId memberProfile "" (Just groupId) currentTs Nothing
|
Connection {connId = directConnId} <- liftIO $ createConnection_ db userId ConnContact Nothing directAgentConnId mcvr memberContactId Nothing customUserProfileId cLevel currentTs
|
||||||
|
liftIO $ setCommandConnId db user directCmdId directConnId
|
||||||
|
(localDisplayName, contactId, memProfileId) <- createContact_ db userId directConnId memberProfile "" (Just groupId) currentTs Nothing
|
||||||
|
pure $ NewGroupMember {memInfo, memCategory = GCPreMember, memStatus = GSMemIntroduced, memInvitedBy = IBUnknown, localDisplayName, memContactId = Just contactId, memProfileId}
|
||||||
|
Nothing -> do
|
||||||
|
(localDisplayName, memProfileId) <- createNewMemberProfile_ db user memInfo currentTs
|
||||||
|
pure $ NewGroupMember {memInfo, memCategory = GCPreMember, memStatus = GSMemIntroduced, memInvitedBy = IBUnknown, localDisplayName, memContactId = Nothing, memProfileId}
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
let newMember =
|
|
||||||
NewGroupMember
|
|
||||||
{ memInfo,
|
|
||||||
memCategory = GCPreMember,
|
|
||||||
memStatus = GSMemIntroduced,
|
|
||||||
memInvitedBy = IBUnknown,
|
|
||||||
localDisplayName,
|
|
||||||
memContactId = Just contactId,
|
|
||||||
memProfileId
|
|
||||||
}
|
|
||||||
member <- createNewMember_ db user gInfo newMember currentTs
|
member <- createNewMember_ db user gInfo newMember currentTs
|
||||||
conn@Connection {connId = groupConnId} <- createMemberConnection_ db userId (groupMemberId' member) groupAgentConnId memberContactId cLevel currentTs
|
conn@Connection {connId = groupConnId} <- createMemberConnection_ db userId (groupMemberId' member) groupAgentConnId mcvr memberContactId cLevel currentTs
|
||||||
liftIO $ setCommandConnId db user groupCmdId groupConnId
|
liftIO $ setCommandConnId db user groupCmdId groupConnId
|
||||||
pure (member :: GroupMember) {activeConn = Just conn}
|
pure (member :: GroupMember) {activeConn = Just conn}
|
||||||
|
|
||||||
createIntroToMemberContact :: DB.Connection -> User -> GroupMember -> GroupMember -> (CommandId, ConnId) -> (CommandId, ConnId) -> Maybe ProfileId -> IO ()
|
createIntroToMemberContact :: DB.Connection -> User -> GroupMember -> GroupMember -> VersionRange -> (CommandId, ConnId) -> Maybe (CommandId, ConnId) -> Maybe ProfileId -> IO ()
|
||||||
createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = viaContactId, activeConn} _to@GroupMember {groupMemberId, localDisplayName} (groupCmdId, groupAgentConnId) (directCmdId, directAgentConnId) customUserProfileId = do
|
createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = viaContactId, activeConn} _to@GroupMember {groupMemberId, localDisplayName} mcvr (groupCmdId, groupAgentConnId) directConnIds customUserProfileId = do
|
||||||
let cLevel = 1 + maybe 0 (connLevel :: Connection -> Int) activeConn
|
let cLevel = 1 + maybe 0 (connLevel :: Connection -> Int) activeConn
|
||||||
currentTs <- getCurrentTime
|
currentTs <- getCurrentTime
|
||||||
Connection {connId = groupConnId} <- createMemberConnection_ db userId groupMemberId groupAgentConnId viaContactId cLevel currentTs
|
Connection {connId = groupConnId} <- createMemberConnection_ db userId groupMemberId groupAgentConnId mcvr viaContactId cLevel currentTs
|
||||||
setCommandConnId db user groupCmdId groupConnId
|
setCommandConnId db user groupCmdId groupConnId
|
||||||
Connection {connId = directConnId} <- createConnection_ db userId ConnContact Nothing directAgentConnId viaContactId Nothing customUserProfileId cLevel currentTs
|
forM_ directConnIds $ \(directCmdId, directAgentConnId) -> do
|
||||||
setCommandConnId db user directCmdId directConnId
|
Connection {connId = directConnId} <- createConnection_ db userId ConnContact Nothing directAgentConnId mcvr viaContactId Nothing customUserProfileId cLevel currentTs
|
||||||
contactId <- createMemberContact_ directConnId currentTs
|
setCommandConnId db user directCmdId directConnId
|
||||||
updateMember_ contactId currentTs
|
contactId <- createMemberContact_ directConnId currentTs
|
||||||
|
updateMember_ contactId currentTs
|
||||||
where
|
where
|
||||||
createMemberContact_ :: Int64 -> UTCTime -> IO Int64
|
createMemberContact_ :: Int64 -> UTCTime -> IO Int64
|
||||||
createMemberContact_ connId ts = do
|
createMemberContact_ connId ts = do
|
||||||
@ -966,8 +977,8 @@ createIntroToMemberContact db user@User {userId} GroupMember {memberContactId =
|
|||||||
|]
|
|]
|
||||||
[":contact_id" := contactId, ":updated_at" := ts, ":group_member_id" := groupMemberId]
|
[":contact_id" := contactId, ":updated_at" := ts, ":group_member_id" := groupMemberId]
|
||||||
|
|
||||||
createMemberConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> Maybe Int64 -> Int -> UTCTime -> IO Connection
|
createMemberConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> VersionRange -> Maybe Int64 -> Int -> UTCTime -> IO Connection
|
||||||
createMemberConnection_ db userId groupMemberId agentConnId viaContact = createConnection_ db userId ConnMember (Just groupMemberId) agentConnId viaContact Nothing Nothing
|
createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange viaContact = createConnection_ db userId ConnMember (Just groupMemberId) agentConnId peerChatVRange viaContact Nothing Nothing
|
||||||
|
|
||||||
getViaGroupMember :: DB.Connection -> User -> Contact -> IO (Maybe (GroupInfo, GroupMember))
|
getViaGroupMember :: DB.Connection -> User -> Contact -> IO (Maybe (GroupInfo, GroupMember))
|
||||||
getViaGroupMember db User {userId, userContactId} Contact {contactId} =
|
getViaGroupMember db User {userId, userContactId} Contact {contactId} =
|
||||||
@ -987,7 +998,8 @@ getViaGroupMember db User {userId, userContactId} Contact {contactId} =
|
|||||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
||||||
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
|
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM group_members m
|
FROM group_members m
|
||||||
JOIN contacts ct ON ct.contact_id = m.contact_id
|
JOIN contacts ct ON ct.contact_id = m.contact_id
|
||||||
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
||||||
@ -1020,7 +1032,8 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} =
|
|||||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||||
p.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
p.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM contacts ct
|
FROM contacts ct
|
||||||
JOIN contact_profiles p ON ct.contact_profile_id = p.contact_profile_id
|
JOIN contact_profiles p ON ct.contact_profile_id = p.contact_profile_id
|
||||||
JOIN connections c ON c.connection_id = (
|
JOIN connections c ON c.connection_id = (
|
||||||
@ -1336,3 +1349,9 @@ getXGrpMemIntroContGroup db User {userId} GroupMember {groupMemberId} = do
|
|||||||
toCont (hostConnId, connReq_) = case connReq_ of
|
toCont (hostConnId, connReq_) = case connReq_ of
|
||||||
Just connReq -> Just (hostConnId, connReq)
|
Just connReq -> Just (hostConnId, connReq)
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
|
getHostConnId :: DB.Connection -> User -> GroupId -> ExceptT StoreError IO GroupMemberId
|
||||||
|
getHostConnId db user@User {userId} groupId = do
|
||||||
|
hostMemberId <- getHostMemberId_ db user groupId
|
||||||
|
ExceptT . firstRow fromOnly (SEConnectionNotFoundByMemberId hostMemberId) $
|
||||||
|
DB.query db "SELECT connection_id FROM connections WHERE user_id = ? AND group_member_id = ?" (userId, hostMemberId)
|
||||||
|
@ -479,6 +479,7 @@ getDirectChatPreviews_ db user@User {userId} = do
|
|||||||
-- Connection
|
-- Connection
|
||||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version,
|
||||||
-- ChatStats
|
-- ChatStats
|
||||||
COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat,
|
COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat,
|
||||||
-- ChatItem
|
-- ChatItem
|
||||||
@ -609,7 +610,8 @@ getContactRequestChatPreviews_ db User {userId} =
|
|||||||
[sql|
|
[sql|
|
||||||
SELECT
|
SELECT
|
||||||
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
|
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
|
||||||
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at
|
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at,
|
||||||
|
cr.peer_chat_min_version, cr.peer_chat_max_version
|
||||||
FROM contact_requests cr
|
FROM contact_requests cr
|
||||||
JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id
|
JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id
|
||||||
JOIN contact_profiles p ON p.contact_profile_id = cr.contact_profile_id
|
JOIN contact_profiles p ON p.contact_profile_id = cr.contact_profile_id
|
||||||
|
@ -77,6 +77,7 @@ import Simplex.Chat.Migrations.M20230705_delivery_receipts
|
|||||||
import Simplex.Chat.Migrations.M20230721_group_snd_item_statuses
|
import Simplex.Chat.Migrations.M20230721_group_snd_item_statuses
|
||||||
import Simplex.Chat.Migrations.M20230814_indexes
|
import Simplex.Chat.Migrations.M20230814_indexes
|
||||||
import Simplex.Chat.Migrations.M20230827_file_encryption
|
import Simplex.Chat.Migrations.M20230827_file_encryption
|
||||||
|
import Simplex.Chat.Migrations.M20230829_connections_chat_vrange
|
||||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
||||||
|
|
||||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||||
@ -153,7 +154,8 @@ schemaMigrations =
|
|||||||
("20230705_delivery_receipts", m20230705_delivery_receipts, Just down_m20230705_delivery_receipts),
|
("20230705_delivery_receipts", m20230705_delivery_receipts, Just down_m20230705_delivery_receipts),
|
||||||
("20230721_group_snd_item_statuses", m20230721_group_snd_item_statuses, Just down_m20230721_group_snd_item_statuses),
|
("20230721_group_snd_item_statuses", m20230721_group_snd_item_statuses, Just down_m20230721_group_snd_item_statuses),
|
||||||
("20230814_indexes", m20230814_indexes, Just down_m20230814_indexes),
|
("20230814_indexes", m20230814_indexes, Just down_m20230814_indexes),
|
||||||
("20230827_file_encryption", m20230827_file_encryption, Just down_m20230827_file_encryption)
|
("20230827_file_encryption", m20230827_file_encryption, Just down_m20230827_file_encryption),
|
||||||
|
("20230829_connections_chat_vrange", m20230829_connections_chat_vrange, Just down_m20230829_connections_chat_vrange)
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | The list of migrations in ascending order by date
|
-- | The list of migrations in ascending order by date
|
||||||
|
@ -302,7 +302,7 @@ createUserContactLink db User {userId} agentConnId cReq =
|
|||||||
"INSERT INTO user_contact_links (user_id, conn_req_contact, created_at, updated_at) VALUES (?,?,?,?)"
|
"INSERT INTO user_contact_links (user_id, conn_req_contact, created_at, updated_at) VALUES (?,?,?,?)"
|
||||||
(userId, cReq, currentTs, currentTs)
|
(userId, cReq, currentTs, currentTs)
|
||||||
userContactLinkId <- insertedRowId db
|
userContactLinkId <- insertedRowId db
|
||||||
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId Nothing Nothing Nothing 0 currentTs
|
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId chatInitialVRange Nothing Nothing Nothing 0 currentTs
|
||||||
|
|
||||||
getUserAddressConnections :: DB.Connection -> User -> ExceptT StoreError IO [Connection]
|
getUserAddressConnections :: DB.Connection -> User -> ExceptT StoreError IO [Connection]
|
||||||
getUserAddressConnections db User {userId} = do
|
getUserAddressConnections db User {userId} = do
|
||||||
@ -316,7 +316,8 @@ getUserAddressConnections db User {userId} = do
|
|||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version
|
||||||
FROM connections c
|
FROM connections c
|
||||||
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
||||||
WHERE c.user_id = ? AND uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL
|
WHERE c.user_id = ? AND uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL
|
||||||
@ -331,6 +332,7 @@ getUserContactLinks db User {userId} =
|
|||||||
[sql|
|
[sql|
|
||||||
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||||
|
c.peer_chat_min_version, c.peer_chat_max_version,
|
||||||
uc.user_contact_link_id, uc.conn_req_contact, uc.group_id
|
uc.user_contact_link_id, uc.conn_req_contact, uc.group_id
|
||||||
FROM connections c
|
FROM connections c
|
||||||
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
||||||
|
@ -17,8 +17,8 @@ import Control.Monad.Except
|
|||||||
import Crypto.Random (ChaChaDRG, randomBytesGenerate)
|
import Crypto.Random (ChaChaDRG, randomBytesGenerate)
|
||||||
import Data.Aeson (ToJSON)
|
import Data.Aeson (ToJSON)
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
import Data.ByteString.Char8 (ByteString)
|
|
||||||
import qualified Data.ByteString.Base64 as B64
|
import qualified Data.ByteString.Base64 as B64
|
||||||
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
import Data.Maybe (fromMaybe, isJust, listToMaybe)
|
import Data.Maybe (fromMaybe, isJust, listToMaybe)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
@ -37,6 +37,7 @@ import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
|
|||||||
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
||||||
import Simplex.Messaging.Util (allFinally)
|
import Simplex.Messaging.Util (allFinally)
|
||||||
|
import Simplex.Messaging.Version
|
||||||
import UnliftIO.STM
|
import UnliftIO.STM
|
||||||
|
|
||||||
-- These error type constructors must be added to mobile apps
|
-- These error type constructors must be added to mobile apps
|
||||||
@ -50,6 +51,7 @@ data StoreError
|
|||||||
| SEUserNotFoundByContactRequestId {contactRequestId :: Int64}
|
| SEUserNotFoundByContactRequestId {contactRequestId :: Int64}
|
||||||
| SEContactNotFound {contactId :: ContactId}
|
| SEContactNotFound {contactId :: ContactId}
|
||||||
| SEContactNotFoundByName {contactName :: ContactName}
|
| SEContactNotFoundByName {contactName :: ContactName}
|
||||||
|
| SEContactNotFoundByMemberId {groupMemberId :: GroupMemberId}
|
||||||
| SEContactNotReady {contactName :: ContactName}
|
| SEContactNotReady {contactName :: ContactName}
|
||||||
| SEDuplicateContactLink
|
| SEDuplicateContactLink
|
||||||
| SEUserContactLinkNotFound
|
| SEUserContactLinkNotFound
|
||||||
@ -77,6 +79,7 @@ data StoreError
|
|||||||
| SERcvFileNotFoundXFTP {agentRcvFileId :: AgentRcvFileId}
|
| SERcvFileNotFoundXFTP {agentRcvFileId :: AgentRcvFileId}
|
||||||
| SEConnectionNotFound {agentConnId :: AgentConnId}
|
| SEConnectionNotFound {agentConnId :: AgentConnId}
|
||||||
| SEConnectionNotFoundById {connId :: Int64}
|
| SEConnectionNotFoundById {connId :: Int64}
|
||||||
|
| SEConnectionNotFoundByMemberId {groupMemberId :: GroupMemberId}
|
||||||
| SEPendingConnectionNotFound {connId :: Int64}
|
| SEPendingConnectionNotFound {connId :: Int64}
|
||||||
| SEIntroNotFound
|
| SEIntroNotFound
|
||||||
| SEUniqueID
|
| SEUniqueID
|
||||||
@ -132,15 +135,16 @@ toFileInfo (fileId, fileStatus, filePath) = CIFileInfo {fileId, fileStatus, file
|
|||||||
|
|
||||||
type EntityIdsRow = (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64)
|
type EntityIdsRow = (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64)
|
||||||
|
|
||||||
type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Bool, Maybe GroupLinkId, Maybe Int64, ConnStatus, ConnType, LocalAlias) :. EntityIdsRow :. (UTCTime, Maybe Text, Maybe UTCTime, Int)
|
type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Bool, Maybe GroupLinkId, Maybe Int64, ConnStatus, ConnType, LocalAlias) :. EntityIdsRow :. (UTCTime, Maybe Text, Maybe UTCTime, Int, Version, Version)
|
||||||
|
|
||||||
type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Bool, Maybe GroupLinkId, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe LocalAlias) :. EntityIdsRow :. (Maybe UTCTime, Maybe Text, Maybe UTCTime, Maybe Int)
|
type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Bool, Maybe GroupLinkId, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe LocalAlias) :. EntityIdsRow :. (Maybe UTCTime, Maybe Text, Maybe UTCTime, Maybe Int, Maybe Version, Maybe Version)
|
||||||
|
|
||||||
toConnection :: ConnectionRow -> Connection
|
toConnection :: ConnectionRow -> Connection
|
||||||
toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, authErrCounter)) =
|
toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, authErrCounter, minVer, maxVer)) =
|
||||||
let entityId = entityId_ connType
|
let entityId = entityId_ connType
|
||||||
connectionCode = SecurityCode <$> code_ <*> verifiedAt_
|
connectionCode = SecurityCode <$> code_ <*> verifiedAt_
|
||||||
in Connection {connId, agentConnId = AgentConnId acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias, entityId, connectionCode, authErrCounter, createdAt}
|
peerChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
|
||||||
|
in Connection {connId, agentConnId = AgentConnId acId, peerChatVRange, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias, entityId, connectionCode, authErrCounter, createdAt}
|
||||||
where
|
where
|
||||||
entityId_ :: ConnType -> Maybe Int64
|
entityId_ :: ConnType -> Maybe Int64
|
||||||
entityId_ ConnContact = contactId
|
entityId_ ConnContact = contactId
|
||||||
@ -150,12 +154,12 @@ toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroup
|
|||||||
entityId_ ConnUserContact = userContactLinkId
|
entityId_ ConnUserContact = userContactLinkId
|
||||||
|
|
||||||
toMaybeConnection :: MaybeConnectionRow -> Maybe Connection
|
toMaybeConnection :: MaybeConnectionRow -> Maybe Connection
|
||||||
toMaybeConnection ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, Just viaGroupLink, groupLinkId, customUserProfileId, Just connStatus, Just connType, Just localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (Just createdAt, code_, verifiedAt_, Just authErrCounter)) =
|
toMaybeConnection ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, Just viaGroupLink, groupLinkId, customUserProfileId, Just connStatus, Just connType, Just localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (Just createdAt, code_, verifiedAt_, Just authErrCounter, Just minVer, Just maxVer)) =
|
||||||
Just $ toConnection ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, authErrCounter))
|
Just $ toConnection ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, authErrCounter, minVer, maxVer))
|
||||||
toMaybeConnection _ = Nothing
|
toMaybeConnection _ = Nothing
|
||||||
|
|
||||||
createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> IO Connection
|
createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> VersionRange -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> IO Connection
|
||||||
createConnection_ db userId connType entityId acId viaContact viaUserContactLink customUserProfileId connLevel currentTs = do
|
createConnection_ db userId connType entityId acId peerChatVRange@(VersionRange minV maxV) viaContact viaUserContactLink customUserProfileId connLevel currentTs = do
|
||||||
viaLinkGroupId :: Maybe Int64 <- fmap join . forM viaUserContactLink $ \ucLinkId ->
|
viaLinkGroupId :: Maybe Int64 <- fmap join . forM viaUserContactLink $ \ucLinkId ->
|
||||||
maybeFirstRow fromOnly $ DB.query db "SELECT group_id FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? AND group_id IS NOT NULL" (userId, ucLinkId)
|
maybeFirstRow fromOnly $ DB.query db "SELECT group_id FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? AND group_id IS NOT NULL" (userId, ucLinkId)
|
||||||
let viaGroupLink = isJust viaLinkGroupId
|
let viaGroupLink = isJust viaLinkGroupId
|
||||||
@ -164,17 +168,30 @@ createConnection_ db userId connType entityId acId viaContact viaUserContactLink
|
|||||||
[sql|
|
[sql|
|
||||||
INSERT INTO connections (
|
INSERT INTO connections (
|
||||||
user_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, custom_user_profile_id, conn_status, conn_type,
|
user_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, custom_user_profile_id, conn_status, conn_type,
|
||||||
contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, updated_at
|
contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, updated_at,
|
||||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
peer_chat_min_version, peer_chat_max_version
|
||||||
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
|]
|
|]
|
||||||
( (userId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, customUserProfileId, ConnNew, connType)
|
( (userId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, customUserProfileId, ConnNew, connType)
|
||||||
:. (ent ConnContact, ent ConnMember, ent ConnSndFile, ent ConnRcvFile, ent ConnUserContact, currentTs, currentTs)
|
:. (ent ConnContact, ent ConnMember, ent ConnSndFile, ent ConnRcvFile, ent ConnUserContact, currentTs, currentTs)
|
||||||
|
:. (minV, maxV)
|
||||||
)
|
)
|
||||||
connId <- insertedRowId db
|
connId <- insertedRowId db
|
||||||
pure Connection {connId, agentConnId = AgentConnId acId, connType, entityId, viaContact, viaUserContactLink, viaGroupLink, groupLinkId = Nothing, customUserProfileId, connLevel, connStatus = ConnNew, localAlias = "", createdAt = currentTs, connectionCode = Nothing, authErrCounter = 0}
|
pure Connection {connId, agentConnId = AgentConnId acId, peerChatVRange, connType, entityId, viaContact, viaUserContactLink, viaGroupLink, groupLinkId = Nothing, customUserProfileId, connLevel, connStatus = ConnNew, localAlias = "", createdAt = currentTs, connectionCode = Nothing, authErrCounter = 0}
|
||||||
where
|
where
|
||||||
ent ct = if connType == ct then entityId else Nothing
|
ent ct = if connType == ct then entityId else Nothing
|
||||||
|
|
||||||
|
setPeerChatVRange :: DB.Connection -> Int64 -> VersionRange -> IO ()
|
||||||
|
setPeerChatVRange db connId (VersionRange minVer maxVer) =
|
||||||
|
DB.execute
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
UPDATE connections
|
||||||
|
SET peer_chat_min_version = ?, peer_chat_max_version = ?
|
||||||
|
WHERE connection_id = ?
|
||||||
|
|]
|
||||||
|
(minVer, maxVer, connId)
|
||||||
|
|
||||||
setCommandConnId :: DB.Connection -> User -> CommandId -> Int64 -> IO ()
|
setCommandConnId :: DB.Connection -> User -> CommandId -> Int64 -> IO ()
|
||||||
setCommandConnId db User {userId} cmdId connId = do
|
setCommandConnId db User {userId} cmdId connId = do
|
||||||
updatedAt <- getCurrentTime
|
updatedAt <- getCurrentTime
|
||||||
@ -256,12 +273,13 @@ getProfileById db userId profileId =
|
|||||||
toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) -> LocalProfile
|
toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) -> LocalProfile
|
||||||
toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
|
toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
|
||||||
|
|
||||||
type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, Maybe Preferences, UTCTime, UTCTime)
|
type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, Maybe Preferences, UTCTime, UTCTime, Version, Version)
|
||||||
|
|
||||||
toContactRequest :: ContactRequestRow -> UserContactRequest
|
toContactRequest :: ContactRequestRow -> UserContactRequest
|
||||||
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, preferences, createdAt, updatedAt)) = do
|
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, preferences, createdAt, updatedAt, minVer, maxVer)) = do
|
||||||
let profile = Profile {displayName, fullName, image, contactLink, preferences}
|
let profile = Profile {displayName, fullName, image, contactLink, preferences}
|
||||||
in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, localDisplayName, profileId, profile, xContactId, createdAt, updatedAt}
|
cReqChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
|
||||||
|
in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, createdAt, updatedAt}
|
||||||
|
|
||||||
userQuery :: Query
|
userQuery :: Query
|
||||||
userQuery =
|
userQuery =
|
||||||
|
@ -47,6 +47,7 @@ import Simplex.Messaging.Encoding.String
|
|||||||
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, sumTypeJSON, taggedObjectJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, sumTypeJSON, taggedObjectJSON)
|
||||||
import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI)
|
import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI)
|
||||||
import Simplex.Messaging.Util ((<$?>))
|
import Simplex.Messaging.Util ((<$?>))
|
||||||
|
import Simplex.Messaging.Version
|
||||||
|
|
||||||
class IsContact a where
|
class IsContact a where
|
||||||
contactId' :: a -> ContactId
|
contactId' :: a -> ContactId
|
||||||
@ -232,6 +233,7 @@ data UserContactRequest = UserContactRequest
|
|||||||
agentInvitationId :: AgentInvId,
|
agentInvitationId :: AgentInvId,
|
||||||
userContactLinkId :: Int64,
|
userContactLinkId :: Int64,
|
||||||
agentContactConnId :: AgentConnId, -- connection id of user contact
|
agentContactConnId :: AgentConnId, -- connection id of user contact
|
||||||
|
cReqChatVRange :: VersionRange,
|
||||||
localDisplayName :: ContactName,
|
localDisplayName :: ContactName,
|
||||||
profileId :: Int64,
|
profileId :: Int64,
|
||||||
profile :: Profile,
|
profile :: Profile,
|
||||||
@ -538,24 +540,31 @@ instance ToJSON MemberIdRole where toEncoding = J.genericToEncoding J.defaultOpt
|
|||||||
|
|
||||||
data IntroInvitation = IntroInvitation
|
data IntroInvitation = IntroInvitation
|
||||||
{ groupConnReq :: ConnReqInvitation,
|
{ groupConnReq :: ConnReqInvitation,
|
||||||
directConnReq :: ConnReqInvitation
|
directConnReq :: Maybe ConnReqInvitation
|
||||||
}
|
}
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
instance ToJSON IntroInvitation where toEncoding = J.genericToEncoding J.defaultOptions
|
instance ToJSON IntroInvitation where
|
||||||
|
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
data MemberInfo = MemberInfo
|
data MemberInfo = MemberInfo
|
||||||
{ memberId :: MemberId,
|
{ memberId :: MemberId,
|
||||||
memberRole :: GroupMemberRole,
|
memberRole :: GroupMemberRole,
|
||||||
|
v :: Maybe ChatVersionRange,
|
||||||
profile :: Profile
|
profile :: Profile
|
||||||
}
|
}
|
||||||
deriving (Eq, Show, Generic, FromJSON)
|
deriving (Eq, Show, Generic, FromJSON)
|
||||||
|
|
||||||
instance ToJSON MemberInfo where toEncoding = J.genericToEncoding J.defaultOptions
|
instance ToJSON MemberInfo where
|
||||||
|
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
memberInfo :: GroupMember -> MemberInfo
|
memberInfo :: GroupMember -> MemberInfo
|
||||||
memberInfo GroupMember {memberId, memberRole, memberProfile} =
|
memberInfo GroupMember {memberId, memberRole, memberProfile, activeConn} =
|
||||||
MemberInfo memberId memberRole (fromLocalProfile memberProfile)
|
MemberInfo memberId memberRole memberChatVRange (fromLocalProfile memberProfile)
|
||||||
|
where
|
||||||
|
memberChatVRange = ChatVersionRange . peerChatVRange <$> activeConn
|
||||||
|
|
||||||
data ReceivedGroupInvitation = ReceivedGroupInvitation
|
data ReceivedGroupInvitation = ReceivedGroupInvitation
|
||||||
{ fromMember :: GroupMember,
|
{ fromMember :: GroupMember,
|
||||||
@ -1158,6 +1167,7 @@ type ConnReqContact = ConnectionRequestUri 'CMContact
|
|||||||
data Connection = Connection
|
data Connection = Connection
|
||||||
{ connId :: Int64,
|
{ connId :: Int64,
|
||||||
agentConnId :: AgentConnId,
|
agentConnId :: AgentConnId,
|
||||||
|
peerChatVRange :: VersionRange,
|
||||||
connLevel :: Int,
|
connLevel :: Int,
|
||||||
viaContact :: Maybe Int64, -- group member contact ID, if not direct connection
|
viaContact :: Maybe Int64, -- group member contact ID, if not direct connection
|
||||||
viaUserContactLink :: Maybe Int64, -- user contact link ID, if connected via "user address"
|
viaUserContactLink :: Maybe Int64, -- user contact link ID, if connected via "user address"
|
||||||
@ -1468,3 +1478,15 @@ instance ProtocolTypeI p => ToJSON (ServerCfg p) where
|
|||||||
|
|
||||||
instance ProtocolTypeI p => FromJSON (ServerCfg p) where
|
instance ProtocolTypeI p => FromJSON (ServerCfg p) where
|
||||||
parseJSON = J.genericParseJSON J.defaultOptions {J.omitNothingFields = True}
|
parseJSON = J.genericParseJSON J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
|
newtype ChatVersionRange = ChatVersionRange {fromChatVRange :: VersionRange} deriving (Eq, Show)
|
||||||
|
|
||||||
|
chatInitialVRange :: VersionRange
|
||||||
|
chatInitialVRange = versionToRange 1
|
||||||
|
|
||||||
|
instance FromJSON ChatVersionRange where
|
||||||
|
parseJSON v = ChatVersionRange <$> strParseJSON "ChatVersionRange" v
|
||||||
|
|
||||||
|
instance ToJSON ChatVersionRange where
|
||||||
|
toJSON (ChatVersionRange vr) = strToJSON vr
|
||||||
|
toEncoding (ChatVersionRange vr) = strToJEncoding vr
|
||||||
|
@ -59,6 +59,7 @@ import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, Pro
|
|||||||
import qualified Simplex.Messaging.Protocol as SMP
|
import qualified Simplex.Messaging.Protocol as SMP
|
||||||
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
||||||
import Simplex.Messaging.Util (bshow, tshow)
|
import Simplex.Messaging.Util (bshow, tshow)
|
||||||
|
import Simplex.Messaging.Version hiding (version)
|
||||||
import System.Console.ANSI.Types
|
import System.Console.ANSI.Types
|
||||||
|
|
||||||
type CurrentTime = UTCTime
|
type CurrentTime = UTCTime
|
||||||
@ -952,7 +953,7 @@ viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout} =
|
|||||||
]
|
]
|
||||||
|
|
||||||
viewContactInfo :: Contact -> ConnectionStats -> Maybe Profile -> [StyledString]
|
viewContactInfo :: Contact -> ConnectionStats -> Maybe Profile -> [StyledString]
|
||||||
viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}} stats incognitoProfile =
|
viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn} stats incognitoProfile =
|
||||||
["contact ID: " <> sShow contactId]
|
["contact ID: " <> sShow contactId]
|
||||||
<> viewConnectionStats stats
|
<> viewConnectionStats stats
|
||||||
<> maybe [] (\l -> ["contact address: " <> (plain . strEncode) l]) contactLink
|
<> maybe [] (\l -> ["contact address: " <> (plain . strEncode) l]) contactLink
|
||||||
@ -962,6 +963,7 @@ viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, conta
|
|||||||
incognitoProfile
|
incognitoProfile
|
||||||
<> ["alias: " <> plain localAlias | localAlias /= ""]
|
<> ["alias: " <> plain localAlias | localAlias /= ""]
|
||||||
<> [viewConnectionVerified (contactSecurityCode ct)]
|
<> [viewConnectionVerified (contactSecurityCode ct)]
|
||||||
|
<> [viewPeerChatVRange (peerChatVRange activeConn)]
|
||||||
|
|
||||||
viewGroupInfo :: GroupInfo -> GroupSummary -> [StyledString]
|
viewGroupInfo :: GroupInfo -> GroupSummary -> [StyledString]
|
||||||
viewGroupInfo GroupInfo {groupId} s =
|
viewGroupInfo GroupInfo {groupId} s =
|
||||||
@ -970,18 +972,22 @@ viewGroupInfo GroupInfo {groupId} s =
|
|||||||
]
|
]
|
||||||
|
|
||||||
viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [StyledString]
|
viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [StyledString]
|
||||||
viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProfile = LocalProfile {localAlias}} stats =
|
viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProfile = LocalProfile {localAlias}, activeConn} stats =
|
||||||
[ "group ID: " <> sShow groupId,
|
[ "group ID: " <> sShow groupId,
|
||||||
"member ID: " <> sShow groupMemberId
|
"member ID: " <> sShow groupMemberId
|
||||||
]
|
]
|
||||||
<> maybe ["member not connected"] viewConnectionStats stats
|
<> maybe ["member not connected"] viewConnectionStats stats
|
||||||
<> ["alias: " <> plain localAlias | localAlias /= ""]
|
<> ["alias: " <> plain localAlias | localAlias /= ""]
|
||||||
<> [viewConnectionVerified (memberSecurityCode m) | isJust stats]
|
<> [viewConnectionVerified (memberSecurityCode m) | isJust stats]
|
||||||
|
<> maybe [] (\ac -> [viewPeerChatVRange (peerChatVRange ac)]) activeConn
|
||||||
|
|
||||||
viewConnectionVerified :: Maybe SecurityCode -> StyledString
|
viewConnectionVerified :: Maybe SecurityCode -> StyledString
|
||||||
viewConnectionVerified (Just _) = "connection verified" -- TODO show verification time?
|
viewConnectionVerified (Just _) = "connection verified" -- TODO show verification time?
|
||||||
viewConnectionVerified _ = "connection not verified, use " <> highlight' "/code" <> " command to see security code"
|
viewConnectionVerified _ = "connection not verified, use " <> highlight' "/code" <> " command to see security code"
|
||||||
|
|
||||||
|
viewPeerChatVRange :: VersionRange -> StyledString
|
||||||
|
viewPeerChatVRange (VersionRange minVer maxVer) = "peer chat protocol version range: (" <> sShow minVer <> ", " <> sShow maxVer <> ")"
|
||||||
|
|
||||||
viewConnectionStats :: ConnectionStats -> [StyledString]
|
viewConnectionStats :: ConnectionStats -> [StyledString]
|
||||||
viewConnectionStats ConnectionStats {rcvQueuesInfo, sndQueuesInfo} =
|
viewConnectionStats ConnectionStats {rcvQueuesInfo, sndQueuesInfo} =
|
||||||
["receiving messages via: " <> viewRcvQueuesInfo rcvQueuesInfo | not $ null rcvQueuesInfo]
|
["receiving messages via: " <> viewRcvQueuesInfo rcvQueuesInfo | not $ null rcvQueuesInfo]
|
||||||
|
@ -49,7 +49,7 @@ extra-deps:
|
|||||||
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
||||||
# - ../simplexmq
|
# - ../simplexmq
|
||||||
- github: simplex-chat/simplexmq
|
- github: simplex-chat/simplexmq
|
||||||
commit: 980e5c4d1ec15f44290542fd2a5d1c08456f00d1
|
commit: 351f42650c57f310fc1ea858ff9b7178823f1fd4
|
||||||
- github: kazu-yamamoto/http2
|
- github: kazu-yamamoto/http2
|
||||||
commit: b5a1b7200cf5bc7044af34ba325284271f6dff25
|
commit: b5a1b7200cf5bc7044af34ba325284271f6dff25
|
||||||
# - ../direct-sqlcipher
|
# - ../direct-sqlcipher
|
||||||
|
@ -13,13 +13,14 @@ import Control.Monad (forM_)
|
|||||||
import Directory.Options
|
import Directory.Options
|
||||||
import Directory.Service
|
import Directory.Service
|
||||||
import Directory.Store
|
import Directory.Store
|
||||||
|
import GHC.IO.Handle (hClose)
|
||||||
import Simplex.Chat.Bot.KnownContacts
|
import Simplex.Chat.Bot.KnownContacts
|
||||||
|
import Simplex.Chat.Controller (ChatConfig (..))
|
||||||
import Simplex.Chat.Core
|
import Simplex.Chat.Core
|
||||||
import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..))
|
import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..))
|
||||||
import Simplex.Chat.Types (GroupMemberRole (..), Profile (..))
|
import Simplex.Chat.Types (GroupMemberRole (..), Profile (..))
|
||||||
import System.FilePath ((</>))
|
import System.FilePath ((</>))
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import GHC.IO.Handle (hClose)
|
|
||||||
|
|
||||||
directoryServiceTests :: SpecWith FilePath
|
directoryServiceTests :: SpecWith FilePath
|
||||||
directoryServiceTests = do
|
directoryServiceTests = do
|
||||||
@ -232,10 +233,10 @@ testJoinGroup tmp =
|
|||||||
dan <## "bob (Bob): contact is connected"
|
dan <## "bob (Bob): contact is connected"
|
||||||
dan <## "#privacy: you joined the group"
|
dan <## "#privacy: you joined the group"
|
||||||
dan <# ("#privacy bob> " <> welcomeMsg)
|
dan <# ("#privacy bob> " <> welcomeMsg)
|
||||||
dan <###
|
dan
|
||||||
[ "#privacy: member SimpleX-Directory is connected",
|
<### [ "#privacy: member SimpleX-Directory is connected",
|
||||||
"#privacy: member cath (Catherine) is connected"
|
"#privacy: member cath (Catherine) is connected"
|
||||||
],
|
],
|
||||||
do
|
do
|
||||||
cath <## "#privacy: bob added dan (Daniel) to the group (connecting...)"
|
cath <## "#privacy: bob added dan (Daniel) to the group (connecting...)"
|
||||||
cath <## "#privacy: new member dan is connected"
|
cath <## "#privacy: new member dan is connected"
|
||||||
@ -243,9 +244,9 @@ testJoinGroup tmp =
|
|||||||
|
|
||||||
testDelistedOwnerLeaves :: HasCallStack => FilePath -> IO ()
|
testDelistedOwnerLeaves :: HasCallStack => FilePath -> IO ()
|
||||||
testDelistedOwnerLeaves tmp =
|
testDelistedOwnerLeaves tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -259,9 +260,9 @@ testDelistedOwnerLeaves tmp =
|
|||||||
|
|
||||||
testDelistedOwnerRemoved :: HasCallStack => FilePath -> IO ()
|
testDelistedOwnerRemoved :: HasCallStack => FilePath -> IO ()
|
||||||
testDelistedOwnerRemoved tmp =
|
testDelistedOwnerRemoved tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -274,9 +275,9 @@ testDelistedOwnerRemoved tmp =
|
|||||||
|
|
||||||
testNotDelistedMemberLeaves :: HasCallStack => FilePath -> IO ()
|
testNotDelistedMemberLeaves :: HasCallStack => FilePath -> IO ()
|
||||||
testNotDelistedMemberLeaves tmp =
|
testNotDelistedMemberLeaves tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -286,10 +287,10 @@ testNotDelistedMemberLeaves tmp =
|
|||||||
groupFound cath "privacy"
|
groupFound cath "privacy"
|
||||||
|
|
||||||
testNotDelistedMemberRemoved :: HasCallStack => FilePath -> IO ()
|
testNotDelistedMemberRemoved :: HasCallStack => FilePath -> IO ()
|
||||||
testNotDelistedMemberRemoved tmp =
|
testNotDelistedMemberRemoved tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -299,9 +300,9 @@ testNotDelistedMemberRemoved tmp =
|
|||||||
|
|
||||||
testDelistedServiceRemoved :: HasCallStack => FilePath -> IO ()
|
testDelistedServiceRemoved :: HasCallStack => FilePath -> IO ()
|
||||||
testDelistedServiceRemoved tmp =
|
testDelistedServiceRemoved tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -316,9 +317,9 @@ testDelistedServiceRemoved tmp =
|
|||||||
|
|
||||||
testDelistedRoleChanges :: HasCallStack => FilePath -> IO ()
|
testDelistedRoleChanges :: HasCallStack => FilePath -> IO ()
|
||||||
testDelistedRoleChanges tmp =
|
testDelistedRoleChanges tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -362,9 +363,9 @@ testDelistedRoleChanges tmp =
|
|||||||
|
|
||||||
testNotDelistedMemberRoleChanged :: HasCallStack => FilePath -> IO ()
|
testNotDelistedMemberRoleChanged :: HasCallStack => FilePath -> IO ()
|
||||||
testNotDelistedMemberRoleChanged tmp =
|
testNotDelistedMemberRoleChanged tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -426,9 +427,9 @@ testNotApprovedBadRoles tmp =
|
|||||||
|
|
||||||
testRegOwnerChangedProfile :: HasCallStack => FilePath -> IO ()
|
testRegOwnerChangedProfile :: HasCallStack => FilePath -> IO ()
|
||||||
testRegOwnerChangedProfile tmp =
|
testRegOwnerChangedProfile tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -445,9 +446,9 @@ testRegOwnerChangedProfile tmp =
|
|||||||
|
|
||||||
testAnotherOwnerChangedProfile :: HasCallStack => FilePath -> IO ()
|
testAnotherOwnerChangedProfile :: HasCallStack => FilePath -> IO ()
|
||||||
testAnotherOwnerChangedProfile tmp =
|
testAnotherOwnerChangedProfile tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -464,9 +465,9 @@ testAnotherOwnerChangedProfile tmp =
|
|||||||
|
|
||||||
testRegOwnerRemovedLink :: HasCallStack => FilePath -> IO ()
|
testRegOwnerRemovedLink :: HasCallStack => FilePath -> IO ()
|
||||||
testRegOwnerRemovedLink tmp =
|
testRegOwnerRemovedLink tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -497,9 +498,9 @@ testRegOwnerRemovedLink tmp =
|
|||||||
|
|
||||||
testAnotherOwnerRemovedLink :: HasCallStack => FilePath -> IO ()
|
testAnotherOwnerRemovedLink :: HasCallStack => FilePath -> IO ()
|
||||||
testAnotherOwnerRemovedLink tmp =
|
testAnotherOwnerRemovedLink tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
addCathAsOwner bob cath
|
addCathAsOwner bob cath
|
||||||
@ -646,9 +647,9 @@ testDuplicateProhibitApproval tmp =
|
|||||||
|
|
||||||
testListUserGroups :: HasCallStack => FilePath -> IO ()
|
testListUserGroups :: HasCallStack => FilePath -> IO ()
|
||||||
testListUserGroups tmp =
|
testListUserGroups tmp =
|
||||||
withDirectoryService tmp $ \superUser dsLink ->
|
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob ->
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "bob" bobProfile $ \bob ->
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp testCfgCreateGroupDirect "cath" cathProfile $ \cath -> do
|
||||||
bob `connectVia` dsLink
|
bob `connectVia` dsLink
|
||||||
cath `connectVia` dsLink
|
cath `connectVia` dsLink
|
||||||
registerGroup superUser bob "privacy" "Privacy"
|
registerGroup superUser bob "privacy" "Privacy"
|
||||||
@ -686,15 +687,15 @@ testRestoreDirectory tmp = do
|
|||||||
withTestChat tmp "bob" $ \bob ->
|
withTestChat tmp "bob" $ \bob ->
|
||||||
withTestChat tmp "cath" $ \cath -> do
|
withTestChat tmp "cath" $ \cath -> do
|
||||||
bob <## "2 contacts connected (use /cs for the list)"
|
bob <## "2 contacts connected (use /cs for the list)"
|
||||||
bob <###
|
bob
|
||||||
[ "#privacy (Privacy): connected to server(s)",
|
<### [ "#privacy (Privacy): connected to server(s)",
|
||||||
"#security (Security): connected to server(s)"
|
"#security (Security): connected to server(s)"
|
||||||
]
|
]
|
||||||
cath <## "2 contacts connected (use /cs for the list)"
|
cath <## "2 contacts connected (use /cs for the list)"
|
||||||
cath <###
|
cath
|
||||||
[ "#privacy (Privacy): connected to server(s)",
|
<### [ "#privacy (Privacy): connected to server(s)",
|
||||||
"#anonymity (Anonymity): connected to server(s)"
|
"#anonymity (Anonymity): connected to server(s)"
|
||||||
]
|
]
|
||||||
listGroups superUser bob cath
|
listGroups superUser bob cath
|
||||||
groupFoundN 3 bob "privacy"
|
groupFoundN 3 bob "privacy"
|
||||||
groupFound bob "security"
|
groupFound bob "security"
|
||||||
@ -784,14 +785,17 @@ addCathAsOwner bob cath = do
|
|||||||
cath <## "#privacy: member SimpleX-Directory is connected"
|
cath <## "#privacy: member SimpleX-Directory is connected"
|
||||||
|
|
||||||
withDirectoryService :: HasCallStack => FilePath -> (TestCC -> String -> IO ()) -> IO ()
|
withDirectoryService :: HasCallStack => FilePath -> (TestCC -> String -> IO ()) -> IO ()
|
||||||
withDirectoryService tmp test = do
|
withDirectoryService tmp = withDirectoryServiceCfg tmp testCfg
|
||||||
|
|
||||||
|
withDirectoryServiceCfg :: HasCallStack => FilePath -> ChatConfig -> (TestCC -> String -> IO ()) -> IO ()
|
||||||
|
withDirectoryServiceCfg tmp cfg test = do
|
||||||
dsLink <-
|
dsLink <-
|
||||||
withNewTestChat tmp serviceDbPrefix directoryProfile $ \ds ->
|
withNewTestChatCfg tmp cfg serviceDbPrefix directoryProfile $ \ds ->
|
||||||
withNewTestChat tmp "super_user" aliceProfile $ \superUser -> do
|
withNewTestChatCfg tmp cfg "super_user" aliceProfile $ \superUser -> do
|
||||||
connectUsers ds superUser
|
connectUsers ds superUser
|
||||||
ds ##> "/ad"
|
ds ##> "/ad"
|
||||||
getContactLink ds True
|
getContactLink ds True
|
||||||
withDirectory tmp dsLink test
|
withDirectory tmp cfg dsLink test
|
||||||
|
|
||||||
restoreDirectoryService :: HasCallStack => FilePath -> Int -> Int -> (TestCC -> String -> IO ()) -> IO ()
|
restoreDirectoryService :: HasCallStack => FilePath -> Int -> Int -> (TestCC -> String -> IO ()) -> IO ()
|
||||||
restoreDirectoryService tmp ctCount grCount test = do
|
restoreDirectoryService tmp ctCount grCount test = do
|
||||||
@ -800,29 +804,29 @@ restoreDirectoryService tmp ctCount grCount test = do
|
|||||||
ds <## (show ctCount <> " contacts connected (use /cs for the list)")
|
ds <## (show ctCount <> " contacts connected (use /cs for the list)")
|
||||||
ds <## "Your address is active! To show: /sa"
|
ds <## "Your address is active! To show: /sa"
|
||||||
ds <## (show grCount <> " group links active")
|
ds <## (show grCount <> " group links active")
|
||||||
forM_ [1..grCount] $ \_ -> ds <##. "#"
|
forM_ [1 .. grCount] $ \_ -> ds <##. "#"
|
||||||
ds ##> "/sa"
|
ds ##> "/sa"
|
||||||
dsLink <- getContactLink ds False
|
dsLink <- getContactLink ds False
|
||||||
ds <## "auto_accept on"
|
ds <## "auto_accept on"
|
||||||
pure dsLink
|
pure dsLink
|
||||||
withDirectory tmp dsLink test
|
withDirectory tmp testCfg dsLink test
|
||||||
|
|
||||||
withDirectory :: HasCallStack => FilePath -> String -> (TestCC -> String -> IO ()) -> IO ()
|
withDirectory :: HasCallStack => FilePath -> ChatConfig -> String -> (TestCC -> String -> IO ()) -> IO ()
|
||||||
withDirectory tmp dsLink test = do
|
withDirectory tmp cfg dsLink test = do
|
||||||
let opts = mkDirectoryOpts tmp [KnownContact 2 "alice"]
|
let opts = mkDirectoryOpts tmp [KnownContact 2 "alice"]
|
||||||
runDirectory opts $
|
runDirectory cfg opts $
|
||||||
withTestChat tmp "super_user" $ \superUser -> do
|
withTestChatCfg tmp cfg "super_user" $ \superUser -> do
|
||||||
superUser <## "1 contacts connected (use /cs for the list)"
|
superUser <## "1 contacts connected (use /cs for the list)"
|
||||||
test superUser dsLink
|
test superUser dsLink
|
||||||
|
|
||||||
runDirectory :: DirectoryOpts -> IO () -> IO ()
|
runDirectory :: ChatConfig -> DirectoryOpts -> IO () -> IO ()
|
||||||
runDirectory opts@DirectoryOpts {directoryLog} action = do
|
runDirectory cfg opts@DirectoryOpts {directoryLog} action = do
|
||||||
st <- restoreDirectoryStore directoryLog
|
st <- restoreDirectoryStore directoryLog
|
||||||
t <- forkIO $ bot st
|
t <- forkIO $ bot st
|
||||||
threadDelay 500000
|
threadDelay 500000
|
||||||
action `finally` (mapM_ hClose (directoryLogFile st) >> killThread t)
|
action `finally` (mapM_ hClose (directoryLogFile st) >> killThread t)
|
||||||
where
|
where
|
||||||
bot st = simplexChatCore testCfg (mkChatOpts opts) Nothing $ directoryService st opts
|
bot st = simplexChatCore cfg (mkChatOpts opts) Nothing $ directoryService st opts
|
||||||
|
|
||||||
registerGroup :: TestCC -> TestCC -> String -> String -> IO ()
|
registerGroup :: TestCC -> TestCC -> String -> String -> IO ()
|
||||||
registerGroup su u n fn = registerGroupId su u n fn 1 1
|
registerGroup su u n fn = registerGroupId su u n fn 1 1
|
||||||
|
@ -133,6 +133,16 @@ testAgentCfgV1 =
|
|||||||
testCfgV1 :: ChatConfig
|
testCfgV1 :: ChatConfig
|
||||||
testCfgV1 = testCfg {agentConfig = testAgentCfgV1}
|
testCfgV1 = testCfg {agentConfig = testAgentCfgV1}
|
||||||
|
|
||||||
|
testCfgCreateGroupDirect :: ChatConfig
|
||||||
|
testCfgCreateGroupDirect =
|
||||||
|
mkCfgCreateGroupDirect testCfg
|
||||||
|
|
||||||
|
mkCfgCreateGroupDirect :: ChatConfig -> ChatConfig
|
||||||
|
mkCfgCreateGroupDirect cfg = cfg {chatVRange = groupCreateDirectVRange}
|
||||||
|
|
||||||
|
groupCreateDirectVRange :: VersionRange
|
||||||
|
groupCreateDirectVRange = mkVersionRange 1 1
|
||||||
|
|
||||||
createTestChat :: FilePath -> ChatConfig -> ChatOpts -> String -> Profile -> IO TestCC
|
createTestChat :: FilePath -> ChatConfig -> ChatOpts -> String -> Profile -> IO TestCC
|
||||||
createTestChat tmp cfg opts@ChatOpts {coreOptions = CoreChatOpts {dbKey}} dbPrefix profile = do
|
createTestChat tmp cfg opts@ChatOpts {coreOptions = CoreChatOpts {dbKey}} dbPrefix profile = do
|
||||||
Right db@ChatDatabase {chatStore} <- createChatDatabase (tmp </> dbPrefix) dbKey MCError
|
Right db@ChatDatabase {chatStore} <- createChatDatabase (tmp </> dbPrefix) dbKey MCError
|
||||||
@ -288,7 +298,10 @@ testChatCfgOpts3 cfg opts p1 p2 p3 test = testChatN cfg opts [p1, p2, p3] test_
|
|||||||
test_ _ = error "expected 3 chat clients"
|
test_ _ = error "expected 3 chat clients"
|
||||||
|
|
||||||
testChat4 :: HasCallStack => Profile -> Profile -> Profile -> Profile -> (HasCallStack => TestCC -> TestCC -> TestCC -> TestCC -> IO ()) -> FilePath -> IO ()
|
testChat4 :: HasCallStack => Profile -> Profile -> Profile -> Profile -> (HasCallStack => TestCC -> TestCC -> TestCC -> TestCC -> IO ()) -> FilePath -> IO ()
|
||||||
testChat4 p1 p2 p3 p4 test = testChatN testCfg testOpts [p1, p2, p3, p4] test_
|
testChat4 = testChatCfg4 testCfg
|
||||||
|
|
||||||
|
testChatCfg4 :: HasCallStack => ChatConfig -> Profile -> Profile -> Profile -> Profile -> (HasCallStack => TestCC -> TestCC -> TestCC -> TestCC -> IO ()) -> FilePath -> IO ()
|
||||||
|
testChatCfg4 cfg p1 p2 p3 p4 test = testChatN cfg testOpts [p1, p2, p3, p4] test_
|
||||||
where
|
where
|
||||||
test_ :: HasCallStack => [TestCC] -> IO ()
|
test_ :: HasCallStack => [TestCC] -> IO ()
|
||||||
test_ [tc1, tc2, tc3, tc4] = test tc1 tc2 tc3 tc4
|
test_ [tc1, tc2, tc3, tc4] = test tc1 tc2 tc3 tc4
|
||||||
|
@ -17,9 +17,11 @@ import qualified Data.ByteString.Lazy.Char8 as LB
|
|||||||
import Simplex.Chat.Call
|
import Simplex.Chat.Call
|
||||||
import Simplex.Chat.Controller (ChatConfig (..))
|
import Simplex.Chat.Controller (ChatConfig (..))
|
||||||
import Simplex.Chat.Options (ChatOpts (..))
|
import Simplex.Chat.Options (ChatOpts (..))
|
||||||
|
import Simplex.Chat.Protocol (supportedChatVRange)
|
||||||
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
|
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
|
||||||
import Simplex.Chat.Types (authErrDisableCount, sameVerificationCode, verificationCode)
|
import Simplex.Chat.Types (authErrDisableCount, sameVerificationCode, verificationCode)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
|
import Simplex.Messaging.Version
|
||||||
import System.Directory (copyFile, doesDirectoryExist, doesFileExist)
|
import System.Directory (copyFile, doesDirectoryExist, doesFileExist)
|
||||||
import System.FilePath ((</>))
|
import System.FilePath ((</>))
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
@ -94,6 +96,21 @@ chatDirectTests = do
|
|||||||
describe "delivery receipts" $ do
|
describe "delivery receipts" $ do
|
||||||
it "should send delivery receipts" testSendDeliveryReceipts
|
it "should send delivery receipts" testSendDeliveryReceipts
|
||||||
it "should send delivery receipts depending on configuration" testConfigureDeliveryReceipts
|
it "should send delivery receipts depending on configuration" testConfigureDeliveryReceipts
|
||||||
|
describe "negotiate connection peer chat protocol version range" $ do
|
||||||
|
describe "peer version range correctly set for new connection via invitation" $ do
|
||||||
|
testInvVRange supportedChatVRange supportedChatVRange
|
||||||
|
testInvVRange supportedChatVRange vr11
|
||||||
|
testInvVRange vr11 supportedChatVRange
|
||||||
|
testInvVRange vr11 vr11
|
||||||
|
describe "peer version range correctly set for new connection via contact request" $ do
|
||||||
|
testReqVRange supportedChatVRange supportedChatVRange
|
||||||
|
testReqVRange supportedChatVRange vr11
|
||||||
|
testReqVRange vr11 supportedChatVRange
|
||||||
|
testReqVRange vr11 vr11
|
||||||
|
it "update peer version range on received messages" testUpdatePeerChatVRange
|
||||||
|
where
|
||||||
|
testInvVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnInvChatVRange vr1 vr2
|
||||||
|
testReqVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnReqChatVRange vr1 vr2
|
||||||
|
|
||||||
testAddContact :: HasCallStack => SpecWith FilePath
|
testAddContact :: HasCallStack => SpecWith FilePath
|
||||||
testAddContact = versionTestMatrix2 runTestAddContact
|
testAddContact = versionTestMatrix2 runTestAddContact
|
||||||
@ -1939,8 +1956,7 @@ testMarkContactVerified =
|
|||||||
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
||||||
connectUsers alice bob
|
connectUsers alice bob
|
||||||
alice ##> "/i bob"
|
alice ##> "/i bob"
|
||||||
bobInfo alice
|
bobInfo alice False
|
||||||
alice <## "connection not verified, use /code command to see security code"
|
|
||||||
alice ##> "/code bob"
|
alice ##> "/code bob"
|
||||||
bCode <- getTermLine alice
|
bCode <- getTermLine alice
|
||||||
bob ##> "/code alice"
|
bob ##> "/code alice"
|
||||||
@ -1951,28 +1967,31 @@ testMarkContactVerified =
|
|||||||
alice ##> ("/verify bob " <> aCode)
|
alice ##> ("/verify bob " <> aCode)
|
||||||
alice <## "connection verified"
|
alice <## "connection verified"
|
||||||
alice ##> "/i bob"
|
alice ##> "/i bob"
|
||||||
bobInfo alice
|
bobInfo alice True
|
||||||
alice <## "connection verified"
|
|
||||||
alice ##> "/verify bob"
|
alice ##> "/verify bob"
|
||||||
alice <##. "connection not verified, current code is "
|
alice <##. "connection not verified, current code is "
|
||||||
alice ##> "/i bob"
|
alice ##> "/i bob"
|
||||||
bobInfo alice
|
bobInfo alice False
|
||||||
alice <## "connection not verified, use /code command to see security code"
|
|
||||||
where
|
where
|
||||||
bobInfo :: HasCallStack => TestCC -> IO ()
|
bobInfo :: HasCallStack => TestCC -> Bool -> IO ()
|
||||||
bobInfo alice = do
|
bobInfo alice verified = do
|
||||||
alice <## "contact ID: 2"
|
alice <## "contact ID: 2"
|
||||||
alice <## "receiving messages via: localhost"
|
alice <## "receiving messages via: localhost"
|
||||||
alice <## "sending messages via: localhost"
|
alice <## "sending messages via: localhost"
|
||||||
alice <## "you've shared main profile with this contact"
|
alice <## "you've shared main profile with this contact"
|
||||||
|
alice <## connVerified
|
||||||
|
alice <## currentChatVRangeInfo
|
||||||
|
where
|
||||||
|
connVerified
|
||||||
|
| verified = "connection verified"
|
||||||
|
| otherwise = "connection not verified, use /code command to see security code"
|
||||||
|
|
||||||
testMarkGroupMemberVerified :: HasCallStack => FilePath -> IO ()
|
testMarkGroupMemberVerified :: HasCallStack => FilePath -> IO ()
|
||||||
testMarkGroupMemberVerified =
|
testMarkGroupMemberVerified =
|
||||||
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
||||||
createGroup2 "team" alice bob
|
createGroup2 "team" alice bob
|
||||||
alice ##> "/i #team bob"
|
alice ##> "/i #team bob"
|
||||||
bobInfo alice
|
bobInfo alice False
|
||||||
alice <## "connection not verified, use /code command to see security code"
|
|
||||||
alice ##> "/code #team bob"
|
alice ##> "/code #team bob"
|
||||||
bCode <- getTermLine alice
|
bCode <- getTermLine alice
|
||||||
bob ##> "/code #team alice"
|
bob ##> "/code #team alice"
|
||||||
@ -1983,20 +2002,24 @@ testMarkGroupMemberVerified =
|
|||||||
alice ##> ("/verify #team bob " <> aCode)
|
alice ##> ("/verify #team bob " <> aCode)
|
||||||
alice <## "connection verified"
|
alice <## "connection verified"
|
||||||
alice ##> "/i #team bob"
|
alice ##> "/i #team bob"
|
||||||
bobInfo alice
|
bobInfo alice True
|
||||||
alice <## "connection verified"
|
|
||||||
alice ##> "/verify #team bob"
|
alice ##> "/verify #team bob"
|
||||||
alice <##. "connection not verified, current code is "
|
alice <##. "connection not verified, current code is "
|
||||||
alice ##> "/i #team bob"
|
alice ##> "/i #team bob"
|
||||||
bobInfo alice
|
bobInfo alice False
|
||||||
alice <## "connection not verified, use /code command to see security code"
|
|
||||||
where
|
where
|
||||||
bobInfo :: HasCallStack => TestCC -> IO ()
|
bobInfo :: HasCallStack => TestCC -> Bool -> IO ()
|
||||||
bobInfo alice = do
|
bobInfo alice verified = do
|
||||||
alice <## "group ID: 1"
|
alice <## "group ID: 1"
|
||||||
alice <## "member ID: 2"
|
alice <## "member ID: 2"
|
||||||
alice <## "receiving messages via: localhost"
|
alice <## "receiving messages via: localhost"
|
||||||
alice <## "sending messages via: localhost"
|
alice <## "sending messages via: localhost"
|
||||||
|
alice <## connVerified
|
||||||
|
alice <## currentChatVRangeInfo
|
||||||
|
where
|
||||||
|
connVerified
|
||||||
|
| verified = "connection verified"
|
||||||
|
| otherwise = "connection not verified, use /code command to see security code"
|
||||||
|
|
||||||
testMsgDecryptError :: HasCallStack => FilePath -> IO ()
|
testMsgDecryptError :: HasCallStack => FilePath -> IO ()
|
||||||
testMsgDecryptError tmp =
|
testMsgDecryptError tmp =
|
||||||
@ -2088,8 +2111,7 @@ testSyncRatchetCodeReset tmp =
|
|||||||
alice <# "bob> hey"
|
alice <# "bob> hey"
|
||||||
-- connection not verified
|
-- connection not verified
|
||||||
bob ##> "/i alice"
|
bob ##> "/i alice"
|
||||||
aliceInfo bob
|
aliceInfo bob False
|
||||||
bob <## "connection not verified, use /code command to see security code"
|
|
||||||
-- verify connection
|
-- verify connection
|
||||||
alice ##> "/code bob"
|
alice ##> "/code bob"
|
||||||
bCode <- getTermLine alice
|
bCode <- getTermLine alice
|
||||||
@ -2097,8 +2119,7 @@ testSyncRatchetCodeReset tmp =
|
|||||||
bob <## "connection verified"
|
bob <## "connection verified"
|
||||||
-- connection verified
|
-- connection verified
|
||||||
bob ##> "/i alice"
|
bob ##> "/i alice"
|
||||||
aliceInfo bob
|
aliceInfo bob True
|
||||||
bob <## "connection verified"
|
|
||||||
setupDesynchronizedRatchet tmp alice
|
setupDesynchronizedRatchet tmp alice
|
||||||
withTestChat tmp "bob_old" $ \bob -> do
|
withTestChat tmp "bob_old" $ \bob -> do
|
||||||
bob <## "1 contacts connected (use /cs for the list)"
|
bob <## "1 contacts connected (use /cs for the list)"
|
||||||
@ -2115,20 +2136,25 @@ testSyncRatchetCodeReset tmp =
|
|||||||
|
|
||||||
-- connection not verified
|
-- connection not verified
|
||||||
bob ##> "/i alice"
|
bob ##> "/i alice"
|
||||||
aliceInfo bob
|
aliceInfo bob False
|
||||||
bob <## "connection not verified, use /code command to see security code"
|
|
||||||
|
|
||||||
alice #> "@bob hello again"
|
alice #> "@bob hello again"
|
||||||
bob <# "alice> hello again"
|
bob <# "alice> hello again"
|
||||||
bob #> "@alice received!"
|
bob #> "@alice received!"
|
||||||
alice <# "bob> received!"
|
alice <# "bob> received!"
|
||||||
where
|
where
|
||||||
aliceInfo :: HasCallStack => TestCC -> IO ()
|
aliceInfo :: HasCallStack => TestCC -> Bool -> IO ()
|
||||||
aliceInfo bob = do
|
aliceInfo bob verified = do
|
||||||
bob <## "contact ID: 2"
|
bob <## "contact ID: 2"
|
||||||
bob <## "receiving messages via: localhost"
|
bob <## "receiving messages via: localhost"
|
||||||
bob <## "sending messages via: localhost"
|
bob <## "sending messages via: localhost"
|
||||||
bob <## "you've shared main profile with this contact"
|
bob <## "you've shared main profile with this contact"
|
||||||
|
bob <## connVerified
|
||||||
|
bob <## currentChatVRangeInfo
|
||||||
|
where
|
||||||
|
connVerified
|
||||||
|
| verified = "connection verified"
|
||||||
|
| otherwise = "connection not verified, use /code command to see security code"
|
||||||
|
|
||||||
testSetMessageReactions :: HasCallStack => FilePath -> IO ()
|
testSetMessageReactions :: HasCallStack => FilePath -> IO ()
|
||||||
testSetMessageReactions =
|
testSetMessageReactions =
|
||||||
@ -2271,3 +2297,85 @@ testConfigureDeliveryReceipts tmp =
|
|||||||
cc1 #> ("@" <> name2 <> " " <> msg)
|
cc1 #> ("@" <> name2 <> " " <> msg)
|
||||||
cc2 <# (name1 <> "> " <> msg)
|
cc2 <# (name1 <> "> " <> msg)
|
||||||
cc1 <// 50000
|
cc1 <// 50000
|
||||||
|
|
||||||
|
testConnInvChatVRange :: HasCallStack => VersionRange -> VersionRange -> FilePath -> IO ()
|
||||||
|
testConnInvChatVRange ct1VRange ct2VRange tmp =
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = ct1VRange} "alice" aliceProfile $ \alice -> do
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = ct2VRange} "bob" bobProfile $ \bob -> do
|
||||||
|
connectUsers alice bob
|
||||||
|
|
||||||
|
alice ##> "/i bob"
|
||||||
|
contactInfoChatVRange alice ct2VRange
|
||||||
|
|
||||||
|
bob ##> "/i alice"
|
||||||
|
contactInfoChatVRange bob ct1VRange
|
||||||
|
|
||||||
|
testConnReqChatVRange :: HasCallStack => VersionRange -> VersionRange -> FilePath -> IO ()
|
||||||
|
testConnReqChatVRange ct1VRange ct2VRange tmp =
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = ct1VRange} "alice" aliceProfile $ \alice -> do
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = ct2VRange} "bob" bobProfile $ \bob -> do
|
||||||
|
alice ##> "/ad"
|
||||||
|
cLink <- getContactLink alice True
|
||||||
|
bob ##> ("/c " <> cLink)
|
||||||
|
alice <#? bob
|
||||||
|
alice ##> "/ac bob"
|
||||||
|
alice <## "bob (Bob): accepting contact request..."
|
||||||
|
concurrently_
|
||||||
|
(bob <## "alice (Alice): contact is connected")
|
||||||
|
(alice <## "bob (Bob): contact is connected")
|
||||||
|
|
||||||
|
alice ##> "/i bob"
|
||||||
|
contactInfoChatVRange alice ct2VRange
|
||||||
|
|
||||||
|
bob ##> "/i alice"
|
||||||
|
contactInfoChatVRange bob ct1VRange
|
||||||
|
|
||||||
|
testUpdatePeerChatVRange :: HasCallStack => FilePath -> IO ()
|
||||||
|
testUpdatePeerChatVRange tmp =
|
||||||
|
withNewTestChat tmp "alice" aliceProfile $ \alice -> do
|
||||||
|
withNewTestChatCfg tmp cfg11 "bob" bobProfile $ \bob -> do
|
||||||
|
connectUsers alice bob
|
||||||
|
|
||||||
|
alice ##> "/i bob"
|
||||||
|
contactInfoChatVRange alice vr11
|
||||||
|
|
||||||
|
bob ##> "/i alice"
|
||||||
|
contactInfoChatVRange bob supportedChatVRange
|
||||||
|
|
||||||
|
withTestChat tmp "bob" $ \bob -> do
|
||||||
|
bob <## "1 contacts connected (use /cs for the list)"
|
||||||
|
|
||||||
|
bob #> "@alice hello 1"
|
||||||
|
alice <# "bob> hello 1"
|
||||||
|
|
||||||
|
alice ##> "/i bob"
|
||||||
|
contactInfoChatVRange alice supportedChatVRange
|
||||||
|
|
||||||
|
bob ##> "/i alice"
|
||||||
|
contactInfoChatVRange bob supportedChatVRange
|
||||||
|
|
||||||
|
withTestChatCfg tmp cfg11 "bob" $ \bob -> do
|
||||||
|
bob <## "1 contacts connected (use /cs for the list)"
|
||||||
|
|
||||||
|
bob #> "@alice hello 2"
|
||||||
|
alice <# "bob> hello 2"
|
||||||
|
|
||||||
|
alice ##> "/i bob"
|
||||||
|
contactInfoChatVRange alice vr11
|
||||||
|
|
||||||
|
bob ##> "/i alice"
|
||||||
|
contactInfoChatVRange bob supportedChatVRange
|
||||||
|
where
|
||||||
|
cfg11 = testCfg {chatVRange = vr11} :: ChatConfig
|
||||||
|
|
||||||
|
vr11 :: VersionRange
|
||||||
|
vr11 = mkVersionRange 1 1
|
||||||
|
|
||||||
|
contactInfoChatVRange :: TestCC -> VersionRange -> IO ()
|
||||||
|
contactInfoChatVRange cc (VersionRange minVer maxVer) = 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 <## ("peer chat protocol version range: (" <> show minVer <> ", " <> show maxVer <> ")")
|
||||||
|
@ -51,7 +51,7 @@ chatFileTests = do
|
|||||||
it "files folder: sender deleted file during transfer" testFilesFoldersImageSndDelete
|
it "files folder: sender deleted file during transfer" testFilesFoldersImageSndDelete
|
||||||
it "files folder: recipient deleted file during transfer" testFilesFoldersImageRcvDelete
|
it "files folder: recipient deleted file during transfer" testFilesFoldersImageRcvDelete
|
||||||
it "send and receive image with text and quote" testSendImageWithTextAndQuote
|
it "send and receive image with text and quote" testSendImageWithTextAndQuote
|
||||||
describe "send and receive image to group" testGroupSendImage
|
it "send and receive image to group" testGroupSendImage
|
||||||
it "send and receive image with text and quote to group" testGroupSendImageWithTextAndQuote
|
it "send and receive image with text and quote to group" testGroupSendImageWithTextAndQuote
|
||||||
describe "async sending and receiving files" $ do
|
describe "async sending and receiving files" $ do
|
||||||
-- fails on CI
|
-- fails on CI
|
||||||
@ -730,11 +730,10 @@ testSendImageWithTextAndQuote =
|
|||||||
(alice <## "completed sending file 3 (test.jpg) to bob")
|
(alice <## "completed sending file 3 (test.jpg) to bob")
|
||||||
B.readFile "./tests/tmp/test_1.jpg" `shouldReturn` src
|
B.readFile "./tests/tmp/test_1.jpg" `shouldReturn` src
|
||||||
|
|
||||||
testGroupSendImage :: SpecWith FilePath
|
testGroupSendImage :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupSendImage = versionTestMatrix3 runTestGroupSendImage
|
testGroupSendImage =
|
||||||
where
|
testChat3 aliceProfile bobProfile cathProfile $
|
||||||
runTestGroupSendImage :: HasCallStack => TestCC -> TestCC -> TestCC -> IO ()
|
\alice bob cath -> do
|
||||||
runTestGroupSendImage alice bob cath = do
|
|
||||||
createGroup3 "team" alice bob cath
|
createGroup3 "team" alice bob cath
|
||||||
threadDelay 1000000
|
threadDelay 1000000
|
||||||
alice ##> "/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}"
|
alice ##> "/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}"
|
||||||
|
@ -10,8 +10,10 @@ import Control.Concurrent.Async (concurrently_)
|
|||||||
import Control.Monad (when)
|
import Control.Monad (when)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Simplex.Chat.Controller (ChatConfig (..))
|
import Simplex.Chat.Controller (ChatConfig (..))
|
||||||
|
import Simplex.Chat.Protocol (supportedChatVRange)
|
||||||
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
|
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
|
||||||
import Simplex.Chat.Types (GroupMemberRole (..))
|
import Simplex.Chat.Types (GroupMemberRole (..))
|
||||||
|
import Simplex.Messaging.Version
|
||||||
import System.Directory (copyFile)
|
import System.Directory (copyFile)
|
||||||
import System.FilePath ((</>))
|
import System.FilePath ((</>))
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
@ -19,7 +21,7 @@ import Test.Hspec
|
|||||||
chatGroupTests :: SpecWith FilePath
|
chatGroupTests :: SpecWith FilePath
|
||||||
chatGroupTests = do
|
chatGroupTests = do
|
||||||
describe "chat groups" $ do
|
describe "chat groups" $ do
|
||||||
describe "add contacts, create group and send/receive messages" testGroup
|
it "add contacts, create group and send/receive messages" testGroup
|
||||||
it "add contacts, create group and send/receive messages, check messages" testGroupCheckMessages
|
it "add contacts, create group and send/receive messages, check messages" testGroupCheckMessages
|
||||||
it "create and join group with 4 members" testGroup2
|
it "create and join group with 4 members" testGroup2
|
||||||
it "create and delete group" testGroupDelete
|
it "create and delete group" testGroupDelete
|
||||||
@ -64,15 +66,54 @@ chatGroupTests = do
|
|||||||
describe "group delivery receipts" $ do
|
describe "group delivery receipts" $ do
|
||||||
it "should send delivery receipts in group" testSendGroupDeliveryReceipts
|
it "should send delivery receipts in group" testSendGroupDeliveryReceipts
|
||||||
it "should send delivery receipts in group depending on configuration" testConfigureGroupDeliveryReceipts
|
it "should send delivery receipts in group depending on configuration" testConfigureGroupDeliveryReceipts
|
||||||
|
describe "direct connections in group are not established based on chat protocol version" $ do
|
||||||
testGroup :: HasCallStack => SpecWith FilePath
|
describe "3 members group" $ do
|
||||||
testGroup = versionTestMatrix3 runTestGroup
|
testNoDirect _0 _0 False -- True
|
||||||
|
testNoDirect _0 _1 False -- True
|
||||||
|
testNoDirect _1 _0 False
|
||||||
|
testNoDirect _1 _1 False
|
||||||
|
describe "4 members group" $ do
|
||||||
|
testNoDirect4 _0 _0 _0 False False False -- True True True
|
||||||
|
testNoDirect4 _0 _0 _1 False False False -- True True True
|
||||||
|
testNoDirect4 _0 _1 _0 False False False -- True True False
|
||||||
|
testNoDirect4 _0 _1 _1 False False False -- True True False
|
||||||
|
testNoDirect4 _1 _0 _0 False False False -- False False True
|
||||||
|
testNoDirect4 _1 _0 _1 False False False -- False False True
|
||||||
|
testNoDirect4 _1 _1 _0 False False False
|
||||||
|
testNoDirect4 _1 _1 _1 False False False
|
||||||
where
|
where
|
||||||
runTestGroup alice bob cath = testGroupShared alice bob cath False
|
_0 = supportedChatVRange -- don't create direct connections
|
||||||
|
_1 = groupCreateDirectVRange
|
||||||
|
-- having host configured with older version doesn't have effect in tests
|
||||||
|
-- because host uses current code and sends version in MemberInfo
|
||||||
|
testNoDirect vrMem2 vrMem3 noConns =
|
||||||
|
it
|
||||||
|
( "host " <> vRangeStr supportedChatVRange
|
||||||
|
<> (", 2nd mem " <> vRangeStr vrMem2)
|
||||||
|
<> (", 3rd mem " <> vRangeStr vrMem3)
|
||||||
|
<> (if noConns then " : 2 <!!> 3" else " : 2 <##> 3")
|
||||||
|
)
|
||||||
|
$ testNoGroupDirectConns supportedChatVRange vrMem2 vrMem3 noConns
|
||||||
|
testNoDirect4 vrMem2 vrMem3 vrMem4 noConns23 noConns24 noConns34 =
|
||||||
|
it
|
||||||
|
( "host " <> vRangeStr supportedChatVRange
|
||||||
|
<> (", 2nd mem " <> vRangeStr vrMem2)
|
||||||
|
<> (", 3rd mem " <> vRangeStr vrMem3)
|
||||||
|
<> (", 4th mem " <> vRangeStr vrMem4)
|
||||||
|
<> (if noConns23 then " : 2 <!!> 3" else " : 2 <##> 3")
|
||||||
|
<> (if noConns24 then " : 2 <!!> 4" else " : 2 <##> 4")
|
||||||
|
<> (if noConns34 then " : 3 <!!> 4" else " : 3 <##> 4")
|
||||||
|
)
|
||||||
|
$ testNoGroupDirectConns4Members supportedChatVRange vrMem2 vrMem3 vrMem4 noConns23 noConns24 noConns34
|
||||||
|
|
||||||
|
testGroup :: HasCallStack => FilePath -> IO ()
|
||||||
|
testGroup =
|
||||||
|
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||||
|
\alice bob cath -> testGroupShared alice bob cath False
|
||||||
|
|
||||||
testGroupCheckMessages :: HasCallStack => FilePath -> IO ()
|
testGroupCheckMessages :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupCheckMessages =
|
testGroupCheckMessages =
|
||||||
testChat3 aliceProfile bobProfile cathProfile $
|
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||||
\alice bob cath -> testGroupShared alice bob cath True
|
\alice bob cath -> testGroupShared alice bob cath True
|
||||||
|
|
||||||
testGroupShared :: HasCallStack => TestCC -> TestCC -> TestCC -> Bool -> IO ()
|
testGroupShared :: HasCallStack => TestCC -> TestCC -> TestCC -> Bool -> IO ()
|
||||||
@ -233,7 +274,7 @@ testGroupShared alice bob cath checkMessages = do
|
|||||||
|
|
||||||
testGroup2 :: HasCallStack => FilePath -> IO ()
|
testGroup2 :: HasCallStack => FilePath -> IO ()
|
||||||
testGroup2 =
|
testGroup2 =
|
||||||
testChat4 aliceProfile bobProfile cathProfile danProfile $
|
testChatCfg4 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile danProfile $
|
||||||
\alice bob cath dan -> do
|
\alice bob cath dan -> do
|
||||||
connectUsers alice bob
|
connectUsers alice bob
|
||||||
connectUsers alice cath
|
connectUsers alice cath
|
||||||
@ -679,7 +720,7 @@ testDeleteGroupMemberProfileKept =
|
|||||||
|
|
||||||
testGroupRemoveAdd :: HasCallStack => FilePath -> IO ()
|
testGroupRemoveAdd :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupRemoveAdd =
|
testGroupRemoveAdd =
|
||||||
testChat3 aliceProfile bobProfile cathProfile $
|
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||||
\alice bob cath -> do
|
\alice bob cath -> do
|
||||||
createGroup3 "team" alice bob cath
|
createGroup3 "team" alice bob cath
|
||||||
-- remove member
|
-- remove member
|
||||||
@ -754,7 +795,7 @@ testGroupList =
|
|||||||
|
|
||||||
testGroupMessageQuotedReply :: HasCallStack => FilePath -> IO ()
|
testGroupMessageQuotedReply :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupMessageQuotedReply =
|
testGroupMessageQuotedReply =
|
||||||
testChat3 aliceProfile bobProfile cathProfile $
|
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||||
\alice bob cath -> do
|
\alice bob cath -> do
|
||||||
createGroup3 "team" alice bob cath
|
createGroup3 "team" alice bob cath
|
||||||
threadDelay 1000000
|
threadDelay 1000000
|
||||||
@ -1232,7 +1273,7 @@ testGroupDeleteUnusedContacts =
|
|||||||
cath <## "alice (Alice)"
|
cath <## "alice (Alice)"
|
||||||
cath `hasContactProfiles` ["alice", "cath"]
|
cath `hasContactProfiles` ["alice", "cath"]
|
||||||
where
|
where
|
||||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerInterval = 1, cleanupManagerStepDelay = 0}
|
cfg = mkCfgCreateGroupDirect $ testCfg {initialCleanupManagerDelay = 0, cleanupManagerInterval = 1, cleanupManagerStepDelay = 0}
|
||||||
deleteGroup :: HasCallStack => TestCC -> TestCC -> TestCC -> String -> IO ()
|
deleteGroup :: HasCallStack => TestCC -> TestCC -> TestCC -> String -> IO ()
|
||||||
deleteGroup alice bob cath group = do
|
deleteGroup alice bob cath group = do
|
||||||
alice ##> ("/d #" <> group)
|
alice ##> ("/d #" <> group)
|
||||||
@ -1321,7 +1362,7 @@ testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile
|
|||||||
|
|
||||||
testGroupModerate :: HasCallStack => FilePath -> IO ()
|
testGroupModerate :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupModerate =
|
testGroupModerate =
|
||||||
testChat3 aliceProfile bobProfile cathProfile $
|
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||||
\alice bob cath -> do
|
\alice bob cath -> do
|
||||||
createGroup3 "team" alice bob cath
|
createGroup3 "team" alice bob cath
|
||||||
alice ##> "/mr team cath member"
|
alice ##> "/mr team cath member"
|
||||||
@ -1352,7 +1393,7 @@ testGroupModerate =
|
|||||||
|
|
||||||
testGroupModerateFullDelete :: HasCallStack => FilePath -> IO ()
|
testGroupModerateFullDelete :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupModerateFullDelete =
|
testGroupModerateFullDelete =
|
||||||
testChat3 aliceProfile bobProfile cathProfile $
|
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||||
\alice bob cath -> do
|
\alice bob cath -> do
|
||||||
createGroup3 "team" alice bob cath
|
createGroup3 "team" alice bob cath
|
||||||
alice ##> "/mr team cath member"
|
alice ##> "/mr team cath member"
|
||||||
@ -1390,10 +1431,10 @@ testGroupModerateFullDelete =
|
|||||||
|
|
||||||
testGroupDelayedModeration :: HasCallStack => FilePath -> IO ()
|
testGroupDelayedModeration :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupDelayedModeration tmp = do
|
testGroupDelayedModeration tmp = do
|
||||||
withNewTestChat tmp "alice" aliceProfile $ \alice -> do
|
withNewTestChatCfg tmp cfg "alice" aliceProfile $ \alice -> do
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob -> do
|
withNewTestChatCfg tmp cfg "bob" bobProfile $ \bob -> do
|
||||||
createGroup2 "team" alice bob
|
createGroup2 "team" alice bob
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp cfg "cath" cathProfile $ \cath -> do
|
||||||
connectUsers alice cath
|
connectUsers alice cath
|
||||||
addMember "team" alice cath GRMember
|
addMember "team" alice cath GRMember
|
||||||
cath ##> "/j team"
|
cath ##> "/j team"
|
||||||
@ -1407,11 +1448,11 @@ testGroupDelayedModeration tmp = do
|
|||||||
alice ##> "\\\\ #team @cath hi"
|
alice ##> "\\\\ #team @cath hi"
|
||||||
alice <## "message marked deleted by you"
|
alice <## "message marked deleted by you"
|
||||||
cath <# "#team cath> [marked deleted by alice] hi"
|
cath <# "#team cath> [marked deleted by alice] hi"
|
||||||
withTestChat tmp "bob" $ \bob -> do
|
withTestChatCfg tmp cfg "bob" $ \bob -> do
|
||||||
bob <## "1 contacts connected (use /cs for the list)"
|
bob <## "1 contacts connected (use /cs for the list)"
|
||||||
bob <## "#team: connected to server(s)"
|
bob <## "#team: connected to server(s)"
|
||||||
bob <## "#team: alice added cath (Catherine) to the group (connecting...)"
|
bob <## "#team: alice added cath (Catherine) to the group (connecting...)"
|
||||||
withTestChat tmp "cath" $ \cath -> do
|
withTestChatCfg tmp cfg "cath" $ \cath -> do
|
||||||
cath <## "2 contacts connected (use /cs for the list)"
|
cath <## "2 contacts connected (use /cs for the list)"
|
||||||
cath <## "#team: connected to server(s)"
|
cath <## "#team: connected to server(s)"
|
||||||
cath <## "#team: member bob (Bob) is connected"
|
cath <## "#team: member bob (Bob) is connected"
|
||||||
@ -1424,13 +1465,15 @@ testGroupDelayedModeration tmp = do
|
|||||||
bob ##> "/_get chat #1 count=2"
|
bob ##> "/_get chat #1 count=2"
|
||||||
r <- chat <$> getTermLine bob
|
r <- chat <$> getTermLine bob
|
||||||
r `shouldMatchList` [(0, "connected"), (0, "hi [marked deleted by alice]")]
|
r `shouldMatchList` [(0, "connected"), (0, "hi [marked deleted by alice]")]
|
||||||
|
where
|
||||||
|
cfg = testCfgCreateGroupDirect
|
||||||
|
|
||||||
testGroupDelayedModerationFullDelete :: HasCallStack => FilePath -> IO ()
|
testGroupDelayedModerationFullDelete :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupDelayedModerationFullDelete tmp = do
|
testGroupDelayedModerationFullDelete tmp = do
|
||||||
withNewTestChat tmp "alice" aliceProfile $ \alice -> do
|
withNewTestChatCfg tmp cfg "alice" aliceProfile $ \alice -> do
|
||||||
withNewTestChat tmp "bob" bobProfile $ \bob -> do
|
withNewTestChatCfg tmp cfg "bob" bobProfile $ \bob -> do
|
||||||
createGroup2 "team" alice bob
|
createGroup2 "team" alice bob
|
||||||
withNewTestChat tmp "cath" cathProfile $ \cath -> do
|
withNewTestChatCfg tmp cfg "cath" cathProfile $ \cath -> do
|
||||||
connectUsers alice cath
|
connectUsers alice cath
|
||||||
addMember "team" alice cath GRMember
|
addMember "team" alice cath GRMember
|
||||||
cath ##> "/j team"
|
cath ##> "/j team"
|
||||||
@ -1452,14 +1495,14 @@ testGroupDelayedModerationFullDelete tmp = do
|
|||||||
cath <## "alice updated group #team:"
|
cath <## "alice updated group #team:"
|
||||||
cath <## "updated group preferences:"
|
cath <## "updated group preferences:"
|
||||||
cath <## "Full deletion: on"
|
cath <## "Full deletion: on"
|
||||||
withTestChat tmp "bob" $ \bob -> do
|
withTestChatCfg tmp cfg "bob" $ \bob -> do
|
||||||
bob <## "1 contacts connected (use /cs for the list)"
|
bob <## "1 contacts connected (use /cs for the list)"
|
||||||
bob <## "#team: connected to server(s)"
|
bob <## "#team: connected to server(s)"
|
||||||
bob <## "#team: alice added cath (Catherine) to the group (connecting...)"
|
bob <## "#team: alice added cath (Catherine) to the group (connecting...)"
|
||||||
bob <## "alice updated group #team:"
|
bob <## "alice updated group #team:"
|
||||||
bob <## "updated group preferences:"
|
bob <## "updated group preferences:"
|
||||||
bob <## "Full deletion: on"
|
bob <## "Full deletion: on"
|
||||||
withTestChat tmp "cath" $ \cath -> do
|
withTestChatCfg tmp cfg "cath" $ \cath -> do
|
||||||
cath <## "2 contacts connected (use /cs for the list)"
|
cath <## "2 contacts connected (use /cs for the list)"
|
||||||
cath <## "#team: connected to server(s)"
|
cath <## "#team: connected to server(s)"
|
||||||
cath <## "#team: member bob (Bob) is connected"
|
cath <## "#team: member bob (Bob) is connected"
|
||||||
@ -1472,6 +1515,8 @@ testGroupDelayedModerationFullDelete tmp = do
|
|||||||
bob ##> "/_get chat #1 count=3"
|
bob ##> "/_get chat #1 count=3"
|
||||||
r <- chat <$> getTermLine bob
|
r <- chat <$> getTermLine bob
|
||||||
r `shouldMatchList` [(0, "Full deletion: on"), (0, "connected"), (0, "moderated [deleted by alice]")]
|
r `shouldMatchList` [(0, "Full deletion: on"), (0, "connected"), (0, "moderated [deleted by alice]")]
|
||||||
|
where
|
||||||
|
cfg = testCfgCreateGroupDirect
|
||||||
|
|
||||||
testGroupAsync :: HasCallStack => FilePath -> IO ()
|
testGroupAsync :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupAsync tmp = do
|
testGroupAsync tmp = do
|
||||||
@ -2127,7 +2172,7 @@ testGroupLinkMemberRole =
|
|||||||
|
|
||||||
testGroupLinkLeaveDelete :: HasCallStack => FilePath -> IO ()
|
testGroupLinkLeaveDelete :: HasCallStack => FilePath -> IO ()
|
||||||
testGroupLinkLeaveDelete =
|
testGroupLinkLeaveDelete =
|
||||||
testChat3 aliceProfile bobProfile cathProfile $
|
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||||
\alice bob cath -> do
|
\alice bob cath -> do
|
||||||
connectUsers alice bob
|
connectUsers alice bob
|
||||||
connectUsers cath bob
|
connectUsers cath bob
|
||||||
@ -2289,8 +2334,7 @@ testGroupSyncRatchetCodeReset tmp =
|
|||||||
alice <# "#team bob> hey"
|
alice <# "#team bob> hey"
|
||||||
-- connection not verified
|
-- connection not verified
|
||||||
bob ##> "/i #team alice"
|
bob ##> "/i #team alice"
|
||||||
aliceInfo bob
|
aliceInfo bob False
|
||||||
bob <## "connection not verified, use /code command to see security code"
|
|
||||||
-- verify connection
|
-- verify connection
|
||||||
alice ##> "/code #team bob"
|
alice ##> "/code #team bob"
|
||||||
bCode <- getTermLine alice
|
bCode <- getTermLine alice
|
||||||
@ -2298,8 +2342,7 @@ testGroupSyncRatchetCodeReset tmp =
|
|||||||
bob <## "connection verified"
|
bob <## "connection verified"
|
||||||
-- connection verified
|
-- connection verified
|
||||||
bob ##> "/i #team alice"
|
bob ##> "/i #team alice"
|
||||||
aliceInfo bob
|
aliceInfo bob True
|
||||||
bob <## "connection verified"
|
|
||||||
setupDesynchronizedRatchet tmp alice
|
setupDesynchronizedRatchet tmp alice
|
||||||
withTestChat tmp "bob_old" $ \bob -> do
|
withTestChat tmp "bob_old" $ \bob -> do
|
||||||
bob <## "1 contacts connected (use /cs for the list)"
|
bob <## "1 contacts connected (use /cs for the list)"
|
||||||
@ -2317,20 +2360,25 @@ testGroupSyncRatchetCodeReset tmp =
|
|||||||
|
|
||||||
-- connection not verified
|
-- connection not verified
|
||||||
bob ##> "/i #team alice"
|
bob ##> "/i #team alice"
|
||||||
aliceInfo bob
|
aliceInfo bob False
|
||||||
bob <## "connection not verified, use /code command to see security code"
|
|
||||||
|
|
||||||
alice #> "#team hello again"
|
alice #> "#team hello again"
|
||||||
bob <# "#team alice> hello again"
|
bob <# "#team alice> hello again"
|
||||||
bob #> "#team received!"
|
bob #> "#team received!"
|
||||||
alice <# "#team bob> received!"
|
alice <# "#team bob> received!"
|
||||||
where
|
where
|
||||||
aliceInfo :: HasCallStack => TestCC -> IO ()
|
aliceInfo :: HasCallStack => TestCC -> Bool -> IO ()
|
||||||
aliceInfo bob = do
|
aliceInfo bob verified = do
|
||||||
bob <## "group ID: 1"
|
bob <## "group ID: 1"
|
||||||
bob <## "member ID: 1"
|
bob <## "member ID: 1"
|
||||||
bob <## "receiving messages via: localhost"
|
bob <## "receiving messages via: localhost"
|
||||||
bob <## "sending messages via: localhost"
|
bob <## "sending messages via: localhost"
|
||||||
|
bob <## connVerified
|
||||||
|
bob <## currentChatVRangeInfo
|
||||||
|
where
|
||||||
|
connVerified
|
||||||
|
| verified = "connection verified"
|
||||||
|
| otherwise = "connection not verified, use /code command to see security code"
|
||||||
|
|
||||||
testSetGroupMessageReactions :: HasCallStack => FilePath -> IO ()
|
testSetGroupMessageReactions :: HasCallStack => FilePath -> IO ()
|
||||||
testSetGroupMessageReactions =
|
testSetGroupMessageReactions =
|
||||||
@ -2559,7 +2607,7 @@ testConfigureGroupDeliveryReceipts tmp =
|
|||||||
receipt bob alice cath "team" "25"
|
receipt bob alice cath "team" "25"
|
||||||
noReceipt bob alice cath "club" "26"
|
noReceipt bob alice cath "club" "26"
|
||||||
where
|
where
|
||||||
cfg = testCfg {showReceipts = True}
|
cfg = mkCfgCreateGroupDirect $ testCfg {showReceipts = True}
|
||||||
receipt cc1 cc2 cc3 gName msg = do
|
receipt cc1 cc2 cc3 gName msg = do
|
||||||
name1 <- userName cc1
|
name1 <- userName cc1
|
||||||
cc1 #> ("#" <> gName <> " " <> msg)
|
cc1 #> ("#" <> gName <> " " <> msg)
|
||||||
@ -2579,3 +2627,62 @@ testConfigureGroupDeliveryReceipts tmp =
|
|||||||
cc2 <# ("#" <> gName <> " " <> name1 <> "> " <> msg)
|
cc2 <# ("#" <> gName <> " " <> name1 <> "> " <> msg)
|
||||||
cc3 <# ("#" <> gName <> " " <> name1 <> "> " <> msg)
|
cc3 <# ("#" <> gName <> " " <> name1 <> "> " <> msg)
|
||||||
cc1 <// 50000
|
cc1 <// 50000
|
||||||
|
|
||||||
|
testNoGroupDirectConns :: HasCallStack => VersionRange -> VersionRange -> VersionRange -> Bool -> FilePath -> IO ()
|
||||||
|
testNoGroupDirectConns hostVRange mem2VRange mem3VRange noDirectConns tmp =
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = hostVRange} "alice" aliceProfile $ \alice -> do
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = mem2VRange} "bob" bobProfile $ \bob -> do
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = mem3VRange} "cath" cathProfile $ \cath -> do
|
||||||
|
createGroup3 "team" alice bob cath
|
||||||
|
if noDirectConns
|
||||||
|
then contactsDontExist bob cath
|
||||||
|
else bob <##> cath
|
||||||
|
where
|
||||||
|
contactsDontExist bob cath = do
|
||||||
|
bob ##> "@cath hi"
|
||||||
|
bob <## "no contact cath"
|
||||||
|
cath ##> "@bob hi"
|
||||||
|
cath <## "no contact bob"
|
||||||
|
|
||||||
|
testNoGroupDirectConns4Members :: HasCallStack => VersionRange -> VersionRange -> VersionRange -> VersionRange -> Bool -> Bool -> Bool -> FilePath -> IO ()
|
||||||
|
testNoGroupDirectConns4Members hostVRange mem2VRange mem3VRange mem4VRange noConns23 noConns24 noConns34 tmp =
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = hostVRange} "alice" aliceProfile $ \alice -> do
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = mem2VRange} "bob" bobProfile $ \bob -> do
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = mem3VRange} "cath" cathProfile $ \cath -> do
|
||||||
|
withNewTestChatCfg tmp testCfg {chatVRange = mem4VRange} "dan" danProfile $ \dan -> do
|
||||||
|
createGroup3 "team" alice bob cath
|
||||||
|
connectUsers alice dan
|
||||||
|
addMember "team" alice dan GRMember
|
||||||
|
dan ##> "/j team"
|
||||||
|
concurrentlyN_
|
||||||
|
[ alice <## "#team: dan joined the group",
|
||||||
|
do
|
||||||
|
dan <## "#team: you joined the group"
|
||||||
|
dan
|
||||||
|
<### [ "#team: member bob (Bob) is connected",
|
||||||
|
"#team: member cath (Catherine) is connected"
|
||||||
|
],
|
||||||
|
aliceAddedDan bob,
|
||||||
|
aliceAddedDan cath
|
||||||
|
]
|
||||||
|
if noConns23
|
||||||
|
then contactsDontExist bob cath
|
||||||
|
else bob <##> cath
|
||||||
|
if noConns24
|
||||||
|
then contactsDontExist bob dan
|
||||||
|
else bob <##> dan
|
||||||
|
if noConns34
|
||||||
|
then contactsDontExist cath dan
|
||||||
|
else cath <##> dan
|
||||||
|
where
|
||||||
|
aliceAddedDan :: HasCallStack => TestCC -> IO ()
|
||||||
|
aliceAddedDan cc = do
|
||||||
|
cc <## "#team: alice added dan (Daniel) to the group (connecting...)"
|
||||||
|
cc <## "#team: new member dan is connected"
|
||||||
|
contactsDontExist cc1 cc2 = do
|
||||||
|
name1 <- userName cc1
|
||||||
|
name2 <- userName cc2
|
||||||
|
cc1 ##> ("@" <> name2 <> " hi")
|
||||||
|
cc1 <## ("no contact " <> name2)
|
||||||
|
cc2 ##> ("@" <> name1 <> " hi")
|
||||||
|
cc2 <## ("no contact " <> name1)
|
||||||
|
@ -18,7 +18,7 @@ chatProfileTests = do
|
|||||||
it "update user profile and notify contacts" testUpdateProfile
|
it "update user profile and notify contacts" testUpdateProfile
|
||||||
it "update user profile with image" testUpdateProfileImage
|
it "update user profile with image" testUpdateProfileImage
|
||||||
describe "user contact link" $ do
|
describe "user contact link" $ do
|
||||||
describe "create and connect via contact link" testUserContactLink
|
it "create and connect via contact link" testUserContactLink
|
||||||
it "add contact link to profile" testProfileLink
|
it "add contact link to profile" testProfileLink
|
||||||
it "auto accept contact requests" testUserContactLinkAutoAccept
|
it "auto accept contact requests" testUserContactLinkAutoAccept
|
||||||
it "deduplicate contact requests" testDeduplicateContactRequests
|
it "deduplicate contact requests" testDeduplicateContactRequests
|
||||||
@ -57,7 +57,7 @@ chatProfileTests = do
|
|||||||
|
|
||||||
testUpdateProfile :: HasCallStack => FilePath -> IO ()
|
testUpdateProfile :: HasCallStack => FilePath -> IO ()
|
||||||
testUpdateProfile =
|
testUpdateProfile =
|
||||||
testChat3 aliceProfile bobProfile cathProfile $
|
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||||
\alice bob cath -> do
|
\alice bob cath -> do
|
||||||
createGroup3 "team" alice bob cath
|
createGroup3 "team" alice bob cath
|
||||||
alice ##> "/p"
|
alice ##> "/p"
|
||||||
@ -117,33 +117,35 @@ testUpdateProfileImage =
|
|||||||
bob <## "use @alice2 <message> to send messages"
|
bob <## "use @alice2 <message> to send messages"
|
||||||
(bob </)
|
(bob </)
|
||||||
|
|
||||||
testUserContactLink :: SpecWith FilePath
|
testUserContactLink :: HasCallStack => FilePath -> IO ()
|
||||||
testUserContactLink = versionTestMatrix3 $ \alice bob cath -> do
|
testUserContactLink =
|
||||||
alice ##> "/ad"
|
testChat3 aliceProfile bobProfile cathProfile $
|
||||||
cLink <- getContactLink alice True
|
\alice bob cath -> do
|
||||||
bob ##> ("/c " <> cLink)
|
alice ##> "/ad"
|
||||||
alice <#? bob
|
cLink <- getContactLink alice True
|
||||||
alice @@@ [("<@bob", "")]
|
bob ##> ("/c " <> cLink)
|
||||||
alice ##> "/ac bob"
|
alice <#? bob
|
||||||
alice <## "bob (Bob): accepting contact request..."
|
alice @@@ [("<@bob", "")]
|
||||||
concurrently_
|
alice ##> "/ac bob"
|
||||||
(bob <## "alice (Alice): contact is connected")
|
alice <## "bob (Bob): accepting contact request..."
|
||||||
(alice <## "bob (Bob): contact is connected")
|
concurrently_
|
||||||
threadDelay 100000
|
(bob <## "alice (Alice): contact is connected")
|
||||||
alice @@@ [("@bob", lastChatFeature)]
|
(alice <## "bob (Bob): contact is connected")
|
||||||
alice <##> bob
|
threadDelay 100000
|
||||||
|
alice @@@ [("@bob", lastChatFeature)]
|
||||||
|
alice <##> bob
|
||||||
|
|
||||||
cath ##> ("/c " <> cLink)
|
cath ##> ("/c " <> cLink)
|
||||||
alice <#? cath
|
alice <#? cath
|
||||||
alice @@@ [("<@cath", ""), ("@bob", "hey")]
|
alice @@@ [("<@cath", ""), ("@bob", "hey")]
|
||||||
alice ##> "/ac cath"
|
alice ##> "/ac cath"
|
||||||
alice <## "cath (Catherine): accepting contact request..."
|
alice <## "cath (Catherine): accepting contact request..."
|
||||||
concurrently_
|
concurrently_
|
||||||
(cath <## "alice (Alice): contact is connected")
|
(cath <## "alice (Alice): contact is connected")
|
||||||
(alice <## "cath (Catherine): contact is connected")
|
(alice <## "cath (Catherine): contact is connected")
|
||||||
threadDelay 100000
|
threadDelay 100000
|
||||||
alice @@@ [("@cath", lastChatFeature), ("@bob", "hey")]
|
alice @@@ [("@cath", lastChatFeature), ("@bob", "hey")]
|
||||||
alice <##> cath
|
alice <##> cath
|
||||||
|
|
||||||
testProfileLink :: HasCallStack => FilePath -> IO ()
|
testProfileLink :: HasCallStack => FilePath -> IO ()
|
||||||
testProfileLink =
|
testProfileLink =
|
||||||
@ -214,6 +216,7 @@ testProfileLink =
|
|||||||
cc <## ("contact address: " <> cLink)
|
cc <## ("contact address: " <> cLink)
|
||||||
cc <## "you've shared main profile with this contact"
|
cc <## "you've shared main profile with this contact"
|
||||||
cc <## "connection not verified, use /code command to see security code"
|
cc <## "connection not verified, use /code command to see security code"
|
||||||
|
cc <## currentChatVRangeInfo
|
||||||
checkAliceNoProfileLink cc = do
|
checkAliceNoProfileLink cc = do
|
||||||
cc ##> "/info alice"
|
cc ##> "/info alice"
|
||||||
cc <## "contact ID: 2"
|
cc <## "contact ID: 2"
|
||||||
@ -221,6 +224,7 @@ testProfileLink =
|
|||||||
cc <##. "sending messages via"
|
cc <##. "sending messages via"
|
||||||
cc <## "you've shared main profile with this contact"
|
cc <## "you've shared main profile with this contact"
|
||||||
cc <## "connection not verified, use /code command to see security code"
|
cc <## "connection not verified, use /code command to see security code"
|
||||||
|
cc <## currentChatVRangeInfo
|
||||||
|
|
||||||
testUserContactLinkAutoAccept :: HasCallStack => FilePath -> IO ()
|
testUserContactLinkAutoAccept :: HasCallStack => FilePath -> IO ()
|
||||||
testUserContactLinkAutoAccept =
|
testUserContactLinkAutoAccept =
|
||||||
@ -760,192 +764,193 @@ testSetResetSetConnectionIncognito = testChat2 aliceProfile bobProfile $
|
|||||||
bob `hasContactProfiles` ["bob", T.pack aliceIncognito]
|
bob `hasContactProfiles` ["bob", T.pack aliceIncognito]
|
||||||
|
|
||||||
testJoinGroupIncognito :: HasCallStack => FilePath -> IO ()
|
testJoinGroupIncognito :: HasCallStack => FilePath -> IO ()
|
||||||
testJoinGroupIncognito = testChat4 aliceProfile bobProfile cathProfile danProfile $
|
testJoinGroupIncognito =
|
||||||
\alice bob cath dan -> do
|
testChatCfg4 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile danProfile $
|
||||||
-- non incognito connections
|
\alice bob cath dan -> do
|
||||||
connectUsers alice bob
|
-- non incognito connections
|
||||||
connectUsers alice dan
|
connectUsers alice bob
|
||||||
connectUsers bob cath
|
connectUsers alice dan
|
||||||
connectUsers bob dan
|
connectUsers bob cath
|
||||||
connectUsers cath dan
|
connectUsers bob dan
|
||||||
-- cath connected incognito to alice
|
connectUsers cath dan
|
||||||
alice ##> "/c"
|
-- cath connected incognito to alice
|
||||||
inv <- getInvitation alice
|
alice ##> "/c"
|
||||||
cath ##> ("/c i " <> inv)
|
inv <- getInvitation alice
|
||||||
cath <## "confirmation sent!"
|
cath ##> ("/c i " <> inv)
|
||||||
cathIncognito <- getTermLine cath
|
cath <## "confirmation sent!"
|
||||||
concurrentlyN_
|
cathIncognito <- getTermLine cath
|
||||||
[ do
|
concurrentlyN_
|
||||||
cath <## ("alice (Alice): contact is connected, your incognito profile for this contact is " <> cathIncognito)
|
[ do
|
||||||
cath <## "use /i alice to print out this incognito profile again",
|
cath <## ("alice (Alice): contact is connected, your incognito profile for this contact is " <> cathIncognito)
|
||||||
alice <## (cathIncognito <> ": contact is connected")
|
cath <## "use /i alice to print out this incognito profile again",
|
||||||
]
|
alice <## (cathIncognito <> ": contact is connected")
|
||||||
-- alice creates group
|
]
|
||||||
alice ##> "/g secret_club"
|
-- alice creates group
|
||||||
alice <## "group #secret_club is created"
|
alice ##> "/g secret_club"
|
||||||
alice <## "to add members use /a secret_club <name> or /create link #secret_club"
|
alice <## "group #secret_club is created"
|
||||||
-- alice invites bob
|
alice <## "to add members use /a secret_club <name> or /create link #secret_club"
|
||||||
alice ##> "/a secret_club bob admin"
|
-- alice invites bob
|
||||||
concurrentlyN_
|
alice ##> "/a secret_club bob admin"
|
||||||
[ alice <## "invitation to join the group #secret_club sent to bob",
|
concurrentlyN_
|
||||||
do
|
[ alice <## "invitation to join the group #secret_club sent to bob",
|
||||||
bob <## "#secret_club: alice invites you to join the group as admin"
|
do
|
||||||
bob <## "use /j secret_club to accept"
|
bob <## "#secret_club: alice invites you to join the group as admin"
|
||||||
]
|
bob <## "use /j secret_club to accept"
|
||||||
bob ##> "/j secret_club"
|
]
|
||||||
concurrently_
|
bob ##> "/j secret_club"
|
||||||
(alice <## "#secret_club: bob joined the group")
|
concurrently_
|
||||||
(bob <## "#secret_club: you joined the group")
|
(alice <## "#secret_club: bob joined the group")
|
||||||
-- alice invites cath
|
(bob <## "#secret_club: you joined the group")
|
||||||
alice ##> ("/a secret_club " <> cathIncognito <> " admin")
|
-- alice invites cath
|
||||||
concurrentlyN_
|
alice ##> ("/a secret_club " <> cathIncognito <> " admin")
|
||||||
[ alice <## ("invitation to join the group #secret_club sent to " <> cathIncognito),
|
concurrentlyN_
|
||||||
do
|
[ alice <## ("invitation to join the group #secret_club sent to " <> cathIncognito),
|
||||||
cath <## "#secret_club: alice invites you to join the group as admin"
|
do
|
||||||
cath <## ("use /j secret_club to join incognito as " <> cathIncognito)
|
cath <## "#secret_club: alice invites you to join the group as admin"
|
||||||
]
|
cath <## ("use /j secret_club to join incognito as " <> cathIncognito)
|
||||||
-- cath uses the same incognito profile when joining group, cath and bob don't merge contacts
|
]
|
||||||
cath ##> "/j secret_club"
|
-- cath uses the same incognito profile when joining group, cath and bob don't merge contacts
|
||||||
concurrentlyN_
|
cath ##> "/j secret_club"
|
||||||
[ alice <## ("#secret_club: " <> cathIncognito <> " joined the group"),
|
concurrentlyN_
|
||||||
do
|
[ alice <## ("#secret_club: " <> cathIncognito <> " joined the group"),
|
||||||
cath <## ("#secret_club: you joined the group incognito as " <> cathIncognito)
|
do
|
||||||
cath <## "#secret_club: member bob_1 (Bob) is connected",
|
cath <## ("#secret_club: you joined the group incognito as " <> cathIncognito)
|
||||||
do
|
cath <## "#secret_club: member bob_1 (Bob) is connected",
|
||||||
bob <## ("#secret_club: alice added " <> cathIncognito <> " to the group (connecting...)")
|
do
|
||||||
bob <## ("#secret_club: new member " <> cathIncognito <> " is connected")
|
bob <## ("#secret_club: alice added " <> cathIncognito <> " to the group (connecting...)")
|
||||||
]
|
bob <## ("#secret_club: new member " <> cathIncognito <> " is connected")
|
||||||
-- cath cannot invite to the group because her membership is incognito
|
]
|
||||||
cath ##> "/a secret_club dan"
|
-- cath cannot invite to the group because her membership is incognito
|
||||||
cath <## "you've connected to this group using an incognito profile - prohibited to invite contacts"
|
cath ##> "/a secret_club dan"
|
||||||
-- alice invites dan
|
cath <## "you've connected to this group using an incognito profile - prohibited to invite contacts"
|
||||||
alice ##> "/a secret_club dan admin"
|
-- alice invites dan
|
||||||
concurrentlyN_
|
alice ##> "/a secret_club dan admin"
|
||||||
[ alice <## "invitation to join the group #secret_club sent to dan",
|
concurrentlyN_
|
||||||
do
|
[ alice <## "invitation to join the group #secret_club sent to dan",
|
||||||
dan <## "#secret_club: alice invites you to join the group as admin"
|
do
|
||||||
dan <## "use /j secret_club to accept"
|
dan <## "#secret_club: alice invites you to join the group as admin"
|
||||||
]
|
dan <## "use /j secret_club to accept"
|
||||||
dan ##> "/j secret_club"
|
]
|
||||||
-- cath and dan don't merge contacts
|
dan ##> "/j secret_club"
|
||||||
concurrentlyN_
|
-- cath and dan don't merge contacts
|
||||||
[ alice <## "#secret_club: dan joined the group",
|
concurrentlyN_
|
||||||
do
|
[ alice <## "#secret_club: dan joined the group",
|
||||||
dan <## "#secret_club: you joined the group"
|
do
|
||||||
dan
|
dan <## "#secret_club: you joined the group"
|
||||||
<### [ ConsoleString $ "#secret_club: member " <> cathIncognito <> " is connected",
|
dan
|
||||||
"#secret_club: member bob_1 (Bob) is connected",
|
<### [ ConsoleString $ "#secret_club: member " <> cathIncognito <> " is connected",
|
||||||
"contact bob_1 is merged into bob",
|
"#secret_club: member bob_1 (Bob) is connected",
|
||||||
"use @bob <message> to send messages"
|
"contact bob_1 is merged into bob",
|
||||||
],
|
"use @bob <message> to send messages"
|
||||||
do
|
],
|
||||||
bob <## "#secret_club: alice added dan_1 (Daniel) to the group (connecting...)"
|
do
|
||||||
bob <## "#secret_club: new member dan_1 is connected"
|
bob <## "#secret_club: alice added dan_1 (Daniel) to the group (connecting...)"
|
||||||
bob <## "contact dan_1 is merged into dan"
|
bob <## "#secret_club: new member dan_1 is connected"
|
||||||
bob <## "use @dan <message> to send messages",
|
bob <## "contact dan_1 is merged into dan"
|
||||||
do
|
bob <## "use @dan <message> to send messages",
|
||||||
cath <## "#secret_club: alice added dan_1 (Daniel) to the group (connecting...)"
|
do
|
||||||
cath <## "#secret_club: new member dan_1 is connected"
|
cath <## "#secret_club: alice added dan_1 (Daniel) to the group (connecting...)"
|
||||||
]
|
cath <## "#secret_club: new member dan_1 is connected"
|
||||||
-- send messages - group is incognito for cath
|
]
|
||||||
alice #> "#secret_club hello"
|
-- send messages - group is incognito for cath
|
||||||
concurrentlyN_
|
alice #> "#secret_club hello"
|
||||||
[ bob <# "#secret_club alice> hello",
|
concurrentlyN_
|
||||||
cath ?<# "#secret_club alice> hello",
|
[ bob <# "#secret_club alice> hello",
|
||||||
dan <# "#secret_club alice> hello"
|
cath ?<# "#secret_club alice> hello",
|
||||||
]
|
dan <# "#secret_club alice> hello"
|
||||||
bob #> "#secret_club hi there"
|
]
|
||||||
concurrentlyN_
|
bob #> "#secret_club hi there"
|
||||||
[ alice <# "#secret_club bob> hi there",
|
concurrentlyN_
|
||||||
cath ?<# "#secret_club bob_1> hi there",
|
[ alice <# "#secret_club bob> hi there",
|
||||||
dan <# "#secret_club bob> hi there"
|
cath ?<# "#secret_club bob_1> hi there",
|
||||||
]
|
dan <# "#secret_club bob> hi there"
|
||||||
cath ?#> "#secret_club hey"
|
]
|
||||||
concurrentlyN_
|
cath ?#> "#secret_club hey"
|
||||||
[ alice <# ("#secret_club " <> cathIncognito <> "> hey"),
|
concurrentlyN_
|
||||||
bob <# ("#secret_club " <> cathIncognito <> "> hey"),
|
[ alice <# ("#secret_club " <> cathIncognito <> "> hey"),
|
||||||
dan <# ("#secret_club " <> cathIncognito <> "> hey")
|
bob <# ("#secret_club " <> cathIncognito <> "> hey"),
|
||||||
]
|
dan <# ("#secret_club " <> cathIncognito <> "> hey")
|
||||||
dan #> "#secret_club how is it going?"
|
]
|
||||||
concurrentlyN_
|
dan #> "#secret_club how is it going?"
|
||||||
[ alice <# "#secret_club dan> how is it going?",
|
concurrentlyN_
|
||||||
bob <# "#secret_club dan> how is it going?",
|
[ alice <# "#secret_club dan> how is it going?",
|
||||||
cath ?<# "#secret_club dan_1> how is it going?"
|
bob <# "#secret_club dan> how is it going?",
|
||||||
]
|
cath ?<# "#secret_club dan_1> how is it going?"
|
||||||
-- cath and bob can send messages via new direct connection, cath is incognito
|
]
|
||||||
bob #> ("@" <> cathIncognito <> " hi, I'm bob")
|
-- cath and bob can send messages via new direct connection, cath is incognito
|
||||||
cath ?<# "bob_1> hi, I'm bob"
|
bob #> ("@" <> cathIncognito <> " hi, I'm bob")
|
||||||
cath ?#> "@bob_1 hey, I'm incognito"
|
cath ?<# "bob_1> hi, I'm bob"
|
||||||
bob <# (cathIncognito <> "> hey, I'm incognito")
|
cath ?#> "@bob_1 hey, I'm incognito"
|
||||||
-- cath and dan can send messages via new direct connection, cath is incognito
|
bob <# (cathIncognito <> "> hey, I'm incognito")
|
||||||
dan #> ("@" <> cathIncognito <> " hi, I'm dan")
|
-- cath and dan can send messages via new direct connection, cath is incognito
|
||||||
cath ?<# "dan_1> hi, I'm dan"
|
dan #> ("@" <> cathIncognito <> " hi, I'm dan")
|
||||||
cath ?#> "@dan_1 hey, I'm incognito"
|
cath ?<# "dan_1> hi, I'm dan"
|
||||||
dan <# (cathIncognito <> "> hey, I'm incognito")
|
cath ?#> "@dan_1 hey, I'm incognito"
|
||||||
-- non incognito connections are separate
|
dan <# (cathIncognito <> "> hey, I'm incognito")
|
||||||
bob <##> cath
|
-- non incognito connections are separate
|
||||||
dan <##> cath
|
bob <##> cath
|
||||||
-- list groups
|
dan <##> cath
|
||||||
cath ##> "/gs"
|
-- list groups
|
||||||
cath <## "i #secret_club (4 members)"
|
cath ##> "/gs"
|
||||||
-- list group members
|
cath <## "i #secret_club (4 members)"
|
||||||
alice ##> "/ms secret_club"
|
-- list group members
|
||||||
alice
|
alice ##> "/ms secret_club"
|
||||||
<### [ "alice (Alice): owner, you, created group",
|
alice
|
||||||
"bob (Bob): admin, invited, connected",
|
<### [ "alice (Alice): owner, you, created group",
|
||||||
ConsoleString $ cathIncognito <> ": admin, invited, connected",
|
"bob (Bob): admin, invited, connected",
|
||||||
"dan (Daniel): admin, invited, connected"
|
ConsoleString $ cathIncognito <> ": admin, invited, connected",
|
||||||
]
|
"dan (Daniel): admin, invited, connected"
|
||||||
bob ##> "/ms secret_club"
|
]
|
||||||
bob
|
bob ##> "/ms secret_club"
|
||||||
<### [ "alice (Alice): owner, host, connected",
|
bob
|
||||||
"bob (Bob): admin, you, connected",
|
<### [ "alice (Alice): owner, host, connected",
|
||||||
ConsoleString $ cathIncognito <> ": admin, connected",
|
"bob (Bob): admin, you, connected",
|
||||||
"dan (Daniel): admin, connected"
|
ConsoleString $ cathIncognito <> ": admin, connected",
|
||||||
]
|
"dan (Daniel): admin, connected"
|
||||||
cath ##> "/ms secret_club"
|
]
|
||||||
cath
|
cath ##> "/ms secret_club"
|
||||||
<### [ "alice (Alice): owner, host, connected",
|
cath
|
||||||
"bob_1 (Bob): admin, connected",
|
<### [ "alice (Alice): owner, host, connected",
|
||||||
ConsoleString $ "i " <> cathIncognito <> ": admin, you, connected",
|
"bob_1 (Bob): admin, connected",
|
||||||
"dan_1 (Daniel): admin, connected"
|
ConsoleString $ "i " <> cathIncognito <> ": admin, you, connected",
|
||||||
]
|
"dan_1 (Daniel): admin, connected"
|
||||||
dan ##> "/ms secret_club"
|
]
|
||||||
dan
|
dan ##> "/ms secret_club"
|
||||||
<### [ "alice (Alice): owner, host, connected",
|
dan
|
||||||
"bob (Bob): admin, connected",
|
<### [ "alice (Alice): owner, host, connected",
|
||||||
ConsoleString $ cathIncognito <> ": admin, connected",
|
"bob (Bob): admin, connected",
|
||||||
"dan (Daniel): admin, you, connected"
|
ConsoleString $ cathIncognito <> ": admin, connected",
|
||||||
]
|
"dan (Daniel): admin, you, connected"
|
||||||
-- remove member
|
]
|
||||||
bob ##> ("/rm secret_club " <> cathIncognito)
|
-- remove member
|
||||||
concurrentlyN_
|
bob ##> ("/rm secret_club " <> cathIncognito)
|
||||||
[ bob <## ("#secret_club: you removed " <> cathIncognito <> " from the group"),
|
concurrentlyN_
|
||||||
alice <## ("#secret_club: bob removed " <> cathIncognito <> " from the group"),
|
[ bob <## ("#secret_club: you removed " <> cathIncognito <> " from the group"),
|
||||||
dan <## ("#secret_club: bob removed " <> cathIncognito <> " from the group"),
|
alice <## ("#secret_club: bob removed " <> cathIncognito <> " from the group"),
|
||||||
do
|
dan <## ("#secret_club: bob removed " <> cathIncognito <> " from the group"),
|
||||||
cath <## "#secret_club: bob_1 removed you from the group"
|
do
|
||||||
cath <## "use /d #secret_club to delete the group"
|
cath <## "#secret_club: bob_1 removed you from the group"
|
||||||
]
|
cath <## "use /d #secret_club to delete the group"
|
||||||
bob #> "#secret_club hi"
|
]
|
||||||
concurrentlyN_
|
bob #> "#secret_club hi"
|
||||||
[ alice <# "#secret_club bob> hi",
|
concurrentlyN_
|
||||||
dan <# "#secret_club bob> hi",
|
[ alice <# "#secret_club bob> hi",
|
||||||
(cath </)
|
dan <# "#secret_club bob> hi",
|
||||||
]
|
(cath </)
|
||||||
alice #> "#secret_club hello"
|
]
|
||||||
concurrentlyN_
|
alice #> "#secret_club hello"
|
||||||
[ bob <# "#secret_club alice> hello",
|
concurrentlyN_
|
||||||
dan <# "#secret_club alice> hello",
|
[ bob <# "#secret_club alice> hello",
|
||||||
(cath </)
|
dan <# "#secret_club alice> hello",
|
||||||
]
|
(cath </)
|
||||||
cath ##> "#secret_club hello"
|
]
|
||||||
cath <## "you are no longer a member of the group"
|
cath ##> "#secret_club hello"
|
||||||
-- cath can still message members directly
|
cath <## "you are no longer a member of the group"
|
||||||
bob #> ("@" <> cathIncognito <> " I removed you from group")
|
-- cath can still message members directly
|
||||||
cath ?<# "bob_1> I removed you from group"
|
bob #> ("@" <> cathIncognito <> " I removed you from group")
|
||||||
cath ?#> "@bob_1 ok"
|
cath ?<# "bob_1> I removed you from group"
|
||||||
bob <# (cathIncognito <> "> ok")
|
cath ?#> "@bob_1 ok"
|
||||||
|
bob <# (cathIncognito <> "> ok")
|
||||||
|
|
||||||
testCantInviteContactIncognito :: HasCallStack => FilePath -> IO ()
|
testCantInviteContactIncognito :: HasCallStack => FilePath -> IO ()
|
||||||
testCantInviteContactIncognito = testChat2 aliceProfile bobProfile $
|
testCantInviteContactIncognito = testChat2 aliceProfile bobProfile $
|
||||||
@ -1354,54 +1359,55 @@ testAllowFullDeletionGroup =
|
|||||||
|
|
||||||
testProhibitDirectMessages :: HasCallStack => FilePath -> IO ()
|
testProhibitDirectMessages :: HasCallStack => FilePath -> IO ()
|
||||||
testProhibitDirectMessages =
|
testProhibitDirectMessages =
|
||||||
testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do
|
testChatCfg4 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile danProfile $
|
||||||
createGroup3 "team" alice bob cath
|
\alice bob cath dan -> do
|
||||||
threadDelay 1000000
|
createGroup3 "team" alice bob cath
|
||||||
alice ##> "/set direct #team off"
|
threadDelay 1000000
|
||||||
alice <## "updated group preferences:"
|
alice ##> "/set direct #team off"
|
||||||
alice <## "Direct messages: off"
|
alice <## "updated group preferences:"
|
||||||
directProhibited bob
|
alice <## "Direct messages: off"
|
||||||
directProhibited cath
|
directProhibited bob
|
||||||
threadDelay 1000000
|
directProhibited cath
|
||||||
-- still can send direct messages to direct contacts
|
threadDelay 1000000
|
||||||
alice #> "@bob hello again"
|
-- still can send direct messages to direct contacts
|
||||||
bob <# "alice> hello again"
|
alice #> "@bob hello again"
|
||||||
alice #> "@cath hello again"
|
bob <# "alice> hello again"
|
||||||
cath <# "alice> hello again"
|
alice #> "@cath hello again"
|
||||||
bob ##> "@cath hello again"
|
cath <# "alice> hello again"
|
||||||
bob <## "direct messages to indirect contact cath are prohibited"
|
bob ##> "@cath hello again"
|
||||||
(cath </)
|
bob <## "direct messages to indirect contact cath are prohibited"
|
||||||
connectUsers cath dan
|
(cath </)
|
||||||
addMember "team" cath dan GRMember
|
connectUsers cath dan
|
||||||
dan ##> "/j #team"
|
addMember "team" cath dan GRMember
|
||||||
concurrentlyN_
|
dan ##> "/j #team"
|
||||||
[ cath <## "#team: dan joined the group",
|
concurrentlyN_
|
||||||
do
|
[ cath <## "#team: dan joined the group",
|
||||||
dan <## "#team: you joined the group"
|
do
|
||||||
dan
|
dan <## "#team: you joined the group"
|
||||||
<### [ "#team: member alice (Alice) is connected",
|
dan
|
||||||
"#team: member bob (Bob) is connected"
|
<### [ "#team: member alice (Alice) is connected",
|
||||||
],
|
"#team: member bob (Bob) is connected"
|
||||||
do
|
],
|
||||||
alice <## "#team: cath added dan (Daniel) to the group (connecting...)"
|
do
|
||||||
alice <## "#team: new member dan is connected",
|
alice <## "#team: cath added dan (Daniel) to the group (connecting...)"
|
||||||
do
|
alice <## "#team: new member dan is connected",
|
||||||
bob <## "#team: cath added dan (Daniel) to the group (connecting...)"
|
do
|
||||||
bob <## "#team: new member dan is connected"
|
bob <## "#team: cath added dan (Daniel) to the group (connecting...)"
|
||||||
]
|
bob <## "#team: new member dan is connected"
|
||||||
alice ##> "@dan hi"
|
]
|
||||||
alice <## "direct messages to indirect contact dan are prohibited"
|
alice ##> "@dan hi"
|
||||||
bob ##> "@dan hi"
|
alice <## "direct messages to indirect contact dan are prohibited"
|
||||||
bob <## "direct messages to indirect contact dan are prohibited"
|
bob ##> "@dan hi"
|
||||||
(dan </)
|
bob <## "direct messages to indirect contact dan are prohibited"
|
||||||
dan ##> "@alice hi"
|
(dan </)
|
||||||
dan <## "direct messages to indirect contact alice are prohibited"
|
dan ##> "@alice hi"
|
||||||
dan ##> "@bob hi"
|
dan <## "direct messages to indirect contact alice are prohibited"
|
||||||
dan <## "direct messages to indirect contact bob are prohibited"
|
dan ##> "@bob hi"
|
||||||
dan #> "@cath hi"
|
dan <## "direct messages to indirect contact bob are prohibited"
|
||||||
cath <# "dan> hi"
|
dan #> "@cath hi"
|
||||||
cath #> "@dan hi"
|
cath <# "dan> hi"
|
||||||
dan <# "cath> hi"
|
cath #> "@dan hi"
|
||||||
|
dan <# "cath> hi"
|
||||||
where
|
where
|
||||||
directProhibited :: HasCallStack => TestCC -> IO ()
|
directProhibited :: HasCallStack => TestCC -> IO ()
|
||||||
directProhibited cc = do
|
directProhibited cc = do
|
||||||
|
@ -18,11 +18,13 @@ import Data.Maybe (fromMaybe)
|
|||||||
import Data.String
|
import Data.String
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), InlineFilesConfig (..), defaultInlineFilesConfig)
|
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), InlineFilesConfig (..), defaultInlineFilesConfig)
|
||||||
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Store.Profiles (getUserContactProfiles)
|
import Simplex.Chat.Store.Profiles (getUserContactProfiles)
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Chat.Types.Preferences
|
import Simplex.Chat.Types.Preferences
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (withTransaction)
|
import Simplex.Messaging.Agent.Store.SQLite (withTransaction)
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
|
import Simplex.Messaging.Version
|
||||||
import System.Directory (doesFileExist)
|
import System.Directory (doesFileExist)
|
||||||
import System.Environment (lookupEnv)
|
import System.Environment (lookupEnv)
|
||||||
import System.FilePath ((</>))
|
import System.FilePath ((</>))
|
||||||
@ -65,9 +67,9 @@ versionTestMatrix2 runTest = do
|
|||||||
it "v1 to v2" $ runTestCfg2 testCfg testCfgV1 runTest
|
it "v1 to v2" $ runTestCfg2 testCfg testCfgV1 runTest
|
||||||
it "v2 to v1" $ runTestCfg2 testCfgV1 testCfg runTest
|
it "v2 to v1" $ runTestCfg2 testCfgV1 testCfg runTest
|
||||||
|
|
||||||
versionTestMatrix3 :: (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> SpecWith FilePath
|
-- versionTestMatrix3 :: (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> SpecWith FilePath
|
||||||
versionTestMatrix3 runTest = do
|
-- versionTestMatrix3 runTest = do
|
||||||
it "v2" $ testChat3 aliceProfile bobProfile cathProfile runTest
|
-- it "v2" $ testChat3 aliceProfile bobProfile cathProfile runTest
|
||||||
|
|
||||||
-- it "v1" $ testChatCfg3 testCfgV1 aliceProfile bobProfile cathProfile runTest
|
-- it "v1" $ testChatCfg3 testCfgV1 aliceProfile bobProfile cathProfile runTest
|
||||||
-- it "v1 to v2" $ runTestCfg3 testCfg testCfgV1 testCfgV1 runTest
|
-- it "v1 to v2" $ runTestCfg3 testCfg testCfgV1 testCfgV1 runTest
|
||||||
@ -356,7 +358,7 @@ dropTime_ msg = case splitAt 6 msg of
|
|||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
dropStrPrefix :: HasCallStack => String -> String -> String
|
dropStrPrefix :: HasCallStack => String -> String -> String
|
||||||
dropStrPrefix pfx s =
|
dropStrPrefix pfx s =
|
||||||
let (p, rest) = splitAt (length pfx) s
|
let (p, rest) = splitAt (length pfx) s
|
||||||
in if p == pfx then rest else error $ "no prefix " <> pfx <> " in string : " <> s
|
in if p == pfx then rest else error $ "no prefix " <> pfx <> " in string : " <> s
|
||||||
|
|
||||||
@ -523,3 +525,10 @@ startFileTransferWithDest' cc1 cc2 fileName fileSize fileDest_ = do
|
|||||||
concurrently_
|
concurrently_
|
||||||
(cc2 <## ("started receiving file 1 (" <> fileName <> ") from " <> name1))
|
(cc2 <## ("started receiving file 1 (" <> fileName <> ") from " <> name1))
|
||||||
(cc1 <## ("started sending file 1 (" <> fileName <> ") to " <> name2))
|
(cc1 <## ("started sending file 1 (" <> fileName <> ") to " <> name2))
|
||||||
|
|
||||||
|
currentChatVRangeInfo :: String
|
||||||
|
currentChatVRangeInfo =
|
||||||
|
"peer chat protocol version range: " <> vRangeStr supportedChatVRange
|
||||||
|
|
||||||
|
vRangeStr :: VersionRange -> String
|
||||||
|
vRangeStr (VersionRange minVer maxVer) = "(" <> show minVer <> ", " <> show maxVer <> ")"
|
||||||
|
@ -76,10 +76,10 @@ s ##==## msg = do
|
|||||||
s ==## msg
|
s ==## msg
|
||||||
|
|
||||||
(==#) :: MsgEncodingI e => ByteString -> ChatMsgEvent e -> Expectation
|
(==#) :: MsgEncodingI e => ByteString -> ChatMsgEvent e -> Expectation
|
||||||
s ==# msg = s ==## ChatMessage Nothing msg
|
s ==# msg = s ==## ChatMessage chatInitialVRange Nothing msg
|
||||||
|
|
||||||
(#==) :: MsgEncodingI e => ByteString -> ChatMsgEvent e -> Expectation
|
(#==) :: MsgEncodingI e => ByteString -> ChatMsgEvent e -> Expectation
|
||||||
s #== msg = s ##== ChatMessage Nothing msg
|
s #== msg = s ##== ChatMessage chatInitialVRange Nothing msg
|
||||||
|
|
||||||
(#==#) :: MsgEncodingI e => ByteString -> ChatMsgEvent e -> Expectation
|
(#==#) :: MsgEncodingI e => ByteString -> ChatMsgEvent e -> Expectation
|
||||||
s #==# msg = do
|
s #==# msg = do
|
||||||
@ -101,59 +101,66 @@ testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", descri
|
|||||||
decodeChatMessageTest :: Spec
|
decodeChatMessageTest :: Spec
|
||||||
decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||||
it "x.msg.new simple text" $
|
it "x.msg.new simple text" $
|
||||||
"{\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||||
#==# XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))
|
#==# XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))
|
||||||
it "x.msg.new simple text - timed message TTL" $
|
it "x.msg.new simple text - timed message TTL" $
|
||||||
"{\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"ttl\":3600}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"ttl\":3600}}"
|
||||||
#==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing (Just 3600) Nothing))
|
#==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing (Just 3600) Nothing))
|
||||||
it "x.msg.new simple text - live message" $
|
it "x.msg.new simple text - live message" $
|
||||||
"{\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"live\":true}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"live\":true}}"
|
||||||
#==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing Nothing (Just True)))
|
#==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing Nothing (Just True)))
|
||||||
it "x.msg.new simple link" $
|
it "x.msg.new simple link" $
|
||||||
"{\"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\":\"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\"}}}}"
|
||||||
#==# XMsgNew (MCSimple (extMsgContent (MCLink "https://simplex.chat" $ LinkPreview {uri = "https://simplex.chat", title = "SimpleX Chat", description = "SimpleX Chat", image = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA", content = Nothing}) Nothing))
|
#==# XMsgNew (MCSimple (extMsgContent (MCLink "https://simplex.chat" $ LinkPreview {uri = "https://simplex.chat", title = "SimpleX Chat", description = "SimpleX Chat", image = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA", content = Nothing}) Nothing))
|
||||||
it "x.msg.new simple image" $
|
it "x.msg.new simple image" $
|
||||||
"{\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}}"
|
||||||
#==# XMsgNew (MCSimple (extMsgContent (MCImage "" $ ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=") Nothing))
|
#==# XMsgNew (MCSimple (extMsgContent (MCImage "" $ ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=") Nothing))
|
||||||
it "x.msg.new simple image with text" $
|
it "x.msg.new simple image with text" $
|
||||||
"{\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"here's an image\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}}"
|
"{\"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=\"}}}"
|
||||||
#==# XMsgNew (MCSimple (extMsgContent (MCImage "here's an image" $ ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=") Nothing))
|
#==# XMsgNew (MCSimple (extMsgContent (MCImage "here's an image" $ ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=") Nothing))
|
||||||
it "x.msg.new chat message " $
|
it "x.msg.new chat message" $
|
||||||
"{\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||||
##==## ChatMessage (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||||
|
it "x.msg.new chat message with chat version range" $
|
||||||
|
"{\"v\":\"1-2\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||||
|
##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||||
it "x.msg.new quote" $
|
it "x.msg.new quote" $
|
||||||
"{\"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\":\"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\"}}}}"
|
||||||
##==## ChatMessage
|
##==## ChatMessage
|
||||||
|
chatInitialVRange
|
||||||
(Just $ SharedMsgId "\1\2\3\4")
|
(Just $ SharedMsgId "\1\2\3\4")
|
||||||
(XMsgNew (MCQuote quotedMsg (extMsgContent (MCText "hello to you too") Nothing)))
|
(XMsgNew (MCQuote quotedMsg (extMsgContent (MCText "hello to you too") Nothing)))
|
||||||
it "x.msg.new quote - timed message TTL" $
|
it "x.msg.new quote - timed message TTL" $
|
||||||
"{\"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\":\"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}}"
|
||||||
##==## ChatMessage
|
##==## ChatMessage
|
||||||
|
chatInitialVRange
|
||||||
(Just $ SharedMsgId "\1\2\3\4")
|
(Just $ SharedMsgId "\1\2\3\4")
|
||||||
(XMsgNew (MCQuote quotedMsg (ExtMsgContent (MCText "hello to you too") Nothing (Just 3600) Nothing)))
|
(XMsgNew (MCQuote quotedMsg (ExtMsgContent (MCText "hello to you too") Nothing (Just 3600) Nothing)))
|
||||||
it "x.msg.new quote - live message" $
|
it "x.msg.new quote - live message" $
|
||||||
"{\"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\":\"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}}"
|
||||||
##==## ChatMessage
|
##==## ChatMessage
|
||||||
|
chatInitialVRange
|
||||||
(Just $ SharedMsgId "\1\2\3\4")
|
(Just $ SharedMsgId "\1\2\3\4")
|
||||||
(XMsgNew (MCQuote quotedMsg (ExtMsgContent (MCText "hello to you too") Nothing Nothing (Just True))))
|
(XMsgNew (MCQuote quotedMsg (ExtMsgContent (MCText "hello to you too") Nothing Nothing (Just True))))
|
||||||
it "x.msg.new forward" $
|
it "x.msg.new forward" $
|
||||||
"{\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true}}"
|
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true}}"
|
||||||
##==## ChatMessage (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (extMsgContent (MCText "hello") Nothing))
|
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (extMsgContent (MCText "hello") Nothing))
|
||||||
it "x.msg.new forward - timed message TTL" $
|
it "x.msg.new forward - timed message TTL" $
|
||||||
"{\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"ttl\":3600}}"
|
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"ttl\":3600}}"
|
||||||
##==## ChatMessage (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (ExtMsgContent (MCText "hello") Nothing (Just 3600) Nothing))
|
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (ExtMsgContent (MCText "hello") Nothing (Just 3600) Nothing))
|
||||||
it "x.msg.new forward - live message" $
|
it "x.msg.new forward - live message" $
|
||||||
"{\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"live\":true}}"
|
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"live\":true}}"
|
||||||
##==## ChatMessage (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (ExtMsgContent (MCText "hello") Nothing Nothing (Just True)))
|
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (ExtMsgContent (MCText "hello") Nothing Nothing (Just True)))
|
||||||
it "x.msg.new simple text with file" $
|
it "x.msg.new simple text with file" $
|
||||||
"{\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
||||||
#==# XMsgNew (MCSimple (extMsgContent (MCText "hello") (Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing})))
|
#==# XMsgNew (MCSimple (extMsgContent (MCText "hello") (Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing})))
|
||||||
it "x.msg.new simple file with file" $
|
it "x.msg.new simple file with file" $
|
||||||
"{\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"type\":\"file\"},\"file\":{\"fileSize\":12345,\"fileName\":\"file.txt\"}}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"\",\"type\":\"file\"},\"file\":{\"fileSize\":12345,\"fileName\":\"file.txt\"}}}"
|
||||||
#==# XMsgNew (MCSimple (extMsgContent (MCFile "") (Just FileInvitation {fileName = "file.txt", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing})))
|
#==# XMsgNew (MCSimple (extMsgContent (MCFile "") (Just FileInvitation {fileName = "file.txt", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing})))
|
||||||
it "x.msg.new quote with file" $
|
it "x.msg.new quote with file" $
|
||||||
"{\"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\"}},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
"{\"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\"}},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
||||||
##==## ChatMessage
|
##==## ChatMessage
|
||||||
|
chatInitialVRange
|
||||||
(Just $ SharedMsgId "\1\2\3\4")
|
(Just $ SharedMsgId "\1\2\3\4")
|
||||||
( XMsgNew
|
( XMsgNew
|
||||||
( MCQuote
|
( MCQuote
|
||||||
@ -165,101 +172,113 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
it "x.msg.new forward with file" $
|
it "x.msg.new forward with file" $
|
||||||
"{\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
||||||
##==## ChatMessage (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (extMsgContent (MCText "hello") (Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing})))
|
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (extMsgContent (MCText "hello") (Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing})))
|
||||||
it "x.msg.update" $
|
it "x.msg.update" $
|
||||||
"{\"event\":\"x.msg.update\",\"params\":{\"msgId\":\"AQIDBA==\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.update\",\"params\":{\"msgId\":\"AQIDBA==\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||||
#==# XMsgUpdate (SharedMsgId "\1\2\3\4") (MCText "hello") Nothing Nothing
|
#==# XMsgUpdate (SharedMsgId "\1\2\3\4") (MCText "hello") Nothing Nothing
|
||||||
it "x.msg.del" $
|
it "x.msg.del" $
|
||||||
"{\"event\":\"x.msg.del\",\"params\":{\"msgId\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.del\",\"params\":{\"msgId\":\"AQIDBA==\"}}"
|
||||||
#==# XMsgDel (SharedMsgId "\1\2\3\4") Nothing
|
#==# XMsgDel (SharedMsgId "\1\2\3\4") Nothing
|
||||||
it "x.msg.deleted" $
|
it "x.msg.deleted" $
|
||||||
"{\"event\":\"x.msg.deleted\",\"params\":{}}"
|
"{\"v\":\"1\",\"event\":\"x.msg.deleted\",\"params\":{}}"
|
||||||
#==# XMsgDeleted
|
#==# XMsgDeleted
|
||||||
it "x.file" $
|
it "x.file" $
|
||||||
"{\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
"{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%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}
|
#==# XFile FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Just testConnReq, fileInline = Nothing, fileDescr = Nothing}
|
||||||
it "x.file without file invitation" $
|
it "x.file without file invitation" $
|
||||||
"{\"event\":\"x.file\",\"params\":{\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
"{\"v\":\"1\",\"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}
|
#==# XFile FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing}
|
||||||
it "x.file.acpt" $
|
it "x.file.acpt" $
|
||||||
"{\"event\":\"x.file.acpt\",\"params\":{\"fileName\":\"photo.jpg\"}}"
|
"{\"v\":\"1\",\"event\":\"x.file.acpt\",\"params\":{\"fileName\":\"photo.jpg\"}}"
|
||||||
#==# XFileAcpt "photo.jpg"
|
#==# XFileAcpt "photo.jpg"
|
||||||
it "x.file.acpt.inv" $
|
it "x.file.acpt.inv" $
|
||||||
"{\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}"
|
"{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}"
|
||||||
#==# XFileAcptInv (SharedMsgId "\1\2\3\4") (Just testConnReq) "photo.jpg"
|
#==# XFileAcptInv (SharedMsgId "\1\2\3\4") (Just testConnReq) "photo.jpg"
|
||||||
it "x.file.acpt.inv" $
|
it "x.file.acpt.inv" $
|
||||||
"{\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\"}}"
|
"{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\"}}"
|
||||||
#==# XFileAcptInv (SharedMsgId "\1\2\3\4") Nothing "photo.jpg"
|
#==# XFileAcptInv (SharedMsgId "\1\2\3\4") Nothing "photo.jpg"
|
||||||
it "x.file.cancel" $
|
it "x.file.cancel" $
|
||||||
"{\"event\":\"x.file.cancel\",\"params\":{\"msgId\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.file.cancel\",\"params\":{\"msgId\":\"AQIDBA==\"}}"
|
||||||
#==# XFileCancel (SharedMsgId "\1\2\3\4")
|
#==# XFileCancel (SharedMsgId "\1\2\3\4")
|
||||||
it "x.info" $
|
it "x.info" $
|
||||||
"{\"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\":\"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\"}}}}}"
|
||||||
#==# XInfo testProfile
|
#==# XInfo testProfile
|
||||||
it "x.info with empty full name" $
|
it "x.info with empty full name" $
|
||||||
"{\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
|
||||||
#==# XInfo Profile {displayName = "alice", fullName = "", image = Nothing, contactLink = Nothing, preferences = testChatPreferences}
|
#==# XInfo Profile {displayName = "alice", fullName = "", image = Nothing, contactLink = Nothing, preferences = testChatPreferences}
|
||||||
it "x.contact with xContactId" $
|
it "x.contact with xContactId" $
|
||||||
"{\"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\":\"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\"}}}}}"
|
||||||
#==# XContact testProfile (Just $ XContactId "\1\2\3\4")
|
#==# XContact testProfile (Just $ XContactId "\1\2\3\4")
|
||||||
it "x.contact without XContactId" $
|
it "x.contact without XContactId" $
|
||||||
"{\"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\":\"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\"}}}}}"
|
||||||
#==# XContact testProfile Nothing
|
#==# XContact testProfile Nothing
|
||||||
it "x.contact with content null" $
|
it "x.contact with content null" $
|
||||||
"{\"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\":\"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\"}}}}}"
|
||||||
==# XContact testProfile Nothing
|
==# XContact testProfile Nothing
|
||||||
it "x.contact with content (ignored)" $
|
it "x.contact with content (ignored)" $
|
||||||
"{\"event\":\"x.contact\",\"params\":{\"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\":\"1\",\"event\":\"x.contact\",\"params\":{\"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
|
==# XContact testProfile Nothing
|
||||||
it "x.grp.inv" $
|
it "x.grp.inv" $
|
||||||
"{\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%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\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%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, groupLinkId = Nothing}
|
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Nothing}
|
||||||
it "x.grp.inv with group link id" $
|
it "x.grp.inv with group link id" $
|
||||||
"{\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%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\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%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, groupLinkId = Just $ GroupLinkId "\1\2\3\4"}
|
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Just $ GroupLinkId "\1\2\3\4"}
|
||||||
it "x.grp.acpt without incognito profile" $
|
it "x.grp.acpt without incognito profile" $
|
||||||
"{\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
||||||
#==# XGrpAcpt (MemberId "\1\2\3\4")
|
#==# XGrpAcpt (MemberId "\1\2\3\4")
|
||||||
it "x.grp.mem.new" $
|
it "x.grp.mem.new" $
|
||||||
"{\"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\":\"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\"}}}}}}"
|
||||||
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, profile = testProfile}
|
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile}
|
||||||
|
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-2\",\"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}
|
||||||
it "x.grp.mem.intro" $
|
it "x.grp.mem.intro" $
|
||||||
"{\"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\":\"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\"}}}}}}"
|
||||||
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, profile = testProfile}
|
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile}
|
||||||
|
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-2\",\"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}
|
||||||
it "x.grp.mem.inv" $
|
it "x.grp.mem.inv" $
|
||||||
"{\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}"
|
||||||
#==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = testConnReq}
|
#==# 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\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%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" $
|
it "x.grp.mem.fwd" $
|
||||||
"{\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%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\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%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, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = testConnReq}
|
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} 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\":\"https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-2\",\"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} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
|
||||||
it "x.grp.mem.info" $
|
it "x.grp.mem.info" $
|
||||||
"{\"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\":\"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\"}}}}}"
|
||||||
#==# XGrpMemInfo (MemberId "\1\2\3\4") testProfile
|
#==# XGrpMemInfo (MemberId "\1\2\3\4") testProfile
|
||||||
it "x.grp.mem.con" $
|
it "x.grp.mem.con" $
|
||||||
"{\"event\":\"x.grp.mem.con\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.con\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
||||||
#==# XGrpMemCon (MemberId "\1\2\3\4")
|
#==# XGrpMemCon (MemberId "\1\2\3\4")
|
||||||
it "x.grp.mem.con.all" $
|
it "x.grp.mem.con.all" $
|
||||||
"{\"event\":\"x.grp.mem.con.all\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.con.all\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
||||||
#==# XGrpMemConAll (MemberId "\1\2\3\4")
|
#==# XGrpMemConAll (MemberId "\1\2\3\4")
|
||||||
it "x.grp.mem.del" $
|
it "x.grp.mem.del" $
|
||||||
"{\"event\":\"x.grp.mem.del\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.del\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
||||||
#==# XGrpMemDel (MemberId "\1\2\3\4")
|
#==# XGrpMemDel (MemberId "\1\2\3\4")
|
||||||
it "x.grp.leave" $
|
it "x.grp.leave" $
|
||||||
"{\"event\":\"x.grp.leave\",\"params\":{}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.leave\",\"params\":{}}"
|
||||||
==# XGrpLeave
|
==# XGrpLeave
|
||||||
it "x.grp.del" $
|
it "x.grp.del" $
|
||||||
"{\"event\":\"x.grp.del\",\"params\":{}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.del\",\"params\":{}}"
|
||||||
==# XGrpDel
|
==# XGrpDel
|
||||||
it "x.info.probe" $
|
it "x.info.probe" $
|
||||||
"{\"event\":\"x.info.probe\",\"params\":{\"probe\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.info.probe\",\"params\":{\"probe\":\"AQIDBA==\"}}"
|
||||||
#==# XInfoProbe (Probe "\1\2\3\4")
|
#==# XInfoProbe (Probe "\1\2\3\4")
|
||||||
it "x.info.probe.check" $
|
it "x.info.probe.check" $
|
||||||
"{\"event\":\"x.info.probe.check\",\"params\":{\"probeHash\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.info.probe.check\",\"params\":{\"probeHash\":\"AQIDBA==\"}}"
|
||||||
#==# XInfoProbeCheck (ProbeHash "\1\2\3\4")
|
#==# XInfoProbeCheck (ProbeHash "\1\2\3\4")
|
||||||
it "x.info.probe.ok" $
|
it "x.info.probe.ok" $
|
||||||
"{\"event\":\"x.info.probe.ok\",\"params\":{\"probe\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.info.probe.ok\",\"params\":{\"probe\":\"AQIDBA==\"}}"
|
||||||
#==# XInfoProbeOk (Probe "\1\2\3\4")
|
#==# XInfoProbeOk (Probe "\1\2\3\4")
|
||||||
it "x.ok" $
|
it "x.ok" $
|
||||||
"{\"event\":\"x.ok\",\"params\":{}}"
|
"{\"v\":\"1\",\"event\":\"x.ok\",\"params\":{}}"
|
||||||
==# XOk
|
==# XOk
|
||||||
|
Loading…
Reference in New Issue
Block a user