core: chat item content types for chat initiation (#5998)

* core: chat item content types for chat initiation

* connection mode for ui

* padding

* simplexmq

* initial items

* update content items

* core: feature and e2e items

* refactor

* chat items

* ios types

* fix condition for PQ encryption of link
This commit is contained in:
Evgeny
2025-06-20 11:54:21 +01:00
committed by GitHub
parent 05dba0bda2
commit fc04872c91
19 changed files with 230 additions and 103 deletions
+1 -1
View File
@@ -451,7 +451,7 @@ data ChatCommand
| APIChangeConnectionUser Int64 UserId -- new user id to switch connection to
| APIConnectPlan UserId AConnectionLink
| APIPrepareContact UserId ACreatedConnLink ContactShortLinkData
| APIPrepareGroup UserId ACreatedConnLink GroupShortLinkData
| APIPrepareGroup UserId CreatedLinkContact GroupShortLinkData
| APIChangePreparedContactUser ContactId UserId
| APIChangePreparedGroupUser GroupId UserId
| APIConnectPreparedContact {contactId :: ContactId, incognito :: IncognitoEnabled, msgContent_ :: Maybe MsgContent}
+37 -18
View File
@@ -99,7 +99,7 @@ import Simplex.Messaging.Compression (compressionLevel)
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
import qualified Simplex.Messaging.Crypto.File as CF
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..), pattern IKPQOff, pattern IKPQOn, pattern PQEncOff, pattern PQSupportOff, pattern PQSupportOn)
import Simplex.Messaging.Crypto.Ratchet (E2ERatchetParamsUri (..), PQEncryption (..), PQSupport (..), pattern IKPQOff, pattern IKPQOn, pattern PQEncOff, pattern PQSupportOff, pattern PQSupportOn, pqRatchetE2EEncryptVersion)
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (base64P)
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, userProtocol)
@@ -849,6 +849,7 @@ processChatCommand' vr = \case
MCVoice {text} -> text /= ""
MCFile t -> t /= ""
MCReport {} -> True
MCChat {} -> True
MCUnknown {} -> True
-- TODO [knocking] forward from / to scope
APIForwardChatItems toChat@(ChatRef toCType toChatId toScope) fromChat@(ChatRef fromCType fromChatId _fromScope) itemIds itemTTL -> withUser $ \user -> case toCType of
@@ -1734,19 +1735,32 @@ processChatCommand' vr = \case
pure conn'
APIConnectPlan userId cLink -> withUserId userId $ \user ->
uncurry (CRConnectionPlan user) <$> connectPlan user cLink
APIPrepareContact userId accLink contactSLinkData -> withUserId userId $ \user -> do
let ContactShortLinkData {profile, message} = contactSLinkData
APIPrepareContact userId accLink@(ACCL cMode (CCLink _ shortLink)) contactSLinkData -> withUserId userId $ \user -> do
let ContactShortLinkData {profile, message, business} = contactSLinkData
ct <- withStore $ \db -> createPreparedContact db user profile accLink
forM_ message $ \msg ->
createInternalChatItem user (CDDirectRcv ct) (CIRcvMsgContent $ MCText msg) Nothing
let cMode' = connMode cMode
createItem content = void $ createInternalItemForChat user (CDDirectRcv ct) content Nothing
msgChatLink = \case
sl@CSLContact {} -> MCLContact sl profile business
sl@CSLInvitation {} -> MCLInvitation sl profile
mapM_ (\sl -> createItem $ CIRcvMsgContent $ MCChat (safeDecodeUtf8 $ strEncode sl) $ msgChatLink sl) shortLink
createItem $ CIRcvDirectE2EEInfo $ E2EInfo $ connLinkPQEncryption accLink
void $ createFeatureEnabledItems_ user ct
mapM_ (createItem . CIRcvMsgContent . MCText) message
pure $ CRNewPreparedContact user ct
APIPrepareGroup userId accLink groupSLinkData -> withUserId userId $ \user -> do
let GroupShortLinkData {groupProfile} = groupSLinkData
gInfo <- withStore $ \db -> createPreparedGroup db vr user groupProfile accLink
APIPrepareGroup userId ccLink@(CCLink _ shortLink) groupSLinkData -> withUserId userId $ \user -> do
let GroupShortLinkData {groupProfile = gp@GroupProfile {description}} = groupSLinkData
gInfo <- withStore $ \db -> createPreparedGroup db vr user gp ccLink
-- TODO use received item without member
let cd = CDGroupRcv gInfo Nothing $ membership gInfo
createItem content = void $ createInternalItemForChat user cd content Nothing
mapM_ (\sl -> createItem $ CIRcvMsgContent $ MCChat (safeDecodeUtf8 $ strEncode sl) $ MCLGroup sl gp) shortLink
void $ createGroupFeatureItems_ user cd CIRcvGroupFeature gInfo
mapM_ (createItem . CIRcvMsgContent . MCText) description
pure $ CRNewPreparedGroup user gInfo
APIChangePreparedContactUser contactId newUserId -> withUser $ \user -> do
ct@Contact {connLinkToConnect} <- withFastStore $ \db -> getContact db vr user contactId
when (isNothing connLinkToConnect) $ throwCmdError "contact doesn't have link to connect"
ct@Contact {preparedContact} <- withFastStore $ \db -> getContact db vr user contactId
when (isNothing preparedContact) $ throwCmdError "contact doesn't have link to connect"
when (isJust $ contactConn ct) $ throwCmdError "contact already has connection"
newUser <- privateGetUser newUserId
ct' <- withFastStore $ \db -> updatePreparedContactUser db vr user ct newUser
@@ -1760,10 +1774,10 @@ processChatCommand' vr = \case
gInfo' <- withFastStore $ \db -> updatePreparedGroupUser db vr user gInfo hostMember newUser
pure $ CRGroupUserChanged user gInfo newUser gInfo'
APIConnectPreparedContact contactId incognito msgContent_ -> withUser $ \user -> do
Contact {connLinkToConnect} <- withFastStore $ \db -> getContact db vr user contactId
case connLinkToConnect of
Contact {preparedContact} <- withFastStore $ \db -> getContact db vr user contactId
case preparedContact of
Nothing -> throwCmdError "contact doesn't have link to connect"
Just (ACCL SCMInvitation ccLink) ->
Just PreparedContact {connLinkToConnect = ACCL SCMInvitation ccLink} ->
connectViaInvitation user incognito ccLink (Just contactId) >>= \case
CRSentConfirmation {customUserProfile} -> do
-- get updated contact with connection
@@ -1775,7 +1789,7 @@ processChatCommand' vr = \case
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci]
pure $ CRStartedConnectionToContact user ct' customUserProfile
cr -> pure cr
Just (ACCL SCMContact ccLink) ->
Just PreparedContact {connLinkToConnect = ACCL SCMContact ccLink} ->
connectViaContact user incognito ccLink msgContent_ (Just $ ACCGContact contactId) >>= \case
CRSentInvitation {customUserProfile} -> do
-- get updated contact with connection
@@ -2031,7 +2045,7 @@ processChatCommand' vr = \case
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
gInfo <- withFastStore $ \db -> createNewGroup db vr gVar user gProfile incognitoProfile
let cd = CDGroupSnd gInfo Nothing
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
createGroupFeatureItems user cd CISndGroupFeature gInfo
pure $ CRGroupCreated user gInfo
NewGroup incognito gProfile -> withUser $ \User {userId} ->
@@ -3438,9 +3452,10 @@ processChatCommand' vr = \case
contactShortLinkData :: Profile -> Maybe Text -> CM UserLinkData
contactShortLinkData p msg = do
large <- chatReadVar useLargeLinkData
-- TODO [short links] business
let contactData
| large = ContactShortLinkData p msg
| otherwise = ContactShortLinkData p {fullName = "", image = Nothing, contactLink = Nothing} Nothing
| large = ContactShortLinkData p msg False
| otherwise = ContactShortLinkData p {fullName = "", image = Nothing, contactLink = Nothing} Nothing False
pure $ encodeShortLinkData large contactData
groupShortLinkData :: GroupProfile -> CM UserLinkData
groupShortLinkData gp = do
@@ -4441,7 +4456,7 @@ chatCommandP =
"/contacts" $> ListContacts,
"/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> strP),
"/_prepare contact " *> (APIPrepareContact <$> A.decimal <* A.space <*> connLinkP <* A.space <*> jsonP),
"/_prepare group " *> (APIPrepareGroup <$> A.decimal <* A.space <*> connLinkP <* A.space <*> jsonP),
"/_prepare group " *> (APIPrepareGroup <$> A.decimal <* A.space <*> connLinkP' <* A.space <*> jsonP),
"/_set contact user @" *> (APIChangePreparedContactUser <$> A.decimal <* A.space <*> A.decimal),
"/_set group user #" *> (APIChangePreparedGroupUser <$> A.decimal <* A.space <*> A.decimal),
"/_connect contact @" *> (APIConnectPreparedContact <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)),
@@ -4561,6 +4576,10 @@ chatCommandP =
(ACR m cReq) <- strP
sLink_ <- optional (A.space *> strP)
pure $ ACCL m (CCLink cReq sLink_)
connLinkP' = do
cReq <- strP
sLink_ <- optional (A.space *> strP)
pure $ CCLink cReq sLink_
connLinkP_ =
((Just <$> connLinkP) <|> A.takeTill (== ' ') $> Nothing)
incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False
+32 -8
View File
@@ -316,6 +316,7 @@ quoteContent mc qmc ciFile_
MCVideo {} -> True
MCVoice {} -> False
MCReport {} -> False
MCChat {} -> True
MCUnknown {} -> True
qText = msgContentText qmc
getFileName :: CIFile d -> String
@@ -1019,7 +1020,7 @@ acceptBusinessJoinRequestAsync
connIds <- agentAcceptContactAsync user True cReqInvId msg subMode PQSupportOff chatV
withStore' $ \db -> createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
let cd = CDGroupSnd gInfo Nothing
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
createGroupFeatureItems user cd CISndGroupFeature gInfo
pure (gInfo, clientMember)
where
@@ -1265,7 +1266,7 @@ createContactPQSndItem :: User -> Contact -> Connection -> PQEncryption -> CM (C
createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' =
flip catchChatError (const $ pure (ct, conn)) $ case (pqSndEnabled, pqSndEnabled') of
(Just b, b') | b' /= b -> createPQItem $ CISndConnEvent (SCEPqEnabled pqSndEnabled')
(Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (E2EInfo pqSndEnabled')
(Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (E2EInfo $ Just pqSndEnabled')
_ -> pure (ct, conn)
where
createPQItem ciContent = do
@@ -1280,7 +1281,7 @@ updateContactPQRcv :: User -> Contact -> Connection -> PQEncryption -> CM (Conta
updateContactPQRcv user ct conn@Connection {connId, pqRcvEnabled} pqRcvEnabled' =
flip catchChatError (const $ pure (ct, conn)) $ case (pqRcvEnabled, pqRcvEnabled') of
(Just b, b') | b' /= b -> updatePQ $ CIRcvConnEvent (RCEPqEnabled pqRcvEnabled')
(Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (E2EInfo pqRcvEnabled')
(Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (E2EInfo $ Just pqRcvEnabled')
_ -> pure (ct, conn)
where
updatePQ ciContent = do
@@ -2261,6 +2262,12 @@ userProfileToSend user@User {profile = p} incognitoProfile ct inGroup = do
let userPrefs = maybe (preferences' user) (const Nothing) incognitoProfile
in (p' :: Profile) {preferences = Just . toChatPrefs $ mergePreferences (userPreferences <$> ct) userPrefs}
connLinkPQEncryption :: ACreatedConnLink -> Maybe PQEncryption
connLinkPQEncryption (ACCL _ (CCLink cReq _)) = case cReq of
CRContactUri _ -> Nothing
CRInvitationUri _ (CR.E2ERatchetParamsUri vr' _ _ pq) ->
Just $ PQEncryption $ maxVersion vr' >= CR.pqRatchetE2EEncryptVersion && isJust pq
createRcvFeatureItems :: User -> Contact -> Contact -> CM' ()
createRcvFeatureItems user ct ct' =
createFeatureItems user ct ct' CDDirectRcv CIRcvChatFeature CIRcvChatPreference contactPreference
@@ -2275,6 +2282,15 @@ createSndFeatureItems user ct ct' =
type FeatureContent a d = ChatFeature -> a -> Maybe Int -> CIContent d
createFeatureEnabledItems :: User -> Contact -> CM ()
createFeatureEnabledItems user ct = createFeatureEnabledItems_ user ct >>= toView . CEvtNewChatItems user
createFeatureEnabledItems_ :: User -> Contact -> CM [AChatItem]
createFeatureEnabledItems_ user ct@Contact {mergedPreferences} =
forM allChatFeatures $ \(ACF f) -> do
let state = featureState $ getContactUserPreference f mergedPreferences
createInternalItemForChat user (CDDirectRcv ct) (uncurry (CIRcvChatFeature $ chatFeature f) state) Nothing
createFeatureItems ::
MsgDirectionI d =>
User ->
@@ -2337,16 +2353,24 @@ sameGroupProfileInfo :: GroupProfile -> GroupProfile -> Bool
sameGroupProfileInfo p p' = p {groupPreferences = Nothing} == p' {groupPreferences = Nothing}
createGroupFeatureItems :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM ()
createGroupFeatureItems user cd ciContent GroupInfo {fullGroupPreferences} =
forM_ allGroupFeatures $ \(AGF f) -> do
createGroupFeatureItems user cd ciContent g = createGroupFeatureItems_ user cd ciContent g >>= toView . CEvtNewChatItems user
createGroupFeatureItems_ :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM [AChatItem]
createGroupFeatureItems_ user cd ciContent GroupInfo {fullGroupPreferences} =
forM allGroupFeatures $ \(AGF f) -> do
let p = getGroupPreference f fullGroupPreferences
(_, param, role) = groupFeatureState p
createInternalChatItem user cd (ciContent (toGroupFeature f) (toGroupPreference p) param role) Nothing
createInternalItemForChat user cd (ciContent (toGroupFeature f) (toGroupPreference p) param role) Nothing
createInternalChatItem :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM ()
createInternalChatItem user cd content itemTs_ =
createInternalChatItem user cd content itemTs_ = do
ci <- createInternalItemForChat user cd content itemTs_
toView $ CEvtNewChatItems user [ci]
createInternalItemForChat :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM AChatItem
createInternalItemForChat user cd content itemTs_ =
lift (createInternalItemsForChats user itemTs_ [(cd, [content])]) >>= \case
[Right aci] -> toView $ CEvtNewChatItems user [aci]
[Right ci] -> pure ci
[Left e] -> throwError e
rs -> throwChatError $ CEInternalError $ "createInternalChatItem: expected 1 result, got " <> show (length rs)
+12 -13
View File
@@ -556,8 +556,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- TODO check member ID
-- TODO update member profile
pure ()
XInfo profile ->
void $ processContactProfileUpdate ct profile False
XInfo profile -> do
let prepared = isJust $ preparedContact ct
void $ processContactProfileUpdate ct profile prepared
XOk -> pure ()
_ -> messageError "INFO for existing contact must have x.grp.mem.info, x.info or x.ok"
CON pqEnc ->
@@ -570,9 +571,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
lift $ setContactNetworkStatus ct' NSConnected
toView $ CEvtContactConnected user ct' (fmap fromLocalProfile incognitoProfile)
when (directOrUsed ct') $ do
createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo pqEnc) Nothing
createFeatureEnabledItems ct'
let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo $ Just pqEnc) Nothing
when (directOrUsed ct') $ case preparedContact ct' of
Nothing -> do
createE2EItem
createFeatureEnabledItems user ct'
Just PreparedContact {connLinkToConnect = cl} ->
unless (Just pqEnc == connLinkPQEncryption cl) createE2EItem
when (contactConnInitiated conn') $ do
let Connection {groupLinkId} = conn'
doProbeContacts = isJust groupLinkId
@@ -799,7 +804,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtUserJoinedGroup user gInfo' m'
(gInfo'', m'', scopeInfo) <- mkGroupChatScope gInfo' m'
let cd = CDGroupRcv gInfo'' scopeInfo m''
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
createGroupFeatureItems user cd CIRcvGroupFeature gInfo''
memberConnectedChatItem gInfo'' scopeInfo m''
unless (memberPending membership) $ maybeCreateGroupDescrLocal gInfo'' m''
@@ -2181,7 +2186,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- create item in both scopes
let gInfo' = gInfo {membership = membership'}
cd = CDGroupRcv gInfo' Nothing m
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
createGroupFeatureItems user cd CIRcvGroupFeature gInfo'
maybeCreateGroupDescrLocal gInfo' m
createInternalChatItem user cd (CIRcvGroupEvent RGEUserAccepted) Nothing
@@ -2250,12 +2255,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p'
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m'') ciContent itemTs_
createFeatureEnabledItems :: Contact -> CM ()
createFeatureEnabledItems ct@Contact {mergedPreferences} =
forM_ allChatFeatures $ \(ACF f) -> do
let state = featureState $ getContactUserPreference f mergedPreferences
createInternalChatItem user (CDDirectRcv ct) (uncurry (CIRcvChatFeature $ chatFeature f) state) Nothing
xInfoProbe :: ContactOrMember -> Probe -> CM ()
xInfoProbe cgm2 probe = do
contactMerge <- readTVarIO =<< asks contactMergeEnabled
+10 -4
View File
@@ -173,7 +173,7 @@ data CIContent (d :: MsgDirection) where
deriving instance Show (CIContent d)
data E2EInfo = E2EInfo {pqEnabled :: PQEncryption}
data E2EInfo = E2EInfo {pqEnabled :: Maybe PQEncryption}
deriving (Eq, Show)
ciMsgContent :: CIContent d -> Maybe MsgContent
@@ -296,11 +296,17 @@ ciContentToText = \case
directE2EInfoToText :: E2EInfo -> Text
directE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of
PQEncOn -> e2eInfoPQText
PQEncOff -> e2eInfoNoPQText
Just PQEncOn -> e2eInfoPQText
Just PQEncOff -> e2eInfoNoPQText
Nothing -> simpleE2EText
groupE2EInfoToText :: E2EInfo -> Text
groupE2EInfoToText _e2eeInfo = e2eInfoNoPQText
groupE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of
Just _ -> e2eInfoNoPQText
Nothing -> simpleE2EText
simpleE2EText :: Text
simpleE2EText = "This conversation is protected by end-to-end encryption"
e2eInfoNoPQText :: Text
e2eInfoNoPQText =
+29 -1
View File
@@ -533,7 +533,16 @@ cmToQuotedMsg = \case
ACME _ (XMsgNew (MCQuote quotedMsg _)) -> Just quotedMsg
_ -> Nothing
data MsgContentTag = MCText_ | MCLink_ | MCImage_ | MCVideo_ | MCVoice_ | MCFile_ | MCReport_ | MCUnknown_ Text
data MsgContentTag
= MCText_
| MCLink_
| MCImage_
| MCVideo_
| MCVoice_
| MCFile_
| MCReport_
| MCChat_
| MCUnknown_ Text
deriving (Eq, Show)
instance StrEncoding MsgContentTag where
@@ -545,6 +554,7 @@ instance StrEncoding MsgContentTag where
MCFile_ -> "file"
MCVoice_ -> "voice"
MCReport_ -> "report"
MCChat_ -> "chat"
MCUnknown_ t -> encodeUtf8 t
strDecode = \case
"text" -> Right MCText_
@@ -554,6 +564,7 @@ instance StrEncoding MsgContentTag where
"voice" -> Right MCVoice_
"file" -> Right MCFile_
"report" -> Right MCReport_
"chat" -> Right MCChat_
t -> Right . MCUnknown_ $ safeDecodeUtf8 t
strP = strDecode <$?> A.takeTill (== ' ')
@@ -593,9 +604,16 @@ data MsgContent
| MCVoice {text :: Text, duration :: Int}
| MCFile {text :: Text}
| MCReport {text :: Text, reason :: ReportReason}
| MCChat {text :: Text, chatLink :: MsgChatLink}
| MCUnknown {tag :: Text, text :: Text, json :: J.Object}
deriving (Eq, Show)
data MsgChatLink
= MCLContact {connLink :: ShortLinkContact, profile :: Profile, business :: Bool}
| MCLInvitation {invLink :: ShortLinkInvitation, profile :: Profile}
| MCLGroup {connLink :: ShortLinkContact, groupProfile :: GroupProfile}
deriving (Eq, Show)
msgContentText :: MsgContent -> Text
msgContentText = \case
MCText t -> t
@@ -611,6 +629,7 @@ msgContentText = \case
if T.null text then msg else msg <> ": " <> text
where
msg = "report " <> safeDecodeUtf8 (strEncode reason)
MCChat {text} -> text
MCUnknown {text} -> text
durationText :: Int -> Text
@@ -646,6 +665,7 @@ msgContentTag = \case
MCVoice {} -> MCVoice_
MCFile {} -> MCFile_
MCReport {} -> MCReport_
MCChat {} -> MCChat_
MCUnknown {tag} -> MCUnknown_ tag
data ExtMsgContent = ExtMsgContent
@@ -664,6 +684,8 @@ data ExtMsgContent = ExtMsgContent
data MsgMention = MsgMention {memberId :: MemberId}
deriving (Eq, Show)
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "MCL") ''MsgChatLink)
$(JQ.deriveJSON defaultJSON ''MsgMention)
$(JQ.deriveJSON defaultJSON ''QuotedMsg)
@@ -773,6 +795,10 @@ instance FromJSON MsgContent where
text <- v .: "text"
reason <- v .: "reason"
pure MCReport {text, reason}
MCChat_ -> do
text <- v .: "text"
chatLink <- v .: "chatLink"
pure MCChat {text, chatLink}
MCUnknown_ tag -> do
text <- fromMaybe unknownMsgType <$> v .:? "text"
pure MCUnknown {tag, text, json = v}
@@ -807,6 +833,7 @@ instance ToJSON MsgContent where
MCVoice {text, duration} -> J.object ["type" .= MCVoice_, "text" .= text, "duration" .= duration]
MCFile t -> J.object ["type" .= MCFile_, "text" .= t]
MCReport {text, reason} -> J.object ["type" .= MCReport_, "text" .= text, "reason" .= reason]
MCChat {text, chatLink} -> J.object ["type" .= MCChat_, "text" .= text, "chatLink" .= chatLink]
toEncoding = \case
MCUnknown {json} -> JE.value $ J.Object json
MCText t -> J.pairs $ "type" .= MCText_ <> "text" .= t
@@ -816,6 +843,7 @@ instance ToJSON MsgContent where
MCVoice {text, duration} -> J.pairs $ "type" .= MCVoice_ <> "text" .= text <> "duration" .= duration
MCFile t -> J.pairs $ "type" .= MCFile_ <> "text" .= t
MCReport {text, reason} -> J.pairs $ "type" .= MCReport_ <> "text" .= text <> "reason" .= reason
MCChat {text, chatLink} -> J.pairs $ "type" .= MCChat_ <> "text" .= text <> "chatLink" .= chatLink
instance ToField MsgContent where
toField = toField . encodeJSON
+3 -3
View File
@@ -125,8 +125,8 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
activeConn = Just conn
connLinkToConnect = toACreatedConnLink_ connFullLink connShortLink
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, connLinkToConnect, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
preparedContact = toPreparedContact connFullLink connShortLink
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, preparedContact, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember)
getGroupAndMember_ groupMemberId c = do
gm <-
@@ -218,7 +218,7 @@ getConnectionEntityViaShortLink db vr user@User {userId} shortLink = fmap either
(cReq, connId) <- ExceptT getConnReqConnId
(cReq,) <$> getConnectionEntity db vr user connId
where
getConnReqConnId =
getConnReqConnId =
firstRow' toConnReqConnId (SEInternalError "connection not found") $
DB.query
db
+5 -5
View File
@@ -104,7 +104,7 @@ import Simplex.Chat.Store.Shared
import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.UITheme
import Simplex.Messaging.Agent.Protocol (ACreatedConnLink, ConnId, CreatedConnLink (..), InvitationId, UserId)
import Simplex.Messaging.Agent.Protocol (ACreatedConnLink (..), ConnId, CreatedConnLink (..), InvitationId, UserId, connMode)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
@@ -263,7 +263,7 @@ createIncognitoProfile db User {userId} p = do
createIncognitoProfile_ db userId createdAt p
createPreparedContact :: DB.Connection -> User -> Profile -> ACreatedConnLink -> ExceptT StoreError IO Contact
createPreparedContact db user@User {userId} p@Profile {preferences} connLinkToConnect = do
createPreparedContact db user@User {userId} p@Profile {preferences} connLinkToConnect@(ACCL m _) = do
currentTs <- liftIO getCurrentTime
(localDisplayName, contactId, profileId) <- createContact_ db userId p (Just connLinkToConnect) "" Nothing currentTs
let profile = toLocalProfile profileId p ""
@@ -284,7 +284,7 @@ createPreparedContact db user@User {userId} p@Profile {preferences} connLinkToCo
createdAt = currentTs,
updatedAt = currentTs,
chatTs = Just currentTs,
connLinkToConnect = Just connLinkToConnect,
preparedContact = Just $ PreparedContact connLinkToConnect $ connMode m,
contactRequestId = Nothing,
contactGroupMemberId = Nothing,
contactGrpInvSent = False,
@@ -347,7 +347,7 @@ createDirectContact db user@User {userId} conn@Connection {connId, localAlias} p
createdAt = currentTs,
updatedAt = currentTs,
chatTs = Just currentTs,
connLinkToConnect = Nothing,
preparedContact = Nothing,
contactRequestId = Nothing,
contactGroupMemberId = Nothing,
contactGrpInvSent = False,
@@ -931,7 +931,7 @@ createContactFromRequest db user@User {userId, profile = LocalProfile {preferenc
createdAt = currentTs,
updatedAt = currentTs,
chatTs = Just currentTs,
connLinkToConnect = Nothing,
preparedContact = Nothing,
contactRequestId = Nothing,
contactGroupMemberId = Nothing,
contactGrpInvSent = False,
+6 -6
View File
@@ -172,7 +172,7 @@ import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared
import Simplex.Chat.Types.UITheme
import Simplex.Messaging.Agent.Protocol (ACreatedConnLink, ConnId, CreatedConnLink (..), UserId)
import Simplex.Messaging.Agent.Protocol (ConnId, CreatedConnLink (..), UserId)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, fromOnlyBI, maybeFirstRow)
import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
@@ -579,7 +579,7 @@ deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile {
DB.execute db "DELETE FROM contacts WHERE contact_id = ?" (Only contactId)
DB.execute db "DELETE FROM contact_profiles WHERE contact_profile_id = ?" (Only profileId)
createPreparedGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> ACreatedConnLink -> ExceptT StoreError IO GroupInfo
createPreparedGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> CreatedLinkContact -> ExceptT StoreError IO GroupInfo
createPreparedGroup db vr user@User {userId, userContactId} groupProfile connLinkToConnect = do
currentTs <- liftIO getCurrentTime
(groupId, groupLDN) <- createGroup_ db userId groupProfile (Just connLinkToConnect) Nothing currentTs
@@ -781,7 +781,7 @@ createGroupViaLink'
)
insertedRowId db
createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe ACreatedConnLink -> Maybe BusinessChatInfo -> UTCTime -> ExceptT StoreError IO (GroupId, Text)
createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe CreatedLinkContact -> Maybe BusinessChatInfo -> UTCTime -> ExceptT StoreError IO (GroupId, Text)
createGroup_ db userId groupProfile connLinkToConnect business currentTs = ExceptT $ do
let GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} = groupProfile
withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do
@@ -800,7 +800,7 @@ createGroup_ db userId groupProfile connLinkToConnect business currentTs = Excep
business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. connLinkToConnectRow connLinkToConnect :. businessChatInfoRow business)
((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. connLinkToConnectRow' connLinkToConnect :. businessChatInfoRow business)
groupId <- insertedRowId db
pure (groupId, localDisplayName)
@@ -2525,7 +2525,7 @@ createMemberContact
quotaErrCounter = 0
}
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, connLinkToConnect = Nothing, contactRequestId = Nothing, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, preparedContact = Nothing, contactRequestId = Nothing, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
getMemberContact :: DB.Connection -> VersionRangeChat -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation)
getMemberContact db vr user contactId = do
@@ -2562,7 +2562,7 @@ createMemberContactInvited
contactId <- createContactUpdateMember currentTs userPreferences
ctConn <- createMemberContactConn_ db user connIds gInfo mConn contactId subMode
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, connLinkToConnect = Nothing, contactRequestId = Nothing, contactGroupMemberId = Nothing, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, preparedContact = Nothing, contactRequestId = Nothing, contactGroupMemberId = Nothing, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
m' = m {memberContactId = Just contactId}
pure (mCt', m')
where
+22 -12
View File
@@ -30,6 +30,7 @@ import Data.Maybe (fromMaybe, isJust, listToMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Data.Type.Equality
import Simplex.Chat.Messages
import Simplex.Chat.Protocol
import Simplex.Chat.Remote.Types
@@ -37,7 +38,7 @@ import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared
import Simplex.Chat.Types.UITheme
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), AConnShortLink (..), ACreatedConnLink (..), ConnId, ConnShortLink, ConnectionMode (..), CreatedConnLink (..), SConnectionMode (..), UserId)
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), AConnShortLink (..), ACreatedConnLink (..), ConnId, ConnShortLink, ConnectionMode (..), ConnectionRequestUri, CreatedConnLink (..), UserId, connMode)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
@@ -396,13 +397,21 @@ createContact_ db userId Profile {displayName, fullName, image, contactLink, pre
contactId <- insertedRowId db
pure $ Right (ldn, contactId, profileId)
type ConnLinkToConnectRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink)
type AConnLinkToConnectRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink)
connLinkToConnectRow :: Maybe ACreatedConnLink -> ConnLinkToConnectRow
connLinkToConnectRow :: Maybe ACreatedConnLink -> AConnLinkToConnectRow
connLinkToConnectRow = \case
Just (ACCL m (CCLink fullLink shortLink)) -> (Just (ACR m fullLink), ACSL m <$> shortLink)
Nothing -> (Nothing, Nothing)
type ConnLinkToConnectRow m = (Maybe (ConnectionRequestUri m), Maybe (ConnShortLink m))
connLinkToConnectRow' :: Maybe (CreatedConnLink m) -> ConnLinkToConnectRow m
connLinkToConnectRow' = \case
Just (CCLink fullLink shortLink) -> (Just fullLink, shortLink)
Nothing -> (Nothing, Nothing)
{-# INLINE connLinkToConnectRow' #-}
deleteUnusedIncognitoProfileById_ :: DB.Connection -> User -> ProfileId -> IO ()
deleteUnusedIncognitoProfileById_ db User {userId} profileId =
DB.execute
@@ -432,17 +441,18 @@ toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, via
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
incognito = maybe False connIncognito activeConn
mergedPreferences = contactUserPreferences user userPreferences preferences incognito
connLinkToConnect = toACreatedConnLink_ connFullLink connShortLink
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, connLinkToConnect, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
preparedContact = toPreparedContact connFullLink connShortLink
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, preparedContact, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
toPreparedContact :: Maybe AConnectionRequestUri -> Maybe AConnShortLink -> Maybe PreparedContact
toPreparedContact connFullLink connShortLink =
(\cl@(ACCL m _) -> PreparedContact cl $ connMode m) <$> toACreatedConnLink_ connFullLink connShortLink
toACreatedConnLink_ :: Maybe AConnectionRequestUri -> Maybe AConnShortLink -> Maybe ACreatedConnLink
toACreatedConnLink_ connFullLink connShortLink = case (connFullLink, connShortLink) of
(Nothing, _) -> Nothing
(Just (ACR m cr), Nothing) -> Just $ ACCL m (CCLink cr Nothing)
(Just (ACR m cr), Just (ACSL m' l)) -> case (m, m') of
(SCMInvitation, SCMInvitation) -> Just $ ACCL SCMInvitation (CCLink cr (Just l))
(SCMContact, SCMContact) -> Just $ ACCL SCMContact (CCLink cr (Just l))
_ -> Nothing
toACreatedConnLink_ Nothing _ = Nothing
toACreatedConnLink_ (Just (ACR m cr)) csl = case csl of
Nothing -> Just $ ACCL m $ CCLink cr Nothing
Just (ACSL m' l) -> (\Refl -> ACCL m $ CCLink cr (Just l)) <$> testEquality m m'
getProfileById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO LocalProfile
getProfileById db userId profileId =
+8 -2
View File
@@ -188,7 +188,7 @@ data Contact = Contact
createdAt :: UTCTime,
updatedAt :: UTCTime,
chatTs :: Maybe UTCTime,
connLinkToConnect :: Maybe ACreatedConnLink,
preparedContact :: Maybe PreparedContact,
contactRequestId :: Maybe Int64,
contactGroupMemberId :: Maybe GroupMemberId,
contactGrpInvSent :: Bool,
@@ -200,6 +200,9 @@ data Contact = Contact
}
deriving (Eq, Show)
data PreparedContact = PreparedContact {connLinkToConnect :: ACreatedConnLink, uiConnLinkType :: ConnectionMode}
deriving (Eq, Show)
newtype CustomData = CustomData J.Object
deriving (Eq, Show)
@@ -658,7 +661,8 @@ deriving newtype instance FromField ImageData
data ContactShortLinkData = ContactShortLinkData
{ profile :: Profile,
message :: Maybe Text
message :: Maybe Text,
business :: Bool
}
deriving (Show)
@@ -1978,6 +1982,8 @@ $(JQ.deriveJSON defaultJSON ''XFTPSndFile)
$(JQ.deriveJSON defaultJSON ''FileTransferMeta)
$(JQ.deriveJSON defaultJSON ''PreparedContact)
$(JQ.deriveJSON defaultJSON ''LocalFileMeta)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FT") ''FileTransfer)