diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index a0e93340d9..64fba806a9 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -1420,7 +1420,6 @@ struct UserContactLink: Decodable, Hashable { struct AddressSettings: Codable, Hashable { var businessAddress: Bool - var welcomeMessage: String? var autoAccept: AutoAccept? var autoReply: MsgContent? } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 08fcf402e1..5894a9c923 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -701,6 +701,7 @@ struct ComposeView: View { private func sendMemberContactInvitation() { Task { do { + await MainActor.run { hideKeyboard() } if let mc = connectCheckLinkPreview() { await sending() let contact = try await apiSendMemberContactInvitation(chat.chatInfo.apiId, mc) @@ -733,13 +734,14 @@ struct ComposeView: View { ), secondaryButton: empty - ? .cancel(Text("Add message")) { keyboardVisible = true } + ? .cancel(Text("Add message"), action: hideKeyboard) : .cancel() )) } private func sendConnectPreparedContact() { Task { + await MainActor.run { hideKeyboard() } await sending() let mc = connectCheckLinkPreview() if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) { @@ -756,6 +758,7 @@ struct ComposeView: View { private func connectPreparedGroup() { Task { + await MainActor.run { hideKeyboard() } await sending() let mc = connectCheckLinkPreview() if let groupInfo = await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 5d86c472ed..9f1377e4d5 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -371,7 +371,6 @@ struct ToggleShortLinkHeader: View { struct AddressSettingsState: Equatable { var businessAddress = false - var welcomeMessage = "" var autoAccept = false var autoAcceptIncognito = false var autoReply = "" @@ -380,7 +379,6 @@ struct AddressSettingsState: Equatable { init(settings: AddressSettings) { self.businessAddress = settings.businessAddress - self.welcomeMessage = settings.welcomeMessage ?? "" self.autoAccept = settings.autoAccept != nil self.autoAcceptIncognito = settings.autoAccept?.acceptIncognito == true self.autoReply = settings.autoReply?.text ?? "" @@ -389,7 +387,6 @@ struct AddressSettingsState: Equatable { var addressSettings: AddressSettings { AddressSettings( businessAddress: self.businessAddress, - welcomeMessage: self.welcomeMessage.isEmpty ? nil : self.welcomeMessage, autoAccept: self.autoAccept ? AutoAccept(acceptIncognito: self.autoAcceptIncognito) : nil, autoReply: self.autoReply.isEmpty ? nil : MsgContent.text(self.autoReply) ) @@ -463,14 +460,8 @@ struct UserAddressSettingsView: View { autoAcceptToggle().disabled(settings.businessAddress) } - Section { - messageEditor(placeholder: NSLocalizedString("Enter welcome message… (optional)", comment: "placeholder"), text: $settings.welcomeMessage) - } header: { - Text("Welcome message") - .foregroundColor(theme.colors.secondary) - } footer: { - Text("Shown to your contact before connection.") - } + // TODO v6.4.1 move auto-reply editor here + messageEditor(placeholder: NSLocalizedString("Enter welcome message… (optional)", comment: "placeholder"), text: $settings.autoReply) if settings.autoAccept { autoAcceptSection() @@ -554,7 +545,8 @@ struct UserAddressSettingsView: View { if !ChatModel.shared.addressShortLinkDataSet && !settings.businessAddress { acceptIncognitoToggle() } - messageEditor(placeholder: NSLocalizedString("Enter auto-reply message… (optional)", comment: "placeholder"), text: $settings.autoReply) + // TODO v6.4.1 show this message editor even with auto-accept disabled + messageEditor(placeholder: NSLocalizedString("Enter welcome message… (optional)", comment: "placeholder"), text: $settings.autoReply) } header: { Text("Auto-accept") .foregroundColor(theme.colors.secondary) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 9858b92350..c1ea3946a8 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2217,7 +2217,7 @@ public enum MemberCriteria: String, Codable, Identifiable, Hashable { public struct ContactShortLinkData: Codable, Hashable { public var profile: Profile - public var message: String? + public var message: MsgContent? public var business: Bool } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index a893c50de1..8ead5c2292 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -521,7 +521,7 @@ object ChatModel { } } } - + private fun chatItemBelongsToScope(cInfo: ChatInfo, cItem: ChatItem): Boolean = when (secondaryContextFilter) { null -> @@ -2063,7 +2063,7 @@ enum class MemberCriteria { @Serializable data class ContactShortLinkData ( val profile: Profile, - val message: String?, + val message: MsgContent?, val business: Boolean ) diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index c2e2d0c3a3..ff14dae6db 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -64,7 +64,7 @@ initializeBotAddress' logAddress cc = do when logAddress $ do putStrLn $ "Bot's contact address is: " <> B.unpack (maybe (strEncode uri) strEncode shortUri) when (isJust shortUri) $ putStrLn $ "Full contact address for old clients: " <> B.unpack (strEncode uri) - let settings = AddressSettings {businessAddress = False, welcomeMessage = Nothing, autoAccept = Just AutoAccept {acceptIncognito = False}, autoReply = Nothing} + let settings = AddressSettings {businessAddress = False, autoAccept = Just AutoAccept {acceptIncognito = False}, autoReply = Nothing} void $ sendChatCmd cc $ SetAddressSettings settings sendMessage :: ChatController -> Contact -> Text -> IO () diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index c5dafe4a8c..e86fc3cea0 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -72,6 +72,7 @@ import Simplex.Chat.Library.Internal import Simplex.Chat.Stats import Simplex.Chat.Store import Simplex.Chat.Store.AppSettings +import Simplex.Chat.Store.ContactRequest import Simplex.Chat.Store.Connections import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Files @@ -1153,10 +1154,10 @@ processChatCommand' vr = \case uclId <- getUserContactLinkIdByCReq db connReqId uclGLinkInfo <- getUserContactLinkById db userId uclId pure (uclId, uclGLinkInfo) - let UserContactLink {shortLinkDataSet} = ucl + let UserContactLink {shortLinkDataSet, addressSettings} = ucl when (shortLinkDataSet && incognito) $ throwCmdError "incognito not allowed for address with short link data" withUserContactLock "acceptContact" uclId $ do - cReq <- withFastStore $ \db -> getContactRequest db user connReqId + cReq@UserContactRequest {welcomeSharedMsgId} <- withFastStore $ \db -> getContactRequest db user connReqId (ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito let contactUsed = isNothing gLinkInfo_ ct' <- withStore' $ \db -> do @@ -1166,6 +1167,13 @@ processChatCommand' vr = \case then conn {connStatus = ConnSndReady} <$ updateConnectionStatusFromTo db connId ConnNew ConnSndReady else pure conn pure ct {contactUsed, activeConn = Just conn'} + when sqSecured $ forM_ (autoReply addressSettings) $ \mc -> case welcomeSharedMsgId of + Just smId -> + void $ sendDirectContactMessage user ct' $ XMsgUpdate smId mc M.empty Nothing Nothing Nothing + Nothing -> do + (msg, _) <- sendDirectContactMessage user ct' $ XMsgNew $ MCSimple $ extMsgContent mc Nothing + ci <- saveSndChatItem user (CDDirectSnd ct') msg (CISndMsgContent mc) + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci] pure $ CRAcceptingContactRequest user ct' APIRejectContact connReqId -> withUser $ \user -> do userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId @@ -1748,7 +1756,7 @@ processChatCommand' vr = \case createItem sharedMsgId content = createChatItem user cd True content sharedMsgId Nothing cInfo = GroupChat gInfo Nothing void $ createGroupFeatureItems_ user cd True CIRcvGroupFeature gInfo - aci <- mapM (createItem welcomeSharedMsgId . CIRcvMsgContent . MCText) message + aci <- mapM (createItem welcomeSharedMsgId . CIRcvMsgContent) message let chat = case aci of Just (AChatItem SCTGroup dir _ ci) -> Chat cInfo [CChatItem dir ci] emptyChatStats {unreadCount = 1, minUnreadItemId = chatItemId' ci} _ -> Chat cInfo [] emptyChatStats @@ -1759,7 +1767,7 @@ processChatCommand' vr = \case cInfo = DirectChat ct void $ createItem Nothing $ CIRcvDirectE2EEInfo $ E2EInfo $ connRequestPQEncryption cReq void $ createFeatureEnabledItems_ user ct - aci <- mapM (createItem welcomeSharedMsgId . CIRcvMsgContent . MCText) message + aci <- mapM (createItem welcomeSharedMsgId . CIRcvMsgContent) message let chat = case aci of Just (AChatItem SCTDirect dir _ ci) -> Chat cInfo [CChatItem dir ci] emptyChatStats {unreadCount = 1, minUnreadItemId = chatItemId' ci} _ -> Chat cInfo [] emptyChatStats @@ -1804,9 +1812,13 @@ processChatCommand' vr = \case ci <- saveSndChatItem user (CDDirectSnd ct') msg (CISndMsgContent mc) toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci] pure $ CRStartedConnectionToContact user ct' customUserProfile - Just PreparedContact {connLinkToConnect = ACCL SCMContact ccLink, welcomeSharedMsgId} -> do - -- TODO [short links] reuse welcomeSharedMsgId - msg_ <- forM msgContent_ $ \mc -> (,mc) <$> getSharedMsgId + Just PreparedContact {connLinkToConnect = ACCL SCMContact ccLink, welcomeSharedMsgId, requestSharedMsgId} -> do + msg_ <- forM msgContent_ $ \mc -> case requestSharedMsgId of + Just smId -> pure (smId, mc) + Nothing -> do + smId <- getSharedMsgId + withFastStore' $ \db -> setRequestSharedMsgIdForContact db contactId smId + pure (smId, mc) connectViaContact user incognito ccLink welcomeSharedMsgId msg_ (Just $ ACCGContact contactId) >>= \case CRSentInvitation {customUserProfile} -> do -- get updated contact with connection @@ -1820,12 +1832,20 @@ processChatCommand' vr = \case (gInfo, hostMember) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user groupId <*> getHostMember db vr user groupId case preparedGroup gInfo of Nothing -> throwCmdError "group doesn't have link to connect" - Just PreparedGroup {connLinkToConnect} -> - -- TODO [short links] store request message with shared message ID (for business chat) - connectViaContact user incognito connLinkToConnect Nothing Nothing (Just $ ACCGGroup gInfo (groupMemberId' hostMember)) >>= \case + Just PreparedGroup {connLinkToConnect, welcomeSharedMsgId, requestSharedMsgId} -> do + msg_ <- forM msgContent_ $ \mc -> case requestSharedMsgId of + Just smId -> pure (smId, mc) + Nothing -> do + smId <- getSharedMsgId + withFastStore' $ \db -> setRequestSharedMsgIdForGroup db groupId smId + pure (smId, mc) + connectViaContact user incognito connLinkToConnect welcomeSharedMsgId msg_ (Just $ ACCGGroup gInfo $ groupMemberId' hostMember) >>= \case CRSentInvitation {customUserProfile} -> do -- get updated group info (connLinkStartedConnection and incognito membership) gInfo' <- withFastStore $ \db -> getGroupInfo db vr user groupId + forM_ msg_ $ \(sharedMsgId, mc) -> do + ci <- createChatItem user (CDGroupSnd gInfo' Nothing) False (CISndMsgContent mc) (Just sharedMsgId) Nothing + toView $ CEvtNewChatItems user [ci] pure $ CRStartedConnectionToGroup user gInfo' customUserProfile cr -> pure cr APIConnect userId incognito acl -> withUserId userId $ \user -> case acl of @@ -3461,7 +3481,7 @@ processChatCommand' vr = \case contactShortLinkData :: Profile -> Maybe AddressSettings -> CM UserLinkData contactShortLinkData p settings = do large <- chatReadVar useLargeLinkData - let msg = welcomeMessage =<< settings + let msg = autoReply =<< settings business = maybe False businessAddress settings contactData | large = ContactShortLinkData p msg business @@ -4769,10 +4789,10 @@ chatCommandP = dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False} #endif -- TODO [short links] parser for address settings - autoAcceptP = ifM onOffP (businessAA <|> addressAA) (pure $ AddressSettings False Nothing Nothing Nothing) + autoAcceptP = ifM onOffP (businessAA <|> addressAA) (pure $ AddressSettings False Nothing Nothing) where - addressAA = AddressSettings False Nothing <$> (Just . AutoAccept <$> (" incognito=" *> onOffP <|> pure False)) <*> autoReply - businessAA = " business" *> (AddressSettings True Nothing (Just $ AutoAccept False) <$> autoReply) + addressAA = AddressSettings False <$> (Just . AutoAccept <$> (" incognito=" *> onOffP <|> pure False)) <*> autoReply + businessAA = " business" *> (AddressSettings True (Just $ AutoAccept False) <$> autoReply) autoReply = optional (A.space *> msgContentP) rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P)) text1P = safeDecodeUtf8 <$> A.takeTill (== ' ') diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 810af2fe0e..32ee6ecc2b 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -36,7 +36,7 @@ import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, mapMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1) -import Data.Time.Clock (UTCTime, diffUTCTime) +import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime) import qualified Data.UUID as UUID import qualified Data.UUID.V4 as V4 import Data.Word (Word32) @@ -580,9 +580,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createFeatureEnabledItems user ct' (Just PreparedContact {connLinkToConnect = ACCL _ (CCLink cReq _)}, _) -> unless (Just pqEnc == connRequestPQEncryption cReq) createE2EItem - (_, Just connReqId) -> do - UserContactRequest {pqSupport} <- withStore $ \db -> getContactRequest db user connReqId - unless (CR.pqSupportToEnc pqSupport == pqEnc) createE2EItem + (_, Just connReqId) -> + withStore' (\db -> getContactRequest' db user connReqId) >>= \case + Just UserContactRequest {pqSupport} | CR.pqSupportToEnc pqSupport == pqEnc -> pure () + _ -> createE2EItem when (contactConnInitiated conn') $ do let Connection {groupLinkId} = conn' doProbeContacts = isJust groupLinkId @@ -590,7 +591,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> resetContactConnInitiated db user conn' forM_ viaUserContactLink $ \userContactLinkId -> do (ucl, gli_) <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId - when (connChatVersion < batchSend2Version) $ sendAutoReply ucl ct' + -- let UserContactLink {addressSettings = AddressSettings {autoReply}} = ucl + when (connChatVersion < batchSend2Version) $ forM_ (autoReply $ addressSettings ucl) $ \mc -> sendAutoReply ct' mc Nothing -- old versions only -- TODO REMOVE LEGACY vvv forM_ gli_ $ \GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do groupInfo <- withStore $ \db -> getGroupInfo db vr user groupId @@ -656,9 +658,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (directOrUsed ct && sqSecured) $ do lift $ setContactNetworkStatus ct NSConnected toView $ CEvtContactSndReady user ct - forM_ viaUserContactLink $ \userContactLinkId -> do + when (connChatVersion >= batchSend2Version) $ forM_ viaUserContactLink $ \userContactLinkId -> do (ucl, _) <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId - when (connChatVersion >= batchSend2Version) $ sendAutoReply ucl ct + forM_ (autoReply $ addressSettings ucl) $ \mc -> do + connReq_ <- pure (contactRequestId' ct) $>>= \connReqId -> withStore' (\db -> getContactRequest' db user connReqId) + sendAutoReply ct mc connReq_ QCONT -> void $ continueSending connEntity conn MWARN msgId err -> do @@ -678,9 +682,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO add debugging output _ -> pure () where - sendAutoReply UserContactLink {addressSettings = AddressSettings {autoReply}} ct = - forM_ autoReply $ \mc -> do - (msg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) + sendAutoReply ct mc = \case + Just UserContactRequest {welcomeSharedMsgId = Just smId} -> + void $ sendDirectContactMessage user ct $ XMsgUpdate smId mc M.empty Nothing Nothing Nothing + _ -> do + (msg, _) <- sendDirectContactMessage user ct $ XMsgNew $ MCSimple $ extMsgContent mc Nothing ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc) toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] @@ -808,9 +814,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = (gInfo'', m'', scopeInfo) <- mkGroupChatScope gInfo' m' let cd = CDGroupRcv gInfo'' scopeInfo m'' createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing - createGroupFeatureItems user cd CIRcvGroupFeature gInfo'' + let business = isJust $ businessChat gInfo'' + unless business $ createGroupFeatureItems user cd CIRcvGroupFeature gInfo'' memberConnectedChatItem gInfo'' scopeInfo m'' - unless (memberPending membership) $ maybeCreateGroupDescrLocal gInfo'' m'' + unless (memberPending membership || business) $ maybeCreateGroupDescrLocal gInfo'' m'' GCInviteeMember -> do (gInfo', mStatus) <- if not (memberPending m) @@ -829,7 +836,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CEvtJoinedGroupMember user gInfo'' m' {memberStatus = mStatus} let Connection {viaUserContactLink} = conn when (isJust viaUserContactLink && isNothing (memberContactId m')) $ sendXGrpLinkMem gInfo'' - when (connChatVersion < batchSend2Version) sendGroupAutoReply + when (connChatVersion < batchSend2Version) $ getAutoReplyMsg >>= mapM_ (\mc -> sendGroupAutoReply mc Nothing) case mStatus of GSMemPendingApproval -> pure () GSMemPendingReview -> introduceToModerators vr user gInfo'' m' @@ -1011,7 +1018,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = JOINED sqSecured _serviceId -> -- [async agent commands] continuation on receiving JOINED when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> - when (sqSecured && connChatVersion >= batchSend2Version) sendGroupAutoReply + when (sqSecured && connChatVersion >= batchSend2Version) $ do + mc_ <- getAutoReplyMsg + forM_ mc_ $ \mc -> do + connReq_ <- withStore' $ \db -> getBusinessContactRequest db user groupId + sendGroupAutoReply mc connReq_ QCONT -> do continued <- continueSending connEntity conn when continued $ sendPendingGroupMessages user m conn @@ -1039,22 +1050,23 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateGroupItemsErrorStatus db msgId groupMemberId newStatus = do itemIds <- getChatItemIdsByAgentMsgId db connId msgId forM_ itemIds $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus - sendGroupAutoReply = autoReplyMC >>= mapM_ send - where - autoReplyMC = do - let GroupInfo {businessChat} = gInfo - GroupMember {memberId = joiningMemberId} = m - case businessChat of - Just BusinessChatInfo {customerId, chatType = BCCustomer} - | joiningMemberId == customerId -> useReply <$> withStore (`getUserAddress` user) - where - useReply UserContactLink {addressSettings = AddressSettings {autoReply}} = autoReply - _ -> pure Nothing - send mc = do - msg <- sendGroupMessage' user gInfo [m] (XMsgNew $ MCSimple (extMsgContent mc Nothing)) - ci <- saveSndChatItem user (CDGroupSnd gInfo Nothing) msg (CISndMsgContent mc) - withStore' $ \db -> createGroupSndStatus db (chatItemId' ci) (groupMemberId' m) GSSNew - toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo Nothing) ci] + getAutoReplyMsg = do + let GroupInfo {businessChat} = gInfo + GroupMember {memberId = joiningMemberId} = m + case businessChat of + Just BusinessChatInfo {customerId, chatType = BCCustomer} + | joiningMemberId == customerId -> useReply <$> withStore (`getUserAddress` user) + where + useReply UserContactLink {addressSettings = AddressSettings {autoReply}} = autoReply + _ -> pure Nothing + sendGroupAutoReply mc = \case + Just UserContactRequest {welcomeSharedMsgId = Just smId} -> + void $ sendGroupMessage' user gInfo [m] $ XMsgUpdate smId mc M.empty Nothing Nothing Nothing + _ -> do + msg <- sendGroupMessage' user gInfo [m] $ XMsgNew $ MCSimple $ extMsgContent mc Nothing + ci <- saveSndChatItem user (CDGroupSnd gInfo Nothing) msg (CISndMsgContent mc) + withStore' $ \db -> createGroupSndStatus db (chatItemId' ci) (groupMemberId' m) GSSNew + toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo Nothing) ci] agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32) agentMsgDecryptError = \case @@ -1255,56 +1267,121 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = REBusinessChat gInfo _clientMember -> -- TODO [short links] update request msg toView $ CEvtBusinessRequestAlreadyAccepted user gInfo - RSCurrentRequest ucr re_ repeatRequest -> case re_ of + RSCurrentRequest prevUcr_ ucr@UserContactRequest {welcomeSharedMsgId} re_ -> case re_ of Nothing -> toView $ CEvtReceivedContactRequest user ucr Nothing Just (REContact ct) -> do - -- TODO [short links] prevent duplicate items - -- update welcome message if changed (send update event to UI) and add updated feature items. - -- Do not created e2e item on repeat request - if repeatRequest - then do - -- TODO [short links] update request msg - -- .... - acceptOrShow Nothing -- pass item? - else do - -- TODO [short links] save sharedMsgId instead of the last Nothing - let createItem content = createChatItem user (CDDirectRcv ct) False content Nothing Nothing - void $ createItem $ CIRcvDirectE2EEInfo $ E2EInfo $ Just $ CR.pqSupportToEnc $ reqPQSup + let cd = CDDirectRcv ct + aci_ <- case prevUcr_ of + Just UserContactRequest {requestSharedMsgId = prevSharedMsgId_} -> + -- TODO [short links] this branch does not update feature items and e2e items, as they are highly unlikely to change + -- they will be updated after connection is accepted. + upsertDirectRequestItem cd (requestMsg_, prevSharedMsgId_) + Nothing -> do + let e2eContent = CIRcvDirectE2EEInfo $ E2EInfo $ Just $ CR.pqSupportToEnc $ reqPQSup + void $ createChatItem user cd False e2eContent Nothing Nothing void $ createFeatureEnabledItems_ user ct - -- TODO [short links] save sharedMsgId - aci <- forM requestMsg_ $ \(sharedMsgId, mc) -> do - aci <- createItem $ CIRcvMsgContent mc - unlessM (asks $ coreApi . config) $ toView $ CEvtNewChatItems user [aci] - pure aci - acceptOrShow aci - where - acceptOrShow aci_ = - case autoAccept of - Nothing -> do - let cInfo = DirectChat ct - chat = AChat SCTDirect $ case aci_ of - Just (AChatItem SCTDirect dir _ ci) -> Chat cInfo [CChatItem dir ci] emptyChatStats {unreadCount = 1, minUnreadItemId = chatItemId' ci} - _ -> Chat cInfo [] emptyChatStats - toView $ CEvtReceivedContactRequest user ucr (Just chat) - Just AutoAccept {acceptIncognito} -> do - incognitoProfile <- - if not shortLinkDataSet && acceptIncognito - then Just . NewIncognito <$> liftIO generateRandomProfile - else pure Nothing - ct' <- acceptContactRequestAsync user uclId ct ucr incognitoProfile - -- chat in event? - toView $ CEvtAcceptingContactRequest user ct' + forM_ (autoReply addressSettings) $ \mc -> forM_ welcomeSharedMsgId $ \sharedMsgId -> + createChatItem user (CDDirectSnd ct) False (CISndMsgContent mc) (Just sharedMsgId) Nothing + mapM (createRequestItem cd) requestMsg_ + case autoAccept of + Nothing -> do + let cInfo = DirectChat ct + chat = AChat SCTDirect $ case aci_ of + Just (AChatItem SCTDirect dir _ ci) -> Chat cInfo [CChatItem dir ci] emptyChatStats {unreadCount = 1, minUnreadItemId = chatItemId' ci} + _ -> Chat cInfo [] emptyChatStats + toView $ CEvtReceivedContactRequest user ucr $ Just chat + Just AutoAccept {acceptIncognito} -> do + incognitoProfile <- + if not shortLinkDataSet && acceptIncognito + then Just . NewIncognito <$> liftIO generateRandomProfile + else pure Nothing + ct' <- acceptContactRequestAsync user uclId ct ucr incognitoProfile + toView $ CEvtAcceptingContactRequest user ct' Just (REBusinessChat gInfo clientMember) -> do - -- TODO [short links] prevent duplicate items (use repeatRequest like for REContact) (_gInfo', _clientMember') <- acceptBusinessJoinRequestAsync user uclId gInfo clientMember ucr - -- TODO [short links] add welcome message if welcomeMsgId is present - -- forM_ autoReply $ \arMC -> - -- when (shortLinkDataSet && v >= shortLinkDataVersion) $ - -- createInternalChatItem user (CDGroupSnd gInfo Nothing) (CISndMsgContent arMC) Nothing - -- TODO [short links] save sharedMsgId - forM_ requestMsg_ $ \(sharedMsgId, mc) -> - createInternalChatItem user (CDGroupRcv gInfo Nothing clientMember) (CIRcvMsgContent mc) Nothing + let cd = CDGroupRcv gInfo Nothing clientMember + void $ case prevUcr_ of + Just UserContactRequest {requestSharedMsgId = prevSharedMsgId_} -> + -- TODO [short links] this branch does not update feature items and e2e items, as they are highly unlikely to change + -- they will be updated after connection is accepted. + upsertBusinessRequestItem cd (requestMsg_, prevSharedMsgId_) + Nothing -> do + -- TODO [short links] possibly, we can just keep them created where they are created on the business side due to auto-accept + -- let e2eContent = CIRcvGroupE2EEInfo $ E2EInfo $ Just False -- no PQ encryption in groups + -- void $ createChatItem user cd False e2eContent Nothing Nothing + -- void $ createFeatureEnabledItems_ user ct + forM_ (autoReply addressSettings) $ \arMC -> forM_ welcomeSharedMsgId $ \sharedMsgId -> + createChatItem user (CDGroupSnd gInfo Nothing) False (CISndMsgContent arMC) (Just sharedMsgId) Nothing + mapM (createRequestItem cd) requestMsg_ toView $ CEvtAcceptingBusinessRequest user gInfo + where + upsertDirectRequestItem :: ChatDirection 'CTDirect 'MDRcv -> (Maybe (SharedMsgId, MsgContent), Maybe SharedMsgId) -> CM (Maybe AChatItem) + upsertDirectRequestItem cd@(CDDirectRcv ct@Contact {contactId}) = upsertRequestItem cd updateRequestItem markRequestItemDeleted + where + updateRequestItem (sharedMsgId, mc) = + withStore (\db -> getDirectChatItemBySharedMsgId db user contactId sharedMsgId) >>= \case + CChatItem SMDRcv ci@ChatItem {content = CIRcvMsgContent oldMC} + | mc /= oldMC -> do + currentTs <- liftIO getCurrentTime + aci <- withStore' $ \db -> do + addInitialAndNewCIVersions db (chatItemId' ci) (chatItemTs' ci, oldMC) (currentTs, mc) + aChatItem <$> updateDirectChatItem' db user contactId ci (CIRcvMsgContent mc) True False Nothing Nothing + toView $ CEvtChatItemUpdated user aci + pure $ Just aci + | otherwise -> pure $ Just $ aChatItem ci + _ -> pure Nothing + where + aChatItem = AChatItem SCTDirect SMDRcv (DirectChat ct) + markRequestItemDeleted sharedMsgId = + withStore' (\db -> runExceptT $ getDirectChatItemBySharedMsgId db user contactId sharedMsgId) >>= \case + Right (cci@(CChatItem SMDRcv _)) -> do + currentTs <- liftIO getCurrentTime + deletions <- if featureAllowed SCFFullDelete forContact ct + then deleteDirectCIs user ct [cci] + else markDirectCIsDeleted user ct [cci] currentTs + toView $ CEvtChatItemsDeleted user deletions False False + _ -> pure () + upsertBusinessRequestItem :: ChatDirection 'CTGroup 'MDRcv -> (Maybe (SharedMsgId, MsgContent), Maybe SharedMsgId) -> CM (Maybe AChatItem) + upsertBusinessRequestItem cd@(CDGroupRcv gInfo@GroupInfo {groupId} _ clientMember) = upsertRequestItem cd updateRequestItem markRequestItemDeleted + where + updateRequestItem (sharedMsgId, mc) = + withStore (\db -> getGroupChatItemBySharedMsgId db user gInfo (groupMemberId' clientMember) sharedMsgId) >>= \case + CChatItem SMDRcv ci@ChatItem {chatDir = CIGroupRcv m', content = CIRcvMsgContent oldMC} + | sameMemberId (memberId' clientMember) m' -> + if mc /= oldMC + then do + currentTs <- liftIO getCurrentTime + aci <- withStore' $ \db -> do + addInitialAndNewCIVersions db (chatItemId' ci) (chatItemTs' ci, oldMC) (currentTs, mc) + aChatItem <$> updateGroupChatItem db user groupId ci (CIRcvMsgContent mc) True False Nothing + toView $ CEvtChatItemUpdated user aci + pure $ Just aci + else pure $ Just $ aChatItem ci + _ -> pure Nothing + where + aChatItem = AChatItem SCTGroup SMDRcv (GroupChat gInfo Nothing) + markRequestItemDeleted sharedMsgId = + withStore' (\db -> runExceptT $ getGroupMemberCIBySharedMsgId db user gInfo (memberId' clientMember) sharedMsgId) >>= \case + Right cci@(CChatItem SMDRcv ChatItem {chatDir = CIGroupRcv m'}) + | sameMemberId (memberId' clientMember) m' -> do + currentTs <- liftIO getCurrentTime + deletions <- if groupFeatureMemberAllowed SGFFullDelete clientMember gInfo + then deleteGroupCIs user gInfo Nothing [cci] Nothing currentTs + else markGroupCIsDeleted user gInfo Nothing [cci] Nothing currentTs + toView $ CEvtChatItemsDeleted user deletions False False + _ -> pure () + createRequestItem :: ChatTypeI c => ChatDirection c 'MDRcv -> (SharedMsgId, MsgContent) -> CM AChatItem + createRequestItem cd (sharedMsgId, mc) = do + aci <- createChatItem user cd False (CIRcvMsgContent mc) (Just sharedMsgId) Nothing + toView $ CEvtNewChatItems user [aci] + pure aci + upsertRequestItem :: ChatTypeI c => ChatDirection c 'MDRcv -> ((SharedMsgId, MsgContent) -> CM (Maybe AChatItem)) -> (SharedMsgId -> CM ()) -> (Maybe (SharedMsgId, MsgContent), Maybe SharedMsgId) -> CM (Maybe AChatItem) + upsertRequestItem cd update delete = \case + (Just msg, Nothing) -> Just <$> createRequestItem cd msg + (Just msg@(sharedMsgId, _), Just prevSharedMsgId) | sharedMsgId == prevSharedMsgId -> + update msg `catchCINotFound` \_ -> Just <$> createRequestItem cd msg + (Nothing, Just prevSharedMsgId) -> Nothing <$ delete prevSharedMsgId + _ -> pure Nothing -- ##### Group link join requests (don't create contact requests) ##### Just gli@GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do -- TODO [short links] deduplicate request by xContactId? diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 5a0518aae8..1fad623967 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -1224,3 +1224,19 @@ instance ToJSON (ChatMessage 'Json) where instance FromJSON (ChatMessage 'Json) where parseJSON v = appJsonToCM <$?> parseJSON v + +data ContactShortLinkData = ContactShortLinkData + { profile :: Profile, + message :: Maybe MsgContent, + business :: Bool + } + deriving (Show) + +data GroupShortLinkData = GroupShortLinkData + { groupProfile :: GroupProfile + } + deriving (Show) + +$(JQ.deriveJSON defaultJSON ''ContactShortLinkData) + +$(JQ.deriveJSON defaultJSON ''GroupShortLinkData) diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 90df45357f..0a19daf751 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -112,7 +112,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do [sql| SELECT c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, - p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.contact_request_id, + p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, c.contact_group_member_id, c.contact_grp_inv_sent, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl FROM contacts c JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id @@ -140,7 +140,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupInfo {membership} diff --git a/src/Simplex/Chat/Store/ContactRequest.hs b/src/Simplex/Chat/Store/ContactRequest.hs index 9fae87eaa1..a56c89102b 100644 --- a/src/Simplex/Chat/Store/ContactRequest.hs +++ b/src/Simplex/Chat/Store/ContactRequest.hs @@ -12,7 +12,9 @@ module Simplex.Chat.Store.ContactRequest ( createOrUpdateContactRequest, setContactAcceptedXContactId, - setBusinessChatAcceptedXContactId + setBusinessChatAcceptedXContactId, + setRequestSharedMsgIdForContact, + setRequestSharedMsgIdForGroup ) where @@ -81,11 +83,14 @@ createOrUpdateContactRequest Just xContactId -> -- 1) first we try to find accepted contact or business chat by xContactId liftIO (getAcceptedContact xContactId) >>= \case - Just ct -> pure $ RSAcceptedRequest Nothing (REContact ct) + Just ct -> do + cr <- liftIO $ getContactRequestByXContactId xContactId + pure $ RSAcceptedRequest cr (REContact ct) Nothing -> liftIO (getAcceptedBusinessChat xContactId) >>= \case Just gInfo@GroupInfo {businessChat = Just BusinessChatInfo {customerId}} -> do clientMember <- getGroupMemberByMemberId db vr user gInfo customerId - pure $ RSAcceptedRequest Nothing (REBusinessChat gInfo clientMember) + cr <- liftIO $ getContactRequestByXContactId xContactId + pure $ RSAcceptedRequest cr (REBusinessChat gInfo clientMember) Just GroupInfo {businessChat = Nothing} -> throwError SEInvalidBusinessChatContactRequest -- 2) if no legacy accepted contact or business chat was found, next we try to find an existing request Nothing -> @@ -105,7 +110,7 @@ createOrUpdateContactRequest SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.contact_request_id, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, @@ -196,7 +201,7 @@ createOrUpdateContactRequest (contactId, contactRequestId) ucr <- getContactRequest db user contactRequestId ct <- getContact db vr user contactId - pure $ RSCurrentRequest ucr (Just $ REContact ct) False + pure $ RSCurrentRequest Nothing ucr (Just $ REContact ct) createBusinessChat = do let Profile {preferences = userPreferences} = profileToSendOnAccept user Nothing True groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs userPreferences @@ -208,15 +213,15 @@ createOrUpdateContactRequest "UPDATE contact_requests SET business_group_id = ? WHERE contact_request_id = ?" (groupId, contactRequestId) ucr <- getContactRequest db user contactRequestId - pure $ RSCurrentRequest ucr (Just $ REBusinessChat gInfo clientMember) False + pure $ RSCurrentRequest Nothing ucr (Just $ REBusinessChat gInfo clientMember) updateContactRequest :: UserContactRequest -> ExceptT StoreError IO RequestStage - updateContactRequest UserContactRequest {contactRequestId, contactId_, localDisplayName = oldLdn, profile = Profile {displayName = oldDisplayName}} = do + updateContactRequest ucr@UserContactRequest {contactRequestId, contactId_, localDisplayName = oldLdn, profile = Profile {displayName = oldDisplayName}} = do currentTs <- liftIO getCurrentTime liftIO $ updateProfile currentTs updateRequest currentTs ucr' <- getContactRequest db user contactRequestId re_ <- getRequestEntity ucr' - pure $ RSCurrentRequest ucr' re_ True + pure $ RSCurrentRequest (Just ucr) ucr' re_ where updateProfile currentTs = DB.execute @@ -288,12 +293,16 @@ createOrUpdateContactRequest setContactAcceptedXContactId :: DB.Connection -> Contact -> XContactId -> IO () setContactAcceptedXContactId db Contact {contactId} xContactId = - DB.execute - db "UPDATE contacts SET xcontact_id = ? WHERE contact_id = ?" - (xContactId, contactId) + DB.execute db "UPDATE contacts SET xcontact_id = ? WHERE contact_id = ?" (xContactId, contactId) setBusinessChatAcceptedXContactId :: DB.Connection -> GroupInfo -> XContactId -> IO () setBusinessChatAcceptedXContactId db GroupInfo {groupId} xContactId = - DB.execute - db "UPDATE groups SET business_xcontact_id = ? WHERE group_id = ?" - (xContactId, groupId) + DB.execute db "UPDATE groups SET business_xcontact_id = ? WHERE group_id = ?" (xContactId, groupId) + +setRequestSharedMsgIdForContact :: DB.Connection -> ContactId -> SharedMsgId -> IO () +setRequestSharedMsgIdForContact db contactId sharedMsgId = do + DB.execute db "UPDATE contacts SET request_shared_msg_id = ? WHERE contact_id = ?" (sharedMsgId, contactId) + +setRequestSharedMsgIdForGroup :: DB.Connection -> GroupId -> SharedMsgId -> IO () +setRequestSharedMsgIdForGroup db groupId sharedMsgId = do + DB.execute db "UPDATE groups SET request_shared_msg_id = ? WHERE group_id = ?" (sharedMsgId, groupId) diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 79012d29fb..1b6f57b9f5 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -60,6 +60,8 @@ module Simplex.Chat.Store.Direct getUserContacts, getUserContactLinkIdByCReq, getContactRequest, + getContactRequest', + getBusinessContactRequest, getContactRequestIdByName, deleteContactRequest, createContactFromRequest, @@ -108,10 +110,10 @@ import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Crypto.Ratchet (PQSupport) import Simplex.Messaging.Protocol (SubscriptionMode (..)) #if defined(dbPostgres) -import Database.PostgreSQL.Simple (Only (..), (:.) (..)) +import Database.PostgreSQL.Simple (Only (..), Query, (:.) (..)) import Database.PostgreSQL.Simple.SqlQQ (sql) #else -import Database.SQLite.Simple (Only (..), (:.) (..)) +import Database.SQLite.Simple (Only (..), Query, (:.) (..)) import Database.SQLite.Simple.QQ (sql) #endif @@ -218,7 +220,7 @@ getContactByConnReqHash db vr user@User {userId} cReqHash = do SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.contact_request_id, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, @@ -286,7 +288,7 @@ createPreparedContact db user@User {userId} p@Profile {preferences} connLinkToCo createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, - preparedContact = Just $ PreparedContact connLinkToConnect (connMode m) welcomeSharedMsgId, + preparedContact = Just PreparedContact {connLinkToConnect, uiConnLinkType = connMode m, welcomeSharedMsgId, requestSharedMsgId = Nothing}, contactRequestId = Nothing, contactGroupMemberId = Nothing, contactGrpInvSent = False, @@ -687,23 +689,32 @@ getUserContactLinkIdByCReq db contactRequestId = getContactRequest :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO UserContactRequest getContactRequest db User {userId} contactRequestId = ExceptT . firstRow toContactRequest (SEContactRequestNotFound contactRequestId) $ - DB.query - db - [sql| - SELECT - cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, - cr.contact_id, cr.business_group_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, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, - cr.created_at, cr.updated_at, - cr.peer_chat_min_version, cr.peer_chat_max_version - FROM contact_requests cr - JOIN connections c USING (user_contact_link_id) - JOIN contact_profiles p USING (contact_profile_id) - WHERE cr.user_id = ? - AND cr.contact_request_id = ? - |] - (userId, contactRequestId) + DB.query db (contactRequestQuery <> " WHERE cr.user_id = ? AND cr.contact_request_id = ?") (userId, contactRequestId) + +getContactRequest' :: DB.Connection -> User -> Int64 -> IO (Maybe UserContactRequest) +getContactRequest' db User {userId} contactRequestId = + maybeFirstRow toContactRequest $ + DB.query db (contactRequestQuery <> " WHERE cr.user_id = ? AND cr.contact_request_id = ?") (userId, contactRequestId) + +getBusinessContactRequest :: DB.Connection -> User -> GroupId -> IO (Maybe UserContactRequest) +getBusinessContactRequest db User {userId} groupId = + maybeFirstRow toContactRequest $ + DB.query db (contactRequestQuery <> " WHERE cr.user_id = ? AND cr.business_group_id = ?") (userId, groupId) + +contactRequestQuery :: Query +contactRequestQuery = + [sql| + SELECT + cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, + cr.contact_id, cr.business_group_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, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.created_at, cr.updated_at, + cr.peer_chat_min_version, cr.peer_chat_max_version + FROM contact_requests cr + JOIN connections c USING (user_contact_link_id) + JOIN contact_profiles p USING (contact_profile_id) + |] getContactRequestIdByName :: DB.Connection -> UserId -> ContactName -> ExceptT StoreError IO Int64 getContactRequestIdByName db userId cName = @@ -807,7 +818,7 @@ getContact_ db vr user@User {userId} contactId deleted = do SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.contact_request_id, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index e9462988df..ecd7dbe828 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -937,7 +937,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, @@ -1291,7 +1291,7 @@ createBusinessRequestGroup gVar user@User {userId, userContactId} cReqChatVRange - Profile {displayName, fullName, image, contactLink, preferences} + Profile {displayName, fullName, image} profileId -- contact request profile id, to be used for member profile ldn -- contact request local display name, to be used for group local display name groupPreferences = do @@ -1813,7 +1813,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupInfo {membership} diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index bc18f5e7f3..cf60d32b0f 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -441,9 +441,8 @@ data GroupLinkInfo = GroupLinkInfo data AddressSettings = AddressSettings { businessAddress :: Bool, -- possibly, it can be wrapped together with acceptIncognito, or AutoAccept made sum type - welcomeMessage :: Maybe Text, -- included in short link information autoAccept :: Maybe AutoAccept, -- accept automatically - autoReply :: Maybe MsgContent -- sent on acceptance, can be supported with manual acceptance as well + autoReply :: Maybe MsgContent -- included in short link information, sent on acceptance in case wasn't shown during connection } deriving (Eq, Show) @@ -458,11 +457,11 @@ $(J.deriveJSON defaultJSON ''AddressSettings) $(J.deriveJSON defaultJSON ''UserContactLink) -toUserContactLink :: (Int64, ConnReqContact, Maybe ShortLinkContact, BoolInt, BoolInt, Maybe Text, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink -toUserContactLink (userContactLinkId, connReq, shortLink, BI shortLinkDataSet, BI businessAddress, welcomeMessage, BI autoAccept', BI acceptIncognito, autoReply) = +toUserContactLink :: (Int64, ConnReqContact, Maybe ShortLinkContact, BoolInt, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink +toUserContactLink (userContactLinkId, connReq, shortLink, BI shortLinkDataSet, BI businessAddress, BI autoAccept', BI acceptIncognito, autoReply) = UserContactLink userContactLinkId (CCLink connReq shortLink) shortLinkDataSet $ let autoAccept = if autoAccept' then Just AutoAccept {acceptIncognito} else Nothing - in AddressSettings {businessAddress, welcomeMessage, autoAccept, autoReply} + in AddressSettings {businessAddress, autoAccept, autoReply} getUserAddress :: DB.Connection -> User -> ExceptT StoreError IO UserContactLink getUserAddress db User {userId} = @@ -475,7 +474,7 @@ getUserContactLinkById db userId userContactLinkId = DB.query db [sql| - SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, address_welcome_message, auto_accept, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role + SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, auto_accept, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? |] @@ -511,7 +510,7 @@ getUserContactLinkViaShortLink db User {userId} shortLink = userContactLinkQuery :: Query userContactLinkQuery = [sql| - SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, address_welcome_message, auto_accept, auto_accept_incognito, auto_reply_msg_content + SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, auto_accept, auto_accept_incognito, auto_reply_msg_content FROM user_contact_links |] @@ -561,15 +560,15 @@ getContactWithoutConnViaShortAddress db vr user@User {userId} shortLink = do maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db vr user) ctId_ updateUserAddressSettings :: DB.Connection -> Int64 -> AddressSettings -> IO () -updateUserAddressSettings db userContactLinkId AddressSettings {businessAddress, welcomeMessage, autoAccept, autoReply} = +updateUserAddressSettings db userContactLinkId AddressSettings {businessAddress, autoAccept, autoReply} = DB.execute db [sql| UPDATE user_contact_links - SET auto_accept = ?, auto_accept_incognito = ?, business_address = ?, address_welcome_message = ?, auto_reply_msg_content = ? + SET auto_accept = ?, auto_accept_incognito = ?, business_address = ?, auto_reply_msg_content = ? WHERE user_contact_link_id = ? |] - (autoAcceptValues :. (businessAddress, welcomeMessage, autoReply, userContactLinkId)) + (autoAcceptValues :. (businessAddress, autoReply, userContactLinkId)) where autoAcceptValues = case autoAccept of Just AutoAccept {acceptIncognito} -> (BI True, BI acceptIncognito) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250526_short_links.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250526_short_links.hs index a2fa9c3d11..9da184f581 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20250526_short_links.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250526_short_links.hs @@ -11,6 +11,7 @@ m20250526_short_links = ALTER TABLE contacts ADD COLUMN conn_full_link_to_connect BLOB; ALTER TABLE contacts ADD COLUMN conn_short_link_to_connect BLOB; ALTER TABLE contacts ADD COLUMN welcome_shared_msg_id BLOB; +ALTER TABLE contacts ADD COLUMN request_shared_msg_id BLOB; ALTER TABLE contacts ADD COLUMN contact_request_id INTEGER REFERENCES contact_requests ON DELETE SET NULL; CREATE INDEX idx_contacts_contact_request_id ON contacts(contact_request_id); @@ -23,12 +24,12 @@ ALTER TABLE contact_requests ADD COLUMN request_shared_msg_id BLOB; ALTER TABLE group_members ADD COLUMN member_xcontact_id BLOB; ALTER TABLE user_contact_links ADD COLUMN short_link_data_set INTEGER NOT NULL DEFAULT 0; -ALTER TABLE user_contact_links ADD COLUMN address_welcome_message TEXT; ALTER TABLE groups ADD COLUMN conn_full_link_to_connect BLOB; ALTER TABLE groups ADD COLUMN conn_short_link_to_connect BLOB; ALTER TABLE groups ADD COLUMN conn_link_started_connection INTEGER NOT NULL DEFAULT 0; ALTER TABLE groups ADD COLUMN welcome_shared_msg_id BLOB; +ALTER TABLE groups ADD COLUMN request_shared_msg_id BLOB; ALTER TABLE chat_items ADD COLUMN show_group_as_sender INTEGER NOT NULL DEFAULT 0; |] @@ -39,6 +40,7 @@ down_m20250526_short_links = ALTER TABLE contacts DROP COLUMN conn_full_link_to_connect; ALTER TABLE contacts DROP COLUMN conn_short_link_to_connect; ALTER TABLE contacts DROP COLUMN welcome_shared_msg_id; +ALTER TABLE contacts DROP COLUMN request_shared_msg_id; DROP INDEX idx_contacts_contact_request_id; ALTER TABLE contacts DROP COLUMN contact_request_id; @@ -51,12 +53,12 @@ ALTER TABLE contact_requests DROP COLUMN request_shared_msg_id; ALTER TABLE group_members DROP COLUMN member_xcontact_id; ALTER TABLE user_contact_links DROP COLUMN short_link_data_set; -ALTER TABLE user_contact_links DROP COLUMN address_welcome_message; ALTER TABLE groups DROP COLUMN conn_full_link_to_connect; ALTER TABLE groups DROP COLUMN conn_short_link_to_connect; ALTER TABLE groups DROP COLUMN conn_link_started_connection; ALTER TABLE groups DROP COLUMN welcome_shared_msg_id; +ALTER TABLE groups DROP COLUMN request_shared_msg_id; ALTER TABLE chat_items DROP COLUMN show_group_as_sender; |] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index a258eac2ab..4a3aea95ba 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -63,7 +63,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupInfo {membership} @@ -197,7 +197,7 @@ Query: SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.contact_request_id, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, @@ -384,7 +384,7 @@ Plan: Query: SELECT c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, - p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.contact_request_id, + p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, c.contact_group_member_id, c.contact_grp_inv_sent, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl FROM contacts c JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id @@ -920,7 +920,7 @@ Query: SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.contact_request_id, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, @@ -945,7 +945,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupInfo {membership} @@ -995,7 +995,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, @@ -1504,7 +1504,7 @@ Query: SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.contact_request_id, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, @@ -1692,25 +1692,6 @@ SEARCH cr USING INDEX idx_contact_requests_updated_at (user_id=?) SEARCH p USING INTEGER PRIMARY KEY (rowid=?) SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?) -Query: - SELECT - cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, - cr.contact_id, cr.business_group_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, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, - cr.created_at, cr.updated_at, - cr.peer_chat_min_version, cr.peer_chat_max_version - FROM contact_requests cr - JOIN connections c USING (user_contact_link_id) - JOIN contact_profiles p USING (contact_profile_id) - WHERE cr.user_id = ? - AND cr.contact_request_id = ? - -Plan: -SEARCH cr USING INTEGER PRIMARY KEY (rowid=?) -SEARCH p USING INTEGER PRIMARY KEY (rowid=?) -SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?) - Query: SELECT created_at, updated_at, chat_ts, favorite, unread_chat @@ -3511,7 +3492,7 @@ Plan: SEARCH usage_conditions USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, address_welcome_message, auto_accept, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role + SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, auto_accept, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? @@ -4620,7 +4601,7 @@ SEARCH server_operators USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE user_contact_links - SET auto_accept = ?, auto_accept_incognito = ?, business_address = ?, address_welcome_message = ?, auto_reply_msg_content = ? + SET auto_accept = ?, auto_accept_incognito = ?, business_address = ?, auto_reply_msg_content = ? WHERE user_contact_link_id = ? Plan: @@ -4686,7 +4667,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupMember - membership @@ -4712,7 +4693,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupMember - membership @@ -4732,6 +4713,40 @@ SEARCH gp USING INTEGER PRIMARY KEY (rowid=?) SEARCH mu USING INDEX idx_group_members_contact_id (contact_id=?) SEARCH pu USING INTEGER PRIMARY KEY (rowid=?) +Query: + SELECT + cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, + cr.contact_id, cr.business_group_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, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.created_at, cr.updated_at, + cr.peer_chat_min_version, cr.peer_chat_max_version + FROM contact_requests cr + JOIN connections c USING (user_contact_link_id) + JOIN contact_profiles p USING (contact_profile_id) + WHERE cr.user_id = ? AND cr.business_group_id = ? +Plan: +SEARCH cr USING INDEX idx_contact_requests_business_group_id (business_group_id=?) +SEARCH p USING INTEGER PRIMARY KEY (rowid=?) +SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?) + +Query: + SELECT + cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, + cr.contact_id, cr.business_group_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, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.created_at, cr.updated_at, + cr.peer_chat_min_version, cr.peer_chat_max_version + FROM contact_requests cr + JOIN connections c USING (user_contact_link_id) + JOIN contact_profiles p USING (contact_profile_id) + WHERE cr.user_id = ? AND cr.contact_request_id = ? +Plan: +SEARCH cr USING INTEGER PRIMARY KEY (rowid=?) +SEARCH p USING INTEGER PRIMARY KEY (rowid=?) +SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?) + Query: SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, @@ -5171,21 +5186,21 @@ Plan: SCAN usage_conditions Query: - SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, address_welcome_message, auto_accept, auto_accept_incognito, auto_reply_msg_content + SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, auto_accept, auto_accept_incognito, auto_reply_msg_content FROM user_contact_links WHERE user_id = ? AND conn_req_contact IN (?,?) Plan: SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?) Query: - SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, address_welcome_message, auto_accept, auto_accept_incognito, auto_reply_msg_content + SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, auto_accept, auto_accept_incognito, auto_reply_msg_content FROM user_contact_links WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL Plan: SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?) Query: - SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, address_welcome_message, auto_accept, auto_accept_incognito, auto_reply_msg_content + SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, business_address, auto_accept, auto_accept_incognito, auto_reply_msg_content FROM user_contact_links WHERE user_id = ? AND short_link_contact = ? Plan: @@ -6038,6 +6053,10 @@ Query: UPDATE contacts SET local_display_name = ?, updated_at = ? WHERE user_id Plan: SEARCH contacts USING INTEGER PRIMARY KEY (rowid=?) +Query: UPDATE contacts SET request_shared_msg_id = ? WHERE contact_id = ? +Plan: +SEARCH contacts USING INTEGER PRIMARY KEY (rowid=?) + Query: UPDATE contacts SET send_rcpts = NULL Plan: SCAN contacts diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 78207bc64f..b95d6f7772 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -82,6 +82,7 @@ CREATE TABLE contacts( conn_full_link_to_connect BLOB, conn_short_link_to_connect BLOB, welcome_shared_msg_id BLOB, + request_shared_msg_id BLOB, contact_request_id INTEGER REFERENCES contact_requests ON DELETE SET NULL, FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) @@ -143,7 +144,8 @@ CREATE TABLE groups( conn_full_link_to_connect BLOB, conn_short_link_to_connect BLOB, conn_link_started_connection INTEGER NOT NULL DEFAULT 0, - welcome_shared_msg_id BLOB, -- received + welcome_shared_msg_id BLOB, + request_shared_msg_id BLOB, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -336,7 +338,6 @@ CREATE TABLE user_contact_links( business_address INTEGER DEFAULT 0, short_link_contact BLOB, short_link_data_set INTEGER NOT NULL DEFAULT 0, - address_welcome_message TEXT, UNIQUE(user_id, local_display_name) ); CREATE TABLE contact_requests( diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index afd7915256..92e9361426 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -431,7 +431,7 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId = |] (userId, profileId, userId, profileId, userId, profileId) -type PreparedContactRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink, Maybe SharedMsgId) +type PreparedContactRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink, Maybe SharedMsgId, Maybe SharedMsgId) type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) @@ -448,8 +448,9 @@ toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, via 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 :: PreparedContactRow -> Maybe PreparedContact -toPreparedContact (connFullLink, connShortLink, welcomeSharedMsgId) = - (\cl@(ACCL m _) -> PreparedContact cl (connMode m) welcomeSharedMsgId) <$> toACreatedConnLink_ connFullLink connShortLink +toPreparedContact (connFullLink, connShortLink, welcomeSharedMsgId, requestSharedMsgId) = + (\cl@(ACCL m _) -> PreparedContact {connLinkToConnect = cl, uiConnLinkType = connMode m, welcomeSharedMsgId, requestSharedMsgId}) + <$> toACreatedConnLink_ connFullLink connShortLink toACreatedConnLink_ :: Maybe AConnectionRequestUri -> Maybe AConnShortLink -> Maybe ACreatedConnLink toACreatedConnLink_ Nothing _ = Nothing @@ -604,7 +605,7 @@ safeDeleteLDN db User {userId} localDisplayName = do |] (userId, localDisplayName, userId) -type PreparedGroupRow = (Maybe ConnReqContact, Maybe ShortLinkContact, BoolInt, Maybe SharedMsgId) +type PreparedGroupRow = (Maybe ConnReqContact, Maybe ShortLinkContact, BoolInt, Maybe SharedMsgId, Maybe SharedMsgId) type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId) @@ -624,8 +625,8 @@ toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, toPreparedGroup :: PreparedGroupRow -> Maybe PreparedGroup toPreparedGroup = \case - (Just fullLink, shortLink_, BI connLinkStartedConnection, welcomeSharedMsgId) -> - Just PreparedGroup {connLinkToConnect = CCLink fullLink shortLink_, connLinkStartedConnection, welcomeSharedMsgId} + (Just fullLink, shortLink_, BI connLinkStartedConnection, welcomeSharedMsgId, requestSharedMsgId) -> + Just PreparedGroup {connLinkToConnect = CCLink fullLink shortLink_, connLinkStartedConnection, welcomeSharedMsgId, requestSharedMsgId} _ -> Nothing toGroupMember :: Int64 -> GroupMemberRow -> GroupMember @@ -660,7 +661,7 @@ groupInfoQuery = g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupMember - membership diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index c9059dace7..eb61988f8a 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -207,7 +207,8 @@ contactRequestId' Contact {contactRequestId} = contactRequestId data PreparedContact = PreparedContact { connLinkToConnect :: ACreatedConnLink, uiConnLinkType :: ConnectionMode, - welcomeSharedMsgId :: Maybe SharedMsgId + welcomeSharedMsgId :: Maybe SharedMsgId, + requestSharedMsgId :: Maybe SharedMsgId } deriving (Eq, Show) @@ -404,8 +405,15 @@ data RequestEntity type RepeatRequest = Bool data RequestStage - = RSAcceptedRequest (Maybe UserContactRequest) RequestEntity -- Optional request is for legacy deleted requests - | RSCurrentRequest UserContactRequest (Maybe RequestEntity) RepeatRequest -- Optional entity is for legacy requests without entity + = RSAcceptedRequest + { acceptedRequest :: Maybe UserContactRequest, -- Request is optional to support deleted legacy requests + requestEntity :: RequestEntity + } + | RSCurrentRequest + { previousRequest :: Maybe UserContactRequest, + currentRequest :: UserContactRequest, + requestEntity_ :: Maybe RequestEntity -- Entity is optional to support legacy requests without entity + } type UserName = Text @@ -488,7 +496,8 @@ instance ToField BusinessChatType where toField = toField . textEncode data PreparedGroup = PreparedGroup { connLinkToConnect :: CreatedLinkContact, connLinkStartedConnection :: Bool, - welcomeSharedMsgId :: Maybe SharedMsgId -- it is stored only for business chats, and only if welcome message is specified + welcomeSharedMsgId :: Maybe SharedMsgId, -- it is stored only for business chats, and only if welcome message is specified + requestSharedMsgId :: Maybe SharedMsgId } deriving (Eq, Show) @@ -715,18 +724,6 @@ instance ToField ImageData where toField (ImageData t) = toField t deriving newtype instance FromField ImageData -data ContactShortLinkData = ContactShortLinkData - { profile :: Profile, - message :: Maybe Text, - business :: Bool - } - deriving (Show) - -data GroupShortLinkData = GroupShortLinkData - { groupProfile :: GroupProfile - } - deriving (Show) - data CReqClientData = CRDataGroup {groupLinkId :: GroupLinkId} newtype GroupLinkId = GroupLinkId {unGroupLinkId :: ByteString} -- used to identify invitation via group link @@ -1998,10 +1995,6 @@ instance FromField MsgFilter where fromField = fromIntField_ msgFilterIntP instance ToField MsgFilter where toField = toField . msgFilterInt -$(JQ.deriveJSON defaultJSON ''ContactShortLinkData) - -$(JQ.deriveJSON defaultJSON ''GroupShortLinkData) - $(JQ.deriveJSON defaultJSON ''CReqClientData) $(JQ.deriveJSON defaultJSON ''MemberIdRole) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 8602e4670c..0f5ec0345e 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1084,7 +1084,7 @@ simplexChatContact' = \case -- TODO [short links] show all settings viewAddressSettings :: AddressSettings -> [StyledString] -viewAddressSettings AddressSettings {businessAddress, welcomeMessage = _, autoAccept, autoReply} = case autoAccept of +viewAddressSettings AddressSettings {businessAddress, autoAccept, autoReply} = case autoAccept of Just AutoAccept {acceptIncognito} -> ("auto_accept on" <> aaInfo) : maybe [] ((["auto reply:"] <>) . ttyMsgContent) autoReply diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 1843124084..1c14868485 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -3662,8 +3662,9 @@ testShortLinkAddressChangeAutoReply = alice <# "bob> hello" alice <## "bob (Bob): accepting contact request..." alice <## "bob (Bob): you can send messages to contact" - alice <# "@bob welcome!" -- auto reply - bob <# "alice> welcome!" + -- welcome messages, not sent as events + -- alice <# "@bob welcome!" + -- bob <# "alice> welcome!" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected")