Merge branch 'short-links' into f/short-links-kotlin-user-picker

This commit is contained in:
spaced4ndy 2025-06-27 13:14:31 +04:00
commit 86953de2f4
10 changed files with 94 additions and 76 deletions

View file

@ -1533,9 +1533,10 @@ func acceptContactRequest(incognito: Bool, contactRequestId: Int64) async {
NetworkModel.shared.setContactNetworkStatus(contact, .connected) NetworkModel.shared.setContactNetworkStatus(contact, .connected)
} }
if contact.sndReady { if contact.sndReady {
let chatId = chat.id
DispatchQueue.main.async { DispatchQueue.main.async {
dismissAllSheets(animated: true) { dismissAllSheets(animated: true) {
ItemsModel.shared.loadOpenChat(chat.id) ItemsModel.shared.loadOpenChat(chatId)
} }
} }
} }

View file

@ -707,6 +707,7 @@ struct ComposeView: View {
await MainActor.run { await MainActor.run {
self.chatModel.updateContact(contact) self.chatModel.updateContact(contact)
clearState() clearState()
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
} }
} else { } else {
AlertManager.shared.showAlertMsg(title: "Empty message!") AlertManager.shared.showAlertMsg(title: "Empty message!")
@ -744,6 +745,7 @@ struct ComposeView: View {
if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) { if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) {
await MainActor.run { await MainActor.run {
self.chatModel.updateContact(contact) self.chatModel.updateContact(contact)
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
clearState() clearState()
} }
} else { } else {

View file

@ -455,7 +455,7 @@ struct ChatPreviewView: View {
let size = dynamicSize(userFont).incognitoSize let size = dynamicSize(userFont).incognitoSize
switch chat.chatInfo { switch chat.chatInfo {
case let .direct(contact): case let .direct(contact):
if contact.active && contact.activeConn != nil { if contact.active, let status = contact.activeConn?.connStatus, status == .ready || status == .sndReady {
NetworkStatusView(contact: contact, size: size) NetworkStatusView(contact: contact, size: size)
} else { } else {
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size) incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)

View file

@ -1735,8 +1735,8 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } } public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } }
public var active: Bool { get { contactStatus == .active } } public var active: Bool { get { contactStatus == .active } }
public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } } public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } }
public var nextConnectPrepared: Bool { preparedContact != nil && activeConn == nil } public var nextConnectPrepared: Bool { preparedContact != nil && (activeConn == nil || activeConn?.connStatus == .prepared) }
public var nextAcceptContactRequest: Bool { contactRequestId != nil && activeConn == nil } public var nextAcceptContactRequest: Bool { contactRequestId != nil && (activeConn == nil || activeConn?.connStatus == .new) }
public var sendMsgToConnect: Bool { nextSendGrpInv || nextConnectPrepared } public var sendMsgToConnect: Bool { nextSendGrpInv || nextConnectPrepared }
public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var displayName: String { localAlias == "" ? profile.displayName : localAlias }
public var fullName: String { get { profile.fullName } } public var fullName: String { get { profile.fullName } }
@ -1833,7 +1833,7 @@ public struct Connection: Decodable, Hashable {
public var connId: Int64 public var connId: Int64
public var agentConnId: String public var agentConnId: String
public var peerChatVRange: VersionRange public var peerChatVRange: VersionRange
var connStatus: ConnStatus public var connStatus: ConnStatus
public var connLevel: Int public var connLevel: Int
public var viaGroupLink: Bool public var viaGroupLink: Bool
public var customUserProfileId: Int64? public var customUserProfileId: Int64?

View file

@ -456,7 +456,7 @@ data ChatCommand
| APIChangePreparedGroupUser GroupId UserId | APIChangePreparedGroupUser GroupId UserId
| APIConnectPreparedContact {contactId :: ContactId, incognito :: IncognitoEnabled, msgContent_ :: Maybe MsgContent} | APIConnectPreparedContact {contactId :: ContactId, incognito :: IncognitoEnabled, msgContent_ :: Maybe MsgContent}
| APIConnectPreparedGroup GroupId IncognitoEnabled (Maybe MsgContent) | APIConnectPreparedGroup GroupId IncognitoEnabled (Maybe MsgContent)
| APIConnect UserId IncognitoEnabled (Maybe ACreatedConnLink) | APIConnect UserId IncognitoEnabled ACreatedConnLink
| Connect IncognitoEnabled (Maybe AConnectionLink) | Connect IncognitoEnabled (Maybe AConnectionLink)
| APIConnectContactViaAddress UserId IncognitoEnabled ContactId | APIConnectContactViaAddress UserId IncognitoEnabled ContactId
| ConnectSimplex IncognitoEnabled -- UserId (not used in UI) | ConnectSimplex IncognitoEnabled -- UserId (not used in UI)

View file

@ -1157,7 +1157,6 @@ processChatCommand' vr = \case
when (shortLinkDataSet && incognito) $ throwCmdError "incognito not allowed for address with short link data" when (shortLinkDataSet && incognito) $ throwCmdError "incognito not allowed for address with short link data"
withUserContactLock "acceptContact" uclId $ do withUserContactLock "acceptContact" uclId $ do
cReq <- withFastStore $ \db -> getContactRequest db user connReqId cReq <- withFastStore $ \db -> getContactRequest db user connReqId
-- TODO [short links] accept async, move to continuation on JOIN?
(ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito (ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito
let contactUsed = isNothing gLinkInfo_ let contactUsed = isNothing gLinkInfo_
ct' <- withStore' $ \db -> do ct' <- withStore' $ \db -> do
@ -1795,18 +1794,16 @@ processChatCommand' vr = \case
Contact {preparedContact} <- withFastStore $ \db -> getContact db vr user contactId Contact {preparedContact} <- withFastStore $ \db -> getContact db vr user contactId
case preparedContact of case preparedContact of
Nothing -> throwCmdError "contact doesn't have link to connect" Nothing -> throwCmdError "contact doesn't have link to connect"
Just PreparedContact {connLinkToConnect = ACCL SCMInvitation ccLink} -> Just PreparedContact {connLinkToConnect = ACCL SCMInvitation ccLink} -> do
connectViaInvitation user incognito ccLink (Just contactId) >>= \case (_, customUserProfile) <- connectViaInvitation user incognito ccLink (Just contactId)
CRSentConfirmation {customUserProfile} -> do -- get updated contact with connection
-- get updated contact with connection ct' <- withFastStore $ \db -> getContact db vr user contactId
ct' <- withFastStore $ \db -> getContact db vr user contactId forM_ msgContent_ $ \mc -> do
forM_ msgContent_ $ \mc -> do let evt = XMsgNew $ MCSimple (extMsgContent mc Nothing)
let evt = XMsgNew $ MCSimple (extMsgContent mc Nothing) (msg, _) <- sendDirectContactMessage user ct' evt
(msg, _) <- sendDirectContactMessage user ct' evt ci <- saveSndChatItem user (CDDirectSnd ct') msg (CISndMsgContent mc)
ci <- saveSndChatItem user (CDDirectSnd ct') msg (CISndMsgContent mc) toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci]
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci] pure $ CRStartedConnectionToContact user ct' customUserProfile
pure $ CRStartedConnectionToContact user ct' customUserProfile
cr -> pure cr
Just PreparedContact {connLinkToConnect = ACCL SCMContact ccLink, welcomeSharedMsgId} -> do Just PreparedContact {connLinkToConnect = ACCL SCMContact ccLink, welcomeSharedMsgId} -> do
-- TODO [short links] reuse welcomeSharedMsgId -- TODO [short links] reuse welcomeSharedMsgId
msg_ <- forM msgContent_ $ \mc -> (,mc) <$> getSharedMsgId msg_ <- forM msgContent_ $ \mc -> (,mc) <$> getSharedMsgId
@ -1831,11 +1828,12 @@ processChatCommand' vr = \case
gInfo' <- withFastStore $ \db -> getGroupInfo db vr user groupId gInfo' <- withFastStore $ \db -> getGroupInfo db vr user groupId
pure $ CRStartedConnectionToGroup user gInfo' customUserProfile pure $ CRStartedConnectionToGroup user gInfo' customUserProfile
cr -> pure cr cr -> pure cr
APIConnect userId incognito (Just (ACCL SCMInvitation ccLink)) -> withUserId userId $ \user -> APIConnect userId incognito acl -> withUserId userId $ \user -> case acl of
connectViaInvitation user incognito ccLink Nothing ACCL SCMInvitation ccLink -> do
APIConnect userId incognito (Just (ACCL SCMContact ccLink)) -> withUserId userId $ \user -> (dbConnId, incognitoProfile) <- connectViaInvitation user incognito ccLink Nothing
connectViaContact user incognito ccLink Nothing Nothing Nothing pcc <- withFastStore $ \db -> getPendingContactConnection db userId dbConnId
APIConnect _ _ Nothing -> throwChatError CEInvalidConnReq pure $ CRSentConfirmation user pcc incognitoProfile
ACCL SCMContact ccLink -> connectViaContact user incognito ccLink Nothing Nothing Nothing
Connect incognito (Just cLink@(ACL m cLink')) -> withUser $ \user -> do Connect incognito (Just cLink@(ACL m cLink')) -> withUser $ \user -> do
(ccLink, plan) <- connectPlan user cLink `catchChatError` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink (ILPOk Nothing)); _ -> throwError e (ccLink, plan) <- connectPlan user cLink `catchChatError` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink (ILPOk Nothing)); _ -> throwError e
connectWithPlan user incognito ccLink plan connectWithPlan user incognito ccLink plan
@ -2869,9 +2867,9 @@ processChatCommand' vr = \case
CTGroup -> withFastStore $ \db -> getGroupChatItemIdByText' db user cId msg CTGroup -> withFastStore $ \db -> getGroupChatItemIdByText' db user cId msg
CTLocal -> withFastStore $ \db -> getLocalChatItemIdByText' db user cId msg CTLocal -> withFastStore $ \db -> getLocalChatItemIdByText' db user cId msg
_ -> throwCmdError "not supported" _ -> throwCmdError "not supported"
connectViaInvitation :: User -> IncognitoEnabled -> CreatedLinkInvitation -> Maybe ContactId -> CM ChatResponse connectViaInvitation :: User -> IncognitoEnabled -> CreatedLinkInvitation -> Maybe ContactId -> CM (Int64, Maybe Profile)
connectViaInvitation user@User {userId} incognito (CCLink cReq@(CRInvitationUri crData e2e) sLnk_) contactId_ = connectViaInvitation user@User {userId} incognito (CCLink cReq@(CRInvitationUri crData e2e) sLnk_) contactId_ =
withInvitationLock "connect" (strEncode cReq) . procCmd $ do withInvitationLock "connect" (strEncode cReq) $ do
subMode <- chatReadVar subscriptionMode subMode <- chatReadVar subscriptionMode
-- [incognito] generate profile to send -- [incognito] generate profile to send
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
@ -2884,24 +2882,22 @@ processChatCommand' vr = \case
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqs) >>= \case withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqs) >>= \case
Nothing -> joinNewConn chatV dm Nothing -> joinNewConn chatV dm
Just (RcvDirectMsgConnection conn@Connection {connId, connStatus, contactConnInitiated} _ct_) Just (RcvDirectMsgConnection conn@Connection {connId = dbConnId, connStatus, contactConnInitiated} _ct_)
| connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link | connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link
| connStatus == ConnPrepared -> do | connStatus == ConnPrepared -> joinPreparedConn dbConnId (aConnId conn) dm -- retrying join after error
-- retrying join after error
pcc <- withFastStore $ \db -> getPendingContactConnection db userId connId
joinPreparedConn (aConnId conn) pcc dm
Just ent -> throwCmdError $ "connection is not RcvDirectMsgConnection: " <> show (connEntityInfo ent) Just ent -> throwCmdError $ "connection is not RcvDirectMsgConnection: " <> show (connEntityInfo ent)
where where
joinNewConn chatV dm = do joinNewConn chatV dm = do
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup'
let ccLink = CCLink cReq $ serverShortLink <$> sLnk_ let ccLink = CCLink cReq $ serverShortLink <$> sLnk_
pcc <- withFastStore' $ \db -> createDirectConnection db user connId ccLink contactId_ ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup' createdAt <- liftIO getCurrentTime
joinPreparedConn connId pcc dm (dbConnId, _) <- withFastStore' $ \db -> createDirectConnection_ db userId connId ccLink contactId_ ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup' createdAt
joinPreparedConn connId pcc@PendingContactConnection {pccConnId} dm = do joinPreparedConn dbConnId connId dm
joinPreparedConn dbConnId connId dm = do
(sqSecured, _serviceId) <- withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode (sqSecured, _serviceId) <- withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode
let newStatus = if sqSecured then ConnSndReady else ConnJoined let newStatus = if sqSecured then ConnSndReady else ConnJoined
withFastStore' $ \db -> updateConnectionStatusFromTo db pccConnId ConnPrepared newStatus withFastStore' $ \db -> updateConnectionStatusFromTo db dbConnId ConnPrepared newStatus
pure $ CRSentConfirmation user pcc {pccConnStatus = newStatus} incognitoProfile pure (dbConnId, incognitoProfile)
cReqs = cReqs =
( CRInvitationUri crData {crScheme = SSSimplex} e2e, ( CRInvitationUri crData {crScheme = SSSimplex} e2e,
CRInvitationUri crData {crScheme = simplexChat} e2e CRInvitationUri crData {crScheme = simplexChat} e2e
@ -3363,7 +3359,7 @@ processChatCommand' vr = \case
case plan of case plan of
CPContactAddress (CAPContactViaAddress Contact {contactId}) -> CPContactAddress (CAPContactViaAddress Contact {contactId}) ->
processChatCommand $ APIConnectContactViaAddress userId incognito contactId processChatCommand $ APIConnectContactViaAddress userId incognito contactId
_ -> processChatCommand $ APIConnect userId incognito (Just ccLink) _ -> processChatCommand $ APIConnect userId incognito ccLink
| otherwise = pure $ CRConnectionPlan user ccLink plan | otherwise = pure $ CRConnectionPlan user ccLink plan
invitationRequestPlan :: User -> ConnReqInvitation -> Maybe ContactShortLinkData -> CM ConnectionPlan invitationRequestPlan :: User -> ConnReqInvitation -> Maybe ContactShortLinkData -> CM ConnectionPlan
invitationRequestPlan user cReq contactSLinkData_ = do invitationRequestPlan user cReq contactSLinkData_ = do
@ -4480,7 +4476,7 @@ chatCommandP =
"/_connect contact @" *> (APIConnectPreparedContact <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)), "/_connect contact @" *> (APIConnectPreparedContact <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)),
"/_connect group #" *> (APIConnectPreparedGroup <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)), "/_connect group #" *> (APIConnectPreparedGroup <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)),
"/_connect " *> (APIAddContact <$> A.decimal <*> incognitoOnOffP), "/_connect " *> (APIAddContact <$> A.decimal <*> incognitoOnOffP),
"/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> connLinkP_), "/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> connLinkP),
"/_set incognito :" *> (APISetConnectionIncognito <$> A.decimal <* A.space <*> onOffP), "/_set incognito :" *> (APISetConnectionIncognito <$> A.decimal <* A.space <*> onOffP),
"/_set conn user :" *> (APIChangeConnectionUser <$> A.decimal <* A.space <*> A.decimal), "/_set conn user :" *> (APIChangeConnectionUser <$> A.decimal <* A.space <*> A.decimal),
("/connect" <|> "/c") *> (AddContact <$> incognitoP), ("/connect" <|> "/c") *> (AddContact <$> incognitoP),
@ -4598,8 +4594,6 @@ chatCommandP =
cReq <- strP cReq <- strP
sLink_ <- optional (A.space *> strP) sLink_ <- optional (A.space *> strP)
pure $ CCLink cReq sLink_ pure $ CCLink cReq sLink_
connLinkP_ =
((Just <$> connLinkP) <|> A.takeTill (== ' ') $> Nothing)
incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False
incognitoOnOffP = (A.space *> "incognito=" *> onOffP) <|> pure False incognitoOnOffP = (A.space *> "incognito=" *> onOffP) <|> pure False
imagePrefix = (<>) <$> "data:" <*> ("image/png;base64," <|> "image/jpg;base64,") imagePrefix = (<>) <$> "data:" <*> ("image/png;base64," <|> "image/jpg;base64,")

View file

@ -869,6 +869,14 @@ getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of
liftIO $ B.hPut h "" >> hFlush h liftIO $ B.hPut h "" >> hFlush h
| otherwise = liftIO $ B.writeFile fPath "" | otherwise = liftIO $ B.writeFile fPath ""
-- TODO [short links]
-- Please note:
-- - the connection here is created as ConnNew, even though when joining it is created as ConnPrepared.
-- (changing it is risky, as there may be existing "prepared" connections that were not accepted in ConnNew status).
-- - after accepted, the status is changed by this func caller to ConnSndReady if it is sndSecure, and not changed otherwise - joined changed to ConnJoined in this case.
-- - xContactId is set on the contact at the first acceptance attempt, not after accept success, which prevents profile updates after such attempt.
-- It may be reasonable to set it when contact is first prepared, but then we can't use it to ignore requests after acceptance,
-- and it may lead to race conditions with XInfo events.
acceptContactRequest :: User -> UserContactRequest -> IncognitoEnabled -> CM (Contact, Connection, SndQueueSecured) 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 acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId = AgentInvId invId, contactId_, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognito = do
subMode <- chatReadVar subscriptionMode subMode <- chatReadVar subscriptionMode

View file

@ -23,6 +23,7 @@ module Simplex.Chat.Store.Direct
-- * Contacts and connections functions -- * Contacts and connections functions
getPendingContactConnection, getPendingContactConnection,
deletePendingContactConnection, deletePendingContactConnection,
createDirectConnection_,
createDirectConnection, createDirectConnection,
createIncognitoProfile, createIncognitoProfile,
createConnReqConnection, createConnReqConnection,
@ -63,7 +64,6 @@ module Simplex.Chat.Store.Direct
deleteContactRequest, deleteContactRequest,
createContactFromRequest, createContactFromRequest,
createAcceptedContactConn, createAcceptedContactConn,
createAcceptedContact,
updateContactAccepted, updateContactAccepted,
getUserByContactRequestId, getUserByContactRequestId,
getPendingContactConnections, getPendingContactConnections,
@ -235,8 +235,13 @@ getContactByConnReqHash db vr user@User {userId} cReqHash = do
mapM (addDirectChatTags db) ct_ mapM (addDirectChatTags db) ct_
createDirectConnection :: DB.Connection -> User -> ConnId -> CreatedLinkInvitation -> Maybe ContactId -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection createDirectConnection :: DB.Connection -> User -> ConnId -> CreatedLinkInvitation -> Maybe ContactId -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection
createDirectConnection db User {userId} acId ccLink@(CCLink cReq shortLinkInv) contactId_ pccConnStatus incognitoProfile subMode chatV pqSup = do createDirectConnection db User {userId} acId ccLink contactId_ pccConnStatus incognitoProfile subMode chatV pqSup = do
createdAt <- getCurrentTime createdAt <- getCurrentTime
(pccConnId, customUserProfileId) <- createDirectConnection_ db userId acId ccLink contactId_ pccConnStatus incognitoProfile subMode chatV pqSup createdAt
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connLinkInv = Just ccLink, localAlias = "", createdAt, updatedAt = createdAt}
createDirectConnection_ :: DB.Connection -> UserId -> ConnId -> CreatedLinkInvitation -> Maybe ContactId -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> UTCTime -> IO (Int64, Maybe Int64)
createDirectConnection_ db userId acId (CCLink cReq shortLinkInv) contactId_ pccConnStatus incognitoProfile subMode chatV pqSup createdAt = do
customUserProfileId <- mapM (createIncognitoProfile_ db userId createdAt) incognitoProfile customUserProfileId <- mapM (createIncognitoProfile_ db userId createdAt) incognitoProfile
let contactConnInitiated = pccConnStatus == ConnNew let contactConnInitiated = pccConnStatus == ConnNew
DB.execute DB.execute
@ -250,8 +255,8 @@ createDirectConnection db User {userId} acId ccLink@(CCLink cReq shortLinkInv) c
( (userId, acId, cReq, shortLinkInv, pccConnStatus, ConnContact, contactId_, BI contactConnInitiated, customUserProfileId) ( (userId, acId, cReq, shortLinkInv, pccConnStatus, ConnContact, contactId_, BI contactConnInitiated, customUserProfileId)
:. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup) :. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup)
) )
pccConnId <- insertedRowId db dbConnId <- insertedRowId db
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connLinkInv = Just ccLink, localAlias = "", createdAt, updatedAt = createdAt} pure (dbConnId, customUserProfileId)
createIncognitoProfile :: DB.Connection -> User -> Profile -> IO Int64 createIncognitoProfile :: DB.Connection -> User -> Profile -> IO Int64
createIncognitoProfile db User {userId} p = do createIncognitoProfile db User {userId} p = do
@ -777,37 +782,6 @@ createAcceptedContactConn db User {userId} uclId contactId agentConnId connChatV
ExistingIncognito LocalProfile {profileId = pId} -> pure pId ExistingIncognito LocalProfile {profileId = pId} -> pure pId
createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just uclId) customUserProfileId 0 currentTs subMode pqSup createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just uclId) customUserProfileId 0 currentTs subMode pqSup
createAcceptedContact :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> Maybe IncognitoProfile -> SubscriptionMode -> ExceptT StoreError IO (Contact, Connection)
createAcceptedContact
db
vr
user@User {userId}
uclId
agentConnId
connChatVersion
cReqChatVRange
Profile {displayName, fullName, image, contactLink, preferences}
xContactId
pqSup
incognitoProfile
subMode = do
currentTs <- liftIO getCurrentTime
let userPreferences = fromMaybe emptyChatPrefs $ incognitoProfile >> preferences
contactId <- ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do
DB.execute
db
"INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)"
(displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs)
profileId <- insertedRowId db
DB.execute
db
"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, ldn, profileId, BI True, userPreferences, currentTs, currentTs, currentTs, xContactId, BI True)
Right <$> insertedRowId db
conn <- liftIO $ createAcceptedContactConn db user uclId contactId agentConnId connChatVersion cReqChatVRange pqSup incognitoProfile subMode currentTs
ct <- getContact db vr user contactId
pure (ct, conn)
updateContactAccepted :: DB.Connection -> User -> Contact -> Bool -> IO () updateContactAccepted :: DB.Connection -> User -> Contact -> Bool -> IO ()
updateContactAccepted db User {userId} Contact {contactId} contactUsed = updateContactAccepted db User {userId} Contact {contactId} contactUsed =
DB.execute DB.execute

View file

@ -99,7 +99,6 @@ import Data.Type.Equality
import Data.Word (Word32) import Data.Word (Word32)
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Protocol
import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Direct
import Simplex.Chat.Store.Messages import Simplex.Chat.Store.Messages
import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Profiles

View file

@ -118,6 +118,8 @@ chatProfileTests = do
it "prepare contact with image in profile" testShortLinkInvitationImage it "prepare contact with image in profile" testShortLinkInvitationImage
it "prepare contact with a long name in profile" testShortLinkInvitationLongName it "prepare contact with a long name in profile" testShortLinkInvitationLongName
it "prepare contact using address short link data and connect" testShortLinkAddressPrepareContact it "prepare contact using address short link data and connect" testShortLinkAddressPrepareContact
it "prepare contact via invitation and connect after it is deleted" testShortLinkDeletedInvitation
it "prepare contact via address and connect after it is deleted" testShortLinkDeletedAddress
it "prepare business chat using address short link data and connect" testShortLinkAddressPrepareBusiness it "prepare business chat using address short link data and connect" testShortLinkAddressPrepareBusiness
it "prepare group using group short link data and connect" testShortLinkPrepareGroup it "prepare group using group short link data and connect" testShortLinkPrepareGroup
it "prepare group using group short link data and connect, host rejects" testShortLinkPrepareGroupReject it "prepare group using group short link data and connect, host rejects" testShortLinkPrepareGroupReject
@ -3008,6 +3010,44 @@ testShortLinkAddressPrepareContact =
(alice <## "bob (Bob): contact is connected") (alice <## "bob (Bob): contact is connected")
alice <##> bob alice <##> bob
testShortLinkDeletedInvitation :: HasCallStack => TestParams -> IO ()
testShortLinkDeletedInvitation =
testChat2 aliceProfile bobProfile $
\alice bob -> do
alice ##> "/_connect 1"
(shortLink, fullLink) <- getInvitations alice
bob ##> ("/_connect plan 1 " <> shortLink)
bob <## "invitation link: ok to connect"
contactSLinkData <- getTermLine bob
bob ##> ("/_prepare contact 1 " <> fullLink <> " " <> shortLink <> " " <> contactSLinkData)
bob <## "alice: contact is prepared"
alice @@@ [(":1","")]
alice ##> "/_delete :1"
alice <## "connection :1 deleted"
bob ##> "/_connect contact @2"
bob <##. "error: connection authorization failed"
bob ##> "/_connect contact @2"
bob <##. "error: connection authorization failed"
testShortLinkDeletedAddress :: HasCallStack => TestParams -> IO ()
testShortLinkDeletedAddress =
testChat2 aliceProfile bobProfile $
\alice bob -> do
alice ##> "/ad"
(shortLink, fullLink) <- getContactLinks alice True
bob ##> ("/_connect plan 1 " <> shortLink)
bob <## "contact address: ok to connect"
contactSLinkData <- getTermLine bob
bob ##> ("/_prepare contact 1 " <> fullLink <> " " <> shortLink <> " " <> contactSLinkData)
bob <## "alice: contact is prepared"
alice ##> "/da"
alice <## "Your chat address is deleted - accepted contacts will remain connected."
alice <## "To create a new chat address use /ad"
bob ##> "/_connect contact @2"
bob <##. "error: connection authorization failed"
bob ##> "/_connect contact @2"
bob <##. "error: connection authorization failed"
testShortLinkAddressPrepareBusiness :: HasCallStack => TestParams -> IO () testShortLinkAddressPrepareBusiness :: HasCallStack => TestParams -> IO ()
testShortLinkAddressPrepareBusiness = testShortLinkAddressPrepareBusiness =
testChat3 businessProfile aliceProfile {fullName = "Alice @ Biz"} bobProfile $ testChat3 businessProfile aliceProfile {fullName = "Alice @ Biz"} bobProfile $