From 14514050aee99f757e9d9ecff75807aa46c8dce8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 23 Apr 2022 17:32:40 +0100 Subject: [PATCH] core: api for pending contact connections (#553) * core: api for pending contact connections * core: pending contact connection events / api --- src/Simplex/Chat.hs | 22 +++++++--- src/Simplex/Chat/Controller.hs | 4 +- src/Simplex/Chat/Messages.hs | 8 +++- src/Simplex/Chat/Store.hs | 76 +++++++++++++++++++++++++++++----- src/Simplex/Chat/Types.hs | 47 ++++++++------------- src/Simplex/Chat/View.hs | 3 ++ 6 files changed, 112 insertions(+), 48 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index bbd4398a1b..f7abde0677 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -183,11 +183,12 @@ processChatCommand = \case ff <- asks filesFolder atomically . writeTVar ff $ Just filesFolder' pure CRCmdOk - APIGetChats -> CRApiChats <$> withUser (\user -> withStore (`getChatPreviews` user)) + APIGetChats withPCC -> CRApiChats <$> withUser (\user -> withStore $ \st -> getChatPreviews st user withPCC) APIGetChat cType cId pagination -> withUser $ \user -> case cType of CTDirect -> CRApiChat . AChat SCTDirect <$> withStore (\st -> getDirectChat st user cId pagination) CTGroup -> CRApiChat . AChat SCTGroup <$> withStore (\st -> getGroupChat st user cId pagination) CTContactRequest -> pure $ chatCmdError "not implemented" + CTContactConnection -> pure $ chatCmdError "not supported" APIGetChatItems _pagination -> pure $ chatCmdError "not implemented" APISendMessage cType chatId file_ quotedItemId_ mc -> withUser $ \user@User {userId} -> withChatLock $ case cType of CTDirect -> do @@ -263,6 +264,7 @@ processChatCommand = \case quoteData (CIRcvMsgContent qmc) (CIGroupRcv m) _ = pure (qmc, CIQGroupRcv $ Just m, False, m) quoteData _ _ _ = throwChatError CEInvalidQuote CTContactRequest -> pure $ chatCmdError "not supported" + CTContactConnection -> pure $ chatCmdError "not supported" where quoteContent qmc = \case MCText _ -> qmc @@ -300,6 +302,7 @@ processChatCommand = \case _ -> throwChatError CEInvalidChatItemUpdate CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate CTContactRequest -> pure $ chatCmdError "not supported" + CTContactConnection -> pure $ chatCmdError "not supported" APIDeleteChatItem cType chatId itemId mode -> withUser $ \user@User {userId} -> withChatLock $ case cType of CTDirect -> do (ct@Contact {localDisplayName = c}, CChatItem msgDir deletedItem@ChatItem {meta = CIMeta {itemSharedMsgId}, file}) <- withStore $ \st -> (,) <$> getContact st userId chatId <*> getDirectChatItem st userId chatId itemId @@ -332,6 +335,7 @@ processChatCommand = \case pure $ CRChatItemDeleted (AChatItem SCTGroup msgDir (GroupChat gInfo) deletedItem) toCi (CIDMBroadcast, _, _) -> throwChatError CEInvalidChatItemDelete CTContactRequest -> pure $ chatCmdError "not supported" + CTContactConnection -> pure $ chatCmdError "not supported" where deleteFile :: MsgDirectionI d => UserId -> Maybe (CIFile d) -> m () deleteFile userId file = @@ -343,6 +347,7 @@ processChatCommand = \case CTDirect -> withStore (\st -> updateDirectChatItemsRead st chatId fromToIds) $> CRCmdOk CTGroup -> withStore (\st -> updateGroupChatItemsRead st chatId fromToIds) $> CRCmdOk CTContactRequest -> pure $ chatCmdError "not supported" + CTContactConnection -> pure $ chatCmdError "not supported" APIDeleteChat cType chatId -> withUser $ \User {userId} -> case cType of CTDirect -> do ct@Contact {localDisplayName} <- withStore $ \st -> getContact st userId chatId @@ -360,6 +365,8 @@ processChatCommand = \case unsetActive $ ActiveC localDisplayName pure $ CRContactDeleted ct gs -> throwChatError $ CEContactGroups ct gs + CTContactConnection -> + CRContactConnectionDeleted <$> withStore (\st -> deletePendingContactConnection st userId chatId) CTGroup -> pure $ chatCmdError "not implemented" CTContactRequest -> pure $ chatCmdError "not supported" APIAcceptContact connReqId -> withUser $ \user@User {userId} -> withChatLock $ do @@ -387,11 +394,13 @@ processChatCommand = \case Welcome -> withUser $ pure . CRWelcome AddContact -> withUser $ \User {userId} -> withChatLock . procCmd $ do (connId, cReq) <- withAgent (`createConnection` SCMInvitation) - withStore $ \st -> createDirectConnection st userId connId + conn <- withStore $ \st -> createDirectConnection st userId connId ConnNew + toView $ CRNewContactConnection conn pure $ CRInvitation cReq Connect (Just (ACR SCMInvitation cReq)) -> withUser $ \User {userId, profile} -> withChatLock . procCmd $ do connId <- withAgent $ \a -> joinConnection a cReq . directMessage $ XInfo profile - withStore $ \st -> createDirectConnection st userId connId + conn <- withStore $ \st -> createDirectConnection st userId connId ConnJoined + toView $ CRNewContactConnection conn pure CRSentConfirmation Connect (Just (ACR SCMContact cReq)) -> withUser $ \User {userId, profile} -> connectViaContact userId cReq profile @@ -647,7 +656,8 @@ processChatCommand = \case let randomXContactId = XContactId <$> (asks idsDrg >>= liftIO . (`randomBytes` 16)) xContactId <- maybe randomXContactId pure xContactId_ connId <- withAgent $ \a -> joinConnection a cReq $ directMessage (XContact profile $ Just xContactId) - withStore $ \st -> createConnReqConnection st userId connId cReqHash xContactId + conn <- withStore $ \st -> createConnReqConnection st userId connId cReqHash xContactId + toView $ CRNewContactConnection conn pure CRSentInvitation contactMember :: Contact -> [GroupMember] -> Maybe GroupMember contactMember Contact {contactId} = @@ -1889,7 +1899,7 @@ chatCommandP = <|> ("/user" <|> "/u") $> ShowActiveUser <|> "/_start" $> StartChat <|> "/_files_folder " *> (SetFilesFolder <$> filePath) - <|> "/_get chats" $> APIGetChats + <|> "/_get chats" *> (APIGetChats <$> (" connections" $> True <|> pure False)) <|> "/_get chat " *> (APIGetChat <$> chatTypeP <*> A.decimal <* A.space <*> chatPaginationP) <|> "/_get items count=" *> (APIGetChatItems <$> A.decimal) <|> "/_send " *> (APISendMessage <$> chatTypeP <*> A.decimal <*> optional filePathTagged <*> optional quotedItemIdTagged <* A.space <*> msgContentP) @@ -1962,7 +1972,7 @@ chatCommandP = where imagePrefix = (<>) <$> "data:" <*> ("image/png;base64," <|> "image/jpg;base64,") imageP = safeDecodeUtf8 <$> ((<>) <$> imagePrefix <*> (B64.encode <$> base64P)) - chatTypeP = A.char '@' $> CTDirect <|> A.char '#' $> CTGroup + chatTypeP = A.char '@' $> CTDirect <|> A.char '#' $> CTGroup <|> A.char ':' $> CTContactConnection chatPaginationP = (CPLast <$ "count=" <*> A.decimal) <|> (CPAfter <$ "after=" <*> A.decimal <* A.space <* "count=" <*> A.decimal) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 1cfa91b288..9d08c72bd3 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -96,7 +96,7 @@ data ChatCommand | CreateActiveUser Profile | StartChat | SetFilesFolder FilePath - | APIGetChats + | APIGetChats {pendingConnections :: Bool} | APIGetChat ChatType Int64 ChatPagination | APIGetChatItems Int | APISendMessage ChatType Int64 (Maybe FilePath) (Maybe ChatItemId) MsgContent @@ -246,6 +246,8 @@ data ChatResponse | CRUserContactLinkSubscribed | CRUserContactLinkSubError {chatError :: ChatError} | CRNtfTokenStatus {status :: NtfTknStatus} + | CRNewContactConnection {connection :: PendingContactConnection} + | CRContactConnectionDeleted {connection :: PendingContactConnection} | CRMessageError {severity :: Text, errorMessage :: Text} | CRChatCmdError {chatError :: ChatError} | CRChatError {chatError :: ChatError} diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index a735566aa0..33528b7cac 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -38,7 +38,7 @@ import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, singleFi import Simplex.Messaging.Protocol (MsgBody) import Simplex.Messaging.Util ((<$?>)) -data ChatType = CTDirect | CTGroup | CTContactRequest +data ChatType = CTDirect | CTGroup | CTContactRequest | CTContactConnection deriving (Show, Generic) instance ToJSON ChatType where @@ -49,6 +49,7 @@ data ChatInfo (c :: ChatType) where DirectChat :: Contact -> ChatInfo 'CTDirect GroupChat :: GroupInfo -> ChatInfo 'CTGroup ContactRequest :: UserContactRequest -> ChatInfo 'CTContactRequest + ContactConnection :: PendingContactConnection -> ChatInfo 'CTContactConnection deriving instance Show (ChatInfo c) @@ -56,6 +57,7 @@ data JSONChatInfo = JCInfoDirect {contact :: Contact} | JCInfoGroup {groupInfo :: GroupInfo} | JCInfoContactRequest {contactRequest :: UserContactRequest} + | JCInfoContactConnection {contactConnection :: PendingContactConnection} deriving (Generic) instance ToJSON JSONChatInfo where @@ -71,6 +73,7 @@ jsonChatInfo = \case DirectChat c -> JCInfoDirect c GroupChat g -> JCInfoGroup g ContactRequest g -> JCInfoContactRequest g + ContactConnection c -> JCInfoContactConnection c data ChatItem (c :: ChatType) (d :: MsgDirection) = ChatItem { chatDir :: CIDirection c d, @@ -519,12 +522,15 @@ data SChatType (c :: ChatType) where SCTDirect :: SChatType 'CTDirect SCTGroup :: SChatType 'CTGroup SCTContactRequest :: SChatType 'CTContactRequest + SCTContactConnection :: SChatType 'CTContactConnection deriving instance Show (SChatType c) instance TestEquality SChatType where testEquality SCTDirect SCTDirect = Just Refl testEquality SCTGroup SCTGroup = Just Refl + testEquality SCTContactRequest SCTContactRequest = Just Refl + testEquality SCTContactConnection SCTContactConnection = Just Refl testEquality _ _ = Nothing class ChatTypeI (c :: ChatType) where diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs index 569368ed98..6d6e767308 100644 --- a/src/Simplex/Chat/Store.hs +++ b/src/Simplex/Chat/Store.hs @@ -151,6 +151,7 @@ module Simplex.Chat.Store updateGroupChatItemsRead, getSMPServers, overwriteSMPServers, + deletePendingContactConnection, ) where @@ -171,7 +172,7 @@ import Data.Function (on) import Data.Functor (($>)) import Data.Int (Int64) import Data.List (find, sortBy, sortOn) -import Data.Maybe (fromMaybe, listToMaybe) +import Data.Maybe (fromMaybe, isJust, listToMaybe) import Data.Ord (Down (..)) import Data.Text (Text) import qualified Data.Text as T @@ -296,10 +297,11 @@ setActiveUser st userId = do DB.execute_ db "UPDATE users SET active_user = 0" DB.execute db "UPDATE users SET active_user = 1 WHERE user_id = ?" (Only userId) -createConnReqConnection :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnId -> ConnReqUriHash -> XContactId -> m () +createConnReqConnection :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnId -> ConnReqUriHash -> XContactId -> m PendingContactConnection createConnReqConnection st userId acId cReqHash xContactId = do liftIO . withTransaction st $ \db -> do - currentTs <- getCurrentTime + createdAt <- getCurrentTime + let pccConnStatus = ConnJoined DB.execute db [sql| @@ -308,7 +310,9 @@ createConnReqConnection st userId acId cReqHash xContactId = do created_at, updated_at, via_contact_uri_hash, xcontact_id ) VALUES (?,?,?,?,?,?,?,?) |] - (userId, acId, ConnNew, ConnContact, currentTs, currentTs, cReqHash, xContactId) + (userId, acId, pccConnStatus, ConnContact, createdAt, createdAt, cReqHash, xContactId) + pccConnId <- insertedRowId db + pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, createdAt} getConnReqContactXContactId :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnReqUriHash -> m (Maybe Contact, Maybe XContactId) getConnReqContactXContactId st userId cReqHash = do @@ -345,11 +349,19 @@ getConnReqContactXContactId st userId cReqHash = do "SELECT xcontact_id FROM connections WHERE user_id = ? AND via_contact_uri_hash = ? LIMIT 1" (userId, cReqHash) -createDirectConnection :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnId -> m () -createDirectConnection st userId agentConnId = +createDirectConnection :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnId -> ConnStatus -> m PendingContactConnection +createDirectConnection st userId acId pccConnStatus = liftIO . withTransaction st $ \db -> do - currentTs <- getCurrentTime - void $ createContactConnection_ db userId agentConnId Nothing 0 currentTs + createdAt <- getCurrentTime + DB.execute + db + [sql| + INSERT INTO connections + (user_id, agent_conn_id, conn_status, conn_type, created_at, updated_at) VALUES (?,?,?,?,?,?) + |] + (userId, acId, pccConnStatus, ConnContact, createdAt, createdAt) + pccConnId <- insertedRowId db + pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, createdAt, viaContactUri = False} createContactConnection_ :: DB.Connection -> UserId -> ConnId -> Maybe Int64 -> Int -> UTCTime -> IO Connection createContactConnection_ db userId = createConnection_ db userId ConnContact Nothing @@ -2500,13 +2512,14 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing ciQuoteGroup ((Only itemId :. memberRow) : _) = ciQuote itemId . CIQGroupRcv . Just $ toGroupMember userContactId memberRow -getChatPreviews :: MonadUnliftIO m => SQLiteStore -> User -> m [AChat] -getChatPreviews st user = +getChatPreviews :: MonadUnliftIO m => SQLiteStore -> User -> Bool -> m [AChat] +getChatPreviews st user withPCC = liftIO . withTransaction st $ \db -> do directChats <- getDirectChatPreviews_ db user groupChats <- getGroupChatPreviews_ db user cReqChats <- getContactRequestChatPreviews_ db user - pure $ sortOn (Down . ts) (directChats <> groupChats <> cReqChats) + connChats <- getContactConnectionChatPreviews_ db user withPCC + pure $ sortOn (Down . ts) (directChats <> groupChats <> cReqChats <> connChats) where ts :: AChat -> UTCTime ts (AChat _ Chat {chatItems = ci : _}) = chatItemTs ci @@ -2514,6 +2527,7 @@ getChatPreviews st user = DirectChat Contact {createdAt} -> createdAt GroupChat GroupInfo {createdAt} -> createdAt ContactRequest UserContactRequest {createdAt} -> createdAt + ContactConnection PendingContactConnection {createdAt} -> createdAt chatItemTs :: CChatItem d -> UTCTime chatItemTs (CChatItem _ ChatItem {meta = CIMeta {itemTs}}) = itemTs @@ -2671,6 +2685,45 @@ getContactRequestChatPreviews_ db User {userId} = stats = ChatStats {unreadCount = 0, minUnreadItemId = 0} in AChat SCTContactRequest $ Chat (ContactRequest cReq) [] stats +getContactConnectionChatPreviews_ :: DB.Connection -> User -> Bool -> IO [AChat] +getContactConnectionChatPreviews_ _ _ False = pure [] +getContactConnectionChatPreviews_ db User {userId} _ = + map toContactConnectionChatPreview + <$> DB.query + db + [sql| + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, created_at + FROM connections + WHERE user_id = ? AND conn_type = ? AND contact_id IS NULL AND conn_level = 0 AND via_contact IS NULL + |] + (userId, ConnContact) + where + toContactConnectionChatPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, UTCTime) -> AChat + toContactConnectionChatPreview connRow = + let conn = toPendingContactConnection connRow + stats = ChatStats {unreadCount = 0, minUnreadItemId = 0} + in AChat SCTContactConnection $ Chat (ContactConnection conn) [] stats + +deletePendingContactConnection :: StoreMonad m => SQLiteStore -> UserId -> Int64 -> m PendingContactConnection +deletePendingContactConnection st userId connId = + liftIOEither . withTransaction st $ \db -> runExceptT $ do + conn <- + ExceptT . firstRow toPendingContactConnection (SEPendingConnectionNotFound connId) $ + DB.query + db + [sql| + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, created_at + FROM connections + WHERE user_id = ? AND conn_type = ? AND contact_id IS NULL AND conn_level = 0 AND via_contact IS NULL + |] + (userId, ConnContact) + liftIO $ DB.execute db "DELETE FROM connections WHERE connection_id = ?" (Only connId) + pure conn + +toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, UTCTime) -> PendingContactConnection +toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, createdAt) = + PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, createdAt} + getDirectChat :: StoreMonad m => SQLiteStore -> User -> Int64 -> ChatPagination -> m (Chat 'CTDirect) getDirectChat st user contactId pagination = liftIOEither . withTransaction st $ \db -> runExceptT $ do @@ -3646,6 +3699,7 @@ data StoreError | SESharedMsgIdNotFoundByFileId {fileId :: FileTransferId} | SEFileIdNotFoundBySharedMsgId {sharedMsgId :: SharedMsgId} | SEConnectionNotFound {agentConnId :: AgentConnId} + | SEPendingConnectionNotFound {connId :: Int64} | SEIntroNotFound | SEUniqueID | SEInternalError {message :: String} diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index edcd3940e3..65f2fa4565 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -72,7 +72,7 @@ data Contact = Contact viaGroup :: Maybe Int64, createdAt :: UTCTime } - deriving (Eq, Show, Generic, FromJSON) + deriving (Eq, Show, Generic) instance ToJSON Contact where toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} @@ -101,7 +101,7 @@ data UserContactRequest = UserContactRequest createdAt :: UTCTime, xContactId :: Maybe XContactId } - deriving (Eq, Show, Generic, FromJSON) + deriving (Eq, Show, Generic) instance ToJSON UserContactRequest where toEncoding = J.genericToEncoding J.defaultOptions @@ -160,7 +160,7 @@ data GroupInfo = GroupInfo membership :: GroupMember, createdAt :: UTCTime } - deriving (Eq, Show, Generic, FromJSON) + deriving (Eq, Show, Generic) instance ToJSON GroupInfo where toEncoding = J.genericToEncoding J.defaultOptions @@ -262,7 +262,7 @@ data GroupMember = GroupMember memberContactId :: Maybe Int64, activeConn :: Maybe Connection } - deriving (Eq, Show, Generic, FromJSON) + deriving (Eq, Show, Generic) instance ToJSON GroupMember where toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} @@ -306,9 +306,6 @@ instance ToJSON MemberId where data InvitedBy = IBContact {byContactId :: Int64} | IBUser | IBUnknown deriving (Eq, Show, Generic) -instance FromJSON InvitedBy where - parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "IB" - instance ToJSON InvitedBy where toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "IB" toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "IB" @@ -401,8 +398,6 @@ instance FromField GroupMemberCategory where fromField = fromTextField_ textDeco instance ToField GroupMemberCategory where toField = toField . textEncode -instance FromJSON GroupMemberCategory where parseJSON = textParseJSON "GroupMemberCategory" - instance ToJSON GroupMemberCategory where toJSON = J.String . textEncode toEncoding = JE.text . textEncode @@ -440,8 +435,6 @@ instance FromField GroupMemberStatus where fromField = fromTextField_ textDecode instance ToField GroupMemberStatus where toField = toField . textEncode -instance FromJSON GroupMemberStatus where parseJSON = textParseJSON "GroupMemberStatus" - instance ToJSON GroupMemberStatus where toJSON = J.String . textEncode toEncoding = JE.text . textEncode @@ -536,7 +529,7 @@ data RcvFileTransfer = RcvFileTransfer cancelled :: Bool, grpMemberId :: Maybe Int64 } - deriving (Eq, Show, Generic, FromJSON) + deriving (Eq, Show, Generic) instance ToJSON RcvFileTransfer where toEncoding = J.genericToEncoding J.defaultOptions @@ -548,9 +541,6 @@ data RcvFileStatus | RFSCancelled RcvFileInfo deriving (Eq, Show, Generic) -instance FromJSON RcvFileStatus where - parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RFS" - instance ToJSON RcvFileStatus where toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "RFS" toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RFS" @@ -560,7 +550,7 @@ data RcvFileInfo = RcvFileInfo connId :: Int64, agentConnId :: AgentConnId } - deriving (Eq, Show, Generic, FromJSON) + deriving (Eq, Show, Generic) instance ToJSON RcvFileInfo where toEncoding = J.genericToEncoding J.defaultOptions @@ -572,9 +562,6 @@ instance StrEncoding AgentConnId where strDecode s = AgentConnId <$> strDecode s strP = AgentConnId <$> strP -instance FromJSON AgentConnId where - parseJSON = strParseJSON "AgentConnId" - instance ToJSON AgentConnId where toJSON = strToJSON toEncoding = strToJEncoding @@ -591,9 +578,6 @@ instance StrEncoding AgentInvId where strDecode s = AgentInvId <$> strDecode s strP = AgentInvId <$> strP -instance FromJSON AgentInvId where - parseJSON = strParseJSON "AgentInvId" - instance ToJSON AgentInvId where toJSON = strToJSON toEncoding = strToJEncoding @@ -636,8 +620,6 @@ instance FromField FileStatus where fromField = fromTextField_ textDecode instance ToField FileStatus where toField = toField . textEncode -instance FromJSON FileStatus where parseJSON = textParseJSON "FileStatus" - instance ToJSON FileStatus where toJSON = J.String . textEncode toEncoding = JE.text . textEncode @@ -674,7 +656,7 @@ data Connection = Connection entityId :: Maybe Int64, -- contact, group member, file ID or user contact ID createdAt :: UTCTime } - deriving (Eq, Show, Generic, FromJSON) + deriving (Eq, Show, Generic) aConnId :: Connection -> ConnId aConnId Connection {agentConnId = AgentConnId cId} = cId @@ -683,6 +665,17 @@ instance ToJSON Connection where toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} +data PendingContactConnection = PendingContactConnection + { pccConnId :: Int64, + pccAgentConnId :: AgentConnId, + pccConnStatus :: ConnStatus, + viaContactUri :: Bool, + createdAt :: UTCTime + } + deriving (Eq, Show, Generic) + +instance ToJSON PendingContactConnection where toEncoding = J.genericToEncoding J.defaultOptions + data ConnStatus = -- | connection is created by initiating party with agent NEW command (createConnection) ConnNew @@ -704,8 +697,6 @@ instance FromField ConnStatus where fromField = fromTextField_ textDecode instance ToField ConnStatus where toField = toField . textEncode -instance FromJSON ConnStatus where parseJSON = textParseJSON "ConnStatus" - instance ToJSON ConnStatus where toJSON = J.String . textEncode toEncoding = JE.text . textEncode @@ -736,8 +727,6 @@ instance FromField ConnType where fromField = fromTextField_ textDecode instance ToField ConnType where toField = toField . textEncode -instance FromJSON ConnType where parseJSON = textParseJSON "ConnType" - instance ToJSON ConnType where toJSON = J.String . textEncode toEncoding = JE.text . textEncode diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index e625574bc0..2c5a6463a8 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -141,6 +141,8 @@ responseToView testView = \case ["received file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] CRUserContactLinkSubscribed -> ["Your address is active! To show: " <> highlight' "/sa"] CRUserContactLinkSubError e -> ["user address error: " <> sShow e, "to delete your address: " <> highlight' "/da"] + CRNewContactConnection _ -> [] + CRContactConnectionDeleted _ -> [] CRNtfTokenStatus status -> ["device token status: " <> plain (smpEncode status)] CRMessageError prefix err -> [plain prefix <> ": " <> plain err] CRChatError e -> viewChatError e @@ -152,6 +154,7 @@ responseToView testView = \case toChatView (AChat _ (Chat (DirectChat Contact {localDisplayName}) items _)) = ("@" <> localDisplayName, toCIPreview items) toChatView (AChat _ (Chat (GroupChat GroupInfo {localDisplayName}) items _)) = ("#" <> localDisplayName, toCIPreview items) toChatView (AChat _ (Chat (ContactRequest UserContactRequest {localDisplayName}) items _)) = ("<@" <> localDisplayName, toCIPreview items) + toChatView (AChat _ (Chat (ContactConnection PendingContactConnection {pccConnId}) items _)) = (":" <> T.pack (show pccConnId), toCIPreview items) toCIPreview :: [CChatItem c] -> Text toCIPreview ((CChatItem _ ChatItem {meta}) : _) = itemText meta toCIPreview _ = ""