mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-28 20:49:50 +00:00
core: do not regenerate key when accepting connection to avoid invalidating invitation link on bad networks (#5018)
* core: prepare conn (plan)
* update
* group join
* comment
* comment
* wip
* Revert "wip"
This reverts commit 0849f43377.
* accept
* save contact_id, reuse contact
* refactor
* simplexmq
* set contactUsed
* support retrying join
* exclude prepared connections from API responses
* avoid race with events
* avoid race better
* fix UI
* update library
* tmp
* update
* display error details on ios cmd prohibited
* underscore instead of empty
* Update apps/ios/Shared/Model/SimpleXAPI.swift
Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
* test
* update simplexmq
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Diogo <diogofncunha@gmail.com>
This commit is contained in:
co-authored by
Evgeny Poberezkin
Diogo
parent
2127c7dcce
commit
9a87f344b5
+68
-29
@@ -1299,12 +1299,18 @@ processChatCommand' vr = \case
|
||||
APIAcceptContact incognito connReqId -> withUser $ \_ -> do
|
||||
(user@User {userId}, cReq@UserContactRequest {userContactLinkId}) <- withFastStore $ \db -> getContactRequest' db connReqId
|
||||
withUserContactLock "acceptContact" userContactLinkId $ do
|
||||
(ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito
|
||||
ucl <- withFastStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
let contactUsed = (\(_, groupId_, _) -> isNothing groupId_) ucl
|
||||
-- [incognito] generate profile to send, create connection with incognito profile
|
||||
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
||||
ct <- acceptContactRequest user cReq incognitoProfile contactUsed
|
||||
pure $ CRAcceptingContactRequest user ct
|
||||
ct' <- withStore' $ \db -> do
|
||||
deleteContactRequestRec db user cReq
|
||||
updateContactAccepted db user ct contactUsed
|
||||
conn' <-
|
||||
if sqSecured
|
||||
then conn {connStatus = ConnSndReady} <$ updateConnectionStatusFromTo db connId ConnNew ConnSndReady
|
||||
else pure conn
|
||||
pure ct {contactUsed, activeConn = Just conn'}
|
||||
pure $ CRAcceptingContactRequest user ct'
|
||||
APIRejectContact connReqId -> withUser $ \user -> do
|
||||
cReq@UserContactRequest {userContactLinkId, agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <-
|
||||
withFastStore $ \db ->
|
||||
@@ -1763,7 +1769,7 @@ processChatCommand' vr = \case
|
||||
pure conn'
|
||||
APIConnectPlan userId cReqUri -> withUserId userId $ \user ->
|
||||
CRConnectionPlan user <$> connectPlan user cReqUri
|
||||
APIConnect userId incognito (Just (ACR SCMInvitation cReq)) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do
|
||||
APIConnect userId incognito (Just (ACR SCMInvitation cReq@(CRInvitationUri crData e2e))) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
-- [incognito] generate profile to send
|
||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||
@@ -1774,10 +1780,27 @@ processChatCommand' vr = \case
|
||||
Just (agentV, pqSup') -> do
|
||||
let chatV = agentToChatVersion agentV
|
||||
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
|
||||
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup'
|
||||
conn@PendingContactConnection {pccConnId} <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnJoined (incognitoProfile $> profileToSend) subMode chatV pqSup'
|
||||
joinPreparedAgentConnection user pccConnId connId cReq dm pqSup' subMode
|
||||
pure $ CRSentConfirmation user conn
|
||||
withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqs) >>= \case
|
||||
Nothing -> joinNewConn chatV dm
|
||||
Just (RcvDirectMsgConnection conn@Connection {connId, connStatus, contactConnInitiated} Nothing)
|
||||
| connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link
|
||||
| connStatus == ConnPrepared -> do -- retrying join after error
|
||||
pcc <- withFastStore $ \db -> getPendingContactConnection db userId connId
|
||||
joinPreparedConn (aConnId conn) pcc dm
|
||||
Just ent -> throwChatError $ CECommandError $ "connection exists: " <> show (connEntityInfo ent)
|
||||
where
|
||||
joinNewConn chatV dm = do
|
||||
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup'
|
||||
pcc <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup'
|
||||
joinPreparedConn connId pcc dm
|
||||
joinPreparedConn connId pcc@PendingContactConnection {pccConnId} dm = do
|
||||
void $ withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode
|
||||
withFastStore' $ \db -> updateConnectionStatusFromTo db pccConnId ConnPrepared ConnJoined
|
||||
pure $ CRSentConfirmation user pcc {pccConnStatus = ConnJoined}
|
||||
cReqs =
|
||||
( CRInvitationUri crData {crScheme = SSSimplex} e2e,
|
||||
CRInvitationUri crData {crScheme = simplexChat} e2e
|
||||
)
|
||||
APIConnect userId incognito (Just (ACR SCMContact cReq)) -> withUserId userId $ \user -> connectViaContact user incognito cReq
|
||||
APIConnect _ _ Nothing -> throwChatError CEInvalidConnReq
|
||||
Connect incognito aCReqUri@(Just cReqUri) -> withUser $ \user@User {userId} -> do
|
||||
@@ -2029,20 +2052,21 @@ processChatCommand' vr = \case
|
||||
Just Connection {peerChatVRange} -> do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
dm <- encodeConnInfo $ XGrpAcpt membershipMemId
|
||||
agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff
|
||||
let chatV = vr `peerConnChatVersion` peerChatVRange
|
||||
cId <- withFastStore' $ \db -> do
|
||||
Connection {connId = cId} <- createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode
|
||||
agentConnId <- case memberConn fromMember of
|
||||
Nothing -> do
|
||||
agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff
|
||||
let chatV = vr `peerConnChatVersion` peerChatVRange
|
||||
void $ withFastStore' $ \db -> createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode
|
||||
pure agentConnId
|
||||
Just conn -> pure $ aConnId conn
|
||||
withFastStore' $ \db -> do
|
||||
updateGroupMemberStatus db userId fromMember GSMemAccepted
|
||||
updateGroupMemberStatus db userId membership GSMemAccepted
|
||||
pure cId
|
||||
void (withAgent $ \a -> joinConnection a (aUserId user) (Just agentConnId) True connRequest dm PQSupportOff subMode)
|
||||
void (withAgent $ \a -> joinConnection a (aUserId user) agentConnId True connRequest dm PQSupportOff subMode)
|
||||
`catchChatError` \e -> do
|
||||
withFastStore' $ \db -> do
|
||||
deleteConnectionRecord db user cId
|
||||
updateGroupMemberStatus db userId fromMember GSMemInvited
|
||||
updateGroupMemberStatus db userId membership GSMemInvited
|
||||
withAgent $ \a -> deleteConnectionAsync a False agentConnId
|
||||
throwError e
|
||||
updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` (toView . CRChatError (Just user))
|
||||
pure $ CRUserAcceptedGroupSent user g {membership = membership {memberStatus = GSMemAccepted}} Nothing
|
||||
@@ -2615,7 +2639,7 @@ processChatCommand' vr = \case
|
||||
joinPreparedAgentConnection user pccConnId connId cReq dm pqSup subMode
|
||||
joinPreparedAgentConnection :: User -> Int64 -> ConnId -> ConnectionRequestUri m -> ByteString -> PQSupport -> SubscriptionMode -> CM ()
|
||||
joinPreparedAgentConnection user pccConnId connId cReq connInfo pqSup subMode = do
|
||||
void (withAgent $ \a -> joinConnection a (aUserId user) (Just connId) True cReq connInfo pqSup subMode)
|
||||
void (withAgent $ \a -> joinConnection a (aUserId user) connId True cReq connInfo pqSup subMode)
|
||||
`catchChatError` \e -> do
|
||||
withFastStore' $ \db -> deleteConnectionRecord db user pccConnId
|
||||
withAgent $ \a -> deleteConnectionAsync a False connId
|
||||
@@ -2857,6 +2881,8 @@ processChatCommand' vr = \case
|
||||
connectPlan user (ACR SCMInvitation (CRInvitationUri crData e2e)) = do
|
||||
withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqSchemas) >>= \case
|
||||
Nothing -> pure $ CPInvitationLink ILPOk
|
||||
Just (RcvDirectMsgConnection Connection {connStatus = ConnPrepared} Nothing) ->
|
||||
pure $ CPInvitationLink ILPOk
|
||||
Just (RcvDirectMsgConnection conn ct_) -> do
|
||||
let Connection {connStatus, contactConnInitiated} = conn
|
||||
if
|
||||
@@ -3664,29 +3690,41 @@ getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of
|
||||
liftIO $ B.hPut h "" >> hFlush h
|
||||
| otherwise = liftIO $ B.writeFile fPath ""
|
||||
|
||||
acceptContactRequest :: User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> CM Contact
|
||||
acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognitoProfile contactUsed = do
|
||||
acceptContactRequest :: User -> UserContactRequest -> IncognitoEnabled -> CM (Contact, Connection, SndQueueSecured)
|
||||
acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId = AgentInvId invId, contactId_, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognito = do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let pqSup = PQSupportOn
|
||||
vr <- chatVersionRange
|
||||
let profileToSend = profileToSendOnAccept user incognitoProfile False
|
||||
chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||
pqSup' = pqSup `CR.pqSupportAnd` pqSupport
|
||||
vr <- chatVersionRange
|
||||
let chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||
(ct, conn, incognitoProfile) <- case contactId_ of
|
||||
Nothing -> do
|
||||
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
||||
connId <- withAgent $ \a -> prepareConnectionToAccept a True invId pqSup'
|
||||
(ct, conn) <- withStore' $ \db -> createAcceptedContact db user connId chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' False
|
||||
pure (ct, conn, incognitoProfile)
|
||||
Just contactId -> do
|
||||
ct <- withFastStore $ \db -> getContact db vr user contactId
|
||||
case contactConn ct of
|
||||
Nothing -> throwChatError $ CECommandError "contact has no connection"
|
||||
Just conn@Connection {customUserProfileId} -> do
|
||||
incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId
|
||||
pure (ct, conn, ExistingIncognito <$> incognitoProfile)
|
||||
let profileToSend = profileToSendOnAccept user incognitoProfile False
|
||||
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
|
||||
(acId, sqSecured) <- withAgent $ \a -> acceptContact a True invId dm pqSup' subMode
|
||||
let connStatus = if sqSecured then ConnSndReady else ConnNew
|
||||
withStore' $ \db -> createAcceptedContact db user acId connStatus chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' contactUsed
|
||||
(ct,conn,) <$> withAgent (\a -> acceptContact a (aConnId conn) True invId dm pqSup' subMode)
|
||||
|
||||
acceptContactRequestAsync :: User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> PQSupport -> CM Contact
|
||||
acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile contactUsed pqSup = do
|
||||
acceptContactRequestAsync user cReq@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile contactUsed pqSup = do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let profileToSend = profileToSendOnAccept user incognitoProfile False
|
||||
vr <- chatVersionRange
|
||||
let chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||
(cmdId, acId) <- agentAcceptContactAsync user True invId (XInfo profileToSend) subMode pqSup chatV
|
||||
withStore' $ \db -> do
|
||||
ct@Contact {activeConn} <- createAcceptedContact db user acId ConnNew chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed
|
||||
forM_ activeConn $ \Connection {connId} -> setCommandConnId db user cmdId connId
|
||||
(ct, Connection {connId}) <- createAcceptedContact db user acId chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed
|
||||
deleteContactRequestRec db user cReq
|
||||
setCommandConnId db user cmdId connId
|
||||
pure ct
|
||||
|
||||
acceptGroupJoinRequestAsync :: User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember
|
||||
@@ -4551,6 +4589,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
lift $ setContactNetworkStatus ct' NSConnected
|
||||
toView $ CRContactConnected user ct' (fmap fromLocalProfile incognitoProfile)
|
||||
when (directOrUsed ct') $ do
|
||||
unless (contactUsed ct') $ withFastStore' $ \db -> updateContactUsed db user ct'
|
||||
createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo pqEnc) Nothing
|
||||
createFeatureEnabledItems ct'
|
||||
when (contactConnInitiated conn') $ do
|
||||
|
||||
@@ -1416,6 +1416,10 @@ catchStoreError :: ExceptT StoreError IO a -> (StoreError -> ExceptT StoreError
|
||||
catchStoreError = catchAllErrors mkStoreError
|
||||
{-# INLINE catchStoreError #-}
|
||||
|
||||
tryStoreError' :: ExceptT StoreError IO a -> IO (Either StoreError a)
|
||||
tryStoreError' = tryAllErrors' mkStoreError
|
||||
{-# INLINE tryStoreError' #-}
|
||||
|
||||
mkStoreError :: SomeException -> StoreError
|
||||
mkStoreError = SEInternalError . show
|
||||
{-# INLINE mkStoreError #-}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Migrations.M20241010_contact_requests_contact_id where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20241010_contact_requests_contact_id :: Query
|
||||
m20241010_contact_requests_contact_id =
|
||||
[sql|
|
||||
ALTER TABLE contact_requests ADD COLUMN contact_id INTEGER REFERENCES contacts ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id);
|
||||
|]
|
||||
|
||||
down_m20241010_contact_requests_contact_id :: Query
|
||||
down_m20241010_contact_requests_contact_id =
|
||||
[sql|
|
||||
DROP INDEX idx_contact_requests_contact_id;
|
||||
|
||||
ALTER TABLE contact_requests DROP COLUMN contact_id;
|
||||
|]
|
||||
@@ -327,6 +327,7 @@ CREATE TABLE contact_requests(
|
||||
peer_chat_min_version INTEGER NOT NULL DEFAULT 1,
|
||||
peer_chat_max_version INTEGER NOT NULL DEFAULT 1,
|
||||
pq_support INTEGER NOT NULL DEFAULT 0,
|
||||
contact_id INTEGER REFERENCES contacts ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id, local_display_name)
|
||||
REFERENCES display_names(user_id, local_display_name)
|
||||
ON UPDATE CASCADE
|
||||
@@ -888,3 +889,4 @@ CREATE INDEX idx_chat_items_fwd_from_chat_item_id ON chat_items(
|
||||
CREATE INDEX idx_received_probes_group_member_id on received_probes(
|
||||
group_member_id
|
||||
);
|
||||
CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id);
|
||||
|
||||
@@ -125,6 +125,17 @@ data ConnectionEntity
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON fstToLower) ''ConnectionEntity)
|
||||
|
||||
connEntityInfo :: ConnectionEntity -> String
|
||||
connEntityInfo = \case
|
||||
RcvDirectMsgConnection c ct_ -> ctInfo ct_ <> ", status: " <> show (connStatus c)
|
||||
RcvGroupMsgConnection c g m -> mInfo g m <> ", status: " <> show (connStatus c)
|
||||
SndFileConnection c _ft -> "snd file, status: " <> show (connStatus c)
|
||||
RcvFileConnection c _ft -> "rcv file, status: " <> show (connStatus c)
|
||||
UserContactConnection c _uc -> "user address, status: " <> show (connStatus c)
|
||||
where
|
||||
ctInfo = maybe "connection" $ \Contact {contactId} -> "contact " <> show contactId
|
||||
mInfo GroupInfo {groupId} GroupMember {groupMemberId} = "group " <> show groupId <> ", member " <> show groupMemberId
|
||||
|
||||
updateEntityConnStatus :: ConnectionEntity -> ConnStatus -> ConnectionEntity
|
||||
updateEntityConnStatus connEntity connStatus = case connEntity of
|
||||
RcvDirectMsgConnection c ct_ -> RcvDirectMsgConnection (st c) ((\ct -> (ct :: Contact) {activeConn = Just $ st c}) <$> ct_)
|
||||
|
||||
@@ -62,6 +62,8 @@ module Simplex.Chat.Store.Direct
|
||||
getContactRequestIdByName,
|
||||
deleteContactRequest,
|
||||
createAcceptedContact,
|
||||
deleteContactRequestRec,
|
||||
updateContactAccepted,
|
||||
getUserByContactRequestId,
|
||||
getPendingContactConnections,
|
||||
updatePCCUser,
|
||||
@@ -69,6 +71,7 @@ module Simplex.Chat.Store.Direct
|
||||
getConnectionById,
|
||||
getConnectionsContacts,
|
||||
updateConnectionStatus,
|
||||
updateConnectionStatusFromTo,
|
||||
updateContactSettings,
|
||||
setConnConnReqInv,
|
||||
resetContactConnInitiated,
|
||||
@@ -655,7 +658,7 @@ createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (V
|
||||
db
|
||||
[sql|
|
||||
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.contact_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, cr.pq_support, p.preferences, cr.created_at, cr.updated_at,
|
||||
cr.peer_chat_min_version, cr.peer_chat_max_version
|
||||
FROM contact_requests cr
|
||||
@@ -724,7 +727,7 @@ getContactRequest db User {userId} contactRequestId =
|
||||
db
|
||||
[sql|
|
||||
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.contact_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, cr.pq_support, p.preferences, cr.created_at, cr.updated_at,
|
||||
cr.peer_chat_min_version, cr.peer_chat_max_version
|
||||
FROM contact_requests cr
|
||||
@@ -766,9 +769,8 @@ deleteContactRequest db User {userId} contactRequestId = do
|
||||
(userId, userId, contactRequestId, userId)
|
||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
|
||||
|
||||
createAcceptedContact :: DB.Connection -> User -> ConnId -> ConnStatus -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO Contact
|
||||
createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connStatus connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do
|
||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
||||
createAcceptedContact :: DB.Connection -> User -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO (Contact, Connection)
|
||||
createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do
|
||||
createdAt <- getCurrentTime
|
||||
customUserProfileId <- forM incognitoProfile $ \case
|
||||
NewIncognito p -> createIncognitoProfile_ db userId createdAt p
|
||||
@@ -779,29 +781,42 @@ 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, contact_used) VALUES (?,?,?,?,?,?,?,?,?,?)"
|
||||
(userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, createdAt, xContactId, contactUsed)
|
||||
contactId <- insertedRowId db
|
||||
conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId connStatus connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup
|
||||
DB.execute db "UPDATE contact_requests SET contact_id = ? WHERE user_id = ? AND local_display_name = ?" (contactId, userId, localDisplayName)
|
||||
conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup
|
||||
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
|
||||
pure $
|
||||
Contact
|
||||
{ contactId,
|
||||
localDisplayName,
|
||||
profile = toLocalProfile profileId profile "",
|
||||
activeConn = Just conn,
|
||||
viaGroup = Nothing,
|
||||
contactUsed,
|
||||
contactStatus = CSActive,
|
||||
chatSettings = defaultChatSettings,
|
||||
userPreferences,
|
||||
mergedPreferences,
|
||||
createdAt,
|
||||
updatedAt = createdAt,
|
||||
chatTs = Just createdAt,
|
||||
contactGroupMemberId = Nothing,
|
||||
contactGrpInvSent = False,
|
||||
uiThemes = Nothing,
|
||||
chatDeleted = False,
|
||||
customData = Nothing
|
||||
}
|
||||
ct =
|
||||
Contact
|
||||
{ contactId,
|
||||
localDisplayName,
|
||||
profile = toLocalProfile profileId profile "",
|
||||
activeConn = Just conn,
|
||||
viaGroup = Nothing,
|
||||
contactUsed,
|
||||
contactStatus = CSActive,
|
||||
chatSettings = defaultChatSettings,
|
||||
userPreferences,
|
||||
mergedPreferences,
|
||||
createdAt,
|
||||
updatedAt = createdAt,
|
||||
chatTs = Just createdAt,
|
||||
contactGroupMemberId = Nothing,
|
||||
contactGrpInvSent = False,
|
||||
uiThemes = Nothing,
|
||||
chatDeleted = False,
|
||||
customData = Nothing
|
||||
}
|
||||
pure (ct, conn)
|
||||
|
||||
deleteContactRequestRec :: DB.Connection -> User -> UserContactRequest -> IO ()
|
||||
deleteContactRequestRec db User {userId} UserContactRequest {contactRequestId} =
|
||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
|
||||
|
||||
updateContactAccepted :: DB.Connection -> User -> Contact -> Bool -> IO ()
|
||||
updateContactAccepted db User {userId} Contact {contactId} contactUsed =
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE contacts SET contact_used = ? WHERE user_id = ? AND contact_id = ?"
|
||||
(contactUsed, userId, contactId)
|
||||
|
||||
getContactIdByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Int64
|
||||
getContactIdByName db User {userId} cName =
|
||||
@@ -927,7 +942,17 @@ getConnectionsContacts db agentConnIds = do
|
||||
toContactRef (contactId, connId, acId, localDisplayName) = ContactRef {contactId, connId, agentConnId = AgentConnId acId, localDisplayName}
|
||||
|
||||
updateConnectionStatus :: DB.Connection -> Connection -> ConnStatus -> IO ()
|
||||
updateConnectionStatus db Connection {connId} connStatus = do
|
||||
updateConnectionStatus db Connection {connId} = updateConnectionStatus_ db connId
|
||||
{-# INLINE updateConnectionStatus #-}
|
||||
|
||||
updateConnectionStatusFromTo :: DB.Connection -> Int64 -> ConnStatus -> ConnStatus -> IO ()
|
||||
updateConnectionStatusFromTo db connId fromStatus toStatus = do
|
||||
maybeFirstRow fromOnly (DB.query db "SELECT conn_status FROM connections WHERE connection_id = ?" (Only connId)) >>= \case
|
||||
Just status | status == fromStatus -> updateConnectionStatus_ db connId toStatus
|
||||
_ -> pure ()
|
||||
|
||||
updateConnectionStatus_ :: DB.Connection -> Int64 -> ConnStatus -> IO ()
|
||||
updateConnectionStatus_ db connId connStatus = do
|
||||
currentTs <- getCurrentTime
|
||||
if connStatus == ConnReady
|
||||
then DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL WHERE connection_id = ?" (connStatus, currentTs, connId)
|
||||
|
||||
@@ -882,7 +882,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of
|
||||
db
|
||||
( [sql|
|
||||
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.contact_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, cr.pq_support, p.preferences,
|
||||
cr.created_at, cr.updated_at as ts,
|
||||
cr.peer_chat_min_version, cr.peer_chat_max_version
|
||||
@@ -930,6 +930,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
|
||||
FROM connections
|
||||
WHERE user_id = :user_id
|
||||
AND conn_type = :conn_contact
|
||||
AND conn_status != :conn_status
|
||||
AND contact_id IS NULL
|
||||
AND conn_level = 0
|
||||
AND via_contact IS NULL
|
||||
@@ -938,7 +939,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
|
||||
|]
|
||||
<> pagQuery
|
||||
)
|
||||
([":user_id" := userId, ":conn_contact" := ConnContact, ":search" := search] <> pagParams)
|
||||
([":user_id" := userId, ":conn_contact" := ConnContact, ":conn_status" := ConnPrepared, ":search" := search] <> pagParams)
|
||||
toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChatPreviewData
|
||||
toPreview connRow =
|
||||
let conn@PendingContactConnection {updatedAt} = toPendingContactConnection connRow
|
||||
|
||||
@@ -113,6 +113,7 @@ import Simplex.Chat.Migrations.M20240528_quota_err_counter
|
||||
import Simplex.Chat.Migrations.M20240827_calls_uuid
|
||||
import Simplex.Chat.Migrations.M20240920_user_order
|
||||
import Simplex.Chat.Migrations.M20241008_indexes
|
||||
import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -225,7 +226,8 @@ schemaMigrations =
|
||||
("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter),
|
||||
("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid),
|
||||
("20240920_user_order", m20240920_user_order, Just down_m20240920_user_order),
|
||||
("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes)
|
||||
("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes),
|
||||
("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -411,13 +411,13 @@ getProfileById db userId profileId =
|
||||
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}
|
||||
|
||||
type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat)
|
||||
type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat)
|
||||
|
||||
toContactRequest :: ContactRequestRow -> UserContactRequest
|
||||
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do
|
||||
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do
|
||||
let profile = Profile {displayName, fullName, image, contactLink, preferences}
|
||||
cReqChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
|
||||
in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, createdAt, updatedAt}
|
||||
in UserContactRequest {contactRequestId, agentInvitationId, contactId_, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, createdAt, updatedAt}
|
||||
|
||||
userQuery :: Query
|
||||
userQuery =
|
||||
|
||||
@@ -297,6 +297,7 @@ userContactGroupId UserContact {groupId} = groupId
|
||||
data UserContactRequest = UserContactRequest
|
||||
{ contactRequestId :: Int64,
|
||||
agentInvitationId :: AgentInvId,
|
||||
contactId_ :: Maybe ContactId,
|
||||
userContactLinkId :: Int64,
|
||||
agentContactConnId :: AgentConnId, -- connection id of user contact
|
||||
cReqChatVRange :: VersionRangeChat,
|
||||
@@ -1371,6 +1372,8 @@ aConnId' PendingContactConnection {pccAgentConnId = AgentConnId cId} = cId
|
||||
data ConnStatus
|
||||
= -- | connection is created by initiating party with agent NEW command (createConnection)
|
||||
ConnNew
|
||||
| -- | connection is prepared, to avoid changing keys on invitation links when retrying.
|
||||
ConnPrepared
|
||||
| -- | connection is joined by joining party with agent JOIN command (joinConnection)
|
||||
ConnJoined
|
||||
| -- | initiating party received CONF notification (to be renamed to REQ)
|
||||
@@ -1399,6 +1402,7 @@ instance ToJSON ConnStatus where
|
||||
instance TextEncoding ConnStatus where
|
||||
textDecode = \case
|
||||
"new" -> Just ConnNew
|
||||
"prepared" -> Just ConnPrepared
|
||||
"joined" -> Just ConnJoined
|
||||
"requested" -> Just ConnRequested
|
||||
"accepted" -> Just ConnAccepted
|
||||
@@ -1408,6 +1412,7 @@ instance TextEncoding ConnStatus where
|
||||
_ -> Nothing
|
||||
textEncode = \case
|
||||
ConnNew -> "new"
|
||||
ConnPrepared -> "prepared"
|
||||
ConnJoined -> "joined"
|
||||
ConnRequested -> "requested"
|
||||
ConnAccepted -> "accepted"
|
||||
|
||||
Reference in New Issue
Block a user