2021-06-25 18:18:24 +01:00
|
|
|
{-# LANGUAGE ConstraintKinds #-}
|
2022-01-28 10:41:09 +00:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
2021-06-25 18:18:24 +01:00
|
|
|
{-# LANGUAGE DeriveAnyClass #-}
|
2022-01-26 21:20:08 +00:00
|
|
|
{-# LANGUAGE DeriveGeneric #-}
|
2021-07-05 19:54:44 +01:00
|
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
2021-06-25 18:18:24 +01:00
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
2021-07-05 19:54:44 +01:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2021-06-25 18:18:24 +01:00
|
|
|
|
|
|
|
|
module Simplex.Chat.Controller where
|
|
|
|
|
|
2022-02-06 16:18:01 +00:00
|
|
|
import Control.Concurrent.Async (Async)
|
2021-06-25 18:18:24 +01:00
|
|
|
import Control.Exception
|
|
|
|
|
import Control.Monad.Except
|
|
|
|
|
import Control.Monad.IO.Unlift
|
|
|
|
|
import Control.Monad.Reader
|
2021-07-12 19:00:03 +01:00
|
|
|
import Crypto.Random (ChaChaDRG)
|
2022-01-26 21:20:08 +00:00
|
|
|
import Data.Aeson (ToJSON)
|
|
|
|
|
import qualified Data.Aeson as J
|
2022-01-24 16:07:17 +00:00
|
|
|
import Data.ByteString.Char8 (ByteString)
|
2021-09-04 07:32:56 +01:00
|
|
|
import Data.Int (Int64)
|
|
|
|
|
import Data.Map.Strict (Map)
|
2022-01-24 16:07:17 +00:00
|
|
|
import Data.Text (Text)
|
2022-01-26 21:20:08 +00:00
|
|
|
import GHC.Generics (Generic)
|
2021-09-04 07:32:56 +01:00
|
|
|
import Numeric.Natural
|
2022-01-24 16:07:17 +00:00
|
|
|
import Simplex.Chat.Messages
|
2022-01-30 10:49:13 +00:00
|
|
|
import Simplex.Chat.Protocol
|
2021-07-05 20:05:07 +01:00
|
|
|
import Simplex.Chat.Store (StoreError)
|
2021-07-04 18:42:24 +01:00
|
|
|
import Simplex.Chat.Types
|
2021-06-25 18:18:24 +01:00
|
|
|
import Simplex.Messaging.Agent (AgentClient)
|
2021-09-04 07:32:56 +01:00
|
|
|
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig)
|
2022-01-24 16:07:17 +00:00
|
|
|
import Simplex.Messaging.Agent.Protocol
|
2021-07-04 18:42:24 +01:00
|
|
|
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore)
|
2022-01-29 20:21:37 +00:00
|
|
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, sumTypeJSON)
|
2022-01-24 16:07:17 +00:00
|
|
|
import Simplex.Messaging.Protocol (CorrId)
|
2021-09-04 07:32:56 +01:00
|
|
|
import System.IO (Handle)
|
2021-06-25 18:18:24 +01:00
|
|
|
import UnliftIO.STM
|
|
|
|
|
|
2021-11-07 21:57:05 +00:00
|
|
|
versionNumber :: String
|
2022-02-10 20:08:29 +04:00
|
|
|
versionNumber = "1.1.1"
|
2022-01-11 21:23:57 +00:00
|
|
|
|
|
|
|
|
versionStr :: String
|
|
|
|
|
versionStr = "SimpleX Chat v" <> versionNumber
|
|
|
|
|
|
|
|
|
|
updateStr :: String
|
|
|
|
|
updateStr = "To update run: curl -o- https://raw.githubusercontent.com/simplex-chat/simplex-chat/master/install.sh | bash"
|
2021-11-07 21:57:05 +00:00
|
|
|
|
2021-09-04 07:32:56 +01:00
|
|
|
data ChatConfig = ChatConfig
|
|
|
|
|
{ agentConfig :: AgentConfig,
|
|
|
|
|
dbPoolSize :: Int,
|
2022-02-07 15:19:34 +04:00
|
|
|
yesToMigrations :: Bool,
|
2021-09-04 07:32:56 +01:00
|
|
|
tbqSize :: Natural,
|
2022-02-09 20:58:02 +04:00
|
|
|
fileChunkSize :: Integer,
|
|
|
|
|
testView :: Bool
|
2021-09-04 07:32:56 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-21 11:09:33 +00:00
|
|
|
data ActiveTo = ActiveNone | ActiveC ContactName | ActiveG GroupName
|
|
|
|
|
deriving (Eq)
|
|
|
|
|
|
2021-06-25 18:18:24 +01:00
|
|
|
data ChatController = ChatController
|
2022-02-06 16:18:01 +00:00
|
|
|
{ currentUser :: TVar (Maybe User),
|
2022-01-21 11:09:33 +00:00
|
|
|
activeTo :: TVar ActiveTo,
|
2021-12-13 12:05:57 +00:00
|
|
|
firstTime :: Bool,
|
2021-07-04 18:42:24 +01:00
|
|
|
smpAgent :: AgentClient,
|
2022-02-06 16:18:01 +00:00
|
|
|
agentAsync :: TVar (Maybe (Async ())),
|
2021-07-04 18:42:24 +01:00
|
|
|
chatStore :: SQLiteStore,
|
2021-07-12 19:00:03 +01:00
|
|
|
idsDrg :: TVar ChaChaDRG,
|
2022-01-24 16:07:17 +00:00
|
|
|
inputQ :: TBQueue String,
|
2022-01-26 21:20:08 +00:00
|
|
|
outputQ :: TBQueue (Maybe CorrId, ChatResponse),
|
2021-06-26 20:20:33 +01:00
|
|
|
notifyQ :: TBQueue Notification,
|
2021-08-05 20:51:48 +01:00
|
|
|
sendNotification :: Notification -> IO (),
|
2021-09-04 07:32:56 +01:00
|
|
|
chatLock :: TMVar (),
|
|
|
|
|
sndFiles :: TVar (Map Int64 Handle),
|
|
|
|
|
rcvFiles :: TVar (Map Int64 Handle),
|
|
|
|
|
config :: ChatConfig
|
2021-06-25 18:18:24 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-24 16:07:17 +00:00
|
|
|
data HelpSection = HSMain | HSFiles | HSGroups | HSMyAddress | HSMarkdown
|
2022-01-26 21:20:08 +00:00
|
|
|
deriving (Show, Generic)
|
|
|
|
|
|
|
|
|
|
instance ToJSON HelpSection where
|
2022-01-27 22:01:15 +00:00
|
|
|
toJSON = J.genericToJSON . enumJSON $ dropPrefix "HS"
|
|
|
|
|
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "HS"
|
2022-01-24 16:07:17 +00:00
|
|
|
|
|
|
|
|
data ChatCommand
|
2022-02-06 16:18:01 +00:00
|
|
|
= ShowActiveUser
|
|
|
|
|
| CreateActiveUser Profile
|
|
|
|
|
| StartChat
|
|
|
|
|
| APIGetChats
|
2022-02-01 15:05:27 +04:00
|
|
|
| APIGetChat ChatType Int64 ChatPagination
|
2022-01-28 10:41:09 +00:00
|
|
|
| APIGetChatItems Int
|
2022-01-30 10:49:13 +00:00
|
|
|
| APISendMessage ChatType Int64 MsgContent
|
2022-02-08 17:27:43 +04:00
|
|
|
| APIChatRead ChatType Int64 (ChatItemId, ChatItemId)
|
2022-01-31 21:53:53 +04:00
|
|
|
| APIDeleteChat ChatType Int64
|
|
|
|
|
| APIAcceptContact Int64
|
2022-02-01 05:31:34 +00:00
|
|
|
| APIRejectContact Int64
|
2022-01-28 10:41:09 +00:00
|
|
|
| ChatHelp HelpSection
|
2022-01-24 16:07:17 +00:00
|
|
|
| Welcome
|
|
|
|
|
| AddContact
|
|
|
|
|
| Connect (Maybe AConnectionRequestUri)
|
|
|
|
|
| ConnectAdmin
|
|
|
|
|
| DeleteContact ContactName
|
|
|
|
|
| ListContacts
|
|
|
|
|
| CreateMyAddress
|
|
|
|
|
| DeleteMyAddress
|
|
|
|
|
| ShowMyAddress
|
|
|
|
|
| AcceptContact ContactName
|
|
|
|
|
| RejectContact ContactName
|
|
|
|
|
| SendMessage ContactName ByteString
|
|
|
|
|
| NewGroup GroupProfile
|
|
|
|
|
| AddMember GroupName ContactName GroupMemberRole
|
|
|
|
|
| JoinGroup GroupName
|
|
|
|
|
| RemoveMember GroupName ContactName
|
|
|
|
|
| MemberRole GroupName ContactName GroupMemberRole
|
|
|
|
|
| LeaveGroup GroupName
|
|
|
|
|
| DeleteGroup GroupName
|
|
|
|
|
| ListMembers GroupName
|
|
|
|
|
| ListGroups
|
|
|
|
|
| SendGroupMessage GroupName ByteString
|
|
|
|
|
| SendFile ContactName FilePath
|
|
|
|
|
| SendGroupFile GroupName FilePath
|
2022-01-26 21:20:08 +00:00
|
|
|
| ReceiveFile FileTransferId (Maybe FilePath)
|
|
|
|
|
| CancelFile FileTransferId
|
|
|
|
|
| FileStatus FileTransferId
|
2022-01-24 16:07:17 +00:00
|
|
|
| ShowProfile
|
|
|
|
|
| UpdateProfile Profile
|
|
|
|
|
| QuitChat
|
|
|
|
|
| ShowVersion
|
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
data ChatResponse
|
2022-02-06 16:18:01 +00:00
|
|
|
= CRActiveUser {user :: User}
|
|
|
|
|
| CRChatStarted
|
|
|
|
|
| CRApiChats {chats :: [AChat]}
|
2022-01-30 10:49:13 +00:00
|
|
|
| CRApiChat {chat :: AChat}
|
2022-01-28 10:41:09 +00:00
|
|
|
| CRNewChatItem {chatItem :: AChatItem}
|
2022-02-07 15:19:34 +04:00
|
|
|
| CRChatItemUpdated {chatItem :: AChatItem}
|
2022-02-02 11:43:52 +00:00
|
|
|
| CRMsgIntegrityError {msgerror :: MsgErrorType} -- TODO make it chat item to support in mobile
|
2022-01-26 21:20:08 +00:00
|
|
|
| CRCmdAccepted {corr :: CorrId}
|
2022-02-08 17:27:43 +04:00
|
|
|
| CRCmdOk
|
2022-01-27 22:01:15 +00:00
|
|
|
| CRChatHelp {helpSection :: HelpSection}
|
|
|
|
|
| CRWelcome {user :: User}
|
2022-01-26 21:20:08 +00:00
|
|
|
| CRGroupCreated {groupInfo :: GroupInfo}
|
|
|
|
|
| CRGroupMembers {group :: Group}
|
|
|
|
|
| CRContactsList {contacts :: [Contact]}
|
|
|
|
|
| CRUserContactLink {connReqContact :: ConnReqContact}
|
2022-01-31 21:53:53 +04:00
|
|
|
| CRContactRequestRejected {contactRequest :: UserContactRequest}
|
2022-01-26 21:20:08 +00:00
|
|
|
| CRUserAcceptedGroupSent {groupInfo :: GroupInfo}
|
|
|
|
|
| CRUserDeletedMember {groupInfo :: GroupInfo, member :: GroupMember}
|
|
|
|
|
| CRGroupsList {groups :: [GroupInfo]}
|
|
|
|
|
| CRSentGroupInvitation {groupInfo :: GroupInfo, contact :: Contact}
|
|
|
|
|
| CRFileTransferStatus (FileTransfer, [Integer]) -- TODO refactor this type to FileTransferStatus
|
|
|
|
|
| CRUserProfile {profile :: Profile}
|
2022-01-24 16:07:17 +00:00
|
|
|
| CRUserProfileNoChange
|
|
|
|
|
| CRVersionInfo
|
2022-01-26 21:20:08 +00:00
|
|
|
| CRInvitation {connReqInvitation :: ConnReqInvitation}
|
2022-01-24 16:07:17 +00:00
|
|
|
| CRSentConfirmation
|
|
|
|
|
| CRSentInvitation
|
|
|
|
|
| CRContactUpdated {fromContact :: Contact, toContact :: Contact}
|
|
|
|
|
| CRContactsMerged {intoContact :: Contact, mergedContact :: Contact}
|
2022-01-31 15:14:56 +04:00
|
|
|
| CRContactDeleted {contact :: Contact}
|
2022-01-26 21:20:08 +00:00
|
|
|
| CRUserContactLinkCreated {connReqContact :: ConnReqContact}
|
2022-01-24 16:07:17 +00:00
|
|
|
| CRUserContactLinkDeleted
|
2022-01-31 21:53:53 +04:00
|
|
|
| CRReceivedContactRequest {contactRequest :: UserContactRequest}
|
2022-02-01 17:04:44 +04:00
|
|
|
| CRAcceptingContactRequest {contact :: Contact}
|
2022-01-26 21:20:08 +00:00
|
|
|
| CRLeftMemberUser {groupInfo :: GroupInfo}
|
|
|
|
|
| CRGroupDeletedUser {groupInfo :: GroupInfo}
|
|
|
|
|
| CRRcvFileAccepted {fileTransfer :: RcvFileTransfer, filePath :: FilePath}
|
|
|
|
|
| CRRcvFileAcceptedSndCancelled {rcvFileTransfer :: RcvFileTransfer}
|
|
|
|
|
| CRRcvFileStart {rcvFileTransfer :: RcvFileTransfer}
|
|
|
|
|
| CRRcvFileComplete {rcvFileTransfer :: RcvFileTransfer}
|
|
|
|
|
| CRRcvFileCancelled {rcvFileTransfer :: RcvFileTransfer}
|
|
|
|
|
| CRRcvFileSndCancelled {rcvFileTransfer :: RcvFileTransfer}
|
|
|
|
|
| CRSndFileStart {sndFileTransfer :: SndFileTransfer}
|
|
|
|
|
| CRSndFileComplete {sndFileTransfer :: SndFileTransfer}
|
|
|
|
|
| CRSndFileCancelled {sndFileTransfer :: SndFileTransfer}
|
|
|
|
|
| CRSndFileRcvCancelled {sndFileTransfer :: SndFileTransfer}
|
|
|
|
|
| CRSndGroupFileCancelled {sndFileTransfers :: [SndFileTransfer]}
|
2022-01-24 16:07:17 +00:00
|
|
|
| CRUserProfileUpdated {fromProfile :: Profile, toProfile :: Profile}
|
2022-02-08 13:04:17 +04:00
|
|
|
| CRContactConnecting {contact :: Contact}
|
2022-01-26 21:20:08 +00:00
|
|
|
| CRContactConnected {contact :: Contact}
|
|
|
|
|
| CRContactAnotherClient {contact :: Contact}
|
|
|
|
|
| CRContactDisconnected {contact :: Contact}
|
|
|
|
|
| CRContactSubscribed {contact :: Contact}
|
|
|
|
|
| CRContactSubError {contact :: Contact, chatError :: ChatError}
|
|
|
|
|
| CRGroupInvitation {groupInfo :: GroupInfo}
|
|
|
|
|
| CRReceivedGroupInvitation {groupInfo :: GroupInfo, contact :: Contact, memberRole :: GroupMemberRole}
|
|
|
|
|
| CRUserJoinedGroup {groupInfo :: GroupInfo}
|
|
|
|
|
| CRJoinedGroupMember {groupInfo :: GroupInfo, member :: GroupMember}
|
|
|
|
|
| CRJoinedGroupMemberConnecting {groupInfo :: GroupInfo, hostMember :: GroupMember, member :: GroupMember}
|
|
|
|
|
| CRConnectedToGroupMember {groupInfo :: GroupInfo, member :: GroupMember}
|
|
|
|
|
| CRDeletedMember {groupInfo :: GroupInfo, byMember :: GroupMember, deletedMember :: GroupMember}
|
|
|
|
|
| CRDeletedMemberUser {groupInfo :: GroupInfo, member :: GroupMember}
|
|
|
|
|
| CRLeftMember {groupInfo :: GroupInfo, member :: GroupMember}
|
|
|
|
|
| CRGroupEmpty {groupInfo :: GroupInfo}
|
|
|
|
|
| CRGroupRemoved {groupInfo :: GroupInfo}
|
|
|
|
|
| CRGroupDeleted {groupInfo :: GroupInfo, member :: GroupMember}
|
|
|
|
|
| CRMemberSubError {groupInfo :: GroupInfo, contactName :: ContactName, chatError :: ChatError} -- TODO Contact? or GroupMember?
|
|
|
|
|
| CRGroupSubscribed {groupInfo :: GroupInfo}
|
|
|
|
|
| CRSndFileSubError {sndFileTransfer :: SndFileTransfer, chatError :: ChatError}
|
|
|
|
|
| CRRcvFileSubError {rcvFileTransfer :: RcvFileTransfer, chatError :: ChatError}
|
2022-01-24 16:07:17 +00:00
|
|
|
| CRUserContactLinkSubscribed
|
2022-01-26 21:20:08 +00:00
|
|
|
| CRUserContactLinkSubError {chatError :: ChatError}
|
|
|
|
|
| CRMessageError {severity :: Text, errorMessage :: Text}
|
|
|
|
|
| CRChatCmdError {chatError :: ChatError}
|
|
|
|
|
| CRChatError {chatError :: ChatError}
|
|
|
|
|
deriving (Show, Generic)
|
|
|
|
|
|
|
|
|
|
instance ToJSON ChatResponse where
|
2022-01-29 20:21:37 +00:00
|
|
|
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CR"
|
|
|
|
|
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CR"
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
data ChatError
|
2022-01-27 22:01:15 +00:00
|
|
|
= ChatError {errorType :: ChatErrorType}
|
|
|
|
|
| ChatErrorAgent {agentError :: AgentErrorType}
|
|
|
|
|
| ChatErrorStore {storeError :: StoreError}
|
2022-01-26 21:20:08 +00:00
|
|
|
deriving (Show, Exception, Generic)
|
|
|
|
|
|
|
|
|
|
instance ToJSON ChatError where
|
2022-01-29 20:21:37 +00:00
|
|
|
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "Chat"
|
|
|
|
|
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "Chat"
|
2021-07-04 18:42:24 +01:00
|
|
|
|
2021-07-16 07:40:55 +01:00
|
|
|
data ChatErrorType
|
2022-02-06 16:18:01 +00:00
|
|
|
= CENoActiveUser
|
|
|
|
|
| CEActiveUserExists
|
|
|
|
|
| CEChatNotStarted
|
2022-01-24 16:07:17 +00:00
|
|
|
| CEInvalidConnReq
|
2022-02-06 16:18:01 +00:00
|
|
|
| CEInvalidChatMessage {message :: String}
|
2022-01-31 15:14:56 +04:00
|
|
|
| CEContactGroups {contact :: Contact, groupNames :: [GroupName]}
|
2022-02-06 16:18:01 +00:00
|
|
|
| CEGroupUserRole
|
2022-01-26 21:20:08 +00:00
|
|
|
| CEGroupContactRole {contactName :: ContactName}
|
|
|
|
|
| CEGroupDuplicateMember {contactName :: ContactName}
|
2021-07-16 07:40:55 +01:00
|
|
|
| CEGroupDuplicateMemberId
|
2022-01-26 21:20:08 +00:00
|
|
|
| CEGroupNotJoined {groupInfo :: GroupInfo}
|
2021-07-24 10:26:28 +01:00
|
|
|
| CEGroupMemberNotActive
|
2021-08-02 20:10:24 +01:00
|
|
|
| CEGroupMemberUserRemoved
|
2022-01-26 21:20:08 +00:00
|
|
|
| CEGroupMemberNotFound {contactName :: ContactName}
|
|
|
|
|
| CEGroupMemberIntroNotFound {contactName :: ContactName}
|
|
|
|
|
| CEGroupCantResendInvitation {groupInfo :: GroupInfo, contactName :: ContactName}
|
|
|
|
|
| CEGroupInternal {message :: String}
|
|
|
|
|
| CEFileNotFound {message :: String}
|
|
|
|
|
| CEFileAlreadyReceiving {message :: String}
|
|
|
|
|
| CEFileAlreadyExists {filePath :: FilePath}
|
|
|
|
|
| CEFileRead {filePath :: FilePath, message :: String}
|
|
|
|
|
| CEFileWrite {filePath :: FilePath, message :: String}
|
|
|
|
|
| CEFileSend {fileId :: FileTransferId, agentError :: AgentErrorType}
|
|
|
|
|
| CEFileRcvChunk {message :: String}
|
|
|
|
|
| CEFileInternal {message :: String}
|
2022-01-11 08:50:44 +00:00
|
|
|
| CEAgentVersion
|
2022-01-26 21:20:08 +00:00
|
|
|
| CECommandError {message :: String}
|
|
|
|
|
deriving (Show, Exception, Generic)
|
|
|
|
|
|
|
|
|
|
instance ToJSON ChatErrorType where
|
2022-01-29 20:21:37 +00:00
|
|
|
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CE"
|
|
|
|
|
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CE"
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2022-01-24 16:07:17 +00:00
|
|
|
type ChatMonad m = (MonadUnliftIO m, MonadReader ChatController m, MonadError ChatError m)
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2022-02-06 16:18:01 +00:00
|
|
|
chatCmdError :: String -> ChatResponse
|
|
|
|
|
chatCmdError = CRChatCmdError . ChatError . CECommandError
|
|
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
setActive :: (MonadUnliftIO m, MonadReader ChatController m) => ActiveTo -> m ()
|
2022-01-21 11:09:33 +00:00
|
|
|
setActive to = asks activeTo >>= atomically . (`writeTVar` to)
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2021-07-04 18:42:24 +01:00
|
|
|
unsetActive :: (MonadUnliftIO m, MonadReader ChatController m) => ActiveTo -> m ()
|
2022-01-21 11:09:33 +00:00
|
|
|
unsetActive a = asks activeTo >>= atomically . (`modifyTVar` unset)
|
2021-06-25 18:18:24 +01:00
|
|
|
where
|
|
|
|
|
unset a' = if a == a' then ActiveNone else a'
|